Communitygithub.com

video-scrub

영상에서 모든 메타데이터를 제거하고 깨끗한 화면+오디오만 남깁니다. GPS, 기기 정보, 생성 시간, 챕터, 텔레메트리 트랙을 완전히 삭제합니다.

video-scrub란 무엇인가요?

이 스킬은 AI 에이전트가 영상 파일에서 민감한 메타데이터를 완전히 제거할 수 있도록 설계된 도구입니다. GPS 위치, 기기 모델, 계정 ID, 생성 시간, 챕터, GoPro의 원격 측정 트랙 등 모든 식별 가능한 정보를 제거합니다. 블랙리스트 방식(지운 항목을 나열)과 달리 화이트리스트 방식(화면 1개 스트림, 오디오 1개 스트림만 유지)을 사용하여 구조적으로 누출을 차단합니다. 일반적인 `ffprobe`로는 볼 수 없는 H.264 SEI, AAC DSE, avc1 compressor name 등의 깊은 위치에 숨겨진 데이터까지 제거합니다. Node.js와 ffmpeg만 있으면 바로 사용할 수 있으며, 12가지 자동 검증 항목(바이트 수준 스캔 포함)을 통해 출력 파일에 원본 파일의 메타데이터 문자열이 하나도 남지 않았음을 확인합니다. 기본 모드(`--mode copy`)는 화면을 바이트 단위로 그대로 복사하여(53초 영상 1.3초) 성능 저하 없이 안전하게 정리하며, `--mode encode`는 완전 재인코딩으로 스트림 수준의 워터마크까지 제거합니다. OBS, QuickTime, ScreenCaptureKit 프로필을 지원하며, HDR 영상의 색상 메타데이터는 자동으로 보존합니다.

지원 대상✓Claude Code✓Codex CLI~Cursor
npx skills add https://github.com/eternityspring/reelbench-skills/tree/HEAD/skills/video-scrub

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

video-scrub은(는) 무엇을 하나요?

영상을 깨끗한 파일로 재구성합니다: 화면과 오디오는 그대로, 메타데이터는 바이트 하나도 남기지 않습니다.

{baseDir} = 이 파일이 있는 디렉터리. 스크립트 {baseDir}/scripts/video-scrub.mjs는 의존성이 없으며 node로 바로 실행할 수 있습니다.

원리: 화이트리스트, 블랙리스트가 아닙니다

메타데이터를 지우는 방법은 두 가지가 있으며, 그 차이는 생사(死活)의 차이입니다:

방법문제점
블랙리스트지울 필드를 하나씩 나열하여 삭제 (exiftool 방식)모르는 건 지울 수 없습니다 — 제조사 전용 udta, GoPro의 원격 측정 트랙, 스트림 내 SEI 등 하나라도 빠지면 그대로 남습니다
화이트리스트무엇을 지울지 말하지 않고 무엇을 가져갈지만 지정합니다: 화면 스트림 하나, 오디오 스트림 하나, 나머지는 전혀 가져오지 않음—

화이트리스트를 사용하면 원본 영상의 메타데이터가 들어올 통로가 아예 없습니다. '삭제된' 것이 아니라 '처음부터 옮겨지지 않은' 것입니다.

하지만 ffmpeg는 이 구조를 두 번 저버리므로 반드시 막아야 합니다:

  1. 기본적으로 원본 컨테이너 태그를 출력에 그대로 옮깁니다 → -map_metadata -1
  2. 기본적으로 스트림을 자동으로 선택하여 GoPro의 gpmd 원격 측정 트랙까지 함께 가져옵니다 → -map 0:v:0 -map 0:a:0?

2번은 GoPro / DJI 같은 원본의 유일한 방어선입니다: 이들의 GPS는 태그에 있는 것이 아니라 독립적인 시간 메타데이터 트랙에 들어 있습니다. 태그를 아무리 지워도 없어지지 않으며, '두 개의 스트림만 선택'하는 방법만이 이를 막을 수 있습니다.

진짜 어려운 점: 보이지 않는 층들

위의 두 가지를 막으면 태그 레벨은 깨끗해지고 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, 영상 포렌식)는 재인코딩을 견디도록 설계되어 두 모드 모두 제거할 수 없습니다. 따라서 모드는 화질과 속도에 따라 선택하고, 재인코딩으로 워터마크가 지워질 것이라고 기대하지 마십시오.

