mirror of
https://github.com/wiltodelta/remove-ai-watermarks.git
synced 2026-06-05 02:28:00 +02:00
c196a16900
Broadens metadata provenance coverage at the detection and container-strip level. Detection: - C2PA soft-binding `alg` -> forensic-watermark vendor (Adobe TrustMark, Digimarc, Imatag, Steg.AI, Microsoft, ...) via C2PA_SOFT_BINDINGS + soft_binding_vendors_in(); names the watermark vendor even when the watermark itself can't be decoded. - IPTC Photo Metadata 2025.1 AI-disclosure XMP fields (AISystemUsed etc.) via iptc_ai_system() + IPTC_AI_FIELD_MARKERS. - Adobe TrustMark open keyless decoder (trustmark_detector.py, optional extra `trustmark`) -- the watermark behind Adobe Durable Content Credentials. Detects provenance, not AI origin, so it does not assert is_ai. Removal / containers: - isobmff.strip_c2pa_boxes now also drops a top-level XMP uuid box that carries an AI label (matched by AI-marker content, byte-order-robust; plain XMP kept). - remove_ai_metadata routes MP4/MOV/M4V/M4A (and any ftyp-sniffed ISOBMFF) through the box stripper; raises a clear error for non-ISOBMFF audio/video (WebM/MP3/WAV) instead of crashing in the image path. Tests: soft-binding scan, IPTC element/attribute/presence, MP4 + M4A detect/ strip, ISOBMFF XMP surgical strip, content-sniff, unsupported-container guard, TrustMark absent-safety + identify integration. ruff clean; pyright clean on all new modules. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
37 lines
1.2 KiB
Python
37 lines
1.2 KiB
Python
"""Tests for the optional Adobe TrustMark detector.
|
|
|
|
TrustMark is an optional dependency (extra ``trustmark``) that downloads model
|
|
weights on first use, so the decode path is only exercised when it is installed
|
|
(mirrors the imwatermark handling). The always-on test pins the graceful
|
|
absent/error behaviour: detect must return None, never raise.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
import pytest
|
|
|
|
from remove_ai_watermarks.trustmark_detector import detect_trustmark, is_available
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
|
|
def test_detect_never_raises(tmp_clean_png: Path):
|
|
# Whether or not trustmark is installed, a clean image must yield None
|
|
# (no watermark) without raising. When absent, the import guard returns None.
|
|
assert detect_trustmark(tmp_clean_png) is None
|
|
|
|
|
|
def test_unreadable_file_returns_none(tmp_path: Path):
|
|
bad = tmp_path / "not_an_image.txt"
|
|
bad.write_bytes(b"not an image")
|
|
assert detect_trustmark(bad) is None
|
|
|
|
|
|
@pytest.mark.skipif(not is_available(), reason="trustmark not installed")
|
|
def test_clean_image_reports_no_watermark(tmp_clean_png: Path):
|
|
# With the decoder present, an un-watermarked image must report absent.
|
|
assert detect_trustmark(tmp_clean_png) is None
|