mirror of
https://github.com/wiltodelta/remove-ai-watermarks.git
synced 2026-10-19 07:37:14 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5764661b4 | ||
|
|
97cba11efe | ||
|
|
809d980fb7 | ||
|
|
10da166f3e |
No files matched your search
+4
-1
@@ -43,7 +43,10 @@ is found, it reports the origin as unknown. It does not claim the image is
|
||||
clean. For C2PA files, the text report shows asset integrity, claim-signature,
|
||||
signer-trust, and signer-validity results separately. An intact claim from an
|
||||
untrusted or expired signer is reported at medium confidence; a failed asset
|
||||
binding or signature does not confirm the claimed origin.
|
||||
binding or signature does not confirm the claimed origin. When a structured
|
||||
C2PA soft binding is present, the report also names its exact algorithm and
|
||||
signed value; removing the manifest does not remove the referenced pixel
|
||||
watermark or content fingerprint.
|
||||
|
||||
Machine readable output:
|
||||
|
||||
|
||||
@@ -407,7 +407,11 @@ remain identical because isolated decoder hits can otherwise be content noise.
|
||||
It accepts Variant P schemas 0-2. Variant Q requires a different model, and
|
||||
schema 3 is rejected at the measured precision threshold. Its NumPy 1.x runtime
|
||||
limits the extra to Python 3.11-3.12; the rest of the package remains supported
|
||||
through Python 3.14. The calibration history is in
|
||||
through Python 3.14. Through 0.29.0 the extra also resolves lightning 2.6.5
|
||||
(PYSEC-2026-3624, no fixed release yet); the vulnerable `load_from_checkpoint`
|
||||
path is unreachable here because TrustMark loads its checksummed checkpoints
|
||||
with plain `torch.load`. Bump lightning and cut a patch release when a fix
|
||||
ships. The calibration history is in
|
||||
[module internals](module-internals.md#metadata-and-provenance).
|
||||
|
||||
External AI versus real image classifiers are out of scope. The project
|
||||
|
||||
@@ -392,6 +392,15 @@ manifest names only `c2pa-tool` while a validated ingredient names Dreamina, and
|
||||
Firefly chains that identify `Adobe_Firefly` without repeating a digital source
|
||||
type. Unreachable manifests remain excluded.
|
||||
|
||||
Reachable `c2pa.soft-binding*` assertions retain their exact `alg` and bounded,
|
||||
printable block `value` in addition to the normalized vendor label. A block value
|
||||
without its algorithm is not surfaced because it cannot be attributed to a
|
||||
decoder or registry entry. `com.microsoft.invismark.1` uses that value as the
|
||||
pixel-watermark identifier in Microsoft Paint output. An InvisMark soft binding
|
||||
keeps the invisible-removal gate fail-safe even when the C2PA asset binding has
|
||||
since become invalid, because metadata damage does not prove the pixel carrier
|
||||
disappeared. Content-fingerprint soft bindings do not trigger pixel regeneration.
|
||||
|
||||
The SDK default enables trust verification but supplies no production trust
|
||||
anchors. Consequently, an installation without an explicitly maintained C2PA
|
||||
trust bundle reports otherwise valid signer chains as untrusted and keeps their
|
||||
|
||||
@@ -11,7 +11,7 @@ A release is complete only after all four published surfaces are verified:
|
||||
|---|---|---|
|
||||
| PyPI | The `remove-ai-watermarks` wheel and source distribution | `publish.yml` |
|
||||
| Homebrew | The `remove-ai-watermarks` formula in `wiltodelta/homebrew-tap` | `distribute.yml` |
|
||||
| Hugging Face Space | A factory rebuild of `wiltodelta/remove-ai-watermarks` against the new PyPI release | `distribute.yml` |
|
||||
| Hugging Face Space | The Space demo deployed on the new release through a requirements-pin bump in `wiltodelta/raiw-hf-space` | `raiw-hf-space` sync workflow; the pin bump is manual, `distribute.yml` only re-installs the pinned version |
|
||||
| ComfyUI Registry | A compatible release of `wiltodelta/ComfyUI-remove-ai-watermarks` with its own node version | `distribute.yml` and the node repository's workflows |
|
||||
|
||||
The GitHub Release is the trigger and release record for this flow. Conda is
|
||||
@@ -58,9 +58,17 @@ PyPI API token from the repository.
|
||||
waits for the matching source distribution to appear on PyPI, then:
|
||||
|
||||
- updates the Homebrew tap formula URL and SHA-256;
|
||||
- triggers a factory rebuild of the Hugging Face Space;
|
||||
- triggers a factory rebuild of the Hugging Face Space: this re-installs the
|
||||
version pinned in the Space repo, it does NOT upgrade the demo;
|
||||
- synchronizes, tests, versions, and publishes the ComfyUI nodes.
|
||||
|
||||
Upgrading the Hugging Face Space is a separate, manual step: bump
|
||||
`remove-ai-watermarks[visible,heif]` in `wiltodelta/raiw-hf-space` (`pyproject.toml`,
|
||||
`uv lock`, and the re-exported `requirements.txt`), then push. That repository's
|
||||
`sync-to-hf.yml` mirrors the files onto the Space, which rebuilds on the new
|
||||
pin. Its callback smoke tests run in that repository's CI on every push; they
|
||||
are the scenario check for the demo surface.
|
||||
|
||||
The workflow can also be started manually with an optional version input.
|
||||
|
||||
If a distribution job fails because a repository or Hugging Face credential is
|
||||
@@ -116,4 +124,14 @@ After publication, verify:
|
||||
- the Homebrew formula points to the new source distribution;
|
||||
- the distribution workflow completed successfully;
|
||||
- the ComfyUI Registry node requires the new library version;
|
||||
- the Hugging Face Space serves the new release: its `requirements.txt` pin
|
||||
matches (raw file via the HF API), the runtime stage is `RUNNING` after the
|
||||
rebuild, and one live `identify` and one live `visible` API call succeed
|
||||
against the running Space;
|
||||
- a clean install can run `remove-ai-watermarks --version`.
|
||||
|
||||
A clean-install check run immediately after publication can fail with "no
|
||||
version of remove-ai-watermarks==X.Y.Z" even though the simple index already
|
||||
lists it: uv serves its cached pre-release view of the index. Re-run with a
|
||||
fresh cache (`UV_CACHE_DIR=$(mktemp -d)`) before treating it as a release
|
||||
failure.
|
||||
@@ -138,6 +138,18 @@ all-media watermark policy, and current OpenAI C2PA carrying an explicit
|
||||
assert SynthID. After provenance metadata is removed, a local negative result
|
||||
is still inconclusive.
|
||||
|
||||
Microsoft Paint can name `com.microsoft.invismark.1` in a C2PA soft-binding
|
||||
assertion. Inspection reports both that exact algorithm and its signed `value`,
|
||||
which Paint uses as the identifier carried by the pixel watermark. Photos has a
|
||||
parallel local writer path, but no public output sample was available for this
|
||||
work. Metadata stripping removes the embedded manifest, not the pixel carrier.
|
||||
The project has no validated local InvisMark decoder. Microsoft's official
|
||||
[Content Provenance Detection API](https://learn.microsoft.com/en-us/azure/ai-services/content-safety/how-to/how-to-provenance-detection)
|
||||
is the external oracle: it reports pixel `Watermark` and embedded `C2PA` results
|
||||
separately. The diffusion profiles have not been broadly certified against that
|
||||
oracle, so a control-positive, output-negative pair is still a per-file result,
|
||||
not a universal guarantee.
|
||||
|
||||
For MP4, MOV, and M4V, `video invisible` or the explicit
|
||||
`video all --invisible` option can regenerate the video through a VAE and strip
|
||||
source metadata. The shipped profile is oracle-certified, but it is not a local
|
||||
@@ -158,6 +170,7 @@ not a universal clean verdict.
|
||||
| Google Gemini | Sparkle | Diffusion regeneration for SynthID | C2PA and related source signals |
|
||||
| Google Veo video | Veo diamond and legacy text | Oracle-certified VAE removal for SynthID | C2PA and related source signals |
|
||||
| OpenAI image generators | None registered | Diffusion regeneration for supported invisible signals | C2PA and generator provenance |
|
||||
| Microsoft Paint and Photos | None registered | External Microsoft oracle for InvisMark; no validated local decoder | Paint C2PA soft-binding algorithm and identifier |
|
||||
| Stable Diffusion and SDXL | None registered | Diffusion regeneration; optional open decoder | Embedded parameters and text metadata |
|
||||
| FLUX | None registered | Diffusion regeneration; optional open decoder | C2PA for supported sources |
|
||||
| Adobe Firefly | None registered | Optional TrustMark Variant P decoder | C2PA |
|
||||
|
||||
@@ -186,10 +186,14 @@ report recall from the detector-sampled set.
|
||||
|
||||
## Tier D -- external oracles (manual, not automatable here)
|
||||
|
||||
SynthID removal cannot be verified locally by design -- no public decoder exists. Each
|
||||
vendor has its own oracle and it covers only that vendor's content: `openai.com/verify` for
|
||||
OpenAI (more accessible, the automation candidate), the Gemini app for Google (manual,
|
||||
rate-limited). A quiet metadata proxy is **not** proof the pixel watermark is gone.
|
||||
Proprietary watermark removal cannot be verified locally by design -- no public decoder
|
||||
exists. Each vendor has its own oracle and it covers only that vendor's content:
|
||||
`openai.com/verify` for OpenAI, the Gemini app for Google, and Microsoft's
|
||||
[Content Provenance Detection API](https://learn.microsoft.com/en-us/azure/ai-services/content-safety/how-to/how-to-provenance-detection)
|
||||
for InvisMark. The Microsoft API reports `Watermark` and `C2PA` separately. It therefore
|
||||
needs a pixel-identical metadata-stripped control: the control must lose `C2PA` while
|
||||
remaining `Watermark`-positive before a candidate's negative result can be attributed to
|
||||
pixel regeneration. A quiet metadata proxy is **not** proof the pixel watermark is gone.
|
||||
|
||||
Scope honestly: this tier certifies strength floors on a handful of images per vendor, and
|
||||
that is all it can do. See `docs/synthid.md`.
|
||||
|
||||
@@ -65,7 +65,8 @@ payloads. Removal remuxes either container through ffmpeg with stream copy.
|
||||
DWT-DCT decoder only applies when the producing pipeline actually ran its
|
||||
encoder and the carrier remains decodable.
|
||||
- **Invisible but NOT locally detectable (proprietary, API/oracle only — same wall as SynthID):** Amazon Titan Image Generator + Nova Canvas (Bedrock `DetectGeneratedContent` API), Kakao (new SynthID image adopter, May 2026), NVIDIA Cosmos (SynthID video). No local detector possible; treat like SynthID.
|
||||
- **C2PA 2.4 "Durable Content Credentials" (April 2026; verified against the spec) raise the bar for metadata stripping.** 2.4 defines soft bindings (an invisible watermark or a content fingerprint) plus a server-side manifest repository and a new `c2pa.repository-receipt` assertion. Per the spec: "if a C2PA manifest is removed from an asset, but a copy of that manifest remains in a provenance store elsewhere, the manifest and asset may be matched using available soft bindings." So our local `metadata --remove` deletes the *embedded* manifest, but a fingerprint/watermark soft binding can still re-link the image to its manifest in a repository server-side. Stripping the file is becoming necessary-but-not-sufficient against durable provenance. (Our parsers target the stable embedded-manifest format documented in C2PA 2.1 §11; that format is unchanged in 2.4 -- the new pieces are repository/soft-binding infra, not the on-file box layout, so no parser change is implied.) Spec: https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html We now READ the soft-binding `alg` (`C2PA_SOFT_BINDINGS` / `soft_binding_vendors_in`) to name the forensic-watermark vendor, and locally DECODE the one open scheme, Adobe TrustMark (`trustmark_detector`); the rest (Digimarc/Imatag/Steg.AI/...) stay name-only (proprietary decoders).
|
||||
- **C2PA 2.4 "Durable Content Credentials" (April 2026; verified against the spec) raise the bar for metadata stripping.** 2.4 defines soft bindings (an invisible watermark or a content fingerprint) plus a server-side manifest repository and a new `c2pa.repository-receipt` assertion. Per the spec: "if a C2PA manifest is removed from an asset, but a copy of that manifest remains in a provenance store elsewhere, the manifest and asset may be matched using available soft bindings." So our local `metadata --remove` deletes the *embedded* manifest, but a fingerprint/watermark soft binding can still re-link the image to its manifest in a repository server-side. Stripping the file is becoming necessary-but-not-sufficient against durable provenance. (Our parsers target the stable embedded-manifest format documented in C2PA 2.1 §11; that format is unchanged in 2.4 -- the new pieces are repository/soft-binding infra, not the on-file box layout, so no parser change is implied.) Spec: https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html We now READ the soft-binding `alg` (`C2PA_SOFT_BINDINGS` / `soft_binding_vendors_in`) to name the forensic-watermark vendor, preserve the structured assertion's signed `value`, and locally DECODE the one open scheme, Adobe TrustMark (`trustmark_detector`); the rest stay name-only unless a compatible decoder is independently verified.
|
||||
- **Microsoft Paint and Photos InvisMark (reverse-engineered 2026-08-20):** Paint receives a per-generation GUID from remote prompt moderation, embeds it into locally generated pixels, and records the same value in `c2pa.soft-binding` under `com.microsoft.invismark.1`. The C2PA soft-binding registry independently identifies that algorithm as Microsoft Responsible AI InvisMark for image and video. Paint's 144-bit writer framing does not match the public repository's 100-bit pretrained checkpoint interface, so compatibility is not assumed. The parser reports the signed identifier; there is no validated local pixel decoder. Microsoft's external Content Provenance Detection API is the removal oracle because it reports `Watermark` separately from `C2PA`; a metadata-stripped, pixel-identical control must remain watermark-positive before an output-negative result is attributed to pixel removal. Sources: https://xusheng.dev/posts/reversing/mspaint_invisible_watermark/main/, https://github.com/c2pa-org/softbinding-algorithm-list/blob/main/softbinding-algorithm-list.json, and https://learn.microsoft.com/en-us/azure/ai-services/content-safety/how-to/how-to-provenance-detection
|
||||
- **Built in the dated batch:** soft-binding vendor detection, IPTC Photo
|
||||
Metadata AI-disclosure fields, C2PA detection and stripping for supported
|
||||
ISOBMFF video, the optional Adobe TrustMark decoder, and temporally stabilized
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
# on it, including the ComfyUI node package. The console script below carries
|
||||
# the same weight, since users have it on PATH.
|
||||
name = "remove-ai-watermarks"
|
||||
version = "0.29.0"
|
||||
version = "0.30.0"
|
||||
description = "AI watermark remover for visible, invisible, and provenance marks in images and video"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11,<3.15"
|
||||
|
||||
@@ -32,7 +32,7 @@ _os.environ.setdefault("TRANSFORMERS_VERBOSITY", "error")
|
||||
_warnings.filterwarnings("ignore", message=r".*ImageProcessorFast.*")
|
||||
|
||||
|
||||
__version__ = "0.29.0"
|
||||
__version__ = "0.30.0"
|
||||
|
||||
__all__ = [
|
||||
"BatchSummary",
|
||||
|
||||
@@ -307,7 +307,9 @@ def c2pa_info_has_invalid_credential(info: dict[str, Any]) -> bool:
|
||||
|
||||
def c2pa_info_has_removal_hint(info: dict[str, Any]) -> bool:
|
||||
"""Return whether a C2PA AI or watermark claim should keep removal fail-safe."""
|
||||
return bool(info.get("ai_source_kind") or info.get("synthid_watermark"))
|
||||
soft_bindings = info.get("soft_binding_vendors")
|
||||
has_invismark = isinstance(soft_bindings, list) and "Microsoft InvisMark" in soft_bindings
|
||||
return bool(info.get("ai_source_kind") or info.get("synthid_watermark") or has_invismark)
|
||||
|
||||
|
||||
def _has_suffix(codes: list[str], suffixes: tuple[str, ...]) -> bool:
|
||||
@@ -390,6 +392,7 @@ def _structured_manifest_fields(store: dict[str, Any]) -> dict[str, Any]:
|
||||
actions: list[str] = []
|
||||
source_types: list[str] = []
|
||||
soft_binding_algorithms: list[str] = []
|
||||
soft_binding_values: list[str] = []
|
||||
claim_generator_asserts_ai = False
|
||||
|
||||
def add_tool_matches(value: str, *, asserts_ai: bool = False) -> None:
|
||||
@@ -431,9 +434,18 @@ def _structured_manifest_fields(store: dict[str, Any]) -> dict[str, Any]:
|
||||
label = assertion.get("label")
|
||||
data = assertion.get("data")
|
||||
if isinstance(label, str) and label.startswith("c2pa.soft-binding") and isinstance(data, dict):
|
||||
algorithm = cast("dict[object, object]", data).get("alg")
|
||||
soft_binding = cast("dict[object, object]", data)
|
||||
algorithm = soft_binding.get("alg")
|
||||
if isinstance(algorithm, str):
|
||||
soft_binding_algorithms.append(algorithm)
|
||||
blocks = soft_binding.get("blocks")
|
||||
if isinstance(blocks, list):
|
||||
for block_value in cast("list[object]", blocks):
|
||||
if not isinstance(block_value, dict):
|
||||
continue
|
||||
value = cast("dict[object, object]", block_value).get("value")
|
||||
if isinstance(value, str) and value.isprintable() and len(value) <= 256:
|
||||
soft_binding_values.append(value)
|
||||
if not (isinstance(label, str) and label.startswith("c2pa.actions") and isinstance(data, dict)):
|
||||
continue
|
||||
action_values = cast("dict[object, object]", data).get("actions")
|
||||
@@ -491,11 +503,15 @@ def _structured_manifest_fields(store: dict[str, Any]) -> dict[str, Any]:
|
||||
info["synthid_watermark"] = synthid_verdict(", ".join(synthid))
|
||||
|
||||
if soft_binding_algorithms:
|
||||
soft_binding_algorithms = list(dict.fromkeys(soft_binding_algorithms))
|
||||
algorithms = "\n".join(soft_binding_algorithms).encode()
|
||||
soft_bindings = soft_binding_vendors_in(algorithms)
|
||||
if soft_bindings:
|
||||
info["soft_binding_vendors"] = soft_bindings
|
||||
info["soft_binding"] = ", ".join(soft_bindings)
|
||||
info["soft_binding_algorithm"] = ", ".join(soft_binding_algorithms)
|
||||
if soft_binding_values:
|
||||
info["soft_binding_value"] = ", ".join(dict.fromkeys(soft_binding_values))
|
||||
return info
|
||||
|
||||
|
||||
|
||||
@@ -145,6 +145,10 @@ _C2PA_CLOUD_CAVEAT = (
|
||||
"It marks Content Credentials, not AI origin: the cloud manifest may describe a "
|
||||
"human edit, and reading it needs a network fetch this tool does not make."
|
||||
)
|
||||
_SOFT_BINDING_CAVEAT = (
|
||||
"Removing the embedded C2PA manifest does not remove its soft binding: the named "
|
||||
"watermark or fingerprint may remain in the pixels and re-link the asset to provenance."
|
||||
)
|
||||
_SAMSUNG_GENAI_CAVEAT = (
|
||||
"Samsung's genAIType marker shows a Galaxy AI editing tool (Generative Edit, "
|
||||
"Sketch to Image, ...) touched the image; it is an undocumented proprietary "
|
||||
@@ -388,6 +392,8 @@ def evidence_from_metadata_record(
|
||||
"actions",
|
||||
"synthid_watermark",
|
||||
"soft_binding",
|
||||
"soft_binding_algorithm",
|
||||
"soft_binding_value",
|
||||
):
|
||||
if key in c2pa_info:
|
||||
ai_metadata.setdefault(key, str(c2pa_info[key]))
|
||||
@@ -1269,12 +1275,20 @@ def _identify_from_evidence(
|
||||
# the watermark itself can't be decoded; names whose watermark stamped the pixels.
|
||||
soft_binding = meta.get("soft_binding") or (", ".join(v) if (v := soft_binding_vendors_in(region)) else None)
|
||||
if soft_binding:
|
||||
soft_binding_algorithm = meta.get("soft_binding_algorithm") or info.get("soft_binding_algorithm")
|
||||
soft_binding_value = meta.get("soft_binding_value") or info.get("soft_binding_value")
|
||||
soft_binding_details = "; ".join(
|
||||
str(value) for value in (soft_binding, soft_binding_algorithm, soft_binding_value) if value
|
||||
)
|
||||
signals.append(
|
||||
Signal(
|
||||
"soft_binding", f"C2PA soft binding: {soft_binding}", "high" if c2pa_level == "verified" else "medium"
|
||||
"soft_binding",
|
||||
f"C2PA soft binding: {soft_binding_details}",
|
||||
"high" if c2pa_level == "verified" else "medium",
|
||||
)
|
||||
)
|
||||
watermarks.append(f"Forensic watermark soft binding ({soft_binding})")
|
||||
watermarks.append(f"Forensic watermark soft binding ({soft_binding_details})")
|
||||
caveats.append(_SOFT_BINDING_CAVEAT)
|
||||
|
||||
# ── IPTC "Made with AI" (Meta etc.), only meaningful without C2PA ─
|
||||
iptc = any(m in head for m in IPTC_AI_MARKERS)
|
||||
|
||||
@@ -1160,6 +1160,8 @@ def get_ai_metadata(image_path: Path) -> dict[str, str]:
|
||||
"actions",
|
||||
"synthid_watermark",
|
||||
"soft_binding",
|
||||
"soft_binding_algorithm",
|
||||
"soft_binding_value",
|
||||
"c2pa_validation_source",
|
||||
"c2pa_validation_state",
|
||||
"c2pa_integrity",
|
||||
|
||||
@@ -1144,6 +1144,36 @@ class TestIdentifySoftBinding:
|
||||
assert any("Digimarc" in w for w in r.watermarks)
|
||||
assert any(s.name == "soft_binding" for s in r.signals)
|
||||
|
||||
def test_invismark_signal_lists_signed_watermark_id(self, tmp_path: Path):
|
||||
watermark_id = "83424621-03cb-40e3-9808-a9fae837156d"
|
||||
record = {
|
||||
"c2pa_store": {
|
||||
"active_manifest": "paint",
|
||||
"manifests": {
|
||||
"paint": {
|
||||
"assertions": [
|
||||
{
|
||||
"label": "c2pa.soft-binding",
|
||||
"data": {
|
||||
"alg": "com.microsoft.invismark.1",
|
||||
"blocks": [{"scope": "the entire image", "value": watermark_id}],
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
evidence = evidence_from_metadata_record(record, path=tmp_path / "paint.png")
|
||||
|
||||
report = identify_from_evidence(evidence)
|
||||
|
||||
assert evidence.ai_metadata["soft_binding_value"] == watermark_id
|
||||
signal = next(signal for signal in report.signals if signal.name == "soft_binding")
|
||||
assert "com.microsoft.invismark.1" in signal.detail
|
||||
assert watermark_id in signal.detail
|
||||
assert any("may remain in the pixels" in caveat for caveat in report.caveats)
|
||||
|
||||
|
||||
class TestIdentifyIptcAi:
|
||||
"""IPTC 2025.1 AISystemUsed drives an AI verdict + platform attribution."""
|
||||
|
||||
@@ -13,6 +13,7 @@ from PIL import Image
|
||||
from remove_ai_watermarks._internal.c2pa import (
|
||||
_parse_c2pa_chunk,
|
||||
c2pa_info_from_manifest_store,
|
||||
c2pa_info_has_removal_hint,
|
||||
cbor_text_after,
|
||||
extract_c2pa_chunk,
|
||||
extract_c2pa_info,
|
||||
@@ -209,6 +210,64 @@ class TestC2PA:
|
||||
assert info["ai_tool"] == "Dreamina"
|
||||
assert info["c2pa_identity_ai"] is True
|
||||
|
||||
def test_structured_invismark_exposes_algorithm_and_watermark_id(self):
|
||||
watermark_id = "83424621-03cb-40e3-9808-a9fae837156d"
|
||||
store = {
|
||||
"active_manifest": "paint",
|
||||
"manifests": {
|
||||
"paint": {
|
||||
"assertions": [
|
||||
{
|
||||
"label": "c2pa.soft-binding",
|
||||
"data": {
|
||||
"alg": "com.microsoft.invismark.1",
|
||||
"blocks": [
|
||||
{
|
||||
"scope": "the entire image",
|
||||
"value": watermark_id,
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
info = c2pa_info_from_manifest_store(store)
|
||||
|
||||
assert info["soft_binding"] == "Microsoft InvisMark"
|
||||
assert info["soft_binding_algorithm"] == "com.microsoft.invismark.1"
|
||||
assert info["soft_binding_value"] == watermark_id
|
||||
|
||||
def test_soft_binding_value_requires_its_algorithm(self):
|
||||
store = {
|
||||
"active_manifest": "broken",
|
||||
"manifests": {
|
||||
"broken": {
|
||||
"assertions": [
|
||||
{
|
||||
"label": "c2pa.soft-binding",
|
||||
"data": {"blocks": [{"value": "not-attributable"}]},
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
assert "soft_binding_value" not in c2pa_info_from_manifest_store(store)
|
||||
|
||||
def test_soft_binding_keeps_invisible_removal_fail_safe(self):
|
||||
assert c2pa_info_has_removal_hint({"soft_binding_vendors": ["Microsoft InvisMark"]}) is True
|
||||
|
||||
def test_content_fingerprint_does_not_trigger_invisible_removal(self):
|
||||
info = {
|
||||
"soft_binding": "Adobe (content fingerprint)",
|
||||
"soft_binding_vendors": ["Adobe (content fingerprint)"],
|
||||
}
|
||||
|
||||
assert c2pa_info_has_removal_hint(info) is False
|
||||
|
||||
def test_invalid_ingredient_does_not_taint_active_validation_or_supply_claims(self):
|
||||
store = {
|
||||
"active_manifest": "update",
|
||||
|
||||
Reference in new issue
Block a user