mirror of
https://github.com/wiltodelta/remove-ai-watermarks.git
synced 2026-07-30 11:18:49 +02:00
feat(metadata): strip container metadata from WebM/MP3/WAV/FLAC/OGG via ffmpeg (v0.6.4)
remove_ai_metadata now handles non-ISOBMFF audio/video (which the box walker can't reach) by shelling out to ffmpeg with a lossless stream copy (`-map_metadata -1 -map_chapters -1 -c copy`): codec data is untouched, only container tags/chapters (ID3 / RIFF / Vorbis comments / EBML tags) are dropped. Requires ffmpeg on PATH; raises a clear RuntimeError if absent or if ffmpeg can't parse the input (instead of crashing in the image path). Verified end-to-end: a real ffmpeg-made WAV/MP3 with a "Suno AI" title tag -> tag gone, audio bytes preserved. NOT built (evaluated, deliberate): Resemble PerTh audio *detection* -- `get_watermark()` returns a raw bit array with no presence/confidence flag, so reliably telling watermarked from clean needs Resemble's fixed payload or a confidence API (neither public; no real sample to calibrate). Same wall as the SynthID pixel detector. AVIF/HEIF meta-box EXIF/XMP stripping also stays a gap (needs exiftool, a non-installed binary). Both documented in CLAUDE.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
bc3228d387
commit
f9cf14c372
+31
-3
@@ -2,6 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import piexif
|
||||
@@ -699,9 +701,35 @@ class TestIsobmffMetadataRemoval:
|
||||
remove_ai_metadata(src, out)
|
||||
assert out.read_bytes() == _MP4_FTYP + _MP4_MDAT
|
||||
|
||||
def test_unsupported_container_raises(self, tmp_path: Path):
|
||||
def test_unparseable_audio_raises(self, tmp_path: Path):
|
||||
# Garbage that ffmpeg can't parse must raise a clear error, not crash in
|
||||
# the image path. (When ffmpeg is absent this still raises RuntimeError.)
|
||||
src = tmp_path / "audio.mp3"
|
||||
src.write_bytes(b"ID3\x04\x00\x00\x00\x00\x00\x00 fake mp3 frames")
|
||||
src.write_bytes(b"ID3\x04\x00\x00\x00\x00\x00\x00 not real mp3 frames")
|
||||
out = tmp_path / "out.mp3"
|
||||
with pytest.raises(ValueError, match="not supported"):
|
||||
with pytest.raises(RuntimeError):
|
||||
remove_ai_metadata(src, out)
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("ffmpeg") is None, reason="ffmpeg not installed")
|
||||
class TestFfmpegMetadataStrip:
|
||||
"""Lossless container-metadata strip for non-ISOBMFF audio/video via ffmpeg."""
|
||||
|
||||
def _wav_with_tag(self, path: Path, tag: str = "Suno AI") -> None:
|
||||
subprocess.run( # noqa: S603
|
||||
[
|
||||
shutil.which("ffmpeg"), "-y", "-loglevel", "error",
|
||||
"-f", "lavfi", "-i", "sine=frequency=440:duration=0.1",
|
||||
"-metadata", f"title={tag}", str(path),
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
|
||||
def test_strips_wav_title_metadata(self, tmp_path: Path):
|
||||
src = tmp_path / "in.wav"
|
||||
self._wav_with_tag(src, "Suno AI generated")
|
||||
assert b"Suno AI generated" in src.read_bytes() # tag is present pre-strip
|
||||
out = tmp_path / "clean.wav"
|
||||
remove_ai_metadata(src, out)
|
||||
assert out.exists()
|
||||
assert b"Suno AI generated" not in out.read_bytes() # tag stripped, audio kept
|
||||
|
||||
Reference in New Issue
Block a user