feat: ASS字幕移到顶部安全区并加透明度(零视差不变)

- srt-to-dual-eye-ass: an2底部→an8顶部对齐,MarginV改为可配margin_top(默认120),
  字幕避开画面中央人脸高发区(B-1顶部安全区)
- 文字填充&H80FFFFFF→&HB3FFFFFF(约70%透明),描边纯黑→&H80000000半透明黑
- A-1零视差保持不变:左右眼水平相对位置一致,字幕固定屏幕平面
- 新增3条测试(顶部对齐/自定义margin_top/invoke读参数),ass节点覆盖率100%
- 新增docs/调查报告:双目视觉景深原理、字幕深度配置公式与遮挡解决方案
This commit is contained in:
2026-09-05 21:03:55 +08:00
parent 5c12bbcb74
commit 2e8bbb15a4
4 changed files with 482 additions and 14 deletions
+50
View File
@@ -666,6 +666,56 @@ def test_ass_parse_and_write(tmp_path) -> None:
assert r"第一行\N第二行" in content
def test_ass_top_aligned_and_translucent(tmp_path) -> None:
"""验证字幕默认渲染到顶部安全区且文字/描边带透明度。
B-1:对齐 an8(顶部居中),MarginV 取顶部安全边距默认 120;
透明度:文字填充 &HB3FFFFFF(约 70% 透明),描边 &H80000000(半透明黑),
而左右眼水平相对位置一致保持零视差(A-1,字幕在屏幕平面)。"""
entries = parse_srt(SAMPLE_SRT)
output = tmp_path / "out.ass"
write_ass(entries, output, "3840x1920")
content = output.read_text(encoding="utf-8")
# 顶部对齐 an8、顶部安全边距默认 120。
assert r"{\an8}" in content
assert r"{\an2}" not in content
# 样式行:MarginV=120(顶部安全区),填充 &HB3FFFFFF,描边 &H80000000。
assert "&HB3FFFFFF" in content
assert "&H80000000" in content
assert ",0,0,0,0,50,100,0,0,1,4,0,8,50,1920,120,1" in content
# 左右眼两行文本一致(水平相对位置相同 → 零视差/A-1)。
assert content.count(r"第一行\N第二行") == 2
def test_ass_write_custom_margin_top(tmp_path) -> None:
"""验证可通过 margin_top 覆盖顶部安全边距(不同分辨率下微调)。"""
entries = parse_srt(SAMPLE_SRT)
output = tmp_path / "out.ass"
write_ass(entries, output, "1920x1080", margin_top=200)
content = output.read_text(encoding="utf-8")
# 1920 宽:每半幅 960MarginV 用自定义 200。
assert ",0,0,0,0,50,100,0,0,1,4,0,8,50,960,200,1" in content
def test_ass_invoke_reads_margin_top(tmp_path) -> None:
"""验证 invoke 从请求参数读取 margin_top 并反映到产物样式。"""
source = tmp_path / "in.srt"
source.write_text(SAMPLE_SRT, encoding="utf-8")
response = ass_invoke(
InvokeRequest(
run_id="run_mt",
node_instance_id="ni_mt",
inputs={"cn_srt_uri": str(source)},
params={"resolution": "1920x1080", "margin_top": 90},
output_dir=str(tmp_path / "out"),
)
)
assert response.status == "completed"
content = Path(response.outputs["ass_uri"]).read_text(encoding="utf-8")
assert "an8" in content
assert ",0,0,0,0,50,100,0,0,1,4,0,8,50,960,90,1" in content
def test_ass_parse_malformed(tmp_path) -> None:
"""验证畸形 SRT 不会抛出异常且返回空条目或忽略坏行。"""
source = tmp_path / "bad.srt"