video-sync
把 shots.json + 原片 合成一條影片:畫面一邊,當前鏡頭的分鏡資訊另一邊, 鏡頭切了資訊跟著切,鏡頭表自動滾動並高亮。
版式只看寬高比,不看別的:
| 原片 | 版式 | 合成方式 |
|---|---|---|
| 橫版(寬 > 高)、方版 | 畫面在上,資訊在下 | vstack |
| 豎版(高 > 寬) | 畫面在左,資訊在右 | hstack |
兩個方向都保證畫面原樣縮放、不裁不拉,面板補足剩下的地方。
只截三張圖,動的部分交給 ffmpeg。 面板上會變的只有兩樣:滾到哪、哪一行亮。
所以量一次行位置、截一張底板、截兩張把鏡頭表鋪開的長圖(一張壓暗、一張全亮),
滾動=從暗底長圖裁一個視窗,高亮=從亮條長圖裁一行蓋上去,位置由 sendcmd
在每個切點下發一條小表達式驅動。53 鏡的片子截圖從 53 次降到 3 次(12 秒)。
當前鏡頭釘在第二行:第 1、2 鏡不滾(高亮往下挪一行),第 3 鏡起每切一次往上滾一行。
動在切點,靜在鏡頭裡——切點處滾 --ease(預設 0.45)秒到位就停住,
16 秒的長鏡頭裡讓列表一直爬,觀眾要盯著它晃 16 秒。
行高由內容決定,描述寫得長行就高;高亮條按行高分層(crop 的高度執行時改不動)。
{baseDir} = 本檔案所在目錄。腳本 {baseDir}/scripts/video-sync.mjs,零依賴,node 直接跑。
邊界(不做的事):不做拉片(那是 video-shots 的活,本 skill 吃它的 shots.json)、
不剪輯不轉場不配樂、不燒字幕到畫面上、不做逐幀渲染(動效由 ffmpeg 按時間裁窗做出來)。
Step 0 — 先有拉片資料
要一份 shots.json(video-shots 的產出)和對應的原片。有 frames/(每鏡關鍵幀)
更好——鏡頭表裡會用起手幀當縮圖,沒有就留空格。
shots.json 必須和這條片子對得上:meta.durationSeconds 與原片不符就別合,
先回去把拉片做對。
Step 1 — 先看幾何,再動影片
node {baseDir}/scripts/video-sync.mjs plan shots.json --video <片>
只算不合成,幾秒出結果:版式、畫面區、面板區、輸出尺寸。先確認這三個數字合理再往下走—— 合成一條 3 分鐘的片子要一兩分鐘,算錯了再重來不值。
不滿意就調:--panel <比例>(橫版是面板高÷畫面高,預設 0.8;豎版是面板寬÷畫面寬,預設 1.6)、
--width / --height(畫面區的上限,預設 1920 / 1080)。
原片太小就放大畫面區:面板是跟著畫面區算的,640×360 的片子面板只有 640×288,
四列擠在一起讀不了。--scale 2 把畫面區放到 1280×720、面板放到 1280×576,寬高比不動。
預設是 1(只縮不放)。plan / panels / compose / export 都要帶上同一個 --scale。
Step 2 — 渲染面板
node {baseDir}/scripts/video-sync.mjs panels shots.json --video <片> \
--frames frames --lang zh|en [--out panels] [--chrome <路徑>]
產出 static.png(底板)、list-dim.png / list-lit.png(整張鏡頭表鋪開的長圖,
一暗一亮)、layout.json(每行的位置,合成時按它算滾動),外加一張 panels/panel.html——
用瀏覽器打開它就能預覽面板,#S07 換鏡頭看效果(預覽裡的滾動動效和導出一致)。
改佈局就改 {baseDir}/scripts/panel.css,改完重跑 panels 就是新版面,
不用碰腳本、不用重新想截圖邏輯。樣式約定見 {baseDir}/references/panel-style.md。
Step 3 — 合成
node {baseDir}/scripts/video-sync.mjs compose shots.json --video <片> \
[--panels panels] [-o out.mp4] [--crf 20] [--ease 0.45]
音軌照搬原片(無聲片自動不接音軌)。合成完 stderr 會報輸出尺寸、時長、版式,核對一眼。
兩步也可以一條龍:
node {baseDir}/scripts/video-sync.mjs export shots.json --video <片> -o out.mp4 --frames frames
Step 4 — 抽幀驗收 ⛔ 不能跳
不要只看合成沒報錯就交。 至少抽三幀看看資訊對不對得上畫面:
for t in 5 30 60; do ffmpeg -v error -y -ss $t -i out.mp4 -frames:v 1 -q:v 3 /tmp/check-$t.jpg; done
看三件事:這一幀的畫面屬於哪一鏡、面板上的鏡號是不是同一個、高亮行有沒有跟著走。
對不上通常是 shots.json 和原片不是同一條,或者 --video 指錯了。
彙報一句話說清:輸出路徑、尺寸、時長、版式、多少鏡、面板哪幾張沒渲出來(如果有)。
邊界
- 滾動只發生在切點:滾
--ease秒到位就停住,鏡頭裡面板是靜止的。 真要做「全程勻速爬」也行(改motionPlan的分段),但長鏡頭會一直晃眼睛 - 需要一個無頭瀏覽器(Chrome / Chromium / Edge)。這是「佈局能隨便改」的代價:
換成 ffmpeg 的
drawtext畫面板,中文換行、縮圖、高亮全得自己實現,改個版式要改濾鏡圖 - 詞表(景別 / 類別 / 運鏡 / 轉場)在本 skill 裡自帶一份,不跨目錄 import——
skill 要能整個拷走。
video-shots那邊加了新詞,這邊也要加,不然顯示成枚舉鍵 - 渲染面板固定三張截圖,和鏡頭數無關:53 鏡約 12 秒。合成按片長走,203 秒的片子約 75 秒
自測
node {baseDir}/scripts/selftest.mjs
122 項斷言,不碰 ffmpeg、不開瀏覽器:幾何(橫/方/豎、偶數邊長、上下限、旋鈕)、 面板頁面的資料契約(詞表下發、節奏角色、縮圖有沒有才給、轉義)、 動畫命令(每條都必須短——表達式長了 ffmpeg 直接配置失敗)、ffmpeg 參數。 改完腳本先跑這個。