video-skill 是做什麼的?
腳本位於此檔案旁 scripts/ 目錄中;以下以 $S 代表該目錄,但在每個指令中都需寫出絕對路徑,因為 shell 變數無法在 Bash 呼叫之間傳遞。
你無法觀看影片,所以每個步驟都將其轉換成你能閱讀的內容:你查看的影格和你檢查的數字。 製作一部影片的流程是:寫入 → 渲染影格 → 閱讀聯絡表 → 修正。理解素材的流程是:探測 → 代理 → 時間戳表 + 轉錄稿 → 判斷。交付則透過一個腳本來檢查編碼格式、影格率、faststart 和響度,讓「到處都能播放」成為事實。
首次在 Mac 上使用
自行執行 sh $S/setup.sh。它會檢查 mise 和 ffmpeg(需要的話會透過 Homebrew 安裝 ffmpeg),在首次使用時安裝固定版本的 OpenFilm 並執行 openfilm doctor。修正它印出的每一個 TODO,或向使用者清楚解釋。可以安全地重複執行。
製作影片:OpenFilm
OpenFilm(MIT 授權)是創作格式:一部影片是一個包含 film.html 的資料夾,該檔案將 HTML 頁面、素材、圖片和聲音剪接在一起,每個頁面會在要求的時刻 t 繪製任何內容。Studio 是其本機瀏覽器編輯器。
- 列印說明文件並完整閱讀(內容簡短且每個版本都會變更):
$S/openfilm。遵循說明文件,當此文件與說明文件不一致時以此文件為準。 $S/openfilm是在此處執行 OpenFilm 的唯一方式:它會固定版本,在首次使用時透過 mise 安裝,並防止 Studio 彈出瀏覽器視窗。請勿使用npx openfilm(會繞過固定版本)或npx skills add openfilm/openfilm(這是與此技能衝突的上游技能)。FILM必須是絕對路徑,例如$HOME/Movies/OpenFilm/Projects/<film-name>。使用相對名稱則會放在當前資料夾,例如某人的 git 儲存庫中。影片絕不能放在儲存庫內。
| 步驟 | 指令 |
|---|---|
| 建立或開啟 | $S/openfilm open FILM |
| 檢查整部影片 | $S/openfilm -C FILM look |
| 檢查某個時刻、片段、檔案 | … look 4.5、look 3-6、look assets/bed.wav |
| 檢查快速進入場景 | … look 0-2 --count 24 |
| 渲染 MP4 | $S/openfilm -C FILM render |
| 已連接哪些媒體服務 | $S/openfilm get |
| 診斷安裝 | $S/openfilm doctor |
look 和 render 可以在不開啟 Studio 的情況下執行,並會印出相對於 FILM 的路徑。Studio 的位址包含一個存取金鑰:只提供給與你合作的使用者(它是 127.0.0.1,使用 Chrome 或 Edge,無法從手機存取)。
在展示任何內容之前,請先閱讀每張 look 輸出的工作表,並修正你所看到的問題。它的檢查(同一個 t 繪製相同畫面、字型已載入、文字裁剪、響度)不會判斷品味或版面配置。關於入口動畫中文字被裁切的 ! 警告是正常的;但在靜止狀態下被裁切則是錯誤。然後以全尺寸查看幾個時刻(look 12.5):一個 300 像素的格子會隱藏小細節,包括人物在內。
透過製作一部影片學到的建構方式:
- 每個鏡頭一個頁面;頁面共享的內容(如同一個 Ken Burns 動作、字型、顏色)放在它們匯入的模組中。每個值都只是
t的函數:沒有計時器、沒有動畫迴圈、沒有狀態。 - 頁面自行淡出(透過
t控制不透明度);在同一個軌道上,鏡頭之間透過黑色淡出是最簡單的轉場。交叉淡入淡出需要後續鏡頭位於前一個鏡頭的上方軌道。 - 照片上的文字需要背景陰影(例如底部漸層),否則會在複雜場景中消失。
- 字型放在
FILM/fonts/中,使用@font-face,透過window.film.ready(document.fonts.ready)等待載入。對於照片,使用ready: img.decode()。 - 使用
#t=in,out進行修剪,絕不剪輯新檔案;比影片還長的音效會使影片延長。
音效
$S/openfilm get 顯示已連接的服務;生成語音、音樂和音效需要使用者自己的提供商金鑰(Studio:OpenFilm 標誌 → 設定 → 提供者)。如果沒有金鑰,則使用 ffmpeg 製作音效,儲存在 FILM/assets/ 下,並將指令保留在 FILM/sound-notes.txt 中以便重新建構,同時說明哪個服務能做得更好。你無法聽到音效,因此透過數字來判斷:
- -16 LUFS 整合值適用於最終混音(Podcast、大多數網路影片);串流平台通常標準化在 -14 左右。保持真峰值低於或等於 -1 dBTP。背景音墊在語音下方約 -22。
- 測量:
ffmpeg -i X -af ebur128=peak=true -f null -。使用loudnorm=I=-16:TP=-1.5:LRA=7標準化生成的音墊;對於最終母帶,請執行loudnorm兩次(先測量,然後將measured_*值回傳)以確保增益是線性的。 look會列印混音的響度,而footage.py check會將未達標的渲染標記為失敗。
收到的素材:導入
手機影片通常與看起來的樣子不同。在查看或剪輯之前,請對任何素材執行以下操作:
uv run --no-project --with pillow python -I $S/footage.py probe CLIP.mov ...
uv run --no-project --with pillow python -I $S/footage.py proxy CLIP.mov <scratch>/clip.mp4
probe 會對以下每一項發出警告,而 proxy 則可在一次處理中修復所有問題(SDR BT.709、內嵌旋轉、固定 30 fps、偶數尺寸、H.264 yuv420p、AAC 48 kHz、faststart):
- HDR(iPhone 預設錄製 HLG:
color_transfer=arib-std-b67、BT.2020)。以 SDR 讀取時,看起來會過曝且顏色偏移:朱紅色變成橙色。proxy會先嘗試使用 macOS 內建的avconvert進行色調映射,否則使用 ffmpeg 的zscale(許多 Homebrew 版本中缺少此功能),再否則發出警告。 - 旋轉:手機將直向影片以旋轉標記的方式儲存;某些工具會忽略此標記。
- 可變影格率:標稱 24,平均 23.997。影格時間會漂移,音訊在剪接後會與畫面不同步。
probe會標記任何差異超過 0.001 fps 的情況。 - 奇數尺寸:H.264 yuv420p 需要偶數的寬度和高度。
查看並從代理檔案進行剪輯;保留原始檔案以備需要維持全品質的情況使用。
理解素材
uv run --no-project --with pillow python -I $S/footage.py frames <scratch>/clip.mp4 <scratch>/sheets --every 4
這個指令會在每次場景變換時(--scene 0.3)以及至少每 --every 秒取一個影格,最多 --max 60 個,產出 5×6 格的工作表,每格標記為 0007 · 1:23.4,並附有 manifest.json 將每個 ID 對應到其時間。閱讀工作表並用 ID 和時間回答。這是實務指引,並非絕對規則:
- 創作物:根據鏡頭長度進行均勻取樣。現有素材:場景變換加上每 2-5 秒至少一個影格。快速動作:在短片段中使用
--every 0.5。 - 工作表長邊保持在 ~2,500 像素以下,並一次閱讀少量工作表;對於長時間的素材,將工作表分發給子代理程式,每個子代理程式為每個格子 ID 寫入一個 JSONL 判決,然後執行
python3 -I $S/check_verdicts.py manifest.json out/*.jsonl(缺失、重複或虛構的 ID 會導致失敗;如果一個工作表中所有格子都得到相同答案,則可能是草率略過)。 - 談話性影片的大部分內容都在音訊中。 使用裝置端轉錄並帶入逐字時間戳:
uvx parakeet-mlx AUDIO.wav --output-format json --output-dir <scratch>(先提取音訊:ffmpeg -i clip.mp4 -ar 16000 -ac 1 audio.wav)。根據時間將詞彙與影格對應。專有名詞可能會出錯(「鳥居」→「Torrey」),因此在引用之前請根據所見內容檢查名稱。
交付
uv run --no-project --with pillow python -I $S/footage.py check FILM/<name>.mp4 --duration 30
uv run --no-project --with pillow python -I $S/footage.py share FILM/<name>.mp4 <scratch>/<name>-share.mp4
uv run --no-project --with pillow python -I $S/footage.py gif FILM/<name>.mp4 <scratch>/loop.webp --start 2.5 --duration 6 --width 480
- 在交付檔案之前,
check必須通過:H.264、yuv420p、偶數尺寸、恆定影格率、moov在mdat之前(faststart:可邊下載邊播放)、持續時間、響度 ±1.5 LU、真峰值。 - 當使用者要求檔案或無法使用這台 Mac 時進行渲染;在 MP4 本身上執行
look作為最終檢查。share會產生一個較小的複本(1280 寬、CRF 28:一個 30 秒的 1080p 渲染從 34 MB 降到 3.8 MB),用於網頁或聊天;原始影片的 MP4 保持不變。 - 循環動畫:使用動畫 WebP 而非 GIF。 一個 5.6 秒的照片循環帶慢速縮放,GIF 為 6.7 MB,WebP 僅 1.6 MB:當每個像素都在移動時,GIF 無法重複使用像素。僅在目標無法顯示 WebP 時才使用 GIF;此時
footage.py gif X.gif會使用生成的色盤(palettegen/paletteuse)。 - 移除不應傳遞的資訊:iPhone 影片在其中繼資料中攜帶 GPS、手機型號和時間。
proxy和share會丟棄所有這些資訊(-map_metadata -1)。原始檔案或透過 AirDrop 或照片匯出產生的複本則會保留。使用exiftool -GPSCoordinates FILE檢查。
影片中的照片
使用 photo-skill 技能(github.com/kylefritz/photo-skill;如果未安裝,請提議將其複製到 ~/.claude/skills/photo-skill)來尋找照片(按描述搜尋圖庫,然後判斷聯絡表),並下載全解析度原始檔案:預覽圖對 1080p 來說太小了。將影片使用的照片複製到 FILM/assets/ 中,並重新儲存(移除 EXIF)。在包含某人照片的影片傳送到任何地方之前,請以全尺寸查看每個鏡頭:人臉偵測會遺漏人群,而 300 像素的格子會隱藏 1080p 影格中顯示的人物。
其他工具
OpenFilm 最適合代理程式,因為一部影片就是純 HTML,而 look 正好能回傳你需要看到的內容。截至 2026 年底的替代方案:
| 工具 | 創作方式 | 備註 |
|---|---|---|
| HyperFrames(HeyGen、Apache-2.0) | HTML/CSS + GSAP,由無頭 Chrome 渲染 | 最接近的類似工具;check --json lint/版面/對比度閘道、快照、Claude Code 外掛 |
| Remotion | React 組件 | 成熟、官方的代理程式技能;原始碼可取得授權,超過一定規模的公司需要授權 |
| Manim(MIT) | Python 場景 | 數學和圖解解說 |
| MoviePy(MIT)/ 原生 ffmpeg | Python / filtergraphs | 簡單的剪接和疊加;ffmpeg 最適合做為 I/O 層 |
升級 OpenFilm
上游經常發布新版本。先閱讀發行說明並執行 npm view openfilm scripts(preinstall 或 postinstall 條目需要檢查),然後更改 scripts/openfilm 中的 OPENFILM_VERSION 並重新執行 setup.sh。之後執行 ls ~/Library/Caches/ms-playwright:OpenFilm 的首次執行會刪除沒有連結標記的 Playwright 瀏覽器;setup.sh 會報告它移除了哪些,而 npx playwright install chromium 在需要它的專案中會將其重新安裝。
常見錯誤
| 錯誤 | 修正 |
|---|---|
npx openfilm … | $S/openfilm …:npx 會繞過固定版本 |
在錯誤的資料夾中執行 look 或 render | $S/openfilm -C FILM … |
在沒有開啟其 PNG 的情況下信任 look 的檢查 | 閱讀工作表;檢查通過並不表示排版沒問題 |
| 直接剪輯或查看 iPhone 素材 | 先 probe,再 proxy:HDR、旋轉和 VFR 都會造成問題 |
| 將主要 MP4 傳送到聊天 | 使用 share,然後 check 複本 |
| 為 README 或頁面製作 GIF | 使用 WebP,大小約為 1/4 |
| 升級後使用過時的 Studio | 停止舊的 studio/server/main.mjs 程序(pgrep -fl openfilm);一個 Studio 對應兩個程序 |