mirror of
https://github.com/wiltodelta/remove-ai-watermarks.git
synced 2026-06-05 02:28:00 +02:00
feat: strip AI-provenance EXIF tags on removal (v0.5.6)
remove_ai_metadata now scrubs AI tags from the JPEG EXIF instead of passing
the block through wholesale. Closes the v0.5.5 follow-up: the xAI/Grok
Signature + UUID-Artist pair was detected but not removed.
- metadata._scrub_ai_exif(): deletes the xAI signature pair and any
Software/Make/Artist/ImageDescription tag carrying an AI_GENERATOR_TOKENS
token (so Ideogram's Make="Ideogram AI" is scrubbed too), keeping genuine
camera/editor EXIF intact.
- Shared _is_xai_signature_pair / _exif_text helpers (module-level compiled
regexes) are now the single source of truth, used by both xai_signature
and _scrub_ai_exif.
- Tests: Grok signature stripped on JPEG output, Ideogram Make stripped,
real-camera Make ("Apple") preserved. 325 passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -471,6 +471,44 @@ class TestXaiSignature:
|
||||
assert has_ai_metadata(_grok_jpeg(tmp_path)) is True
|
||||
|
||||
|
||||
class TestRemoveAiExif:
|
||||
"""remove_ai_metadata scrubs AI-provenance EXIF tags but keeps genuine EXIF."""
|
||||
|
||||
def test_grok_signature_stripped_on_jpeg_output(self, tmp_path: Path):
|
||||
src = _grok_jpeg(tmp_path)
|
||||
assert xai_signature(src) is True
|
||||
out = tmp_path / "clean.jpg"
|
||||
remove_ai_metadata(src, out)
|
||||
assert xai_signature(out) is False
|
||||
assert has_ai_metadata(out) is False
|
||||
|
||||
def test_generator_make_token_stripped(self, tmp_path: Path):
|
||||
# Ideogram's EXIF Make="Ideogram AI" must be scrubbed on removal.
|
||||
exif = piexif.dump({"0th": {piexif.ImageIFD.Make: b"Ideogram AI"}, "Exif": {}, "GPS": {}, "1st": {}})
|
||||
src = tmp_path / "ideogram.jpg"
|
||||
Image.new("RGB", (64, 64)).save(src, exif=exif)
|
||||
out = tmp_path / "clean.jpg"
|
||||
remove_ai_metadata(src, out)
|
||||
assert exif_generator(out) is None
|
||||
|
||||
def test_real_camera_exif_preserved(self, tmp_path: Path):
|
||||
# A real-camera Make ("Apple") carries no AI token and must survive.
|
||||
exif = piexif.dump(
|
||||
{
|
||||
"0th": {piexif.ImageIFD.Make: b"Apple", piexif.ImageIFD.Model: b"iPhone 15"},
|
||||
"Exif": {},
|
||||
"GPS": {},
|
||||
"1st": {},
|
||||
}
|
||||
)
|
||||
src = tmp_path / "photo.jpg"
|
||||
Image.new("RGB", (64, 64)).save(src, exif=exif)
|
||||
out = tmp_path / "out.jpg"
|
||||
remove_ai_metadata(src, out)
|
||||
kept = piexif.load(Image.open(out).info["exif"])["0th"]
|
||||
assert kept.get(piexif.ImageIFD.Make) == b"Apple"
|
||||
|
||||
|
||||
class TestAIGCLabel:
|
||||
"""China TC260 AIGC labeling (Doubao and other China-served generators)."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user