/dynamic-edit
一個從不移動的談話頭影片會讓人感到無趣;但一個無緣無故移動的影片更糟。這個管道會決定哪個效果值得用在哪個詞上,將其寫成一份人類可以否決的計畫,然後才開始花費算力運算。
倉庫根目錄:
THP_HOME=${THP_HOME:-$(cat ~/.config/talking-head-pipeline/root)}
規劃前請先閱讀:$THP_HOME/docs/STYLE_RULES.md(工藝規則)、$THP_HOME/docs/LAYOUTS.md(幾何與字幕安全區域)、references/plan-format.md(架構)、references/qa.md(品質關卡)。3D:$THP_HOME/docs/3D.md。
輸入合約:素材已經剪輯完成。 此處不進行去除靜音或填充詞處理。原始錄影需先經過 /autocut 處理;之後帶著它的 clean.mp4 和工作目錄(用於逐字詞)回來。
絕不能跳過計畫步驟。 旋轉描圖每分鐘素材約耗費 12 分鐘,完整渲染則需數十分鐘。在文字計畫中抓到的一個錯誤猜測不花任何成本。
工作流程
1. 建立專案結構
P=<projects dir>/<slug> # 詢問使用者影片專案存放位置;絕不能是桌面
cp -R "$THP_HOME/template" "$P"
cd "$P"
uv venv --python 3.11 .venv # mediapipe 在 3.12 之後沒有適用的 wheel
uv pip install --python .venv/bin/python "mediapipe==0.10.21" opencv-python backgroundremover numpy pillow
scripts/download_models.sh # MediaPipe 人臉偵測器(未包含在 repo 中)
(cd remotion && pnpm install) # 或使用 npm install
cp "<autocut workdir>/clean.mp4" remotion/public/raw.mp4
python3 "$THP_HOME/scripts/autocut/verbatim_cut_words.py" "<autocut workdir>" -o remotion/public/words.json
從 16:9 的錄影製作直式 Reel:使用 scripts/prep_vertical.sh "<autocut workdir>" "$P" 來取代最後兩行(靜態 9:16 裁切,以頭部中位數 x 為基準,絕不使用跟隨裁切)。
2. 盤點、剪輯點、追蹤
ffprobe -v error -select_streams v:0 -show_entries stream=width,height,r_frame_rate,nb_frames \
-show_entries format=duration -of default=nw=1 remotion/public/raw.mp4
.venv/bin/python scripts/detect_cuts.py remotion/public/raw.mp4 --out remotion/public/cuts.json
.venv/bin/python scripts/track_head.py remotion/public/raw.mp4 --out remotion/public/track.json --cuts remotion/public/cuts.json
- 縮放餘量 = 來源寬度 / 時間軸寬度(2560 進入 1920 = 1.33 倍免費空間)。超出此範圍則需放大。
- 畫面比例從計畫的寬/高自動偵測(
lib/layouts.ts設定檔)。單一引擎,絕不分岔。 - 讀取 track.json 中的
detectionRate。低於約 0.9 時,以人臉為錨點的移動不可靠,請據此提示。 - 剪輯點偵測會遺漏漸變(例如動畫切換到螢幕分享)。每個段落取一個影格取樣,手動將遺漏的邊界加入 cuts.json。螢幕分享段落不應有拉近鏡頭、不繪製字幕、不顯示卡片(將
captions.silence設定為那些範圍)。
3. 校對逐字詞
在 words.json 中搜尋所有劇本提到的專有名詞、品牌、股票代碼和人名。Whisper 系列的模型會把它們弄錯,但句子仍然讀起來流暢,因此審查常常抓不到。請修正詞語檔案(將兩個 token 合併成一個是可以的),但絕不要修改渲染結果。數字請使用阿拉伯數字。
4. 撰寫計畫,然後停止
- 先閱讀完整的逐字稿。找出敘事實際上擁有的節奏:鉤子、主張、數字、轉折、揭露、回報、以及最好用展示而非說明的時刻。
- 從下方的選擇效果中為每個節奏挑選一個工具。許多節奏不需要任何效果。
- 複製
scripts/build_plan.example.py到scripts/build_plan.py,並在其中使用詞語錨點("phrase@seconds")來撰寫節奏。它會寫出remotion/plan.json和字幕。它會拒絕沒有理由、沒有進入和離開的詞語錨點、或拉近間距過於緊湊的事件。 - 向使用者展示一個表格,每個事件一行:時間、效果、「落在 X,在 Y 之後離開,因為 Z」。加上:旋轉描圖的秒數(以及其衍生的遮罩時間)、任何超出縮放餘量的重新構圖、任何超過約 12 秒且無事件的片段,以及所需的 3D 渲染。
- 等待核准。 然後從磁碟重新讀取
plan.json;使用者可能已經編輯過它。
如果使用者指定了某個特定時刻的特定效果,那麼該效果的計畫就是那個。
5. 僅構建計畫所需的東西
- 旋轉描圖視窗:
scripts/render_and_qa.sh會為它們製作遮罩;或者每個視窗使用scripts/matte_frames.py。 - 3D 標題字 / 3D 資產:
tools/render3d(參見 docs/3D.md)。先輸出靜態圖(--stills),查看它們,然後使用--clip --dir=remotion/public/<name>。將與計畫事件中at/until相同的進/出影格放入鏡頭中。 - 側邊卡片: 種類定義在
remotion/src/layouts/cards.tsx;必要時可在其中新增一種。 - 螢幕場景頁面:
remotion/src/screen/pages.tsx。對於任何要放大縮小的 UI,請在程式碼中重建;截圖在 2 倍放大時會變模糊。 - 全螢幕場景:
scenes/fullscreen/registry.ts(depth、clip、stage)。 - 音效: 計畫定案後,使用
scripts/add_sfx.py "$P" sfx_map.json --library <dir>。
6. 用靜態圖檢查,然後渲染
靜態圖只需幾秒鐘;一個渲染需要耗費整個下午:
cd remotion && npx remotion still src/index.ts Edit ../qa/landing.png --frame=<N>
檢查每個關鍵元素的著陸(以及著陸後 +25 影格)、每個佈局進入、每個過場、每個 3D 到達。然後:
scripts/render_and_qa.sh "$P" <name> # 為旋轉描圖視窗製作遮罩、渲染、寫出 QA 檢查表
渲染:--concurrency 4-6,對程序使用 nice,並保持 Config.setChromiumOpenGlRenderer('angle')(已在 remotion.config.ts 中設定),否則 WebGL 場景會在無頭模式下失敗。交付使用 yuv420p bt709;yuvj420p 檔案在 QuickTime 中會播放為空白。
7. QA(references/qa.md),然後同步修剪
在任何人看到它之前,執行 references/qa.md 中的每個檢查點。然後測量並修剪渲染結果相對於來源的音訊延遲(Remotion 渲染會延遲幾十毫秒;需要測量,而非假設):
"$THP_HOME/scripts/master/sync_trim.sh" renders/<name>.mp4 remotion/public/raw.mp4 renders/<name>_final.mp4 [--loudnorm -14]
目標是最終同步誤差低於 5 毫秒。僅在使用者要求時才進行響度正規化(社交媒體預設 -14 LUFS)。為使用者打開最終檔案。
8. 交付後的修正:修補片段範圍,絕不重新渲染整個影片
scripts/patch_render.sh --base renders/prev.mp4 --replace 0-1049 --render 0-887 --out renders/next.mp4
--replace 是舊渲染中要丟棄的範圍,--render 是目前計畫中取代它的範圍(當來源被重新剪輯時,這兩個範圍會不同)。只有要修補的部分會被編碼;片頭和片尾會在關鍵影格上進行串流複製;影格數會被驗證。當計畫已經存在,但來源中需要移除一個失誤時,請從錨點(build_plan.py)重新構建計畫,這樣每個效果都會隨著它的詞語移動。
9. 清理,僅在使用者說影片完成時才進行
先詢問。scripts/cleanup.sh 會刪除遮罩 PNG、影格傾印、QA 檢查表、草稿、venv 和 node_modules;它會保留計畫、來源、追蹤資料、字幕和最終渲染。重新產生一個遮罩每分鐘素材約需 12 分鐘,所以絕不在未經提示的情況下執行它。
選擇效果(一個工具箱,而非執行順序)
沒有公式。一個沒有關鍵字、沒有旋轉描圖、沒有 3D 的影片也是一個有效的計畫。一個直接的鏡頭演講可能只需要拉近鏡頭和字幕。每個效果都是由腳本中的某些東西所贏得的,並且每個事件都要命名其原因(它的敘事任務)、進入(它落在哪個詞上;移動從該詞之前開始,以便它到達該詞)和離開(它在哪個詞之後離開:該想法的結束、下一個真正的停頓、軟化詞之前的那個詞)。絕不做固定停留,絕不因為「安靜了一陣子」。
- 臉優先。 最初的 2-3 秒是說話者的臉,第 0 影格就已經是一個可讀的鉤子畫面(而不是從黑畫面淡入)。在約 2.5 秒之前不要出現全螢幕圖形:自動播放是靜音的,以統計資料開頭的開場會被認為是資訊圖表,從而被滑過。
- 拉近鏡頭(主力效果):長度為一句話,數量少,有目的。它在關鍵句子醞釀時著陸,在其間保持,在最後一個詞釋放。絕不做一個詞的拉近;絕不按計時器間隔排列。在長格式中大約每 30-45 秒的談話頭做一次;在短片中可以更密集,但僅當腳本值得這樣做時。以追蹤到的人臉為錨點。兩個相關聯的短語:對承載故事的那一半進行拉近。嵌套強調(在關鍵短語上保持一個持續強調加上一個小的強調)取代一個句子中的三次拉近。
- 側邊佈局(16:9):當一個數字、列表或比較需要在被解釋時一直停留在螢幕上。主體滑向一側,另一側放置一個內容大小合適的卡片,卡片在它們的詞語上切換,並在數字被說出時進行拉近。長格式中的大多數資訊節奏都需要這個。
- 螢幕場景(16:9):「這是它的樣子」的操作展示。每個影片使用一到兩次,而不是每次提到都用。如果它重現一個產品,就必須與真實的 UI 一一對應;只有資料會改變,而且資料是真實的。
- 偏移重新構圖搭配卡片: 側邊佈局的輕量版,在縮放餘量內進行。
- 旋轉描圖(在主體後方): 僅用於開場和關鍵時刻。預設預算為每個影片 15 秒;超過 30 秒時提出警告;超過 60 秒時詢問。任何從後方穿過的物體都必須值得被遮蔽。
- 主體後方的關鍵字: 一個鉤子、一個硬轉折、被命名的想法。放在頭部上方,每個字型都要可讀(檢查著陸後 +25 影格的靜態圖)。每個影片一個,除非兩個關鍵字相距很遠。
- 3D 標題字: 一個重大的主張或數字,放在頭部旁邊或胸前,與每個畫面邊緣保持相等的內距(在 1080 寬度下至少約 90 像素),絕不放在臉上。
- 3D 資產: 一個被講者命名的標誌或物體,在臉旁邊的空曠空間中旋轉,如果重疊則使用旋轉描圖放在後面。
- 全螢幕場景: 僅當展示比說明更清楚時使用,最多兩個,使用淡化+模糊進行進出。
- 字幕: 固定的下方三分之一,所有地方(談話頭、側邊、螢幕)位置和樣式相同。強調 = 使用強調字型和 1.22 倍大小的整個短語,絕不跨兩個卡片分割。除非要求,否則不開啟頭部追蹤字幕。
- 音效: 每個可見事件(過場、關鍵元素著陸、硬拉近、卡片、點擊)一個,音量要小。
硬性規則
- 任何東西都不能遮住臉。 在其視窗的任何影格中,字幕、卡片、晶片、標誌或 3D 物件都不能覆蓋臉部邊框。移動它、縮小它,或者使用旋轉描圖將它放在後面。從 track.json 測量。
- 講者上方不能有帶框的文字晶片。 任何卡片上都不能有單側的強調線條(頂部、左側或任何邊緣):使用填充色調或完整的均勻邊框。
- 僅全畫面淡入淡出。 絕不能在標題停留時淡化容器。
- 過場: 淡化+模糊(預設)、純粹淡化,或液體轉場。絕不使用鋸齒/牙刷刷狀過場。
- 文字不能被縮放或畫面邊緣切割。 放大的 UI 在字幕帶上方淡出(
World邊緣淡化)。字幕絕不縮放。 - 重新構圖必須與它騰出空間的東西在同一個渲染中輸出。 攝影機不能朝著空牆移動。
- 核對螢幕上的每個數字與所說的話或已儲存、有來源的資料擷取。絕不憑空發明一個。
- 物體和圖示必須是真實的標誌或產生/有來源的圖片。 程式碼繪製運動、文字、UI、圖表。箭頭是一個資產,不是 SVG。
- 螢幕上不能有長破折號。沒有「在下方留言」/「私訊我」的呼籲行動,除非使用者要求。
- 疊加在影片上的每一層都必須是其自己的
AbsoluteFill(一個靜態影片會在任何絕對定位的兄弟元素下方繪製,這會默默地將圖形放在遮罩的前方)。 - 重新構圖時使用
translate + scale,絕不移動transform-origin。
效果參數和測量數字:references/effects.md。