fix: 标题/换行 + TTS replace模式静音原音 + 配音素材库静音原音 #1504

Merged
auto-approve-bot merged 2 commits from fix/title-slash-newline-and-replace-mute into develop 2026-08-25 23:06:00 +08:00
5 changed files with 82 additions and 0 deletions
@@ -824,6 +824,17 @@ class UnifiedRenderService:
)
audio_layer.clips.append(vo_clip)
# replace 模式:静音原视频音轨(main + broll 图层)
if tts_config.overlap_mode == "replace":
for layer in layers:
if layer.role in ("main", "broll"):
for clip in layer.clips:
clip.config["volume"] = 0
logger.info(
"TTS replace 模式:已静音原视频音轨: plan_id=%s",
self.plan.id,
)
logger.info(
"TTS 配音已添加: plan_id=%s voice_id=%s segments=%d total_%.2fs",
self.plan.id,
@@ -889,6 +900,16 @@ class UnifiedRenderService:
)
audio_layer.clips.append(vo_clip)
# 配音素材库默认替换原音:静音原视频音轨(main + broll 图层)
for layer in layers:
if layer.role in ("main", "broll"):
for clip in layer.clips:
clip.config["volume"] = 0
logger.info(
"配音素材库:已静音原视频音轨: plan_id=%s",
self.plan.id,
)
logger.info(
"配音素材库音频已添加到 audio 图层: plan_id=%s duration=%.2fs",
self.plan.id,
+2
View File
@@ -147,6 +147,8 @@ def escape_ass_text(text: str) -> str:
Returns:
转义后的 ASS 文本
"""
# 用户手动换行符(半角/全角斜杠)转为 ASS 硬换行(在自动换行之前优先处理)
text = text.replace("", "\\N").replace("/", "\\N")
# 将实际换行转为 ASS 硬换行
text = text.replace("\r\n", "\\N").replace("\n", "\\N").replace("\r", "\\N")
# 转义大括号(ASS 用它做样式覆盖标签)
@@ -218,6 +218,14 @@ class TestEscapeAssText:
def test_chinese_text(self):
assert escape_ass_text("你好世界") == "你好世界"
def test_slash_converted_to_newline(self):
"""半角斜杠 / 应转为 ASS 硬换行。"""
assert escape_ass_text("标题一/标题二") == "标题一\\N标题二"
def test_fullwidth_slash_converted_to_newline(self):
"""全角斜杠 应转为 ASS 硬换行。"""
assert escape_ass_text("标题一/标题二") == "标题一\\N标题二"
def test_backslash_n_in_input(self):
# 文本里本身有 \n 字符串(不是换行符)
result = escape_ass_text("\\n")
+13
View File
@@ -190,6 +190,19 @@ class TestEscapeAssText:
def test_chinese_text(self):
assert escape_ass_text("你好世界") == "你好世界"
def test_slash_converted_to_newline(self):
"""半角斜杠 / 应转为 ASS 硬换行。"""
assert escape_ass_text("第一行/第二行") == "第一行\\N第二行"
def test_fullwidth_slash_converted_to_newline(self):
"""全角斜杠 应转为 ASS 硬换行。"""
assert escape_ass_text("第一行/第二行") == "第一行\\N第二行"
def test_mixed_slashes_and_newlines(self):
"""斜杠和换行符都应转为硬换行。"""
result = escape_ass_text("A/B\nCD")
assert result == "A\\NB\\NC\\ND"
# ── 时间格式化 ────────────────────────────────────────────────────────────────
@@ -10,6 +10,8 @@
import sys
from pathlib import Path
import pytest
# Ensure packages is importable
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "packages"))
@@ -239,3 +241,39 @@ class TestFullStyleConsistency:
assert int(fields[17]) == 2
# Alignment = 8 (top)
assert int(fields[18]) == 8
class TestTitleSlashNewline:
"""用户输入 / 或 应触发标题换行。"""
def test_halfwidth_slash_in_title(self):
content = build_ass_content(
video_width=1080,
video_height=1920,
video_duration=10.0,
title_text="第一行/第二行",
title_config={"size": 48},
)
for line in content.splitlines():
if "Dialogue" in line and "TitleStyle" in line:
assert "\\N" in line, f"斜杠应转为换行: {line}"
assert "第一行" in line
assert "第二行" in line
break
else:
pytest.fail("未找到 TitleStyle Dialogue 行")
def test_fullwidth_slash_in_title(self):
content = build_ass_content(
video_width=1080,
video_height=1920,
video_duration=10.0,
title_text="第一行/第二行",
title_config={"size": 48},
)
for line in content.splitlines():
if "Dialogue" in line and "TitleStyle" in line:
assert "\\N" in line, f"全角斜杠应转为换行: {line}"
break
else:
pytest.fail("未找到 TitleStyle Dialogue 行")