#!/usr/bin/env -S uv run --script # /// script # dependencies = ["pytest"] # /// import json import shutil import subprocess import sys from pathlib import Path import pytest import ffmpeg_remove_still as script @pytest.mark.parametrize("log, expected", [ ("", []), ("lavfi.freezedetect.freeze_start: 0", [(0.0, 10.0)]), ("lavfi.freezedetect.freeze_start: 2\nlavfi.freezedetect.freeze_end: 5", [(2.0, 5.0)]), ("lavfi.freezedetect.freeze_start: 2\nlavfi.freezedetect.freeze_end: 5\n" "lavfi.freezedetect.freeze_start: 8", [(2.0, 5.0), (8.0, 10.0)]), ]) def test_parse_freezes(log: str, expected: list[tuple[float, float]]) -> None: assert script.parse_freezes(log=log, duration=10.0) == expected @pytest.mark.parametrize("freezes, keep_still, keyframes, expected", [ ([], 1.0, [0.0], [(0.0, 10.0)]), ([(0.0, 10.0)], 1.0, [0.0, 0.5, 9.5], [(0.0, 0.5), (9.5, 10.0)]), ([(2.0, 6.0)], 2.0, [0.0, 3.0, 5.0], [(0.0, 3.0), (5.0, 10.0)]), ([(2.0, 3.0)], 2.0, [0.0, 2.5], [(0.0, 10.0)]), ([(0.0, 4.0), (6.0, 10.0)], 1.0, [0.0, 0.5, 3.5, 6.5, 9.5], [(0.0, 0.5), (3.5, 6.5), (9.5, 10.0)]), ([(2.0, 6.0)], 1.0, [0.0, 3.0, 5.0, 7.0], [(0.0, 3.0), (5.0, 10.0)]), ([(2.0, 6.0)], 1.0, [0.0, 4.0, 8.0], [(0.0, 10.0)]), ([(2.0, 6.0)], 1.0, [0.0, 8.0], [(0.0, 10.0)]), ]) def test_plan_segments(freezes: list[tuple[float, float]], keep_still: float, keyframes: list[float], expected: list[tuple[float, float]]) -> None: assert script.plan_segments(duration=10.0, freezes=freezes, keep_still=keep_still, keyframes=keyframes) == expected @pytest.mark.skipif(not shutil.which(cmd="ffmpeg") or not shutil.which(cmd="ffprobe"), reason="Requires FFmpeg") @pytest.mark.parametrize("has_audio, keep_still, keyframe_interval, expected_duration", [ (False, 0.5, 5, 4.0), (True, 0.5, 5, 4.0), (True, 1.5, 5, 6.0), (True, 0.5, 100, 8.0), ]) def test_copies_packets_without_encoding(tmp_path: Path, has_audio: bool, keep_still: float, keyframe_interval: int, expected_duration: float) -> None: source = tmp_path / "demo's recording.mp4" output = tmp_path / "short.mp4" command = ["ffmpeg", "-hide_banner", "-loglevel", "error", "-nostdin"] for pattern in ["testsrc2=size=64x64:rate=10:duration=1", "color=red:size=64x64:rate=10:duration=3", "testsrc2=size=64x64:rate=10:duration=1", "color=blue:size=64x64:rate=10:duration=3"]: command.extend(["-f", "lavfi", "-i", pattern]) if has_audio: command.extend(["-f", "lavfi", "-i", "sine=frequency=440:sample_rate=48000:duration=8"]) command.extend(["-filter_complex", "[0:v][1:v][2:v][3:v]concat=n=4:v=1:a=0[v]", "-map", "[v]"]) if has_audio: command.extend(["-map", "4:a", "-c:a", "aac"]) command.extend(["-c:v", "libx264", "-g", str(keyframe_interval), "-keyint_min", str(keyframe_interval), "-sc_threshold", "0", "-bf", "2", "-flags", "+cgop", str(source)]) subprocess.run(args=command, check=True, capture_output=True) copied = subprocess.run( args=[sys.executable, str(Path(script.__file__).resolve()), str(source), "--output", str(output), "--min-still", "2", "--keep-still", str(keep_still)], check=True, capture_output=True, text=True, ) assert "non-monoton" not in copied.stderr.lower() result = subprocess.run( args=["ffprobe", "-v", "error", "-show_streams", "-show_format", "-of", "json", str(output)], check=True, capture_output=True, text=True, ) metadata = json.loads(result.stdout) video = next(stream for stream in metadata["streams"] if stream["codec_type"] == "video") assert float(video["duration"]) == pytest.approx(expected_duration, abs=0.15) audio = [stream for stream in metadata["streams"] if stream["codec_type"] == "audio"] assert bool(audio) == has_audio if has_audio: assert float(audio[0]["duration"]) == pytest.approx(float(video["duration"]), abs=0.1) packet_hashes = [] for path in (source, output): packets = subprocess.run( args=["ffprobe", "-v", "error", "-show_packets", "-show_data_hash", "sha256", "-show_entries", "packet=stream_index,data_hash", "-of", "json", str(path)], check=True, capture_output=True, text=True, ) packet_hashes.append({(packet["stream_index"], packet["data_hash"]) for packet in json.loads(packets.stdout)["packets"]}) assert packet_hashes[1] <= packet_hashes[0] decoded = subprocess.run( args=["ffmpeg", "-v", "error", "-i", str(output), "-f", "null", "-"], check=True, capture_output=True, text=True, ) assert decoded.stderr == "" original = source.read_bytes() refused = subprocess.run( args=[sys.executable, str(Path(script.__file__).resolve()), str(source), "--output", str(source), "--overwrite"], capture_output=True, text=True, ) assert refused.returncode == 1 assert source.read_bytes() == original if __name__ == "__main__": sys.exit(pytest.main(args=[str(Path(__file__).resolve()), *sys.argv[1:]]))