"""字幕渲染引擎单元测试 - 工具函数+样式配置等纯逻辑.""" from __future__ import annotations import pytest from video_processing.subtitle_render_engine import ( SubtitleStyle, _escape_ass_text, _format_ass_time, _hex_to_ass_bgr, _hex_to_ass_color, _opacity_to_ass_alpha, _wrap_text, ) # ── 颜色转换测试 ────────────────────────────────────────────── class TestHexToAssColor: """_hex_to_ass_color 测试.""" def test_white(self): """白色.""" assert _hex_to_ass_color("#FFFFFF") == "&H00FFFFFF" def test_black(self): """黑色.""" assert _hex_to_ass_color("#000000") == "&H00000000" def test_red(self): """红色 → BGR: 蓝绿红.""" assert _hex_to_ass_color("#FF0000") == "&H000000FF" def test_green(self): """绿色.""" assert _hex_to_ass_color("#00FF00") == "&H0000FF00" def test_blue(self): """蓝色.""" assert _hex_to_ass_color("#0000FF") == "&H00FF0000" def test_no_hash_prefix(self): """不带#号.""" assert _hex_to_ass_color("FF0000") == "&H000000FF" def test_invalid_length(self): """长度不对返回默认白色.""" assert _hex_to_ass_color("#FFF") == "&H00FFFFFF" assert _hex_to_ass_color("#FF") == "&H00FFFFFF" assert _hex_to_ass_color("") == "&H00FFFFFF" def test_lowercase_input(self): """小写输入转为大写输出.""" assert _hex_to_ass_color("#aabbcc") == "&H00CCBBAA" class TestHexToAssBgr: """_hex_to_ass_bgr 测试.""" def test_white(self): """白色BGR.""" assert _hex_to_ass_bgr("#FFFFFF") == "FFFFFF" def test_red_bgr(self): """红色 → BGR = 0000FF.""" assert _hex_to_ass_bgr("#FF0000") == "0000FF" def test_blue_bgr(self): """蓝色 → BGR = FF0000.""" assert _hex_to_ass_bgr("#0000FF") == "FF0000" def test_invalid_length(self): """长度不对返回默认.""" assert _hex_to_ass_bgr("#FF") == "FFFFFF" class TestOpacityToAssAlpha: """_opacity_to_ass_alpha 测试.""" def test_fully_opaque(self): """完全不透明 → 00.""" assert _opacity_to_ass_alpha(1.0) == "00" def test_fully_transparent(self): """完全透明 → FF.""" assert _opacity_to_ass_alpha(0.0) == "FF" def test_half(self): """50% → 128 → 80.""" assert _opacity_to_ass_alpha(0.5) == "80" def test_quarter(self): """75%不透明 → 64 → 40.""" assert _opacity_to_ass_alpha(0.75) == "40" # ── 文本处理测试 ────────────────────────────────────────────── class TestEscapeAssText: """_escape_ass_text 转义测试.""" def test_normal_text_unchanged(self): """普通文本不变.""" assert _escape_ass_text("hello world") == "hello world" def test_newline_converted(self): """换行转成\\N.""" assert _escape_ass_text("line1\nline2") == "line1\\Nline2" def test_crlf_converted(self): """\\r\\n转成\\N.""" assert _escape_ass_text("line1\r\nline2") == "line1\\Nline2" def test_carriage_return_converted(self): """\\r转成\\N.""" assert _escape_ass_text("line1\rline2") == "line1\\Nline2" def test_curly_braces_replaced(self): """花括号替换成圆括号(ASS控制符).""" assert _escape_ass_text("{text}") == "(text)" def test_mixed_special_chars(self): """混合特殊字符.""" text = "hello\n{world}\r\nend" result = _escape_ass_text(text) assert "\\N" in result assert "{" not in result assert "}" not in result assert "(world)" in result class TestFormatAssTime: """_format_ass_time 时间格式化测试.""" def test_zero(self): """0秒.""" assert _format_ass_time(0) == "0:00:00.00" def test_seconds_only(self): """只有秒.""" assert _format_ass_time(5.5) == "0:00:05.50" def test_minutes_and_seconds(self): """分+秒.""" assert _format_ass_time(125.5) == "0:02:05.50" def test_hours_minutes_seconds(self): """时+分+秒.""" assert _format_ass_time(3725.25) == "1:02:05.25" def test_exactly_one_hour(self): """刚好1小时.""" assert _format_ass_time(3600.0) == "1:00:00.00" def test_single_digit_minute(self): """分钟补零.""" result = _format_ass_time(65.0) parts = result.split(":") assert parts[1] == "01" def test_always_two_decimal_places(self): """总是两位小数.""" result = _format_ass_time(3.0) assert result.endswith(".00") class TestWrapText: """_wrap_text 换行测试.""" def test_short_text_no_wrap(self): """短文本不换行.""" result = _wrap_text("hello", 10) assert len(result) == 1 assert result[0] == "hello" def test_exact_length_no_wrap(self): """刚好长度不换行.""" text = "abcdefghij" # 10 chars result = _wrap_text(text, 10) assert len(result) == 1 assert result[0] == text def test_simple_wrap(self): """简单换行.""" text = "abcdefghijklmnopqrst" # 20 chars result = _wrap_text(text, 10) assert len(result) == 2 assert len(result[0]) == 10 assert len(result[1]) == 10 def test_uneven_wrap(self): """不均等换行.""" text = "abcdefghijklm" # 13 chars result = _wrap_text(text, 5) assert len(result) == 3 assert result[0] == "abcde" assert result[1] == "fghij" assert result[2] == "klm" def test_chinese_text_wrap(self): """中文文本换行(按字符数).""" text = "一二三四五六七八九十" result = _wrap_text(text, 5) assert len(result) == 2 assert result[0] == "一二三四五" assert result[1] == "六七八九十" # ── SubtitleStyle 测试 ──────────────────────────────────── class TestSubtitleStyleDefaults: """SubtitleStyle 默认值测试.""" def test_default_values(self): """默认值正确.""" style = SubtitleStyle() assert style.font_size > 0 assert style.bold is False assert style.italic is False assert style.stroke_enabled is True assert style.shadow_enabled is False assert style.background_enabled is False assert style.fade_in == 0.0 assert style.fade_out == 0.0 assert style.animation_type == "none" class TestSubtitleStyleFromDict: """SubtitleStyle.from_dict 测试.""" def test_none_returns_default(self): """None返回默认样式.""" style = SubtitleStyle.from_dict(None) assert isinstance(style, SubtitleStyle) def test_empty_dict_returns_default(self): """空dict返回默认.""" style = SubtitleStyle.from_dict({}) assert isinstance(style, SubtitleStyle) def test_custom_font_size(self): """自定义字号.""" style = SubtitleStyle.from_dict({"size": 48}) assert style.font_size == 48 def test_custom_color(self): """自定义颜色.""" style = SubtitleStyle.from_dict({"color": "#FF0000"}) assert style.font_color == "#FF0000" def test_bold_enabled(self): """启用粗体.""" style = SubtitleStyle.from_dict({"bold": True}) assert style.bold is True def test_stroke_disabled(self): """禁用描边.""" style = SubtitleStyle.from_dict({"stroke_enabled": False}) assert style.stroke_enabled is False def test_background_enabled(self): """启用背景框.""" style = SubtitleStyle.from_dict({"background_enabled": True}) assert style.background_enabled is True def test_background_opacity_clamped(self): """背景透明度钳制.""" style = SubtitleStyle.from_dict( { "background_enabled": True, "background_opacity": 2.0, } ) assert style.background_opacity == 1.0 def test_invalid_position_falls_back(self): """无效位置回退到默认.""" style = SubtitleStyle.from_dict({"position": "invalid_pos"}) # 回退到默认位置 assert style.position is not None def test_fade_in_non_negative(self): """淡入时长不能为负.""" style = SubtitleStyle.from_dict({"fade_in": -1.0}) assert style.fade_in == 0.0 def test_custom_animation(self): """自定义动画.""" style = SubtitleStyle.from_dict({"animation_type": "fade"}) assert style.animation_type == "fade" class TestSubtitleStyleProperties: """SubtitleStyle 属性测试.""" def test_ass_font_color_format(self): """ass_font_color格式正确.""" style = SubtitleStyle(font_color="#FF0000") result = style.ass_font_color assert result.startswith("&H") assert len(result) == 10 # &H + AABBGGRR = 10 chars def test_ass_background_color_format(self): """背景颜色格式正确.""" style = SubtitleStyle( background_enabled=True, background_color="#000000", background_opacity=0.5, ) result = style.ass_background_color assert result.startswith("&H") def test_alignment_is_int(self): """alignment是整数.""" style = SubtitleStyle() assert isinstance(style.alignment, int)