make-video 是做什么的?
将创意转换为渲染且经过检查的 MP4,仅使用免费的本地工具。从此技能创建的项目拥有其自己的规则(在 AGENTS.md 中),请遵循它们(动画纯度、useScale、sec()、文件夹布局)。
0. 设置一个视频项目(一次性)
如果当前文件夹没有 tools/make-story.sh,则它还不是一个视频项目:
-
首先检查机器:
uname -sm(需要 Darwin arm64)、node --version(20+)、brew --version、df -h ~(约需要 8 GB 可用空间)。在其他操作系统上,在继续之前说明哪些功能无法工作(Chatterbox 和 take 检查器在 Apple GPU 上运行)。如果缺少 Node 或 Homebrew,为用户提供一个安装命令并等待。 -
告知用户将要下载的内容,并等待他们的确认。 未经确认切勿开始下载。显示此表格(全部免费,来自官方源):
下载内容 大小 用途 存放位置 npm 包(Remotion 等) ~500 MB 渲染 node_modules/ffmpeg、uv、FluidSynth(Homebrew,仅当缺失时) ~150 MB 音视频工具 Homebrew Python 包(PyTorch、Chatterbox…) ~1.5 GB 语音、质量检查 .venv/GeneralUser GS 音色库 ~30 MB 音乐 tools/soundfonts/Chatterbox 权重 ~3 GB 语音 Hugging Face 缓存( ~/.cache/huggingface),所有项目共享Whisper small.en ~590 MB 字幕 tools/models/Whisper large-v3-turbo(可选的 take 检查器) ~1.6 GB 重新录制不清晰的台词 Hugging Face 缓存 总计约 6 GB,需要 10-20 分钟。提供更轻量的选项:
SKIP_TAKE_CHECK=1跳过 take 检查器(总计 4.4 GB)。询问在哪里创建项目(默认~/videos)。 -
在他们确认后,将此 SKILL.md 旁边的
template/文件夹复制到项目中,包括点文件(cp -R <skill dir>/template/. <dest>/),然后执行git init。 -
在后台运行
tools/setup.sh,并让用户了解进度:说明当前进行到哪一步(它会打印▶行),并在大文件下载期间告知进度(du -shL ~/.cache/huggingface/hub/models--ResembleAI--chatterbox,目标约 3 GB)。重新运行是安全的:已完成的步骤会被跳过,中断的下载会继续。如果下载停滞(5 分钟内大小没有增长),停止它并重新运行 setup。如果失败,阅读错误信息,修复原因(缺少工具、磁盘空间、网络)并重新运行;不要绕过缺失的模型。 -
验证它能工作:
tools/make-story.sh stories/fridge-drama FridgeDrama将在约 4 分钟内生成 33 秒的示例视频(三个蔬菜在冰箱里争吵)。向用户展示out/FridgeDrama.mp4,然后询问他们想要制作什么。
工具会自动找到模型(Chatterbox 和 take 检查器来自 Hugging Face 缓存,字幕来自 tools/models/),因此设置后无需进行任何配置。第二个项目会重用缓存的权重,因此其设置会快得多。
1. 锁定简报(仅在缺失时询问)
| 字段 | 默认值 |
|---|---|
| 平台/格式 | Reel/Short:1080×1920,30 fps · YouTube:1920×1080,30 fps · Feed 帖子:1080×1350 |
| 时长 | Reel/Short:15-45 秒 · YouTube:3-10 分钟 |
| 类型 | 故事(角色交谈,因此使用 §3B)或 动态图形(文本、数据、图表,因此使用 §3A) |
| 主题 + 单一要点 | 观众应记住的唯一一件事 |
| 音频 | 语音(Chatterbox)、音乐 + 音效(tools/music.py)或仅音乐 |
| 语音 | Chatterbox 的内置声音,或用户自己录音的克隆(§3B)。未经他人同意切勿克隆。 |
2. 在编写任何代码之前编写脚本
时长预算: 以 Chatterbox 的语速,英语口语约每秒 2.6 个单词。目标时长 ≈ 单词数 ÷ 2.6 + Σ 停顿 + 片尾。在生成之前进行检查。(冰箱戏剧:62 个单词 + 6.4 秒停顿 + 3 秒片尾 = 33 秒。)
竖屏短视频的节奏规则:
- 第 0 帧开始挂钩画面和音频。前 1.5 秒决定留存率,第 0 帧通常是缩略图,所以永远不要从空白或淡入帧开始。
- 每行 ≤ 12 个单词。字幕一次显示 ≤ 3 个单词。
- 每秒都有移动。
- 1080×1920 的安全区: 任何重要内容不得高于 y=220 或低于 y=1540(平台 UI),并且低于 y≈1000 时不得在 x≈940 右侧(点赞、评论、分享按钮沿右边缘向下排列——
tools/qa.py --layout会检查失败)。将主持人放在左下角。 - 以回报或笑点结束,然后是 2-3 秒的行动号召卡片。
讲解模板(“它是如何工作的”和开发者技巧 Reels,45-60 秒,120-160 个单词):
- 挂钩(≤ 3 秒):一个令人惊讶的真实事实或结果。
- “这就是它实际工作的方式”:一句打开解释的话。
- 2-4 个节拍,每个包含一个数字 + 一个类比(“视频只是每秒 30 张照片,就像翻页书”)。
- “但这里有个问题”:转折、难题或常见错误。
- 一行回报,然后是一个明确的行动号召(保存、分享或关注)。
然后,列出脚本下的每一个事实主张及其来源链接,标记任何你无法验证的内容,并在配音前修复或删除它。当一个数字或主张出现时,在屏幕上显示来源(小号,卡片底部)。
Shorts/Reels 的制作清单:
- 可见的进度。 分步视频使用
<StepCard steps={[{at: 0, title: "Download it"}, …]} />(src/components/StepCard.tsx):一个“STEP 2/4”胶囊和分段条。 - 卡拉 OK 字幕(可选样式)。
<Karaoke words={captions} keywords={["free"]} style={{left: 60, right: 140, top: 1180}} />(src/components/Karaoke.tsx):2-5 个单词的整个块显示在屏幕上,正在说的单词实心,即将出现的单词灰色,关键词和重读单词使用强调色,以便观众在静音自动播放时可以提前阅读。传递qa以便布局检查能看到它。 - 系列标签。 左上角的情节标记(“PART 2”)让帖子看起来容易狂看。
- 显示对比。 之前/之后或“我们 vs 他们”作为双栏卡片比演讲效果更好。
- 故事节拍胜过列表。 “尝试 1 失败 → 尝试 2 失败 → 什么有效”比列出功能更能吸引注意力。
- 在 CTA 之前放回报。 一句人性化的话,然后是行动号召。
- 只有当背后有真实的东西(指南、提示、仓库)在等着时,才承诺“评论关键字,我会 DM 你”的奖励。
面向 TTS 的写作:
- 不要以“…”开头一行:Chatterbox 会即兴添加一个填充词。
- 不要以裸数字词开头一行(“Two. The face”变成了“to the face”)。写“Second, …”。
- 按照实际发音写出数字和奇怪的名称(“five point six”,“X Z”),并将真实文本放入该行的
caption字段。 - 四舍五入大数字(“over 600”);TTS 会混淆“646”和“87,000”。
- 挂钩中的短词会模糊(“This repo”被听成“The three”)。重新措辞,直到 take 检查器能正确识别。
- 不要让多个声音同时喊同一个短词;混音会使其模糊。
- 标点符号指导表达:“?”提升,“.”落下,大写字母增加强调。
3A. 动态图形视频
- 复制
src/compositions/_Template→src/compositions/<Name>,每种节拍类型(Hook、Point、Stat、List、CTA)对应一个组件。 - 从脚本计算时长(节拍之和减去转场),就像
getSampleIntroDuration所做的那样。 - 在
src/Root.tsx中使用内联defaultProps注册它。 - 仅音乐:编写一个包含
totalMs、outroStartMs和events的 JSON 时间线,然后运行tools/music.py。
3B. 配音故事(角色 + 字幕 + 音乐)
创建 stories/<slug>/script.json(复制 stories/fridge-drama/script.json):
characters:color、exaggeration(0.3 平静 → 0.9 戏剧化),以及可选的refVoice:一个干净的 10-30 秒 WAV 文件,位于tools/voices/中用于克隆。没有它,则使用 Chatterbox 的内置声音。要克隆用户的声音:他们在安静的房间中自然交谈 60-90 秒,然后裁剪出一个干净的 10-15 秒片段,没有音乐或回声(ffmpeg -ss <start> -t 12 -i note.m4a -af highpass=f=80,loudnorm=I=-18 -ar 24000 tools/voices/me.wav)。lines:speaker、text、pauseAfter(秒),可选的event+eventAt: "start"用于标记故事节拍(门开了、躲起来…),动画和音乐都以此为关键帧。- 每行可选的声音指导:
emotion(calm|neutral|warm|excited|dramatic|whisper)、pace(slow|normal|fast)、emphasis(用大写表示并在字幕中高亮显示的单词)、pauseBefore(秒)。"pace": "slow"加上 0.4 秒的停顿可以使每个词都清晰。
然后运行一个命令:
tools/make-story.sh stories/<slug> <CompositionId> # 语音 → 字幕 → 音乐/SFX → lint → 渲染 → 母带 → 质量检查
SKIP_VOICE=1 tools/make-story.sh … # 仅迭代视觉效果
SAMPLE=15 tools/make-story.sh … # 渲染前 15 秒以供批准,然后停止
| 工具 | 输出 |
|---|---|
tools/voice.py | public/<slug>/lines/*.wav(按文本和语音设置缓存)、public/<slug>/voice.wav、stories/<slug>/timeline.json(行时间、事件、每帧嘴型包络)。安装了 take 检查器后,Whisper 会听每一句话并重新录制不清晰的句子(VOICE_TAKES,默认为 3)。 |
tools/captions.mjs | public/<slug>/captions.json(来自 Whisper 的单词时间,来自脚本的拼写)、stories/<slug>/captions-report.json(每行的词错误率和匹配词、Whisper 遗漏的“填充”部分、整体 matchRate) |
tools/music.py | public/<slug>/music.wav(MIDI → FluidSynth,适配剪辑)、public/<slug>/sfx.wav |
tools/qa.py | stories/<slug>/qa/report.md:PASS / FIX / FAIL |
从 timeline.json / captions.json 构建组合(参见 src/compositions/FridgeDrama/data.ts)。使用 mouthOpen() 驱动嘴型,视线朝向活跃的说话者,通过 timeline.events.* 驱动事件,时长来自 timeline.durationInFrames。
任何时间变化(文本或停顿)都需要重新生成字幕。 字幕时间是绝对的,否则会漂移。make-story.sh 总是重新运行字幕。
4. 验证(始终在声称完成之前进行)
tools/make-story.sh 以 tools/qa.py 结束,如果出现 FAIL 则退出并返回非零值。它检查文件、响度(-14 ±1 LUFS,真峰值 ≤ -1)、字幕准确性、节拍剪辑(≤ 120 毫秒)、冻结片段(≤ 3 秒)、视觉多样性(最长相同外观的运行 ≤ 5 秒)以及重复屏幕文本的字幕。LAYOUT=<CompId> 还会根据死区和面部检查字幕(组合需要一个 qa 属性,用 #FF00FF 勾勒字幕,用 #00FFFF 勾勒面部)。阅读 stories/<slug>/qa/report.md 并修复每一个 FAIL 和 FIX。
然后目视检查:
npm run lint。- 如果
stories/<slug>/captions-report.json中任何一行的 WER > 0.2,则可能含糊不清:重新措辞并重新生成(由于缓存,成本低)。Whisper 遗漏的单词仅放置在语音部分上方的间隙中,较长的单词获得更多时间,并列在filled下:听那些部分。匹配率低于 70% 时,脚本和音频不一致,因此在继续之前重新配音。 - 在关键节拍(挂钩、每个说话者、每个事件、片尾)渲染静态帧,将它们合并成一张联系表,然后查看它:
检查角色大小、安全区、覆盖角色的叠加层、片尾后面的遗留物、字幕同步以及文本大小(标签 ≥ 28 像素,正文 ≥ 54 像素,宽度为 1080)。npx remotion still <Id> out/stills/f<N>.png --frame=<N> --scale=0.4 ffmpeg -i a.png -i b.png … -filter_complex hstack=inputs=K out/stills/contact.png - 从最终的 MP4 中提取 4-5 帧并查看它们。
- 对于任何重要内容,请让一个全新的子代理在没有任何背景的情况下评论联系表。新鲜的视角能发现制作者遗漏的问题。
5. 交付
向用户提供 MP4、标题、说明、3-5 个标签和一个缩略图帧。未经明确批准,切勿发布或上传任何内容。 提醒他们在使用克隆声音或 AI 面部时,勾选 YouTube 的“修改或合成内容”标签。
能节省数小时的规则
- 一次只渲染一个。 同时进行多个 Remotion 渲染会使机器过载数小时。管道已传递
--timeout=180000 --concurrency=2。 - 使用
npx remotion add <pkg>添加 Remotion 包,永远不要使用普通的npm i,以保持版本匹配。 - 响度:对话 −16 LUFS,音乐背景约 −24,最终母带 −14(两遍,已在
make-story.sh中)。保持语音至少比音乐高出 10 dB。 - 只有当
defaultProps是Root.tsx中的内联对象时,Studio 才能保存属性编辑。 - 编写覆盖行的 zsh 循环,使用
while read -r a b c; do …; done <<EOF,因为for x in "a b c"会中断。
更多经验教训,以及如何添加你自己的:reference/lessons.md。
测量成本(M1 Max,64 GB)
| 步骤 | 时间 |
|---|---|
| Chatterbox 模型加载 | 每次运行约 20 秒(每行都缓存时跳过) |
| 语音生成 | 第一行约 55 秒(预热),之后约 3 倍实时 |
| Take 检查 | 每个需要重录的句子约 20-30 秒 |
| 字幕(Whisper small.en,WebGPU) | 11 行约 10 秒 |
| 音乐 + 音效 | 2-5 秒 |
| 渲染 1080×1920,33 秒 | 约 97 秒(每秒视频约 3 秒) |
| 完整管道 | 缓存语音时约 2 分钟;从头开始约 4 分钟 |
make-video 由 TheNoumanDev 创建,来自 zero-dollar-studio。它是 @0x45.dev YouTube 频道背后的管道。