video-scrub은(는) 무엇을 하나요?
영상을 깨끗한 파일로 재구성합니다: 화면과 오디오는 그대로, 메타데이터는 바이트 하나도 남기지 않습니다.
{baseDir} = 이 파일이 있는 디렉터리. 스크립트 {baseDir}/scripts/video-scrub.mjs는 의존성이 없으며 node로 바로 실행할 수 있습니다.
원리: 화이트리스트, 블랙리스트가 아닙니다
메타데이터를 지우는 방법은 두 가지가 있으며, 그 차이는 생사(死活)의 차이입니다:
| 방법 | 문제점 | |
|---|---|---|
| 블랙리스트 | 지울 필드를 하나씩 나열하여 삭제 (exiftool 방식) | 모르는 건 지울 수 없습니다 — 제조사 전용 udta, GoPro의 원격 측정 트랙, 스트림 내 SEI 등 하나라도 빠지면 그대로 남습니다 |
| 화이트리스트 | 무엇을 지울지 말하지 않고 무엇을 가져갈지만 지정합니다: 화면 스트림 하나, 오디오 스트림 하나, 나머지는 전혀 가져오지 않음 | — |
화이트리스트를 사용하면 원본 영상의 메타데이터가 들어올 통로가 아예 없습니다. '삭제된' 것이 아니라 '처음부터 옮겨지지 않은' 것입니다.
하지만 ffmpeg는 이 구조를 두 번 저버리므로 반드시 막아야 합니다:
- 기본적으로 원본 컨테이너 태그를 출력에 그대로 옮깁니다 →
-map_metadata -1 - 기본적으로 스트림을 자동으로 선택하여 GoPro의
gpmd원격 측정 트랙까지 함께 가져옵니다 →-map 0:v:0 -map 0:a:0?
2번은 GoPro / DJI 같은 원본의 유일한 방어선입니다: 이들의 GPS는 태그에 있는 것이 아니라 독립적인 시간 메타데이터 트랙에 들어 있습니다. 태그를 아무리 지워도 없어지지 않으며, '두 개의 스트림만 선택'하는 방법만이 이를 막을 수 있습니다.
진짜 어려운 점: 보이지 않는 층들
위의 두 가지를 막으면 태그 레벨은 깨끗해지고 ffprobe는 아무것도 보여주지 않습니다. 하지만 파일 안에는 아직 세 군데의 식별 문자열이 남아 있습니다:
| 숨은 곳 | 내용 | ffprobe로 볼 수 있는가 |
|---|---|---|
| H.264의 SEI | x264 전체 파라미터 문자열 cabac=1 ref=3 … crf=23.0 | ✗ |
| AAC 비트스트림의 DSE | Lavc62.28.102 | ✗ |
| avc1의 compressorname | Lavc 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로 래핑하므로 위조가 아닌 유사 도구의 정상적인 외관 유지 |
quicktime | macOS QuickTime 화면 녹화: Core Media Video / Core Media Audio |
screencapture | ScreenCaptureKit 화면 녹화 |
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개 관문 각각에 대한 침투 테스트 케이스가 포함되어 있어 실제로 차단됨을 증명. 스크립트를 수정한 후 먼저 실행하십시오.