Files
mitus/tests/test_ffmpeg.py
2026-04-01 16:26:25 -03:00

112 lines
3.6 KiB
Python

"""Tests for cht.stream.ffmpeg — pipeline construction and execution."""
import signal
import subprocess
from unittest.mock import patch, MagicMock
import pytest
import ffmpeg as ffmpeg_lib
from cht.stream import ffmpeg as ff
class TestReceiveAndRecord:
def test_compiles_to_valid_cmd(self, tmp_path):
node = ff.receive_and_record(
"tcp://0.0.0.0:4444?listen", tmp_path / "recording.ts"
)
cmd = node.compile()
cmd_str = " ".join(str(c) for c in cmd)
assert cmd[0] == "ffmpeg"
assert "tcp://0.0.0.0:4444?listen" in cmd_str
assert "recording.ts" in cmd_str
def test_input_has_low_latency_flags(self, tmp_path):
node = ff.receive_and_record(
"tcp://0.0.0.0:4444?listen", tmp_path / "rec.ts"
)
cmd_str = " ".join(str(c) for c in node.compile())
assert "nobuffer" in cmd_str
assert "low_delay" in cmd_str
def test_copy_codec(self, tmp_path):
node = ff.receive_and_record(
"tcp://0.0.0.0:4444?listen", tmp_path / "rec.ts"
)
assert "copy" in node.compile()
def test_has_global_args(self, tmp_path):
node = ff.receive_and_record(
"tcp://0.0.0.0:4444?listen", tmp_path / "rec.ts"
)
assert "-hide_banner" in node.compile()
def test_mpegts_format(self, tmp_path):
node = ff.receive_and_record(
"tcp://0.0.0.0:4444?listen", tmp_path / "rec.ts"
)
assert "mpegts" in node.compile()
class TestExtractSceneFrames:
def test_compiles_select_filter(self, tmp_path):
stream = ffmpeg_lib.input(str(tmp_path / "test.ts"))
stream = stream.filter("select", "gt(scene,0.3)+gte(t-prev_selected_t,30)")
stream = stream.filter("showinfo")
output = ffmpeg_lib.output(
stream,
str(tmp_path / "F%04d.jpg"),
vsync="vfr",
**{"q:v": "2"},
start_number=1,
)
cmd_str = " ".join(str(c) for c in output.compile())
assert "select" in cmd_str
assert "scene" in cmd_str
assert "showinfo" in cmd_str
assert "vfr" in cmd_str
def test_returns_decoded_strings(self, tmp_path):
mock_proc = MagicMock()
mock_proc.communicate.return_value = (b"out", b"err")
mock_proc.poll.return_value = 0
with patch("ffmpeg._run.run_async", return_value=mock_proc):
stdout, stderr = ff.extract_scene_frames(
tmp_path / "test.ts", tmp_path,
)
assert stdout == "out"
assert stderr == "err"
class TestRunAsync:
def test_compiles_valid_command(self, tmp_path):
node = ff.receive_and_record("tcp://0.0.0.0:4444?listen", tmp_path / "rec.ts")
cmd = node.compile()
assert cmd[0] == "ffmpeg"
assert "tcp://0.0.0.0:4444?listen" in " ".join(cmd)
class TestStopProc:
def test_sends_sigint_then_waits(self):
proc = MagicMock()
proc.poll.return_value = None
ff.stop_proc(proc, timeout=3)
proc.send_signal.assert_called_once_with(signal.SIGINT)
proc.wait.assert_called_once_with(timeout=3)
def test_kills_on_timeout(self):
proc = MagicMock()
proc.poll.return_value = None
proc.wait.side_effect = subprocess.TimeoutExpired("ffmpeg", 3)
ff.stop_proc(proc, timeout=3)
proc.kill.assert_called_once()
def test_noop_if_already_exited(self):
proc = MagicMock()
proc.poll.return_value = 0
ff.stop_proc(proc)
proc.send_signal.assert_not_called()
def test_noop_if_none(self):
ff.stop_proc(None)