这次做了什么

《你看到的太阳,永远是过去的太阳》:竖屏 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)),合并时只出现了两处视觉问题。