Communitygithub.com

eternityspring/reelbench-skills

將影片重建為「只有畫面與聲音」的乾淨檔案——原始元數據一概不保留:GPS、裝置型號、帳號ID、建立時間、章節、GoPro的遙測軌跡,全部清除。採用白名單而非黑名單機制,隱私保障源自結構設計,而非列舉排除。

reelbench-skills 是什麼?

video-scrub 是一個專注於影片元數據清理的 AI Agent 技能,能將影片重建為僅含畫面與聲音的乾淨檔案。其核心機制是白名單:只保留視訊與音訊流,其他所有元數據——包括 GPS、裝置型號、帳號ID、建立時間、章節、GoPro/DJI 的遙測軌跡——皆無通道進入輸出檔案。這與黑名單方法(如 exiftool)不同,後者需逐一列舉刪除項目,容易遺漏隱藏數據。 此技能支援 Claude Code、Cursor 等 AI 代理,使用 Node.js 和 ffmpeg,無需任何依賴或 API key。其最大特色是能清理 ffprobe 也看不見的隱藏身份字串:H.264 的 SEI(內含完整 x264 編碼參數)、AAC 碼流的 DSE(包含編碼器版本號)、avc1 的 compressorname。預設 `--mode copy` 模式逐位元複製視訊流,僅重編音訊,處理 53 秒影片僅需 1.3 秒;`--mode encode` 模式則完整重編碼,可消除碼流層浮水印與脆弱隱寫。 驗證機制極其嚴格:將原始影片的所有元數據字串作為「針」,在輸出檔案中進行逐位元組掃描,只要扎中一根即判定失敗。共設 12 道檢查門,涵蓋位元組殘留、標籤層、地理位置、裝置軟體標識、時間戳、旋轉矩陣、流構成、章節、碼流身份字串、profile 相符性、時長及可解碼性。HDR 影片因 SEI 包含必要色彩元數據,會自動跳過 SEI 刪除以避免色調映射錯誤。

相容平台✓Claude Code✓Codex CLI~Cursor
npx skills add https://github.com/eternityspring/reelbench-skills/tree/HEAD/skills/video-scrub

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

video-scrub

把一條影片重建成乾淨檔案:畫面與聲音帶過去,元數據一個位元組都不帶。

{baseDir} = 本檔案所在目錄。腳本 {baseDir}/scripts/video-scrub.mjs,零依賴,node 直接執行。

原理:白名單,不是黑名單

清除元數據有兩種思路,差別是死活:

怎麼做問題
黑名單列出要刪的欄位逐個刪(exiftool 那種)刪不完你不知道的東西——廠商私有的 udta、GoPro 的遙測軌、碼流裡的 SEI,漏一個就漏了
白名單不說刪什麼,只說帶什麼過去:畫面一條流、聲音一條流,別的一律不要——

走白名單,原始元數據沒有任何通道能進來。它不是「被刪掉了」,是從來沒被搬運。

但 ffmpeg 預設會背叛這個結構兩次,必須按住:

  1. 它預設把原始容器的 tag 搬到輸出 → -map_metadata -1
  2. 它預設自動挑流,會把 GoPro 的 gpmd 遙測軌一起帶走 → -map 0:v:0 -map 0:a:0?

第 2 條是 GoPro / DJI 這類原始影片的唯一防線:它們的 GPS 不在 tag 裡,在一條獨立的定時元數據軌上。 清 tag 清不掉,只有「只挑兩條流」擋得住。

真正的難點:看不見的那幾層

按住上面兩條,tag 層就乾淨了,ffprobe 一片安靜。但檔案裡還躺著三處身份串:

藏在哪內容ffprobe 看得見
H.264 的 SEIx264 完整參數串 cabac=1 ref=3 … crf=23.0✗
AAC 碼流的 DSELavc62.28.102✗
avc1 的 compressornameLavc libx264✗

五個藏身處和各自的堵法見 {baseDir}/references/residue-map.md——每一條都有實測記錄, 包括幾個「看著該管用其實沒用」的坑(-flags:v +bitexact 和 -x264-params info=0 都攔不住 SEI)。

兩種模式

