Ignore collector diagnostics in metadata evidence

This commit is contained in:
Victor Kuznetsov
2026-07-29 16:46:42 -07:00
parent 4ef180541f
commit 7c9bec0eba
7 changed files with 56 additions and 6 deletions
+3 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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"
+1 -1
View File
@@ -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"]
+3
View File
@@ -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):
+43
View File
@@ -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",
[
Generated
+1 -1
View File
@@ -3187,7 +3187,7 @@ wheels = [
[[package]]
name = "remove-ai-watermarks"
version = "0.21.1"
version = "0.21.2"
source = { editable = "." }
dependencies = [
{ name = "c2pa-python" },