diff --git a/main.py b/main.py index 535f0e5..ecda455 100644 --- a/main.py +++ b/main.py @@ -77,6 +77,20 @@ def build_ffmpeg_command( return cmd +def build_audio_extract_command(input_path: str, start: float, sequence_dir: str) -> list[str]: + """Return an ffmpeg command that extracts audio to .wav.""" + audio_path = sequence_dir + ".wav" + return [ + "ffmpeg", "-y", + "-ss", str(start), + "-i", input_path, + "-t", "8", + "-vn", + "-c:a", "pcm_s16le", + audio_path, + ] + + def build_mask_output_dir(video_path: str) -> str: """Return path of mask output directory: _masks/ next to the video.""" p = Path(video_path) @@ -232,6 +246,13 @@ class ExportWorker(QThread): ) result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode == 0: + if self._image_sequence: + audio_cmd = build_audio_extract_command( + self._input, self._start, self._output + ) + subprocess.run(audio_cmd, capture_output=True, text=True, timeout=60) + # Audio extraction failure (e.g. no audio stream) is ignored — + # the frame sequence is the primary output. self.finished.emit(self._output) else: self.error.emit(result.stderr[-500:]) diff --git a/tests/test_utils.py b/tests/test_utils.py index cc33fb1..dc100fa 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,5 +1,5 @@ import tempfile, os -from main import build_export_path, format_time, build_ffmpeg_command, build_mask_output_dir, build_sequence_dir +from main import build_export_path, format_time, build_ffmpeg_command, build_mask_output_dir, build_sequence_dir, build_audio_extract_command from main import _normalize_filename, ProcessedDB @@ -184,6 +184,29 @@ def test_mask_output_dir_nested(): assert build_mask_output_dir("/a/b/c/shot_042.mp4") == "/a/b/c/shot_042_masks" +# --- build_audio_extract_command --- + +def test_audio_extract_output_path(): + cmd = build_audio_extract_command("/in/v.mp4", 0.0, "/out/clip_001") + assert cmd[-1] == "/out/clip_001.wav" + +def test_audio_extract_no_video(): + cmd = build_audio_extract_command("/in/v.mp4", 0.0, "/out/clip_001") + assert "-vn" in cmd + +def test_audio_extract_lossless_codec(): + cmd = build_audio_extract_command("/in/v.mp4", 0.0, "/out/clip_001") + assert "-c:a" in cmd + assert cmd[cmd.index("-c:a") + 1] == "pcm_s16le" + +def test_audio_extract_timing(): + cmd = build_audio_extract_command("/in/v.mp4", 12.5, "/out/clip_001") + assert "-ss" in cmd + assert cmd[cmd.index("-ss") + 1] == "12.5" + assert "-t" in cmd + assert cmd[cmd.index("-t") + 1] == "8" + + def test_build_sequence_dir_basic(): assert build_sequence_dir("/out", "clip", 1) == "/out/clip_001"