模式畫面音訊速度多殺掉什麼
copy(預設)逐位元組照搬重編53 秒的片子 1.3 秒——
encode重編碼重編慢得多碼流層浮水印、脆弱隱寫

音訊兩種模式都重編——AAC 的版本號寫在 DSE 裡,那是碼流內部,-c:a copy 抹不掉它。 重編一遍很划算,換來音軌乾淨。畫面則在 copy 模式下真的一個位元都沒動(cmp 驗證過, 除了被摘掉的 SEI)。

關於浮水印:encode 比 copy 多殺的是碼流層浮水印和脆弱隱寫,這兩類在實際素材裡少見。 真正要命的穩健像素浮水印(SynthID、影視取證浮水印)設計目標就是扛住重編碼,兩種模式都殺不掉。 所以選模式按畫質和速度選,別指望重編碼能洗掉浮水印。

四個 profile

擦白之後往回寫的那一層:

profile寫什麼
obs(預設)OBS Studio 的長相。OBS 本來就是用 ffmpeg 封裝的,所以這檔不是偽造,是保持同類工具的正常外觀
quicktimemacOS QuickTime 錄屏:Core Media Video / Core Media Audio
screencaptureScreenCaptureKit 錄屏
bare什麼都不寫。隱私強度最高——不留任何可被證偽的聲明

有一件事做不到,寫在這裡省得再試:容器的 encoder 欄位由 muxer 自己佔著, -metadata encoder=Lavf60.16.100 蓋不過去,置空也清不掉(實測)。只有 -fflags +bitexact 能讓它整個消失。所以 obs 檔不偽造版本號,它就讓 ffmpeg 寫自己的真版本號。


Step 0 — 先看原始影片帶了什麼

node {baseDir}/scripts/video-scrub.mjs inspect <video>

一屏列全:容器 tag、每條流的 tag 與 side data、章節,以及碼流裡那幾處 ffprobe 看不見的身份串。 末尾會告訴你驗收時要拿幾根針去掃,以及 SEI 這刀動不動。

先看這一屏再動手。 尤其注意兩件事:

  • 有沒有 data / timecode 流——有就說明原始影片可能帶遙測軌(GoPro、DJI)
  • 是不是 HDR——是的話 SEI 一律不刪(下面說為什麼)

Step 1 — 清

node {baseDir}/scripts/video-scrub.mjs scrub <video> -o out.mp4

預設 --mode copy --profile obs。常用參數:

--mode encode          # 完整重編碼
--profile bare         # 什麼都不寫,隱私強度最高
--crf 18               # 只在 encode 模式有意義
--date 2026-01-01T00:00:00Z   # 指定建立時間,不給就用當前時間

Step 2 — 驗收 ⛔ 不能跳

node {baseDir}/scripts/video-scrub.mjs verify <原始> <輸出>

或者一條龍(日常就用這個):

node {baseDir}/scripts/video-scrub.mjs run <video> -o out.mp4

驗收的核心是一句話:前面全是「我調了正確的參數」,屬於聲稱;這一步是「我掃了,真沒了」,屬於證明。

做法是把原始影片的所有元數據字串抓出來當「針」——tag 的鍵和值、handler 名、章節標題—— 在輸出檔案裡做位元組級掃描,扎到一根就紅。

兩條實現上的講究:

  • 針要 ≥5 位元組。更短的串會在壓縮資料裡隨機撞出假陽性,跳過的針會在提示裡列出來。
  • 通用串不當針。VideoHandler 滿世界都是,資訊量為零;Lavf58.45.100 釘死了一個版本, 是指紋。分界線是「能不能把範圍縮小到某個人、某台裝置、某次匯出」。

12 道門

門攔什麼
位元組級殘留原始影片的元資料串扎中了輸出
tag 層殘留輸出的 tag 值來自原始影片
位置資訊任何 GPS / location 欄位
裝置與軟體標識make / model / software / artist / comment 之類
原始影片時間戳輸出的 creation_time 等於原始影片的
旋轉矩陣encode 模式下旋轉沒烘進畫面(copy 模式跳過——刪了畫面就是歪的)
流構成混進了 data / timecode / 附件流
章節章節跟過來了
碼流身份串SEI / DSE / compressorname 裡還有編碼器名字
profile 相符剩下的 tag 和聲明的 profile 對不上
時長輸出被偷偷截斷
可解碼產出了打不開的壞檔案

