What-If Reel Maker 短片制作
用户只需提供一行创意(例如:“如果月球消失 5 秒钟会怎样?”)。所有其他工作均由 AI 自动完成,最终交付 final.mp4(1080×1920,30 fps,60 秒,含配音、字幕、音效)以及用于 Instagram 的 caption.txt。除非创意本身不清楚,否则不要提问。AI 将自行选取合理的默认值,并告知用户选择了哪些默认值。
频道风格为低多边形 3D 实景,基于代码(Three.js)构建。包含左上角倒计时 HUD、斜体衬线字幕、白边黑底的“X 光”轮廓视图、宏观镜头(行星/太空/微观)、逐步升级的后果,以及带有关注按钮的结尾卡片。频道 ID:@the.whatif.studio。
0. 一次性初始化(检查并仅安装缺失的组件)
要求:Node 18+、Python 3.10+ 以及 ffmpeg 已加入 PATH(winget install ffmpeg / brew install ffmpeg)。
项目根目录:~/whatif/(如不存在则创建)。共享引擎文件存放在根目录,每个视频的项目文件存放在 videos/<slug>/ 目录下。
mkdir -p ~/whatif/models ~/whatif/videos && cd ~/whatif
npm init -y && npm i [email protected] playwright @fontsource/cormorant-garamond @fontsource/ibm-plex-mono
npx playwright install chromium
pip install kokoro-onnx soundfile scipy numpy
curl -L -o models/kokoro-v1.0.onnx https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/kokoro-v1.0.onnx
curl -L -o models/voices-v1.0.bin https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/voices-v1.0.bin
然后,如果根目录中尚不存在引擎文件,请从此技能的 engine/ 文件夹中复制(参见第 9 节)。在 Windows 上,使用 python 而非 python3。
1. 物理第一(最重要)
评论区观众会检查科学性。在编写任何内容之前:
- 计算出真实发生的物理过程。将推理过程和所有数字写入
videos/<slug>/physics.md,并使用 Python 快速计算验证。 - 寻找反直觉的转折点(例如:重力消失后,最初看起来什么也没发生,因为被挤压的岩石、水和空气向外弹射,这种推力感觉就像重力)。这个转折点就是视频的“钩子”。
- 在条件允许时,通过网络搜索验证关键声明。如果某些内容不确定,请使用模糊措辞(如“持续数分钟”、“数公里”),切勿编造精确数字。
2. 脚本(videos/<slug>/script.json)
遵循以下 60 秒节拍模板(如果故事需要,可调整时间):
| 时间 | 节拍 | 视觉 | HUD |
|---|---|---|---|
| 0–8 | 正常世界,钩子 | 缓慢推轨穿过场景;标题 0.2–3.9 秒 | <事件> 即将发生 倒计时 8.00 秒→0,副标题 = 地点 |
| 8.0 | 事件发生 | 涟漪 + 色度脉冲 | — |
| 8–28 | 事件窗口,以慢速展示(例如 5 真实秒以 4 倍速播放) | 先正常视图,然后 X 光视图 | <事件结束> 剩余 5.00 秒,副标题 以 4 倍慢速播放 · <实时统计> |
| 16–24 | 隐藏机制 | 轮廓模式 + 发光粒子 | 同上 |
| 24–28 | 宏观镜头 | 独立场景(行星/太空/细胞),白色闪入闪出 | 同上 |
| 28 | 回归 | 闪光 + 轰鸣 | <事物> 是 / 回归 |
| 28–52 | 后果逐步升级 | 震动、裂缝、波浪、碎片等 | 关键统计(例如 176 公里/小时) |
| ~46 | 点睛之笔 | — | “是的。<后果>。” |
| 51.2–52 | 渐隐至黑屏 | ||
| 52–60 | 结尾卡片 | 事实陈述 + 关注获取更多 What If,按钮点击 ~57.3 秒 | — |
旁白:14–16 行,每秒不超过 3 个词,冷静的纪录片语调。第 1 行在 0.3 秒处,是标题问题。结尾卡片有两行:一行是一句话的事实陈述(约 52.7 秒处),另一行是“关注获取更多 what if。”(约 56.6 秒处)。字幕是旁白行的缩写版本(不超过 2 行,使用 <br> 分隔)。
{
"handle": "@the.whatif.studio",
"voice": "am_michael",
"title": "如果重力消失<br>5 秒钟会怎样?",
"titleEnd": 3.9,
"endTitle": "如果重力消失 5 秒钟会怎样?",
"endFact": "你从未感受到重力。你感受到的是地面在向上推你。",
"duration": 60, "endStart": 52, "press": 57.3,
"narration": [[0.3, "如果重力消失……持续五秒钟呢?"], [4.1, "此刻,重力正在牵引着这个公园里的每一个原子。"]],
"captions": [[4.1, 7.9, "此刻,重力正在牵引着<br>这个公园里的每一个原子。"]]
}
3. 先制作语音,再调整时间
运行 python3 gen.py <slug>。它会生成 videos/<slug>/audio/lNN.wav 和 durs.json,并打印每句旁白的时长。如果某句旁白与下一句发生时间重叠或与某个节拍冲突,请缩短文本或移动其位置,然后重新运行。然后将每条字幕设置为 [start, start + duration + 0.35, text]。可选语音:am_michael(默认男声)、af_heart(最佳女声)、bm_george(英式男声)。
4. 构建场景(videos/<slug>/scene.js)
为每个视频编写全新的场景。必须导出 build(h)、update(t, h) 和 hud(t)(模板见第 9 节)。规则:
- 确定性:在构建时仅使用
h.rnd()/h.R(a,b)(基于种子的随机)。update中的所有内容都是关于t的纯函数。切勿使用Math.random、Date或performance.now。 - 通过世界时间实现慢动作:
w = t<8 ? t : t<28 ? 8+(t-8)/4 : 13+(t-28)。使用w来驱动人物、水和云朵的动画。 - 视觉风格:使用
flatShading的低多边形基本几何体。松散地模拟真实地点(码头、公园、街道、海滩)。使用盒子作为建筑物(使用画布纹理模拟窗户)、盒子和球体组合作为颜色各异的人物、实例化树木、带有白色球体的黑色灯柱。添加渐变天空着色器、精灵云朵、用于景深的THREE.Fog,以及一个带有阴影的太阳DirectionalLight(阴影相机范围 ±45)和HemisphereLight。 - 相机:垂直视场角约 58 度,眼睛高度 1.7–2.2 米,缓慢推轨和轻柔倾斜。在后果展示阶段,添加由正弦波叠加产生的震动效果。
- X 光阶段:返回
outline: 0→1(约 0.6 秒内淡入)。开启时,将建筑纹理映射替换为null并隐藏云朵(防止噪点边缘)。可选择返回overScene:一个叠加的Points场景(depthTest为 false),用于显示条纹/粒子。 - 宏观镜头:构建第二个场景和相机,并在 24–28 秒期间以
scene/camera形式返回。 - 从
update返回的 FX 效果:flash(0–1 白色)、chroma、ripple+rp(涟漪半径)、fade(51.2–52 秒间 0→1)。 - 性能:保持绘制调用在 400 次以下(使用实例化),避免在相机附近使用大量大的透明精灵,因为这会导致非常严重的过度绘制。
- 保持文本在底部 20% 和右侧边缘之外(Instagram UI 区域)。字幕位于 y≈1240 处。
5. 预览然后修正
运行 node render.js preview <slug> 2,6,10,18,22,26,30,40,47,55。它会将 JPEG 图片写入 videos/<slug>/prev/。打开图片并检查:场景是否可辨识、字幕是否清晰、轮廓模式是否干净、宏观镜头是否构图良好、后果展示是否具有戏剧性?修复问题并再次预览。仅当静态帧预览看起来满意时,才渲染完整视频。
6. 渲染各层
场景(主体画面)和文字(覆盖层)分开渲染,这样文字修正无需重新渲染 3D 场景。
# 主体画面:跨进程分配(每 2 个 CPU 核心一个进程),每个进程占用不同的 PORT;仅渲染 0..endStart 范围(其余为黑场)
LAYER=plate PORT=8781 node render.js full <slug> plate1.mp4 0 26
LAYER=plate PORT=8782 node render.js full <slug> plate2.mp4 26 52
LAYER=overlay PORT=8783 node render.js full <slug> overlay.mov 0 60
在后台运行这些命令并监控日志。如果 WebGL 报告使用 SwiftShader(慢速、CPU 渲染),请尝试设置 HEADFUL=1 以使用真实 GPU。
7. 音效(videos/<slug>/mix.py)
从第 9 节复制混音模板,并根据节拍编辑 EVENTS 块:环境音、倒计时滴答声、事件前的渐强音效、事件发生和回归时的 boom 轰鸣、X 光阶段的 shimmer 闪烁、进入宏观镜头的 whoosh 呼啸、后果展示阶段的隆隆声/噼啪声等其他音效、结尾垫音以及按钮点击 pop 声。脚本会自动放置旁白,并在语音下方降低音效音量。运行脚本,然后进行标准化处理:
python3 videos/<slug>/mix.py <slug>
ffmpeg -y -i videos/<slug>/mix.wav -af loudnorm=I=-14:TP=-1.5:LRA=11 -ar 48000 videos/<slug>/mix_norm.wav
8. 合成并交付
cd videos/<slug>
ffmpeg -y -i plate1.mp4 -i plate2.mp4 -f lavfi -t 8 -i "color=black:s=1080x1920:r=30" -i overlay.mov -i mix_norm.wav \
-filter_complex "[0:v][1:v][2:v]concat=n=3:v=1:a=0,format=yuv420p,setsar=1[b];[b][3:v]overlay=format=auto,format=yuv420p[v]" \
-map "[v]" -map 4:a -c:v libx264 -preset veryfast -crf 23 -c:a aac -b:a 192k -movflags +faststart -shortest final.mp4
(黑场片段长度 = 总时长 − endStart。)从 final.mp4 中提取 8–10 帧进行检查,之后再交付。编写 caption.txt:一行钩子,2–3 句真实的物理原理,“关注 @the.whatif.studio 获取更多 what if”,以及 12–15 个话题标签(#whatif #physics #science #3danimation …)。告知用户,他们可以在 CapCut 中以 15–20% 的音量添加背景音乐。
9. 引擎文件
可复用的引擎存放在此技能的 engine/ 文件夹中。从中复制文件 —— 不要重新输入。一个共享引擎被所有视频复用;每个视频需要复制并编辑两个特定文件。
共享引擎(一次性复制到 ~/whatif/):
engine/index.html→~/whatif/index.htmlengine/main.js→~/whatif/main.jsengine/render.js→~/whatif/render.jsengine/gen.py→~/whatif/gen.py
每个视频所需文件(为每个新视频复制,然后编辑):
engine/scene.template.js→videos/<slug>/scene.js— 为每个视频重写场景(参见第 4 节)。必须导出build(h)、update(t, h)、hud(t)。engine/mix.py→videos/<slug>/mix.py— 编辑 EVENTS 块以匹配节拍(参见第 7 节)。
# SKILL_DIR = 此技能的文件夹;从项目根目录 (~/whatif) 运行
cp "$SKILL_DIR"/engine/{index.html,main.js,render.js,gen.py} ~/whatif/
cp "$SKILL_DIR"/engine/scene.template.js videos/<slug>/scene.js
cp "$SKILL_DIR"/engine/mix.py videos/<slug>/mix.py
交付前的质量检查清单
- 物理原理已核实,数字已计算;转折点在前 15 秒内清晰展现。
- 每句旁白都完美契合其节拍,无时间重叠;字幕与旁白匹配。
- 预览效果良好;最终视频在 8–10 个关键帧上进行了抽查。
- 频道 ID 显示为 @the.whatif.studio;结尾卡片工作正常,按钮点击发生在“关注获取更多 what if”之后。
- 响度约为 −14 LUFS;人声音量明显高于音效。