make-video 是做什麼的?
將一個想法轉換為已渲染並經檢查的 MP4,僅使用免費的本地工具。由此 Skill 建立的專案擁有自己的 AGENTS.md 規則,因此請遵循這些規則(動畫純淨度、useScale、sec()、資料夾佈局)。
0. 建立影片專案(一次性)
如果目前資料夾沒有 tools/make-story.sh,就還不是影片專案:
-
先檢查機器:
uname -sm(需要 Darwin arm64)、node --version(20+)、brew --version、df -h ~(約需 8 GB 可用空間)。如果系統不同,請在繼續之前說明哪些功能無法使用(Chatterbox 和鏡頭檢查器在 Apple GPU 上執行)。如果缺少 Node 或 Homebrew,給使用者一個安裝指令並等待。 -
告知使用者將下載什麼,並等待他們確認。 在得到確認前絕不開始下載。顯示以下表格(全部免費,來自官方來源):
下載項目 大小 用途 存放位置 npm 套件(Remotion 等) ~500 MB 渲染 node_modules/ffmpeg、uv、FluidSynth(Homebrew,只在缺少時安裝) ~150 MB 音訊/影片工具 Homebrew Python 套件(PyTorch、Chatterbox…) ~1.5 GB 語音、QA .venv/GeneralUser GS soundfont ~30 MB 音樂 tools/soundfonts/Chatterbox 權重 ~3 GB 語音 Hugging Face 快取( ~/.cache/huggingface),所有專案共用Whisper small.en ~590 MB 字幕 tools/models/Whisper large-v3-turbo(可選的鏡頭檢查器) ~1.6 GB 重新錄製不清楚的台詞 Hugging Face 快取 總計約 6 GB 和 10-20 分鐘。提供較輕量的選項:
SKIP_TAKE_CHECK=1可跳過鏡頭檢查器(總計 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 和鏡頭檢查器來自 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 + Σ 停頓 + 片尾。在生成之前檢查它。(冰箱 drama:62 字 + 6.4 秒停頓 + 3 秒片尾 = 33 秒。)
垂直短片的節奏規則:
- 鉤子(Hook)在第 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: "下載它"}, …]} />(src/components/StepCard.tsx):一個「步驟 2/4」藥丸形狀的標籤和一個分段進度條。 - 卡拉 OK 字幕(可選樣式)。
<Karaoke words={captions} keywords={["免費"]} style={{left: 60, right: 140, top: 1180}} />(src/components/Karaoke.tsx):整個 2-5 字的片段顯示在畫面上,正在說的字為實心,即將出現的字為灰色,關鍵字和重讀字使用強調色,讓觀眾可以在靜音自動播放時預先閱讀。background會提供淺色卡片外觀;透過qaprop 傳遞,以便版面檢查能看到它。 - 系列標籤。 左上角的集數標記(「第 2 部分」)會讓貼文看起來更值得追看。
- 顯示對比。 作為雙欄卡片呈現的「之前/之後」或「我們 vs 他們」效果比用言語描述更好。
- 故事節拍勝過列表。 「嘗試 1 失敗 → 嘗試 2 失敗 → 什麼有效」比列出功能更能吸引注意力。
- 在 CTA 之前放笑點。 一句人性化的台詞,然後是行動呼籲。
- 只有在有實際內容(指南、提示、儲存庫)在等著時,才承諾「留言關鍵字,我就私訊你」獎勵。
為 TTS 寫作:
- 不要以「…」開頭:Chatterbox 會即興添加一個填充詞。
- 不要以純數字開頭(「Two. The face」被說成「to the face」)。寫成「Second, …」。
- 按照發音方式書寫數字和奇怪的名字(「five point six」、「X Z」),並將真實文字放在該行的
caption欄位中。 - 將大數字四捨五入(「超過 600」);TTS 會混淆「646」和「87,000」。
- 鉤子中的短詞會模糊(「This repo」被聽成「The three」)。重新措辭,直到鏡頭檢查器正確聽取。
- 不要讓多個聲音同時喊出同一個短詞;混合會使其模糊。
- 標點符號引導語調:「?」會提高語調,「。」會降低語調,大寫字母會增加強調。
3A. 動態圖形影片
- 複製
src/compositions/_Template→src/compositions/<Name>,每種節拍類型(鉤子、觀點、統計、列表、CTA)使用一個元件。 - 從腳本計算時長(節拍總和 − 轉場),就像
getSampleIntroDuration所做的那樣。 - 使用內聯的
defaultProps在src/Root.tsx中註冊。 - 僅音樂:建立一個包含
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(以 CAPS 表示並在字幕中強調的單字)、pauseBefore(秒)。"pace": "slow"配上 0.4 秒的停頓,可以使每個字都清晰。
然後執行一個指令:
tools/make-story.sh stories/<slug> <CompositionId> # 語音 → 字幕 → 音樂/音效 → lint → 渲染 → 母帶 → QA
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(台詞時間、事件、每幀嘴型輪廓)。如果安裝了鏡頭檢查器,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 prop,用來描繪字幕 #FF00FF 和臉部 #00FFFF)。閱讀 stories/<slug>/qa/report.md 並修復所有 FAIL 和 FIX。
然後用眼睛檢查:
npm run lint。stories/<slug>/captions-report.json中任何 WER > 0.2 的行很可能含糊不清:重新措辭並重新生成(由於快取,成本很低)。Whisper 遺漏的單字會被放置在僅有語音部分的間隙中,較長的單字獲得更多的時間,並列在filled下:聽取那些位置。matchRate低於 70% 表示腳本和音訊有差異,因此在繼續之前重新配音。- 在關鍵節拍(鉤子、每個說話者、每個事件、片尾)處渲染靜止幀,將它們組合成一張聯絡表,然後仔細查看它:
檢查角色大小、安全區域、覆蓋角色的疊加層、片尾後的遺留物、字幕同步以及文字大小(晶片 ≥ 28 px,正文 ≥ 54 px,在 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。 - Studio 只能在
defaultProps是Root.tsx中的內聯物件時儲存 prop 編輯。 - 將 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 倍即時速度 |
| 鏡頭檢查 | 每個需要重拍的句子約 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 頻道背後的管線。