HDR 與內嵌字幕:SEI 這刀什麼時候不許動

ffmpeg 只給得起 NAL 級的粒度——filter_units 按 NAL 類型刪, h264_metadata 也沒有「只刪某個 SEI payload」的選項。所以 remove_types=6 是 全部 SEI 一起刪。而 SEI 裡除了 x264 那串垃圾,還住著:

  • HDR 靜態元資料(mastering display / content light level)——刪了會掉色,tone mapping 崩
  • CEA-608/708 內嵌字幕——刪了字幕沒了

所以腳本的策略是:只在真查到身份串時才動刀,HDR 原始影片一律不動(會明說沒動, 以及為什麼)。這個判斷由 seiPlan() 做,不需要你操心,但你該知道它在那兒。

邊界(不做的事)

不去除浮水印(穩健像素浮水印重編碼也殺不掉,別指望)、不改畫面內容(不裁切不縮放不旋轉)、 不偽造裝置型號和 GPS(profile 只寫工具類資訊,不編造拍攝裝置和位置)、 不做批次目錄掃描(一次一條片子)、不處理非 mp4/mov 之外的容器。

自測

node {baseDir}/scripts/selftest.mjs

117 項斷言,不碰 ffmpeg、不碰真檔案。12 道門每道都有擊穿用例——證明它真的會攔。 改完腳本先跑這個。

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

eternityspring/reelbench-skills

拉片:把一条成片拆成逐镜头的分析表——每个镜头的时长、景别、类别、运镜、画面。 分工刻在骨子里:**能量的都由代码量**(切点来自 ffmpeg 场景检测,时长是切点相减, 运动量是逐帧差分的中位数),模型只判断它真正该判断的那几件事(景别 / 类别 / 运镜 / 画面 / 节奏), 然后每一条判断都被代码当场对账——**声称推拉摇移却实测几乎不动,门直接拦**。 看片走联系表(每镜起手帧 + 收尾帧各拼一张大图,一屏二十几个镜头,a/b 对照就是运镜), 不是一张张翻。检测漏刀多刀用 recut 补刀并刀,自动重编号重算时长,手改边界过不了门。 产出 shots.json + Markdown 镜头表 + **单页交互式拉片报告**:内嵌播放器(播放时同步高亮镜头、 点镜头跳转)、镜头节奏带、可搜索可筛选可排序的镜头表(列表 / 卡片两种视图、首尾关键帧并排、 点图开大图)、景别类别运镜分布、出场人物、质量门、导出 JSON。单文件零依赖,离线双击能开。 15 道质量门全部由脚本确定性检查。 零依赖、零 API key,只要 node 和 ffmpeg。 Use when asked to 拉片、拆镜头、分析视频镜头、镜头时长、景别、运镜、镜头表、 video shot breakdown、shot list from video。

eternityspring/reelbench-skills

把拉片数据和原片合成一条**能直接看的视频**:一边是画面,一边是这一镜的分镜信息 (镜号、起止、时长、景别、类别、运镜、画面描述、台词),**镜头切了信息跟着切**, 镜头表自动滚动并高亮当前这一镜。 版式只看原片的宽高比:**横版 / 方版 → 画面在上、信息在下;竖版 → 画面在左、信息在右**, 画面永远原样缩放,不裁不拉。 镜头表**随播放滚动**:切点处滚一小段把当前镜头带到锚点、随后停住,高亮条同步滑过去。 实现上只截三张图(底板 + 铺开的长图 ×2),滚动与高亮由 ffmpeg 按时间裁窗, **布局全在 scripts/panel.css 里,改它就能改版式**,不用碰脚本。 吃 video-shots 产出的 shots.json(有 frames/ 就把关键帧当缩略图用)。 零 npm 依赖,用 ffmpeg 合成、无头浏览器渲面板。 Use when asked to 导出视频、合成视频、分镜视频、带分镜信息的视频、解说版视频、 video with shot info、annotated shot video。

相關技能