What-If Reel Maker 是做什麼的?
使用者只說一句(例如「如果月球消失了 5 秒」),其他一切都由你完成,最終交出 final.mp4(1080×1920、30 fps、60 秒、附旁白、字幕、音效)以及供 Instagram 使用的 caption.txt。除非點子不明確,否則不要提問。選用合理的預設值,並告知你選擇了哪些。
品牌風格是以程式建立的低多邊形 3D 真實世界地點(Three.js)。左上角有倒數 HUD、斜體襯線字幕、白邊黑底的「X 光」段落、大景別鏡頭(行星/太空/微觀)、逐漸升級的後果,以及一個附追蹤按鈕的結尾卡。帳號:@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 | 結尾卡 | 事實行 + 關注以獲取更多如果,按鈕按壓 ~57.3 | — |
旁白:14–16 行,每秒 ≤3 字,冷靜紀錄片語氣。第 1 行在 0.3 秒處是標題問題。結尾卡有兩行:一句話的事實(~52.7)和「關注以獲取更多如果。」(~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,以及一個帶陰影(陰影相機 ±45)的DirectionalLight太陽 +HemisphereLight。 - 相機:垂直 FOV ~58,視線高度 1.7–2.2 m,緩慢推軌與溫和傾斜。在後果階段,加入由正弦波疊加產生的震動。
- 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. 渲染圖層
場景(plate)和文字(overlay)分開渲染,這樣修正文字就不需要重新渲染 3D。
# plate:跨程序分割(每 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 區塊以符合節拍:環境音、倒數滴答聲、事件前的 crescendo、事件發生與回歸時的 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 以獲取更多如果」,以及 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 幀。
- 帳號顯示 @the.whatif.studio;結尾卡正常運作,按鈕按壓恰好在「關注以獲取更多如果」之後。
- 響度約 −14 LUFS;語音明顯高於音效。