Voiceover -> coded 2D animated short
你正在製作一部短片,而不是投影片。旁白是固定的;你的工作是為每一刻發明觀眾看到的內容,用程式碼繪製出來,並提供一部剪輯和視覺節拍精準對應到指定詞語的 MP4 影片。
整個管線是確定性的:每個場景都是時間的純函數,因此任何影格都可以獨立渲染。這一個特性賦予了你其他一切——即時靜態畫面供審閱、平行渲染、完美的音訊同步。
機器上需要的東西
開始前請先檢查這些;它們是唯幾會動的零件。
python -c "import faster_whisper, playwright, PIL; print('ok')"
ffmpeg -version
缺少的部分:pip install faster-whisper playwright pillow,然後 python -m playwright install chromium。ffmpeg 必須在 PATH 中。如果無法安裝某項東西,請告訴使用者缺少什麼,而不是改用較弱的方法。
工作流程
保持使用者的視角簡單:他們給了你音訊並想要一部影片。依序完成這些步驟,並保持一個可見的任務清單。
1. 先聽:字詞時間點
python <skill>/scripts/transcribe.py voiceover.mp3 work
它會列印出帶有開始/結束時間的句子以及每個 word@start,並儲存 work/words.json。下游的一切都根據這些數字規劃。注意總時長。
2. 在音檔旁邊設置專案
將 <skill>/assets/template/ 複製到工作資料夾,使其看起來像這樣:
voiceover.mp3
render.py
src/index.html src/engine.js src/scenes.js src/main.js
閱讀 src/engine.js 一次(它很短),並瀏覽 references/engine-api.md 以便了解你已經有什麼:緩動、隨機種子、攝影機、部分路徑繪製,以及版畫風格的主題(雲、雨、閃電、煙、天際線)。
3. 決定美術方向,然後根據文字製作分鏡表
為整部影片選擇一個視覺語言並保持一致:一個小調色板(五或六種顏色)、一種邊緣和紋理的處理方式、一種繪製人物的方法。範本附帶一個版畫/雕刻風格(骨色紙張、海軍藍墨、朱紅、金色、搖晃的手工邊緣)。使用它,或透過更改 scenes.js 中的調色板和 POST 開關來重新設計風格——但不要在場景之間混合風格。多樣性來自於哪種顏色在每個場景中佔主導地位(夜晚場景、紙張場景、紅色場景),而不是改變規則。
然後在寫程式碼之前寫分鏡表:一個包含鏡頭絕對開始/結束時間、每個鏡頭涵蓋的文字、螢幕上顯示的內容以及每個節拍對應的確切詞語的表格。閱讀 references/storyboarding.md 了解如何將旁白轉化為圖像——這是決定影片好壞的部分。
簡短版本:
- 每個片語一個想法。一個有三個子句的句子通常是三個鏡頭或一個鏡頭內的三個節拍。
- 顯示事物,不要加標題。重複旁白的螢幕文字會讓觀眾閱讀而不是觀看。預設完全不加文字;名稱、真實數據和圖表上的簡短中性標籤是例外。如果使用者已說明文字偏好,則以那些為準。
- 給影片一個主幹:一個重複出現的角色或主題,以及在結尾回顧開頭某個事物的設計。
- 有目的地改變景別和攝影機:廣角、特寫、圖表、地圖、剖面圖。連續兩個鏡頭不應有相同的構圖。
- 如果旁白以一個妙句結尾,在最後一個詞之後添加 1-2 秒的無聲尾部,以便最終圖像能夠落地。
render.py會填充音訊。
4. 在編寫其餘場景之前,先完整建立一個場景
在 src/scenes.js 中編寫第一個鏡頭,將其註冊到 SHOTS,並查看它:
python render.py stills 0.3,1.5,2.9 first
然後用 Read 工具打開 work/first.png。在編寫其他場景之前執行此操作:它驗證了管線,更重要的是,讓你在更換風格成本還低的時候判斷風格。
5. 每次編寫幾個剩餘場景
維持系統運作的規則(每一條都存在,因為違反它會導致特定的失敗):
- 時間的純函數。 一個鏡頭是
function name(g, t, u),其中t是絕對秒數,u是自該鏡頭開始以來的秒數。沒有在影格之間累積的變數,沒有Math.random(),沒有Date。使用hash(n)、noise1(x)、rng(seed)。影格由多個瀏覽器同時亂序渲染;任何有狀態的東西都會顯示為閃爍或無法匹配的區塊。 - 使用絕對時間進行動畫。 編寫
E.out(seg(t, 12.62, 13.1)),直接使用文字列表中的數字。當剪輯改變時,節拍仍會停留在它們的詞語上。 - 萬物皆微動。 一個靜止的畫面會被視為錯誤。為每個鏡頭添加緩慢的攝影機漂移(
1 + u * .01縮放就足夠了)和一些次要運動(雨、煙、擺動的物體)。 - 萬物皆緩動。 用
E.out讓東西到達,用E.inOut做攝影機移動,用E.back做彈出,用E.bounce做落地。線性運動看起來很機械。 - 當攝影機拉遠或搖晃時,繪製比畫面更大的世界;將背景和地面的填充範圍遠遠超出 0..1920,這樣任何邊緣都不會暴露。
- 衝擊需要三件事:一個 2-3 影格的全面
flash、一個衰減的shake,以及一個反應(一股煙、一個後座力、一個顏色翻轉)。 - 將場景程式碼拆分到多個檔案(
s1.js、s2.js、...),一旦檔案超過幾百行,並在scenes.js之前將它們添加到index.html。
編寫兩三個鏡頭,渲染它們的縮印版,查看,修正,繼續。不要盲目地編寫整部影片。
6. 用靜態畫面審查——這是你的眼睛
你無法觀看影片,因此縮印版就是你的審查。對於每個鏡頭,至少取樣開始、主要動作的中間、結束,以及每個節拍的確切影格:
python render.py stills 12.2,13.3,14.3,15.4,16.5 act2 # 每個縮印版最多約 16 個時間戳
python render.py sheet 4 # 整部影片,每 4 秒一幀
讀取 PNG 並逐鏡頭檢查:主體是否大到一眼就能看清?是否有任何東西被裁剪、重疊或懸浮?重要的事物與其背景是否有對比?它看起來是否經過設計,還是畫面的一半是空的?然後修正並重新渲染相同的時間戳。references/pitfalls.md 列出了容易犯且難以察覺的錯誤。
頁面錯誤由 render.py 以 PAGE ERROR: 行打印——一個黑色或半繪製的影格通常表示該鏡頭中發生了異常。
7. 渲染並驗證
python render.py full --workers 8
在背景執行它;一部 70 秒的影片大約有 2,100 幀,需要幾分鐘。它會輸出 output.mp4(1920x1080,H.264 + AAC,音訊填充至完整長度)。然後驗證檔案本身,而不僅僅是靜態畫面:
ffprobe -v error -show_entries format=duration:stream=codec_name,width,height -of default=nw=1 output.mp4
python render.py sheet 6
確認持續時間、有音訊串流,且總覽縮印版沒有空白影格。在你的報告中清楚地說明你檢查了靜態畫面和檔案屬性,但沒有觀看其動態——使用者應該觀看一次。
8. 交付
提供使用者 output.mp4 的路徑、總覽縮印版,以及一行關於如何在編輯後重新渲染的說明。src/index.html 在瀏覽器中打開時是一個帶有音訊的即時播放器,這是他們檢查時間點最快的方式。
聲音
除非使用者要求音樂或音效,否則僅提供旁白。如果他們要求,將它們作為額外的 ffmpeg 輸入添加到最終的混流中,時間點與你用於視覺效果的節拍列表相同。
參考檔案
references/storyboarding.md- 將旁白轉化為鏡頭;節拍放置;視覺隱喻;什麼讓場景感覺經過設計。在第 3 步之前閱讀。references/engine-api.md-engine.js中的每個輔助函數及其一行說明用途。在第 5 步之前閱讀。references/pitfalls.md- 經由困難方式發現的失敗模式,以及修正方法。在第 6 步之前閱讀。references/example-bells/- 一個完整的 71 秒影片(16 個鏡頭,約 900 行程式碼)及其分鏡表。閱讀storyboard.md以了解預期的規劃層級;當你想了解特定效果(跟隨攀爬角色的攝影機、爆炸、地圖、畫線的羽毛筆、眯起的眼睛)是如何建構時,打開src/s*.js檔案。