Merge remote-tracking branch 'origin/main' into docs/arxiv-paper-review

# Conflicts:
#	docs/installation.md
#	docs/supported-signals.md
#	docs/verification-plan.md
This commit is contained in:
Victor Kuznetsov
2026-08-28 11:18:08 -07:00
122 changed files with 2634 additions and 456 deletions
+35 -2
View File
@@ -228,8 +228,8 @@ class TestVisibleCommand:
# The transparent corners must remain transparent.
assert out[0, 0, 3] == 0
assert out[199, 199, 3] == 0
# The opaque centre remains opaque (the watermark region default is bottom-right,
# which doesn't overlap the centre square at 200x200).
# The opaque center remains opaque (the watermark region default is bottom-right,
# which doesn't overlap the center square at 200x200).
assert out[100, 100, 3] == 255
def test_visible_keeps_alpha_opaque_in_watermark_region(self, runner, tmp_path):
@@ -426,6 +426,39 @@ class TestInvisibleCommand:
assert result.exit_code == 0, result.output
mock_engine.remove_watermark.assert_called_once()
def test_invisible_explicit_vendor_implies_force_and_sets_cohort(self, runner, sample_png, tmp_path):
"""--vendor meta names a cohort the file cannot prove (Content Seal carries
no C2PA), so it must both bypass the no-signal skip and arrive at the engine
as the vendor, where it resolves to the measured Meta floor (not the
area-curve value the same size would otherwise get)."""
mock_cls, mock_engine = _mock_invisible_engine()
output = tmp_path / "clean.png"
with (
patch("remove_ai_watermarks.invisible_engine.is_available", return_value=True),
patch("remove_ai_watermarks.cli.InvisibleEngine", mock_cls, create=True),
patch("remove_ai_watermarks.invisible_engine.InvisibleEngine", mock_cls),
):
result = runner.invoke(main, ["invisible", str(sample_png), "-o", str(output), "--vendor", "meta"])
assert result.exit_code == 0, result.output
kwargs = mock_engine.remove_watermark.call_args.kwargs
assert kwargs["vendor"] == "meta"
assert "0.1" in result.output # the resolved Meta floor, printed
assert "0.094" not in result.output # not the sample's area-curve value (200x200 -> ~0.0944)
def test_invisible_vendor_auto_keeps_detection_semantics(self, runner, sample_png, tmp_path):
"""--vendor auto is the default spelled out: detection still runs and a
no-signal file still skips."""
mock_cls, mock_engine = _mock_invisible_engine()
output = tmp_path / "clean.png"
with (
patch("remove_ai_watermarks.invisible_engine.is_available", return_value=True),
patch("remove_ai_watermarks.cli.InvisibleEngine", mock_cls, create=True),
patch("remove_ai_watermarks.invisible_engine.InvisibleEngine", mock_cls),
):
result = runner.invoke(main, ["invisible", str(sample_png), "-o", str(output), "--vendor", "auto"])
assert result.exit_code == 2, result.output
mock_engine.remove_watermark.assert_not_called()
def test_invisible_runs_without_force_when_signal_present(self, runner, tmp_path):
"""An image carrying an AI metadata signal IS a scrub target, so the run
proceeds with no --force needed."""
+69
View File
@@ -0,0 +1,69 @@
"""Tests for the Content Seal oracle corpus layout.
Mirrors the synthid corpus guard: the manifest is the source of truth for
which binaries exist, and every recorded hash must match the file it names.
Derived rows are recipes, not stored files, so only originals are checked
against disk.
"""
from __future__ import annotations
import csv
import hashlib
import re
from pathlib import Path
CORPUS_DIR = Path(__file__).resolve().parent.parent / "data" / "contentseal"
MANIFEST = CORPUS_DIR / "manifest.csv"
ORIGINALS = CORPUS_DIR / "originals"
_SHA256 = re.compile(r"^[0-9a-f]{64}$")
_VALID_VERDICTS = {"detected", "not_detected", ""}
_VALID_ORIGINS = {"meta-model-api", "derived", "meta-blog-cdn", "synthetic-local"}
def _manifest_rows() -> list[dict[str, str]]:
with open(MANIFEST, newline="") as f:
return list(csv.DictReader(f))
def test_manifest_original_rows_match_binaries_and_hashes() -> None:
rows = [r for r in _manifest_rows() if r["file"]]
stored = {path.name for path in ORIGINALS.iterdir() if path.is_file()}
assert {r["file"].removeprefix("originals/") for r in rows} == stored
for row in rows:
digest = hashlib.sha256((CORPUS_DIR / row["file"]).read_bytes()).hexdigest()
assert digest == row["sha256"], row["file"]
def test_manifest_rows_are_well_formed() -> None:
rows = _manifest_rows()
assert len({row["sha256"] for row in rows}) == len(rows), "duplicate sha256"
for row in rows:
assert _SHA256.match(row["sha256"]), row["name"]
assert row["origin"].split(":")[0] in _VALID_ORIGINS, row["name"]
assert row["oracle_verdict"] in _VALID_VERDICTS, row["name"]
# Every oracle verdict must carry its check timestamp.
if row["oracle_verdict"]:
assert row["checked_at_utc"], row["name"]
# Detection rows must name the oracle attribution.
if row["oracle_verdict"] == "detected":
assert "Muse Image 1" in row["oracle_attribution"], row["name"]
def test_default_pipeline_clearance_is_recorded() -> None:
"""The verified claim that the default profile clears Content Seal must stay."""
rows = {row["name"]: row for row in _manifest_rows()}
for name in ("fox_modal_invisible", "text_modal_invisible"):
assert rows[name]["oracle_verdict"] == "not_detected", name
def test_deterministic_transforms_reproduce_recorded_hashes(tmp_path: Path) -> None:
from scripts.contentseal_transforms import reproduce_transforms
outputs = reproduce_transforms(tmp_path)
assert len(outputs) == 8
assert all(path.is_file() for path in outputs)
@@ -0,0 +1,88 @@
"""Regression: a manifest that names its own forensic soft binding must not
also report a SynthID watermark from the generic vendor-token inference.
Microsoft Designer manifests sign as Microsoft, carry the InvisMark
``c2pa.watermarked`` action, and name their generation agent
"Azure OpenAI ImageGen". The OpenAI issuer token inside that agent name plus
the watermarked action used to satisfy the OpenAI SynthID-evidence rule,
double-counting one forensic mark as two pixel watermarks.
"""
from __future__ import annotations
from remove_ai_watermarks._internal.c2pa import c2pa_info_from_manifest_store
DESIGNER_STORE = {
"active_manifest": "designer",
"manifests": {
"designer": {
"signature_info": {"issuer": "Microsoft Corporation", "common_name": "Microsoft Corporation"},
"claim_generator_info": [{"name": "Microsoft Responsible AI Provenance", "version": "1.0"}],
"assertions": [
{
"label": "c2pa.actions",
"data": {
"actions": [
{
"action": "c2pa.created",
"softwareAgent": {"name": "Azure OpenAI ImageGen"},
"digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia",
},
{"action": "c2pa.watermarked"},
]
},
},
{
"label": "c2pa.soft-binding",
"data": {
"alg": "com.microsoft.invismark.1",
"blocks": [{"value": "bf7a2993-cc1f-47e1-b1f0-cd8839aabb22"}],
},
},
],
}
},
}
def test_named_soft_binding_suppresses_generic_synthid_evidence() -> None:
info = c2pa_info_from_manifest_store(DESIGNER_STORE)
assert info["ai_source_kind"] == "generated"
assert info["soft_binding_algorithm"] == "com.microsoft.invismark.1"
assert info.get("synthid_watermark") is None
assert info.get("synthid_vendors") is None
def test_vendor_agent_name_alone_is_not_the_vendors_provenance() -> None:
"""The identity-scoped inference must not fire on a service name either.
Same manifest without the soft binding: the "Azure OpenAI ImageGen" agent
is not an OpenAI signature or claim generator, so no OpenAI SynthID
evidence may be derived from it.
"""
store = {
"active_manifest": "designer",
"manifests": {
"designer": {
"signature_info": {"issuer": "Microsoft Corporation", "common_name": "Microsoft Corporation"},
"claim_generator_info": [{"name": "Microsoft Responsible AI Provenance", "version": "1.0"}],
"assertions": [
{
"label": "c2pa.actions",
"data": {
"actions": [
{
"action": "c2pa.created",
"softwareAgent": {"name": "Azure OpenAI ImageGen"},
"digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia",
},
{"action": "c2pa.watermarked"},
]
},
}
],
}
},
}
info = c2pa_info_from_manifest_store(store)
assert info.get("synthid_watermark") is None
+23
View File
@@ -0,0 +1,23 @@
"""Cross-corpus integrity checks for tracked evaluation tables."""
from __future__ import annotations
import csv
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
EVALUATIONS = ROOT / "data" / "evaluations"
SHA256 = re.compile(r"^[0-9a-f]{64}$")
def test_every_recorded_evaluation_sha256_is_well_formed() -> None:
checked = 0
for path in sorted(EVALUATIONS.rglob("*.csv")):
with path.open(newline="", encoding="utf-8") as stream:
for line_number, row in enumerate(csv.DictReader(stream), start=2):
for field, value in row.items():
if field is not None and field.endswith("sha256") and value:
assert SHA256.fullmatch(value), f"{path.relative_to(ROOT)}:{line_number} {field}={value!r}"
checked += 1
assert checked > 0
+89 -20
View File
@@ -17,7 +17,11 @@ from unittest.mock import patch
import pytest
from remove_ai_watermarks._internal.c2pa import c2pa_info_from_manifest_store
from remove_ai_watermarks._internal.constants import C2PA_AI_VENDORS, C2PA_CLAIM_GENERATOR_PLATFORMS
from remove_ai_watermarks._internal.constants import (
C2PA_AI_VENDORS,
C2PA_CLAIM_GENERATOR_PLATFORMS,
C2PA_IDENTITY_AI_ORGS,
)
from remove_ai_watermarks.identify import (
ProvenanceEvidence,
ProvenanceReport,
@@ -179,7 +183,7 @@ class TestProvenanceEvidence:
assert report.is_ai_generated is True
assert report.confidence == "high"
assert report.platform == "OpenAI (ChatGPT / gpt-image / DALL-E / Sora)"
assert report.platform == "OpenAI (ChatGPT / GPT Image / DALL·E / Sora)"
assert not any("not anchored" in caveat for caveat in report.caveats)
def test_external_metadata_record_builds_equivalent_evidence(self, tmp_path: Path):
@@ -244,7 +248,7 @@ class TestProvenanceEvidence:
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 report.platform == "OpenAI (ChatGPT / GPT Image / DALL·E / Sora)"
assert [signal.name for signal in report.signals] == ["c2pa"]
def test_external_generator_bytes_are_normalized(self, tmp_path: Path):
@@ -374,13 +378,12 @@ class TestAttributePlatform:
assert platform
assert "Canva" in platform
def test_byteplus_attributes_to_bytedance(self):
def test_byteplus_keeps_its_product_name(self):
# ByteDance's intl brand signs as "Byteplus Pte. Ltd."; the registry maps
# it to the ByteDance platform (was mis-read as Adobe via an incidental
# it to the ByteDance family (was mis-read as Adobe via an incidental
# "Adobe XMP" file string before the entry existed).
platform = _attribute_platform(["BytePlus (ByteDance)"])
assert platform
assert "ByteDance" in platform
assert platform == "BytePlus (ByteDance)"
def test_empty_is_none(self):
assert _attribute_platform([]) is None
@@ -444,7 +447,7 @@ class TestIdentifyNonPng:
path = self._c2pa_jpeg(tmp_path, b"certificate_center@volcengine.com ... trainedAlgorithmicMedia")
r = identify(path, check_visible=False, check_invisible=False)
assert r.is_ai_generated is True
assert "ByteDance" in (r.platform or "")
assert r.platform == "ByteDance Volcano Engine"
def test_bytedance_chinese_legal_name_attributed(self, tmp_path: Path):
# Some Volcano Engine certs name the signer with the Chinese legal entity
@@ -454,7 +457,7 @@ class TestIdentifyNonPng:
path = self._c2pa_jpeg(tmp_path, blob)
r = identify(path, check_visible=False, check_invisible=False)
assert r.is_ai_generated is True
assert "ByteDance" in (r.platform or "")
assert r.platform == "ByteDance Volcano Engine"
@pytest.mark.parametrize(
("claim_generator", "platform"),
@@ -498,7 +501,7 @@ class TestIdentifyNonPng:
path = self._c2pa_jpeg(tmp_path, b"Bytedance Pte. Ltd. Dreamina/7.5.0 c2pa.created")
r = identify(path, check_visible=False, check_invisible=False)
assert r.is_ai_generated is True
assert "ByteDance" in (r.platform or "")
assert r.platform == "ByteDance Dreamina"
def test_elevenlabs_attributed(self, tmp_path: Path):
path = self._c2pa_jpeg(tmp_path, b"Eleven Labs Inc. ... trainedAlgorithmicMedia")
@@ -666,6 +669,57 @@ class TestIdentifyRealSamples:
assert r.is_ai_generated is True
assert r.ai_source_kind == "enhanced"
def test_standalone_ai_tag_attributes_the_content_seal(self, tmp_path: Path):
"""A standalone AI digital-source tag emits the seal as its own signal.
Muse Image outputs carry no C2PA; this tag is their only provenance, and
Muse stamps every output with the invisible Content Seal. The signal is
the strength router's Meta bet as evidence - an attribution, not a decode
(no public decoder exists), so its confidence is medium and the caveat
still points at the oracle.
"""
p = tmp_path / "muse-tag.jpg"
p.write_bytes(
b'\xff\xd8\xff\xe1<x:xmpmeta Iptc4xmpExt:DigitalSourceType="trainedAlgorithmicMedia"></x:xmpmeta>\xff\xd9'
)
r = identify(p, check_visible=False, check_invisible=False)
names = [s.name for s in r.signals]
assert "iptc" in names
assert "content_seal" in names
seal = next(s for s in r.signals if s.name == "content_seal")
assert seal.confidence == "medium"
assert "Invisible Content Seal watermark (Meta Muse attribution)" in r.watermarks
assert any("meta.ai/identification" in c for c in r.caveats)
def test_seal_platform_attribution_follows_the_signal(self, tmp_path: Path):
"""The Likely-source line follows the same bet the seal signal makes.
Apple keeps its own attribution; every other standalone-tag file gets the
hedged Muse attribution instead of "platform not specified", so the panel
that prices the Content Seal and the source line agree.
"""
muse = tmp_path / "muse.jpg"
muse.write_bytes(
b'\xff\xd8\xff\xe1<x:xmpmeta Iptc4xmpExt:DigitalSourceType="trainedAlgorithmicMedia"></x:xmpmeta>\xff\xd9'
)
apple = tmp_path / "apple.jpg"
apple.write_bytes(
b'\xff\xd8\xff\xe1<x:xmpmeta Iptc4xmpExt:DigitalSourceType="compositeWithTrainedAlgorithmicMedia" '
b'photoshop:Credit="Apple Photos Clean Up"></x:xmpmeta>\xff\xd9'
)
assert identify(muse, check_visible=False, check_invisible=False).platform == (
"Meta Muse Image (attributed by the standalone AI digital-source tag)"
)
assert identify(apple, check_visible=False, check_invisible=False).platform == "Apple Photos (Clean Up AI edit)"
def test_c2pa_backed_file_gets_no_content_seal_attribution(self):
"""C2PA issuers win first: a manifest-backed file is not Meta-routed."""
r = identify(SAMPLES_DIR / "flux-1.png", check_visible=False, check_invisible=False)
assert "content_seal" not in [s.name for s in r.signals]
def test_flux_bfl_c2pa_png(self):
# flux-1.png: real Black Forest Labs FLUX.2 Playground output (signed C2PA).
r = identify(SAMPLES_DIR / "flux-1.png", check_visible=False)
@@ -844,7 +898,7 @@ class TestIdentifyAigcPngChunk:
assert "doubao" in signal.detail
# ── HuggingFace-hosted job marker (medium confidence) ───────────────
# ── Hugging Face-hosted job marker (medium confidence) ─────────────
class TestIdentifyHuggingFaceJob:
@@ -866,7 +920,7 @@ class TestIdentifyHuggingFaceJob:
assert r.is_ai_generated is True
assert r.confidence == "medium"
assert r.platform is not None
assert "HuggingFace" in r.platform
assert "Hugging Face" in r.platform
signal = next(s for s in r.signals if s.name == "hf_job")
assert signal.confidence == "medium"
@@ -1170,7 +1224,7 @@ class TestSynthIDProvenanceEvidence:
png = self._png(tmp_path, "dreamina.png", self._png_chunk(b"caBX", b"jumbc2pa Dreamina/7.5.0 c2pa.created"))
r = identify(png, check_visible=False, check_invisible=False)
assert r.is_ai_generated is True
assert "ByteDance" in (r.platform or "")
assert r.platform == "ByteDance Dreamina"
class TestReportSerializable:
@@ -1401,7 +1455,7 @@ class TestIdentifyAIGC:
class TestVendorOf:
def test_openai_variants(self):
assert _vendor_of("OpenAI (ChatGPT / gpt-image / DALL-E / Sora)") == "OpenAI"
assert _vendor_of("OpenAI (ChatGPT / GPT Image / DALL·E / Sora)") == "OpenAI"
assert _vendor_of("DALL-E 3") == "OpenAI"
def test_google_variants(self):
@@ -1427,17 +1481,32 @@ class TestVendorOf:
# entered clash detection (a coverage hole). They now normalize to one origin.
assert _vendor_of("Microsoft (Copilot / Designer)") == "Microsoft"
assert _vendor_of("Copilot") == "Microsoft"
assert _vendor_of("ByteDance (Doubao / Jimeng / Dreamina / Volcano Engine)") == "ByteDance"
assert _vendor_of("ByteDance Volcano Engine") == "ByteDance"
assert _vendor_of("BytePlus (ByteDance)") == "ByteDance"
assert _vendor_of("Dreamina/1.2") == "ByteDance"
assert _vendor_of("Canva (Magic Media)") == "Canva"
assert _vendor_of("Black Forest Labs (FLUX)") == "Black Forest Labs"
assert _vendor_of("Eleven Labs Inc.") == "ElevenLabs"
assert _vendor_of("Ideogram") == "Ideogram"
def test_bytedance_issuers_share_one_platform(self):
expected = "ByteDance (Doubao / Jimeng / Dreamina / Volcano Engine)"
platforms = {vendor.platform for vendor in C2PA_AI_VENDORS if vendor.needle == "ByteDance"}
assert platforms == {expected}
assert ("dreamina", expected) in C2PA_CLAIM_GENERATOR_PLATFORMS
def test_ideogram_issuer_attributed(self):
# Corpus evidence 2026-08-08: four uploads signed "Ideogram, Inc" read as
# unknown-signer C2PA with no platform. The issuer token is the org prefix.
platforms = {v.issuer: v.platform for v in C2PA_AI_VENDORS}
assert platforms[b"Ideogram"] == "Ideogram"
assert "Ideogram" in C2PA_IDENTITY_AI_ORGS
assert _issuers_in(b"...CN=Ideogram, Inc...trainedAlgorithmicMedia") == ["Ideogram"]
def test_bytedance_issuers_keep_the_source_product(self):
platforms = {
vendor.issuer: vendor.platform
for vendor in C2PA_AI_VENDORS
if vendor.org.startswith("ByteDance") or vendor.org.startswith("BytePlus")
}
assert platforms[b"volcengine"] == "ByteDance Volcano Engine"
assert platforms[b"Byteplus"] == "BytePlus (ByteDance)"
assert platforms[b"Dreamina"] == "ByteDance Dreamina"
assert ("dreamina", "ByteDance Dreamina") in C2PA_CLAIM_GENERATOR_PLATFORMS
class TestIntegrityClashesHelper:
+2 -5
View File
@@ -60,6 +60,7 @@ class TestVerifiedTextMode:
manifest.write_text("{}", encoding="utf-8")
cases = (
("sdxl-zimage", {}, "qwen-zimage"),
("qwen-zimage", {"tile": True}, "not calibrated with --tile"),
("qwen-zimage", {"max_resolution": 1024}, "max-resolution 0"),
("qwen-zimage", {"humanize": 1.0}, "humanize=0"),
("qwen-zimage", {"adaptive_polish": True}, "polish disabled"),
@@ -128,10 +129,6 @@ class TestVerifiedTextMode:
assert seen["fidelity_anchor"] is True
engine.remove_watermark(source, output, text_manifest=manifest, tile=True)
assert seen["tile"] is True
class TestNativeOutputSize:
"""Model-side latent-grid rounding must not change the public output size."""
@@ -248,7 +245,7 @@ class TestEngineResolvesThePolishPerProfile:
This is the change that stopped a library caller and a CLI caller on one profile
from producing different pixels, and it had no test: rebinding
``resolve_adaptive_polish`` to ``bool(value)`` -- exactly the pre-commit behaviour --
``resolve_adaptive_polish`` to ``bool(value)`` -- exactly the pre-commit behavior --
left the whole suite green.
"""
+1 -1
View File
@@ -72,7 +72,7 @@ class TestConfig:
assert KlingEngine().config.provenance_ncc_factor == 1.0
def test_gate_above_clean_arm_max(self):
# Clean arm scored p99 0.304 / max 0.320 on 286 hand-labelled frames; the
# Clean arm scored p99 0.304 / max 0.320 on 286 hand-labeled frames; the
# gate must sit above that with margin.
assert KlingEngine().config.detect_ncc_threshold > 0.32
+4 -4
View File
@@ -1,4 +1,4 @@
"""Tests for the LibLibAI ("LibLibAI" wordmark) visible-watermark engine.
"""Tests for the LiblibAI ("LiblibAI" wordmark) visible-watermark engine.
Every tuned constant in ``liblib_engine`` was measured on the 15-frame vendor
cohort (2026-07-22); these tests pin the load-bearing ones: the bottom-CENTER
@@ -24,7 +24,7 @@ _MARK_FRAC = 0.10 # measured wordmark width, fraction of the frame WIDTH
def _compose(w: int, h: int, bg: float = 100.0):
"""Composite a triangle logo + the LibLibAI wordmark, bottom-center."""
"""Composite a triangle logo + the LiblibAI wordmark, bottom-center."""
img = np.full((h, w, 3), bg, np.float32)
at = _alpha_template()
gw = int(_MARK_FRAC * w)
@@ -112,8 +112,8 @@ class TestDetectAndMask:
assert eng.footprint_mask(img) is None
def test_confident_liblib_detection_suppresses_the_jimeng_pill(self):
# A LibLibAI image is TC260 too but is not Jimeng-basic: like Doubao/Qwen/
# Kling/RunningHub/Baidu, a confident LibLibAI detection must veto the pill.
# A LiblibAI image is TC260 too but is not Jimeng-basic: like Doubao/Qwen/
# Kling/RunningHub/Baidu, a confident LiblibAI detection must veto the pill.
# It was the one mark the hand-written veto list in ``_keep_pill`` missed.
from remove_ai_watermarks.watermark_registry import _keep_pill
+6 -3
View File
@@ -1562,7 +1562,7 @@ class TestAIGCLabel:
class TestHuggingFaceJob:
"""HuggingFace-hosted job marker (``hf-job-id`` PNG text chunk)."""
"""Hugging Face-hosted job marker (``hf-job-id`` PNG text chunk)."""
def _hf_png(self, tmp_path: Path, job_id: str = "ec8380a6-2091-423a-b835-209420f99ee1") -> Path:
p = tmp_path / "hfjob.png"
@@ -1622,10 +1622,13 @@ class TestSoftBinding:
from remove_ai_watermarks._internal.c2pa import soft_binding_vendors_in
assert soft_binding_vendors_in(b"...alg...com.adobe.trustmark.P...") == ["Adobe TrustMark"]
assert soft_binding_vendors_in(b"com.digimarc.validate.1") == ["Digimarc"]
assert soft_binding_vendors_in(b"com.digimarc.validate.1") == ["Digimarc Validate"]
assert soft_binding_vendors_in(b"ai.steg.api blah") == ["Steg.AI"]
# Registry-verified vendors added in v0.6.x.
assert soft_binding_vendors_in(b"ai.trufo.gen1.image") == ["Trufo"]
assert soft_binding_vendors_in(b"ai.trufo.pawprint.watermark") == ["Trufo PawPrint"]
assert soft_binding_vendors_in(b"com.aiwatermark.pixelseal.1") == ["AIWatermark PixelSeal"]
assert soft_binding_vendors_in(b"com.aiwatermark.videoseal.1") == ["AIWatermark VideoSeal"]
assert soft_binding_vendors_in(b"com.aiwatermark.audioseal.1") == ["AIWatermark AudioSeal"]
assert soft_binding_vendors_in(b"io.iscc.v0") == ["ISCC (content code)"]
def test_vendors_in_empty_when_absent(self):
+5 -5
View File
@@ -266,8 +266,8 @@ class TestC2PA:
def test_content_fingerprint_does_not_trigger_invisible_removal(self):
info = {
"soft_binding": "Adobe (content fingerprint)",
"soft_binding_vendors": ["Adobe (content fingerprint)"],
"soft_binding": "Adobe Image Comparator Network",
"soft_binding_vendors": ["Adobe Image Comparator Network"],
}
assert c2pa_info_has_removal_hint(info) is False
@@ -1007,7 +1007,7 @@ class TestTc260ContainerRouting:
def _riff_chunk(chunk_id: bytes, payload: bytes) -> bytes:
return chunk_id + len(payload).to_bytes(4, "little") + payload + (b"\x00" if len(payload) & 1 else b"")
def _labelled_avi(self) -> bytes:
def _labeled_avi(self) -> bytes:
info = self._riff_chunk(b"AIGC", _TC260_AIGC_VALUE)
body = b"AVI " + self._riff_chunk(b"LIST", b"INFO" + info)
return b"RIFF" + len(body).to_bytes(4, "little") + body
@@ -1016,7 +1016,7 @@ class TestTc260ContainerRouting:
from remove_ai_watermarks.metadata import aigc_label
target = tmp_path / "clip.bin" # correct AVI bytes, wrong suffix
target.write_bytes(self._labelled_avi())
target.write_bytes(self._labeled_avi())
label = aigc_label(target)
assert label is not None
assert label["Label"] == "1"
@@ -1025,7 +1025,7 @@ class TestTc260ContainerRouting:
from remove_ai_watermarks.metadata import aigc_label
target = tmp_path / "clip.avi"
target.write_bytes(self._labelled_avi())
target.write_bytes(self._labeled_avi())
assert aigc_label(target) is not None
def test_webp_yields_nothing_from_the_riff_reader(self, tmp_path: Path):
+166
View File
@@ -0,0 +1,166 @@
"""Tests for the measured Microsoft top-right AI-badge engine.
The covered variant is a white top-right pill with dark internal shapes. The
2026-08-27 calibration kept visually confirmed carriers, provenance-only files,
and no-signal controls separate. These tests pin the load-bearing constants --
especially the long-side scale basis and the internal holes as the discriminator.
"""
from __future__ import annotations
import cv2
import numpy as np
import pytest
from remove_ai_watermarks import watermark_registry as registry
from remove_ai_watermarks.microsoft_engine import (
_ALPHA_HEIGHT_FRAC,
_ALPHA_WIDTH_FRAC,
MicrosoftEngine,
_alpha_template,
)
_INSET = 0.010 # measured pill inset from the top/right edges (long-side fraction)
def _pill_geometry(w: int, h: int) -> tuple[int, int, int, int]:
long_side = max(w, h)
pw = int(_ALPHA_WIDTH_FRAC * long_side)
ph = max(4, int(_ALPHA_HEIGHT_FRAC * long_side))
pad = int(_INSET * long_side)
return w - pad - pw, pad, pw, ph
def _compose(w: int, h: int, bg: float = 110.0):
"""Composite the synthetic pill at its measured size onto a flat background."""
img = np.full((h, w, 3), bg, np.uint8)
at = _alpha_template()
x0, y0, pw, ph = _pill_geometry(w, h)
pill = cv2.resize(at, (pw, ph))
region = img[y0 : y0 + ph, x0 : x0 + pw]
bright = pill > 0.6
region[bright] = 245
# Internal holes are dark ink inside the pill, not background.
region[~bright] = 45
return img, (x0, y0, pw, ph)
def _plain_pill(w: int, h: int, text: str | None = None) -> np.ndarray:
"""Return a white rounded pill without the expected holes, or with foreign text."""
img = np.full((h, w, 3), 110.0, np.uint8)
x0, y0, pw, ph = _pill_geometry(w, h)
cv2.rectangle(img, (x0, y0), (x0 + pw, y0 + ph), (245, 245, 245), -1)
cv2.circle(img, (x0 + ph // 2, y0 + ph // 2), ph // 3, (110, 110, 110), -1)
if text:
cv2.putText(img, text, (x0 + ph, y0 + ph // 2 + ph // 6), cv2.FONT_HERSHEY_SIMPLEX, ph / 90.0, (45, 45, 45), 1)
return img
class TestLocate:
def test_box_anchored_top_right(self):
eng = MicrosoftEngine()
loc = eng.locate(np.zeros((1024, 1024, 3), np.uint8))
assert loc.x + loc.w == pytest.approx(1024 - int(0.004 * 1024), abs=2)
assert loc.y == pytest.approx(int(0.003 * 1024), abs=2)
def test_box_scales_with_long_side_not_width(self):
# Measured: the pill tracks the render dimension, so a 1024x1536 portrait
# carries the SAME pill size as 1536x1024. A width basis undersized the
# template by the aspect ratio and dropped every portrait carrier.
eng = MicrosoftEngine()
portrait = eng.locate(np.zeros((1536, 1024, 3), np.uint8))
landscape = eng.locate(np.zeros((1024, 1536, 3), np.uint8))
assert portrait.w == landscape.w
small = eng.locate(np.zeros((720, 480, 3), np.uint8))
assert small.w < portrait.w
class TestConfig:
def test_provenance_relaxation_is_the_measured_07(self):
# The relaxed band was measured on the OCR-censused MS cohort: 257
# badge-less files max 0.251, so the 0.266 relaxed gate admits the three
# faint badges in [0.251, 0.38) with zero measured false fills. Do not
# move the factor without re-censusing the badge-less cohort.
assert MicrosoftEngine().config.provenance_ncc_factor == 0.7
def test_long_scale_basis(self):
assert MicrosoftEngine().config.scale_basis == "long"
def test_threshold_and_geometry_pins(self):
from remove_ai_watermarks.microsoft_engine import (
DETECT_NCC_THRESHOLD,
MARGIN_RIGHT_FRAC,
WM_WIDTH_FRAC,
)
assert pytest.approx(0.38) == DETECT_NCC_THRESHOLD # controls max 0.293; carriers max 0.579
assert pytest.approx(0.170) == WM_WIDTH_FRAC
assert pytest.approx(0.004) == MARGIN_RIGHT_FRAC
def test_registry_row(self):
mark = registry.get_mark("microsoft")
assert mark.location == "top-right"
assert mark.label == "Microsoft top-right AI badge"
assert mark.in_auto
assert mark.provenance_platform_tokens == ("microsoft",)
assert mark.label_regime is None # not a China-TC260 mark
class TestDetect:
@pytest.mark.parametrize(("w", "h"), [(1024, 1024), (1536, 1024), (1024, 1536), (720, 480), (1206, 1194)])
def test_composites_detected_across_sizes(self, w, h):
eng = MicrosoftEngine()
img, _box = _compose(w, h)
det = eng.detect(img)
assert det.detected, f"{w}x{h}: conf={det.confidence:.3f}"
assert det.confidence >= 0.38
def test_portrait_composite_region_covers_pill(self):
eng = MicrosoftEngine()
img, (x, y, pw, ph) = _compose(1024, 1536)
det = eng.detect(img)
assert det.detected
rx, ry, rw, _rh = det.region
assert abs((rx + rw) - (x + pw)) < 0.08 * pw
assert abs(ry - y) < 0.4 * ph
def test_clean_gradient_not_detected(self):
eng = MicrosoftEngine()
ramp = np.tile(np.linspace(0, 255, 1024, dtype=np.uint8), (1024, 1))
img = cv2.cvtColor(ramp, cv2.COLOR_GRAY2BGR)
assert not eng.detect(img).detected
def test_plain_white_pill_not_detected(self):
# The expected internal holes are the discriminator: any other bright rounded
# element in the corner must not attribute Microsoft.
eng = MicrosoftEngine()
assert not eng.detect(_plain_pill(1024, 1024)).detected
def test_foreign_text_pill_not_detected(self):
eng = MicrosoftEngine()
assert not eng.detect(_plain_pill(1024, 1024, text="Sample Text")).detected
def test_busy_content_corner_not_detected(self):
# A photo-like textured corner must stay under the gate.
eng = MicrosoftEngine()
rng = np.random.default_rng(7)
img = rng.integers(0, 255, (1024, 1024, 3), dtype=np.uint8)
img = cv2.GaussianBlur(img, (0, 0), 3)
assert not eng.detect(img).detected
class TestMask:
def test_footprint_covers_the_pill(self):
eng = MicrosoftEngine()
img, (x, y, pw, ph) = _compose(1536, 1024)
det = eng.detect(img)
assert det.detected
mask = eng.footprint_mask(img, detection=det)
assert mask.shape[:2] == img.shape[:2]
ys, xs = np.where(mask > 0)
assert xs.min() >= x - 0.15 * pw
assert xs.max() <= x + pw + 0.15 * pw
assert ys.min() >= y - 0.3 * ph
assert ys.max() <= y + ph + 0.3 * ph
# the fill must cover the pill area, not just the text glyphs
assert float(mask[y : y + ph, x : x + pw].mean()) > 0.4
+52 -2
View File
@@ -6,6 +6,7 @@ answer and a clean refusal rather than a fallback ladder.
from __future__ import annotations
import struct
from pathlib import Path
from unittest.mock import MagicMock, patch
@@ -15,6 +16,7 @@ from remove_ai_watermarks._internal.utils import get_image_format, is_supported_
from remove_ai_watermarks._internal.watermark_profiles import (
PROFILE_CHOICES,
QWEN_ZIMAGE_GOOGLE_STRENGTH,
QWEN_ZIMAGE_META_STRENGTH,
QWEN_ZIMAGE_OPENAI_STRENGTH,
REMOVAL_MODULES,
SDXL_ZIMAGE_GEMINI_STRENGTH,
@@ -198,6 +200,30 @@ class TestNoReembeddedWatermark:
assert "add_watermarker" not in calls["controlnet"]
# Minimal WebP stub whose XMP chunk carries the IPTC trainedAlgorithmicMedia
# tag exactly as Muse outputs place it (built inline so the test has no binary
# fixture dependency).
_XMP_PAYLOAD = (
b'<x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax#">'
b'<rdf:Description rdf:about="" xmlns:iptcExt="http://iptc.org/std/Iptc4xmpExt/2008-02-29/" '
b'iptcExt:DigitalSourceType="http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia"/>'
b"</rdf:RDF></x:xmpmeta>"
)
def _webp_stub(xmp: bytes | None) -> bytes:
def chunk(cid: bytes, data: bytes) -> bytes:
return cid + struct.pack("<I", len(data)) + data + (b"\x00" if len(data) % 2 else b"")
body = b"WEBP" + chunk(b"VP8L", b"\x00" * 20)
if xmp is not None:
body += chunk(b"XMP ", xmp)
return b"RIFF" + struct.pack("<I", len(body)) + body
_MUSE_WEBP_WITH_IPTC_TAG = _webp_stub(_XMP_PAYLOAD)
class TestResolveStrength:
"""resolve_strength owns the qwen-zimage and sdxl-zimage policies."""
@@ -222,12 +248,36 @@ class TestResolveStrength:
with pytest.raises(ValueError, match="size is required"):
resolve_strength(None, "openai", "qwen-zimage")
@pytest.mark.parametrize(("vendor", "expected"), [("microsoft", 0.15), ("openai", 0.07675)])
@pytest.mark.parametrize(("vendor", "expected"), [("microsoft", 0.15), ("openai", 0.07675), ("meta", 0.1)])
def test_qwen_zimage_measured_vendors_use_flat_cross_source_margins(self, vendor, expected):
"""Measured providers must not fall below their operating points on small files."""
assert resolve_strength(None, vendor, "qwen-zimage", size=(600, 500)) == pytest.approx(expected)
assert resolve_strength(None, vendor, "qwen-zimage", size=(4000, 3000)) == pytest.approx(expected)
def test_meta_floor_is_size_independent_and_sdxl_falls_to_unknown(self):
"""The Meta floor bypasses the area curve at every size (Content Seal removal
was bracketed on 2.56 MP generations and derived as a flat cross-source
margin, like the other measured cohorts), while sdxl-zimage has no measured
Meta rung and must fall to the conservative unknown value, not invent one."""
for size in ((600, 500), (1600, 1600), (1920, 1280), (4000, 3000)):
assert resolve_strength(None, "meta", "qwen-zimage", size=size) == pytest.approx(QWEN_ZIMAGE_META_STRENGTH)
assert resolve_strength(None, "meta", "sdxl-zimage") == SDXL_ZIMAGE_UNKNOWN_STRENGTH
def test_vendor_for_strength_routes_standalone_iptc_to_meta(self, tmp_path):
"""Auto mode: a file whose only provenance is the AI IPTC tag routes to the
meta cohort (Muse carries no C2PA; the tag is its fallback companion), while
a file without the tag stays on the resolution curve and a C2PA issuer still
wins over the tag."""
from remove_ai_watermarks._internal.watermark_profiles import vendor_for_strength
tagged = tmp_path / "tagged.webp"
tagged.write_bytes(_MUSE_WEBP_WITH_IPTC_TAG)
assert vendor_for_strength(tagged) == "meta"
stripped = tmp_path / "stripped.webp"
stripped.write_bytes(b"RIFF\x24\x00\x00\x00WEBPVP8 \x18\x00\x00\x00" + b"\x00" * 16)
assert vendor_for_strength(stripped) is None
def test_sdxl_zimage_uses_its_flat_vendor_ladder(self):
assert SDXL_ZIMAGE_OPENAI_STRENGTH == 0.15
@@ -235,7 +285,7 @@ class TestResolveStrength:
assert SDXL_ZIMAGE_UNKNOWN_STRENGTH == SDXL_ZIMAGE_GEMINI_STRENGTH
assert resolve_strength(None, "openai", "sdxl-zimage") == SDXL_ZIMAGE_OPENAI_STRENGTH
assert resolve_strength(None, "google", "sdxl-zimage") == SDXL_ZIMAGE_GEMINI_STRENGTH
# An unrecognised issuer takes the stricter Gemini value, not the OpenAI one.
# An unrecognized issuer takes the stricter Gemini value, not the OpenAI one.
assert resolve_strength(None, "adobe", "sdxl-zimage") == SDXL_ZIMAGE_UNKNOWN_STRENGTH
assert resolve_strength(None, None, "sdxl-zimage") == SDXL_ZIMAGE_UNKNOWN_STRENGTH
+34 -25
View File
@@ -659,8 +659,7 @@ def test_no_face_path_still_runs_verified_text_restoration(monkeypatch):
restore.assert_called_with(source, anchor, donor, manifest.lines)
def test_tiled_verified_text_runs_vae_donor_per_tile(monkeypatch):
from remove_ai_watermarks._internal import qwen_zimage_pipeline, text_restoration
def test_tiled_verified_text_is_rejected_before_model_work():
from remove_ai_watermarks._internal.qwen_zimage_pipeline import QwenZImagePipeline
from remove_ai_watermarks._internal.text_restoration import VerifiedTextLine, VerifiedTextManifest
@@ -668,12 +667,6 @@ def test_tiled_verified_text_runs_vae_donor_per_tile(monkeypatch):
pipeline.device = "cuda"
pipeline.progress_callback = None
source = Image.new("RGB", (96, 80), (10, 20, 30))
restored = Image.new("RGB", (96, 80), (130, 140, 150))
pipeline._qwen_vae_roundtrip = MagicMock(side_effect=lambda tile: tile)
pipeline._run_global = MagicMock(side_effect=lambda tile, _strength, _seed: tile)
monkeypatch.setattr(qwen_zimage_pipeline, "detect_faces", lambda _image: [])
restore = MagicMock(return_value=restored)
monkeypatch.setattr(text_restoration, "restore_verified_text", restore)
manifest = VerifiedTextManifest(
"0" * 64,
96,
@@ -681,23 +674,16 @@ def test_tiled_verified_text_runs_vae_donor_per_tile(monkeypatch):
(VerifiedTextLine((4, 4, 20, 16), "Exact", "alphabetic"),),
)
result = pipeline.run(
source,
strength=0.1,
seed=0,
tile=True,
tile_size=64,
tile_overlap=16,
text_manifest=manifest,
)
assert result is restored
assert pipeline._qwen_vae_roundtrip.call_count > 1
assert pipeline._run_global.call_count > 1
restore.assert_called_once()
assert restore.call_args.args[0].size == (96, 80)
assert restore.call_args.args[1].size == (96, 80)
assert restore.call_args.args[2].size == (96, 80)
with pytest.raises(ValueError, match="not calibrated with tiled diffusion"):
pipeline.run(
source,
strength=0.1,
seed=0,
tile=True,
tile_size=64,
tile_overlap=16,
text_manifest=manifest,
)
def test_watermark_remover_dispatches_to_full_pipeline(tmp_path, monkeypatch):
@@ -757,6 +743,29 @@ def test_watermark_remover_dispatches_qwen_tiling_to_full_pipeline(tmp_path, mon
assert output.exists()
def test_watermark_remover_rejects_tiled_verified_text_before_pipeline(tmp_path, monkeypatch):
from remove_ai_watermarks._internal.text_restoration import VerifiedTextLine, VerifiedTextManifest
from remove_ai_watermarks._internal.watermark_remover import WatermarkRemover
_mock_watermark_runtime_deps(monkeypatch)
source = tmp_path / "source.png"
Image.new("RGB", (96, 80), (20, 30, 40)).save(source)
manifest = VerifiedTextManifest(
"0" * 64,
96,
80,
(VerifiedTextLine((4, 4, 20, 16), "Exact", "alphabetic"),),
)
remover = WatermarkRemover(device="cuda", pipeline="qwen-zimage")
runtime = MagicMock()
monkeypatch.setattr(remover, "_load_qwen_zimage_pipeline", lambda: runtime)
with pytest.raises(ValueError, match="not calibrated with tiled diffusion"):
remover.remove_watermark(source, text_manifest=manifest, tile=True)
runtime.run.assert_not_called()
def test_qwen_tiling_runs_global_tiles_then_one_full_frame_face_stage(monkeypatch):
from remove_ai_watermarks._internal.qwen_zimage_pipeline import (
QwenZImagePipeline,
+59
View File
@@ -0,0 +1,59 @@
"""Contracts for the registered visible-mark calibration harness."""
from __future__ import annotations
import importlib.util
import json
from pathlib import Path
import pytest
SCRIPT = Path(__file__).parents[1] / "scripts" / "registered_mark_calibrate.py"
SPEC = importlib.util.spec_from_file_location("registered_mark_calibrate", SCRIPT)
assert SPEC
assert SPEC.loader
module = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(module)
def test_manifest_preserves_evidence_arms(tmp_path: Path) -> None:
manifest = tmp_path / "manifest.jsonl"
manifest.write_text(
"\n".join(
[
json.dumps({"path": "carrier.png", "arm": "positive"}),
json.dumps({"path": "provider.png", "arm": "metadata"}),
json.dumps({"path": "comparison.png", "arm": "control"}),
]
),
encoding="utf-8",
)
rows = module.load_manifest(manifest)
assert [row["arm"] for row in rows] == ["positive", "metadata", "control"]
assert all(Path(row["path"]).is_absolute() for row in rows)
def test_manifest_rejects_one_file_in_multiple_arms(tmp_path: Path) -> None:
manifest = tmp_path / "manifest.jsonl"
manifest.write_text(
"\n".join(
[
json.dumps({"path": "same.png", "arm": "positive"}),
json.dumps({"path": "same.png", "arm": "control"}),
]
),
encoding="utf-8",
)
with pytest.raises(ValueError, match="duplicate image path"):
module.load_manifest(manifest)
def test_summary_never_relabels_a_control() -> None:
summary = module.summarize([0.1, 0.4], unreadable=1, fires=1)
assert summary["n"] == 2
assert summary["unreadable"] == 1
assert summary["fires"] == 1
+1 -1
View File
@@ -71,7 +71,7 @@ class TestConfig:
assert RunningHubEngine().config.provenance_ncc_factor == 1.0
def test_gate_above_clean_arm_max(self):
# Clean arm scored p99 0.273 / max 0.295 on 286 hand-labelled frames.
# Clean arm scored p99 0.273 / max 0.295 on 286 hand-labeled frames.
assert RunningHubEngine().config.detect_ncc_threshold > 0.295
def test_registry_row(self):
+50
View File
@@ -0,0 +1,50 @@
"""Basic command-line contracts for standalone maintainer scripts."""
from __future__ import annotations
import subprocess
import sys
from os import environ
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
@pytest.mark.parametrize(
"script",
[
"visible_groundtruth.py",
"visible_recall_sample.py",
"visible_sheets.py",
"registered_mark_calibrate.py",
],
)
def test_script_help_exits_cleanly(script: str) -> None:
result = subprocess.run( # noqa: S603 -- fixed interpreter and repository-owned script path
[sys.executable, str(ROOT / "scripts" / script), "--help"],
cwd=ROOT,
capture_output=True,
text=True,
check=False,
)
assert result.returncode == 0, result.stderr
assert "usage:" in result.stdout
def test_visible_groundtruth_help_is_cp1252_safe() -> None:
env = environ.copy()
env["PYTHONIOENCODING"] = "cp1252"
result = subprocess.run( # noqa: S603 -- fixed interpreter and repository-owned script path
[sys.executable, str(ROOT / "scripts" / "visible_groundtruth.py"), "--help"],
cwd=ROOT,
env=env,
capture_output=True,
text=True,
check=False,
)
assert result.returncode == 0, result.stderr
assert "usage:" in result.stdout
+2 -2
View File
@@ -1,6 +1,6 @@
"""Policy-level tests for the shared text-mark engine config.
These assert calibrated TUNING, not algorithm behaviour --
These assert calibrated TUNING, not algorithm behavior --
they exist so a future edit cannot silently revert a calibrated constant back to a
value that was measured to be wrong. The measurements themselves live in
`docs/module-internals.md` and in the comment at
@@ -74,7 +74,7 @@ class TestScaleBasis:
assert doubao_engine._CONFIG.scale_basis == "short"
def test_jimeng_scales_with_width(self):
"""Measured, not an oversight: the short-side basis took jimeng's labelled
"""Measured, not an oversight: the short-side basis took jimeng's labeled
landscape positives from 13/13 to 0/13."""
from remove_ai_watermarks import jimeng_engine
+1 -1
View File
@@ -118,7 +118,7 @@ class TestFaintMaskStaysTight:
loc = eng.locate(img)
roi = loc.w * loc.h
# The mark's own glyph box is ~40% of the corner ROI and the mask pads it, so a
# correct mask lands near 60%. The pre-fix behaviour measured 120.9% (the whole
# correct mask lands near 60%. The pre-fix behavior measured 120.9% (the whole
# ROI plus padding), which this bound excludes.
assert area < 0.85 * roi, f"mask covers {100 * area / roi:.0f}% of the corner box"
+1 -1
View File
@@ -100,7 +100,7 @@ class TestRunTiled:
def test_identity_generate_reconstructs_image(self):
# A blend of identical (unchanged) tiles must reproduce the input exactly,
# regardless of overlap -- the feather weights are a partition-of-unity once
# normalised. This is the seam-free guarantee.
# normalized. This is the seam-free guarantee.
rng = np.random.default_rng(0)
arr = rng.integers(0, 256, size=(1500, 1300, 3), dtype=np.uint8)
image = Image.fromarray(arr)
+1 -1
View File
@@ -3,7 +3,7 @@
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.
absent/error behavior: detect must return None, never raise.
"""
from __future__ import annotations
+7 -1
View File
@@ -860,6 +860,12 @@ class TestVideoMetadataApi:
class TestVideoProvenanceApi:
def test_c2pa_platform_keeps_the_bytedance_surface_name(self):
from remove_ai_watermarks.video import _platform_from_video_metadata
assert _platform_from_video_metadata({"issuer": "BytePlus (ByteDance)"}) == "BytePlus (ByteDance)"
assert _platform_from_video_metadata({"issuer": "ByteDance (Volcano Engine)"}) == "ByteDance Volcano Engine"
def test_identifies_metadata_without_pixel_scan(self, tmp_path: Path):
from remove_ai_watermarks.video import identify_video
@@ -870,7 +876,7 @@ class TestVideoProvenanceApi:
assert report.source == source
assert report.is_ai_generated is True
assert report.confidence == "high"
assert report.platform == "OpenAI (ChatGPT / gpt-image / DALL-E / Sora)"
assert report.platform == "OpenAI (ChatGPT / GPT Image / DALL·E / Sora)"
assert report.visible_mark is None
assert report.total_frames is None
assert report.has_ai_metadata is True
+1 -1
View File
@@ -142,7 +142,7 @@ def test_pairing_follows_the_engine_sampling_rule_not_just_the_frame_count(
in the suite that constrains the sampler's phase at all.
The clips encode losslessly, so the aligned pairing is exact and clears any
floor; the misaligned one compares frames a full colour step apart and lands
floor; the misaligned one compares frames a full color step apart and lands
near 9 dB, which is what leaves the 25 dB ceiling a wide moat rather than a
tuned threshold.
"""
+59 -3
View File
@@ -3,12 +3,14 @@
from __future__ import annotations
import csv
import math
import sys
import threading
from pathlib import Path
from types import SimpleNamespace
from typing import TYPE_CHECKING, cast
from typing import TYPE_CHECKING, Any, cast
import numpy as np
import pytest
from remove_ai_watermarks import optional_deps, video_encoding, video_invisible
@@ -92,6 +94,50 @@ def test_regeneration_rejects_noise_outside_unit_interval(tmp_path: Path) -> Non
)
@pytest.mark.parametrize(
("kwargs", "message"),
[
({"fps": 0.0}, "fps must be at least 1"),
({"batch_size": 0}, "batch_size must be at least 1"),
({"duration": 0.0}, "duration must be positive"),
({"device": "tpu"}, "device must be auto, cuda, mps, or cpu"),
],
)
def test_regeneration_rejects_invalid_controls_before_probing(
tmp_path: Path,
kwargs: dict[str, Any],
message: str,
) -> None:
with pytest.raises(ValueError, match=message):
video_invisible.regenerate_video_candidate(
tmp_path / "source.mp4",
tmp_path / "candidate.mp4",
**kwargs,
)
def test_fit_size_and_paired_psnr_validate_geometry() -> None:
assert video_invisible._fit_size(1920, 1080, 512) == (512, 288)
with pytest.raises(ValueError, match="positive"):
video_invisible._fit_size(0, 1080, 512)
with pytest.raises(ValueError, match="at least"):
video_invisible._fit_size(1920, 1080, 4)
frame = np.zeros((2, 2, 3), dtype=np.uint8)
assert video_invisible.paired_psnr(frame, frame) == math.inf
with pytest.raises(ValueError, match="matching shapes"):
video_invisible.paired_psnr(frame, frame[:1])
def test_load_runtime_rejects_invalid_device_and_missing_extra(monkeypatch: pytest.MonkeyPatch) -> None:
with pytest.raises(ValueError, match="device must be"):
video_invisible.load_video_vae_runtime(device="tpu")
monkeypatch.setattr(video_invisible, "is_available", lambda: False)
with pytest.raises(RuntimeError, match="diffusion extra"):
video_invisible.load_video_vae_runtime()
def test_encoder_and_mux_commands_separate_streaming_frames_from_source_audio(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
@@ -298,8 +344,9 @@ def test_shipped_defaults_match_a_certified_manifest_row() -> None:
rate now fails here until a ``not_detected`` row exists for that exact triple.
The tuple stops at three fields because the model is a fourth thing the oracle
was shown and neither tracked row records it. That omission is data-driven: add
``vae`` here in the same commit as the first row that records one.
was shown and neither historical row recorded it. The manifest says
``unrecorded`` rather than leaving an ambiguous empty field. Add ``vae`` here in
the same commit as the first row that records one.
"""
with ORACLE_MANIFEST.open(newline="", encoding="utf-8") as stream:
certified = {
@@ -320,6 +367,15 @@ def test_shipped_defaults_match_a_certified_manifest_row() -> None:
)
def test_oracle_manifest_marks_missing_vae_identity_explicitly() -> None:
with ORACLE_MANIFEST.open(newline="", encoding="utf-8") as stream:
rows = list(csv.DictReader(stream))
assert rows
assert all(row["vae"] for row in rows)
assert all(row["vae"] == "unrecorded" or "/" in row["vae"] for row in rows)
def test_stream_batches_consumes_only_one_batch_ahead() -> None:
consumed: list[int] = []
+57
View File
@@ -0,0 +1,57 @@
"""The visible-mark example gallery is complete and self-consistent.
Two failures this suite exists to catch:
* a mark registered without a committed example (the gallery lags the registry);
* an engine that no longer detects its own canonical example (the gallery is
generated from the engines' measured geometry, so this is a regression tripwire).
The examples are SYNTHETIC (``scripts/render_visible_examples.py`` composites the
committed silhouettes onto a generated base). User uploads never enter the repo.
"""
from __future__ import annotations
from pathlib import Path
import pytest
from remove_ai_watermarks import watermark_registry as wr
from remove_ai_watermarks.image_io import imread
from remove_ai_watermarks.video import VIDEO_VISIBLE_MARKS, identify_video
_ROOT = Path(__file__).resolve().parents[1]
_GALLERY = _ROOT / "data" / "fixtures" / "visible"
_IMAGE_KEYS = [m.key for m in wr.known_marks()]
class TestGallery:
def test_every_registered_mark_has_an_example(self) -> None:
missing = [key for key in _IMAGE_KEYS if not (_GALLERY / key / "example.png").is_file()]
assert missing == [], f"registered without an example: {missing}; run scripts/render_visible_examples.py"
@pytest.mark.parametrize("key", _IMAGE_KEYS)
def test_engine_detects_its_own_example(self, key: str) -> None:
img = imread(str(_GALLERY / key / "example.png"))
assert img is not None, key
det = wr.get_mark(key).detect(img, provenance=False)
assert det.detected, f"{key}: confidence {det.confidence:.3f} on its own example"
def test_gallery_has_no_stray_directories(self) -> None:
known = set(_IMAGE_KEYS) | set(VIDEO_VISIBLE_MARKS) | {"README.md"}
extra = sorted(p.name for p in _GALLERY.iterdir() if p.name not in known)
assert extra == [], f"gallery holds unregistered examples: {extra}; remove or register them"
class TestVideoGallery:
def test_every_registered_video_mark_has_an_example(self) -> None:
missing = [key for key in VIDEO_VISIBLE_MARKS if not (_GALLERY / key / "example.mp4").is_file()]
assert missing == [], f"video mark without an example: {missing}; run scripts/render_visible_examples.py"
def test_selection_accepts_each_example(self) -> None:
# The shipped temporal selection (not just the per-frame detector) must
# accept the clip: table order resolves cross-template ties, so the example
# must carry the discriminative variant of its mark.
for key in VIDEO_VISIBLE_MARKS:
rep = identify_video(_GALLERY / key / "example.mp4", check_visible=True)
assert rep.visible_mark == key, f"{key}: selection returned {rep.visible_mark!r}"
+13 -8
View File
@@ -25,6 +25,7 @@ class TestCatalog:
"runninghub",
"baidu",
"liblib",
"microsoft",
"jimeng_pill",
]
@@ -113,6 +114,7 @@ class TestScan:
"runninghub",
"baidu",
"liblib",
"microsoft",
"jimeng_pill",
}
@@ -219,7 +221,7 @@ class TestProvenanceGate:
# 0.38 is inside the measured 13%-precision band and above the engine's own
# 0.35 floor, so the engine reports `detected` and only the registry gate can
# reject it. Hardcoded on purpose: if the gate is ever lowered back under this
# value, this test must fail on the BEHAVIOUR below, not on its own arithmetic.
# value, this test must fail on the behavior below, not on its own arithmetic.
self._stub(monkeypatch, 0.38)
img = np.zeros((256, 256, 3), np.uint8)
assert reg.get_mark("gemini").detect(img).detected is False
@@ -529,7 +531,7 @@ class TestMarkKnowledgeIsOnTheRow:
Product family, label regime, the platform sentence and the metadata signals that
confirm the vendor all used to live in separate hand-maintained tables across
``watermark_registry``, ``identify`` and ``api``. That is how LibLibAI ended up
``watermark_registry``, ``identify`` and ``api``. That is how LiblibAI ended up
registered but absent from the pill veto.
"""
@@ -555,15 +557,18 @@ class TestMarkKnowledgeIsOnTheRow:
if mark.label_regime == "tc260" and mark.key != "jimeng_pill":
assert "aigc" in mark.provenance_signals, mark.key
def test_only_gemini_claims_platform_tokens(self):
def test_platform_token_marks_are_the_c2pa_attributed_ones(self):
# Gemini (Google C2PA) and Microsoft (issuer "Microsoft") are the marks whose
# vendor a C2PA platform string can confirm; every other mark reaches its
# provenance through TC260 codes or product signals instead.
by_token = {m.key for m in reg.known_marks() if m.provenance_platform_tokens}
assert by_token == {"gemini"}
assert by_token == {"gemini", "microsoft"}
class TestPillSuppressors:
"""The pill veto is derived from the registry, not hand-listed.
The hand-written list drifted: LibLibAI was registered in the same commit as
The hand-written list drifted: LiblibAI was registered in the same commit as
RunningHub and Baidu, both of which were added to the veto, and it was not. A
derived set cannot be forgotten by the next registration.
"""
@@ -581,7 +586,7 @@ class TestPillSuppressors:
assert not reg._keep_pill({"liblib"}, provenance=frozenset({"jimeng"}), footprint_flat=1.0)
def test_pill_dropped_on_liblib_even_with_the_jimeng_wordmark(self):
"""The veto precedes the wordmark arm, so a co-firing LibLibAI wins.
"""The veto precedes the wordmark arm, so a co-firing LiblibAI wins.
This is the broader half of the change: it needs neither TC260 provenance nor
a flat footprint, so it is reachable on more inputs than the metadata arm.
@@ -589,7 +594,7 @@ class TestPillSuppressors:
assert not reg._keep_pill({"liblib", "jimeng"}, provenance=frozenset(), footprint_flat=1.0)
def test_pill_survives_gemini_and_samsung(self):
"""Neither is a TC260 labeller, and neither can put "jimeng" into provenance,
"""Neither is a TC260 labeler, and neither can put "jimeng" into provenance,
so neither may veto the arm it could not have enabled."""
assert reg._keep_pill({"gemini", "jimeng"}, provenance=frozenset(), footprint_flat=1.0)
assert reg._keep_pill({"samsung", "jimeng"}, provenance=frozenset(), footprint_flat=1.0)
@@ -632,4 +637,4 @@ class TestProvenanceMaskThreading:
)
monkeypatch.setattr(eng, "footprint_mask", lambda image, *, force=False, region=None, dilate=None: None)
_, removed = reg.remove_auto_marks(np.zeros((256, 256, 3), np.uint8), sensitivity="strict", backend="cv2")
assert "Google Gemini sparkle" not in removed
assert "Google Gemini visible watermark (sparkle)" not in removed