Files
xiaoxia-saas/tests/unit/test_render_subtitles.py
T
xiaoxia 37d111dcd5
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 Lint (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (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 / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
test(P3-1): 第57波 worker层小工具+配置单测(+60) (#849)
2026-07-24 21:29:40 +08:00

217 lines
6.3 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
render_subtitles ASS 字幕纯函数测试.
覆盖 _hex_to_ass_color / _position_to_ass_alignment / _build_ass_style / _escape_ass_text / _format_ass_time 等纯逻辑.
文件生成与 FFmpeg 渲染由集成测试覆盖.
"""
from __future__ import annotations
from pathlib import Path
import pytest
from video_processing.render_subtitles import (
_build_ass_style,
_escape_ass_text,
_format_ass_time,
_hex_to_ass_color,
_position_to_ass_alignment,
)
class TestHexToAssColor:
"""HEX → ASS 颜色转换(不含 alpha 前缀版本)."""
def test_white(self):
assert _hex_to_ass_color("#FFFFFF") == "&HFFFFFF"
def test_black(self):
assert _hex_to_ass_color("#000000") == "&H000000"
def test_red(self):
# #FF0000 → R=FF, G=00, B=00 → BGR=0000FF
assert _hex_to_ass_color("#FF0000") == "&H0000FF"
def test_blue(self):
# #0000FF → R=00, G=00, B=FF → BGR=FF0000
assert _hex_to_ass_color("#0000FF") == "&HFF0000"
def test_green(self):
# #00FF00 → R=00, G=FF, B=00 → BGR=00FF00
assert _hex_to_ass_color("#00FF00") == "&H00FF00"
def test_without_hash(self):
assert _hex_to_ass_color("FF0000") == "&H0000FF"
def test_lowercase(self):
assert _hex_to_ass_color("#ff0000") == "&H0000FF"
def test_invalid_length_returns_default(self):
assert _hex_to_ass_color("#FFF") == "&H000000" # 3位
assert _hex_to_ass_color("") == "&H000000" # 空
def test_mixed_case(self):
result = _hex_to_ass_color("#aBcDeF")
assert result == "&HEFCDAB"
class TestPositionToAssAlignment:
"""位置 → ASS 对齐编号映射."""
def test_top(self):
assert _position_to_ass_alignment("top") == 8
def test_center(self):
assert _position_to_ass_alignment("center") == 5
def test_bottom(self):
assert _position_to_ass_alignment("bottom") == 2
def test_unknown_returns_top_default(self):
assert _position_to_ass_alignment("unknown") == 8
assert _position_to_ass_alignment("") == 8
assert _position_to_ass_alignment("left") == 8
assert _position_to_ass_alignment(None) == 8
class TestBuildAssStyle:
"""构建 ASS Style 行."""
def test_basic_style(self):
style = _build_ass_style("Default")
assert style.startswith("Style: Default,")
assert "思源黑体" in style
assert "48" in style # font_size
def test_custom_font(self):
style = _build_ass_style("Custom", font_name="Arial", font_size=32)
assert "Arial" in style
assert ",32," in style
def test_bold(self):
style = _build_ass_style("Bold", bold=True)
assert ",-1," in style # bold = -1 (true)
def test_not_bold(self):
style = _build_ass_style("Normal", bold=False)
parts = style.split(",")
# Bold 是第 8 个字段(index 7)
assert parts[7] == "0"
def test_italic(self):
style = _build_ass_style("Italic", italic=True)
parts = style.split(",")
# Italic 是第 9 个字段(index 8)
assert parts[8] == "-1"
def test_alignment(self):
style = _build_ass_style("Bottom", alignment=2)
parts = style.split(",")
# Alignment 是第 19 个字段(index 18)
assert parts[18] == "2"
def test_margins(self):
style = _build_ass_style(
"Margins",
margin_v=80,
margin_l=60,
margin_r=60,
)
parts = style.split(",")
# MarginL = parts[19], MarginR = parts[20], MarginV = parts[21]
assert parts[19] == "60"
assert parts[20] == "60"
assert parts[21] == "80"
def test_outline_width(self):
style = _build_ass_style("Outline", outline_width=3.0)
parts = style.split(",")
# Outline 是第 17 个字段(index 16)
assert parts[16] == "3.0"
def test_shadow_with_blur(self):
style = _build_ass_style(
"Shadow",
shadow_blur=1.0,
shadow_offset=(2, 3),
)
parts = style.split(",")
# Shadow 是第 18 个字段(index 17)
assert parts[17] == "3" # shadow_offset[1]
def test_shadow_without_blur(self):
style = _build_ass_style(
"NoShadow",
shadow_blur=0.0,
shadow_offset=(2, 3),
)
parts = style.split(",")
assert parts[17] == "0" # 无模糊时阴影深度为0
def test_style_format_has_correct_field_count(self):
"""ASS Style 行应该有 23 个字段."""
style = _build_ass_style("Test")
parts = style.split(",")
assert len(parts) >= 22 # 至少22个字段(Format定义的)
class TestEscapeAssText:
"""ASS 文本转义."""
def test_plain_text(self):
assert _escape_ass_text("hello") == "hello"
def test_newline_unix(self):
assert _escape_ass_text("a\nb") == "a\\Nb"
def test_newline_windows(self):
assert _escape_ass_text("a\r\nb") == "a\\Nb"
def test_newline_mac(self):
assert _escape_ass_text("a\rb") == "a\\Nb"
def test_curly_braces(self):
assert _escape_ass_text("{text}") == "(text)"
def test_multiple_braces(self):
assert _escape_ass_text("{a}b{c}") == "(a)b(c)"
def test_mixed_special_chars(self):
result = _escape_ass_text("line1\n{bold}\nline3")
assert "\\N" in result
assert "(bold)" in result
assert "{" not in result
def test_empty(self):
assert _escape_ass_text("") == ""
class TestFormatAssTime:
"""秒 → ASS 时间格式."""
def test_zero(self):
assert _format_ass_time(0.0) == "0:00:00.00"
def test_seconds(self):
assert _format_ass_time(5.5) == "0:00:05.50"
def test_minutes(self):
assert _format_ass_time(65.25) == "0:01:05.25"
def test_hours(self):
assert _format_ass_time(3661.5) == "1:01:01.50"
def test_exact_minute(self):
assert _format_ass_time(60.0) == "0:01:00.00"
def test_exact_hour(self):
assert _format_ass_time(3600.0) == "1:00:00.00"
def test_sub_second_precision(self):
result = _format_ass_time(1.234)
parts = result.split(":")
sec_part = parts[2]
decimals = sec_part.split(".")[1]
assert len(decimals) == 2 # 两位小数(厘秒)