fb6f9964d8
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
119 lines
3.6 KiB
Python
119 lines
3.6 KiB
Python
"""字幕模块单元测试。"""
|
|
|
|
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
|
|
<i>This is italic</i> 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
|