"""字幕模块单元测试。""" from __future__ import annotations from app.tree.subtitle import ( SRTEntry, assign_subtitles_voronoi, check_subtitle_completeness, extract_subtitle_for_range, parse_srt, ) _SAMPLE_SRT = """\ 1 00:00:01,000 --> 00:00:03,500 Hello world. 2 00:00:05,000 --> 00:00:08,000 This is italic text. 3 00:00:10,000 --> 00:00:12,000 Final line. """ class TestParseSrt: def test_basic_parse(self, tmp_path): srt_file = tmp_path / "test.srt" srt_file.write_text(_SAMPLE_SRT, encoding="utf-8") entries = parse_srt(str(srt_file)) assert len(entries) == 3 assert entries[0] == SRTEntry(start=1.0, end=3.5, text="Hello world.") assert entries[1].text == "This is italic text." def test_empty_srt(self, tmp_path): srt_file = tmp_path / "empty.srt" srt_file.write_text("", encoding="utf-8") entries = parse_srt(str(srt_file)) assert entries == [] def test_malformed_srt_skips_bad_blocks(self, tmp_path): bad_srt = "garbage\n\n1\n00:00:01,000 --> 00:00:02,000\nGood line.\n" srt_file = tmp_path / "bad.srt" srt_file.write_text(bad_srt, encoding="utf-8") entries = parse_srt(str(srt_file)) assert len(entries) == 1 assert entries[0].text == "Good line." class TestCompletenessCheck: def test_good_coverage(self): entries = [SRTEntry(0.0, 5.0, "a"), SRTEntry(5.0, 10.0, "b")] report = check_subtitle_completeness(entries, duration=10.0, min_coverage=0.5) assert report.usable is True assert report.coverage_ratio >= 0.5 def test_poor_coverage(self): entries = [SRTEntry(0.0, 1.0, "short")] report = check_subtitle_completeness(entries, duration=100.0, min_coverage=0.3) assert report.usable is False def test_max_gap(self): entries = [SRTEntry(0.0, 1.0, "a"), SRTEntry(50.0, 51.0, "b")] report = check_subtitle_completeness(entries, duration=60.0) assert report.max_gap_sec >= 49.0 class TestExtractForRange: def test_overlap(self): entries = [ SRTEntry(0.0, 5.0, "first"), SRTEntry(4.0, 8.0, "second"), SRTEntry(10.0, 12.0, "third"), ] text = extract_subtitle_for_range(entries, (3.0, 9.0)) assert "first" in text assert "second" in text assert "third" not in text class TestVoronoiAssign: def test_assigns_to_l3_nodes(self): from app.tree.index import ( IndexMeta, L1Card, L1Node, L2Card, L2Node, L3Card, L3Node, TreeIndex, ) l3_0 = L3Node(id="l1_0_l2_0_l3_0", card=L3Card("desc0", [], [], [], "", {}), timestamp=2.0) l3_1 = L3Node(id="l1_0_l2_0_l3_1", card=L3Card("desc1", [], [], [], "", {}), timestamp=6.0) l2 = L2Node( id="l1_0_l2_0", card=L2Card("evt", [], [], [], [], "", None), time_range=(0.0, 10.0), children=[l3_0, l3_1], ) l1 = L1Node( id="l1_0", card=L1Card("scene", "", [], [], [], [], ""), time_range=(0.0, 10.0), children=[l2], ) index = TreeIndex(metadata=IndexMeta("/test.mp4", "video"), roots=[l1]) entries = [SRTEntry(1.0, 3.0, "hello"), SRTEntry(5.0, 7.0, "world")] assign_subtitles_voronoi(index, entries) assert l3_0.subtitle is not None assert "hello" in l3_0.subtitle assert l3_1.subtitle is not None assert "world" in l3_1.subtitle