Add paid-service captures, aggregator attribution, and provenance vetoes

Collect provider samples from Higgsfield, Runway and Dreamina plus Google
Photos, Gemini, Grok and YouTube re-encodes into data/captures/2026-09 with
a hash-checked manifest, and use them to fix attribution and false positives:

- Attribute aggregator outputs: Higgsfield job metadata, Dreamina's
  re-signed manifests, Runway's own C2PA signer, TC260 video producers.
- Veto any visible video mark when a valid C2PA claim names another vendor
  (a Gemini Omni clip read as Sora on steam), and gate Kling and Hailuo
  matches on edge, anchor and white-label evidence.
- Veto the Gemini sparkle when trusted provenance names another vendor.
- Report SynthID as present for Google Photos AI edits: Google's checker
  found it on all four measured edits, though manifests record no action.
- Add Vidu video and Wan image marks, TC260 audio placements, and record
  the AIGC key in metadata records.
- Document the research sweep, the Dreamina badge decision, and verified
  Google statements on SynthID in Photos.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Victor KuznetsovandClaude Opus 5.5 committed 2026-09-26 14:32:53 -07:00
1 parent 6f629a4ea6
commit 7ba07869d2
192 files changed
+2582 -194

No files matched your search

+3 -1
View File
@@ -297,7 +297,9 @@ def _pixel_fingerprint(path: Path) -> tuple[str, int, int, str]:
"""Return a bounded-memory RGBA pixel hash, width, height, and format."""
with Image.open(path) as image:
image.load()
image_format = image.format
# iPhone JPEGs are MPO: a JPEG primary image followed by an HDR gain map.
# PIL decodes the primary frame, which is what a provider checks.
image_format = "JPEG" if image.format == "MPO" else image.format
if image_format not in {"JPEG", "PNG", "WEBP"}:
raise ValueError(f"unsupported image format: {image_format or 'unknown'}")
digest = hashlib.sha256()
+58
View File
@@ -81,6 +81,9 @@ MARKS = {
"chromastudio_alpha.png": "ChromaStudio.ai",
"digenai_alpha.png": "DIGENAI",
"gendo_alpha.png": "GendoAI",
# Alibaba Wan (通义万相) bottom-right logo plus "Wan" wordmark. Sentinel:
# drawn by draw_wan(), not font-rendered alone.
"wan_alpha.png": "Wan logo + Wan",
# Jianying / 剪映, the China product in the CapCut family, stamps 剪映AI
# bottom-right (the international CapCut pill sits top-left).
"jianying_alpha.png": "剪映AI",
@@ -300,10 +303,65 @@ def draw_samsung_en(width: int = 335) -> np.ndarray:
return arr[ys.min() : ys.max() + 1, xs.min() : xs.max() + 1]
def draw_wan(width: int = 335) -> np.ndarray:
"""Alibaba Wan logo plus "Wan" wordmark, bottom-right, light-glyph class.
Geometry measured on the one real Wan 2.7 Pro export (2048 px square,
2026-09-23): logo 102 x 103 px, a 19 px gap, then "Wan" 147 px wide with a
51 px cap height centered on the logo; 268 x 103 overall. The logo is
approximated as a hexagon with a central triangular hole and three pinwheel
cuts; the wordmark is Avenir Next Demi Bold, the nearest installed geometric sans.
Hole size, cut width and weight were chosen by the best match to the real mark
over a small grid (0.66 vs 0.60-0.65 for the neighbors).
"""
import math
k = width / 268
h = round(103 * k)
im = Image.new("L", (width, h), 0)
d = ImageDraw.Draw(im)
logo = round(102 * k)
cx, cy, radius = logo / 2, h / 2, logo / 2
d.polygon(
[
(cx + radius * math.cos(math.radians(60 * i)), cy + radius * 0.98 * math.sin(math.radians(60 * i)))
for i in range(6)
],
fill=255,
)
inner = [
(
cx + radius * 0.42 * math.cos(math.radians(90 + 120 * i)),
cy + radius * 0.42 * math.sin(math.radians(90 + 120 * i)),
)
for i in range(3)
]
d.polygon(inner, fill=0)
cut = max(2, round(logo * 0.05))
for i, (px, py) in enumerate(inner):
angle = math.radians(90 + 120 * i + 90)
d.line(
[(px, py), (px + radius * 1.2 * math.cos(angle), py + radius * 1.2 * math.sin(angle))], fill=0, width=cut
)
text = "Wan"
probe = ImageDraw.Draw(Image.new("L", (10, 10)))
font = _fit_font(
"/System/Library/Fonts/Avenir Next.ttc",
lambda f: probe.textbbox((0, 0), text, font=f)[3] - probe.textbbox((0, 0), text, font=f)[1] >= round(51 * k),
index=2,
)
bb = probe.textbbox((0, 0), text, font=font)
d.text((round((102 + 19) * k) - bb[0], round(h / 2 - (bb[3] - bb[1]) / 2) - bb[1]), text, font=font, fill=255)
arr = np.array(im)
ys, xs = np.where(arr > 0)
return arr[ys.min() : ys.max() + 1, xs.min() : xs.max() + 1]
_CUSTOM_RENDERERS = {
"catlogo_alpha.png": draw_catlogo,
"microsoft_alpha.png": draw_msbadge,
"samsung_en_alpha.png": draw_samsung_en,
"wan_alpha.png": draw_wan,
}
+8
View File
@@ -283,6 +283,14 @@ def _video_mark_frame(key: str, w: int, h: int) -> np.ndarray:
tmpl, scale, x, y = templates["dola"], 0.036, int(w * 0.70), int(h * 0.88)
elif key == "hailuo":
tmpl, scale, x, y = templates["hailuo"], 0.052, int(w * 0.34), int(h * 0.82)
elif key == "vidu":
# Logo plus wordmark bottom-right, at the measured 1080p geometry: 4.6% of
# the short side tall, ~3% off the right and bottom edges.
tmpl = templates["vidu"]
th = max(8, round(short * 0.046))
tw = max(1, round(tmpl.shape[1] * th / tmpl.shape[0]))
x, y = w - tw - round(short * 0.029), h - th - round(short * 0.033)
return _composite_light(base, cv2.resize(tmpl, (tw, th)).astype(np.float32) / 255.0, x, y, 250)
elif key == "kling":
# The FULL mark: swirl logo left of the text run, flush against the
# bottom-right EDGE. The font arm is edge-gated (region must reach
+1 -1
View File
@@ -74,7 +74,7 @@ _LEGACY_FAMILIES: tuple[tuple[str, tuple[str, ...]], ...] = (
("samsung_genai", ("genaitype", "photoeditor_re_edit")),
("soft_binding", ("soft binding", "soft-binding", "invismark")),
("c2pa_cloud", ("cloud manifest",)),
("hf_job", ("hf-job", "hugging face job")),
("hf_job", ("hf-job", "higgsfield job", "hugging face job")),
("exif_generator", ("exif", "software tag", "png text")),
)