匯出(成片)
這是鏈條最後一段,也是整個產品裡唯一一個真正畫出像素的地方。
在它之前全部是標註:帳本記「播哪些詞」,字幕記「螢幕上寫什麼」,畫面記「蓋什麼層」, 預覽把這三樣即時拼給人看,不落盤。匯出把它們燒成一個檔案。
需要 edit-list.json(必須)、subtitles.json、visuals.json + modules/
產出 成片.mp4
前提工具:機器上有 Google Chrome(用來把字幕和動畫畫成圖)。桌面安裝來源的
FFmpeg / FFprobe 已隨 App 進入 Product 受管目錄;純 CLI 安裝仍要求系統
ffmpeg ≥ 6。缺 Chrome 會明確報錯,不要試圖繞過——沒有它就沒有字幕層和動畫層。
先讀取並執行 業務 Skill 的階段合約 裡的「結論等級」一節。匯出不進剪輯狀態機:它不改任何專案檔案、不做 CAS 寫入、 不推進 stage,產出是一個新檔案,重跑一次就覆蓋。所以它不需要確認卡。
0. 就緒
先執行 檢查更新 的「就緒檢查」——skills 是否
最新、Runtime 是否配套;插件根也在那裡定位(本文命令裡的 <插件根> 都代入
那個字面路徑)。只有「就緒」才繼續;「需新會話」或「停」按它的處置執行
(含「禁止自製替代介面」禁令),業務 Skill 不自帶環境邏輯。
若就緒結果為 runtime.kind=desktop-managed,直接複用桌面 App 已安裝的穩定 CLI、
媒體工具與同一 launchd/windows-task 服務;不要解析 Electron 路徑、另裝
FFmpeg/Bun 或起第二個 Runtime。
命令
node "<插件根>/scripts/ensure-running.cjs" --json
node "<插件根>/scripts/videocut-cli.cjs" export <project> --dry-run --json # 先看計畫,不編碼
node "<插件根>/scripts/videocut-cli.cjs" export <project> --json # 出成片(預設 2 倍、來源幀率)
node "<插件根>/scripts/videocut-cli.cjs" export <project> --out /path/成片.mp4 --json
node "<插件根>/scripts/videocut-cli.cjs" export <project> --scale 1 --json # 只要來源尺寸
node "<插件根>/scripts/videocut-cli.cjs" export <project> --keep-work --json # 留下中間片和逐幀 PNG,供排查
ensure-running 身分不匹配、連接埠衝突或服務不健康時立即停止;不允許用 foreground
臨時頂替後繼續匯出。
兩步,別只跑第二步
① --dry-run 先報計畫 片長、幀數、字幕螢幕數、畫面層數、推近段數、輸出尺寸
唸給使用者聽。數字不對就是上游不對,編碼十分鐘不會修好它
② 真跑 assemble → overlay → compose → verify 四段進度
--dry-run 裡的 warnings 必須原樣轉述。它只報一類事:某些字幕螢幕或畫面層的詞
已經被剪掉了,所以它們不會出現在成片裡。這是上游要決定的事,不是匯出該替人吞掉的。
清晰度:來源片是天花板,先看來源再談放大
匯出前看一眼來源解析度(--dry-run 的 source 欄位就有):
來源寬 ≥2560(Retina 原生錄影) → --scale 1,輸出就是原生像素,這是最好的情況
來源寬 <1920(如 960×720) → 先停一下:問使用者有沒有同一次錄製的高畫質匯出。
錄影工具常常能把同一次錄製重新匯出成 3 倍解析度,
換來源比任何後期都管用(見下節)。確實沒有 → --scale 2
--scale 2 對低清來源有用的原因:底片放大不會變清楚,但字幕和動畫是按輸出尺寸
重畫的——觀眾真正在讀的就是這兩樣;平台再壓一次時,大圖分到的位元率也更高。
不要為了「更清晰」去調 --fps。 幀率跟著來源片走;改它只會讓動畫的取樣和錄影對不上。
換來源:使用者拿出同一次錄製的高畫質版時
這不是匯出流程的一部分——匯出本身永遠唯讀。換來源是使用者明確點頭後的獨立素材操作, 做完再回來正常匯出。
先判定是不是同一條(前兩條就足夠硬):
ffprobe -v error -show_entries format=duration -of csv=p=0 <兩個檔案> # 時長精確到毫秒相同
ffmpeg -v error -i <檔案> -map 0:a:0 -c copy -f md5 - # 音訊串逐位相同
# 再抽兩三幀比對畫面內容,眼睛確認
音訊逐位相同 = 時間線相同:逐詞稿、帳本、字幕、畫面層全綁詞 id,一個不用動。 這正是「綁詞不綁秒」在換來源這件事上的兌現。
換來源四步(2026-07-29 真實走過一次;第 ③ 步當時漏了,剪輯預覽當場報 「產生失敗」——指紋記錄不止一處,grep 舊指紋找全再動手):
① 換檔案 input/source.mp4 和 uploads/source.mp4 是硬連結對 ——
刪兩個,拷新檔案到 input/,再建硬連結回 uploads/
(macOS 用 ln;Windows 用 New-Item -ItemType HardLink)
② 更新指紋 project.json 的 source.sha256 改成新檔案的
③ 再查一處 workbench.json 的 sourceSha256 也記著來源片指紋,
剪輯預覽管線校驗的恰恰是這本 —— 漏了它,預覽拒絕產生
(這是產品的正確行為:指紋對不上寧可失敗,不拿舊預覽冒充)
④ 重導 --scale 1 —— 換來源就是為了原生像素,別再放大
保險做法:用你的搜尋工具在 <專案目錄> 的全部 *.json 裡找舊指紋前 8 位, 列出來的每一處都要處理(快取記錄如 preview-edited/current.json 會自動重算,不用手動改)。
音訊不同(重新錄了一遍、剪過、時長不一樣)就不是換來源,是新專案: 逐詞稿要重新轉,所有標註作廢。別硬套。
驗收:三件事,缺一件就不算匯出完成
① 命令成功返回 產品自己數成片的尺寸、幀數、音軌,和計畫逐項比。
對不上就是 readback_mismatch 報錯,檔案留在盤上當證據,
不算匯出完成
② 抽幀看像素 從成片裡抽幀,用眼睛看。至少覆蓋:一個推近段、
一個整螢幕動畫段、一個只有字幕的段、一個層與層的邊界
③ 人耳聽感 沒人真的聽過,一律記 human listening UNVERIFIED
② 不許用預覽截圖代替。預覽和成片是兩條渲染路徑,驗收要看的正是它們對不對得上—— 拿預覽的圖當成片的證據,等於把要驗的那件事當成了前提。
判推近要找判別性地標,整體印象會騙人(2026-07-29 真踩過):1.6 倍推近後的 螢幕頁面看起來仍像