diff --git a/README.md b/README.md index cf27d1f..f752c18 100644 --- a/README.md +++ b/README.md @@ -203,9 +203,9 @@ to one provider. Sora covers the moving Sora 2 mascot and wordmark. Veo covers both the current four-point diamond and the legacy `Veo` text. Seedance covers the fixed boxed `AI` label, Dola covers the fixed `Dola AI` text, Hailuo AI covers the composite -`MINIMAX | hailuo AI` label, and Kling AI covers the bottom-right `KLING AI` -label with its version suffix. A completed encode is published atomically. No -output is written when no stable mark is found. +`MINIMAX | hailuo AI` label, and Kling AI covers the bottom-right `KLING AI` or +`KlingAI` label with its version suffix. A completed encode is published +atomically. No output is written when no stable mark is found. HDR, PQ/HLG, and greater-than-8-bit inputs are rejected before encoding rather than silently reduced through OpenCV's 8-bit BGR boundary. diff --git a/data/README.md b/data/README.md index d4549e0..59706b0 100644 --- a/data/README.md +++ b/data/README.md @@ -7,8 +7,9 @@ data/ fixtures/ provenance/ Real format and provenance fixtures used by tests (source records live in fixtures/README.md) - visible/ Synthetic per-mark example gallery (one committed example per - registered visible mark; see fixtures/visible/README.md) + visible/ Per-mark example gallery: one synthetic canonical example per + registered mark plus documented provider originals where available + (see fixtures/visible/README.md) calibration/ / Minimal controlled inputs needed to rebuild detector assets synthid/ @@ -24,6 +25,11 @@ data/ Reproducible full-clip Gemini SynthID verdicts ``` +[`image-watermark-examples.csv`](image-watermark-examples.csv) and +[`video-watermark-examples.csv`](video-watermark-examples.csv) are the coverage +indexes for real carriers. They point to each canonical binary instead of +duplicating it and record unsupported gaps and source-specific licensing. + ## Storage rules Large local-only research data (the 211 GB Spaces corpus and the frozen diff --git a/data/fixtures/README.md b/data/fixtures/README.md index 47176b6..d453f50 100644 --- a/data/fixtures/README.md +++ b/data/fixtures/README.md @@ -3,6 +3,151 @@ These files exercise real container, metadata, and pixel-provenance formats. They are test inputs, not evaluation corpora. +## OpenRouter metadata survey + +The following files are original byte streams returned by OpenRouter's Image +API on 2026-09-02. Each request used a repository-maintainer account, the same +neutral still-life prompt, one 1:1 output, and no image reference. The prompt +was `A documentary photograph of three smooth river stones on pale linen beside +a small blue glass vase, soft natural daylight, plain uncluttered background, +no text, no logos`. + +| File | Model | Cost | SHA-256 | Expected metadata result | +| --- | --- | --- | --- | --- | +| `recraft-v3.webp` | `recraft/recraft-v3` | USD 0.04 | `e94c99ab50410092dbb601c8269ce812879e2271e18c7880021d2118666542ae` | Valid C2PA; `recraft.ai`; `trainedAlgorithmicMedia` | +| `krea-2-medium-turbo.png` | `krea/krea-2-medium-turbo` | USD 0.015 | `ee1302c01e8da8e316e6563b0f9d36d511b9048fb2f70b8851eeeda98c5fb6a0` | No locally readable provenance | + +Both were paid API generations. Recraft grants ownership for paid-plan output, +and Krea states that users own compliant outputs. They are distributed under +the repository license. + +- Recraft terms: +- Krea terms: + +The same survey generated `sourceful/riverflow-v2-fast` output and found no +locally readable metadata. It is not committed because Riverflow's terms +restrict redistribution and using results to test or improve machine-learning +systems. Its measured SHA-256 was +`d4d7e80a3ed450129a0e77bdf330a83eb7de1e287d5b2b737cd26a011194a270`; +OpenRouter recorded a USD 0.02 request cost. + +## WaveSpeedAI metadata survey + +The following measurements came from original byte streams returned by the +WaveSpeedAI API on 2026-09-03. Each request used a repository-maintainer +account and the same neutral still-life prompt recorded above. The account +balance moved from USD 12.00 to USD 11.82 for the four successful requests. + +| File | WaveSpeedAI model | SHA-256 | Expected metadata result | +| --- | --- | --- | --- | +| `qwen-image.png` | `wavespeed-ai/qwen-image/text-to-image` | `5b6d832834397498d0587b9b2536f88c75e923195c43c2b71d9f7f3c5422b4f1` | No provenance signal; one non-provenance PNG `job-id` text field | +| `hunyuan-image-3.png` | `wavespeed-ai/hunyuan-image-3` | `6ae2ac073ac79b02b0934c905364f200420c2ff60fea232bd9e0a5fca785cc09` | Valid C2PA from `fal.ai` naming `fal-ai/hunyuan-image`, plus Adobe TrustMark Variant P, schema 0 | +| `kling-image-v3.png` | `kwaivgi/kling-image-v3/text-to-image` | `501ab865b47ba59b64bc0f68118300744b0d5c7be2fa4e2ecbb3a78de5d8a82b` | TC260 PNG `AIGC` field naming producer `001191110108335469089C10100`; no visible Kling mark | +| `seedream-v4.jpg` | `bytedance/seedream-v4` | `9ec6af1fc0509d74e264b9631a6cddd2ce7ce20146347fe418e9306032beb759` | No locally readable provenance | + +All four original byte streams are committed. Section 11.1 of WaveSpeedAI's +terms assigns its rights in Customer Data, explicitly including Outputs, to the +customer. The live catalog records used for these requests expose no additional +license, terms URL, attribution requirement, territory restriction, or +commercial-use restriction. The maintainer therefore distributes these paid, +neutral-prompt outputs under the repository license. This conclusion applies +to these WaveSpeedAI API transactions; it does not import unrelated terms from +the vendors' direct consumer services. + +- WaveSpeedAI terms: +- WaveSpeedAI commercial-use policy: +- Qwen-Image license: + +## Qwen Create consumer output + +`qwen-create-qwen-image-2.png` is the original 2048 x 2048 PNG downloaded from +an authenticated Qwen Create generation on 2026-09-03. It was generated by +`Qwen-Image 2.0` at 1:1 using the neutral still-life prompt recorded above. +The current global consumer surface differs from the supported Chinese Qwen +text mark: it places a small Qwen symbol at the bottom right, not +`千问AI生成`. The existing Qwen text detector therefore correctly does not +claim this new shape (measured strict confidence 0.178). + +- SHA-256: `60c39ed8aedb081193c62289be549785e74d31fc8d071961003fabd74712429d` +- Expected metadata signal: TC260 AIGC label +- Expected `ContentProducer`: `001191440101MA9Y9T4H7A00000` +- Expected visible result: no registered visible mark; the Qwen symbol is a + newly observed, currently unsupported variant +- License: repository license; generated and explicitly cleared by the + maintainer +- Qwen Create terms: + +## Open DWT-DCT + +`sdxl-dwt-dct.png` is a real AI-generated carrier with the public 48-bit SDXL +watermark embedded by ShieldMnt's `invisible-watermark` encoder using its +`dwtDct` method. The carrier is the original `kling-image-v3.png` WaveSpeedAI +output documented above, not a procedurally generated test texture. Encoding +was lossless to PNG and removed the source container metadata; the resulting +fixture contains only the expected open DWT-DCT signal. + +- Source carrier SHA-256: `501ab865b47ba59b64bc0f68118300744b0d5c7be2fa4e2ecbb3a78de5d8a82b` +- Fixture SHA-256: `9ec3dcbfea19e4651b898f98ada28a1c81f054d63621a0bba2d484aa77da251a` +- Payload: `0b101100111110110010010000011110111011000110011110` +- Expected signal: open DWT-DCT, `Stable Diffusion XL` +- Verification: 48/48 payload bits recovered by both the upstream decoder and + the in-tree decoder; `identify` reports the expected high-confidence signal +- Encoder: +- License: repository license; the encoder is MIT-licensed + +## Microsoft InvisMark + +### Microsoft Paint + +`microsoft-paint-invismark.png` is the original byte stream supplied directly +by Xusheng Li on 2026-09-02 after his reverse-engineering research documented +Paint's pixel watermark and its matching C2PA soft binding. He authorized public +redistribution of the fixture under the MIT license. The neutral image was +generated by Microsoft Paint from the prompt `a cobalt blue circle above a tiny +orange square`. + +- Source research: +- SHA-256: `74eda22d210d534fcb5b75bedf8acd3bd110b48a95f4b9e546cd34f546effc05` +- Dimensions: 1254 x 1254 RGB PNG +- Expected signal: C2PA soft binding `com.microsoft.invismark.1` +- Expected watermark ID: `a4ed421e-7c37-4409-998e-f8788c564572` +- Expected platform: `Microsoft (Copilot / Designer)` +- Expected C2PA result: valid asset binding and signature +- License: MIT, reproduced in `../licenses/xusheng-li-paint-invismark-MIT.txt` + +### Microsoft MAI-Image through OpenRouter + +`microsoft-invismark.png` is the original byte stream returned by OpenRouter's +Image API for `microsoft/mai-image-2.5-pro` on 2026-09-02. The request used a +repository-maintainer account and asked for one 1:1 image. OpenRouter routed +the request to Microsoft Azure. Microsoft states that it does not claim +ownership of AI-service output; the maintainer distributes this output under +the repository license. + +- SHA-256: `be27ac7c351e7f381dc69e140ca3611ecd9db81e40926c68050ceffdd0ce6c04` +- Expected signal: C2PA soft binding `com.microsoft.invismark.1` +- Expected watermark ID: `19b070a1-23f0-40eb-826c-cade306337eb` +- Expected platform: `Microsoft (Copilot / Designer)` +- Request cost recorded by OpenRouter: USD 0.110757 +- License: repository license +- Terms: + +### Microsoft MAI-Image through Bing Image Creator + +`microsoft-bing-image-creator.jpg` is the original download produced without +sign-in by Microsoft's public Bing Image Creator on 2026-09-03. The page named +the model `MAI-Image-2.5-Flash`; it used the neutral still-life prompt recorded +above and the 3:2 aspect ratio. The downloaded image has no visible AI badge, +so it does not close the separate visible Microsoft-badge fixture request. + +- SHA-256: `55cad5355b6eeeb25f84ea074f85c0ea55b82d63c27f43e3429bb75f28734ac7` +- Dimensions: 1248 x 832 RGB JPEG +- Expected signal: valid C2PA plus soft binding `com.microsoft.invismark.1` +- Expected watermark ID: `aec9a34e-665f-4549-9c02-9d29337a5de6` +- Expected platform: `Microsoft (Copilot / Designer)` +- License: repository license +- Terms: + ## Adobe TrustMark `adobe-trustmark-p.png` is Adobe's official `images/ghost_P.png` Variant P diff --git a/data/fixtures/provenance/hunyuan-image-3.png b/data/fixtures/provenance/hunyuan-image-3.png new file mode 100644 index 0000000..7c14d1f Binary files /dev/null and b/data/fixtures/provenance/hunyuan-image-3.png differ diff --git a/data/fixtures/provenance/kling-image-v3.png b/data/fixtures/provenance/kling-image-v3.png new file mode 100644 index 0000000..89eb36c Binary files /dev/null and b/data/fixtures/provenance/kling-image-v3.png differ diff --git a/data/fixtures/provenance/krea-2-medium-turbo.png b/data/fixtures/provenance/krea-2-medium-turbo.png new file mode 100644 index 0000000..11e68b6 Binary files /dev/null and b/data/fixtures/provenance/krea-2-medium-turbo.png differ diff --git a/data/fixtures/provenance/microsoft-bing-image-creator.jpg b/data/fixtures/provenance/microsoft-bing-image-creator.jpg new file mode 100644 index 0000000..252ccf1 Binary files /dev/null and b/data/fixtures/provenance/microsoft-bing-image-creator.jpg differ diff --git a/data/fixtures/provenance/microsoft-invismark.png b/data/fixtures/provenance/microsoft-invismark.png new file mode 100644 index 0000000..05db1b2 Binary files /dev/null and b/data/fixtures/provenance/microsoft-invismark.png differ diff --git a/data/fixtures/provenance/microsoft-paint-invismark.png b/data/fixtures/provenance/microsoft-paint-invismark.png new file mode 100644 index 0000000..32f1dc5 Binary files /dev/null and b/data/fixtures/provenance/microsoft-paint-invismark.png differ diff --git a/data/fixtures/provenance/qwen-create-qwen-image-2.png b/data/fixtures/provenance/qwen-create-qwen-image-2.png new file mode 100644 index 0000000..f790774 Binary files /dev/null and b/data/fixtures/provenance/qwen-create-qwen-image-2.png differ diff --git a/data/fixtures/provenance/qwen-image.png b/data/fixtures/provenance/qwen-image.png new file mode 100644 index 0000000..ed0be9d Binary files /dev/null and b/data/fixtures/provenance/qwen-image.png differ diff --git a/data/fixtures/provenance/recraft-v3.webp b/data/fixtures/provenance/recraft-v3.webp new file mode 100644 index 0000000..3eeb953 Binary files /dev/null and b/data/fixtures/provenance/recraft-v3.webp differ diff --git a/data/fixtures/provenance/sdxl-dwt-dct.png b/data/fixtures/provenance/sdxl-dwt-dct.png new file mode 100644 index 0000000..9090d63 Binary files /dev/null and b/data/fixtures/provenance/sdxl-dwt-dct.png differ diff --git a/data/fixtures/provenance/seedream-v4.jpg b/data/fixtures/provenance/seedream-v4.jpg new file mode 100644 index 0000000..8dda638 Binary files /dev/null and b/data/fixtures/provenance/seedream-v4.jpg differ diff --git a/data/fixtures/visible/README.md b/data/fixtures/visible/README.md index a7f0694..2abe0eb 100644 --- a/data/fixtures/visible/README.md +++ b/data/fixtures/visible/README.md @@ -17,6 +17,154 @@ The examples demonstrate DETECTION geometry and house style, not vendor raster fidelity; real-world variants (fonts, opacities, sizes) are covered by the engines' recorded calibration evidence. +The Qwen, Kling, Yuanbao, Baidu, Jimeng, LiblibAI, Microsoft, Sora, Hailuo, and Veo +directories additionally contain real provider-marked sources. These files +retain the source bytes and names used here are deliberately distinct from the +generated canonical examples. The current LiblibAI output is evidence of a +provider format change, not a positive example for the registered historical +wordmark detector: + +| File | Source | SHA-256 | Expected detector | +| --- | --- | --- | --- | +| `qwen/provider-original.png` | OpenNoMark `examples/qwen/image_324855086256596.png`, commit `9455d2d` | `dce9e17c9b9b0483c672b71183b6eb5562f8205bb39a6da14451e07db13b1a50` | Qwen, confidence 0.73 | +| `kling/provider-original-direct.png` | Authenticated Kling AI IMAGE 3.0 generation, downloaded 2026-09-04 | `aefbf69a99cf7eb4211cc0513c6979b81731107a1bc605c972e01900b7c56f51` | Kling, confidence 0.43 | +| `yuanbao/provider-original.png` | Authenticated Tencent Yuanbao AI Image generation, downloaded 2026-09-05 | `e4fd856565d0434ef06deb78ff2088e597c4fec98640ad737213c7c0a917c82d` | Yuanbao, confidence 0.49 | +| `baidu/provider-original.jpeg` | OpenNoMark `examples/baidu/baidu_01.jpeg`, commit `9455d2d` | `52852c528f1a7b8918b665b2fcf883737ecc5016481d9c7683ebe07e152cc9d2` | Baidu, confidence 0.66 | +| `jimeng_pill/provider-published-example.jpg` | JPEG embedded on page 29 of Zhonglun W&D's 2026 *Bank Finance Legal Newsletter* | `1182fba74d3753785345f8a35d09a8c9b52ea8383ebe9ee999d0d027437f03fd` | Jimeng AI pill, confidence 0.28; Jimeng wordmark, confidence 0.61 | +| `liblib/current-provider-output.png` | Authenticated LiblibAI Smart Image V2 generation, downloaded 2026-09-05 | `60398d4f06fd50e047d3321c62405dae0a1c4a7070e77ac5e0548c516257d2e3` | No `liblib` detection: current top-left `AI生成` pill differs from the registered bottom-center `LiblibAI` wordmark | +| `microsoft/provider-original.png` | Authenticated Microsoft Copilot generation, downloaded 2026-09-03 | `0e76fb8aa0540b18fee4d63ac4b5fd63c7eee310474e896d21d27e668403afe7` | Microsoft, confidence 0.54 | +| `sora/provider-frame.jpg` | Sora 2 video frame from LLinked's Apache-2.0 `sora-watermark-dataset` | `bbcf70b737c30568668572a5bb23e70c52c9687808b0714ca98f9fac073f12c4` | Sora frame localization, confidence 0.69 | +| `sora/provider-original.mp4` | LLinked's Apache-2.0 `SoraWatermarkCleaner` fixture `resources/puppies.mp4`, commit `ad35ce9` | `7dd8a9d43d43cd5b901946c7ba14e8018e18d47e75f0f774157327ba7e9181c5` | Sora, 300/300 stable frames; Sora C2PA claim | +| `hailuo/provider-original.mp4` | Authenticated Hailuo AI MiniMax H3 generation, downloaded with `With Watermark` selected on 2026-09-03 | `6152609e0339c443af79e244440e1b1050f3c615a50f76bfaf6146584e68142f` | Hailuo, 124/124 stable frames | +| `kling/provider-original.mp4` | Authenticated Kling AI VIDEO 3.0 generation, downloaded 2026-09-04 | `f3e8d6a765fca699036d68fde28eb16f7aed97b6702f06f318e61ae8d4e53a69` | Kling, 121/121 stable frames | +| `veo/provider-original.mp4` | Authenticated Google Flow Veo 3.1 Fast generation with `Visible watermarking` enabled, downloaded 2026-09-03 | `e778194b545e34dafd2d5be58691031a070862890c41e6c527a652a114a7c7a4` | Veo, 192/192 stable frames | + +OpenNoMark describes its corpus as real-image examples and distributes the +repository under CC BY-NC-SA 4.0. Its license text is reproduced in +`../../licenses/open-no-mark-CC-BY-NC-SA-4.0.txt`. The upstream repository does +not record per-image authorship beyond that repository-level license and claim. +The Qwen and Baidu files remain under that license and are not offered under +this repository's Apache-2.0 license. + +The Microsoft file is the original 1024 x 1024 PNG downloaded from an +authenticated Copilot image-generation conversation. It uses the neutral +still-life prompt recorded in `../README.md`, carries the visible top-right +`Made with AI` pill, valid Microsoft C2PA, and InvisMark soft-binding ID +`975290d4-9c2f-4359-ad5a-92c5b5b850a4`. Microsoft does not claim ownership of +AI-service output; the maintainer distributes this generated output under the +repository license. + +The Sora frame is the original JPEG frame +`images/train/images/failed_image_000016_frame_000150.jpg` from LLinked's +`sora-watermark-dataset`. The dataset card describes its images as frames +extracted from Sora-generated videos, provides manual watermark boxes, and +licenses the dataset under Apache-2.0. This neutral CGI frame carries the +current moving mascot-and-wordmark raster and passes the in-tree frame +localizer. + +The Sora MP4 is the byte-for-byte `resources/puppies.mp4` fixture tracked by +LLinked's Apache-2.0 `SoraWatermarkCleaner` at commit `ad35ce9`. It preserves +the moving mascot-and-wordmark raster, timing, audio, and the source C2PA +manifest. The in-tree video path detects Sora on all 300 frames. The C2PA claim +names Sora as the generator and OpenAI/Truepic as the issuer; its content +integrity and signature verify, while the reader reports the signer certificate +as expired and untrusted. + +- Sora watermark dataset: + +- SoraWatermarkCleaner source fixture: + + +The Hailuo file is the original 2944 x 1248, 5-second MP4 downloaded from an +authenticated Hailuo AI generation. It was generated with MiniMax H3 for 60 +top-up credits from the neutral prompt `A blue ceramic mug slowly rotates on a +plain light gray studio table, soft even lighting, fixed camera, clean +background.` The download menu explicitly offered clean and `With Watermark` +variants; this fixture is the latter. It carries the visible lower-right +`MINIMAX | hailuo AI` composite and a TC260 AIGC label naming MiniMax as the +producer. The maintainer generated and explicitly cleared this output for use +under the repository license. + +- Hailuo AI paid-service and credit terms: + + +The Kling video is the original 1280 x 720, 5-second MP4 generated with VIDEO +3.0 for 30 credits at 720p without native audio. Its neutral prompt was `A +locked-off wide shot of a small white paper boat floating across a calm +dark-blue pond at dusk, gentle ripples, realistic cinematic lighting, no text, +no logos.` It carries the fixed lower-right `KlingAI 3.0` logo and no locally +readable provenance metadata. The maintainer generated and explicitly cleared +the output for this noncommercial deterministic computer-vision regression +fixture. Kling AI's terms say the user owns applicable rights in the output, +require a regular account to retain Kling branding when distributing it, and +prohibit commercial use without written permission. The fixture retains the +brand and remains subject to those terms; it is not offered under the +repository license. + +- Kling AI terms of service: +- Kling AI paid-service terms: + +The direct Kling image is the original 1024 x 1024 RGB PNG generated with +IMAGE 3.0 in 1K SD mode for one credit. Its neutral prompt was `A documentary +photograph of three smooth river stones on pale linen beside a small blue glass +vase, soft natural daylight, plain uncluttered background, no text, no logos.` +It carries the current lower-right `KlingAI 3.0` mark and a TC260 AIGC field +whose producer code `001191110108335469089C10100` normalizes to Kuaishou's +registered USCC. The maintainer generated and explicitly cleared the output for +this noncommercial deterministic computer-vision regression fixture. The same +Kling terms and retained-brand restriction listed for the video apply; the file +is not offered under the repository license. + +The Yuanbao image is the original 1776 x 1328 RGB PNG returned by an +authenticated AI Image conversation. It was generated at no charge from the +neutral prompt `A documentary photograph of a blue ceramic mug on a plain +light-gray studio table, soft even lighting, no text, no logos, no people.` It +carries the visible lower-right two-line `元宝` / `AI生成` mark and a TC260 AIGC +field naming Tencent's registered producer. The maintainer generated and +explicitly cleared this output for use under the repository license. + +The Jimeng image is the byte-for-byte embedded JPEG extracted with +`pdfimages` from page 29 of Zhonglun W&D's publicly distributed 2026 *Bank +Finance Legal Newsletter*. It is a real Jimeng-generated debt-collection +poster carrying both the top-left `AI生成` pill and the lower-right `即梦AI` +wordmark. The repository retains one 705 x 705 image rather than the complete +33-page publication, solely to test identification and removal of the two +marks. Copyright remains with the source rights holders; this research fixture +is not licensed under Apache-2.0. See +`../../licenses/zhonglunwende-jimeng-fair-use.txt` for provenance and use +limitations. + +- Zhonglun W&D source publication: + + +The LiblibAI image is the original 2048 x 2048 RGBA PNG generated with Smart +Image V2 for 18 introductory points from the same neutral prompt. It carries a +top-left `AI生成` pill and a TC260 AIGC field whose producer code contains +LiblibAI's registered USCC, but it does not carry the historical bottom-center +`LiblibAI` wordmark that the registered `liblib` engine detects. This file +therefore records the current provider behavior without falsely closing the +historical positive-example gap. The maintainer generated and explicitly +cleared the output for use under the repository license. + +- LiblibAI user agreement (generated-output ownership and watermark choices): + + +The Veo file is the original 1280 x 720, 8-second MP4 downloaded from an +authenticated Google Flow project. It was generated with Veo 3.1 Fast for 20 +Flow credits from the neutral prompt `A blue mug rotates on gray. Static camera. +No people, text, logos, or audio.` after the account's `Visible watermarking` +setting was enabled. It carries the fixed, low-contrast `Veo` text at the bottom +right, valid Google C2PA, and an invisible SynthID marker. Google documents that +Flow clips can be downloaded and shared, states that Google does not claim +ownership of original generated content, and explicitly identifies SynthID in +all Flow video output. The maintainer distributes this generated output under +the repository license. + +- Google Flow output and watermark documentation: + +- Google Flow download and sharing documentation: + + ## Regeneration uv run python scripts/render_visible_examples.py @@ -27,12 +175,14 @@ detect on its own example, so regeneration is the fix point for drift. ## Layout /example.png 1536x2048..2048x2048 PNG, one per image mark + /provider-original.* + Documented real provider-marked source where available /example.mp4 960x540 90-frame clip, one per video mark (kling carries both: it is registered in both registries) Special cases: `gemini` composites the sparkle alpha map at the provider's -configured position; `jimeng_pill` is the capture-less pill at the measured -3:4 portrait geometry; `microsoft` is the opaque white pill with dark text +configured position; `jimeng_pill` uses the measured 3:4 portrait geometry; +`microsoft` is the opaque white pill with dark text holes (the discriminator its detector keys on). Video examples composite the detector's own synthetic template on every frame; where two marks share a shape family the example carries the discriminative variant (`veo` the legacy diff --git a/data/fixtures/visible/baidu/provider-original.jpeg b/data/fixtures/visible/baidu/provider-original.jpeg new file mode 100644 index 0000000..86010ce Binary files /dev/null and b/data/fixtures/visible/baidu/provider-original.jpeg differ diff --git a/data/fixtures/visible/hailuo/provider-original.mp4 b/data/fixtures/visible/hailuo/provider-original.mp4 new file mode 100644 index 0000000..72bba72 Binary files /dev/null and b/data/fixtures/visible/hailuo/provider-original.mp4 differ diff --git a/data/fixtures/visible/jimeng_pill/provider-published-example.jpg b/data/fixtures/visible/jimeng_pill/provider-published-example.jpg new file mode 100644 index 0000000..80bdbcd Binary files /dev/null and b/data/fixtures/visible/jimeng_pill/provider-published-example.jpg differ diff --git a/data/fixtures/visible/kling/provider-original-direct.png b/data/fixtures/visible/kling/provider-original-direct.png new file mode 100644 index 0000000..72b60d1 Binary files /dev/null and b/data/fixtures/visible/kling/provider-original-direct.png differ diff --git a/data/fixtures/visible/kling/provider-original.mp4 b/data/fixtures/visible/kling/provider-original.mp4 new file mode 100644 index 0000000..3a7187b Binary files /dev/null and b/data/fixtures/visible/kling/provider-original.mp4 differ diff --git a/data/fixtures/visible/liblib/current-provider-output.png b/data/fixtures/visible/liblib/current-provider-output.png new file mode 100644 index 0000000..9eb8f7d Binary files /dev/null and b/data/fixtures/visible/liblib/current-provider-output.png differ diff --git a/data/fixtures/visible/microsoft/provider-original.png b/data/fixtures/visible/microsoft/provider-original.png new file mode 100644 index 0000000..ca9c98d Binary files /dev/null and b/data/fixtures/visible/microsoft/provider-original.png differ diff --git a/data/fixtures/visible/qwen/provider-original.png b/data/fixtures/visible/qwen/provider-original.png new file mode 100644 index 0000000..dc3305e Binary files /dev/null and b/data/fixtures/visible/qwen/provider-original.png differ diff --git a/data/fixtures/visible/sora/provider-frame.jpg b/data/fixtures/visible/sora/provider-frame.jpg new file mode 100644 index 0000000..d57593b Binary files /dev/null and b/data/fixtures/visible/sora/provider-frame.jpg differ diff --git a/data/fixtures/visible/sora/provider-original.mp4 b/data/fixtures/visible/sora/provider-original.mp4 new file mode 100644 index 0000000..2ceb2d4 Binary files /dev/null and b/data/fixtures/visible/sora/provider-original.mp4 differ diff --git a/data/fixtures/visible/veo/provider-original.mp4 b/data/fixtures/visible/veo/provider-original.mp4 new file mode 100644 index 0000000..c6f0731 Binary files /dev/null and b/data/fixtures/visible/veo/provider-original.mp4 differ diff --git a/data/fixtures/visible/yuanbao/provider-original.png b/data/fixtures/visible/yuanbao/provider-original.png new file mode 100644 index 0000000..45bb3a5 Binary files /dev/null and b/data/fixtures/visible/yuanbao/provider-original.png differ diff --git a/data/image-watermark-examples.csv b/data/image-watermark-examples.csv new file mode 100644 index 0000000..0b2a897 --- /dev/null +++ b/data/image-watermark-examples.csv @@ -0,0 +1,19 @@ +key,provider,watermark_type,path,status,verification,license +gemini,Google Gemini,visible sparkle,data/calibration/gemini/gemini_black_2048.png,available,original provider capture and detector calibration,repository +doubao,ByteDance Doubao,visible text,data/calibration/doubao/doubao_gray_1x1_1.png,available,original provider capture and detector calibration,repository +jimeng,ByteDance Jimeng,visible wordmark,data/calibration/jimeng/jimeng_cap_C.png,available,original provider capture and detector calibration,repository +qwen,Alibaba Qwen consumer app,visible text,data/fixtures/visible/qwen/provider-original.png,available,detected by Qwen engine; distributed under the OpenNoMark repository's CC BY-NC-SA 4.0 license; current global Qwen Create uses a different symbol captured in provenance fixtures,CC-BY-NC-SA-4.0; excluded from Apache-2.0 +kling,Kuaishou Kling AI,visible text,data/fixtures/visible/kling/provider-original-direct.png,available,authenticated Kling IMAGE 3.0 original; Latin KlingAI 3.0 mark detected at confidence 0.43; TC260 producer maps to Kuaishou,subject to Kling AI terms; noncommercial fixture +yuanbao,Tencent Yuanbao,visible two-line text,data/fixtures/visible/yuanbao/provider-original.png,available,authenticated Yuanbao original; detected by Yuanbao engine at confidence 0.49; TC260 producer metadata maps to Tencent,repository +samsung,Samsung Galaxy AI,visible Italian text,data/calibration/samsung/samsung_gray_1.png,available,original provider capture and detector calibration,repository +runninghub,RunningHub,visible text,,missing,no redistributable provider original found, +baidu,Baidu,visible text and tag,data/fixtures/visible/baidu/provider-original.jpeg,available,detected by Baidu engine; distributed under the OpenNoMark repository's CC BY-NC-SA 4.0 license,CC-BY-NC-SA-4.0; excluded from Apache-2.0 +liblib,LiblibAI,visible wordmark,data/fixtures/visible/liblib/current-provider-output.png,partial,"authenticated current LiblibAI original carries a top-left AI生成 pill rather than the registered historical bottom-center LiblibAI wordmark; TC260 producer metadata maps to LiblibAI; historical positive remains missing",repository +microsoft,Microsoft image output,visible top-right AI badge,data/fixtures/visible/microsoft/provider-original.png,available,authenticated Copilot original detected by Microsoft engine at confidence 0.54; also has valid C2PA and InvisMark,repository +jimeng_pill,ByteDance Jimeng,visible AI pill,data/fixtures/visible/jimeng_pill/provider-published-example.jpg,available,"real Jimeng output embedded in page 29 of Zhonglun W&D's 2026 Bank Finance Legal Newsletter; direct jimeng_pill detection confidence 0.28; the same image also carries the Jimeng wordmark",fair-use research fixture; excluded from Apache-2.0 +google_synthid,Google,image SynthID,data/synthid/originals/Gemini_Generated_Image_633uuy633uuy633u.png,available,provider oracle recorded in data/synthid/manifest.csv,repository +openai_synthid,OpenAI,image SynthID,"data/synthid/originals/ChatGPT Image May 30, 2026, 10_31_08 AM.png",available,provider oracle recorded in data/synthid/manifest.csv,repository +meta_content_seal,Meta Muse Image,image Content Seal,data/contentseal/originals/gen_fox_forest.webp,available,provider oracle recorded in data/contentseal/manifest.csv,repository +microsoft_invismark,Microsoft Paint,image InvisMark,data/fixtures/provenance/microsoft-paint-invismark.png,available,"original Paint output supplied by Xusheng Li; valid C2PA names com.microsoft.invismark.1 and watermark ID a4ed421e-7c37-4409-998e-f8788c564572",MIT +open_dwt_dct,Stable Diffusion / SDXL / FLUX workflows,open DWT-DCT,data/fixtures/provenance/sdxl-dwt-dct.png,available,real AI-generated carrier with the official SDXL payload recovered 48/48 by upstream and in-tree decoders,repository +adobe_trustmark_p,Adobe,image TrustMark Variant P,data/fixtures/provenance/adobe-trustmark-p.png,available,official Adobe example and expected schema recorded in data/fixtures/README.md,MIT diff --git a/data/licenses/open-no-mark-CC-BY-NC-SA-4.0.txt b/data/licenses/open-no-mark-CC-BY-NC-SA-4.0.txt new file mode 100644 index 0000000..b5f2625 --- /dev/null +++ b/data/licenses/open-no-mark-CC-BY-NC-SA-4.0.txt @@ -0,0 +1,439 @@ +Attribution-NonCommercial-ShareAlike 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International +Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-NonCommercial-ShareAlike 4.0 International Public License +("Public License"). To the extent this Public License may be +interpreted as a contract, You are granted the Licensed Rights in +consideration of Your acceptance of these terms and conditions, and the +Licensor grants You such rights in consideration of benefits the +Licensor receives from making the Licensed Material available under +these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-NC-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution, NonCommercial, and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. NonCommercial means not primarily intended for or directed towards + commercial advantage or monetary compensation. For purposes of + this Public License, the exchange of the Licensed Material for + other material subject to Copyright and Similar Rights by digital + file-sharing or similar means is NonCommercial provided there is + no payment of monetary compensation in connection with the + exchange. + + l. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + m. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + n. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part, for NonCommercial purposes only; and + + b. produce, reproduce, and Share Adapted Material for + NonCommercial purposes only. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the terms and conditions of the Adapter's License + You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties, including when + the Licensed Material is used other than for NonCommercial + purposes. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-NC-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of databases for NonCommercial purposes + only; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + including for purposes of Section 3(b); and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/data/licenses/xusheng-li-paint-invismark-MIT.txt b/data/licenses/xusheng-li-paint-invismark-MIT.txt new file mode 100644 index 0000000..7a97114 --- /dev/null +++ b/data/licenses/xusheng-li-paint-invismark-MIT.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Xusheng Li + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/data/licenses/zhonglunwende-jimeng-fair-use.txt b/data/licenses/zhonglunwende-jimeng-fair-use.txt new file mode 100644 index 0000000..8cbb772 --- /dev/null +++ b/data/licenses/zhonglunwende-jimeng-fair-use.txt @@ -0,0 +1,22 @@ +Jimeng published-image research fixture +======================================= + +File: + data/fixtures/visible/jimeng_pill/provider-published-example.jpg + +Source: + Zhonglun W&D, Bank Finance Legal Newsletter, 2026, page 29 + https://www.zhonglunwende.com/uploads/20260202/97f9a8f64892eac6b7e04e5e7ea45d0e.pdf + +Extraction: + The file is the JPEG image embedded in the source PDF, extracted byte-for-byte + with pdfimages. SHA-256: + 1182fba74d3753785345f8a35d09a8c9b52ea8383ebe9ee999d0d027437f03fd + +Use in this repository: + This single image is retained as a minimal research and interoperability + fixture for identifying and removing the visible Jimeng AI-generation marks. + It is not offered under this repository's Apache-2.0 license. Copyright and + all other rights remain with their respective holders. No license for uses + outside the repository's research, testing, criticism, and documentation is + granted here. diff --git a/data/video-watermark-examples.csv b/data/video-watermark-examples.csv new file mode 100644 index 0000000..55864b8 --- /dev/null +++ b/data/video-watermark-examples.csv @@ -0,0 +1,8 @@ +key,provider,watermark_type,path,status,verification,license +sora,OpenAI Sora,visible moving mascot and wordmark,data/fixtures/visible/sora/provider-original.mp4,available,"Apache-2.0 upstream original; detected by Sora engine on 300/300 frames; C2PA claim names Sora and OpenAI/Truepic, with valid integrity and signature but an expired untrusted signer certificate",Apache-2.0 +veo,Google Veo,visible fixed text,data/fixtures/visible/veo/provider-original.mp4,available,"authenticated Google Flow Veo 3.1 Fast original; detected by Veo engine on 192/192 frames; valid C2PA and provider-confirmed SynthID",repository +seedance,ByteDance Seedance,visible boxed AI label,,missing,"OpenRouter Seedance 2.0 Mini with watermark=true produced BytePlus C2PA but no visible boxed AI label; direct consumer original still needed", +doubao,ByteDance Doubao,visible fixed text,,missing,no redistributable provider original found, +dola,ByteDance Dola,visible fixed text,,missing,no redistributable provider original found, +hailuo,MiniMax Hailuo AI,visible composite label,data/fixtures/visible/hailuo/provider-original.mp4,available,"authenticated Hailuo AI MiniMax H3 original downloaded with With Watermark selected; detected by Hailuo engine on 124/124 frames; TC260 AIGC label names MiniMax",repository +kling,Kuaishou Kling AI,visible fixed logo and wordmark,data/fixtures/visible/kling/provider-original.mp4,available,"authenticated Kling AI VIDEO 3.0 original; detected by Kling engine on 121/121 frames; no locally readable provenance metadata",Kling AI terms; noncommercial fixture diff --git a/docs/chroma1-engine-research.md b/docs/chroma1-engine-research.md index 34e333b..d594881 100644 --- a/docs/chroma1-engine-research.md +++ b/docs/chroma1-engine-research.md @@ -112,8 +112,9 @@ one observed cross-source spread): Derived by the shipped rule: 0.08 + (0.08 - 0.04) = 0.12, rounded to the measured rung **0.125**, which was then oracle-verified clean on both worst sources. Oracle: the public `ai.azure.com/nextgen/validate` page; its - `Inconclusive` verdict is the site's watermark-negative, weaker than an - API-level `Watermark: false`, the same caveat the qwen cohort carries. + `Inconclusive` verdict is the site's clean outcome and provides the provider + oracle used for this certification. The API exposes a more detailed report, + not a different class of certification. ## Fidelity at the floors (scripts/fidelity_metrics.py) diff --git a/docs/code-provenance.md b/docs/code-provenance.md index eb5de34..0edba9d 100644 --- a/docs/code-provenance.md +++ b/docs/code-provenance.md @@ -19,3 +19,8 @@ This page records notices required by source dependencies and licensed derivativ TrustMark Variant P example, licensed under MIT. Its source commit, digest, and reproduced license are recorded beside the fixture in `data/fixtures/README.md` and `data/licenses/adobe-trustmark-MIT.txt`. +- `data/fixtures/provenance/microsoft-paint-invismark.png` is an original + Microsoft Paint output supplied by Xusheng Li and licensed by him under MIT. + Its source research, digest, expected signal, and reproduced license are + recorded in `data/fixtures/README.md` and + `data/licenses/xusheng-li-paint-invismark-MIT.txt`. diff --git a/docs/known-limitations.md b/docs/known-limitations.md index 83b9d1a..03855e8 100644 --- a/docs/known-limitations.md +++ b/docs/known-limitations.md @@ -373,9 +373,9 @@ supported AI provenance metadata without transcoding streams. `video visible` and `remove_video_visible` additionally support the moving Sora 2 mascot and wordmark, the current Veo four-point diamond, the legacy `Veo` text, the Seedance boxed `AI` label, the fixed `Dola AI` text, the Hailuo AI -MINIMAX/Hailuo AI composite label, and the bottom-right Kling AI label with its -version suffix. Detection requires a recurring visual candidate across -adjacent frames. Fixed-mark candidates must remain anchored rather than +MINIMAX/Hailuo AI composite label, and the bottom-right Kling AI `KLING AI` or +`KlingAI` label with its version suffix. Detection requires a recurring visual +candidate across adjacent frames. Fixed-mark candidates must remain anchored rather than drifting with a scene object. Kling AI also requires a bright low-saturation candidate near the expected frame edge. Provider provenance can recover low-contrast runs only after visual evidence exists for the marks that define a diff --git a/docs/module-internals.md b/docs/module-internals.md index 17ece90..bf69d47 100644 --- a/docs/module-internals.md +++ b/docs/module-internals.md @@ -333,7 +333,7 @@ synthetic silhouettes for the current four-point diamond and legacy `Veo` text. Seedance uses a synthetic rounded boxed-`AI` silhouette, while Dola uses an OpenCV-font `Dola AI` silhouette. Hailuo AI uses a synthetic waveform, MINIMAX/Hailuo AI text, separator, and ring. Kling AI combines synthetic font -variants with a ring approximation of its swirl; the logo path rescues +and capitalization variants with a ring approximation of its swirl; the logo path rescues wordmarks whose version or font differs, while the edge and white-label gates reject recurring scene texture. All fixed-mark searches are bounded to the expected lower-frame area and calibrated independently. A strong relocated Veo @@ -345,7 +345,7 @@ normalized representations across all detectors, and caches resized synthetic template features for the fixed stream geometry. Provider confidence scales are not comparable: selection applies each provider's temporal arbiter and takes the first stable result in specificity order (`sora`, `veo`, `seedance`, -`dola`, `hailuo`, `kling`). An explicit mark uses the same scan path with one +`doubao`, `dola`, `hailuo`, `kling`). An explicit mark uses the same scan path with one candidate. Removal also collects authoritative per-frame timestamps for the encoder, while identification omits that unused ffprobe pass. @@ -1199,8 +1199,8 @@ the platform sentence `identify` reports for it, and the metadata signals that confirm its vendor. `identify._VISIBLE_MARK_PLATFORM` and the signal mapping in `api.visible_provenance` are derived from those rows rather than hand-maintained beside them, so registering a mark is one edit. Two marks carry no platform of -their own: the Gemini sparkle has its own higher-confidence path, and the -capture-less pill is too weak to attribute. +their own: the Gemini sparkle has its own higher-confidence path, and the pill +alone is too weak to attribute. The set of marks that veto the pill is DERIVED from the registry rows: every mark under the same label regime (`tc260`) belonging to a different product. It used to @@ -1285,6 +1285,15 @@ be represented by the shared base: - [`liblib_engine.py`](../src/remove_ai_watermarks/liblib_engine.py) - [`microsoft_engine.py`](../src/remove_ai_watermarks/microsoft_engine.py) +Kling keeps two independently gated silhouettes under one registry key. The +older measured cohort uses `可灵AI 3.0`; a direct authenticated IMAGE 3.0 export +on 2026-09-04 uses `KlingAI 3.0`. The Latin template scored 0.429 on that 1024 x +1024 provider original. Its narrow 0.9/1.0/1.1 scale ladder scored at most 0.349 +across the 94 available neighboring real and synthetic image controls, while an +adversarial solid corner blob reached 0.379, so its strict gate is 0.40. This is +one-positive provisional calibration, intentionally separate from the older +CJK detector rather than presented as broad recall evidence. + The measured Microsoft badge variant (2026-08-27 registration) is the first `tr`-corner mark and the first `long`-side scale basis: the pill tracks the render dimension, so a @@ -1406,12 +1415,15 @@ correlate with textured corners, not with pills: the edge-NCC keys on "text-like structure top-left". As with the Jimeng wordmark's silhouette, no threshold repairs this; a bare arm needs a detector that keys on the pill's rounded-rectangle geometry, and until one exists the bare-arm question is settled by this -measurement, not by gate tuning. Harness: `data/spaces/_pill_bare_measure.py` +measurement, not by gate tuning. The local calibration harness is not tracked. (cohorts A/B/C over the no-signal pool). -The capture-less Jimeng pill lives in +The weak Jimeng pill detector lives in [`pill_engine.py`](../src/remove_ai_watermarks/pill_engine.py). It uses a -synthetic silhouette for detection and a fixed top-left footprint. +synthetic silhouette for detection and a fixed top-left footprint. Its public, +published real-world regression carrier is +`data/fixtures/visible/jimeng_pill/provider-published-example.jpg`; the direct +detector scores the pill at 0.28 and the same image's Jimeng wordmark at 0.61. Each engine has a corresponding test module under [`tests/`](../tests/). Shared behavior is covered by: @@ -1434,10 +1446,10 @@ negative, so the bar stays at 0.35. Through the shipped arbiter (`identify_video`): 32/39 selected as doubao, 0/25 false; the remainder split between no stable run (5) and cross-template ties handed to earlier table entries (sora 1, kling 1) -- the registry's known order-decides behavior, not a -doubao-specific defect. Harness: `data/spaces/_doubao_seq_measure.py`. +doubao-specific defect. The local sequence-calibration harness is not tracked. Two further video candidates were measured and PARKED the same day (2026-08-28, -`data/spaces/_rh_jm_video_calibrate.py`): the Jimeng CN wordmark does not +local RunningHub/Jimeng video calibration): the Jimeng CN wordmark does not separate on video (1/5 positives vs 2/25 negatives hold a 12-frame 0.35 run -- the same silhouette-generality the image jimeng engine documents), and RunningHub video separates (1/2 positives, 0/25 negatives) but a two-positive diff --git a/docs/supported-signals.md b/docs/supported-signals.md index 6c55bd8..dba01c8 100644 --- a/docs/supported-signals.md +++ b/docs/supported-signals.md @@ -14,12 +14,12 @@ The `visible` command registers these mark keys: | `doubao` | `豆包AI生成` | Bottom right | Vendor specific text detector. | | `jimeng` | `★ 即梦AI` | Bottom right | Vendor specific text detector. | | `qwen` | `千问AI生成` | Bottom right | Strict visual gate. | -| `kling` | `可灵AI 3.0` | Bottom right | Only calibrated variants are covered. | +| `kling` | `可灵AI 3.0` or `KlingAI 3.0` | Bottom right | Separate calibrated silhouettes cover the older CJK and current IMAGE 3.0 Latin variants. | | `yuanbao` | `元宝` over `AI生成` | Bottom right | Standard two-line variant only. | | `samsung` | `✦ Contenuti generati dall'AI` | Bottom left | Calibrated for the Italian text variant. | | `runninghub` | `RunningHub AI生成` | Top left | Strict visual and position gates. | | `baidu` | `百度 AI生成` | Bottom right | Detector and extended removal footprint. | -| `liblib` | `LiblibAI` | Bottom center | Includes a minimum image size gate. | +| `liblib` | `LiblibAI` | Bottom center | Includes a minimum image size gate. A 2026-09-05 Smart Image V2 provider original instead carries a smaller top-left `AI生成` pill and is not detected by this historical-wordmark engine. | | `microsoft` | One Microsoft white AI-badge variant | Top right | Strict uses the visual gate; auto can use Microsoft provenance for the measured [relaxed gate](module-internals.md#visible-mark-removal). Other documented icon, text, and position variants are not covered. | | `jimeng_pill` | `AI生成` pill | Top left | Weak detector with additional product and background gates. | @@ -30,9 +30,16 @@ the masked area. Marks from other vendors are not detected automatically. Use `erase --region` when you can select the affected area yourself. -Synthetic examples for every registered image and video mark live in the -[visible-mark gallery](../data/fixtures/visible/README.md). They demonstrate the -detectors' canonical geometry and house style, not vendor raster fidelity. +Synthetic canonical examples for every registered image and video mark live in +the [visible-mark gallery](../data/fixtures/visible/README.md), alongside +documented real provider originals where redistribution is cleared. The +canonical examples demonstrate detector geometry and house style; provider +originals exercise vendor raster fidelity. + +Provider formats can change independently of the registered key. In particular, +the current LiblibAI sample in the gallery preserves a real new-format output +and its LiblibAI TC260 producer metadata, but is deliberately not listed as a +positive detector fixture. ### Visible video marks @@ -44,12 +51,12 @@ detectors' canonical geometry and house style, not vendor raster fidelity. | `seedance` | Boxed `AI` label | Fixed bottom-right corner | Requires an anchored recurring match; the full localized box is filled because a thinner synthetic shape mask leaves the real translucent rim behind. | | `dola` | `Dola AI` text | Fixed bottom-right corner | Requires an anchored recurring match; ByteDance or BytePlus provenance can relax only an existing visual run. | | `hailuo` | `MINIMAX \| hailuo AI` composite label | Fixed lower edge | Uses a synthetic waveform, text, separator, and ring silhouette; the complete recurring label box is filled. A TC260 label naming MiniMax as producer can relax only an existing stable run. | -| `kling` | Kling AI swirl, `KLING AI`, version, and optional `PRO` suffix | Fixed bottom-right edge | Combines a synthetic logo rescue with font variants, an edge gate, a white-label gate, and anchored temporal recurrence. | +| `kling` | Kling AI swirl, `KLING AI` or `KlingAI`, version, and optional `PRO` suffix | Fixed bottom-right edge | Combines a synthetic logo rescue with font and capitalization variants, an edge gate, a white-label gate, and anchored temporal recurrence. | `video identify`, `video visible`, and `video all` share this registry and the same temporal arbiter. It is separate from the image registry because selection is made over a sequence rather than one raster. The default `auto` mode scans -all six entries in one decode pass and selects the first temporally stable +all seven entries in one decode pass and selects the first temporally stable match in table order; an explicit mark restricts the scan to that row. Accepted fills are motion-aligned across adjacent frames by default. The prior fill contributes only where its warped mask covers the current removal mask and @@ -181,10 +188,10 @@ inspection cannot independently verify the output. Microsoft's official is the external oracle: it reports pixel `Watermark` and embedded `C2PA` results separately; a control-positive, output-negative pair is the available per-file verification path. The API needs Azure credentials; the page a human can check -without an account is . Its verdict is -weaker than the API's: it collapses watermark and C2PA into one rendered result -and tops out at `Inconclusive` rather than a watermark-negative, so treat -`Inconclusive` on a processed file as "not confirmed", not as "detected still". +without an account is . That page is the +public provider oracle used for the measured removal certification. Its clean +outcome is rendered as `Inconclusive`; the authenticated API exposes separate +watermark and C2PA fields but is not a different class of certification. Meta Muse Image stamps every output with Content Seal, a proprietary invisible pixel watermark, and ships no visible mark (the legacy `Imagined with AI` diff --git a/docs/watermarking-landscape.md b/docs/watermarking-landscape.md index e6002b9..45ced41 100644 --- a/docs/watermarking-landscape.md +++ b/docs/watermarking-landscape.md @@ -25,8 +25,16 @@ icon-only variants need separate calibrated silhouettes. Some applications sign C2PA through an upstream model or infrastructure provider. For AI claims, exact product mappings in `claim_generator` therefore take precedence over issuer attribution. Supported mappings include Higgsfield -AI, Topaz Labs Image API, and TikTok Ad Creative Toolbox; an unknown claim -generator still falls back to the certificate issuer. +AI, Recraft, Topaz Labs Image API, and TikTok Ad Creative Toolbox; an unknown +claim generator still falls back to the certificate issuer. + +**OpenRouter raster metadata survey (2026-09-02):** a paid Recraft V3 API +output carried a valid C2PA claim with `claim_generator` `recraft.ai`, a +`c2pa.created` action, and `trainedAlgorithmicMedia`; that exact generator now +attributes the platform to Recraft. Paid Krea 2 Medium Turbo and Sourceful +Riverflow V2 Fast API outputs carried no locally readable C2PA, XMP, EXIF, or +container metadata. Their absence is evidence about those exact files and API +routes, not a claim that every Krea or Riverflow export is metadata-free. **ByteDance Volcano Engine (Volcengine)** — the cloud behind Doubao / Jimeng — signs its AI image output with a cert from `certificate_center@volcengine.com` + `trainedAlgorithmicMedia` (issuer `b"volcengine"` → "ByteDance (Volcano Engine)", platform "ByteDance Volcano Engine"); note this is the C2PA-signed surface, distinct from the XMP/PNG TC260 `AIGC` label Doubao also uses. ByteDance's **international brand (BytePlus / Seedream / Seededit)** signs the same content as **"Byteplus Pte. Ltd."**. The bare `volcengine` needle missed it, so BytePlus output was mis-attributed to "Adobe Firefly" through an incidental "Adobe XMP" toolkit string. Issuer `b"Byteplus"` maps directly to "BytePlus (ByteDance)". ByteDance's consumer app **Dreamina** (the international Jimeng brand) signs as **"Bytedance Pte. Ltd."** with a `Dreamina/x.y` claim generator but, unlike the Volcano Engine surface, ships **no `trainedAlgorithmicMedia`**. Issuer `b"Dreamina"` maps to "ByteDance Dreamina" with **`asserts_ai=True`**. Registering the broader **issuer** `b"Bytedance Pte"` was deliberately avoided because that same entity also signs non-AI CapCut edits; keying on the `Dreamina` generator token is precise. - **EXIF/XMP/PNG-text generator tag (caught by `exif_generator`):** **Ideogram** writes EXIF `Make="Ideogram AI"` (collected 2026-05-24 — no C2PA, no SynthID, no imwatermark; the Make tag is the only signal). Additional verified generator stamps include **NovelAI** (`Software`, `Source`, and `Title` PNG text chunks), **Reve** (`Software` or XMP `CreatorTool` = `reve.com`), and **Aphrodite AI** (`Make` or `Software` = `Aphrodite AI`). @@ -66,7 +74,7 @@ payloads. Removal remuxes either container through ffmpeg with stream copy. 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 READ the soft-binding `alg`, preserve the structured assertion's signed `value`, and generate the display/type registry from the official C2PA JSON with `scripts/sync_c2pa_soft_bindings.py`. The packaged snapshot keeps runtime inspection offline and pins the exact upstream revision. Registration is name-only support: Adobe TrustMark remains the one locally decoded scheme (`trustmark_detector`) unless another compatible decoder is independently verified. Fingerprint entries remain re-linkability signals and are not mislabeled as pixel watermarks. -- **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. The no-account web page for the same check is (its `Inconclusive` verdict is weaker than the API's separate watermark result). 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 +- **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. The no-account web page for the same check is ; its clean outcome is rendered as `Inconclusive`, and that public provider oracle is the surface used for the measured removal certification. 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 @@ -131,6 +139,58 @@ instead uses Florence-2 to identify arbitrary watermarks before LaMa inpainting. That is broader, but it carries a much heavier model and a less auditable detection boundary than the provider-specific synthetic path here. +**Aggregator video export survey (2026-09-03).** Four paid, neutral-prompt +text-to-video jobs tested whether API access could reproduce the registered +consumer-facing marks. None did. OpenRouter `bytedance/seedance-2.0-mini` +(4 s, 480p, `watermark=true`, USD 0.0568316) returned a valid BytePlus C2PA +manifest but no visible boxed-`AI` mark; SHA-256 +`19c1e323fbb085e5679374a8b646d65a5e902292ff65e33462828ba211d089f4`. +OpenRouter `minimax/hailuo-3-max` (5 s, 480p, +`aigc_watermark=true`, USD 0.25) returned a native TC260 AIGC label but no +visible `MINIMAX | hailuo AI` composite; SHA-256 +`c850c79df6475e12a219ab6a4927b874c82a3ab783175f75acc6aa7513d2c303`. +WaveSpeedAI `openai/sora-2/text-to-video` (4 s, 720p) and +`kwaivgi/kling-v1.6-t2v-standard` (5 s, 720p) returned neither their registered +visible marks nor locally readable provenance; SHA-256 respectively +`85c29592fdfdac882a6536f6c27781c5d4f6a19b49681808eb744d0e33b63281` +and `392c4bd279ac94fa573f051b514b243c933637fdc7d0dae2df4af1f2312a85d0`. +Each result was checked through the explicit provider video path, not only the +auto arbiter, and returned no stable mark. The files are not fixtures: the two +clean exports assert no supported signal, while the OpenRouter outputs need +their model-specific redistribution terms settled before public inclusion. +For fixture collection, use the consumer surfaces that apply the marks rather +than repeating these aggregator routes. In particular, the provider option +names `watermark` and `aigc_watermark` proved to request machine-readable +provenance on these routes, not the visible overlays their names might imply. + +**Direct Google Flow control (2026-09-03).** Two authenticated Veo 3.1 Fast +generations used the same neutral blue-mug scene at 720p for 20 Flow credits +each. With the account's `Visible watermarking` setting off, the downloaded +8-second MP4 had valid Google C2PA and SynthID but no visible mark (SHA-256 +`f15454c2adda75fd79fcb8561e24088fa715f560e3c300c9f68e22fbc7275a20`). +That clean control exposed a false positive: the current auto arbiter selected +`hailuo` on all 192 frames and consequently misattributed the platform to +MiniMax, while the explicit `veo` path correctly found no mark. With the +setting on, the second 8-second MP4 carried low-contrast `Veo` text at the fixed +bottom-right position, and the shipped detector selected `veo` on all 192 +frames (SHA-256 +`e778194b545e34dafd2d5be58691031a070862890c41e6c527a652a114a7c7a4`). +The marked file is committed as the real provider original; the clean control +remains outside the repository until its Hailuo false positive is fixed and +locked by a negative regression test. Google documents both the user-controlled +visible-watermark setting and invisible SynthID on every Flow output. + +**Direct Hailuo AI control (2026-09-03).** An authenticated MiniMax H3 +generation used a neutral blue-mug scene, 2K output, and 60 top-up credits. The +download menu separately offered `Remove watermark` and `With Watermark`; the +latter produced a 2944 x 1248, 5-second MP4 carrying the complete lower-right +`MINIMAX | hailuo AI` composite and a TC260 AIGC label naming MiniMax as the +producer. The shipped detector selected `hailuo` on all 124 frames (SHA-256 +`6152609e0339c443af79e244440e1b1050f3c615a50f76bfaf6146584e68142f`). +This provider original is committed as the real Hailuo regression fixture and +confirms that the direct consumer export differs from the metadata-only +OpenRouter result above. + **Learned detectors change the visible-mark precision/recall tradeoff; they do not establish a universal separator.** The visible-watermark-detection literature has moved to learned segmentation and object detection (WDNet WACV'21 arXiv:2012.07616; SLBR ACM MM'21, open code and weights; the PRCV'18 large-scale detector; Su et al. survey 2025). The cited arXiv:1705.08593 paper reports a method that significantly reduces false matches and eliminates them after rejecting a small fraction of matches on its electron-microscopy task; it does not prove an information-theoretic limit for watermark detection. Learned watermark detectors also rely on large, pattern-diverse labeled datasets built with synthetic composites (PRCV'18: 60k images / 80 watermark classes; CLWD: 60k / 160 marks), and transfer depends on the diversity represented in training. Inference can be cheap (WDNet reports roughly 8 ms at 256x256), while the labeled-data and evaluation pipeline remain the larger project cost. A learned detector is therefore a possible future operating point, not evidence that the current NCC gate is theoretically optimal. **Visible-mark landscape beyond the registry.** Since Muse Image (2026-07) Meta's own generation output carries no visible mark, only the invisible proprietary Content Seal; the legacy visible "Imagined with AI" mark belongs to the pre-2026 Meta AI / "Imagine" pipeline and remains unregistered (string verified, position not). For third-party images Meta relies on C2PA / IPTC, not a visible mark. The Samsung detector covers only the measured Italian text wordmark; no separate icon-only variant has been verified. Visible overlays and embedded metadata can usually be removed without changing the underlying generation model, which is the tool's premise. diff --git a/scripts/render_vendor_silhouettes.py b/scripts/render_vendor_silhouettes.py index 552430f..ff70cfe 100644 --- a/scripts/render_vendor_silhouettes.py +++ b/scripts/render_vendor_silhouettes.py @@ -38,11 +38,13 @@ MARKS = { # stacked and ITALIC-SLANTED. A rare one-line variant exists, but the stacked block # is dominant. "yuanbao_alpha.png": "元宝\nAI生成", - # Kling (可灵) stamps a thin light-gray one-line "可灵AI 3.0" bottom-right (an - # "Omni" suffix variant and a latin "KlingAI 3.0" variant also exist; the CJK - # run without the suffix is the common core). The leading spiral logo is NOT - # rendered (logos vary; the text run discriminates). + # Older Kling (可灵) exports stamp a thin light-gray one-line "可灵AI 3.0" + # bottom-right. The leading spiral logo is NOT rendered (logos vary; the text + # run discriminates). "kling_alpha.png": "可灵AI 3.0", + # Current IMAGE 3.0 exports use the Latin ``KlingAI 3.0`` run. Keep it as a + # second installed silhouette rather than replacing the measured CJK variant. + "kling_latin_alpha.png": "KlingAI 3.0", # The "cat-logo" candidate stamps an outline cat-head plus bold "AI生成", # bottom-right. It remains unregistered pending sufficient calibration coverage. "catlogo_alpha.png": "cat logo + AI生成", @@ -96,8 +98,13 @@ MARK_OPTS: dict[str, dict[str, Any]] = { "qingyan_alpha.png": {"font": "/System/Library/Fonts/Hiragino Sans GB.ttc", "font_index": 2}, # LibLibAI uses an Arial-class grotesque. "liblib_alpha.png": {"font": "/System/Library/Fonts/Supplemental/Arial.ttf"}, + "kling_latin_alpha.png": {"font": "/System/Library/Fonts/HelveticaNeue.ttc", "font_index": 1}, } +# ``kling_latin`` is a variant of the registered ``kling`` row, not a second +# registry key, so it cannot be derived from ``mark_keys()`` above. +_REGISTERED.add("kling_latin_alpha.png") + def _fit_font( font_path: str, diff --git a/src/remove_ai_watermarks/_internal/constants.py b/src/remove_ai_watermarks/_internal/constants.py index 6aac949..3057417 100644 --- a/src/remove_ai_watermarks/_internal/constants.py +++ b/src/remove_ai_watermarks/_internal/constants.py @@ -123,6 +123,7 @@ C2PA_CLAIM_GENERATOR_PLATFORMS: tuple[tuple[str, str], ...] = ( ("firefly", "Adobe Firefly"), ("dreamina", "ByteDance Dreamina"), ("higgsfield ai", "Higgsfield AI"), + ("recraft.ai", "Recraft"), ("topaz labs image api", "Topaz Labs"), ("tiktok ad creative toolbox", "TikTok Ad Creative Toolbox"), ) diff --git a/src/remove_ai_watermarks/assets/kling_latin_alpha.png b/src/remove_ai_watermarks/assets/kling_latin_alpha.png new file mode 100644 index 0000000..1aedb03 Binary files /dev/null and b/src/remove_ai_watermarks/assets/kling_latin_alpha.png differ diff --git a/src/remove_ai_watermarks/kling_engine.py b/src/remove_ai_watermarks/kling_engine.py index e35f86a..53a1c22 100644 --- a/src/remove_ai_watermarks/kling_engine.py +++ b/src/remove_ai_watermarks/kling_engine.py @@ -1,19 +1,21 @@ """Kling AI (可灵AI, Kuaishou) visible watermark detector/localizer. -Kling AI stamps its generations with a thin, light-gray "可灵AI 3.0" text strip in the -bottom-right corner, preceded by the vendor's spiral logo (not part of the detection -silhouette -- logos vary between releases, the text run is what discriminates). -Known variants: an "Omni" suffix release, a latin "KlingAI 3.0" release, and a -version-less "可灵AI" -- the silhouette targets the common "可灵AI 3.0" core, so the -suffix variants are only caught when the core run is bold enough (measured below). +Kling AI stamps its generations with a thin, light-gray text strip in the +bottom-right corner, preceded by the vendor's spiral logo (not part of the +detection silhouette -- logos vary between releases, the text run is what +discriminates). Separate silhouettes cover the older "可灵AI 3.0" release and +the current IMAGE 3.0 "KlingAI 3.0" release. An "Omni" suffix and a version-less +"可灵AI" remain outside the calibrated pair. Detection matches the bundled glyph silhouette against the corner. Removal unions -the pixel-derived footprint with that silhouette aligned to the winning match box, +the pixel-derived footprint with the known silhouettes aligned to the winning match box, then sends the hybrid mask through the shared **localize -> fill** path in -``region_eraser``; it is not reverse-alpha. The asset -(``assets/kling_alpha.png``) is a font-rendered synthetic silhouette from -``scripts/render_vendor_silhouettes.py``, never cut from an upload. The detector also -feeds ``identify`` as the medium-confidence ``visible_kling`` signal via the registry. +``region_eraser``; it is not reverse-alpha. This module supplies two tuned +:class:`TextMarkConfig` instances (``assets/kling_alpha.png`` and +``assets/kling_latin_alpha.png`` -- font-rendered synthetic silhouettes from +``scripts/render_vendor_silhouettes.py``, never cut from an upload). The detector +also feeds ``identify`` as the medium-confidence ``visible_kling`` signal via the +registry. EVERY tuned number below was measured on the vendor cohort (30 TC260 carriers whose producer USCC 91110108335469089C names the entity, 2026-07-21; harness @@ -25,13 +27,14 @@ producer USCC 91110108335469089C names the entity, 2026-07-21; harness box fractions below are fitted from the measured absolute mark rects. * ``alpha_height_frac`` comes from the silhouette aspect (0.239) at the fitted width, matching the aspect the fit converged on (0.25). - * Gate 0.35, one step above the clean arm's max: on the cohort-vs-clean run + * The CJK gate is 0.35, one step above the clean arm's max: on the cohort-vs-clean run (cohort-contamination-guarded, 286 hand-labeled clean frames) the clean arm scored p99 0.304 / max 0.320, and every cohort frame >= 0.35 carries a visible 可灵AI 3.0 mark (9 of ~19 eyeballed visible marks fire = ~47% recall of visible - marks; the misses are the faint "Omni"-suffix release, the latin "KlingAI" - release and the version-less "可灵AI", which score 0.17-0.25 and cannot be - reached without engulfing the clean arm). + marks; the misses are the faint "Omni"-suffix release and the version-less + "可灵AI", which score 0.17-0.25 and cannot be reached without engulfing the + clean arm). The separately measured Latin IMAGE 3.0 variant is documented + beside ``_LATIN_CONFIG`` below. * STRICT ONLY (``provenance_ncc_factor`` 1.0): the sub-gate band holds real Kling variants AND the clean arm's top (clean p90 0.220 vs variant marks at 0.17-0.25 -- they overlap), so a provenance-relaxed arm cannot separate them. No @@ -49,6 +52,7 @@ producer USCC 91110108335469089C names the entity, 2026-07-21; harness from __future__ import annotations +from dataclasses import replace from typing import TYPE_CHECKING, Any import cv2 @@ -121,6 +125,23 @@ _CONFIG = TextMarkConfig( provenance_ncc_factor=1.0, ) +# Direct Kling IMAGE 3.0 export, 2026-09-04: the current Latin run measures +# 0.095 x 0.028 of the short side inside the same bottom-right locate box. The +# narrow ladder covers small rasterization shifts without admitting any of the +# 94 available neighboring real/synthetic image controls (max 0.349, versus +# 0.429 on the provider original). A deliberately adversarial solid corner blob +# reaches 0.379, so the 0.40 gate stays above both measured negative arms. Keep +# this a separate silhouette and gate: +# replacing the CJK template would discard the older measured release. +_LATIN_CONFIG = replace( + _CONFIG, + asset_name="kling_latin_alpha.png", + alpha_width_frac=0.095, + alpha_height_frac=0.028, + ladder=(0.9, 1.0, 1.1), + detect_ncc_threshold=0.40, +) + def _alpha_template() -> NDArray[Any] | None: """The bundled Kling AI alpha template (float [0,1]), or None.""" @@ -133,10 +154,29 @@ def _glyph_silhouette() -> NDArray[Any] | None: class KlingEngine(TextMarkEngine): - """Detect/localize the visible Kling AI "可灵AI 3.0" watermark (locate -> mask; mask feeds the fill).""" + """Detect/localize Kling AI's CJK and Latin 3.0 marks (locate -> mask -> fill).""" def __init__(self) -> None: super().__init__(_CONFIG) + self._latin = TextMarkEngine(_LATIN_CONFIG) + + @staticmethod + def _best(*detections: TextMarkDetection) -> TextMarkDetection: + """Prefer an accepted variant, then retain the strongest rejected score.""" + return max(detections, key=lambda detection: (detection.detected, detection.confidence)) + + def detect(self, image: NDArray[Any], *, provenance: bool = False) -> TextMarkDetection: + """Return the strongest CJK or Latin Kling wordmark verdict.""" + return self._best( + super().detect(image, provenance=provenance), + self._latin.detect(image, provenance=provenance), + ) + + def detect_both(self, image: NDArray[Any] | None) -> tuple[TextMarkDetection, TextMarkDetection]: + """Scan each immutable variant once and combine strict/relaxed verdicts.""" + cjk_strict, cjk_relaxed = super().detect_both(image) + latin_strict, latin_relaxed = self._latin.detect_both(image) + return self._best(cjk_strict, latin_strict), self._best(cjk_relaxed, latin_relaxed) def footprint_mask( self, @@ -146,7 +186,7 @@ class KlingEngine(TextMarkEngine): dilate: int | None = None, detection: TextMarkDetection | None = None, ) -> NDArray[Any] | None: - """Add the detector-aligned Kling core to the pixel-derived footprint. + """Add detector-aligned Kling cores to the pixel-derived footprint. The legacy mask remains in the union because it can cover variant-specific strokes outside the synthetic core. The aligned alpha closes holes caused by @@ -159,17 +199,19 @@ class KlingEngine(TextMarkEngine): det = detection if detection is not None else self.detect(image) legacy = super().footprint_mask(image, force=False, dilate=dilate, detection=det) - alpha = _alpha_template() - if alpha is None: - return legacy radius = _FOOTPRINT_DILATE if dilate is None else max(0, dilate) - core = self._aligned_alpha_mask( - image, - det, - alpha, - alpha_floor=_FOOTPRINT_ALPHA_FLOOR, - dilate=radius, - ) - if core is None: - return legacy - return core if legacy is None else cv2.bitwise_or(legacy, core) + result = legacy + for config in (_CONFIG, _LATIN_CONFIG): + alpha = _text_mark_engine.load_alpha_template(config.asset_name) + if alpha is None: + continue + core = self._aligned_alpha_mask( + image, + det, + alpha, + alpha_floor=_FOOTPRINT_ALPHA_FLOOR, + dilate=radius, + ) + if core is not None: + result = core if result is None else cv2.bitwise_or(result, core) + return result diff --git a/src/remove_ai_watermarks/metadata.py b/src/remove_ai_watermarks/metadata.py index 35bf18e..e0d55b3 100644 --- a/src/remove_ai_watermarks/metadata.py +++ b/src/remove_ai_watermarks/metadata.py @@ -820,6 +820,7 @@ def synthid_source(image_path: Path, *, c2pa_info: dict[str, Any] | None = None) from remove_ai_watermarks._internal.c2pa import ( c2pa_info_has_invalid_credential, extract_c2pa_info, + soft_binding_registry_entries_in, synthid_evidence_vendors_in, ) @@ -832,6 +833,11 @@ def synthid_source(image_path: Path, *, c2pa_info: dict[str, Any] | None = None) vendors = c2pa.get("synthid_vendors") if vendors: return ", ".join(vendors) + soft_binding_algorithm = c2pa.get("soft_binding_algorithm") + if soft_binding_algorithm: + entries = soft_binding_registry_entries_in(str(soft_binding_algorithm).encode()) + if any(entry.kind == "watermark" for entry in entries) or not entries: + return None # Non-PNG containers (JPEG APP11, WebP, AVIF/HEIF/JXL uuid box) keep the # C2PA manifest where the PNG parser can't reach it. Binary-scan for the @@ -842,8 +848,6 @@ def synthid_source(image_path: Path, *, c2pa_info: dict[str, Any] | None = None) ai_source = b"trainedAlgorithmicMedia" in data or b"TrainedAlgorithmicMedia" in data if not (has_c2pa and ai_source): return None - from remove_ai_watermarks._internal.c2pa import soft_binding_registry_entries_in - # A declared watermark blocks a second, generic watermark attribution. A # content fingerprint does not suppress independent SynthID evidence. if any(entry.kind == "watermark" for entry in soft_binding_registry_entries_in(data)): diff --git a/src/remove_ai_watermarks/video_visible.py b/src/remove_ai_watermarks/video_visible.py index 46bc91e..1ee8c98 100644 --- a/src/remove_ai_watermarks/video_visible.py +++ b/src/remove_ai_watermarks/video_visible.py @@ -271,31 +271,32 @@ def _hailuo_template() -> NDArray[Any]: @lru_cache(maxsize=1) def _kling_templates() -> tuple[NDArray[Any], ...]: - """Return synthetic font variants for the Kling wordmark core.""" - canvas = Image.new("L", (430, 104), 0) - draw = ImageDraw.Draw(canvas) - draw.text( - (2, 12), - "KLING AI", - font=_scalable_default_font(64), - fill=255, - stroke_width=1, - stroke_fill=255, - ) - templates = [_crop_nonzero(np.asarray(canvas, dtype=np.uint8))] - for font in (cv2.FONT_HERSHEY_SIMPLEX, cv2.FONT_HERSHEY_DUPLEX): - cv_template = np.zeros((100, 500), dtype=np.uint8) - cv2.putText( - cv_template, - "KLING AI", - (2, 72), - font, - 2.2, - 255, - 3, - cv2.LINE_AA, + """Return synthetic font and capitalization variants for the Kling wordmark core.""" + templates: list[NDArray[Any]] = [] + for text in ("KLING AI", "KlingAI"): + canvas = Image.new("L", (430, 104), 0) + ImageDraw.Draw(canvas).text( + (2, 12), + text, + font=_scalable_default_font(64), + fill=255, + stroke_width=1, + stroke_fill=255, ) - templates.append(_crop_nonzero(cv_template)) + templates.append(_crop_nonzero(np.asarray(canvas, dtype=np.uint8))) + for font in (cv2.FONT_HERSHEY_SIMPLEX, cv2.FONT_HERSHEY_DUPLEX): + cv_template = np.zeros((100, 500), dtype=np.uint8) + cv2.putText( + cv_template, + text, + (2, 72), + font, + 2.2, + 255, + 3, + cv2.LINE_AA, + ) + templates.append(_crop_nonzero(cv_template)) return tuple(templates) diff --git a/src/remove_ai_watermarks/watermark_registry.py b/src/remove_ai_watermarks/watermark_registry.py index a1d548c..456490f 100644 --- a/src/remove_ai_watermarks/watermark_registry.py +++ b/src/remove_ai_watermarks/watermark_registry.py @@ -22,7 +22,7 @@ Entries: - ``doubao`` -- ByteDance Doubao "豆包AI生成" text strip, bottom-right. - ``jimeng`` -- ByteDance Jimeng / Dreamina "★ 即梦AI" wordmark, bottom-right. - ``qwen`` -- Alibaba Cloud Qwen "千问AI生成" text strip, bottom-right. - - ``kling`` -- Kuaishou Kling AI "可灵AI 3.0" text strip, bottom-right. + - ``kling`` -- Kuaishou Kling AI "可灵AI 3.0" / "KlingAI 3.0" strip, bottom-right. - ``yuanbao`` -- Tencent Yuanbao "元宝 / AI生成" two-line mark, bottom-right. - ``samsung`` -- Samsung Galaxy AI "Contenuti generati dall'AI" strip, bottom-left. - ``jimeng_pill`` -- Jimeng-basic "AI生成" pill, top-left (capture-less). @@ -726,9 +726,9 @@ _REGISTRY: tuple[KnownMark, ...] = ( ), _text_mark( "kling", - "Kling AI 可灵AI 3.0 text", + "Kling AI 可灵AI / KlingAI 3.0 text", "bottom-right", - platform="Kuaishou Kling AI (visible 可灵AI 3.0 mark detected)", + platform="Kuaishou Kling AI (visible 可灵AI / KlingAI 3.0 mark detected)", tc260_producer_codes=("91110108335469089C",), ), _text_mark( diff --git a/tests/test_identify.py b/tests/test_identify.py index f17e73e..36e4619 100644 --- a/tests/test_identify.py +++ b/tests/test_identify.py @@ -7,6 +7,7 @@ against the real committed C2PA / IPTC fixtures in data/fixtures/provenance/. from __future__ import annotations import base64 +import hashlib import json import subprocess import sys @@ -463,6 +464,7 @@ class TestIdentifyNonPng: ("claim_generator", "platform"), [ ("Higgsfield AI", "Higgsfield AI"), + ("recraft.ai", "Recraft"), ("Topaz Labs Image API", "Topaz Labs"), ("TIKTOK AD Creative Toolbox", "TikTok Ad Creative Toolbox"), ], @@ -491,6 +493,78 @@ class TestIdentifyNonPng: assert report.platform == platform assert claim_generator in next(s.detail for s in report.signals if s.name == "c2pa") + def test_real_recraft_c2pa_fixture(self): + report = identify(SAMPLES_DIR / "recraft-v3.webp", check_visible=False) + + assert report.is_ai_generated is True + assert report.confidence == "high" + assert report.platform == "Recraft" + assert any(signal.name == "c2pa" and "recraft.ai" in signal.detail for signal in report.signals) + + def test_real_krea_api_fixture_has_no_local_provenance(self): + report = identify(SAMPLES_DIR / "krea-2-medium-turbo.png", check_visible=False) + + assert report.is_ai_generated is None + assert report.platform is None + assert report.signals == [] + + def test_real_direct_kling_fixture_has_tc260_and_current_visible_mark(self): + path = SAMPLES_DIR.parent / "visible" / "kling" / "provider-original-direct.png" + report = identify(path, check_visible=True) + + assert report.is_ai_generated is True + assert any( + signal.name == "aigc" and "001191110108335469089C10100" in signal.detail for signal in report.signals + ) + assert any(signal.name == "visible_kling" for signal in report.signals) + + @pytest.mark.parametrize("filename", ["qwen-image.png", "seedream-v4.jpg"]) + def test_real_wavespeed_api_fixture_has_no_local_provenance(self, filename): + report = identify(SAMPLES_DIR / filename, check_visible=False) + + assert report.is_ai_generated is None + assert report.platform is None + assert report.signals == [] + + def test_real_wavespeed_hunyuan_fixture_has_fal_c2pa(self): + path = SAMPLES_DIR / "hunyuan-image-3.png" + assert hashlib.sha256(path.read_bytes()).hexdigest() == ( + "6ae2ac073ac79b02b0934c905364f200420c2ff60fea232bd9e0a5fca785cc09" + ) + + report = identify(path, check_visible=False, check_invisible=False) + + assert report.is_ai_generated is True + assert report.platform == "fal.ai" + assert report.confidence == "high" + assert any(signal.name == "c2pa" and "fal-ai/hunyuan-image" in signal.detail for signal in report.signals) + + def test_real_wavespeed_kling_fixture_has_tc260_metadata(self): + path = SAMPLES_DIR / "kling-image-v3.png" + assert hashlib.sha256(path.read_bytes()).hexdigest() == ( + "501ab865b47ba59b64bc0f68118300744b0d5c7be2fa4e2ecbb3a78de5d8a82b" + ) + + report = identify(path, check_visible=False, check_invisible=False) + + assert report.is_ai_generated is True + assert report.confidence == "high" + aigc = next(signal for signal in report.signals if signal.name == "aigc") + assert "001191110108335469089C10100" in aigc.detail + + def test_real_qwen_create_fixture_has_tc260_metadata(self): + path = SAMPLES_DIR / "qwen-create-qwen-image-2.png" + assert hashlib.sha256(path.read_bytes()).hexdigest() == ( + "60c39ed8aedb081193c62289be549785e74d31fc8d071961003fabd74712429d" + ) + + report = identify(path, check_visible=False, check_invisible=False) + + assert report.is_ai_generated is True + assert report.confidence == "high" + aigc = next(signal for signal in report.signals if signal.name == "aigc") + assert "001191440101MA9Y9T4H7A00000" in aigc.detail + def test_dreamina_attributed_without_source_type(self, tmp_path: Path): # Dreamina (ByteDance's international Jimeng brand) signs C2PA as # "Bytedance Pte. Ltd." with a "Dreamina/x.y" claim generator and NO @@ -1333,6 +1407,43 @@ class TestIdentifySoftBinding: assert watermark_id in invismark.detail assert any("may remain in the pixels" in caveat for caveat in report.caveats) + @pytest.mark.parametrize( + ("filename", "watermark_id", "sha256"), + [ + ( + "microsoft-invismark.png", + "19b070a1-23f0-40eb-826c-cade306337eb", + "be27ac7c351e7f381dc69e140ca3611ecd9db81e40926c68050ceffdd0ce6c04", + ), + ( + "microsoft-paint-invismark.png", + "a4ed421e-7c37-4409-998e-f8788c564572", + "74eda22d210d534fcb5b75bedf8acd3bd110b48a95f4b9e546cd34f546effc05", + ), + ( + "microsoft-bing-image-creator.jpg", + "aec9a34e-665f-4549-9c02-9d29337a5de6", + "55cad5355b6eeeb25f84ea074f85c0ea55b82d63c27f43e3429bb75f28734ac7", + ), + ( + "../visible/microsoft/provider-original.png", + "975290d4-9c2f-4359-ad5a-92c5b5b850a4", + "0e76fb8aa0540b18fee4d63ac4b5fd63c7eee310474e896d21d27e668403afe7", + ), + ], + ) + def test_real_microsoft_invismark_fixture(self, filename, watermark_id, sha256): + path = SAMPLES_DIR / filename + assert hashlib.sha256(path.read_bytes()).hexdigest() == sha256 + + report = identify(path, check_visible=False) + + assert report.platform == "Microsoft (Copilot / Designer)" + soft_binding = next(signal for signal in report.signals if signal.name == "soft_binding") + assert "com.microsoft.invismark.1" in soft_binding.detail + assert watermark_id in soft_binding.detail + assert any(signal.name == "invismark" for signal in report.signals) + class TestIdentifyIptcAi: """IPTC 2025.1 AISystemUsed drives an AI verdict + platform attribution.""" diff --git a/tests/test_invisible_watermark.py b/tests/test_invisible_watermark.py index db0568c..7493ac7 100644 --- a/tests/test_invisible_watermark.py +++ b/tests/test_invisible_watermark.py @@ -6,15 +6,12 @@ both identify them and match the upstream decoder bit for bit. from __future__ import annotations -from typing import TYPE_CHECKING +from pathlib import Path import cv2 import numpy as np import pytest -if TYPE_CHECKING: - from pathlib import Path - from remove_ai_watermarks.invisible_watermark import ( _BITS_48, _SD1_STRING, @@ -101,6 +98,10 @@ class TestRaveledHaarPass: class TestDetect: + def test_committed_sdxl_carrier(self): + path = Path(__file__).parents[1] / "data/fixtures/provenance/sdxl-dwt-dct.png" + assert detect_invisible_watermark(path) == "Stable Diffusion XL" + def test_in_tree_decoder_matches_upstream(self, tmp_path: Path): from imwatermark import WatermarkDecoder diff --git a/tests/test_kling_engine.py b/tests/test_kling_engine.py index 30bf8d2..d055e0c 100644 --- a/tests/test_kling_engine.py +++ b/tests/test_kling_engine.py @@ -1,4 +1,4 @@ -"""Tests for the Kling (可灵AI 3.0) visible-watermark engine (localize -> fill). +"""Tests for the Kling CJK/Latin 3.0 visible-watermark engine (localize -> fill). Every tuned constant in ``kling_engine`` was measured on the 30-frame vendor cohort (2026-07-21, ``scripts/vendor_mark_calibrate.py``); these tests pin the @@ -18,6 +18,7 @@ from remove_ai_watermarks import watermark_registry as registry from remove_ai_watermarks.kling_engine import ( _ALPHA_HEIGHT_FRAC, _ALPHA_WIDTH_FRAC, + _LATIN_CONFIG, KlingEngine, _alpha_template, _glyph_silhouette, @@ -78,6 +79,11 @@ class TestConfig: # gate must sit above that with margin. assert KlingEngine().config.detect_ncc_threshold > 0.32 + def test_latin_variant_keeps_its_measured_narrow_ladder(self): + assert _LATIN_CONFIG.asset_name == "kling_latin_alpha.png" + assert _LATIN_CONFIG.ladder == (0.9, 1.0, 1.1) + assert _LATIN_CONFIG.detect_ncc_threshold == 0.40 + def test_registry_row(self): mark = registry.get_mark("kling") assert mark.location == "bottom-right" @@ -126,6 +132,12 @@ class TestDetect: assert eng.detect(wm).detected assert not eng.detect(cv2.resize(wm, (150, 112))).detected + def test_metadata_only_kling_api_export_is_not_a_visible_mark(self): + path = Path(__file__).resolve().parents[1] / "data" / "fixtures" / "provenance" / "kling-image-v3.png" + image = cv2.imread(str(path)) + assert image is not None + assert not KlingEngine().detect(image).detected + class TestFootprintMaskAndRemoval: def test_removes_composed_mark(self): diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 4dc74c3..e269cc1 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -560,6 +560,11 @@ class TestSynthIDSource: assert synthid_source(SAMPLES_DIR / "firefly-1.png") is None assert "synthid_watermark" not in get_ai_metadata(SAMPLES_DIR / "firefly-1.png") + def test_microsoft_invismark_is_not_openai_synthid(self): + path = SAMPLES_DIR / "microsoft-paint-invismark.png" + assert synthid_source(path) is None + assert "synthid_watermark" not in get_ai_metadata(path) + def test_non_ai_image_is_not_synthid_source(self, clean_photo: Path): assert synthid_source(clean_photo) is None diff --git a/tests/test_pill_engine.py b/tests/test_pill_engine.py index 9aca773..165cb4e 100644 --- a/tests/test_pill_engine.py +++ b/tests/test_pill_engine.py @@ -1,5 +1,8 @@ -"""Jimeng-basic 'AI生成' pill: capture-less mark (detect via synthetic silhouette -edge-NCC, remove via inpaint). No model download -- cv2 fallback / pure logic only.""" +"""Jimeng-basic 'AI生成' pill: synthetic-silhouette edge-NCC and inpainting. + +No model download -- cv2 fallback / pure logic only. The separate visible-example +suite exercises the detector against a published real-world carrier. +""" from __future__ import annotations diff --git a/tests/test_visible_examples.py b/tests/test_visible_examples.py index 4e1e27f..541e53d 100644 --- a/tests/test_visible_examples.py +++ b/tests/test_visible_examples.py @@ -6,7 +6,9 @@ Two failures this suite exists to catch: 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). +committed silhouettes onto a generated base). Documented provider originals and +provider frames are separate regression inputs and remain byte-for-byte copies of +their sources. """ from __future__ import annotations @@ -18,11 +20,26 @@ 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 +from remove_ai_watermarks.video_visible import detect_sora_frame _ROOT = Path(__file__).resolve().parents[1] _GALLERY = _ROOT / "data" / "fixtures" / "visible" _IMAGE_KEYS = [m.key for m in wr.known_marks()] +_PROVIDER_ORIGINALS = { + "baidu": "provider-original.jpeg", + "jimeng_pill": "provider-published-example.jpg", + "kling": "provider-original-direct.png", + "microsoft": "provider-original.png", + "qwen": "provider-original.png", + "yuanbao": "provider-original.png", +} +_VIDEO_PROVIDER_ORIGINALS = { + "hailuo": "provider-original.mp4", + "kling": "provider-original.mp4", + "sora": "provider-original.mp4", + "veo": "provider-original.mp4", +} class TestGallery: @@ -42,6 +59,13 @@ class TestGallery: 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" + @pytest.mark.parametrize("key", sorted(_PROVIDER_ORIGINALS)) + def test_engine_detects_provider_original(self, key: str) -> None: + img = imread(str(_GALLERY / key / _PROVIDER_ORIGINALS[key])) + 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 provider original" + class TestVideoGallery: def test_every_registered_video_mark_has_an_example(self) -> None: @@ -55,3 +79,14 @@ class TestVideoGallery: 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}" + + @pytest.mark.parametrize("key", sorted(_VIDEO_PROVIDER_ORIGINALS)) + def test_selection_accepts_provider_original(self, key: str) -> None: + rep = identify_video(_GALLERY / key / _VIDEO_PROVIDER_ORIGINALS[key], check_visible=True) + assert rep.visible_mark == key, f"{key}: selection returned {rep.visible_mark!r}" + + def test_sora_frame_localizer_accepts_provider_frame(self) -> None: + img = imread(str(_GALLERY / "sora" / "provider-frame.jpg")) + assert img is not None + det = detect_sora_frame(img) + assert det.confidence >= 0.60, f"sora: confidence {det.confidence:.3f} on provider frame"