"""字幕领域模型单元测试.""" from __future__ import annotations from packages.domain.subtitle import SubtitleSegment, SubtitleTimeline, SubtitleWord class TestSubtitleWord: """SubtitleWord 测试.""" def test_basic_properties(self): word = SubtitleWord(text="你好", start=1.0, end=1.5) assert word.text == "你好" assert word.start == 1.0 assert word.end == 1.5 assert word.duration == 0.5 def test_duration_zero_when_end_before_start(self): word = SubtitleWord(text="test", start=2.0, end=1.0) assert word.duration == 0.0 def test_duration_zero_when_same_time(self): word = SubtitleWord(text="test", start=1.0, end=1.0) assert word.duration == 0.0 class TestSubtitleSegment: """SubtitleSegment 测试.""" def test_basic_properties(self): seg = SubtitleSegment(text="大家好", start=0.0, end=2.0) assert seg.text == "大家好" assert seg.start == 0.0 assert seg.end == 2.0 assert seg.duration == 2.0 assert seg.char_count == 3 assert seg.words == [] def test_duration_with_words(self): words = [ SubtitleWord(text="大", start=0.0, end=0.5), SubtitleWord(text="家", start=0.5, end=1.0), SubtitleWord(text="好", start=1.0, end=1.5), ] seg = SubtitleSegment(text="大家好", start=0.0, end=1.5, words=words) assert seg.duration == 1.5 assert seg.char_count == 3 assert len(seg.words) == 3 def test_duration_zero_when_end_before_start(self): seg = SubtitleSegment(text="test", start=3.0, end=1.0) assert seg.duration == 0.0 class TestSubtitleTimelineBasics: """SubtitleTimeline 基础属性测试.""" def test_empty_timeline(self): tl = SubtitleTimeline() assert tl.segment_count == 0 assert tl.total_chars == 0 assert tl.language == "zh" assert tl.total_duration == 0.0 def test_single_segment(self): seg = SubtitleSegment(text="测试", start=0.0, end=1.0) tl = SubtitleTimeline(segments=[seg]) assert tl.segment_count == 1 assert tl.total_chars == 2 def test_multiple_segments(self): segs = [ SubtitleSegment(text="第一句", start=0.0, end=1.0), SubtitleSegment(text="第二句", start=1.0, end=2.0), SubtitleSegment(text="第三句", start=2.0, end=3.0), ] tl = SubtitleTimeline(segments=segs, total_duration=3.0) assert tl.segment_count == 3 assert tl.total_chars == 9 assert tl.total_duration == 3.0 def test_custom_language(self): tl = SubtitleTimeline(language="en") assert tl.language == "en" class TestSubtitleTimelineMergeShort: """合并短字幕片段测试.""" def test_empty_or_single_no_change(self): tl = SubtitleTimeline() result = tl.merge_short_segments() assert result.segment_count == 0 seg = SubtitleSegment(text="短", start=0.0, end=0.5) tl2 = SubtitleTimeline(segments=[seg]) result2 = tl2.merge_short_segments() assert result2.segment_count == 1 def test_merge_short_segments(self): segs = [ SubtitleSegment(text="你好", start=0.0, end=0.5), SubtitleSegment(text="世界", start=0.5, end=1.0), SubtitleSegment(text="今天天气很好", start=1.0, end=2.0), ] tl = SubtitleTimeline(segments=segs) result = tl.merge_short_segments(min_chars=4) # "你好"+"世界"=4字,合并;"今天天气很好"=6字,保留 assert result.segment_count == 2 assert result.segments[0].text == "你好世界" assert result.segments[0].start == 0.0 assert result.segments[0].end == 1.0 assert result.segments[1].text == "今天天气很好" def test_merge_trailing_short_to_last(self): segs = [ SubtitleSegment(text="一二三四五六七八", start=0.0, end=1.0), SubtitleSegment(text="短", start=1.0, end=1.2), SubtitleSegment(text="尾", start=1.2, end=1.4), ] tl = SubtitleTimeline(segments=segs) result = tl.merge_short_segments(min_chars=4) # "一二三四五六七八"=8字 → 保留 # "短"+"尾"=2字 < 4 → 合并到上一段 assert result.segment_count == 1 assert result.segments[0].text == "一二三四五六七八短尾" def test_merge_with_words(self): words1 = [SubtitleWord(text="你", start=0.0, end=0.25), SubtitleWord(text="好", start=0.25, end=0.5)] words2 = [SubtitleWord(text="世", start=0.5, end=0.75), SubtitleWord(text="界", start=0.75, end=1.0)] segs = [ SubtitleSegment(text="你好", start=0.0, end=0.5, words=words1), SubtitleSegment(text="世界", start=0.5, end=1.0, words=words2), ] tl = SubtitleTimeline(segments=segs) result = tl.merge_short_segments(min_chars=4) assert result.segment_count == 1 assert len(result.segments[0].words) == 4 def test_preserves_language_and_duration(self): segs = [SubtitleSegment(text="短", start=0.0, end=0.5)] tl = SubtitleTimeline(segments=segs, language="ja", total_duration=0.5) result = tl.merge_short_segments(min_chars=4) assert result.language == "ja" assert result.total_duration == 0.5 class TestSubtitleTimelineSplitLong: """拆分长字幕片段测试.""" def test_short_segments_no_change(self): segs = [SubtitleSegment(text="短句", start=0.0, end=1.0)] tl = SubtitleTimeline(segments=segs) result = tl.split_long_segments(max_chars=10) assert result.segment_count == 1 def test_split_by_sentence_punctuation(self): text = "今天天气很好。我们出去散步吧!" seg = SubtitleSegment(text=text, start=0.0, end=10.0) tl = SubtitleTimeline(segments=[seg]) result = tl.split_long_segments(max_chars=8) assert result.segment_count >= 2 assert result.segments[0].text.endswith("。") assert result.total_chars == len(text) def test_split_long_text_no_punctuation_hard_cut(self): text = "一二三四五六七八九十十一十二十三十四十五十六十七十八" seg = SubtitleSegment(text=text, start=0.0, end=10.0) tl = SubtitleTimeline(segments=[seg]) result = tl.split_long_segments(max_chars=8) assert result.segment_count > 1 # 所有片段都不超过 max_chars for s in result.segments: assert s.char_count <= 8 def test_split_time_proportional(self): text = "一二三四。五六七八。" seg = SubtitleSegment(text=text, start=0.0, end=10.0) tl = SubtitleTimeline(segments=[seg]) result = tl.split_long_segments(max_chars=4) assert result.segment_count >= 2 # 总时长保持一致 assert abs(result.segments[-1].end - 10.0) < 0.01 def test_split_with_words(self): words = [ SubtitleWord(text="一", start=0.0, end=0.5), SubtitleWord(text="二", start=0.5, end=1.0), SubtitleWord(text="三", start=1.0, end=1.5), SubtitleWord(text="四", start=1.5, end=2.0), ] text = "一二三四五六七八" seg = SubtitleSegment(text=text, start=0.0, end=4.0, words=words) tl = SubtitleTimeline(segments=[seg]) result = tl.split_long_segments(max_chars=4) assert result.segment_count >= 2 # 词的总数应该不变 total_words = sum(len(s.words) for s in result.segments) assert total_words == 4 def test_split_preserves_language(self): seg = SubtitleSegment(text="test", start=0.0, end=1.0) tl = SubtitleTimeline(segments=[seg], language="en") result = tl.split_long_segments(max_chars=2) assert result.language == "en" class TestSplitTextByPunctuation: """标点拆分静态方法测试.""" def test_empty_text(self): result = SubtitleTimeline._split_text_by_punctuation("", 10) assert result == [] def test_short_text_no_split(self): result = SubtitleTimeline._split_text_by_punctuation("短文本", 10) assert len(result) == 1 def test_split_by_period(self): result = SubtitleTimeline._split_text_by_punctuation("第一句。第二句。", 4) assert len(result) >= 2 assert "。" in result[0] def test_split_by_exclamation(self): result = SubtitleTimeline._split_text_by_punctuation("你好!世界!", 3) assert len(result) >= 2 def test_split_by_comma_when_long(self): text = "这是一个很长的句子,中间有逗号分隔,后面还有内容" result = SubtitleTimeline._split_text_by_punctuation(text, 8) assert len(result) >= 2 def test_no_punctuation_hard_cut(self): text = "一二三四五六七八九十十一十二十三十四十五" result = SubtitleTimeline._split_text_by_punctuation(text, 8) assert len(result) > 1 for part in result: assert len(part) <= 8 def test_sentence_end_triggers_split_when_half_max(self): # 句末标点在 max_chars//2 以上就拆分 text = "你好世界。abcdefghij" result = SubtitleTimeline._split_text_by_punctuation(text, 10) # "你好世界。"=5字 < 10但>=5(half),应该拆分 assert len(result) >= 2 class TestMergeSegments: """_merge_segments 静态方法测试.""" def test_merge_empty(self): result = SubtitleTimeline._merge_segments([]) assert result.text == "" assert result.start == 0 assert result.end == 0 def test_merge_single(self): seg = SubtitleSegment(text="test", start=1.0, end=2.0) result = SubtitleTimeline._merge_segments([seg]) assert result.text == "test" assert result.start == 1.0 assert result.end == 2.0 def test_merge_multiple(self): segs = [ SubtitleSegment(text="第一", start=0.0, end=1.0), SubtitleSegment(text="第二", start=1.0, end=2.0), ] result = SubtitleTimeline._merge_segments(segs) assert result.text == "第一第二" assert result.start == 0.0 assert result.end == 2.0