d66046aa4b
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 57s
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m30s
CI/CD Pipeline / Frontend Lint (push) Successful in 1m41s
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
273 lines
10 KiB
Python
Executable File
273 lines
10 KiB
Python
Executable File
"""字幕领域模型单元测试."""
|
|
|
|
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
|