diff --git a/packages/domain/ass_subtitle_builder.py b/packages/domain/ass_subtitle_builder.py index a2579c1b9..0da7fea4a 100755 --- a/packages/domain/ass_subtitle_builder.py +++ b/packages/domain/ass_subtitle_builder.py @@ -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) diff --git a/tests/unit/domain/test_ass_subtitle_builder.py b/tests/unit/domain/test_ass_subtitle_builder.py index 04565d471..97adda325 100644 --- a/tests/unit/domain/test_ass_subtitle_builder.py +++ b/tests/unit/domain/test_ass_subtitle_builder.py @@ -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): diff --git a/tests/unit/test_title_render_consistency.py b/tests/unit/test_title_render_consistency.py index 320be819f..677eabc34 100644 --- a/tests/unit/test_title_render_consistency.py +++ b/tests/unit/test_title_render_consistency.py @@ -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"