Compare commits
No files matched your search
@@ -6,7 +6,7 @@
|
||||
},
|
||||
"metadata": {
|
||||
"description": "Agent skill and Claude Code plugin for the remove-ai-watermarks CLI.",
|
||||
"version": "1.0.3"
|
||||
"version": "1.0.11"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
---
|
||||
globs: ["src/**/*.py", "tests/**/*.py", "scripts/**/*.py", "skills/**", "pyproject.toml", "uv.lock", "maintain.sh", ".github/workflows/*.yml"]
|
||||
paths:
|
||||
- "src/**/*.py"
|
||||
- "tests/**/*.py"
|
||||
- "scripts/**/*.py"
|
||||
- "skills/**"
|
||||
- "pyproject.toml"
|
||||
- "uv.lock"
|
||||
- "maintain.sh"
|
||||
- ".github/workflows/*.yml"
|
||||
description: Command contracts, project gate, typing boundaries, model-adjacent test invariants, the agent-skill parity seam, and the detection-path measurement rule.
|
||||
---
|
||||
|
||||
@@ -17,6 +25,50 @@ Provider verification services and provenance APIs are development-only oracles.
|
||||
their SDKs in the development extra and their adapters out of the installed CLI,
|
||||
top-level Python API, and runtime detection/removal pipelines.
|
||||
|
||||
The unified maintainer entry point is `scripts/provider_oracles.py`; its workflow and
|
||||
slot schema live in `docs/provider-oracles.md`. Multi-account and multi-network work
|
||||
selects each named slot explicitly from the gitignored repository-local
|
||||
`.oracle-slots.json` by default and snapshots it into a separate immutable batch
|
||||
manifest. A bounded provider check may continue through its finite set of authorized
|
||||
account slots after a refusal; do not add new logins, IP rotation, network-route
|
||||
failover, unbounded retries, or cross-session result merging. Never commit the local
|
||||
slot file.
|
||||
Every manual result stays bound to the exact sanitized upload hash and preserves the
|
||||
provider response verbatim.
|
||||
|
||||
Provider surface selection is API-first. OpenAI and Microsoft use their official API
|
||||
adapters before their Web surfaces when the requested media and local configuration are
|
||||
supported. Google and Meta currently use Web because this tooling has no usable API route
|
||||
for them. Web remains an explicit second surface, never an automatic retry after an API
|
||||
refusal, quota response, transport error, or indeterminate verdict.
|
||||
|
||||
Google Web oracle automation uses only the user's existing authenticated real Chrome
|
||||
session and explicit `/u/N/` account index. OpenAI, Microsoft, and Meta Web automation
|
||||
uses isolated Playwright contexts; it must never attach to or copy state from the user's
|
||||
real browser. Proxy credentials are read only through a slot's named environment variable
|
||||
and never enter a manifest or log. Dotenv interpolation may derive routed proxy URLs from
|
||||
one secret key without mutating the process environment. A selected proxy context may
|
||||
accept that proxy's TLS interception certificate; direct contexts keep normal TLS checks.
|
||||
ThorData rotating endpoints are pinned to one manifest-derived sticky session for the
|
||||
life of a Web batch so one browser context does not change exit identity between page
|
||||
requests. This is route continuity, not account or IP failover; a refusal still ends on
|
||||
the selected slot and never selects another route automatically.
|
||||
Upload readiness is surface-specific: OpenAI reaches it at page load but keeps background
|
||||
requests alive, while Microsoft and Meta require network idle. Preserve exact provider
|
||||
errors as indeterminate evidence rather than retrying or interpreting them as clean.
|
||||
|
||||
OpenAI API slots likewise carry only `api_key_env`. Store multiple key values in the
|
||||
gitignored `.env`, select one slot explicitly, and never rotate to another token after a
|
||||
refusal or rate limit. The process environment may override the matching `.env` value.
|
||||
Their SDK transport sets `trust_env=False`; environment proxy variables must not reroute
|
||||
an API check.
|
||||
|
||||
Microsoft API slots use the Azure Content Safety and private Blob Storage resource
|
||||
identities from the gitignored `.env`. The adapter reads resource keys only into memory
|
||||
through authenticated Azure CLI, downloads and hashes the private blob before requiring
|
||||
equality with the named local source, keeps `trust_env=False`, never retries the submit,
|
||||
and reports Watermark independently from C2PA.
|
||||
|
||||
Do not add an option whose only outcome is an error. Model id, step count and CFG are fixed by the profile, so none of them is a parameter of the CLI, `InvisibleEngine`, or `WatermarkRemover` -- they were accepted-then-rejected for a while, which moved the failure several frames below the caller and advertised choices the pinned stack cannot honor. If a value cannot vary, delete the knob rather than validating it.
|
||||
|
||||
`device` is the deliberate exception and stays a library parameter: `None`/`"auto"` detect, `"cuda"` pins without detecting, and everything else raises at construction. On the image path it is not a CLI option, because the only value a user could usefully type is the one auto-detection already finds. The video SynthID commands (`video invisible`, `video all`, `video batch`) do expose `--device`, whose VAE runs on cuda, mps, or cpu, so a user can usefully pick one. `test_device_exists_exactly_where_the_skill_says_it_does` pins that split; do not read this paragraph as licence to delete the video flag.
|
||||
@@ -27,10 +79,33 @@ The same rule applies to install hints: name the extra that actually makes the c
|
||||
|
||||
Run `bash maintain.sh` from the repository root. The authoritative type gate is scoped to `src/`; full-project Pyright can exhaust Node memory on the ML dependency graph.
|
||||
|
||||
The full gate outlasts the agent's 10-minute foreground shell cap, which kills it mid-suite. Run it detached under `nohup caffeinate -dimu` with its exit code written to a file and poll that file. To stop a stray run, kill its own PID, never `pkill -f pytest`, which also takes down other sessions' suites on the machine.
|
||||
|
||||
The security scanner's exit status is authoritative, including a failure after a success
|
||||
message. `tests/test_maintenance.py` checks clean, vulnerable, and broken scanner
|
||||
runs through the real shell entry point, and pins the CI `security` job to the
|
||||
same scanner line with no `continue-on-error`. Recheck unresolved advisories using the
|
||||
procedure in `docs/development.md`; do not revive the old text-matching bypass.
|
||||
|
||||
Boundary modules for cv2, Torch, and Diffusers may carry narrow per-file relaxations for unknown third-party types. Keep pure-logic files strict, preserve the local piexif stub, and fix real errors before widening a pragma.
|
||||
|
||||
From a worktree, `uv run` imports the package from the MAIN checkout -- that is where the editable install points. A script measuring a worktree's edit must insert that worktree's `src` at `sys.path[0]` and assert `module.__file__` resolves inside it, or it silently compares unmodified code against itself.
|
||||
|
||||
## Research harnesses stay out of this repository
|
||||
|
||||
A research script that needs a cloud GPU account to run is not kept here, and
|
||||
`scripts/` carries no `modal` import; `test_scripts_carry_no_cloud_gpu_harness`
|
||||
is the canary. The identifier half of the boundary audit is deliberately not
|
||||
mechanized here, because a denylist written into a tracked file publishes the
|
||||
strings it exists to keep out. Twelve such harnesses were moved out on
|
||||
2026-09-07; the pages that cite their findings
|
||||
([chroma1](../../docs/chroma1-engine-research.md),
|
||||
[module internals](../../docs/module-internals.md)) name them as harnesses kept
|
||||
outside this repository, and the tracked inputs under `data/evaluations/` plus
|
||||
the recorded verdicts are the durable record. Keep a new one outside too, and
|
||||
leave only its offline analysis step here, the way
|
||||
`scripts/analyze_engine_selection_study.py` stayed with its test.
|
||||
|
||||
## Visible-mark example gallery
|
||||
|
||||
Every registered mark carries a committed example: `data/fixtures/visible/<key>/example.png`
|
||||
@@ -63,6 +138,10 @@ Do not classify an entire module as untestable because its main path downloads a
|
||||
- the photo-classify gate in `test_classify.py`: DEFINITELY is ridge AND MLP,
|
||||
POSSIBLY does not emit a provider, and `identify` does not import
|
||||
`remove_ai_watermarks.classify`.
|
||||
- the source-export classifier in `test_source_classify.py`: the Hub revision
|
||||
and artifact hash are immutable, the pickle-free schema and exact GELU are
|
||||
validated, threshold mutation changes the real decision, and `identify`
|
||||
never consumes the result as watermark evidence.
|
||||
- the `InvisibleOptions` defaults, in `test_api.py`. When one signature promises to
|
||||
mirror another, compare them field by field rather than pinning the values you happen
|
||||
to know about, so the next field added on one side and not the other fails at the
|
||||
@@ -186,7 +265,11 @@ path exempt from the gate it exists to feed, which is exactly backwards.
|
||||
Prove a video-path refactor the same way the detection path is proven, and without
|
||||
needing an oracle carrier: build a clip from a tracked fixture with ffmpeg, run the
|
||||
engine before and after, and require an identical output sha256. Keep the generated
|
||||
media outside the repository.
|
||||
media outside the repository. Bind that comparison to one synced environment: a
|
||||
full-extras `uv sync` was observed to change re-encoded H.264 packet bytes with
|
||||
no code change while every detection stayed identical
|
||||
(`docs/audio-provenance-experiment.md`), so a moved video hash across an
|
||||
environment change is not by itself a regression signal.
|
||||
|
||||
Frame sampling is compared as `timestamp + 1e-9 >= next_sample_time`, so mutating
|
||||
that `>=` to `>` changes nothing and a green suite proves nothing. Mutate the phase
|
||||
@@ -197,3 +280,35 @@ frame-count check cannot see and what
|
||||
catch.
|
||||
|
||||
Environment setup, dependency recovery, CI behavior, and fixture policy: [`../../docs/development.md`](../../docs/development.md).
|
||||
|
||||
## Audit regression contracts
|
||||
|
||||
Image output paths must describe the artifact published by the current call,
|
||||
including skipped invisible stages and in-place writes. Carry explicit EXIF
|
||||
decode state through image staging; equal dimensions do not prove that a
|
||||
mirror or 180-degree rotation was applied. Metadata collection must propagate
|
||||
late-read failures into an incomplete record, and stream-copy metadata removal
|
||||
must map every input stream before atomically publishing the output.
|
||||
|
||||
Research observations must describe the decoded, hash-bound saved artifact.
|
||||
Keep period selection independent of confirmation patches, require explicit
|
||||
negative evidence, and deduplicate source corpora by content identity rather
|
||||
than detector-score equality. Use the metric's actual denominator in its name;
|
||||
normalized edit distance is not reference-normalized character error rate.
|
||||
|
||||
Tests that reload a module after changing environment variables must restore
|
||||
the environment before reloading the original state. Otherwise a temporary
|
||||
cache path survives teardown and later model tests silently attempt downloads.
|
||||
Dotenv configuration tests must clear every fixture variable from the invoked
|
||||
process environment. Seed conflicting inherited values to prove isolation;
|
||||
clearing only some fields lets the remaining host settings override the fixture.
|
||||
|
||||
Cross-platform tests must use host-absolute synthetic interpreter paths and
|
||||
explicit unchanged decodes when comparing stored pixels. Execute Ubuntu Bash
|
||||
workflow harnesses only on POSIX hosts with Bash; keep their pure Python checks
|
||||
active on every platform. Round floating-point image output before uint8
|
||||
conversion when quantization must preserve constant values across OpenCV builds.
|
||||
OpenCV 5 draws Hershey `putText` glyphs smaller and heavier than 4.x, so a
|
||||
calibrated template drawn with `putText` ships as a committed PNG whose recipe
|
||||
refuses any OpenCV but 4.x (`scripts/render_video_hershey_templates.py`,
|
||||
`scripts/build_openart_alpha.py`); never render one at import time.
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"WebFetch",
|
||||
"WebSearch"
|
||||
]
|
||||
},
|
||||
"enabledPlugins": {
|
||||
"pyright-lsp@claude-plugins-official": true,
|
||||
"code-simplifier@claude-plugins-official": true,
|
||||
"claude-md-management@claude-plugins-official": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
---
|
||||
name: provider-oracles
|
||||
description: Run hash-bound Google, OpenAI, Microsoft, or Meta watermark-oracle checks for this repository. Use for provider verification, multi-account Gemini checks, isolated Playwright checks, or recording oracle evidence.
|
||||
metadata:
|
||||
internal: true
|
||||
---
|
||||
|
||||
# Provider oracles
|
||||
|
||||
Read `docs/provider-oracles.md` before acting. Use `scripts/provider_oracles.py`
|
||||
for surface discovery, slot validation, upload preparation, result recording,
|
||||
and integrity verification.
|
||||
|
||||
Named slots live in the gitignored repository-local
|
||||
`.oracle-slots.json`. Never commit that file. Pass `--slot NAME` to use it;
|
||||
pass `--slots PATH` only to override the local path.
|
||||
|
||||
Never substitute one provider's negative verdict for another provider's signal.
|
||||
Do not optimize candidates adaptively against an external oracle.
|
||||
|
||||
## Account continuation
|
||||
|
||||
A request to verify a bounded set of files with a provider authorizes using that
|
||||
provider's configured account slots unless the user limits the account scope.
|
||||
Before starting, identify the finite set of available authenticated slots and
|
||||
prepare one immutable batch per slot. After a quota refusal or service failure,
|
||||
record the exact response, leave that slot's remaining rows unsubmitted, and
|
||||
continue automatically through the remaining authorized slots. Do not stop to
|
||||
request permission for each account or file already covered by the task.
|
||||
|
||||
Submit each frozen artifact at most once per slot in that pass. Preserve
|
||||
inconclusive results and disagreements between accounts. Stop when the bounded
|
||||
plan is complete or no authorized usable slot remains; report remaining rows.
|
||||
New logins, another provider, a new network route, or another pass after the plan
|
||||
is exhausted require authorization unless already requested. Account
|
||||
continuation does not authorize changing candidate pixels or erasing failures.
|
||||
|
||||
## Default surface priority
|
||||
|
||||
Prefer an official API adapter whenever that provider, media type, and configured
|
||||
slot support it. Use Web only when no usable API route exists or the operator
|
||||
explicitly requests the Web surface as a second check. For OpenAI and Microsoft,
|
||||
the default plans are API then Web. Google and Meta currently have only usable
|
||||
Web routes in this tooling. Inspect the order with `plan PROVIDER`.
|
||||
|
||||
The priority is a selection policy, not automatic failover. An API refusal,
|
||||
quota response, transport error, or indeterminate verdict must be recorded and
|
||||
reported. Do not submit the same artifact to Web unless the operator separately
|
||||
authorizes that next surface.
|
||||
|
||||
## Route by surface
|
||||
|
||||
### Google Gemini
|
||||
|
||||
Use the user's real authenticated Chrome. Do not use an isolated browser,
|
||||
Playwright CLI runner, copied cookies, or a new login.
|
||||
|
||||
1. Read `~/Documents/GitHub/claude-config/docs/browser-automation.md` and select
|
||||
the authenticated-Chrome capability.
|
||||
2. Validate the named slot. Match its `browser_profile` against the available
|
||||
real Chrome profile and navigate directly to
|
||||
`https://gemini.google.com/u/<google_account_index>/app`.
|
||||
3. Prepare an immutable `gemini-web` batch outside the repository.
|
||||
For every image check, visually inspect the source first. Remove every visible
|
||||
AI-provider logo or label with the project's visible-mark remover, derive all
|
||||
paired controls and candidates from that cleaned image, and let `prepare` strip
|
||||
AI metadata. Inspect the prepared upload again and do not submit it if a visible
|
||||
provider mark remains. A model answer based on a visible logo is not a SynthID
|
||||
result.
|
||||
4. Confirm that the prepared files and provider are covered by the user's
|
||||
request or existing upload approval. Ask only if that scope is missing or
|
||||
changed; do not request confirmation again for each file or account in the
|
||||
authorized plan. Do not upload the source path directly.
|
||||
5. Attach one prepared upload and ask: `Does this image/video/audio contain a
|
||||
SynthID watermark? Use the SynthID verification tool only; do not infer from
|
||||
visible content, logos, text, or metadata.` Do not substitute a general
|
||||
Google-AI-origin question; it permits visual-content reasoning instead of a
|
||||
SynthID verdict.
|
||||
6. Read the settled SynthID tool outcome, not the surrounding model reasoning.
|
||||
Preserve unclear, quota, and refusal wording rather than interpreting it as
|
||||
clean.
|
||||
7. Record the verbatim response and verify the completed batch.
|
||||
|
||||
If a planned Chrome profile or `/u/N/` account is not already authenticated,
|
||||
record that it is unavailable and continue with another authorized existing
|
||||
session. Ask the user only when no usable authorized session remains. Do not
|
||||
start a new login.
|
||||
|
||||
### OpenAI, Microsoft, and Meta Web
|
||||
|
||||
Use the isolated Playwright runner, never the user's real Chrome:
|
||||
|
||||
```bash
|
||||
uv run python scripts/provider_oracles.py run-web /outside/repo/batch/manifest.json \
|
||||
--acknowledge-uploads --env-file .env
|
||||
```
|
||||
|
||||
The runner is headed by default because OpenAI's Cloudflare gate blocks the
|
||||
same isolated Chromium in headless mode. Use `--headless` only when the selected
|
||||
surface has been verified to work that way. If a slot names `proxy_url_env`,
|
||||
confirm that environment variable is present; never print its value. The runner
|
||||
uses one context, one route, fresh page navigation per upload, and no retry.
|
||||
CAPTCHA, bot checks, throttling, transport failure, and unknown page wording
|
||||
remain distinct recorded outcomes.
|
||||
|
||||
Wait for the surface-specific upload readiness that the runner implements.
|
||||
OpenAI completes at page load but keeps background requests alive, while
|
||||
Microsoft and Meta require network idle. Do not replace those conditions with
|
||||
one shared wait. Treat OpenAI's exact `Something went wrong` response as a
|
||||
settled `indeterminate` result, not as a clean result or a reason to retry.
|
||||
|
||||
For ThorData residential proxy mode, keep `THORDATA_ROUTE_US`,
|
||||
`THORDATA_ROUTE_DE`, `THORDATA_ROUTE_GB`, `THORDATA_ROUTE_NL`, and
|
||||
`THORDATA_ROUTE_JP` URLs in the gitignored `.env`;
|
||||
OpenAI, Microsoft, and Meta Web slots may each name one of them through
|
||||
`proxy_url_env`. Encode an explicit two-letter ISO country code in each route,
|
||||
verify egress before relying on it, and never automatically change route after
|
||||
a refusal. Bound a batch before starting.
|
||||
|
||||
ThorData user-and-password endpoints without `sessid` rotate the exit on each
|
||||
request, which breaks a browser page whose resources must share one network
|
||||
identity. The runner therefore derives one sticky `sessid` from the immutable
|
||||
manifest hash and keeps it for that batch. A new prepared batch gets a new
|
||||
session; rerunning or resuming the same batch keeps its session while the proxy
|
||||
retains it. An explicit `sessid` in the configured route is preserved. This is
|
||||
session continuity, not permission to retry a refused batch on another route.
|
||||
|
||||
### OpenAI API
|
||||
|
||||
Use `check-openai` with one explicit upload acknowledgement. Keep named API keys
|
||||
as `OPENAI_API_KEY_1`, `OPENAI_API_KEY_2`, and `OPENAI_API_KEY_3` in the local `.env`, and select
|
||||
only their variable names through an `openai-api` slot's `api_key_env`. Never
|
||||
print a key or copy one into a slot or manifest. Continue through authorized
|
||||
named API slots according to the bounded account plan above; keep a separate
|
||||
batch for each. API slots use a direct SDK transport that ignores environment proxy
|
||||
settings. The current adapter is image-only.
|
||||
|
||||
### Microsoft API
|
||||
|
||||
Use `check-microsoft` with one explicit acknowledgement and one private Azure
|
||||
Blob HTTPS URI whose bytes exactly match the local source. Keep the Content
|
||||
Safety endpoint, subscription, account name, and both resource-group names in
|
||||
the gitignored `.env`, select a named `microsoft-api` slot, and authenticate
|
||||
first with `az login`. Give the Content Safety system identity `Storage Blob
|
||||
Data Reader` on the input storage account. The adapter reads resource keys only
|
||||
into memory through Azure CLI, never logs them, ignores environment proxy
|
||||
settings, makes one submit request with no retry, and polls only the returned
|
||||
operation. Keep Microsoft's Watermark and C2PA markers as separate verdicts.
|
||||
|
||||
## Evidence rules
|
||||
|
||||
- Upload only files under the prepared batch's `uploads/` directory.
|
||||
- Keep `manifest.json` and `manifest.sha256` immutable.
|
||||
- Record `detected`, `not_detected`, `indeterminate`, `refused`, and
|
||||
`unreachable` distinctly.
|
||||
- Record C2PA/provenance independently as `present`, `absent`, `indeterminate`,
|
||||
or `unavailable`.
|
||||
- Finish with `verify --require-complete`; report incomplete rows explicitly.
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
name: signal-discovery
|
||||
description: Discover missing AI provenance and watermark coverage in the remove-ai-watermarks library from fresh or historical corpora, then validate detection and removal parity. Use for corpus gap mining, periodic signal audits, new-provider discovery, or deciding whether old retained data must be replayed. Do not use for ordinary per-file identification or removal.
|
||||
metadata:
|
||||
internal: true
|
||||
---
|
||||
|
||||
# Signal discovery
|
||||
|
||||
Work from the `remove-ai-watermarks` repository root. This skill discovers and
|
||||
closes library coverage gaps. Implementation, tests, fixtures, and
|
||||
documentation changes belong in this library; do not patch a downstream
|
||||
consumer to compensate for a detector or cleaner gap.
|
||||
|
||||
Treat retained inputs as sensitive and read-only. Keep reports, contact sheets,
|
||||
sidecars, and derived samples under the library's gitignored `.local-eval/`.
|
||||
Never commit a retained input or an artifact derived from one.
|
||||
Use only publication-cleared or synthetic files for tracked fixtures.
|
||||
|
||||
Before acting, read:
|
||||
|
||||
- `CLAUDE.md` and `.claude/rules/development.md`;
|
||||
- `docs/verification-plan.md`;
|
||||
- `docs/supported-signals.md` and `docs/watermarking-landscape.md`;
|
||||
- [references/coverage-and-cadence.md](references/coverage-and-cadence.md).
|
||||
|
||||
## Choose the run
|
||||
|
||||
Do not call `corpus_gap_scan.py` a complete signal audit. Select every mode the
|
||||
question requires:
|
||||
|
||||
1. **Fresh discovery** finds new providers, serializations, labels, and formats
|
||||
in objects added since the preceding review.
|
||||
2. **Historical replay** reruns current code on the complete retained history.
|
||||
Use it after detection-path or format-support changes and on the periodic
|
||||
backstop defined in the reference.
|
||||
3. **Removal parity** proves that every detected removable signal is cleared
|
||||
without violating the signal family's fidelity invariant.
|
||||
4. **Calibration or provider oracle** is required where the corpus has no
|
||||
self-evident answer key. Never treat a local negative as proof that a
|
||||
proprietary watermark is absent.
|
||||
|
||||
Start by inventorying the current code, not a remembered list. Enumerate image
|
||||
and video visible-mark registries, metadata readers and strippers, local
|
||||
invisible decoders, explicit invisible vendor routes, supported containers, and
|
||||
the stable signal families emitted by `identify`. Reconcile that inventory with
|
||||
the coverage matrix before interpreting any green result. A hand-written marker
|
||||
list or report family map that omits a current registry member is itself a
|
||||
finding.
|
||||
|
||||
## Run protocol
|
||||
|
||||
1. Record the library commit, version, enabled extras, corpus date range, and
|
||||
exact commands in an untracked run directory. Preserve the pre-run Git
|
||||
status so unrelated work is not attributed to the audit.
|
||||
2. Refresh the local retained corpus through its owning, external collection
|
||||
process when the task calls for fresh data. Use an explicit gitignored
|
||||
destination, do not delete local objects, and finish with a zero-delta pass.
|
||||
Fetch paired outputs only for a removal or complaint investigation.
|
||||
3. Run the applicable fresh and historical discovery modes from the reference.
|
||||
Use `corpus_gap_scan.py --since YYYY-MM-DD` for the overlapping fresh window
|
||||
and omit `--since` for the complete history. Report counts per input shape
|
||||
and per outcome; an example does not establish coverage. Give every code and
|
||||
corpus snapshot a distinct `--report` path. The adjacent
|
||||
`.progress.jsonl` checkpoint is the live progress source and makes the same
|
||||
command resumable; use `--restart` only when intentionally discarding it.
|
||||
4. Triage every candidate into one of: parser/serialization gap, missing vendor
|
||||
attribution, new visible mark, possible invisible signal, format/read error,
|
||||
expected non-AI provenance, or false candidate. Preserve unresolved items as
|
||||
unresolved.
|
||||
5. Reproduce an actionable gap with a failing test before changing code. Derive
|
||||
the smallest publication-safe fixture from a controlled source or construct
|
||||
it synthetically; do not move the retained file into a tracked path.
|
||||
6. Implement the fix in the library subsystem that owns it. Update the library
|
||||
registry, parser, remover, public docs, and published agent skill when their
|
||||
contracts move. Updating a downstream consumer to take a released version is
|
||||
a later, separately scoped task.
|
||||
7. Rerun the affected focused audit and the complete historical regression
|
||||
mode. A detection change must name the files expected to move before the
|
||||
after-run; classify lost signals separately from intended new detections.
|
||||
8. Run removal parity for every newly detected removable signal. A quiet
|
||||
post-removal detector is necessary but not always sufficient: check
|
||||
pixel/stream invariants and visually inspect residual-prone visible marks.
|
||||
9. Run `bash maintain.sh`. Report evidence limits, incomplete rows, crashes,
|
||||
unsupported formats, and oracle-unavailable cohorts explicitly.
|
||||
|
||||
External oracle uploads are separate mutations. Use the `provider-oracles`
|
||||
skill only for a bounded, already-selected candidate, and obtain the required
|
||||
action-time upload approval. Do not adapt candidates against an oracle, retry
|
||||
automatically, or substitute one provider's verdict for another's.
|
||||
@@ -0,0 +1,63 @@
|
||||
# Coverage and cadence
|
||||
|
||||
Use this reference to choose the audit modes and decide whether current-only or
|
||||
historical data is sufficient.
|
||||
|
||||
## Coverage matrix
|
||||
|
||||
| Surface | Discovery evidence | Regression check | Removal proof |
|
||||
| --- | --- | --- | --- |
|
||||
| C2PA, EXIF, XMP, IPTC, PNG text, TC260, soft bindings, and container metadata | `scripts/corpus_gap_scan.py` plus review of unattributed and partially attributed reports | `scripts/sidecar_regression.py`; compare record-based and file-based identification when collection changes | `scripts/metadata_removal_audit.py`; signal absent after strip and decoded pixels/streams unchanged |
|
||||
| Registered visible image marks | `scripts/visible_positives.py` records strict and metadata-corroborated results, metadata cohorts, and blinded native-detail bands for uncovered cohorts plus an unbiased quiet sample | `scripts/visible_eval.py` exposes registry members without adjudicated scope, registered examples, and full-corpus crossfire | `scripts/visible_removal_audit.py`; same-mark redetection plus visual residual review and fill-quality checks |
|
||||
| Candidate China-AIGC visible marks | `scripts/vendor_cohort_harvest.py` partitions by producer identity without using pixels; inspect full-width top and bottom sheets | `scripts/vendor_mark_calibrate.py` against independently labelled positives, clean negatives, and neighbouring marks | Register only after the candidate gate survives full-corpus crossfire; then use the normal visible-removal audit |
|
||||
| Registered visible video marks | Review provider-labelled or controlled complete clips, not isolated frames alone | Full-clip tests and the local real-provider audit described in `docs/verification-plan.md` | Preserve sequence, timing, duration, and audio; second-pass detection must be quiet |
|
||||
| Locally decodable invisible image marks, including open DWT-DCT and TrustMark | Positive-control-gated corpus or constructed carriers; a negative on an uncontrolled carrier is inconclusive | `scripts/watermark_benchmark.py` and the relevant decoder tests with the required extras installed | Re-run the same decoder on output and retain a positive control in the same run |
|
||||
| Proprietary image marks: Google SynthID, Microsoft InvisMark, Meta Content Seal | Provider provenance can select a cohort but is not a local pixel decode; use bounded provider-labelled candidates | Hash-bound provider-oracle batches when a new route, profile, or claimed signal is evaluated | Source-positive and output-negative verdicts from that same provider; record C2PA separately from the pixel watermark |
|
||||
| Invisible video and audio marks | Matched provider or open-model cohorts with codec and degradation controls | Benchmark-kernel audio/video rows and temporal aggregation checks | Preserve the non-target stream; use the matching oracle and a control-positive run |
|
||||
| Explicit vendor regeneration with no detectable local signal | This is a user assertion or research cohort, not a discovered signal | Verify routing and profile selection separately from detection | Quality and provider-oracle evidence may certify a cohort, but must not create a local detection claim |
|
||||
|
||||
`classify` is a pixel-origin guess, not provenance, and must not be counted as a
|
||||
new signal. `erase --region` is user-directed editing, not automatic watermark
|
||||
detection, and is outside this audit.
|
||||
|
||||
## Gaps the legacy scanner cannot settle alone
|
||||
|
||||
`corpus_gap_scan.py` is a metadata serialization probe. It reports raw markers,
|
||||
input shapes, identify errors, integrity clashes, unattributed results, and
|
||||
blind-marker candidates, but it still cannot settle other useful classes, so
|
||||
inspect them separately:
|
||||
|
||||
- a known generic signal with missing or wrong vendor attribution;
|
||||
- a newly coexisting marker on a file that already has another AI verdict;
|
||||
- an entirely novel marker absent from the scanner's hand-written list;
|
||||
- visible image or video labels;
|
||||
- proprietary invisible marks without a local decoder;
|
||||
- detection/removal parity failures;
|
||||
- decoder crashes, unsupported extensions, and incomplete reads;
|
||||
- drift between stored identify sidecars and the current library;
|
||||
- crossfire where a new detector fires on an older neighbouring cohort.
|
||||
|
||||
Marker matching must remain metadata-region bounded to avoid random hits in
|
||||
compressed pixels, but its inventory should be reconciled with the current
|
||||
metadata constants and registry on every audit. Do not infer completeness from
|
||||
`No gap candidates` until the other rows of the matrix also ran.
|
||||
|
||||
For a visible discovery pass, use a fresh report path and pass `--sheets` to
|
||||
`visible_positives.py`. Review the numbered PNG files before opening
|
||||
`MANIFEST_DO_NOT_OPEN.csv`; metadata cohorts select where a mark may occur but do
|
||||
not label the pixels. The separate `unbiased-quiet` stratum is required because a
|
||||
new provider may carry neither a mapped producer code nor readable provenance.
|
||||
|
||||
## When to replay old data
|
||||
|
||||
Yes, old data must be rerun. Current code can learn to parse an old
|
||||
serialization, and a new detector can crossfire on an old provider cohort even
|
||||
when no new object demonstrates either problem. The canonical intervals,
|
||||
change triggers, and sampling rules live in
|
||||
[`docs/verification-plan.md#signal-discovery-cadence`](../../../../docs/verification-plan.md#signal-discovery-cadence).
|
||||
|
||||
Keep dated reports immutable enough to compare runs. Record the library SHA and
|
||||
version, corpus minimum and maximum dates, input counts per media/format shape,
|
||||
enabled extras, command line, and output paths. Never commit operational corpus
|
||||
counts or retained identifiers; summarize only durable conclusions in the
|
||||
canonical research document.
|
||||
@@ -2,6 +2,50 @@
|
||||
# Get yours at: https://huggingface.co/settings/tokens
|
||||
# HF_TOKEN=
|
||||
|
||||
# OpenAI API key (optional; only for explicit verify-openai-synthid uploads)
|
||||
# OpenAI API key (optional; only for development scripts/openai_provenance_check.py)
|
||||
# Create and manage keys at: https://platform.openai.com/api-keys
|
||||
# OPENAI_API_KEY=
|
||||
|
||||
# GitHub token (optional; only for scripts/sync_c2pa_soft_bindings.py)
|
||||
# Raises the api.github.com rate limit from 60 to 5000 requests/hour. GH_TOKEN is read as a fallback.
|
||||
# GITHUB_TOKEN=
|
||||
|
||||
# Local photo-classifier weights (optional; otherwise fetched from Hugging Face)
|
||||
# Directory contents are documented in docs/photo-classify.md.
|
||||
# RAIW_CLASSIFY_WEIGHTS=/path/to/classifier-weights
|
||||
|
||||
# Local VideoSeal checkpoint cache (optional; development benchmarks only)
|
||||
# Defaults to ~/.cache/remove-ai-watermarks-dev/videoseal.
|
||||
# VIDEOSEAL_CACHE_DIR=/path/to/videoseal-cache
|
||||
|
||||
# Hugging Face cache root (optional; shared by model downloads and prompt caches)
|
||||
# HF_HOME=/path/to/huggingface-cache
|
||||
# Fallback cache root when HF_HOME is unset (optional)
|
||||
# XDG_CACHE_HOME=/path/to/cache
|
||||
|
||||
# Gemini API key (optional; only for generating Google provider samples by hand)
|
||||
# Paid tier: Nano Banana 2 Lite and Gemini Omni Flash have no free tier.
|
||||
# Create and manage keys at: https://aistudio.google.com/apikey
|
||||
# GEMINI_API_KEY=
|
||||
|
||||
# Named OpenAI keys for provider-oracle slots (optional; keep each value secret)
|
||||
# OPENAI_API_KEY_1=
|
||||
# OPENAI_API_KEY_2=
|
||||
# OPENAI_API_KEY_3=
|
||||
|
||||
# Azure Content Safety resource for direct Microsoft Content Provenance checks
|
||||
# Resource keys are read into memory through the authenticated Azure CLI.
|
||||
# AZURE_CONTENT_SAFETY_ENDPOINT=https://your-resource.cognitiveservices.azure.com/
|
||||
# AZURE_CONTENT_SAFETY_SUBSCRIPTION_ID=
|
||||
# AZURE_CONTENT_SAFETY_RESOURCE_GROUP=
|
||||
# AZURE_CONTENT_SAFETY_ACCOUNT_NAME=
|
||||
# AZURE_ORACLE_STORAGE_RESOURCE_GROUP=
|
||||
|
||||
# ThorData residential proxies (optional; only for explicitly selected Web oracle routes)
|
||||
# Generate these URLs in the ThorData endpoint generator; do not commit credentials.
|
||||
# The Web runner pins a rotating user-and-password route to one sticky session per manifest.
|
||||
# THORDATA_ROUTE_US=http://td-customer-USERNAME-country-US:PASSWORD@HOST.pr.thordata.net:9999
|
||||
# THORDATA_ROUTE_DE=http://td-customer-USERNAME-country-DE:PASSWORD@HOST.pr.thordata.net:9999
|
||||
# THORDATA_ROUTE_GB=http://td-customer-USERNAME-country-GB:PASSWORD@HOST.pr.thordata.net:9999
|
||||
# THORDATA_ROUTE_NL=http://td-customer-USERNAME-country-NL:PASSWORD@HOST.pr.thordata.net:9999
|
||||
# THORDATA_ROUTE_JP=http://td-customer-USERNAME-country-JP:PASSWORD@HOST.pr.thordata.net:9999
|
||||
@@ -14,18 +14,22 @@ updates:
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
# The `all` extra must remain jointly resolvable. These candidates violate
|
||||
# upstream constraints: TrustMark still requires NumPy 1.x, OpenCV 4.12+
|
||||
# requires NumPy 2 on newer Python versions, and this project keeps tokenizers
|
||||
# below 0.23 for the stable Transformers path. Keep Dependabot on the
|
||||
# compatible lines until those constraints move.
|
||||
# The `all` extra must remain jointly resolvable. PaddleX 3.7 (the OCR
|
||||
# extra) requires NumPy <2.4, mirrored by `constraint-dependencies` in
|
||||
# pyproject.toml, and this project keeps tokenizers below 0.23 for the stable
|
||||
# Transformers path. Keep Dependabot on the compatible lines until those
|
||||
# constraints move. Transformers 5.16+ requires tokenizers 0.23.1+,
|
||||
# and Diffusers requires huggingface-hub <2. Mirror the manifest bounds.
|
||||
ignore:
|
||||
- dependency-name: "numpy"
|
||||
versions:
|
||||
- ">=2"
|
||||
- dependency-name: "opencv-python-headless"
|
||||
- ">=2.4"
|
||||
- dependency-name: "transformers"
|
||||
versions:
|
||||
- ">=4.12"
|
||||
- ">=5.16"
|
||||
- dependency-name: "huggingface-hub"
|
||||
versions:
|
||||
- ">=2"
|
||||
- dependency-name: "tokenizers"
|
||||
versions:
|
||||
- ">=0.23"
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
name: Distribute on release
|
||||
|
||||
# Fans a published GitHub Release out to the channels that need a nudge.
|
||||
# PyPI is handled by publish.yml. This workflow event-drives the three channels
|
||||
# PyPI is handled by publish.yml. This workflow event-drives the four channels
|
||||
# that would otherwise be manual:
|
||||
# - Homebrew tap: rewrite the formula's url + sha256 to the new sdist.
|
||||
# - HF Space: factory-rebuild the version already pinned in the Space repo.
|
||||
# - ComfyUI: sync, test, and publish the node against the exact library release.
|
||||
# All wait for the freshly published sdist to appear on PyPI first, since the
|
||||
# Release event fires in parallel with publish.yml's upload.
|
||||
# - ClawHub: publish the agent skill when its own version moved.
|
||||
# All wait for the freshly published distribution to appear in both PyPI APIs
|
||||
# first, since the Release event fires in parallel with publish.yml's upload.
|
||||
|
||||
on:
|
||||
release:
|
||||
@@ -20,7 +21,12 @@ on:
|
||||
|
||||
jobs:
|
||||
resolve:
|
||||
# Model-only prereleases must not reach PyPI, Homebrew, Spaces, or ComfyUI.
|
||||
if: ${{ github.event_name != 'release' || (startsWith(github.event.release.tag_name, 'v') && !github.event.release.prerelease) }}
|
||||
runs-on: ubuntu-latest
|
||||
# Thirty attempts may spend up to 20 seconds each in the JSON request,
|
||||
# simple-index request, and inter-attempt delay. Leave bounded headroom.
|
||||
timeout-minutes: 25
|
||||
outputs:
|
||||
version: ${{ steps.v.outputs.version }}
|
||||
url: ${{ steps.sdist.outputs.url }}
|
||||
@@ -41,26 +47,56 @@ jobs:
|
||||
echo "version=$v" >> "$GITHUB_OUTPUT"
|
||||
echo "Target version: $v"
|
||||
|
||||
- name: Wait for the sdist on PyPI, capture url + sha256
|
||||
- name: Record distributed release
|
||||
env:
|
||||
VERSION: ${{ steps.v.outputs.version }}
|
||||
run: |
|
||||
python3 - <<'PYTHON'
|
||||
import json, os
|
||||
from pathlib import Path
|
||||
Path("release-identity.json").write_text(json.dumps({
|
||||
"version": os.environ["VERSION"],
|
||||
"run_id": os.environ["GITHUB_RUN_ID"],
|
||||
"run_attempt": os.environ["GITHUB_RUN_ATTEMPT"],
|
||||
}), encoding="utf-8")
|
||||
PYTHON
|
||||
|
||||
- name: Upload distributed release identity
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: distributed-release-${{ github.run_attempt }}
|
||||
path: release-identity.json
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Wait for the PyPI JSON and simple indexes, capture sdist metadata
|
||||
id: sdist
|
||||
run: |
|
||||
set -euo pipefail
|
||||
v="${{ steps.v.outputs.version }}"
|
||||
for i in $(seq 1 30); do
|
||||
if rel=$(curl -fsSL "https://pypi.org/pypi/remove-ai-watermarks/$v/json" 2>/dev/null); then
|
||||
url=$(echo "$rel" | python3 -c "import sys,json;d=json.load(sys.stdin);print(next((u['url'] for u in d['urls'] if u['packagetype']=='sdist'),''))")
|
||||
sha=$(echo "$rel" | python3 -c "import sys,json;d=json.load(sys.stdin);print(next((u['digests']['sha256'] for u in d['urls'] if u['packagetype']=='sdist'),''))")
|
||||
if rel=$(curl --connect-timeout 10 --max-time 20 -fsSL "https://pypi.org/pypi/remove-ai-watermarks/$v/json" 2>/dev/null); then
|
||||
read -r url sha <<< "$(echo "$rel" | python3 -c "import sys,json;u=next((u for u in json.load(sys.stdin)['urls'] if u['packagetype']=='sdist'),{});print(u.get('url',''),u.get('digests',{}).get('sha256',''))")"
|
||||
if [ -n "$url" ] && [ -n "$sha" ]; then
|
||||
echo "url=$url" >> "$GITHUB_OUTPUT"
|
||||
echo "sha=$sha" >> "$GITHUB_OUTPUT"
|
||||
echo "Found sdist for $v"
|
||||
exit 0
|
||||
# PyPI's JSON API can show a freshly uploaded file before the
|
||||
# simple index used by pip has caught up. Downstream release
|
||||
# jobs install by package version, so waiting on JSON alone
|
||||
# creates a release-time race for them.
|
||||
simple=$(curl --connect-timeout 10 --max-time 20 -fsSL https://pypi.org/simple/remove-ai-watermarks/ 2>/dev/null || true)
|
||||
artifact=${url##*/}
|
||||
if [[ "$simple" == *"$artifact"* ]]; then
|
||||
echo "url=$url" >> "$GITHUB_OUTPUT"
|
||||
echo "sha=$sha" >> "$GITHUB_OUTPUT"
|
||||
echo "PyPI JSON and simple indexes list $v"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
echo "sdist for $v not on PyPI yet (attempt $i), waiting..."
|
||||
sleep 20
|
||||
if [ "$i" -lt 30 ]; then
|
||||
echo "PyPI indexes do not both list $v yet (attempt $i), waiting..."
|
||||
sleep 20
|
||||
fi
|
||||
done
|
||||
echo "::error::sdist for $v did not appear on PyPI in time"
|
||||
echo "::error::PyPI JSON and simple indexes did not both list $v in time"
|
||||
exit 1
|
||||
|
||||
homebrew:
|
||||
@@ -92,10 +128,91 @@ jobs:
|
||||
needs: resolve
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Factory-rebuild the HuggingFace Space
|
||||
- name: Bump the Space pin, then factory-rebuild
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RAIW_HF_SPACE_TOKEN }}
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
||||
VERSION: ${{ needs.resolve.outputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
repo=wiltodelta/raiw-hf-space
|
||||
workflow=pin-library.yml
|
||||
|
||||
if [ -z "${GH_TOKEN:-}" ]; then
|
||||
echo "RAIW_HF_SPACE_TOKEN secret is not set; skipping the pin bump."
|
||||
else
|
||||
dispatched_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
gh workflow run "$workflow" \
|
||||
--repo "$repo" \
|
||||
--ref main \
|
||||
--raw-field library_version="$VERSION"
|
||||
|
||||
run_id=""
|
||||
for attempt in $(seq 1 30); do
|
||||
run_id=$(
|
||||
gh run list \
|
||||
--repo "$repo" \
|
||||
--workflow "$workflow" \
|
||||
--event workflow_dispatch \
|
||||
--limit 10 \
|
||||
--json databaseId,createdAt \
|
||||
| python3 -c "import json,sys; t=sys.argv[1]; print(next((r['databaseId'] for r in json.load(sys.stdin) if r['createdAt']>=t), ''))" \
|
||||
"$dispatched_at"
|
||||
)
|
||||
if [ -n "$run_id" ]; then
|
||||
break
|
||||
fi
|
||||
echo "Space pin run not visible yet (attempt $attempt), waiting..."
|
||||
sleep 2
|
||||
done
|
||||
if [ -z "$run_id" ]; then
|
||||
echo "::error::The dispatched Space pin run did not appear"
|
||||
exit 1
|
||||
fi
|
||||
for attempt in $(seq 1 180); do
|
||||
read -r status conclusion <<< "$(
|
||||
gh run view "$run_id" \
|
||||
--repo "$repo" \
|
||||
--json status,conclusion \
|
||||
--jq '[.status, (.conclusion // "")] | @tsv'
|
||||
)"
|
||||
if [ "$status" = "completed" ]; then
|
||||
if [ "$conclusion" = "success" ]; then
|
||||
echo "Space pin run $run_id succeeded"
|
||||
break
|
||||
fi
|
||||
echo "::error::Space pin run $run_id completed with $conclusion"
|
||||
exit 1
|
||||
fi
|
||||
echo "Space pin run $run_id is $status (attempt $attempt), waiting..."
|
||||
sleep 5
|
||||
done
|
||||
if [ "${status:-}" != "completed" ]; then
|
||||
echo "::error::Space pin run $run_id did not finish in time"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Waiting for the Space requirements pin to show $VERSION..."
|
||||
pinned=""
|
||||
for attempt in $(seq 1 40); do
|
||||
reqs=$(curl -fsSL \
|
||||
"https://huggingface.co/spaces/wiltodelta/remove-ai-watermarks/raw/main/requirements.txt" \
|
||||
|| true)
|
||||
pinned=$(printf '%s\n' "$reqs" \
|
||||
| sed -n 's/^remove-ai-watermarks==\([0-9][0-9a-zA-Z.]*\).*/\1/p' \
|
||||
| head -1)
|
||||
echo " attempt $attempt: Space pin=$pinned"
|
||||
if [ "$pinned" = "$VERSION" ]; then
|
||||
break
|
||||
fi
|
||||
sleep 15
|
||||
done
|
||||
if [ "$pinned" != "$VERSION" ]; then
|
||||
echo "::error::Space requirements.txt still pinned to ${pinned:-<none>}, not $VERSION"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
python3 -m pip install --quiet huggingface_hub
|
||||
python3 - <<'PY'
|
||||
import os
|
||||
@@ -106,6 +223,44 @@ jobs:
|
||||
print("HF Space factory rebuild triggered")
|
||||
PY
|
||||
|
||||
clawhub:
|
||||
needs: resolve
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Publish the agent skill when its version moved
|
||||
env:
|
||||
CLAWHUB_TOKEN: ${{ secrets.CLAWHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "${CLAWHUB_TOKEN:-}" ]; then
|
||||
echo "CLAWHUB_TOKEN secret is not set; skipping ClawHub publish."
|
||||
exit 0
|
||||
fi
|
||||
npm install --silent --no-fund --no-audit clawhub
|
||||
skill_version=$(python3 - <<'PY'
|
||||
import re
|
||||
text = open("skills/remove-ai-watermarks/SKILL.md", encoding="utf-8").read()
|
||||
m = re.search(r"(?m)^ version:\s*[\"']?([^\"'\s]+)", text)
|
||||
assert m, "skill version missing"
|
||||
print(m.group(1))
|
||||
PY
|
||||
)
|
||||
./node_modules/.bin/clawhub login --token "$CLAWHUB_TOKEN" >/dev/null
|
||||
published=$(./node_modules/.bin/clawhub inspect remove-ai-watermarks --file SKILL.md 2>/dev/null \
|
||||
| python3 -c 'import re,sys; t=sys.stdin.read(); m=re.search(r"(?m)^ version:\s*[\"'"'"']?([^\"'"'"'\s]+)", t); print(m.group(1) if m else "")' || true)
|
||||
if [ "$published" = "$skill_version" ]; then
|
||||
echo "ClawHub already at $skill_version; nothing to publish."
|
||||
exit 0
|
||||
fi
|
||||
echo "ClawHub at ${published:-<none>}; publishing skill $skill_version."
|
||||
./node_modules/.bin/clawhub skill publish skills/remove-ai-watermarks \
|
||||
--slug remove-ai-watermarks \
|
||||
--name "Remove AI Watermarks" \
|
||||
--version "$skill_version" \
|
||||
--changelog "Agent skill sync for the ${{ github.event.release.name || github.ref_name }} release."
|
||||
echo "Submitted; ClawHub runs security scans before the listing goes public."
|
||||
|
||||
comfyui:
|
||||
needs: resolve
|
||||
runs-on: ubuntu-latest
|
||||
@@ -157,7 +312,12 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for attempt in $(seq 1 180); do
|
||||
# 0.40.0's node sync finished after this waiter: 180 * 5s is 15 min,
|
||||
# and the node suite plus registry upload ran longer. Keep the
|
||||
# workflow wait, then accept a registry row for this library version
|
||||
# so a late-but-published node is not reported as a missing release.
|
||||
version="${{ needs.resolve.outputs.version }}"
|
||||
for attempt in $(seq 1 360); do
|
||||
read -r status conclusion <<< "$(
|
||||
gh run view "$run_id" \
|
||||
--repo "$repo" \
|
||||
@@ -175,5 +335,39 @@ jobs:
|
||||
echo "ComfyUI sync run $run_id is $status (attempt $attempt), waiting..."
|
||||
sleep 5
|
||||
done
|
||||
echo "::error::ComfyUI sync run $run_id did not finish in time"
|
||||
echo "ComfyUI sync run $run_id did not finish in time; checking the registry."
|
||||
python3 -m pip install --quiet packaging
|
||||
if curl -fsSL "https://api.comfy.org/nodes/remove-ai-watermarks/versions" \
|
||||
| VERSION="$version" python3 -c '
|
||||
import json, os, sys
|
||||
from packaging.requirements import InvalidRequirement, Requirement
|
||||
from packaging.utils import canonicalize_name
|
||||
from packaging.version import Version
|
||||
version = os.environ["VERSION"]
|
||||
target = Version(version)
|
||||
def requires_release(value):
|
||||
try:
|
||||
requirement = Requirement(value)
|
||||
except InvalidRequirement:
|
||||
return False
|
||||
return (
|
||||
canonicalize_name(requirement.name) == "remove-ai-watermarks"
|
||||
and requirement.url is None
|
||||
and requirement.marker is None
|
||||
and target in requirement.specifier
|
||||
and any(s.operator == ">=" and Version(s.version) == target for s in requirement.specifier)
|
||||
)
|
||||
matches = [
|
||||
v for v in json.load(sys.stdin)
|
||||
if any(requires_release(d) for d in (v.get("dependencies") or []))
|
||||
]
|
||||
if not matches:
|
||||
sys.exit(1)
|
||||
newest = max(matches, key=lambda v: v.get("createdAt") or "")
|
||||
print("%s %s" % (newest.get("version"), newest.get("status")))
|
||||
'; then
|
||||
echo "Registry already lists a node for $version; the wait timed out after publish."
|
||||
exit 0
|
||||
fi
|
||||
echo "::error::ComfyUI sync run $run_id did not finish in time and the registry has no node for $version"
|
||||
exit 1
|
||||
@@ -0,0 +1,59 @@
|
||||
name: Publish image-source classifier to Hugging Face
|
||||
|
||||
# Manual only. The derivative weights live in a GitHub model-freeze release;
|
||||
# private training images and caches never enter this repository.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
confirm:
|
||||
description: "Type publish"
|
||||
required: true
|
||||
weights_tag:
|
||||
description: "GitHub Release tag holding openai-google-source-v1.npz"
|
||||
required: true
|
||||
default: image-source-freeze-2026-09-25-v1
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
if: ${{ github.event.inputs.confirm == 'publish' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Download derivative weights
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TAG: ${{ github.event.inputs.weights_tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p weights
|
||||
gh release download "$TAG" \
|
||||
--repo "${{ github.repository }}" \
|
||||
--pattern openai-google-source-v1.npz \
|
||||
--dir weights
|
||||
test -s weights/openai-google-source-v1.npz
|
||||
- name: Verify artifact hash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
expected="$(jq -r .artifact_sha256 docs/image-source-hf/metrics.json)"
|
||||
test "${#expected}" -eq 64
|
||||
echo "$expected weights/openai-google-source-v1.npz" | sha256sum --check
|
||||
- name: Publish to Hugging Face
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
||||
SHA: ${{ github.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "${HF_TOKEN:-}" ]; then
|
||||
echo "HF_TOKEN secret is not set" >&2
|
||||
exit 1
|
||||
fi
|
||||
uv run --no-project --with huggingface-hub \
|
||||
python scripts/publish_image_source_hf.py \
|
||||
--src weights \
|
||||
--message "Image-source classifier freeze (GitHub ${SHA:0:7})"
|
||||
@@ -6,8 +6,9 @@ name: Publish photo classifier to Hugging Face
|
||||
# Hub model: wiltodelta/raiw-photo-classify
|
||||
# Secret: HF_TOKEN, write-role Hugging Face token (same secret Space sync uses).
|
||||
#
|
||||
# Weights are not in git. Full mode downloads them from a GitHub Release
|
||||
# of this repository, then uploads with the card from docs/photo-classify-hf/.
|
||||
# Weights are not in git. Full mode downloads the frozen PyTorch files from a
|
||||
# GitHub Release, exports the vision-only ONNX graph, then uploads everything
|
||||
# with the card from docs/photo-classify-hf/.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -52,6 +53,14 @@ jobs:
|
||||
gh release download "$TAG" --repo "${{ github.repository }}" --dir weights
|
||||
ls -l weights
|
||||
|
||||
- name: Export the vision-only ONNX graph
|
||||
if: ${{ github.event.inputs.mode == 'full' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
uv run --extra classify --with onnx python scripts/export_photo_classify_onnx.py \
|
||||
--checkpoint weights/clip-l-ft.pt \
|
||||
--out weights/clip-l-ft-vision-fp32.onnx
|
||||
|
||||
- name: Publish to Hugging Face
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
name: Publish source classifier to Hugging Face
|
||||
|
||||
# Manual only. The derivative model weights live in a GitHub model-freeze
|
||||
# release; the private training corpus and caches never enter this repository.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
confirm:
|
||||
description: "Type publish"
|
||||
required: true
|
||||
weights_tag:
|
||||
description: "GitHub Release tag holding source-pipeline-mlp.npz"
|
||||
required: true
|
||||
default: source-classify-freeze-2026-09-16-r2
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
if: ${{ github.event.inputs.confirm == 'publish' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Download derivative model weights
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TAG: ${{ github.event.inputs.weights_tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p weights
|
||||
gh release download "$TAG" \
|
||||
--repo "${{ github.repository }}" \
|
||||
--pattern source-pipeline-mlp.npz \
|
||||
--dir weights
|
||||
test -s weights/source-pipeline-mlp.npz
|
||||
|
||||
- name: Verify the frozen artifact
|
||||
run: |
|
||||
set -euo pipefail
|
||||
expected="$(jq -r .artifact_sha256 docs/source-classify-hf/metrics.json)"
|
||||
test "${#expected}" -eq 64
|
||||
echo "$expected weights/source-pipeline-mlp.npz" | sha256sum --check
|
||||
|
||||
- name: Publish to Hugging Face
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
||||
SHA: ${{ github.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "${HF_TOKEN:-}" ]; then
|
||||
echo "HF_TOKEN secret is not set" >&2
|
||||
exit 1
|
||||
fi
|
||||
uv run --no-project --with huggingface-hub \
|
||||
python scripts/publish_source_classify_hf.py \
|
||||
--src weights \
|
||||
--message "Source-classify freeze (GitHub ${SHA:0:7})"
|
||||
@@ -10,6 +10,8 @@ permissions:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
# Model-only prereleases are not Python package releases.
|
||||
if: ${{ startsWith(github.event.release.tag_name, 'v') && !github.event.release.prerelease }}
|
||||
runs-on: ubuntu-latest
|
||||
environment: pypi
|
||||
steps:
|
||||
@@ -38,4 +40,4 @@ jobs:
|
||||
# is needed. uv's uploader also accepts Metadata-Version 2.5 -- the permanent
|
||||
# fix that lets the hatchling pin be relaxed (see pyproject [build-system]).
|
||||
- name: Publish to PyPI
|
||||
run: uv publish
|
||||
run: uv publish
|
||||
@@ -21,12 +21,27 @@ jobs:
|
||||
- name: Sync dev environment
|
||||
run: uv sync --frozen --extra dev
|
||||
- name: Check C2PA soft-binding registry snapshot
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: uv run python scripts/sync_c2pa_soft_bindings.py --check
|
||||
- name: Ruff lint
|
||||
run: uv run ruff check
|
||||
- name: Ruff format check
|
||||
run: uv run ruff format --check
|
||||
|
||||
security:
|
||||
name: lockfile vulnerability scan
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
enable-cache: false
|
||||
# Policy and triaged blocks: docs/development.md "Known security-gate blocks".
|
||||
- name: Scan uv.lock with uv-secure
|
||||
run: uvx uv-secure uv.lock
|
||||
|
||||
test:
|
||||
name: test ${{ matrix.os }} py${{ matrix.python-version }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -94,7 +109,15 @@ jobs:
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Install ffmpeg
|
||||
run: sudo apt-get update && sudo apt-get install --yes ffmpeg
|
||||
# Third-party apt sources baked into the runner image (google-chrome et
|
||||
# al.) can publish a broken index and fail `apt-get update`, while this
|
||||
# job only needs ffmpeg from Ubuntu's own repositories: drop every
|
||||
# non-Ubuntu source definition before updating.
|
||||
run: |
|
||||
sudo find /etc/apt/sources.list.d -mindepth 1 -maxdepth 1 \
|
||||
! -name 'ubuntu.sources' -delete
|
||||
sudo apt-get update
|
||||
sudo apt-get install --yes ffmpeg
|
||||
- name: Sync dev environment
|
||||
run: uv sync --frozen --extra dev
|
||||
- name: Run full-clip video test
|
||||
|
||||
@@ -17,11 +17,10 @@ on:
|
||||
workflows: ["Distribute on release"]
|
||||
types: [completed]
|
||||
|
||||
# No step reads the GitHub token any more: the ComfyUI check moved to the
|
||||
# registry's public API. Keep the job read-only so it cannot write on a repo
|
||||
# whose default token permission is read/write.
|
||||
# Read the triggering run's release identity while keeping all permissions read-only.
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
@@ -37,16 +36,55 @@ jobs:
|
||||
SPACE_URL: https://wiltodelta-remove-ai-watermarks.hf.space
|
||||
|
||||
steps:
|
||||
- name: Download distributed release identity
|
||||
if: ${{ github.event_name == 'workflow_run' }}
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: distributed-release-*
|
||||
path: distribution-identities
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
github-token: ${{ github.token }}
|
||||
|
||||
- name: Resolve target version
|
||||
id: v
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
INPUT_VERSION: ${{ github.event.inputs.version }}
|
||||
DISTRIBUTION_RUN_ID: ${{ github.event.workflow_run.id }}
|
||||
DISTRIBUTION_RUN_ATTEMPT: ${{ github.event.workflow_run.run_attempt }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -n "${{ github.event.inputs.version }}" ]; then
|
||||
v="${{ github.event.inputs.version }}"
|
||||
if [ "$EVENT_NAME" = "workflow_run" ]; then
|
||||
v=$(python3 - <<'PYTHON'
|
||||
import json, os
|
||||
from pathlib import Path
|
||||
identities = []
|
||||
for path in Path("distribution-identities").rglob("release-identity.json"):
|
||||
item = json.loads(path.read_text(encoding="utf-8"))
|
||||
# download-artifact@v8 extracts the only matched artifact directly
|
||||
# into the path, with no artifact-name directory; several matches
|
||||
# each get one, so the attempt is name-checked only in that layout.
|
||||
if path.parent.name.startswith('distributed-release-'):
|
||||
assert path.parent.name == 'distributed-release-' + item['run_attempt'], "Artifact attempt mismatch"
|
||||
identities.append(item)
|
||||
assert identities, "No distributed release identity found"
|
||||
assert all(item["run_id"] == os.environ["DISTRIBUTION_RUN_ID"] for item in identities), "Distribution run mismatch"
|
||||
# Rerunning only failed jobs can reuse resolve from an earlier attempt.
|
||||
eligible = [item for item in identities if
|
||||
int(item["run_attempt"]) <= int(os.environ["DISTRIBUTION_RUN_ATTEMPT"])]
|
||||
assert eligible, "No identity exists for this distribution attempt"
|
||||
identity = max(eligible, key=lambda item: int(item["run_attempt"]))
|
||||
print(identity["version"])
|
||||
PYTHON
|
||||
)
|
||||
elif [ -n "$INPUT_VERSION" ]; then
|
||||
v="$INPUT_VERSION"
|
||||
else
|
||||
v=$(curl -fsSL https://pypi.org/pypi/remove-ai-watermarks/json \
|
||||
| python3 -c "import sys,json;print(json.load(sys.stdin)['info']['version'])")
|
||||
fi
|
||||
# Versions become URL components and step outputs. Reject malformed identities.
|
||||
[[ "$v" =~ ^[0-9]+\.[0-9]+\.[0-9]+([a-zA-Z0-9.+-]*)$ ]]
|
||||
echo "version=$v" >> "$GITHUB_OUTPUT"
|
||||
echo "Verifying release $v"
|
||||
|
||||
@@ -116,14 +154,30 @@ jobs:
|
||||
# review window a correctly published version looks identical to one
|
||||
# that was never uploaded. Those are opposite situations: one is a
|
||||
# queue nobody here controls, the other is a broken release.
|
||||
python3 -m pip install --quiet packaging
|
||||
versions=$(curl -fsSL "https://api.comfy.org/nodes/remove-ai-watermarks/versions")
|
||||
status=$(printf '%s' "$versions" | VERSION="$VERSION" python3 -c "
|
||||
import json, os, sys
|
||||
version = os.environ['VERSION']
|
||||
floor = f'>={version}'
|
||||
from packaging.requirements import InvalidRequirement, Requirement
|
||||
from packaging.utils import canonicalize_name
|
||||
from packaging.version import Version
|
||||
target = Version(version)
|
||||
def requires_release(value):
|
||||
try:
|
||||
requirement = Requirement(value)
|
||||
except InvalidRequirement:
|
||||
return False
|
||||
return (
|
||||
canonicalize_name(requirement.name) == 'remove-ai-watermarks'
|
||||
and requirement.url is None
|
||||
and requirement.marker is None
|
||||
and target in requirement.specifier
|
||||
and any(s.operator == '>=' and Version(s.version) == target for s in requirement.specifier)
|
||||
)
|
||||
matches = [
|
||||
v for v in json.load(sys.stdin)
|
||||
if any(floor in d for d in (v.get('dependencies') or []))
|
||||
if any(requires_release(d) for d in (v.get('dependencies') or []))
|
||||
]
|
||||
if not matches:
|
||||
sys.exit(f'::error::No ComfyUI node version requires {version}; the sync never published one')
|
||||
|
||||
@@ -9,6 +9,7 @@ tmp/
|
||||
|
||||
# Environment secrets
|
||||
.env
|
||||
.mcp.json
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
@@ -23,6 +24,17 @@ Thumbs.db
|
||||
# Generated calibration inputs
|
||||
data/synthid_corpus/refs/
|
||||
|
||||
# Local-only image pulls held for analysis. They are not publication-cleared and
|
||||
# have no place in this repository's history. Until 2026-09-07 the only thing
|
||||
# keeping them out of a commit was an untracked .gitignore inside the directory,
|
||||
# which a fresh clone would not have.
|
||||
data/spaces/
|
||||
|
||||
# Local-only research data: corpora, checkpoints and campaign reports, tens of
|
||||
# gigabytes that no clone should be able to commit. The layout is described in
|
||||
# data/research/README.md, which is local-only too.
|
||||
data/research/
|
||||
|
||||
# Reference materials
|
||||
_refs/
|
||||
|
||||
@@ -30,8 +42,8 @@ _refs/
|
||||
yolov8n.pt
|
||||
.coverage
|
||||
|
||||
# Local prototype and evaluation outputs (scripts/chroma_scrub_prototype.py and
|
||||
# friends); the durable record of an oracle verdict lives under data/evaluations/
|
||||
# Local prototype and evaluation outputs (research harnesses and probes); the
|
||||
# durable record of an oracle verdict lives under data/evaluations/
|
||||
out/
|
||||
|
||||
# Claude Code local settings
|
||||
@@ -52,6 +64,7 @@ gfpgan/
|
||||
|
||||
# Local evaluation data
|
||||
.local-eval/
|
||||
/.oracle-slots.json
|
||||
|
||||
# Other harnesses' generated config. Claude's sync owns these; a committed copy
|
||||
# is a second source nobody reconciles. Anchored so a same-named source file
|
||||
|
||||
@@ -8,6 +8,7 @@ The project gives users control over provenance marks on content they generated
|
||||
|
||||
- Add visible templates only for AI-generation labels.
|
||||
- Do not add stock, agency, or classifieds marks to `watermark_registry.py`.
|
||||
- Do not add templates for regulator-defined disclosure icons (EU Code of Practice Annex 1).
|
||||
- Keep `erase --region` generic and user-directed; do not build an automatic stock-watermark remover on it.
|
||||
|
||||
Full boundary and legal context: [`docs/legal-and-safety.md`](docs/legal-and-safety.md).
|
||||
@@ -31,7 +32,7 @@ Optional features and installation groups are documented in [`docs/installation.
|
||||
|
||||
`maintain.sh` runs dependency freshness and security checks, Ruff, Pyright scoped to `src/`, and the parallel test suite. Full-project Pyright is not the project gate because the ML dependency graph can exhaust Node memory.
|
||||
|
||||
This gate does NOT pass `--ignore-unfixed` to uv-secure, so a transitive CVE with no released fix stops it before Ruff, Pyright and the tests ever run. That is deliberate and it is not a blocker: run those three separately and report them, per the triage in [`docs/development.md`](docs/development.md). A red gate here is not the same signal as a red gate in a sibling repository that does ignore unfixed findings.
|
||||
The gate does not pass `--ignore-unfixed` to uv-secure, so a transitive CVE with no released fix stops it before Ruff, Pyright and the tests run. This matches the global rule: run and report those three separately. Scanner failures are fatal too. Triaged blockers and the recheck procedure: `Known security-gate blocks` in [`docs/development.md`](docs/development.md).
|
||||
|
||||
Command, gate, typing, and model-test invariants auto-load from [`.claude/rules/development.md`](.claude/rules/development.md). Environment recovery, CI behavior, and fixture policy live in [`docs/development.md`](docs/development.md).
|
||||
|
||||
@@ -43,11 +44,11 @@ The published Agent Skill is [`skills/remove-ai-watermarks/`](skills/remove-ai-w
|
||||
|
||||
[`docs/module-internals.md`](docs/module-internals.md) is the canonical per-module map, including design decisions, thresholds, calibration history, incident records, and regression guards. Read the relevant section before changing a subsystem.
|
||||
|
||||
Research and current constraints are routed through [`docs/index.md`](docs/index.md), especially [`docs/known-limitations.md`](docs/known-limitations.md), [`docs/supported-signals.md`](docs/supported-signals.md), [`docs/synthid.md`](docs/synthid.md), and [`docs/watermarking-landscape.md`](docs/watermarking-landscape.md). Pixel photo classification is [`docs/photo-classify.md`](docs/photo-classify.md); it is not `identify`. Classifier research is split between general [`AI-generated image classifiers`](docs/ai-generated-image-classifiers.md) and [`SynthID source classifiers`](docs/synthid-classifiers.md). Other SynthID campaign logs are [`docs/synthid-detector-research.md`](docs/synthid-detector-research.md) and [`docs/synthid-removal-research.md`](docs/synthid-removal-research.md). The pre-split chronological archive is [`docs/synthid-detector-removal-plan.md`](docs/synthid-detector-removal-plan.md).
|
||||
Research and current constraints are routed through [`docs/index.md`](docs/index.md), especially [`docs/known-limitations.md`](docs/known-limitations.md), [`docs/supported-signals.md`](docs/supported-signals.md), [`docs/synthid.md`](docs/synthid.md), and [`docs/watermarking-landscape.md`](docs/watermarking-landscape.md). Pixel photo classification is [`docs/photo-classify.md`](docs/photo-classify.md); the separate OpenAI/Google/unknown export-pipeline signal is [`docs/source-classify.md`](docs/source-classify.md). Neither runs inside `identify` or constitutes SynthID detection. Classifier research is split between general [`AI-generated image classifiers`](docs/ai-generated-image-classifiers.md) and [`SynthID source classifiers`](docs/synthid-classifiers.md). Other SynthID campaign logs are [`docs/synthid-detector-research.md`](docs/synthid-detector-research.md) and [`docs/synthid-removal-research.md`](docs/synthid-removal-research.md). The pre-split chronological archive is [`docs/synthid-detector-removal-plan.md`](docs/synthid-detector-removal-plan.md). The benchmark kernel and its pinned local oracles (AudioSeal, Perth, PixelSeal, VideoSeal) cover image, audio, and video watermark cohorts and research studies: [`docs/benchmark-kernel.md`](docs/benchmark-kernel.md).
|
||||
|
||||
## Data safety
|
||||
|
||||
Follow [`data/README.md`](data/README.md) for public fixture, calibration, oracle, and evaluation layout. Store each tracked binary once and keep generated evaluation outputs outside the repository.
|
||||
Follow [`data/README.md`](data/README.md) for public fixture, calibration, oracle, and evaluation layout. Use only publication-cleared inputs in tracked data paths. Store each tracked binary once and keep generated evaluation outputs outside the repository.
|
||||
|
||||
## Rules and conventions
|
||||
|
||||
@@ -55,4 +56,4 @@ Topic-specific rules live in `.claude/rules/*.md` and are auto-loaded when match
|
||||
|
||||
| File | Covers |
|
||||
|---|---|
|
||||
| `development.md` | Command contracts, project gate, typing boundaries, model-adjacent tests, the docs-coverage and agent-skill parity seams, and the detection-path measurement rule |
|
||||
| `development.md` | Command contracts, project gate, typing boundaries, model-adjacent tests, the docs-coverage and agent-skill parity seams, the detection-path measurement rule, and why cloud GPU harnesses stay out of `scripts/` |
|
||||
@@ -3,7 +3,7 @@
|
||||
Remove AI provenance marks from images and video you generated yourself:
|
||||
|
||||
- known visible labels such as the Google Gemini sparkle watermark and vendor text marks;
|
||||
- invisible pixel watermarks through diffusion regeneration;
|
||||
- invisible pixel watermarks through direct local-format disruption or diffusion regeneration;
|
||||
- C2PA, EXIF, XMP, IPTC, and related AI metadata.
|
||||
|
||||
Video support covers provenance identification, complete visible-plus-metadata
|
||||
@@ -24,9 +24,12 @@ removal.
|
||||
[](https://github.com/sponsors/wiltodelta)
|
||||
[](https://skills.sh/wiltodelta/remove-ai-watermarks)
|
||||
|
||||
> This project is for lawful use on content you own. It does not target stock
|
||||
> agency previews or other watermarks that protect third party paid content.
|
||||
> See [scope, safety, and legal notes](docs/legal-and-safety.md).
|
||||
> This is a research project on AI provenance signals, for lawful use on content
|
||||
> you own. It is provided as is, without warranty or liability, and each user is
|
||||
> responsible for how they use it. It does not target stock agency previews or
|
||||
> other watermarks that protect third party paid content. Some jurisdictions,
|
||||
> including China, restrict removing AI labels or supplying tools that do. See
|
||||
> [scope, safety, and legal notes](docs/legal-and-safety.md).
|
||||
|
||||
## Choose what you want to do
|
||||
|
||||
@@ -49,7 +52,9 @@ removal.
|
||||
|
||||
Microsoft Paint and Photos InvisMark declarations are routed automatically to
|
||||
pixel regeneration. The `all` command removes both the hidden pixel watermark and
|
||||
its linked C2PA manifest; metadata stripping alone removes only the manifest.
|
||||
its linked C2PA manifest; metadata stripping alone removes only the manifest. A
|
||||
specialized Python API can inspect and disrupt the validated local
|
||||
`Watermarker.dll` payload without diffusion.
|
||||
|
||||
## Installation modes
|
||||
|
||||
@@ -57,15 +62,17 @@ its linked C2PA manifest; metadata stripping alone removes only the manifest.
|
||||
| --- | --- |
|
||||
| Metadata inspection and stripping | `remove-ai-watermarks` |
|
||||
| Photograph AI-versus-camera classification | `remove-ai-watermarks[classify]` |
|
||||
| OpenAI/Google/unknown source-export classification | `remove-ai-watermarks[source-classify]` |
|
||||
| Visible detection and removal | `remove-ai-watermarks[visible]` |
|
||||
| Visible video processing | `remove-ai-watermarks[video]` |
|
||||
| Video SynthID removal | `remove-ai-watermarks[video,diffusion]` |
|
||||
| Torch-free DWT-DCT detection | `remove-ai-watermarks[detect]` |
|
||||
| Direct local Paint InvisMark disruption | `remove-ai-watermarks[pixels]` |
|
||||
| Invisible image removal (needs CUDA) | `remove-ai-watermarks[qwen-zimage]` |
|
||||
| Every production feature available on the active Python | `remove-ai-watermarks[all]` |
|
||||
|
||||
Lower-level and specialized extras include `pixels`, `heif`, `trustmark`,
|
||||
`migan`, `lama`, and `diffusion`. The
|
||||
`migan`, `lama`, `diffusion`, `classify-onnx`, and `source-classify`. The
|
||||
[installation guide](docs/installation.md#feature-extras) documents their exact
|
||||
dependency composition, Python compatibility, and model requirements.
|
||||
|
||||
@@ -93,6 +100,19 @@ remove-ai-watermarks classify image.png
|
||||
|
||||
Guide: [photo pixel classification](docs/photo-classify.md).
|
||||
|
||||
For a lightweight, abstaining OpenAI/Google/unknown source-export signal after
|
||||
metadata removal, use the separate Python API. It is not a SynthID detector:
|
||||
|
||||
```python
|
||||
import remove_ai_watermarks as raiw
|
||||
|
||||
result = raiw.classify_source("image.png")
|
||||
print(result.label, result.reason)
|
||||
```
|
||||
|
||||
Install `remove-ai-watermarks[source-classify]`. Guide:
|
||||
[source-pipeline classification](docs/source-classify.md).
|
||||
|
||||
Signed provenance is the supported route for SynthID and `identify` reads it.
|
||||
There is no local SynthID pixel detector in the package. Research on a
|
||||
periodic lattice expert is in `scripts/synthid_runtime/` and
|
||||
@@ -133,6 +153,8 @@ inspection includes the native TC260 `AIGC` tag in
|
||||
`moov.udta.meta.keys/ilst`, including a `moov` placed after the media payload,
|
||||
plus the QuickTime-form `meta` variants Doubao's iOS export writes (a bare
|
||||
`meta` box as a direct `moov` child, and a keyless `hdlr=mdir` metadata list).
|
||||
Keyed `workflow` and `prompt` entries in the same metadata-list structure are
|
||||
also detected and removed, including ComfyUI exports.
|
||||
MKV and WebM inspection reads the normative
|
||||
`Segment.Tags.Tag.SimpleTag` placement. AVI uses `LIST/INFO/AIGC`, while FLV
|
||||
uses `script.onMetaData.AIGC`. The non-ISOBMFF formats are remuxed with stream
|
||||
@@ -183,9 +205,12 @@ preserved through a timestamped in-memory NUT bridge instead of being flattened
|
||||
to the average frame rate. Non-zero source start timestamps are retained
|
||||
together with the copied audio offset. The default `--mark auto`
|
||||
scans all providers in one decode pass and selects the first stable match in
|
||||
the specificity order shown below. Pass an explicit mark to restrict detection
|
||||
to one provider.
|
||||
Sora covers the moving Sora 2 mascot and wordmark. Veo covers both the current
|
||||
the specificity order shown below. Valid Google AI-video provenance breaks a
|
||||
Sora/Veo cross-match in favor of Veo when the diamond is nested in the Sora
|
||||
region or Veo persists through the complete clip while Sora does not.
|
||||
Pass an explicit mark to restrict detection to one provider.
|
||||
Sora covers the moving Sora 2 mascot, with or without the wordmark; mascot-only
|
||||
matches also require the complete two-eye shape. 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` or
|
||||
@@ -211,8 +236,10 @@ is not a product result state. The CLI therefore describes video-pixel
|
||||
regeneration instead of claiming a per-file SynthID verdict, and reports the
|
||||
copied audio watermark as `UNVERIFIED`.
|
||||
|
||||
For invisible watermark removal, install the `qwen-zimage` extra. **An NVIDIA GPU
|
||||
is required**: all profiles are CUDA-only, and there is no CPU or MPS fallback.
|
||||
For invisible watermark removal through the CLI or high-level API, install the
|
||||
`qwen-zimage` extra. **An NVIDIA GPU is required**: all diffusion profiles are
|
||||
CUDA-only, and there is no CPU or MPS fallback. The specialized local Paint API
|
||||
uses the `pixels` extra without CUDA.
|
||||
|
||||
```bash
|
||||
uv tool install --force "remove-ai-watermarks[qwen-zimage]"
|
||||
@@ -265,8 +292,8 @@ ControlNet, followed by SAM-masked Z-Image repair of any detected face. The
|
||||
alternative, `sdxl-zimage`, swaps the global stage for SDXL and keeps the same face
|
||||
stage. A third profile, `chroma-zimage`, uses the Apache-2.0 Chroma1 global pass
|
||||
with its own flat vendor floors; see `docs/chroma1-engine-research.md` for the
|
||||
calibration. `--pipeline auto` picks chroma-zimage for OpenAI and Microsoft
|
||||
provenance and qwen-zimage otherwise. All are CUDA only.
|
||||
calibration. `--pipeline auto` picks sdxl-zimage for Google, chroma-zimage for
|
||||
Microsoft, and qwen-zimage otherwise. All are CUDA only.
|
||||
|
||||
```bash
|
||||
uv tool install --force "remove-ai-watermarks[qwen-zimage]"
|
||||
@@ -343,15 +370,22 @@ remove-ai-watermarks batch ./images --mode all
|
||||
Visible mark support includes:
|
||||
|
||||
- Google Gemini and Nano Banana visible sparkle watermark;
|
||||
- Doubao, the Jimeng wordmark and top-left `AI生成` pill, Qwen, Kling AI,
|
||||
Yuanbao, Baidu, LiblibAI, and RunningHub labels;
|
||||
- one calibrated Microsoft top-right white AI-badge variant;
|
||||
- one calibrated Samsung Galaxy AI label variant.
|
||||
- Doubao, the Jimeng wordmark and top-left `AI生成` layouts, Qwen, Kling AI,
|
||||
Yuanbao, Dola, WorkBuddy, Baidu, LiblibAI's bottom-center wordmark and compact
|
||||
top-left pill, and RunningHub labels;
|
||||
- calibrated Microsoft top-right white and beige AI-badge layouts;
|
||||
- calibrated Italian and Korean Samsung Galaxy AI disclosures;
|
||||
- a generic brand-less bottom-right `AI生成` fallback for OS/gallery-level
|
||||
AI-edit stamps that carry no vendor wordmark of their own (vivo and Xiaomi
|
||||
Gallery confirmed); synthetic-only calibration, weaker evidence than the
|
||||
vendor-tuned detectors above;
|
||||
- the OpenArt wordmark, placed at the frame center rather than a corner;
|
||||
uncalibrated (one confirmed real case, no captured corpus).
|
||||
|
||||
Metadata and provenance inspection covers C2PA, EXIF, XMP, IPTC, common
|
||||
generator parameters, China TC260 AIGC labels, and several vendor specific
|
||||
signals. Optional decoders add support for open DWT-DCT watermarks and Adobe
|
||||
TrustMark.
|
||||
generator parameters in image and video containers, China TC260 AIGC labels,
|
||||
and several vendor specific signals. Optional decoders add support for open
|
||||
DWT-DCT watermarks and Adobe TrustMark.
|
||||
|
||||
The exact support matrix, including important locale and detector limits, lives
|
||||
in [supported signals](docs/supported-signals.md). The
|
||||
@@ -368,13 +402,14 @@ Visible removal follows three steps:
|
||||
|
||||
Metadata removal uses format aware stripping. JPEG metadata removal preserves
|
||||
the encoded image scan instead of recompressing it. Native MP4/MOV TC260 values
|
||||
are blanked without changing box sizes or media offsets. Other supported
|
||||
containers use their corresponding metadata path.
|
||||
and keyed generation parameters are blanked without changing box sizes or
|
||||
media offsets. Other supported containers use their corresponding metadata
|
||||
path.
|
||||
|
||||
Invisible removal is different. It regenerates the image through a diffusion
|
||||
pipeline to disrupt pixel and frequency domain watermarks. This changes the
|
||||
image and cannot guarantee that a proprietary verifier will reject every
|
||||
output.
|
||||
Invisible removal through the CLI and high-level API regenerates the image through
|
||||
a diffusion pipeline. Specialized callers can instead decode and disrupt the
|
||||
positively identified local Microsoft Paint format with much smaller pixel changes.
|
||||
Neither path can guarantee that every proprietary verifier will reject every output.
|
||||
|
||||
See [supported signals](docs/supported-signals.md) and
|
||||
[known limitations](docs/known-limitations.md) for the full technical boundary.
|
||||
@@ -461,7 +496,7 @@ invisible removal.
|
||||
detail.
|
||||
- Visible video removal recognizes the moving Sora 2 wordmark, the current Veo
|
||||
diamond plus legacy `Veo` text, the Seedance boxed `AI` label, and the fixed
|
||||
Doubao, Dola, Hailuo AI, and Kling AI labels. It does not recognize the older Sora Turbo
|
||||
Doubao, Dola, Hailuo AI, Vidu, and Kling AI labels. It does not recognize the older Sora Turbo
|
||||
corner swirl or unregistered layouts from those providers.
|
||||
The classical OpenCV backend can smear structured backgrounds; use MI-GAN or
|
||||
LaMa when recovery quality matters.
|
||||
@@ -469,9 +504,10 @@ invisible removal.
|
||||
The shipped profile is oracle-certified, but no public local decoder can
|
||||
certify an arbitrary output at runtime. Recheck unusually important outputs
|
||||
after provider changes.
|
||||
- Invisible-watermark removal requires CUDA. All profiles refuse any other
|
||||
device at construction rather than falling back to one that cannot run them.
|
||||
Visible removal, metadata stripping and `identify` still run anywhere.
|
||||
- Invisible-watermark removal through the CLI and high-level API requires CUDA.
|
||||
All diffusion profiles refuse any other device at construction rather than
|
||||
falling back to one that cannot run them. The specialized local Paint API,
|
||||
visible removal, metadata stripping and `identify` run without CUDA.
|
||||
- Provider watermark systems can change. Validate important outputs with the
|
||||
provider's own verifier when one is available.
|
||||
|
||||
@@ -485,8 +521,8 @@ uv run --extra video --extra diffusion python scripts/video_synthid_sweep.py inp
|
||||
```
|
||||
|
||||
To score what an output actually cost, use `scripts/video_fidelity_probe.py`:
|
||||
the engine's own PSNR is measured before the resize and the encode, so only the
|
||||
probe sees the delivered picture.
|
||||
the engine's own PSNR is measured after the input resize and before final
|
||||
encoding, so only the probe sees the delivered picture.
|
||||
|
||||
```bash
|
||||
uv run --extra video python scripts/video_fidelity_probe.py input.mp4 input_clean.mp4
|
||||
@@ -535,7 +571,8 @@ bash maintain.sh
|
||||
```
|
||||
|
||||
See [module internals](docs/module-internals.md) before changing a subsystem
|
||||
with documented invariants.
|
||||
with documented invariants. Report vulnerabilities as described in
|
||||
[SECURITY.md](SECURITY.md).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Security policy
|
||||
|
||||
## Supported versions
|
||||
|
||||
Security fixes go into the latest release. Older releases are not patched;
|
||||
upgrade through the channel you installed from (PyPI or Homebrew).
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
Report vulnerabilities privately through
|
||||
[GitHub private vulnerability reporting](https://github.com/wiltodelta/remove-ai-watermarks/security/advisories/new).
|
||||
Do not open a public issue for an unpatched vulnerability. Include the affected
|
||||
version, the command or API call, and a minimal input that reproduces it.
|
||||
|
||||
Vulnerabilities in a dependency, such as a CVE in a package from `uv.lock`, can
|
||||
be reported as a regular issue when the advisory is already public.
|
||||
|
||||
## Dependency vulnerabilities
|
||||
|
||||
Dependabot raises alerts and update pull requests for `uv.lock` and GitHub
|
||||
Actions, and CI scans `uv.lock` with
|
||||
[`uv-secure`](https://github.com/owenlamont/uv-secure) on every push and pull
|
||||
request. A vulnerable dependency is upgraded to a fixed release; advisories
|
||||
without one are tracked under
|
||||
[Known security-gate blocks](docs/development.md#known-security-gate-blocks).
|
||||
@@ -10,6 +10,11 @@ data/
|
||||
visible/ Per-mark example gallery: one synthetic canonical example per
|
||||
registered mark plus documented provider originals where available
|
||||
(see fixtures/visible/README.md)
|
||||
synthetic/ Frozen synthetic renders that unit tests stamp onto frames, each
|
||||
produced by the script named in the test that loads it
|
||||
captures/
|
||||
2026-09/ Provider outputs from the maintainer's own accounts, stored as
|
||||
served, indexed by manifest.csv (see its README)
|
||||
calibration/
|
||||
<vendor>/ Minimal controlled inputs needed to rebuild detector assets
|
||||
synthid/
|
||||
@@ -33,11 +38,8 @@ 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
|
||||
checkpoints, research corpora, and campaign reports) lives under gitignored
|
||||
paths documented in `data/research/README.md` (ignored via `.git/info/exclude`,
|
||||
never committed). Worktree `.local-eval/` directories are scratch copies; the
|
||||
durable store is `data/research/` in the main checkout.
|
||||
Use only publication-cleared inputs in tracked data paths. Keep generated
|
||||
evaluation outputs outside the repository.
|
||||
|
||||
1. Store each binary image once. Evaluation manifests and documentation point
|
||||
to its canonical location.
|
||||
@@ -54,9 +56,15 @@ durable store is `data/research/` in the main checkout.
|
||||
`evaluations/engine-selection/` is the bounded exception for ORIGINAL study
|
||||
inputs: its manifest tracks a prompt-matched OpenAI/Meta content matrix, not
|
||||
generated model outputs or watermark-oracle evidence.
|
||||
6. Keep third-party fixture license notices in `licenses/`, outside directories
|
||||
6. Put provider captures that no test reads in `captures/<yyyy-mm>/`, with a
|
||||
`manifest.csv` row per capture. A capture that is also a fixture is stored
|
||||
once, in `fixtures/`, and its manifest row points there. Request a new
|
||||
capture only for a service x vendor pair not yet captured, or for a
|
||||
service's own model: another version of a known pair adds no provenance
|
||||
evidence.
|
||||
7. Keep third-party fixture license notices in `licenses/`, outside directories
|
||||
that tests enumerate as media inputs.
|
||||
7. Runtime detector assets belong in `src/remove_ai_watermarks/assets/`.
|
||||
8. Runtime detector assets belong in `src/remove_ai_watermarks/assets/`.
|
||||
Unregistered research candidates belong in
|
||||
`scripts/assets/visible-mark-candidates/` so they are not shipped in the
|
||||
wheel.
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# Provider captures, September 2026
|
||||
|
||||
Original outputs downloaded from the maintainer's own accounts between
|
||||
2026-09-23 and 2026-09-25 and cleared for publication by the maintainer. The
|
||||
services are the Gemini API, xAI Grok, Vidu, Wan, Apple Image Playground,
|
||||
YouTube (re-encodes of our own uploads), and the paid Higgsfield, Runway and
|
||||
Dreamina plans. Every prompt is a neutral coffee-mug scene; Higgsfield
|
||||
Genjutsu and the video-edit samples take our own Higgsfield outputs as input.
|
||||
|
||||
Files are stored byte-for-byte as served. Nothing is re-encoded, because the
|
||||
provenance under study (C2PA, TC260 labels, `hf-job-id` chunks) lives in the
|
||||
container bytes.
|
||||
|
||||
`manifest.csv` lists every capture once, including the ones that are already
|
||||
test fixtures: those rows point at their `data/fixtures/` path instead of a
|
||||
second copy (`stored` is `existing`). Columns:
|
||||
|
||||
| Column | Meaning |
|
||||
| --- | --- |
|
||||
| `sha256` | Digest of the stored file |
|
||||
| `path` | Canonical repository path |
|
||||
| `stored` | `new` for files in this directory, `existing` for a fixture already tracked |
|
||||
| `service` | Service the file was downloaded from |
|
||||
| `source_name` | Local capture name, which names the model |
|
||||
| `captured` | Capture date |
|
||||
| `ai_from_metadata`, `platform` | `identify` (images) or `identify_video` (videos) verdict from metadata only, at capture time |
|
||||
|
||||
What each service does to the upstream provenance, and the per-model
|
||||
findings, are summarized in the live-captures table of
|
||||
[`docs/watermarking-landscape.md`](../../../docs/watermarking-landscape.md).
|
||||
The `google-photos/` files are the maintainer's own iPhone photos edited in
|
||||
Google Photos on iOS (an Ask edit replacing a dog with a cat, an eraser edit, and
|
||||
two other AI edits), published unmodified with their EXIF GPS position by the
|
||||
maintainer's decision on 2026-09-25. Google's checker in Gemini found SynthID in
|
||||
all four with the metadata stripped. The Apple Clean Up photo is not stored.
|
||||
The `google-photos-web/` files are a negative control from the Google Photos web
|
||||
editor (2026-09-26): one of the maintainer's photos, downloaded untouched and after
|
||||
Enhance, Sky (Vivid) and a square crop. The web editor offers no generative tool and
|
||||
writes no Content Credentials, so none of the four carries C2PA, IPTC or an AI
|
||||
credit, and Google's checker in Gemini found no SynthID in any of them (three
|
||||
explicit negatives; the crop drew a reply that did not name SynthID and is recorded
|
||||
as indeterminate).
|
||||
|
||||
- License: repository license; generated and explicitly cleared by the maintainer
|
||||
|
After Width: | Height: | Size: 6.2 MiB |
|
After Width: | Height: | Size: 6.8 MiB |
|
After Width: | Height: | Size: 7.3 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 395 KiB |
|
After Width: | Height: | Size: 335 KiB |
|
After Width: | Height: | Size: 407 KiB |
|
After Width: | Height: | Size: 679 KiB |
|
After Width: | Height: | Size: 636 KiB |
|
After Width: | Height: | Size: 603 KiB |
|
After Width: | Height: | Size: 627 KiB |
|
After Width: | Height: | Size: 715 KiB |
|
After Width: | Height: | Size: 529 KiB |
|
After Width: | Height: | Size: 420 KiB |
|
After Width: | Height: | Size: 146 KiB |
|
After Width: | Height: | Size: 183 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 691 KiB |
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 945 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 5.0 MiB |
|
After Width: | Height: | Size: 4.7 MiB |
|
After Width: | Height: | Size: 5.9 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 4.6 MiB |
|
After Width: | Height: | Size: 4.4 MiB |
|
After Width: | Height: | Size: 4.9 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 6.2 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 4.9 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 3.5 MiB |
@@ -0,0 +1,136 @@
|
||||
sha256,path,stored,service,source_name,captured,ai_from_metadata,platform
|
||||
5ade61fc4cd869e2487745af61c7e23d008a3e9c2b5c440d2ffe16c2d3a32315,data/fixtures/provenance/apple-image-playground.png,existing,apple,image-playground-panda.png,2026-09-23,True,Apple Image Playground
|
||||
f11595c6a9c0a72d4712d228354f589078d6940e529538cb009c799d8b8410d7,data/fixtures/provenance/google-gemini-omni-app.mp4,existing,gemini-omni,omni-wm-toggled.mp4,2026-09-23,True,Google (Gemini / Imagen)
|
||||
cbbf831fb5d394436feb2f022d46e8933989f003acb9616e907791bca8a5dd6d,data/captures/2026-09/grok/download-0.jpg,new,grok,download-0.jpg,2026-09-23,True,xAI Grok Imagine
|
||||
c6e3329009260cd230deaadd0cc7293269a94c4628010cd7eeb3d390b76cc0c2,data/fixtures/provenance/xai-grok-imagine-video.mp4,existing,grok,video-0.mp4,2026-09-23,True,xAI Grok Imagine
|
||||
8e9f2a8bd190e5cfc662151aeef63cbc3372619678e2d37b3432b4fea113fb1c,data/fixtures/visible/vidu/provider-original.mp4,existing,vidu,vidu-free-q3.mp4,2026-09-23,True,ShengShu Vidu
|
||||
583df0ec34d88653fc82c658728780f474a959eb246156649e5d5dde956fc474,data/fixtures/visible/wan/provider-original.png,existing,wan,wan-image-0.png,2026-09-23,True,China AIGC-labeled content (TC260 standard)
|
||||
040e5c5fe2d7d82b467c54ce2558e4d5c0705e676f00464577f29c211bc7784f,data/captures/2026-09/dreamina/gpt-image-2-5-flare.png,new,dreamina,gpt-image-2-5-flare.png,2026-09-24,True,ByteDance Dreamina
|
||||
acfc163bac69099e0d20ed26ec5f70537f21cac7b33ebb8fd7e03dfee287fa9a,data/captures/2026-09/dreamina/gpt-image-2-5-sunburst.png,new,dreamina,gpt-image-2-5-sunburst.png,2026-09-24,True,ByteDance Dreamina
|
||||
047d62991a5adde4d246acf69d543b3f7a4c382e86523a57fbb0732bc7695a47,data/captures/2026-09/dreamina/gpt-image-2.png,new,dreamina,gpt-image-2.png,2026-09-24,True,ByteDance Dreamina
|
||||
a48fc7da8b8bcc4519802e4db57942840aadf113d13c0fb5efc591a5b21617cc,data/captures/2026-09/dreamina/minimax-h3.mp4,new,dreamina,minimax-h3.mp4,2026-09-24,True,ByteDance Dreamina
|
||||
885a47916a72c39ec2e5bea6a295215144979b26c811d7ca63a0473742a90a91,data/captures/2026-09/dreamina/nano-banana-pro.jpeg,new,dreamina,nano-banana-pro.jpeg,2026-09-24,True,ByteDance Dreamina
|
||||
615fa80ca5bcca828f2c058a6021e53b73e196b36950446beed3f83d1a9fcb03,data/captures/2026-09/dreamina/nano-banana.jpeg,new,dreamina,nano-banana.jpeg,2026-09-24,True,ByteDance Dreamina
|
||||
233a79d0d482bb964a67280b71e7999defb0587b64b6d0a84371dea61f670bc2,data/captures/2026-09/dreamina/seedance-1-0-fast.mp4,new,dreamina,seedance-1-0-fast.mp4,2026-09-24,True,ByteDance Dreamina
|
||||
48df1fc4860c29cbe0cc5ebdd36526c1a75e1e8fffa745bc2798871701abaf94,data/captures/2026-09/dreamina/seedance-1-0.mp4,new,dreamina,seedance-1-0.mp4,2026-09-24,True,ByteDance Dreamina
|
||||
561fc4ea7a536e415ef0dc2f466fc35925ad61159f1ea2856d7f525bc86a34ce,data/captures/2026-09/dreamina/seedance-1-5-pro.mp4,new,dreamina,seedance-1-5-pro.mp4,2026-09-24,True,ByteDance Dreamina
|
||||
f58dd63abc2553c9b70d6cda8a068e874e59b5f3eb6d0f3db9ab08fefe9f3c3f,data/captures/2026-09/dreamina/seedance-2-0-fast.mp4,new,dreamina,seedance-2-0-fast.mp4,2026-09-24,True,ByteDance Dreamina
|
||||
d2712d4804074ac08e28fc77ff1c1ff986e6c4bbaf3ae9737f743719e51a59de,data/fixtures/provenance/dreamina-seedance-2-mini.mp4,existing,dreamina,seedance-2-0-mini.mp4,2026-09-24,True,ByteDance Dreamina
|
||||
375dde6314547b7b7c83a058a2e785c45b47ffee9198e81644e804d5769ba9a2,data/captures/2026-09/dreamina/seedance-2-0.mp4,new,dreamina,seedance-2-0.mp4,2026-09-24,True,ByteDance Dreamina
|
||||
ff29e21fd43b79fb9ddb097c4164f02da8b3f56afd27f8fbff851a7763bc9d33,data/captures/2026-09/dreamina/seedance-2-5.mp4,new,dreamina,seedance-2-5.mp4,2026-09-24,True,ByteDance Dreamina
|
||||
5d5a5e7c3f9434245eec4505cb6665fe57df2bf8da99a6f31302c8efacb4c9c8,data/captures/2026-09/dreamina/seedream-3-0.jpeg,new,dreamina,seedream-3-0.jpeg,2026-09-24,True,ByteDance Dreamina
|
||||
a24e9f7c6ee07b60ac9af9f061746a39b305db89006eb92cca19f926bc8ee89e,data/captures/2026-09/dreamina/seedream-3-1.jpeg,new,dreamina,seedream-3-1.jpeg,2026-09-24,True,ByteDance Dreamina
|
||||
ebe832110f0408a7b8cb65721213aec41bac70bfc580ef27d8c627639ceb7f2e,data/captures/2026-09/dreamina/seedream-4-0.jpeg,new,dreamina,seedream-4-0.jpeg,2026-09-24,True,ByteDance Dreamina
|
||||
8ba87f6aa7e5b441a236ead9f325fe5cf74ec638bd359f2f87f91831bb3bdb4a,data/captures/2026-09/dreamina/seedream-4-1.jpeg,new,dreamina,seedream-4-1.jpeg,2026-09-24,True,ByteDance Dreamina
|
||||
e2d14b3df27f22c53ca79f3372251ae04bf6607dab9ff86a16c365a667abc277,data/captures/2026-09/dreamina/seedream-4-5.jpeg,new,dreamina,seedream-4-5.jpeg,2026-09-24,True,ByteDance Dreamina
|
||||
d54e8825d180fbdd6350c320b558015efb98f20839d3ce51ce9e939e59d6d870,data/captures/2026-09/dreamina/seedream-4-6.jpeg,new,dreamina,seedream-4-6.jpeg,2026-09-24,True,ByteDance Dreamina
|
||||
25375e8f0485c1742e35d6f81da79fc64cb4029e4b4a36fb56e419a1646907f1,data/captures/2026-09/dreamina/seedream-4-7.jpeg,new,dreamina,seedream-4-7.jpeg,2026-09-24,True,ByteDance Dreamina
|
||||
41ad0748d6894d40cde35f352082eb5608f3ee83216e120b7499afd0284703db,data/captures/2026-09/dreamina/seedream-5-0-lite.jpeg,new,dreamina,seedream-5-0-lite.jpeg,2026-09-24,True,ByteDance Dreamina
|
||||
27ead86c611fbea25b5f50dfb120c1614f891f1ca07a707edfb1184f5d5109c5,data/captures/2026-09/dreamina/seedream-5-pro-2.jpeg,new,dreamina,seedream-5-pro-2.jpeg,2026-09-24,True,ByteDance Dreamina
|
||||
2393cb470dc7c69de4c421f338aeb08192ed916515bc095a3afcd2ef4af89c90,data/fixtures/provenance/dreamina-seedream-5-pro.jpg,existing,dreamina,seedream-5-pro.jpeg,2026-09-24,True,ByteDance Dreamina
|
||||
2e23ade5758aa6f42eb12261b22fd686e7f17b6a26414046c1afa1012c886e04,data/captures/2026-09/higgsfield/flux-2-flex.png,new,higgsfield,flux-2-flex.png,2026-09-24,True,Higgsfield (model not identified)
|
||||
5d1f676ec1d3ab6684f75292d0c26bdaf2dc0cd93b3a4e657611d0fe34f6e880,data/captures/2026-09/higgsfield/flux-2-max.png,new,higgsfield,flux-2-max.png,2026-09-24,True,Higgsfield (model not identified)
|
||||
4382b0c2a5f3d2b2c52f070ba9a12ffa259b4e0bad2446a65134cd700a06cb0a,data/fixtures/provenance/higgsfield-flux-2-pro.png,existing,higgsfield,flux-2-pro.png,2026-09-24,True,Higgsfield (model not identified)
|
||||
e4eed8c3a5b1d99f3dce1ff16aa90ceb790a308c4ce623a9056cc711d4c5685f,data/captures/2026-09/higgsfield/gpt-image-1-5.png,new,higgsfield,gpt-image-1-5.png,2026-09-24,True,Higgsfield (model not identified)
|
||||
197eb0d30a4967cdf19e68226e9fa9aa6723613a3feab1bd9553b8b4287dae61,data/captures/2026-09/higgsfield/gpt-image-2-5-flare.png,new,higgsfield,gpt-image-2-5-flare.png,2026-09-24,True,Higgsfield (model not identified)
|
||||
3c20bc41a016117651142b4c3911237e6183ecfe2f53d97338b7beefc30cdea6,data/captures/2026-09/higgsfield/gpt-image-2-5-sunburst.png,new,higgsfield,gpt-image-2-5-sunburst.png,2026-09-24,True,Higgsfield (model not identified)
|
||||
f079e02f2b79f5ddf7f06d29de9f7b0a9ae7746c9a1e340555a9998f91637e59,data/captures/2026-09/higgsfield/gpt-image-2.png,new,higgsfield,gpt-image-2.png,2026-09-24,True,Higgsfield (model not identified)
|
||||
4cb06b2084f2f1907421fbab38ccc459ea2cb33859005e69c07c3ea524dc0f6d,data/fixtures/provenance/higgsfield-gpt-image.jpg,existing,higgsfield,gpt-image.jpeg,2026-09-24,True,OpenAI (ChatGPT / GPT Image / DALL·E / Sora)
|
||||
eb47fb7ed862e1e828627457f4a438f63dd27aa0d2c7e492d62d31d2c7939feb,data/captures/2026-09/higgsfield/grok-imagine-2-0.png,new,higgsfield,grok-imagine-2-0.png,2026-09-24,True,Higgsfield (model not identified)
|
||||
90d31906f629c4a99f60d115762fe003fc00dcdcabc895b2179e78e30407ef69,data/captures/2026-09/higgsfield/grok-imagine.png,new,higgsfield,grok-imagine.png,2026-09-24,True,Higgsfield (model not identified)
|
||||
6ad75936db0bb172423564dd24d27bddb992917263555ecb5697cd2f354deeac,data/fixtures/provenance/higgsfield-kling-o1.png,existing,higgsfield,kling-o1.png,2026-09-24,True,China AIGC-labeled content (TC260 standard)
|
||||
5365087f32d3bf8047375722835b3d8e75aa5d0f97afd5d59edfab5ae18f8b41,data/captures/2026-09/higgsfield/nano-banana-2-lite.png,new,higgsfield,nano-banana-2-lite.png,2026-09-24,True,Higgsfield (model not identified)
|
||||
8511db22012b878b996c1993285943eebe87dfbbc3d7d262f112a6a8f36c70bf,data/captures/2026-09/higgsfield/nano-banana-2.png,new,higgsfield,nano-banana-2.png,2026-09-24,True,Higgsfield (model not identified)
|
||||
dec96b39618905556a6d0592041d38aae0fb69e462b7dd4f3295242ff2b72543,data/captures/2026-09/higgsfield/nano-banana-pro.png,new,higgsfield,nano-banana-pro.png,2026-09-24,True,Higgsfield (model not identified)
|
||||
c1e3ad5ace74231a0106827a26cef9e3caf1424977b073ccba19a13f1eb3be3f,data/captures/2026-09/higgsfield/nano-banana.png,new,higgsfield,nano-banana.png,2026-09-24,True,Higgsfield (model not identified)
|
||||
776cba6484b751e729778253ba885f7042a9b53aa72d8315d27303533d2e3681,data/captures/2026-09/higgsfield/recraft-v4-1-utility.png,new,higgsfield,recraft-v4-1-utility.png,2026-09-24,True,Higgsfield (model not identified)
|
||||
1e68c02f97f4f2af68c1096e2631def01e6dc1bf388eaad2eae27a93f67c730a,data/captures/2026-09/higgsfield/recraft-v4-1.png,new,higgsfield,recraft-v4-1.png,2026-09-24,True,Higgsfield (model not identified)
|
||||
6153f35d8a7505162118f884ffc79f2f311a840251b799f8095fee06b4d615f8,data/captures/2026-09/higgsfield/seedream-4-0.png,new,higgsfield,seedream-4-0.png,2026-09-24,True,Higgsfield (model not identified)
|
||||
30d53dbfc5bd80b0c4a7435f02955590408ad206fe41017d29ce82a8e13667c8,data/captures/2026-09/higgsfield/seedream-4-5.png,new,higgsfield,seedream-4-5.png,2026-09-24,True,Higgsfield (model not identified)
|
||||
a86d007946c30c0fee139434d1cf6884e142ea11db3afb7911057e9415110d06,data/captures/2026-09/higgsfield/seedream-5-flash.png,new,higgsfield,seedream-5-flash.png,2026-09-24,True,Higgsfield (model not identified)
|
||||
5ad760ca1ecf73fea60c78520f1903ba3fd6c2b5e4d45e963558a945c91e9007,data/captures/2026-09/higgsfield/seedream-5-lite.png,new,higgsfield,seedream-5-lite.png,2026-09-24,True,Higgsfield (model not identified)
|
||||
2d64e99f5281fe10ad955429b26720dd2092bf7e943456b39f68564eb35c8e59,data/captures/2026-09/higgsfield/seedream-5-pro.png,new,higgsfield,seedream-5-pro.png,2026-09-24,True,Higgsfield (model not identified)
|
||||
0d3a70c1382639241d8b55966b19965ad42824aad60663a20a5f5bc810101cc6,data/captures/2026-09/higgsfield/soul-cinema.png,new,higgsfield,soul-cinema.png,2026-09-24,,
|
||||
fd5703240e9c045dbfb52c5fbef90802974f06aec716f482e8af74ef91288104,data/captures/2026-09/higgsfield/soul-v1.png,new,higgsfield,soul-v1.png,2026-09-24,,
|
||||
91a2160b5a72f23776ac25207250359e7f86ade761b531247856a47488a50e63,data/captures/2026-09/higgsfield/soul2-image.png,new,higgsfield,soul2-image.png,2026-09-24,True,Higgsfield (model not identified)
|
||||
f5f0b26198f8f1da107280b3c2c20c82897c3d522e27ba0acc4cf9eb8f74650a,data/captures/2026-09/higgsfield/flux-3-video.mp4,new,higgsfield,flux-3-video.mp4,2026-09-24,True,Black Forest Labs (FLUX)
|
||||
967dcb6022e19f55639ff705a58e5252a62a24405f247dcffefcc0f610149c30,data/fixtures/provenance/higgsfield-happyhorse.mp4,existing,higgsfield,happyhorse.mp4,2026-09-24,True,"Alibaba Tongyi (Wan, HappyHorse)"
|
||||
7011c92d52ec624da12600f24a4abb8d997f26cdea62d6f5a8d1940bc7ffcc9c,data/fixtures/provenance/higgsfield-kling-3-0-turbo.mp4,existing,higgsfield,kling-3-0-turbo.mp4,2026-09-24,True,Kuaishou Kling AI
|
||||
5fe5b9d08b9fb41a4d3eea470d443eb5df520235300e65134090338701215a43,data/fixtures/provenance/higgsfield-minimax-h3.mp4,existing,higgsfield,minimax-h3.mp4,2026-09-24,True,MiniMax Hailuo AI
|
||||
7d4ffa63755d605538c9a73059b4f6354a57e4f21100a92497aebc3c19dc3485,data/fixtures/provenance/higgsfield-veo-3-1-lite.mp4,existing,higgsfield,veo-3-1-lite.mp4,2026-09-24,True,Google (Gemini / Imagen)
|
||||
07fcc98ddc21f80d2c4b918c8afa9298ddab4ae49fc6428c8e464a9b69c9db21,data/captures/2026-09/higgsfield/wan-2-5.mp4,new,higgsfield,wan-2-5.mp4,2026-09-24,True,"Alibaba Tongyi (Wan, HappyHorse)"
|
||||
40a828d0360820019f3033c367283652df0a99c21150bafaddbc9a92437e743f,data/captures/2026-09/higgsfield/wan-2-2.png,new,higgsfield,wan-2-2.png,2026-09-24,,
|
||||
c3563b5413fe3d54ccc3f60c834c32e02f3dab7a830262bf38b6e0a700a0ac16,data/captures/2026-09/higgsfield/z-image.png,new,higgsfield,z-image.png,2026-09-24,True,Higgsfield (model not identified)
|
||||
1fd7cccffb25f18e74fa5ebc639672f1361db6137e6b9e848bc33fc1f583c0fe,data/fixtures/provenance/google-nano-banana.png,existing,nb-api,gemini-2.5-flash-image.png,2026-09-24,True,Google (Gemini / Imagen)
|
||||
5fd14ce6d32f3806f0128f952eb30d36f7dad384a9eec728e5efbdb46ebca7db,data/fixtures/provenance/google-nano-banana-pro.jpg,existing,nb-api,gemini-3-pro-image.jpg,2026-09-24,True,Google (Gemini / Imagen)
|
||||
977110ac48678627d907d635ac1b110b930044c350b669fb5f927a500e7d95ac,data/fixtures/provenance/google-nano-banana-2.jpg,existing,nb-api,gemini-3.1-flash-image.jpg,2026-09-24,True,Google (Gemini / Imagen)
|
||||
df4f422b9e77d6b08d3e6d095299f0cdf6d48f6c770632bb042a6c7b09627334,data/fixtures/provenance/google-nano-banana-2-lite.jpg,existing,nb2-lite,nb2lite-1.jpg,2026-09-24,True,Google (Gemini / Imagen)
|
||||
3d7dddd333ca6b576b3d7dc5220df42cbef3ea388bd4688f5671cdd796dcb805,data/captures/2026-09/nb2-lite/nb2lite-2.jpg,new,nb2-lite,nb2lite-2.jpg,2026-09-24,True,Google (Gemini / Imagen)
|
||||
e04a0ed3976bdff9df7ffdc318296cadcca3ba6e7166c81247c27a09ba39fe5c,data/captures/2026-09/nb2-lite/nb2lite-3.jpg,new,nb2-lite,nb2lite-3.jpg,2026-09-24,True,Google (Gemini / Imagen)
|
||||
fb034e94e4cbca9e0b56d82389d361fda0d6191f7e16f37ac6c55cd62a3621f7,data/fixtures/provenance/google-gemini-omni-flash-api.mp4,existing,omni-flash,omni-flash-1.mp4,2026-09-24,True,Google (Gemini / Imagen)
|
||||
25b2dc2a3c147dd8f04457a6b85183edd6ee235abdfe92c162c41c25812b0d9a,data/captures/2026-09/runway/flux-2-klein.png,new,runway,flux-2-klein.png,2026-09-24,True,Black Forest Labs (FLUX)
|
||||
e9b75d61adef6842c602767c4c0a5217de816927fd257ba1675e7e4a4dd043d0,data/captures/2026-09/runway/flux-2-max.jpg,new,runway,flux-2-max.jpg,2026-09-24,True,Black Forest Labs (FLUX)
|
||||
12262d853760b4e6dab7a896b833331ee5af43cd6e9467b899b7de76c7361d8d,data/fixtures/provenance/runway-gen4-image.png,existing,runway,gen4-image-artifact.png,2026-09-24,True,Runway
|
||||
98c0040b60aad1187c45df5fd4d6e8a11762950329b1a89183d6a58d5b947ad2,data/fixtures/provenance/runway-gen45-video.mp4,existing,runway,gen45-video-artifact.mp4,2026-09-24,True,Runway
|
||||
eaec9e1c3a6c46c14d6ab8d12954b875e7482c8fde2a6d53ccd2a0b77c227a0b,data/captures/2026-09/runway/gpt-image-1-5.png,new,runway,gpt-image-1-5.png,2026-09-24,True,OpenAI (ChatGPT / GPT Image / DALL·E / Sora)
|
||||
2905127c3caabf13906abe6abaef8096209ee9e6e961e899b112f82ebb6e0c4f,data/captures/2026-09/runway/gpt-image-1-mini.png,new,runway,gpt-image-1-mini.png,2026-09-24,True,OpenAI (ChatGPT / GPT Image / DALL·E / Sora)
|
||||
22fb128833cb401a02b72a4ef5f23a577224a97950f296d03433224fb2249afd,data/captures/2026-09/runway/gpt-image-2-5-flare.png,new,runway,gpt-image-2-5-flare.png,2026-09-24,True,OpenAI (ChatGPT / GPT Image / DALL·E / Sora)
|
||||
249b0d0f3fe3decc140a4ba234aee862d107b7a181bb47882f280cbddd325bfe,data/captures/2026-09/runway/gpt-image-2-5-sunburst.png,new,runway,gpt-image-2-5-sunburst.png,2026-09-24,True,OpenAI (ChatGPT / GPT Image / DALL·E / Sora)
|
||||
8c0d4d0e739e559035a16bdc276fe8d517d4561736e56c0ddf8d1da46181dfbd,data/captures/2026-09/runway/gpt-image-2.png,new,runway,gpt-image-2.png,2026-09-24,True,OpenAI (ChatGPT / GPT Image / DALL·E / Sora)
|
||||
9f7fae91c8faf9eb5554443d395dfd3b23204702b32feb19e1904ab3009d8c29,data/captures/2026-09/runway/grok-imagine-2-0-or-quality-a.jpg,new,runway,grok-imagine-2-0-or-quality-a.jpg,2026-09-24,True,xAI Grok Imagine
|
||||
99c7873a4c3de880af9daeb5c0fa753232313d4e4689f0976bbd020d2b420465,data/captures/2026-09/runway/grok-imagine-2-0-or-quality-b.jpg,new,runway,grok-imagine-2-0-or-quality-b.jpg,2026-09-24,True,xAI Grok Imagine
|
||||
001007bdeda172730028b2d54eb1b2acd90f6396800c10e2be2efe777faa1718,data/captures/2026-09/runway/grok-imagine.jpg,new,runway,grok-imagine.jpg,2026-09-24,True,xAI Grok Imagine
|
||||
14e97f57d83f5438a13b89175eb995ec07f817432642466b1bdb8572f7573da2,data/captures/2026-09/runway/ideogram-4-0.png,new,runway,ideogram-4-0.png,2026-09-24,True,Ideogram
|
||||
8a651b72eed17775e222c1a40b5bbea6e69715a27d6c8e8e0e3d4c5d0723909c,data/captures/2026-09/runway/ideogram-p-image.png,new,runway,ideogram-p-image.png,2026-09-24,True,Ideogram
|
||||
3132a2e7198ecd3dead3042cebc6c9091264638bf63b40d5027ff058bd043547,data/captures/2026-09/runway/kling-o3-standard.mp4,new,runway,kling-o3-standard.mp4,2026-09-24,True,Kuaishou Kling AI
|
||||
b95f4a9f2706fc4f3c8716f6daa016029e55f07177dfd2efd8fedbaf67094981,data/captures/2026-09/runway/ltx-2-5-fast.mp4,new,runway,ltx-2-5-fast.mp4,2026-09-24,,
|
||||
b374c9d29c094a2fd1b0b6257c989a43d155ce62d9fdc3d445707978cd7dd1e5,data/captures/2026-09/runway/meta-muse-image.png,new,runway,meta-muse-image.png,2026-09-24,True,
|
||||
fbc0313aea807e67d1bb904cac7b47c9163b1ac8435d4774d4b63e99ba1975df,data/captures/2026-09/runway/nano-banana-2-lite.jpg,new,runway,nano-banana-2-lite.jpg,2026-09-24,True,Google (Gemini / Imagen)
|
||||
a32344cb19bbd6607733aa138b45b66e919f827cdfcdfc0d7da8de75906237a9,data/captures/2026-09/runway/nano-banana-2-via-runway.png,new,runway,nano-banana-2-via-runway.png,2026-09-24,True,Google (Gemini / Imagen)
|
||||
d2b82a1907056f8f022308fdd16e701238da816a9c9c339d7fa54999af2cf12e,data/captures/2026-09/runway/nano-banana-pro.png,new,runway,nano-banana-pro.png,2026-09-24,True,Google (Gemini / Imagen)
|
||||
077e552f16ab1de180e3487e85567f5e0565e1bd6e942dc1d585309a105fa19a,data/captures/2026-09/runway/nano-banana.png,new,runway,nano-banana.png,2026-09-24,True,Google (Gemini / Imagen)
|
||||
88f0e48032dee30d1e6237962af74cd38991ab52c1424a4f8e12aadf4e1b9346,data/captures/2026-09/runway/seedance-2-mini.mp4,new,runway,seedance-2-mini.mp4,2026-09-24,True,BytePlus (ByteDance)
|
||||
5df40eb93f1b0f4d802bd117fba736ecec9713f5f411c6449726690d94972560,data/captures/2026-09/runway/seedream-5-0.png,new,runway,seedream-5-0.png,2026-09-24,True,BytePlus (ByteDance)
|
||||
8275bf3af0c8a3d4815f76a546f29172c31ddd65c7a153e1bd6d6421beb1eccc,data/captures/2026-09/runway/seedream-5-pro.png,new,runway,seedream-5-pro.png,2026-09-24,True,BytePlus (ByteDance)
|
||||
9d8f9901115697ca4925f02e3b5c91920fb02579657268c6525c0cd9bcf33d63,data/captures/2026-09/youtube/yt-136-video.mp4,new,youtube,yt-136-video.mp4,2026-09-24,,
|
||||
68c87b0697fb954f3533f96ec32875f6dec173c142fd4d4f2552c624c21f7c28,data/captures/2026-09/youtube/yt-398-av1.mp4,new,youtube,yt-398-av1.mp4,2026-09-24,,
|
||||
aa2fde5c0444eda05f40db2a8974183284016876c9788f099dda9df5aac1714f,data/fixtures/provenance/youtube-reencode-gemini-omni-flash.mp4,existing,youtube,yt-studio-download.mp4,2026-09-24,True,Google (Gemini / Imagen)
|
||||
236dd73a25a893b8f2c7bbdad8e1208341251c2b12e7b261c1fff65bf58277a5,data/fixtures/provenance/youtube-reencode-grok.mp4,existing,youtube,yt-studio-grok.mp4,2026-09-24,,
|
||||
7c37874c72d289606e997434ccde73dba3aa89ecdc5adb3dfe64923933da5378,data/captures/2026-09/higgsfield/cinema-studio-3-0.mp4,new,higgsfield,cinema-studio-3-0.mp4,2026-09-25,True,BytePlus (ByteDance)
|
||||
485449b69f1013a97970aabc5429327db1ff88559c81e3430c536fab108db7dc,data/fixtures/provenance/higgsfield-cinema-studio-v2.mp4,existing,higgsfield,cinema-studio-v2.mp4,2026-09-25,True,Kuaishou Kling AI
|
||||
f7915d8e77eeb6feceff7169aa79c380330aa2f71a71b5eba426c4495cb8d696,data/captures/2026-09/higgsfield/edit-flux-3.mp4,new,higgsfield,edit-flux-3.mp4,2026-09-25,True,Black Forest Labs (FLUX)
|
||||
fca47e6aea979b3fb0be01b5775c47f95acde66b4692957aae928a17ce86418d,data/captures/2026-09/higgsfield/edit-gemini-omni-1-1.mp4,new,higgsfield,edit-gemini-omni-1-1.mp4,2026-09-25,True,Google (Gemini / Imagen)
|
||||
4f8aff9e6ef05c71a5028f2d3b5c63191b5e6932ebe39827068ae09cfe00e17b,data/captures/2026-09/higgsfield/gemini-omni-flash-1-1.mp4,new,higgsfield,gemini-omni-flash-1-1.mp4,2026-09-25,True,Google (Gemini / Imagen)
|
||||
7c9a93140bffb50de173bca7cff1ecb7b2a75f611bf11bb26a54d903c043e5e3,data/captures/2026-09/higgsfield/gemini-omni-flash.mp4,new,higgsfield,gemini-omni-flash.mp4,2026-09-25,True,Google (Gemini / Imagen)
|
||||
a3c2e8c506327ff0bdee078372be8d89fb913672f61efc66a4791771026ec0fc,data/fixtures/provenance/higgsfield-genjutsu-motion-control.mp4,existing,higgsfield,genjutsu-motion-control.mp4,2026-09-25,,
|
||||
d3b7a9bbe03abdab2b6c0fb009f90a961cb021405ff652c02943e51e01c38ba0,data/captures/2026-09/higgsfield/genjutsu-replace-object.mp4,new,higgsfield,genjutsu-replace-object.mp4,2026-09-25,,
|
||||
32ec5b3d70636c6295ff312dadb9fc018e8ff061c7ebe786000fcbbdc2161b6b,data/captures/2026-09/higgsfield/grok-video-1-5.mp4,new,higgsfield,grok-video-1-5.mp4,2026-09-25,True,xAI Grok Imagine
|
||||
3811bca40850d8b4caebf3cc0f21b8c414f8b7d7c810f0a72b38b845174bb02e,data/captures/2026-09/higgsfield/grok-video.mp4,new,higgsfield,grok-video.mp4,2026-09-25,True,xAI Grok Imagine
|
||||
67f4b6c49f6b93485fd47c457f64d3f721ec326ec57a9fb39488d6b9023528b7,data/fixtures/provenance/higgsfield-hailuo-2-3.mp4,existing,higgsfield,hailuo-2-3.mp4,2026-09-25,True,MiniMax Hailuo AI
|
||||
96034f675008efcc09ca25c0739068f6e7301dde3ac67f4cfd14b82c5c7f9cae,data/captures/2026-09/higgsfield/kling-2-6.mp4,new,higgsfield,kling-2-6.mp4,2026-09-25,True,Kuaishou Kling AI
|
||||
00a0ab7a2fe37f1c5b3208f9940c928932124015df7391dab4bfa5a9cc21af0c,data/captures/2026-09/higgsfield/kling-3-0.mp4,new,higgsfield,kling-3-0.mp4,2026-09-25,True,Kuaishou Kling AI
|
||||
1f8fb86da8d71db7d2e6c4f87a6a2fd5cab69b7824406d7145a2756c2d358e38,data/captures/2026-09/higgsfield/marketing-studio.mp4,new,higgsfield,marketing-studio.mp4,2026-09-25,True,BytePlus (ByteDance)
|
||||
bacf6dc7ab9b6dfe67488349ecced76b10221f3c73196a1a4bff163588e7270d,data/captures/2026-09/higgsfield/minimax-h3-max.mp4,new,higgsfield,minimax-h3-max.mp4,2026-09-25,True,MiniMax Hailuo AI
|
||||
11b8e1890ac393a41de61f9a39633cefcad45b9e47f82d13a92906ac5f6ced45,data/captures/2026-09/higgsfield/seedance-1-5-pro.mp4,new,higgsfield,seedance-1-5-pro.mp4,2026-09-25,,
|
||||
a65d7b57c528f6fa0e6b844d918d16cc6c2260038921d1c7d6d2e2bf60095a26,data/captures/2026-09/higgsfield/seedance-2-0.mp4,new,higgsfield,seedance-2-0.mp4,2026-09-25,True,BytePlus (ByteDance)
|
||||
24857c4744ee9a7e4385d1f85df0acdd0aba05f1827210b7b97556d1a9d58841,data/captures/2026-09/higgsfield/seedance-2-5.mp4,new,higgsfield,seedance-2-5.mp4,2026-09-25,True,BytePlus (ByteDance)
|
||||
d4aec466d7811ae5670f4db6308f5275d2773653089736849f85f96fe2b262be,data/captures/2026-09/higgsfield/veo-3-1.mp4,new,higgsfield,veo-3-1.mp4,2026-09-25,True,Google (Gemini / Imagen)
|
||||
e78ed6fa92814a72a64b6a377bebfd5b65efeae149d49170c68744917e9246b3,data/captures/2026-09/higgsfield/wan-2-6.mp4,new,higgsfield,wan-2-6.mp4,2026-09-25,True,"Alibaba Tongyi (Wan, HappyHorse)"
|
||||
f59ae93226bae3cc24598214ff623f99957e127838541b57acd55bd9cdb8e72c,data/captures/2026-09/higgsfield/wan-2-7.mp4,new,higgsfield,wan-2-7.mp4,2026-09-25,True,"Alibaba Tongyi (Wan, HappyHorse)"
|
||||
bfb89e7729ef0e54260452f525ebdadd1237fac0902c5bda39edbcd7a48aa6cc,data/captures/2026-09/higgsfield/wan-3-0-prime.mp4,new,higgsfield,wan-3-0-prime.mp4,2026-09-25,True,"Alibaba Tongyi (Wan, HappyHorse)"
|
||||
3e982e49d46445143f78a0b445642ce888a23571823e6db74e93c0c7ede3c9c1,data/captures/2026-09/higgsfield/wan-3-0.mp4,new,higgsfield,wan-3-0.mp4,2026-09-25,True,"Alibaba Tongyi (Wan, HappyHorse)"
|
||||
7fc933d784dcb1ed4bef0f6d857fe79156c1ef1da9d437d750633217d0da23a8,data/captures/2026-09/google-photos/ask-dog-to-cat.jpg,new,google-photos,ask-dog-to-cat.jpg,2026-09-25,True,Google Photos (AI edit)
|
||||
1c8726279b869bc435dd30bcb385d04203dd9af5570b0d2d393ff3f3e5162e5b,data/captures/2026-09/google-photos/eraser-glasses.jpg,new,google-photos,eraser-glasses.jpg,2026-09-25,True,Google Photos (AI edit)
|
||||
1abca17d4649e458626864560e5d2db6397eb51d7bc8617f49bc70be0905522e,data/captures/2026-09/google-photos/gphotos-5.jpg,new,google-photos,gphotos-5.jpg,2026-09-24,True,Google Photos (AI edit)
|
||||
56817730a46a84975a7ac6d9595d2f68a18676b49fd1be1c5230b517beafa9cc,data/captures/2026-09/google-photos/gphotos-6.jpg,new,google-photos,gphotos-6.jpg,2026-09-24,True,Google Photos (AI edit)
|
||||
39f8141415ab51fd6070b6d2591132c81fffa6b44f76f2e5e84b38912b3becc1,data/captures/2026-09/runway/flux-3-video.mp4,new,runway,flux-3-video.mp4,2026-09-25,True,Black Forest Labs (FLUX)
|
||||
81cd555906034c4aff6473047a7a898d14ce060dd4e6a3c6226902547446045b,data/captures/2026-09/runway/grok-imagine-1-5-video.mp4,new,runway,grok-imagine-1-5-video.mp4,2026-09-25,True,xAI Grok Imagine
|
||||
c255d4a7168197e4ab6af162690d305f3e3f48037b1a5fd9e877ab782539e0fb,data/captures/2026-09/runway/gemini-omni-1-1-flash.mp4,new,runway,gemini-omni-1-1-flash.mp4,2026-09-25,True,Google (Gemini / Imagen)
|
||||
a8711432008960149a16afd43704fa673f70826cf527d677aa0c35b7a18502d1,data/captures/2026-09/runway/happyhorse-1-0.mp4,new,runway,happyhorse-1-0.mp4,2026-09-25,True,Alibaba Tongyi (Wan, HappyHorse)
|
||||
4c909ca68ecad62c9200727cae2bef5e80678a255b3fd781f7a43ca0b0824f46,data/captures/2026-09/runway/minimax-h3.mp4,new,runway,minimax-h3.mp4,2026-09-25,True,MiniMax Hailuo AI
|
||||
c04468ef20081aecf16ee94fe81ddd419ddae36acb678e7e4f26def7a1f7bb72,data/captures/2026-09/google-photos-web/forest-original.jpg,new,google-photos-web,forest-original.jpg,2026-09-26,,
|
||||
af0265e41edec3413683f86441a1ffa9c64dbcb7117070d7ccbd2e91c20fd327,data/captures/2026-09/google-photos-web/forest-enhance.jpg,new,google-photos-web,forest-enhance.jpg,2026-09-26,,
|
||||
3f42e0877f67c4677f6f52be18146923fc5eeb471f9e0e27b9477d29dc1e6bbb,data/captures/2026-09/google-photos-web/forest-sky-vivid.jpg,new,google-photos-web,forest-sky-vivid.jpg,2026-09-26,,
|
||||
c83f903117cc030edf78164430ce59ffa897cb3a9efe464cf257e55644a955fd,data/captures/2026-09/google-photos-web/forest-crop-square.jpg,new,google-photos-web,forest-crop-square.jpg,2026-09-26,,
|
||||
e5612ea4bc06df48821d431d8d4758d3c73d1e74bd7b5b5dce921eaffdfbbe93,data/captures/2026-09/higgsfield/edit-kling-3-0-omni.mp4,new,higgsfield,edit-kling-3-0-omni.mp4,2026-09-26,True,Kuaishou Kling AI
|
||||
a76119db1b50f529e397875d38cf3938c5ae3b9e79d893d25567a7eee2340f41,data/captures/2026-09/higgsfield/dop-lite.mp4,new,higgsfield,dop-lite.mp4,2026-09-26,,
|
||||
|