mirror of
https://github.com/wiltodelta/remove-ai-watermarks.git
synced 2026-08-06 22:18:36 +02:00
Ignore collector diagnostics in metadata evidence
This commit is contained in:
@@ -137,7 +137,9 @@ metadata extraction from verdict logic:
|
||||
- `extract_provenance_evidence` reads the supported metadata signals into
|
||||
`ProvenanceEvidence`.
|
||||
- `evidence_from_metadata_record` normalizes an externally collected nested
|
||||
metadata record into the same evidence type without file access.
|
||||
metadata record into the same evidence type without file access. Diagnostic
|
||||
values under `error` and `kind` are excluded from evidence while nested raw
|
||||
bytes remain available through encoded binary fields.
|
||||
- `identify_from_evidence` evaluates that evidence without reopening the source.
|
||||
- `identify` preserves the path-based API and adds the optional registered
|
||||
visible-mark and open invisible-watermark decoders after extraction.
|
||||
|
||||
+4
-2
@@ -132,8 +132,10 @@ report = identify_from_evidence(evidence)
|
||||
|
||||
The normalizer recursively preserves text and byte values. It also decodes
|
||||
strings prefixed with `hex:` and fields named `base64` or ending in
|
||||
`_base64`. Pass a C2PA manifest-store dictionary in `record["c2pa_store"]`, or
|
||||
through the explicit `c2pa_manifest_store` argument.
|
||||
`_base64`. Diagnostic values under `error` and `kind` are ignored because they
|
||||
describe the collector rather than the source file. Pass a C2PA manifest-store
|
||||
dictionary in `record["c2pa_store"]`, or through the explicit
|
||||
`c2pa_manifest_store` argument.
|
||||
|
||||
`identify_from_evidence` does not reopen the source file. It evaluates metadata
|
||||
only; registered visible marks and pixel-backed invisible watermarks remain in
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "remove-ai-watermarks"
|
||||
version = "0.21.1"
|
||||
version = "0.21.2"
|
||||
description = "AI watermark remover: strip visible and invisible AI watermarks (Gemini / Nano Banana sparkle, SynthID) and provenance metadata (C2PA, EXIF) from images"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10.1"
|
||||
|
||||
@@ -25,7 +25,7 @@ _os.environ.setdefault("TRANSFORMERS_VERBOSITY", "error")
|
||||
_warnings.filterwarnings("ignore", message=r".*ImageProcessorFast.*")
|
||||
|
||||
|
||||
__version__ = "0.21.1"
|
||||
__version__ = "0.21.2"
|
||||
|
||||
__all__ = ["__version__", "remove_visible", "visible_provenance"]
|
||||
|
||||
|
||||
@@ -169,6 +169,7 @@ def _external_metadata(value: Any) -> tuple[list[tuple[str, Any]], bytes]:
|
||||
"""Index nested metadata and recover common encoded binary values in one pass."""
|
||||
pairs: list[tuple[str, Any]] = []
|
||||
parts: list[bytes] = []
|
||||
diagnostic_keys = {"error", "kind"}
|
||||
|
||||
def visit(item: Any) -> None:
|
||||
if isinstance(item, dict):
|
||||
@@ -177,6 +178,8 @@ def _external_metadata(value: Any) -> tuple[list[tuple[str, Any]], bytes]:
|
||||
key_text = str(key)
|
||||
pairs.append((key_text, nested))
|
||||
parts.append(key_text.encode("utf-8", "replace"))
|
||||
if key_text.lower() in diagnostic_keys:
|
||||
continue
|
||||
if isinstance(nested, str) and (key_text == "base64" or key_text.endswith("_base64")):
|
||||
encoded = nested.split("...TRUNCATED", 1)[0]
|
||||
with contextlib.suppress(ValueError, TypeError):
|
||||
|
||||
@@ -6,6 +6,7 @@ against the real committed C2PA / IPTC fixtures in data/fixtures/provenance/.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -61,6 +62,48 @@ class TestProvenanceEvidence:
|
||||
assert report.is_ai_generated is True
|
||||
assert {signal.name for signal in report.signals} >= {"gen_params", "xai_signature"}
|
||||
|
||||
def test_external_scanner_diagnostics_do_not_create_c2pa_evidence(self, tmp_path: Path):
|
||||
path = tmp_path / "plain.jpg"
|
||||
record = {
|
||||
"c2pa_store": {"error": "ManifestNotFound: no JUMBF data found"},
|
||||
"jpeg": {
|
||||
"segments": [
|
||||
{
|
||||
"marker": "APP11",
|
||||
"kind": "c2pa_or_jumbf",
|
||||
"base64": "AAA=",
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
report = identify_from_evidence(evidence_from_metadata_record(record, path=path))
|
||||
|
||||
assert report.is_ai_generated is None
|
||||
assert report.signals == []
|
||||
assert report.watermarks == []
|
||||
|
||||
def test_external_scanner_raw_bytes_still_create_c2pa_evidence(self, tmp_path: Path):
|
||||
path = tmp_path / "signed.jpg"
|
||||
manifest = b"jumb c2pa OpenAI trainedAlgorithmicMedia"
|
||||
record = {
|
||||
"jpeg": {
|
||||
"segments": [
|
||||
{
|
||||
"marker": "APP11",
|
||||
"kind": "c2pa_or_jumbf",
|
||||
"base64": base64.b64encode(manifest).decode(),
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
report = identify_from_evidence(evidence_from_metadata_record(record, path=path))
|
||||
|
||||
assert report.is_ai_generated is True
|
||||
assert report.platform == "OpenAI (ChatGPT / gpt-image / DALL-E / Sora)"
|
||||
assert [signal.name for signal in report.signals] == ["c2pa"]
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"filename",
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user