这次做了什么
《你看到的太阳,永远是过去的太阳》:竖屏 1080×1920、30fps、79.5s。前 50.9s 是 three.js 3D 宇宙科普 + SVG 2D 标注,「可,真的是这样吗?」之后转成 Canvas2D 手绘(夕阳教室 → 空秋千)。全程曼波配音 23 句 + 字幕,BGM《As_We_First_Met》踩点,音效全部程序合成。
flowchart TD
A["narration.json 旁白稿"] --> B["tts.py gen:每句 3–7 个 take"]
B --> C["asr.py:faster-whisper 转写"]
C --> D["tts.py pick:选最贴原文的 take,统一电平"]
D --> E["vo.json 时长表"]
E --> F["timeline.js:配音/字幕/画面/音效/BGM 自动化唯一来源"]
F --> G["单文件 HTML,?t= 可 seek"]
F --> H["export-events → mix.py 混音"]
G --> I["SwiftShader 8 分片逐帧渲染"]
I --> J["ffmpeg 合成 mp4"]
H --> J
曼波配音(MamboTTS)
- 引擎:在 MamboTTS 目录执行
./start_engine.sh,启动 GPT-SoVITS 的api.py,监听http://127.0.0.1:9880/。 - 调用:
POST /,JSON 参数text、text_language: "zh"、speed、top_k、top_p、temperature、cut_punc;返回 32kHz 单声道 wav,一句约 1–3 秒。 - 同一句多次生成质量差异很大(吞字、错音)。可靠做法:每句生成 3–7 个 take,用 MamboTTS 自带 venv 里的 faster-whisper small(CPU int8)逐个转写,选与原文相似度最高的。
- ASR 本身对同音字有误差(它/他、消失/消息、凝视/宁氏),相似度 ≥ 0.85 基本可用,可疑句子要人耳确认。
- 语速:科普段 1.0–1.08,反转后放慢到 0.85–0.92,情绪落差明显。
人声优先的混音
| 项 | 设置 |
|---|---|
| BGM 底电平 | −16 dB(相对原曲) |
| 人声期间 BGM 再压 | −8 dB(人声包络侧链,起 80 ms / 放 450 ms / 预读 60 ms) |
| 音效整体 | −3 dB,人声期间再压 3 dB |
| 实测余量 | 人声比 BGM ≥ 14.2 dB,比音效 ≥ 9.9 dB |
| 母版 | −16 LUFS,真峰 −2.6 dBTP |
反转处 BGM 静默但不丢拍
- BGM 拆成两段:第一段播到乐句线 50.93s,提前 1.5s 等功率淡出到 0。
- 静默时长取整 8 拍(8 × 0.53024 = 4.242s),只留曼波一句「可,真的是这样吗?」,不放任何音效。
- 第二段从曲中同一位置 50.93s 接着放,4 拍淡入。由于静默是整拍数,后半段成片时间 = 曲中时间 + GAP,画面和音效仍然落在原来的拍网格上(例如曲中 61.53s 的重音对应成片 65.78s 切到秋千)。
踩过的坑
- 叠加发光层的方框:three.js 里把光晕画在矩形 quad 上,太阳缩成一个点后光晕边缘仍有约 3% 亮度,提亮后能看到方框。修法:所有 glow 在半宽 72% 处开始衰减到 0,并按效果放大 quad。
- 标题压太阳:3D 物体位置变化后,固定 y 的大字会压上去。抽帧对照锚点调整版式。
- 过曝扫描阈值要随片子改:上一期偏暗,阈值 58;这一期有满屏太阳和明亮手绘,正常帧平均亮度到 123,阈值改成参数(默认 130)。判断真假坏帧看是否孤立尖峰,而不是看连续的亮段。
- 渲染继续用 SwiftShader(CPU WebGL):8 分片 2385 帧约 5 分钟,避免 GPU 显存不足导致的泛白/全黑。
分工
3D 宇宙(space.js + hud.js)与 2D 手绘(classroom.js)交给两个并行子代理,各自用独立的开发入口和端口抽静帧迭代;主线只维护 timeline.js、main.js、混音与渲染。接口提前定死(createSpace(canvas).render(t) 返回屏幕锚点,createClassroom(canvas).render(t)),合并时只出现了两处视觉问题。