卡通 3D 動畫故事短片 (three.js + Remotion)
成果產出:一個 1080x1920、30 fps、45-65 秒的短片。以特定參考風格講述一個原創的真實故事:包含一個過去式的荒誕鉤子、嘲笑的反對者、「你看吧」的轉折點,然後是一句總結性的結尾。扁平卡通卡片立於一個帶有墨線輪廓的繪畫式 3D 世界中,搭配在冷色陰影襯托下的暖色定向光源、鏡頭模糊效果以及永不中斷的連續鏡頭。旁白語速較快(約每秒 3.7 字),搭配逐字字幕、原創配樂與合成音效,最終母帶處理至 -14 LUFS。
實作範例:「假購物者」 (1937 年,Sylvan Goldman 僱用演員來推他的新購物車),相關檔案位於 project/src/videos/fake-shoppers/ 和 project/stories/fake-shoppers/。
基本規則 (務必遵守)
- 故事、角色、配音與音樂皆為原創。 參考影片僅用於視覺風格與公式,絕不可使用其腳本、角色或鏡頭畫面。切勿在任何影片、標題或描述中提到該參考頻道的名稱。參考影片是私人學習材料:絕不允許上傳或傳送它們。
- 事實需經過研究並附上來源,並記錄在故事
SCRIPT.md檔案的頂端。笑話應為誇飾或基於真實事實,切勿將虛構的歷史當作事實呈現。 - 僅使用免費的本機工具。 使用 edge-tts 或使用者自己的聲音;不使用任何付費產生器。在任何使用 API 連接器產生費用前,務必先取得使用者同意。
- 配音中無停頓 (將間隙剪至 0.07 秒,或句子結尾 0.2 秒)。字幕需開啟 (參照參考風格)。影片需在最後一個字結束後 ≤0.5 秒內結束,無片尾或 Call to Action。
- 在選擇主題前,請先檢查
references/TOPICS_DONE.md。 - 兩個審批關卡,然後「直接執行」 (沿用原始技能規則):
- 腳本: 展示腳本(附帶來源),並等待使用者確認腳本後,再進行配音與分鏡規劃。之後無需再次請示,直接進行到關卡 2。
- 審查板: 在進行完整渲染前,使用者會觀看一半尺寸的草稿,並在特定時刻或場景上釘上筆記,然後按下「看起來不錯」(步驟 8)。在此之前會重複修改;在審批通過前不得進行完整渲染。
- 使用者的品味與已學習的檢查事項 (來自過去的審查板)。在撰寫腳本(步驟 2)、分鏡規劃(步驟 4)、場景設定(步驟 5)與角色設定(步驟 6)前,請閱讀兩個 PREFERENCES 檔案。在步驟 8 時,需要與 可讀性陷阱檢查 一起檢查這兩個 CHECKS 檔案:
- 所有影片技能共享的檔案:
~/video-editor/review-board/PREFERENCES.md、~/video-editor/review-board/CHECKS.md; - 此風格專用檔案:
references/PREFERENCES.md、references/CHECKS.md。
- 所有影片技能共享的檔案:
- 接手一個現有故事: 請先執行
node ~/video-editor/review-board/cli.mjs show stories/<name>/review。使用者在無連線階段時發送的筆記,需優先處理(步驟 8.5)。 - 僅進行一次完整渲染。 在背景啟動渲染程序,告知使用者預估完成時間(1080p 約每秒 4 幀,2K 渲染 60 秒約需 10 分鐘),且不需輪詢:任務完成時會自動回報。使用
--scale 0.5參數的預覽則可無限次重複進行。
此 Mac 環境 (設定與測試於 2026-11-07)
- 工作目錄:
~/video-editor/toon3d-shorts/,由setup.sh建立。包含src/engine(通用)、src/videos/<name>/(每個影片一個資料夾)、stories/<name>/(腳本、配音、提示、配樂)、audio/(合成套件 + 混音器)、tools/measure.py、scripts/和public/。此技能的project/資料夾中有一份副本。任何位置可使用以下指令重新建立:bash ~/.claude/skills/toon3d-story-shorts/setup.sh <dir>,它會執行 npm install (固定版本) 和pip install --user scipy。 - Node 24 + Remotion 4.0.539: 透過 ANGLE/Metal 使用 GPU。在約 20 秒的打包後,每張靜態圖片約需 1.3 秒渲染;一個完整的 60 秒預覽在
--scale 0.5參數下約需 45 秒。 - Python 3.9: numpy, PIL, scipy。ffmpeg-full。whisper-cli 使用
~/video-editor/models/ggml-large-v3-turbo-q5_0.bin模型。edge-tts (python3 -m edge_tts)。 - 參考研究 (由你自行建立,不包含在專案中):
~/video-editor/reference-study/,從你研究的參考短片中建立 (自行下載到~/Downloads/reference-shorts/;這些是學習材料,絕對不要分享)。作者研究了 19 部短片;references/中的數據即來自該研究:STYLE_BIBLE.md和SCRIPTS_ANNOTATED.mdmeasure/(逐幀統計、逐字稿、2 秒 / 60 幀連絡表、1 fps 幀、summary.json),用作tools/measure.py ours ... --refs的基準frames/和sheets/(用於並排比較的 1 fps / 2 秒檢視)
- 設定
TMPDIR/REMOTION_TMP為~/video-editor/remotion-tmp(stills.mjs 會自動設定)。Remotion 的打包檔案會累積於此。只有當使用者同意時,才刪除舊的remotion-webpack-bundle-*資料夾。
工作流程
-
點子 → 主題 → 事實。 當使用者沒有特定主題,或要求發想時,進行一輪點子發想 (
references/VIRAL_IDEAS.md包含數據、來源、評分規則與事實規則):-
現在什麼題材會紅 (進行少量 YouTube 研究;若使用連接器會產生費用,請先詢問):
- 參考頻道最受歡迎的短片:使用
youtube_channel_shorts,參數為 channel_id<REFERENCE_CHANNEL_ID>(你正在研究其短片的頻道) 和 sort_bypopular。將任何新的標題加入TOPICS_DONE.md。 - 利基突破性題材:使用
faceless_outliers_videos,並根據贏家類型設定語意query(例如「揭露奇怪歷史發明、詭計或法律背後隱藏原因的動畫短片,帶有轉折結局」),videoType 設為shorts,languages 設為["english"],minOutlierScore 設為 3。忽略不符合利基的熱門題材 (如遊戲、小工具)。記錄標題模式,並將已被採用的故事加入TOPICS_DONE.md。
- 參考頻道最受歡迎的短片:使用
-
候選題材: 從來源列表中收集約 30 個真實故事,並使用 WebSearch/WebFetch。將每個故事映射到一個贏家類型:古老物品或習俗背後的隱藏原因、外國制度或漏洞、大腦心理學、金錢詭計或騙局、寓言或悖論。
-
評分 每個題材,使用 10 項評分規則 (滿分 20 分)。針對前 10 名,使用
youtube_search(類型為shorts) 檢查新鮮度:如果一個故事在過去 12 個月內已被製作過且觀看數超過 100 萬,則放棄該故事,或設法找到一個新的切入點。 -
提出 5-8 個點子,內容包含:符合贏家模式的工作標題、一句話鉤子、核心物品、機制 + 類比、分數細項、為何可能病毒式傳播 (該類型的參考數據),以及 2-3 個來源。將所有點子儲存至
references/IDEAS_BANK.md。由使用者選擇一個。 -
研究被選中的題材:
- 每個數字、日期、名稱和主張,都需要 ≥2 個獨立且可靠的來源;
- 傳說需標明為傳說;戲劇化的嘲諷需標明為如此;
- 在
stories/<name>/SCRIPT.md檔案的頂端建立一個事實區塊 (主張 → 連結 → 可信度)。
對於大型主題,可以使用深度研究技能來完成此步驟。
一個好的主題,是指一個令人驚訝的真實故事,包含一個核心物品、一個觀眾可以想像的隱藏機制,以及一個能夠解釋它的日常類比(例如碗、從桌上滑落的手機、電梯按鈕)。
-
-
腳本 (160-240 字,45-65 秒)。參考頻道的量化公式 (
references/STYLE_BIBLE.md§2,SCRIPTS_ANNOTATED.md):- 鉤子 (在約 5 秒內完成,9-20 字): 以過去式敘述一個荒誕的行動作為事實(「他花錢僱人假裝在他的雜貨店裡購物。」)。替代方案:一個悖論式主張(「X 是非法的,但……」)或一個條件式承諾(「如果你曾經……」)。絕對不能是問句。
- 場景設定: 1-2 句現在式句子,包含具體數字和年份。
- 懷疑者: 有人嘲笑或模仿,並引用角色的嘲諷台詞。每個影片使用 1-2 次嘲諷。
- 觀眾提問: 「等等,……?」/「但怎麼會?」(每個影片約一次)。
- 升級: 時間跳躍和重複(「一週又過一週」、「到了七月……」),加上每 10-15 秒一個誇飾笑話(「X 到 Y 的程度」,一個縮小貶低的比較)。
- 轉折: 「然後,某天晚上/早上……」。
- 揭露: 「你看吧,」在影片約 65-87% 的時間點出現,用 1-3 句簡單的句子解釋真正的機制,理想上是透過日常類比。(「你看吧,」在鉤子之後立即使用也可以作為一個情境轉換。)
- 結尾: 一句簡短的陳述句,重新定義一切(懷疑者被證明是錯的)。在此處直接結束。額外加分:將最後一句話設計成能與第一句產生循環,達到無縫銜接的效果。
- 風格: 鉤子用過去式,然後轉為現在式;口語化的用詞並使用縮寫;每句約 14 個字;連接詞用「所以 / 但是 / 然後」,絕對不能用「然後」。第二人稱(「想像你……」)是有效的敘事模式。
- 永遠不要旁白觀眾已經能看到的事物 (沿用原始技能規則)。配音應補充畫面無法呈現的資訊:原因、數字、笑點、利害關係。畫面則用於展示動作。
- 標題: 圍繞核心物品建立一個好奇心缺口,4-8 個字,使用贏家模式(「那個[物品]做了[荒誕結果]」、「為什麼[群體]做了[奇怪的事]」、「一個[小東西]如何完成[大事]」、「[物品]的真正含義」、「那個[人物]的[悖論]」)。提供 3 個選項與腳本一起。
- 鉤子測試: 大聲朗讀第一句話。它必須在約 3 秒內吸引注意力,並且聽起來不合理或不可能。如果需要情境鋪陳,請重新撰寫。
- 提交關卡 1 審批: 腳本、3 個標題選項、以及包含來源的事實區塊。
-
配音 放入
stories/<name>/voice/:- 生成:執行
python3 -m edge_tts --voice en-US-AndrewNeural --rate=+14% --text "$(cat ../script.txt)" --write-media raw.mp3,或使用使用者自己的錄音。 - 收緊剪接:執行
python3 scripts/tighten_voice.py raw.mp3 voice.wav --sentence-gap 0.12。這會將間隙剪至 0.07 秒,或句子結尾 0.12 秒(參考影片平均每部僅有約 1 個超過 0.25 秒的暫停),並輸出 48 kHz 單聲道音頻。 - 單字時間戳記:在一個 16 kHz 的副本上執行 whisper-cli,使用
-l en -ml 1 -sow -oj參數,然後轉換為words.json([{w, a, b}],單位秒)。使用腳本的拼寫,而不是 whisper 的猜測。同時複製一份到words_authored.json:這是分鏡規劃所依據的時間線(story.ts 從這兩份檔案建立RETIME;如果檔案內容相同,則表示無變更)。 - 使用者自己的錄音: 將其單字與腳本進行比較,並測量其語速 (每秒字數、超過 0.25 秒的暫停次數)。對於已經很緊湊的朗讀,收緊剪接幫助不大;如果語速慢於以下目標,則製作一個保持音高但
--tempo 1.1的版本,讓使用者聽過兩個版本後再選擇。 - 在分鏡規劃後更換配音 (相同文字,新的錄音;於 2026-11-08 為「假購物者」所做):不要手動重新調整任何時間。
- 保留舊的時間戳記作為
words_authored.json,並將新的時間戳記放入words.json(腳本拼寫,字數相同)。不同的文字意味著需要新的分鏡規劃。 RETIME = makeRetime(authored, spoken, FPS)(engine/retime.ts) 將每一個輸出幀映射到作者時間線上,逐字跟隨新的配音並使用 ±2 字平滑處理(中位數起始時間誤差約 1 幀)。index.tsx 使用total: RETIME.total, retime: RETIME;角色嘴形同步則繼續匯入words_authored.json。- 在靜態圖片上檢查幾個關鍵字的幀,然後將新的草稿發布到審查板,並在
--changed參數下列出每個場景。 - 聲音會跟著調整,無需手動重新放置任何內容:
- 執行
node scripts/retime_cues.mjs <name>,保留cues_authored.json一份,然後寫入cues.json(輸出時間的事件、dur參數、背景音樂、嗖聲) 以及timemap.json。 - 執行
python3 score.py music.wav <RETIME.total> timemap.json:重音會跟隨畫面,節拍網格則跟隨每個段落。如果沒有對應圖,配樂保持不變(逐樣本檢查)。 - 執行
python3 audio/mix.py stories/<name>/cues.json public/<name>/mix.wav,然後對混音檔執行 whisper 以確認每個字都能被聽到。
- 執行
- 保留舊的時間戳記作為
- 目標:每秒 3.6-3.9 個字,鉤子需在 ≤6 秒內結束。TOTAL = ceil(最後一個字結束時間 × 30) + 6 幀。
- 在分鏡規劃前收緊剪接,絕不能在之後進行。 每個鏡頭幀都基於收緊後的
words.json建立,因此在之後重新收緊意味著重新調整整個影片的時間。(範例跳過了這一步,導致有 10 個超過 0.25 秒的暫停,而參考影片只有約 1 個。)
- 生成:執行
-
分鏡規劃 在
src/videos/<name>/story.ts中 (參考範例):SHOTS:{id, a, b, light, set},單位為幀。大約每 2.2 秒(約每 8 個字) 更換一個新視覺。動作應落在關鍵字上,在其開始前 0-3 幀。TRANSITIONS:鏡頭如何進入:dissolve(4-8 幀):用於地點或時間變更iris(12-16 幀,cx/cy):透過舷窗、窺視孔、鏡頭或時鐘dust(20-26 幀):當煙霧或雲團充滿畫面時flash(4 幀):僅在鉤子之後使用 同一個場景中的鏡頭若無指定轉場,將自動通過鏈接式攝影機滑行來連接,滑行動作以場景邊界為中心 (makeCamAt;chain= 18 幀,或根據移動距離為每個進入的鏡頭設定一個記錄),使得場景轉換發生在移動過程中。絕不能使用硬切。 如果鏈接邊界的移動距離 > 約 1100 像素或轉彎角度 > 90°,則改用 dissolve 轉場。
CAMS:每個鏡頭的關鍵幀{f, dur?, dissolve?, s: f => dolly(pos, look, pushK(f, f0, rate, punch?), {fov, roll})}。鏡頭規則 (references/studies/camera.md,基於 12 部短片,30 fps 分析):- 幀數預算:15-20% 靜止、約 45% 緩慢移動、約 33% 滑行(在 540 寬度下,每秒 3-30 像素)、≤3% 快速移動。
- 約每 1.4 秒更換一次取景。大多數是小幅度重新取景:使用
withReframes(CAMS, ENDS)包裝關鍵幀,讓每個長時間的靜止畫面每隔 42 幀就進行一次 14 幀長的左右平移,移動範圍為主體距離的 ±5%,同時保留該關鍵幀本身的推拉效果。標記有意的靜止畫面為hold: true。每分鐘約有 4 次快速移動:峰值每秒 35-55 像素(上限 70),持續 20-34 幀。 - 作者每 10 秒執行約 7 次移動,其中大部分是幅度小、速度慢的移動(峰值約每秒 10 像素,移動距離 80-150 像素)。使用
withNudges(CAMS, [[shot, baseKeyF, at, dur, ±0.05-0.07], ...])在長時間的靜止畫面內部進行小幅度的側向重新取景(保留該關鍵幀的推拉效果),然後withReframes會填補其餘部分。經此重新設定關鍵幀後,範例中的攝影機在每個類別上都測量到與參考相符的數值(靜止 19.5 / 緩慢移動 49.9 / 滑行 27.1 / 快速移動 3.5 %,每 10 秒 7.4 次移動;請見 studies/camera.md)。 - 在靜態圖片上檢查每個微調的方向。 朝向……
… (其餘內容因篇幅限制而截斷)