네 가지 프로필

지우고 나서 다시 쓰는 레이어:

프로필쓰는 내용
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>

한 화면에 모든 것을 표시: 컨테이너 태그, 각 스트림의 태그와 side data, 챕터, 그리고 ffprobe로는 볼 수 없는 비트스트림 내의 식별 문자열들. 마지막에 검증 시 몇 개의 바늘로 스캔해야 하는지, 그리고 SEI를 건드릴지 여부를 알려줍니다.

작업 전에 이 화면을 먼저 확인하십시오. 특히 다음 두 가지에 주의:

  • data / timecode 스트림이 있는지 — 있다면 원본에 원격 측정 트랙(GoPro, DJI)이 있을 수 있음
  • HDR인지 — 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

검증의 핵심은 한 문장입니다: 앞의 모든 단계는 '올바른 파라미터를 설정했다'는 주장에 불과하며, 이 단계는 '스캔해서 진짜로 없음을 확인했다'는 증명입니다.

방법은 원본 영상의 모든 메타데이터 문자열을 '바늘'로 추출하여 — 태그의 키와 값, 핸들러 이름, 챕터 제목 등 — 출력 파일에서 바이트 레벨로 스캔하여 하나라도 검출되면 빨간불을 표시합니다.

구현상의 두 가지 세부 사항:

  • 바늘은 ≥5바이트여야 합니다. 더 짧은 문자열은 압축 데이터에서 무작위로 가양성을 발생시킬 수 있으며, 건너뛴 바늘은 프롬프트에 표시됩니다.
  • 일반적인 문자열은 바늘로 사용하지 않습니다. VideoHandler는 어디에나 있어 정보량이 0이며, Lavf58.45.100은 특정 버전을 고정하므로 지문입니다. 경계는 '범위를 특정인, 특정 장치, 특정 내보내기로 좁힐 수 있는가'입니다.

12개의 관문

관문차단하는 것
바이트 레벨 잔류물원본 메타데이터 문자열이 출력에서 검출
태그 레벨 잔류물출력 태그 값이 원본에서 유래
위치 정보모든 GPS / location 필드
장치 및 소프트웨어 식별make / model / software / artist / comment 등
원본 타임스탬프출력의 creation_time이 원본과 같음
회전 행렬encode 모드에서 회전이 화면에 굽혀지지 않음 (copy 모드는 건너뜀 — 지우면 화면이 기울어짐)
스트림 구성data / timecode / 첨부 파일 스트림이 혼입
챕터챕터가 따라옴
비트스트림 식별 문자열SEI / DSE / compressorname에 인코더 이름이 남아 있음
프로필 일치남은 태그가 선언된 프로필과 일치하지 않음
길이출력이 몰래 잘림
디코딩 가능열 수 없는 손상 파일이 생성됨

HDR과 내장 자막: SEI를 건드릴 수 없는 경우

ffmpeg는 NAL 레벨의 세분성만 제공합니다 — filter_units는 NAL 유형별로 삭제하고, h264_metadata에도 '특정 SEI 페이로드만 삭제' 옵션은 없습니다. 따라서 remove_types=6은 모든 SEI를 한꺼번에 삭제합니다. 그런데 SEI 안에는 x264의 쓰레기 문자열 외에도 다음이 들어 있습니다:

  • HDR 정적 메타데이터 (mastering display / content light level) — 삭제하면 색상이 깨지고 톤 매핑이 망가짐
  • CEA-608/708 내장 자막 — 삭제하면 자막이 사라짐

따라서 스크립트의 전략은: 실제로 식별 문자열이 발견된 경우에만 SEI를 건드리며, HDR 원본은 절대 건드리지 않습니다 (건드리지 않았으며 그 이유도 명확히 표시). 이 판단은 seiPlan()이 수행하며 사용자가 신경 쓸 필요가 없지만, 존재한다는 사실을 알아야 합니다.

경계 (하지 않는 일)

워터마크 제거(강인 픽셀 워터마크는 재인코딩으로도 제거 불가, 기대하지 마십시오), 화면 내용 변경(자르기/크기 조정/회전 안 함), 장치 모델 및 GPS 위조(프로필은 도구 정보만 기록, 촬영 장치나 위치는 조작하지 않음), 배치 디렉터리 스캔(한 번에 하나의 파일만 처리), 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。

관련 스킬