Explainer
无脸解说是指在一段连续、由代码构建的动画世界(例如剖面图、放大的机制、手持物体)上叠加画外音,顶部显示字幕,无背景音乐。输出为 1080×1920 的 mp4 文件。
编写前请阅读以下文件:
script-craft.md:脚本是如何构建的(钩子、转折、想象、机制、翻转、行动号召)及其节奏。craft-rules.md:让视觉效果生动且有立体感的规则。一个合格的第一版通常会违反其中大部分规则。
难点在于每个节拍的视觉隐喻。第二难的是保持每一帧都在运动且有深度光感。代码反而最简单。
模式
- 一次性(默认): 从概念直接生成最终视频,不停顿。自行做出判断,并在最后汇报结果。
- 审查: 如果用户要求审查或批准,则停止两次:在脚本完成后和故事板完成后。根据用户意见修改并继续。
环境检查(一次性)
确保 ffmpeg 在 PATH 中、remotion/node_modules 存在(否则在 remotion/ 中运行 npm install),以及 .env 中包含 FISH_API_KEY(复制 .env.example)。如果没有 API 密钥,则用 voice --placeholder 构建所有内容,并告知用户最后一步是重新运行 voice 并渲染。
布局
projects/<concept>/(concept 是一个短横线连接的小写词,如 how-fridges-work):
script.txt、storyboard.mdcomposition.tsx(图层栈)、timeline.ts(TL)、captions.ts、words.ts(生成)、scenes/S01.tsx …public/vo.wav、vo/(原始录音和对齐文件)、out/(渲染结果、静帧)
核心库位于 remotion/src/core(场景通过 ../../../remotion/src/core 导入):
makeTimeline→TL.at("phrase")、TL.end("phrase")Captions、Drift、Bokeh、Vignette、handheld、Backdrop、Dust、FootnoteCamera(WorldSvg、camAt、toScreen)、Label、Sparks、Snow/Rain- 缓动函数、
ramp、keys、rand、字体SANS/MONO
工具:python3 bin/explainer.py …(运行 -h 查看帮助)
| 命令 | 功能 |
|---|---|
new <c> | 搭建项目骨架 |
voice <c> [--voice-id ID] [--speed 1.0] | 使用 Fish Audio 录制语音并生成带时间戳的 words.ts,同时输出 public/vo.wav(响度标准化至 −14 LUFS)。打印语速(wpm) |
voice <c> --placeholder [--wpm 200] | 生成无声音频和模拟时间戳,无需 API 调用 |
stills <c> <sec>… | 在指定时间点生成帧截图,输出为 out/ 下的联系表。请查看 |
studio <c> | 在 Remotion Studio 中实时预览(供用户使用) |
render <c> [--blur] | 以 60 fps 输出 mp4 文件至 out/。--blur 以 240 fps 渲染再平均为 60 fps,实现运动模糊(速度慢约 4 倍) |
qa <c> | 冻结检测:画面冻结比例 < 10%,无单次冻结 ≥ 0.3 秒。失败时退出码为 1,并列出每个冻结片段 |
工作流程
-
脚本。 运行
new <c>。- 如果用户提供了脚本,逐字使用(仅将数字和符号转换为词语)。
- 否则根据
script-craft.md编写script.txt:默认使用日常物品格式(270–320 词),产品和网络格式用于系统和网络。运行其检查清单。 - 审查模式:停止等待审批。
-
配音。 运行
voice <c>,然后检查打印的语速(wpm,目标 190–215;如果偏离,使用建议的--speed重新运行)。- 配音使用
--voice-id,否则使用FISH_VOICE_ID,否则使用 Fish 的默认语音。克隆语音的 ID 同样有效。 - 模型使用
--model,否则使用FISH_MODEL,否则使用s2.1-pro。 - 脚本以
words.ts为准,而非script.txt。 配音会拼出数字,TL.at()和字幕短语必须与口语单词匹配。
- 配音使用
-
故事板(
storyboard.md)。先填写“世界”部分,然后每个节拍一行。为节拍构思隐喻时,询问观众会看到什么(如果他们能):- 放大: 剖视图或爆炸视图。
- X 光: 物体内部。
- 慢放: 波传播、部件切换。
- 测量: 读数上升或下降。
“想象”场景成为“异地”世界,每个机制节拍都回到“主图”。运行故事板检查。
- 审查模式:停止等待审批。
-
构建。 按故事板每个镜头在
scenes/中编写一个场景文件,并逐一添加到composition.tsx。- 每个场景都是
t的纯函数,在窗口外返回null。 - 时序: 每个事件都绑定到
TL.at("exact words")。切勿使用原始秒数。 - 连续性: 共享同一示意图的节拍位于一个场景中,使用一个相机路径(A4)。
- 移动: 每个场景通过移动进入,与前一个场景的退出重叠(A2)。
- 每个场景的 SVG id 需添加前缀。
- 每个场景都是
-
迭代。 在每个镜头关键单词处运行
stills,并对照工艺规则 A 和 B 检查。然后运行render和qa。修复每个 FROZEN 片段(为其镜头添加运动载体 A1b)。手动编写captions.ts短语:每个短语一个高亮关键词(琥珀色,说出时弹跳)。 -
交付。 最终
render必须通过qa(如果用户追求最高质量,使用--blur)。向用户提供:- mp4 文件路径
- 脚本
- 故事板每个节拍的单行摘要
- 任何仍需要真实录音的占位配音
危险信号:停止并修复
| 想法 | 规则 |
|---|---|
| “这个节拍换个新场景能保持新鲜感” | C1:一个主图加上 1–2 个异地世界。数一数 |
| “在高潮处完全静止以营造悬念” | A1c:主体停止运动,但保持相机和环境运动 |
| “加一段背景音乐、颗粒效果或在高潮处加一个重音” | D、B8:仅语音,无颗粒效果;深度来自光照 |
| “淡入” / “淡出到下一场景” | A2:通过移动进入、退出和过渡 |
| “全局漂移就能让它保持活力” | A1b:每个镜头需要一个中等尺寸的移动物体,速度 10–60 px/s |
| “切近景来表现这个节拍”(同一示意图) | A4:一个世界,一个相机;交接关键帧 |
| “均匀照明也可以,这是示意图” | B1:光线衰减至暗部、轮廓光、暗角 |
| “激活时给整个画面染色” | B4:从原因传播,具有正面和流动效果 |
| “物体用矩形就够了” | B2:进行一次解剖绘制;构建拉出后会显露的结构 |
“看起来没问题,跳过 qa” | E:qa 必须通过;肉眼会忽略冻结 |