Files
xiaoxia-saas/tests/unit/test_subtitle_render_engine.py
T
CI Bot 7a72cfd709
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m9s
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 / Validate - Type Check (mypy) (push) Successful in 2m19s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 3m10s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m7s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 3m7s
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m17s
CI/CD Pipeline / Frontend Lint (push) Successful in 3m37s
CI/CD Pipeline / Integration Tests (push) Successful in 2m18s
CI/CD Pipeline / Unit Tests (push) Failing after 4m59s
CI/CD Pipeline / Build Staging API Image (push) Successful in 8m39s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 3m25s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 42s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 27m2s
CI/CD Pipeline / Staging API Integration Tests (push) Failing after 30m15s
style: auto-format with black + isort + prettier
2026-07-25 00:16:10 +00:00

319 lines
9.7 KiB
Python
Executable File

"""字幕渲染引擎单元测试 - 工具函数+样式配置等纯逻辑."""
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)