Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 15e429ffb1 |
@@ -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",
|
||||
|
||||
@@ -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 换行逻辑验证
|
||||
# ============================================================
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user