diff --git a/tests/tracks/test_subtitle_convert.py b/tests/tracks/test_subtitle_convert.py index 9d69fc24..794cf1b1 100644 --- a/tests/tracks/test_subtitle_convert.py +++ b/tests/tracks/test_subtitle_convert.py @@ -351,6 +351,24 @@ def test_segmented_fvtt_parses_and_converts(tmp_path, monkeypatch): assert cue_count(out) == 2 +def with_mdhd_timescale(data: bytes, timescale: int) -> bytes: + """The fixture with its version-0 mdhd timescale replaced; sample times stay in the same units.""" + at = data.index(b"mdhd") + 16 + return data[:at] + struct.pack(">I", timescale) + data[at + 4 :] + + +@pytest.mark.parametrize("timescale", [1000, 2000, 90000]) +def test_segmented_fvtt_cue_times_follow_the_media_timescale(timescale): + data = (FIXTURES / "segmented.wvtt.mp4").read_bytes() + assert with_mdhd_timescale(data, 1000) == data # the helper's offset hits the fixture's timescale + + captions, _ = Subtitle.merge_segmented_wvtt(with_mdhd_timescale(data, timescale)) + + # the fixture's cues span 1000-2000 and 3000-4000 media units + times = [t for c in captions for t in (c.start, c.end)] + assert times == pytest.approx([t * 1_000_000 / timescale for t in (1000, 2000, 3000, 4000)]) + + def test_segmented_fttml_joins_a_cue_split_at_a_segment_boundary(): data = build_stpp_mp4( ttml_fragment(("00:00:01.000", "00:00:02.000", "Carried")), @@ -449,17 +467,25 @@ def test_dash_fttml_cues_lose_the_presentation_time_offset(tmp_path, monkeypatch assert (caption.start, caption.end) == times -def test_dash_fvtt_cues_lose_the_presentation_time_offset(tmp_path, monkeypatch): - template = '' - data = (FIXTURES / "segmented.wvtt.mp4").read_bytes() +@pytest.mark.parametrize("method", ["subby", "pycaption"]) +@pytest.mark.parametrize( + "mdhd_timescale, pto, cues", + [ + (1000, 1000, ("00:00:00.000 --> 00:00:01.000", "00:00:02.000 --> 00:00:03.000")), + (2000, 500, ("00:00:00.000 --> 00:00:00.500", "00:00:01.000 --> 00:00:01.500")), + ], +) +def test_dash_fvtt_cues_lose_the_presentation_time_offset(tmp_path, monkeypatch, method, mdhd_timescale, pto, cues): + template = f'' + data = with_mdhd_timescale((FIXTURES / "segmented.wvtt.mp4").read_bytes(), mdhd_timescale) sub = dash_subtitle(tmp_path, monkeypatch, Codec.fVTT, data, template) + monkeypatch.setattr("unshackle.core.config.config.subtitle", {"conversion_method": method}, raising=False) sub.download(None, None) # type: ignore[arg-type] assert sub.codec == Codec.WebVTT text = sub.path.read_text("utf8") - assert "00:00:00.000 --> 00:00:01.000" in text - assert "00:00:02.000 --> 00:00:03.000" in text + assert all(cue in text for cue in cues) def ttml_fragment(*cues: tuple[str, ...]) -> str: diff --git a/unshackle/core/tracks/subtitle.py b/unshackle/core/tracks/subtitle.py index 29cf8542..3e2d13a4 100644 --- a/unshackle/core/tracks/subtitle.py +++ b/unshackle/core/tracks/subtitle.py @@ -1111,8 +1111,8 @@ class Subtitle(Track): if nodes: caption = Caption( - start=start_time * timescale, # as microseconds - end=end_time * timescale, + start=start_time * 1_000_000 / timescale, + end=end_time * 1_000_000 / timescale, nodes=nodes, layout_info=layout, )