fix(ass): 字段名归一化(font_size/font_color→size/color) + 移除字号上限36 #1501

Merged
auto-approve-bot merged 1 commits from fix/ass-subtitle-field-mapping into develop 2026-08-25 21:56:02 +08:00
3 changed files with 87 additions and 13 deletions
+8 -2
View File
@@ -247,6 +247,12 @@ def build_ass_content(
title_config = title_config or {}
subtitle_config = subtitle_config or {}
# ── 字段名归一化:前端传 font_size/font_color,内部用 size/color ──
if "font_size" in title_config and "size" not in title_config:
title_config["size"] = title_config["font_size"]
if "font_color" in title_config and "color" not in title_config:
title_config["color"] = title_config["font_color"]
# ── 兼容前端简化格式:stroke/shadow 为 boolean 时,转换为标准 dict ──
# 前端 TitleSettings 发送 stroke=true/false, shadow=true/false
# 后端 build_ass_style 期望 stroke={enabled, color, width}, shadow={enabled, blur, offset_x, offset_y}
@@ -296,7 +302,7 @@ def build_ass_content(
build_ass_style(
"TitleStyle",
font_name=title_config.get("font", "思源黑体"),
font_size=min(int(title_config.get("size", 36)), 36),
font_size=int(title_config.get("size", 36)),
primary_color=title_color,
outline_color=stroke_color,
outline_width=stroke_width,
@@ -313,7 +319,7 @@ def build_ass_content(
# 根据视频宽度和字号自动换行标题,防止超出画面
# 先 escape 特殊字符,再插入换行符 \N,避免顺序颠倒导致 \N 被转义
title_font_size = min(int(title_config.get("size", 36)), 36)
title_font_size = int(title_config.get("size", 36))
safe_title_text_raw = escape_ass_text(title_text)
safe_title_text = _wrap_title_text(safe_title_text_raw, video_width, title_font_size)
+44 -2
View File
@@ -408,11 +408,53 @@ class TestBuildAssContent:
title_text="T",
title_config={"size": 72},
)
# 在TitleStyle行里查找字体大小
# 在TitleStyle行里查找字体大小(字号上限已移除,72应原样保留)
for line in result.split("\n"):
if line.startswith("Style: TitleStyle"):
parts = line.split(",")
assert parts[2] == "36"
assert parts[2] == "72"
break
def test_title_font_size_frontend_field_alias(self):
"""前端传 font_size 应归一化为内部 size 字段。"""
result = build_ass_content(
video_width=1920,
video_height=1080,
video_duration=5.0,
title_text="T",
title_config={"font_size": 48},
)
for line in result.split("\n"):
if line.startswith("Style: TitleStyle"):
parts = line.split(",")
assert parts[2] == "48"
break
def test_title_font_color_frontend_field_alias(self):
"""前端传 font_color 应归一化为内部 color 字段。"""
result = build_ass_content(
video_width=1920,
video_height=1080,
video_duration=5.0,
title_text="T",
title_config={"font_color": "#FF0000"},
)
# 红色 → &H0000FF
assert "&H0000FF" in result
def test_title_size_takes_precedence_over_font_size(self):
"""同时传 size 和 font_size 时,size 优先。"""
result = build_ass_content(
video_width=1920,
video_height=1080,
video_duration=5.0,
title_text="T",
title_config={"size": 56, "font_size": 28},
)
for line in result.split("\n"):
if line.startswith("Style: TitleStyle"):
parts = line.split(",")
assert parts[2] == "56"
break
def test_title_bold(self):
+35 -9
View File
@@ -1,7 +1,7 @@
"""标题渲染前后端一致性测试。
验证 build_ass_content 生成的 ASS 样式参数与前端 drawTitleOnCanvas.ts 一致:
- 字号上限 36px
- 字号不再设置上限,由前端/调用方控制
- 描边宽度 2px
- 阴影 blur=4, offset=2
- boolean stroke/shadow 自动转换
@@ -16,8 +16,8 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "packages"))
from domain.ass_subtitle_builder import build_ass_content, build_ass_style
class TestFontSizeCap:
"""字号上限应与前端 Math.min(settings.size, 36) 一致"""
class TestFontSize:
"""字号处理:默认值与保留逻辑,不再做上限截断"""
def test_default_font_size_is_36(self):
"""无 size 字段时,默认字号应为 36。"""
@@ -43,8 +43,8 @@ class TestFontSizeCap:
)
assert ",32," in content
def test_size_60_capped_at_36(self):
"""size=60 应被 cap 到 36"""
def test_size_60_preserved(self):
"""size=60 应原样保留(字号上限已移除)"""
config = {"size": 60}
content = build_ass_content(
video_width=1080,
@@ -53,14 +53,40 @@ class TestFontSizeCap:
title_text="测试标题",
title_config=config,
)
# 解析 Style 行的 Fontsize 字段(第3个字段,索引2)
style_line = [line for line in content.splitlines() if line.startswith("Style: TitleStyle")][0]
fields = [f.strip() for f in style_line.split(",")]
font_size = int(fields[2])
assert font_size == 36, f"字号60应被cap到36, 实际={font_size}"
assert font_size == 60, f"字号60应原样保留, 实际={font_size}"
def test_font_size_alias_normalized(self):
"""前端传 font_size 应归一化为 size。"""
config = {"font_size": 52}
content = build_ass_content(
video_width=1080,
video_height=1920,
video_duration=10.0,
title_text="测试标题",
title_config=config,
)
style_line = [line for line in content.splitlines() if line.startswith("Style: TitleStyle")][0]
fields = [f.strip() for f in style_line.split(",")]
assert fields[2] == "52", f"font_size=52 应归一化, 实际={fields[2]}"
def test_font_color_alias_normalized(self):
"""前端传 font_color 应归一化为 color。"""
config = {"font_color": "#00FF00"}
content = build_ass_content(
video_width=1080,
video_height=1920,
video_duration=10.0,
title_text="测试标题",
title_config=config,
)
# 绿色 → &H00FF00
assert "&H00FF00" in content
def test_size_24_preserved(self):
"""size=24 应原样使用(小于36,不cap"""
"""size=24 应原样使用。"""
config = {"size": 24}
content = build_ass_content(
video_width=1080,
@@ -203,7 +229,7 @@ class TestFullStyleConsistency:
# Fontname
assert fields[1] == "思源黑体"
# Fontsize = 28 (小于36,不cap)
# Fontsize = 28
assert fields[2] == "28"
# Bold = -1 (True)
assert fields[7] == "-1"