Compare commits

...

1 Commits

Author SHA1 Message Date
saas-backend-agent 15e429ffb1 fix: 标题默认位置 fallback 改为 bottom,与前端 DEFAULT_TITLE_SETTINGS 对齐
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 24s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 24s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 45s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m29s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 1m26s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m42s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 1m48s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m10s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m53s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 53s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 14s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 33s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 7m39s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 3s
- 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 个标题/字幕相关测试全通过
2026-09-04 15:14:17 +08:00
6 changed files with 69 additions and 27 deletions
@@ -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",
+4 -5
View File
@@ -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(
@@ -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 换行逻辑验证
# ============================================================
+46 -4
View File
@@ -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 应为 bottomalignment=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 应为 2bottom)。"""
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"
+5 -5
View File
@@ -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:
+8 -8
View File
@@ -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: