mirror of
https://github.com/wiltodelta/remove-ai-watermarks.git
synced 2026-08-07 06:28:36 +02:00
feat(identify): detect Dreamina C2PA + Tencent Cloud AIGC schema
Two AI-provenance metadata types mined from the retained corpus that
identify previously read as no-signal:
- Dreamina (ByteDance's international Jimeng brand) signs C2PA as
"Bytedance Pte. Ltd." with a "Dreamina/x.y" claim generator and NO
digitalSourceType, so the generator name is the only AI signal. Add a
C2paAiVendor row with a new asserts_ai flag (identity-AI: presence
asserts AI without trainedAlgorithmicMedia) plus the derived
C2PA_IDENTITY_AI_ORGS view, folded into identify's c2pa_is_ai. Keyed on
the Dreamina generator token, not the "Bytedance Pte" issuer, so non-AI
CapCut edits signed by the same entity stay unattributed. 7/7 corpus
files now attribute to ByteDance.
- Tencent Cloud's TC260 AIGC variant uses a ServiceProvider/ServiceUser
schema (vs the producer-side ContentProducer schema), embedded in EXIF
ImageDescription; add those field names to _TC260_FIELDS so the generic
{"AIGC":{...}} gate accepts it. 11/11 corpus files now flagged.
Test-first: reproducing tests in test_identify.py / test_metadata.py.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
41e20a0606
commit
49869ab02b
@@ -41,7 +41,12 @@ from remove_ai_watermarks.metadata import (
|
||||
xai_signature,
|
||||
)
|
||||
from remove_ai_watermarks.noai.c2pa import cbor_text_after, extract_c2pa_info, soft_binding_vendors_in
|
||||
from remove_ai_watermarks.noai.constants import C2PA_AI_TOOLS, C2PA_AI_VENDORS, C2PA_ISSUERS
|
||||
from remove_ai_watermarks.noai.constants import (
|
||||
C2PA_AI_TOOLS,
|
||||
C2PA_AI_VENDORS,
|
||||
C2PA_IDENTITY_AI_ORGS,
|
||||
C2PA_ISSUERS,
|
||||
)
|
||||
from remove_ai_watermarks.watermark_registry import GEMINI_SPARKLE_TRUST_CONF
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -515,7 +520,14 @@ def identify(image_path: Path, *, check_visible: bool = True, check_invisible: b
|
||||
c2pa_source_kind = "generated"
|
||||
elif b"compositeWithTrainedAlgorithmicMedia" in head:
|
||||
c2pa_source_kind = "enhanced"
|
||||
c2pa_is_ai = c2pa_source_kind is not None
|
||||
# An identity-AI issuer (a pure-generator brand like Dreamina) asserts AI even
|
||||
# without a digitalSourceType -- some ByteDance/Dreamina manifests ship no
|
||||
# trainedAlgorithmicMedia, so the registered generator name is the only signal.
|
||||
# Restricted to the ``asserts_ai`` vendors (distinctive brand strings), so it
|
||||
# does not reopen the incidental-mention problem the common-word issuers have.
|
||||
issuer_blob = " ".join(issuers)
|
||||
c2pa_identity_ai = has_c2pa and any(org in issuer_blob for org in C2PA_IDENTITY_AI_ORGS)
|
||||
c2pa_is_ai = c2pa_source_kind is not None or c2pa_identity_ai
|
||||
# Generator string (for the signal detail): structured for PNG, CBOR-scanned
|
||||
# for other containers. Best-effort -- some manifests key it as
|
||||
# `claim_generator_info` (Pixel), so this can be None even when a device is
|
||||
|
||||
@@ -137,6 +137,7 @@ AIGC_MARKERS: tuple[bytes, ...] = (
|
||||
# label even when the namespaced XMP element is absent.
|
||||
_TC260_FIELDS: frozenset[str] = frozenset(
|
||||
{
|
||||
# Producer-side schema (Doubao and most China-served generators).
|
||||
"Label",
|
||||
"ContentProducer",
|
||||
"ProduceID",
|
||||
@@ -144,6 +145,12 @@ _TC260_FIELDS: frozenset[str] = frozenset(
|
||||
"PropagateID",
|
||||
"ReservedCode1",
|
||||
"ReservedCode2",
|
||||
# Service-provider schema (Tencent Cloud's AIGC variant, mined from the
|
||||
# retained corpus 2026-07): the same ``{"AIGC":{...}}`` wrapper but keyed
|
||||
# ``ServiceProvider`` / ``ServiceUser`` (+ generic ``Time`` / ``ContentId``,
|
||||
# not gated on), embedded in EXIF ``ImageDescription``.
|
||||
"ServiceProvider",
|
||||
"ServiceUser",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -100,6 +100,13 @@ class C2paAiVendor(NamedTuple):
|
||||
# (usually a shorter form of org, e.g. "Google" for "Google LLC"); None when platform is.
|
||||
needle: str | None
|
||||
synthid: bool = False # vendor pairs an invisible SynthID pixel watermark with its C2PA manifest
|
||||
# The vendor's mere presence in the manifest asserts AI generation even without
|
||||
# a digitalSourceType (``trainedAlgorithmicMedia``) assertion. Set ONLY for a
|
||||
# pure-generator brand whose issuer/generator byte string is unambiguous (e.g.
|
||||
# "Dreamina"). Do NOT set for common-word issuers (Adobe/Google/OpenAI/Microsoft):
|
||||
# those appear incidentally in unrelated XMP/trust-chain bytes, so they stay
|
||||
# source-type-gated in identify._attribute_platform.
|
||||
asserts_ai: bool = False
|
||||
|
||||
|
||||
# C2PA known vendors, ORDERED for first-match-wins platform attribution: when a
|
||||
@@ -149,6 +156,24 @@ C2PA_AI_VENDORS: tuple[C2paAiVendor, ...] = (
|
||||
# earlier row's label wins anyway -- they normalize together for clash
|
||||
# detection. Verified on real signed files in production traffic, 2026-06-19.
|
||||
C2paAiVendor(b"Byteplus", "BytePlus (ByteDance)", "ByteDance (Doubao / Jimeng / Volcano Engine)", "ByteDance"),
|
||||
# Dreamina (ByteDance's international Jimeng brand) signs C2PA as "Bytedance
|
||||
# Pte. Ltd." with a "Dreamina/x.y" claim generator and, unlike the Volcano
|
||||
# Engine output, NO digitalSourceType assertion -- so the generator name is the
|
||||
# only AI signal. It is registered by that generator token (which the caBX /
|
||||
# store-JSON byte scan sees across active + ingredient manifests, where the
|
||||
# active manifest is often a plain c2pa-tool transcode). ``asserts_ai`` lets the
|
||||
# issuer alone flag AI without trainedAlgorithmicMedia; "Dreamina" is a
|
||||
# distinctive brand string, so it does not risk the incidental-mention problem
|
||||
# the common-word issuers have. Verified on real signed files in the retained
|
||||
# corpus, 2026-07. Normalizes to the same "ByteDance" needle/platform as the
|
||||
# volcengine row (they collapse together for clash detection).
|
||||
C2paAiVendor(
|
||||
b"Dreamina",
|
||||
"ByteDance (Dreamina)",
|
||||
"ByteDance (Doubao / Jimeng / Volcano Engine)",
|
||||
"ByteDance",
|
||||
asserts_ai=True,
|
||||
),
|
||||
# Canva Magic Media signs AI-generated images as "Canva" with a generic
|
||||
# c2pa-rs claim generator + trainedAlgorithmicMedia; without this entry the
|
||||
# source read AI but no platform was attributed. Verified on real signed files
|
||||
@@ -181,6 +206,12 @@ C2PA_AI_VENDORS: tuple[C2paAiVendor, ...] = (
|
||||
# C2PA issuer signature -> resolved org name, for the manifest byte-scan.
|
||||
C2PA_ISSUERS: dict[bytes, str] = {v.issuer: v.org for v in C2PA_AI_VENDORS}
|
||||
|
||||
# Resolved org names of the vendors whose presence asserts AI generation on its
|
||||
# own (no digitalSourceType needed) -- see the ``asserts_ai`` field. identify uses
|
||||
# this to lift the AI verdict for an identity-AI issuer (e.g. Dreamina) that ships
|
||||
# no trainedAlgorithmicMedia. Derived from the flag -- set it on the vendor, not here.
|
||||
C2PA_IDENTITY_AI_ORGS: frozenset[str] = frozenset(v.org for v in C2PA_AI_VENDORS if v.asserts_ai)
|
||||
|
||||
# C2PA issuers whose signed outputs also carry an invisible SynthID pixel
|
||||
# watermark -- a metadata proxy for "SynthID is in the pixels":
|
||||
# - Google (Imagen/Gemini): embeds SynthID, long-standing (DeepMind docs).
|
||||
|
||||
Reference in New Issue
Block a user