backend/tests/test_ffsubsync_shift_parser.py
Rama master · réplica de lectura del repositorio original.
"""One ffsubsync shift parser, and no silent 0 ms on unparseable output.
The legacy wrapper (``services.video_sync``) and the engine class
(``services.sync_engines.ffsubsync_engine``) each had their own parser with
different patterns, and both returned 0 when no offset line was found. A run
whose output carried no offset then "succeeded" with no shift: the sidecar
was rewritten unchanged and booked as synced.
"""
from unittest.mock import MagicMock, patch
import pytest
from services.sync_engines.ffsubsync_output import parse_ffsubsync_shift
REAL_LINE = "[13:51:53] INFO offset seconds: 22.960 ffsubsync.py:189"
class TestParser:
@pytest.mark.parametrize(
("output", "expected"),
[
("INFO offset seconds: 22.960", 22960),
(REAL_LINE, 22960),
(" INFO offset seconds: -0.990 ffsubsync.py:189", -990),
("offset seconds: 0.000", 0),
("offset: 1.234 s applied", 1234),
("Offset: -0.5 s", -500),
("estimated shift: 0.42s", 420),
("offset -1.2s detected", -1200),
("offset seconds: 1.001", 1001),
],
)
def test_known_formats(self, output, expected):
assert parse_ffsubsync_shift(output) == expected
@pytest.mark.parametrize(
"output",
["", "no offset info", "INFO extracting speech segments...", "max_offset_seconds: 60"],
)
def test_no_offset_line_is_none_not_zero(self, output):
assert parse_ffsubsync_shift(output) is None
def test_both_modules_use_the_shared_parser(self):
from services import video_sync
from services.sync_engines import ffsubsync_engine
assert video_sync._parse_ffsubsync_shift is parse_ffsubsync_shift
assert ffsubsync_engine._parse_ffsubsync_shift is parse_ffsubsync_shift
ORIGINAL = "1\n00:00:01,000 --> 00:00:02,000\nHello\n"
def test_legacy_wrapper_fails_without_an_offset_line(tmp_path):
from services.video_sync import sync_with_ffsubsync
sub = tmp_path / "ep.srt"
sub.write_text(ORIGINAL)
fake = MagicMock(returncode=0, stderr="INFO computing alignments...", stdout="")
with (
patch("services.video_sync.shutil.which", return_value="/usr/bin/ffsubsync"),
patch("services.video_sync.subprocess.run", return_value=fake),
patch("config.get_settings", return_value=MagicMock(sync_sanity_threshold_ms=45_000)),
pytest.raises(RuntimeError, match="no offset"),
):
sync_with_ffsubsync(str(sub), "/video.mkv")
assert sub.read_text() == ORIGINAL
def test_legacy_wrapper_keeps_a_real_zero_offset(tmp_path):
from services.video_sync import sync_with_ffsubsync
sub = tmp_path / "ep.srt"
sub.write_text(ORIGINAL)
fake = MagicMock(returncode=0, stderr="INFO offset seconds: 0.000", stdout="")
with (
patch("services.video_sync.shutil.which", return_value="/usr/bin/ffsubsync"),
patch("services.video_sync.subprocess.run", return_value=fake),
patch("config.get_settings", return_value=MagicMock(sync_sanity_threshold_ms=45_000)),
):
result = sync_with_ffsubsync(str(sub), "/video.mkv")
assert result["shift_ms"] == 0
def test_engine_reports_failure_without_an_offset_line(tmp_path):
from services.sync_engines.ffsubsync_engine import FfsubsyncEngine
sub = tmp_path / "ep.srt"
sub.write_text(ORIGINAL)
fake = MagicMock(returncode=0, stderr="INFO computing alignments...", stdout="")
with (
patch("services.sync_engines.ffsubsync_engine.shutil.which", return_value="ffsubsync"),
patch("services.sync_engines.ffsubsync_engine.subprocess.run", return_value=fake),
patch("services.sync_engines.ffsubsync_engine._fire_after_sync_trigger") as trigger,
):
result = FfsubsyncEngine().sync(str(sub), "/v.mkv")
assert result.ok is False
assert "no offset" in result.reason
assert sub.read_text() == ORIGINAL
trigger.assert_not_called()
def test_preview_fails_without_an_offset_line(tmp_path):
from services import sync_preview
sub = tmp_path / "ep.srt"
sub.write_text(ORIGINAL)
fake = MagicMock(returncode=0, stderr="nothing useful", stdout="")
with (
patch("services.sync_preview.shutil.which", return_value="/usr/bin/ffsubsync"),
patch.object(sync_preview, "_locked_subprocess", return_value=fake),
pytest.raises(RuntimeError, match="no offset"),
):
sync_preview._run_ffsubsync_preview(str(sub), "/v.mkv")