diff --git a/app/ffmpeg.py b/app/ffmpeg.py new file mode 100644 index 0000000..20fd1f3 --- /dev/null +++ b/app/ffmpeg.py @@ -0,0 +1,43 @@ +import shlex +from pathlib import Path + +# FFmpeg options that take a value (not exhaustive, covers common ones) +OPTIONS_WITH_VALUES = { + "-c", "-c:v", "-c:a", "-b", "-b:v", "-b:a", "-r", "-s", "-ar", "-ac", + "-f", "-t", "-ss", "-to", "-vf", "-af", "-filter:v", "-filter:a", + "-preset", "-crf", "-qp", "-profile", "-level", "-pix_fmt", "-map", + "-metadata", "-disposition", "-threads", "-filter_complex", +} + + +def parse_command(command: str) -> list[str]: + """Parse FFmpeg command string into argument list.""" + return shlex.split(command) + + +def resolve_paths(args: list[str], data_path: str) -> list[str]: + """Resolve relative paths against the data directory.""" + resolved = [] + skip_next = False + + for i, arg in enumerate(args): + if skip_next: + resolved.append(arg) + skip_next = False + continue + + # Check if this is an option that takes a value + if arg in OPTIONS_WITH_VALUES or arg.startswith("-"): + resolved.append(arg) + if arg in OPTIONS_WITH_VALUES: + skip_next = True + continue + + # This looks like a file path - resolve if relative + path = Path(arg) + if not path.is_absolute(): + resolved.append(str(Path(data_path) / arg)) + else: + resolved.append(arg) + + return resolved diff --git a/tests/test_ffmpeg.py b/tests/test_ffmpeg.py new file mode 100644 index 0000000..d9ec14c --- /dev/null +++ b/tests/test_ffmpeg.py @@ -0,0 +1,59 @@ +import pytest + +from app.ffmpeg import parse_command, resolve_paths + + +def test_parse_simple_command(): + command = "-i input.mp4 output.mp4" + + args = parse_command(command) + + assert args == ["-i", "input.mp4", "output.mp4"] + + +def test_parse_command_with_options(): + command = "-i input.mp4 -c:v libx264 -crf 23 output.mp4" + + args = parse_command(command) + + assert args == ["-i", "input.mp4", "-c:v", "libx264", "-crf", "23", "output.mp4"] + + +def test_parse_command_with_quotes(): + command = '-i "input file.mp4" output.mp4' + + args = parse_command(command) + + assert args == ["-i", "input file.mp4", "output.mp4"] + + +def test_resolve_paths(): + args = ["-i", "input/video.mp4", "-c:v", "libx264", "output/result.mp4"] + data_path = "/data" + + resolved = resolve_paths(args, data_path) + + assert resolved == [ + "-i", "/data/input/video.mp4", + "-c:v", "libx264", + "/data/output/result.mp4" + ] + + +def test_resolve_paths_preserves_absolute(): + args = ["-i", "/already/absolute.mp4", "output.mp4"] + data_path = "/data" + + resolved = resolve_paths(args, data_path) + + assert resolved == ["-i", "/already/absolute.mp4", "/data/output.mp4"] + + +def test_resolve_paths_skips_options(): + args = ["-c:v", "libx264", "-preset", "fast"] + data_path = "/data" + + resolved = resolve_paths(args, data_path) + + # Options and their values should not be resolved as paths + assert resolved == ["-c:v", "libx264", "-preset", "fast"]