fix: clip pad ±3s + OSD-clock alignment at cut points (single-frame OCR)
ci / smoke (push) Canceled after 0s

This commit is contained in:
andrew committed 2026-09-28 16:33:49 +07:00
1 parent 110d557dbf
commit 14f5ed4aac
7 files changed
+314 -12

No files matched your search

+155 -2
View File
@@ -201,7 +201,8 @@ def test_clip_batch_single_piece_argv(monkeypatch, tmp_path):
calls, captured = [], {}
monkeypatch.setattr(clip, "_run_ffmpeg", fake_run_factory(calls, captured))
out = tmp_path / "sub" / "batch.mp4"
clip.clip_batch(BASE, 1, "2026-09-26T09:00:10", "2026-09-26T09:00:50", str(out))
clip.clip_batch(BASE, 1, "2026-09-26T09:00:10", "2026-09-26T09:00:50", str(out),
pad_seconds=0, align_osd=False)
assert len(calls) == 1
args, timeout = calls[0]
assert timeout == 180
@@ -229,7 +230,8 @@ def test_clip_batch_two_pieces_trim_and_concat(monkeypatch, tmp_path):
calls, captured = [], {}
monkeypatch.setattr(clip, "_run_ffmpeg", fake_run_factory(calls, captured))
out = tmp_path / "batch.mp4"
clip.clip_batch(BASE, 1, START_ISO, END_ISO, str(out), timeout=60)
clip.clip_batch(BASE, 1, START_ISO, END_ISO, str(out), timeout=60,
pad_seconds=0, align_osd=False)
assert len(calls) == 3
(piece_a, timeout_a), (piece_b, _), (concat_args, timeout_b) = calls
assert timeout_a == timeout_b == 60
@@ -273,3 +275,154 @@ def test_clip_batch_bad_iso_raises_cliperror(tmp_path):
with pytest.raises(ClipError):
clip.clip_batch(BASE, 1, "bukan-tanggal", "2026-09-26T10:00:00",
str(tmp_path / "out.mp4"))
# ------------------------------------------------------- pad + OSD alignment
@pytest.fixture(autouse=True)
def _clear_osd_cache():
clip._OSD_CACHE.clear()
yield
clip._OSD_CACHE.clear()
def _one_movie(monkeypatch, start_iso, duration):
movies = [{"path": CLIP_A, "start": dt("2026-09-26 09:00:00")}]
patch_batch_env(monkeypatch, movies, duration=duration)
def test_pad_extends_selection_window(monkeypatch, tmp_path):
# clip 09:00:00 + 5 s ends exactly at batch start → pad 0 finds nothing
movies = [{"path": CLIP_A, "start": dt("2026-09-26 09:00:00")}]
patch_batch_env(monkeypatch, movies, duration=5.0)
calls, captured = [], {}
monkeypatch.setattr(clip, "_run_ffmpeg", fake_run_factory(calls, captured))
with pytest.raises(ClipError, match="Tidak ada rekaman"):
clip.clip_batch(BASE, 1, "2026-09-26T09:00:05", "2026-09-26T09:00:10",
str(tmp_path / "a.mp4"), pad_seconds=0, align_osd=False)
clip.clip_batch(BASE, 1, "2026-09-26T09:00:05", "2026-09-26T09:00:10",
str(tmp_path / "b.mp4"), pad_seconds=3, align_osd=False)
args = calls[0][0]
assert args[args.index("-ss") + 1] == "2.000" # window_start 09:00:02
assert args[args.index("-to") + 1] == "5.000" # clip ends 09:00:05
def test_osd_alignment_corrects_start_offset(monkeypatch, tmp_path):
_one_movie(monkeypatch, None, 600.0)
calls, captured = [], {}
monkeypatch.setattr(clip, "_run_ffmpeg", fake_run_factory(calls, captured))
# batch 09:01:10→09:02:00 (pad 0): filename off0=70, off1=120.
# OSD at media 70 reads desired+14 → shift to 56; at 56 reads desired.
reads = {
70: dt("2026-09-26 09:01:24"),
56: dt("2026-09-26 09:01:10"),
120: dt("2026-09-26 09:02:00"),
}
def fake_osd(url, media_offset, timeout=45):
return reads.get(int(round(media_offset)))
monkeypatch.setattr(clip, "osd_time_at", fake_osd)
out = tmp_path / "out.mp4"
clip.clip_batch(BASE, 1, "2026-09-26T09:01:10", "2026-09-26T09:02:00",
str(out), pad_seconds=0, align_osd=True)
args = calls[0][0]
assert args[args.index("-ss") + 1] == "56.000"
assert args[args.index("-to") + 1] == "120.000"
def test_align_disabled_never_probes_osd(monkeypatch, tmp_path):
_one_movie(monkeypatch, None, 600.0)
calls, captured = [], {}
monkeypatch.setattr(clip, "_run_ffmpeg", fake_run_factory(calls, captured))
def boom(*a, **k):
raise AssertionError("osd_time_at must not be called")
monkeypatch.setattr(clip, "osd_time_at", boom)
clip.clip_batch(BASE, 1, "2026-09-26T09:00:10", "2026-09-26T09:00:50",
str(tmp_path / "o.mp4"), pad_seconds=0, align_osd=False)
args = calls[0][0]
assert args[args.index("-ss") + 1] == "10.000"
def test_osd_failure_keeps_filename_offsets(monkeypatch, tmp_path):
_one_movie(monkeypatch, None, 600.0)
calls, captured = [], {}
monkeypatch.setattr(clip, "_run_ffmpeg", fake_run_factory(calls, captured))
monkeypatch.setattr(clip, "osd_time_at", lambda *a, **k: None)
clip.clip_batch(BASE, 1, "2026-09-26T09:00:10", "2026-09-26T09:00:50",
str(tmp_path / "o.mp4"), pad_seconds=0, align_osd=True)
args = calls[0][0]
assert args[args.index("-ss") + 1] == "10.000"
assert args[args.index("-to") + 1] == "50.000"
def test_osd_alignment_probe_iteration_cap(monkeypatch, tmp_path):
_one_movie(monkeypatch, None, 600.0)
calls, captured = [], {}
monkeypatch.setattr(clip, "_run_ffmpeg", fake_run_factory(calls, captured))
probes = []
def always_off(url, media_offset, timeout=45):
probes.append(int(round(media_offset)))
return dt("2026-09-26 09:01:24") # always +14 vs desired 09:01:10
monkeypatch.setattr(clip, "osd_time_at", always_off)
clip.clip_batch(BASE, 1, "2026-09-26T09:01:10", "2026-09-26T09:02:00",
str(tmp_path / "o.mp4"), pad_seconds=0, align_osd=True)
# start align: at most 2 probes; end align: 2 more (same cap)
assert len(probes) <= 4
start_probes = [p for p in probes if p in (70, 56, 42)]
assert len(start_probes) <= 2
args = calls[0][0]
# after two corrections 70 -> 56 -> 42
assert args[args.index("-ss") + 1] == "42.000"
def test_osd_date_mismatch_corrects_once_then_stops(monkeypatch, tmp_path):
# batch spans New Year's Eve; OSD read lands on the wrong calendar day
movies = [{"path": "/2026-12-31/23-00-00.mp4",
"start": dt("2026-12-31 23:00:00")}]
monkeypatch.setattr(clip, "list_movies", lambda *a, **k: list(movies))
monkeypatch.setattr(clip, "probe_duration", lambda url, timeout=30: 7200.0)
calls, captured = [], {}
monkeypatch.setattr(clip, "_run_ffmpeg", fake_run_factory(calls, captured))
probes = []
def wrong_day(url, media_offset, timeout=45):
off = int(round(media_offset))
probes.append(off)
if off == 1924:
return dt("2027-01-01 00:32:04") # +1 h vs desired Dec31 23:32:04
if off == 0:
return dt("2027-01-01 00:31:50") # still Jan 1 after correction
return None # end-offset probes do nothing
monkeypatch.setattr(clip, "osd_time_at", wrong_day)
clip.clip_batch(BASE, 1, "2026-12-31T23:32:04", "2026-12-31T23:35:00",
str(tmp_path / "o.mp4"), pad_seconds=0, align_osd=True)
# start align: probe at 1924 → corrects (clamped to 0), probe at 0 has
# wrong date again → rejected by date check (2 probes max).
assert probes[:2] == [1924, 0]
args = calls[0][0]
assert args[args.index("-ss") + 1] == "0.000"
assert args[args.index("-to") + 1] == "2100.000" # end offset untouched
def test_alignment_span_collapse_falls_back(monkeypatch, tmp_path):
_one_movie(monkeypatch, None, 600.0)
calls, captured = [], {}
monkeypatch.setattr(clip, "_run_ffmpeg", fake_run_factory(calls, captured))
def collapse(url, media_offset, timeout=45):
# readings that would push off1 before off0
return dt("2026-09-26 09:02:59") # end desired 09:00:50 → huge shift
monkeypatch.setattr(clip, "osd_time_at", collapse)
clip.clip_batch(BASE, 1, "2026-09-26T09:00:10", "2026-09-26T09:00:50",
str(tmp_path / "o.mp4"), pad_seconds=0, align_osd=True)
args = calls[0][0]
# fallback keeps filename-based offsets
assert args[args.index("-ss") + 1] == "10.000"
assert args[args.index("-to") + 1] == "50.000"