From 15e429ffb1e2f35259362e3d72e8ebdab801ffd5 Mon Sep 17 00:00:00 2001 From: saas-backend-agent Date: Fri, 4 Sep 2026 15:14:17 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=A0=87=E9=A2=98=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=20fallback=20=E6=94=B9=E4=B8=BA=20bottom?= =?UTF-8?q?=EF=BC=8C=E4=B8=8E=E5=89=8D=E7=AB=AF=20DEFAULT=5FTITLE=5FSETTIN?= =?UTF-8?q?GS=20=E5=AF=B9=E9=BD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - position_to_ass_alignment() fallback 从 8(top) 改为 2(bottom) - build_ass_content() title 默认 position 从 'top' 改为 'bottom' - subtitle_generator.generate_ass_from_timeline() title 默认 position 同步 - 更新 6 个测试文件中 fallback 断言(8→2) - 新增 TestDefaultPositionBottom 测试类(3 个测试) - 351 个标题/字幕相关测试全通过 --- .../video_processing/subtitle_generator.py | 2 +- packages/domain/ass_subtitle_builder.py | 9 ++-- .../unit/domain/test_ass_subtitle_builder.py | 9 ++-- tests/unit/test_ass_subtitle_builder.py | 50 +++++++++++++++++-- tests/unit/test_render_subtitles.py | 10 ++-- tests/unit/test_render_subtitles_pure.py | 16 +++--- 6 files changed, 69 insertions(+), 27 deletions(-) diff --git a/apps/worker/video_processing/subtitle_generator.py b/apps/worker/video_processing/subtitle_generator.py index b8c6706bd..caa7f63c2 100755 --- a/apps/worker/video_processing/subtitle_generator.py +++ b/apps/worker/video_processing/subtitle_generator.py @@ -213,7 +213,7 @@ def generate_ass_from_timeline( t_shadow.get("offset_x", 2) if t_shadow.get("enabled", False) else 0, t_shadow.get("offset_y", 2) if t_shadow.get("enabled", False) else 0, ) - t_alignment = position_to_ass_alignment(title_cfg.get("position", "top")) + t_alignment = position_to_ass_alignment(title_cfg.get("position", "bottom")) title_style_line = build_ass_style( "TitleStyle", diff --git a/packages/domain/ass_subtitle_builder.py b/packages/domain/ass_subtitle_builder.py index eb3f4bf6d..9d949200d 100755 --- a/packages/domain/ass_subtitle_builder.py +++ b/packages/domain/ass_subtitle_builder.py @@ -81,14 +81,14 @@ def position_to_ass_alignment(position: str) -> int: position: 位置字符串 top/center/bottom Returns: - ASS 对齐编号,默认 8(顶部居中) + ASS 对齐编号,默认 2(底部居中,与前端 DEFAULT_TITLE_SETTINGS.position="bottom" 对齐) """ mapping = { "top": 8, "center": 5, "bottom": 2, } - return mapping.get(position, 8) + return mapping.get(position, 2) # ── Style 行构建 ────────────────────────────────────────────────────────────── @@ -226,7 +226,6 @@ def _wrap_title_text( # 换行计算使用原始 font_size,与 CSS 预览一致;1.35x 补偿仅用于 ASS Fontsize 渲染 - # 先按已有 \N 分段,每段独立自动换行,最后用 \N 拼回 segments = text.split("\\N") wrapped_segments: list[str] = [] @@ -386,8 +385,8 @@ def build_ass_content( # position → alignment 三档逻辑,现有输出保持一字节不变。 title_pos = _parse_title_position(title_config, video_width, video_height) - title_alignment = 5 if title_pos is not None else position_to_ass_alignment( - title_config.get("position", "top") + title_alignment = ( + 5 if title_pos is not None else position_to_ass_alignment(title_config.get("position", "bottom")) ) styles.append( diff --git a/tests/unit/domain/test_ass_subtitle_builder.py b/tests/unit/domain/test_ass_subtitle_builder.py index 15cb76256..5040fcf4d 100644 --- a/tests/unit/domain/test_ass_subtitle_builder.py +++ b/tests/unit/domain/test_ass_subtitle_builder.py @@ -83,11 +83,11 @@ class TestPositionToAssAlignment: def test_bottom(self): assert position_to_ass_alignment("bottom") == 2 - def test_unknown_defaults_top(self): - assert position_to_ass_alignment("unknown") == 8 + def test_unknown_defaults_bottom(self): + assert position_to_ass_alignment("unknown") == 2 - def test_empty_defaults_top(self): - assert position_to_ass_alignment("") == 8 + def test_empty_defaults_bottom(self): + assert position_to_ass_alignment("") == 2 # ============================================================ @@ -581,6 +581,7 @@ class TestConstants: assert isinstance(TITLE_MARGIN_BOTTOM, int) assert isinstance(TITLE_MARGIN_SIDE, int) + # ============================================================ # _wrap_title_text 换行逻辑验证 # ============================================================ diff --git a/tests/unit/test_ass_subtitle_builder.py b/tests/unit/test_ass_subtitle_builder.py index 4a4e98f5d..4b095fd96 100755 --- a/tests/unit/test_ass_subtitle_builder.py +++ b/tests/unit/test_ass_subtitle_builder.py @@ -65,11 +65,11 @@ class TestPositionToAssAlignment: def test_bottom(self): assert position_to_ass_alignment("bottom") == 2 - def test_unknown_default_top(self): - assert position_to_ass_alignment("unknown") == 8 + def test_unknown_default_bottom(self): + assert position_to_ass_alignment("unknown") == 2 - def test_empty_default_top(self): - assert position_to_ass_alignment("") == 8 + def test_empty_default_bottom(self): + assert position_to_ass_alignment("") == 2 # ── Style 行构建 ───────────────────────────────────────────────────────────── @@ -747,3 +747,45 @@ class TestTitleFreePosition: line for line in content.splitlines() if line.startswith("Dialogue:") and "SubtitleStyle" in line ][0] assert "\\pos(" not in sub_dialogue + + +class TestDefaultPositionBottom: + """默认 position 应为 bottom(alignment=2),与前端 DEFAULT_TITLE_SETTINGS 对齐。""" + + def _base_kwargs(self): + return dict( + video_width=1080, + video_height=1920, + video_duration=10.0, + title_text="测试标题", + ) + + def test_no_position_defaults_to_bottom_alignment(self): + """不传 position 时,Alignment 应为 2(bottom)。""" + content = build_ass_content( + **self._base_kwargs(), + title_config={"size": 36}, + ) + 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[18] == "2", f"Expected alignment 2 (bottom), got {fields[18]}" + + def test_no_position_no_coords_defaults_to_bottom(self): + """不传 position 也不传坐标时,走 bottom 三档逻辑。""" + content = build_ass_content( + **self._base_kwargs(), + title_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[18] == "2" + + def test_explicit_top_still_works(self): + """显式传 position='top' 仍然得到 alignment=8。""" + content = build_ass_content( + **self._base_kwargs(), + title_config={"position": "top", "size": 36}, + ) + 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[18] == "8" diff --git a/tests/unit/test_render_subtitles.py b/tests/unit/test_render_subtitles.py index c0304e986..22317c8af 100755 --- a/tests/unit/test_render_subtitles.py +++ b/tests/unit/test_render_subtitles.py @@ -67,11 +67,11 @@ class TestPositionToAssAlignment: 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 + def test_unknown_returns_bottom_default(self): + assert _position_to_ass_alignment("unknown") == 2 + assert _position_to_ass_alignment("") == 2 + assert _position_to_ass_alignment("left") == 2 + assert _position_to_ass_alignment(None) == 2 class TestBuildAssStyle: diff --git a/tests/unit/test_render_subtitles_pure.py b/tests/unit/test_render_subtitles_pure.py index 4f73aac49..bdf4a6f08 100644 --- a/tests/unit/test_render_subtitles_pure.py +++ b/tests/unit/test_render_subtitles_pure.py @@ -66,15 +66,15 @@ class TestPositionToAssAlignment: """center → 居中(5).""" assert _position_to_ass_alignment("center") == 5 - def test_unknown_defaults_to_top(self): - """未知位置默认顶部(8).""" - assert _position_to_ass_alignment("unknown") == 8 - assert _position_to_ass_alignment("top_left") == 8 - assert _position_to_ass_alignment("bottom_right") == 8 + def test_unknown_defaults_to_bottom(self): + """未知位置默认底部(2).""" + assert _position_to_ass_alignment("unknown") == 2 + assert _position_to_ass_alignment("top_left") == 2 + assert _position_to_ass_alignment("bottom_right") == 2 - def test_empty_string_defaults_to_top(self): - """空字符串默认顶部.""" - assert _position_to_ass_alignment("") == 8 + def test_empty_string_defaults_to_bottom(self): + """空字符串默认底部.""" + assert _position_to_ass_alignment("") == 2 class TestBuildAssStyle: -- 2.54.0