glancetest: add a function to convert a video file to an MP3
- ID
bdb4a5d- date
2026-09-12 19:55:42+00:00- author
Alex Chan <alex@alexwlchan.net>- parent
b17f22e- message
glancetest: add a function to convert a video file to an MP3- changed files
3 files, 74 additions, 1 deletion
Changed files
glancecast.py (518 → 1363)
diff --git a/glancecast.py b/glancecast.py
index 50c76c3..7306709 100644
--- a/glancecast.py
+++ b/glancecast.py
@@ -6,8 +6,11 @@ This allows you to listen to a video in an audio-only podcast player like
Overcast, and glance at the screen if you need some visual context.
"""
+from pathlib import Path
+import subprocess
import shutil
import sys
+import tempfile
def ensure_tool_installed(name: str):
@@ -18,3 +21,31 @@ def ensure_tool_installed(name: str):
p = shutil.which(name)
if p is None:
sys.exit(f"missing required tool: {name}")
+
+
+def ffmpeg(*args: str | Path) -> None:
+ """
+ Run an ffmpeg command and ensure it succeeds.
+ """
+ ensure_tool_installed("ffmpeg")
+ cmd = ["ffmpeg"] + [str(a) for a in args]
+
+ # TODO: This dumps the ffmpeg output to stderr. Reduce it or suppress
+ # it unless the command fails.
+ subprocess.check_call(cmd)
+
+
+def convert_video_to_mp3(video_path: Path) -> Path:
+ """
+ Convert a video file to an MP3 file. Return the path to the new MP3 file.
+ """
+ if not video_path.exists():
+ raise FileNotFoundError(f"no video file found: {video_path}")
+
+ tmp_dir = Path(tempfile.mkdtemp())
+ mp3_path = tmp_dir / (video_path.stem + ".mp3")
+
+ # Example: `ffmpeg -i video.mp4 audio.mp3`
+ ffmpeg("-i", video_path, mp3_path)
+
+ return Path(mp3_path)
test_glancecast.py (622 → 1757)
diff --git a/test_glancecast.py b/test_glancecast.py
index ed85144..e7328d7 100644
--- a/test_glancecast.py
+++ b/test_glancecast.py
@@ -2,9 +2,13 @@
Tests for glancecast.
"""
+from pathlib import Path
+from subprocess import CalledProcessError
+
+from mutagen.mp3 import MP3
import pytest
-from glancecast import ensure_tool_installed
+from glancecast import convert_video_to_mp3, ensure_tool_installed
class TestEnsureToolInstalled:
@@ -24,3 +28,41 @@ class TestEnsureToolInstalled:
"""
with pytest.raises(SystemExit, match="missing required tool"):
ensure_tool_installed("does_not_exist")
+
+
+class TestConvertVideoToMP3:
+ """
+ Tests for `convert_video_to_mp3`.
+ """
+
+ def test_creates_mp3_file(self) -> None:
+ """
+ Convert a video file to an MP3, and check it's the expected length.
+ """
+ p = Path("tests/fixtures/lego_age_picker.mp4")
+
+ mp3_path = convert_video_to_mp3(p)
+ assert mp3_path.exists()
+ assert mp3_path.suffix == ".mp3"
+
+ mp3 = MP3(mp3_path)
+ assert mp3.info is not None
+ assert mp3.info.length == 19.536
+
+ def test_missing_video_file(self) -> None:
+ """
+ Trying to convert a non-existent video file is an error.
+ """
+ p = Path("does_not_exist.mkv")
+
+ with pytest.raises(FileNotFoundError):
+ convert_video_to_mp3(p)
+
+ def test_convert_non_video_file(self) -> None:
+ """
+ Trying to convert a non-video file is an error.
+ """
+ p = Path("README.md")
+
+ with pytest.raises(CalledProcessError):
+ convert_video_to_mp3(p)
tests/fixtures/lego_age_picker.mp4 (0 → 916048)
diff --git a/tests/fixtures/lego_age_picker.mp4 b/tests/fixtures/lego_age_picker.mp4
new file mode 100644
index 0000000..93f0606
Binary files /dev/null and b/tests/fixtures/lego_age_picker.mp4 differ