mirror of
https://github.com/wiltodelta/remove-ai-watermarks.git
synced 2026-10-17 06:51:06 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dcc1bf5e07 | ||
|
|
d8fcd0f79b | ||
|
|
0a3b227f2c | ||
|
|
f91f9fd9fc | ||
|
|
c4aecfbbdf | ||
|
|
ab528ec0e8 | ||
|
|
2810b53585 |
No files matched your search
@@ -78,10 +78,9 @@ Measured (2026-08-26/27, oracle `meta.ai/identification`):
|
||||
between easiest and hardest).
|
||||
- Derived Meta floor by the existing worst-boundary-plus-cross-source-spread
|
||||
method: 0.06 + (0.0525 - 0.015) = 0.0975, rounded to **0.1**.
|
||||
- Not shipped as a constant: no provenance signal routes Muse outputs onto a
|
||||
vendor cohort, so the default curve stays authoritative and 0.1 is recorded
|
||||
as the floor to encode if an explicit Meta override is ever added.
|
||||
|
||||
- Shipped as `QWEN_ZIMAGE_META_STRENGTH`: auto mode routes standalone-AI-IPTC
|
||||
files onto the cohort, and `--vendor meta` / `InvisibleOptions.vendor`
|
||||
names it explicitly on stripped files (implying the scrub runs).
|
||||
## Regeneration
|
||||
|
||||
API key is not stored in this repository. Regenerate with the script pattern
|
||||
|
||||
+17
@@ -387,6 +387,23 @@ detected. Use `--force` when you know the image should be processed:
|
||||
remove-ai-watermarks invisible image.png -o clean.png --force
|
||||
```
|
||||
|
||||
### Choose a strength cohort
|
||||
|
||||
`--vendor` selects the cohort the default strength resolves from. `auto`
|
||||
(the default) derives it from provenance: the C2PA issuer for
|
||||
OpenAI/Google/Microsoft, and the standalone AI IPTC tag for Meta Content Seal
|
||||
(Muse output carries no C2PA; the tag is a standard code, so C2PA evidence
|
||||
always wins first). An explicit value both names the cohort for stripped files
|
||||
and implies the scrub runs -- naming the cohort asserts the pixel watermark is
|
||||
present -- exactly like `--force` plus a measured floor:
|
||||
|
||||
```bash
|
||||
remove-ai-watermarks invisible muse_output.webp -o clean.png --vendor meta
|
||||
```
|
||||
|
||||
The same option exists on `all` and `batch`, and as
|
||||
`InvisibleOptions(vendor="meta")` in the Python API.
|
||||
|
||||
### Choose a pipeline
|
||||
|
||||
| Pipeline | When to use it |
|
||||
|
||||
+16
-12
@@ -1170,18 +1170,22 @@ unreachable constant would be dead code.
|
||||
Full spread: worst first-clean boundary (0.0525, 0.06] on lighthouse, easiest
|
||||
source already clean at 0.015. Following the same derivation as the OpenAI and
|
||||
Microsoft floors (worst clean boundary plus one full observed cross-source
|
||||
spread): 0.06 + (0.0525 - 0.015) = 0.0975, rounded up to **0.1**. This is a
|
||||
candidate `meta` floor, not yet a shipped constant: Muse outputs carry no C2PA,
|
||||
so `vendor_for_strength` has no signal that could route them onto the flat
|
||||
vendor map automatically. Encoding 0.1 as `QWEN_ZIMAGE_META_STRENGTH` makes
|
||||
sense only once the tool gains an explicit way to opt into a Meta cohort (for
|
||||
example a CLI `--vendor meta` override); the default resolution-adaptive curve
|
||||
(~0.1305 at 2.56 MP) already clears every measured source, so default behavior
|
||||
needs no change. Oracle verdicts carry a per-generation ID and creation timestamp
|
||||
embedded in the watermark payload; both survived the 512 px resize and JPEG q85
|
||||
rows, so payload recovery outlives the detection threshold. Oracle session
|
||||
limits are per-IP, server-side, and sliding-window: clearing cookies and storage
|
||||
does not reset them, and a burst exhausts the window minutes after it reopens.
|
||||
spread): 0.06 + (0.0525 - 0.015) = 0.0975, rounded up to **0.1**. Shipped as
|
||||
`QWEN_ZIMAGE_META_STRENGTH` with two routing paths: auto mode routes a file
|
||||
whose only provenance is the standalone AI IPTC tag onto the cohort
|
||||
(`vendor_for_strength` checks C2PA issuers first, so Google/OpenAI/Microsoft
|
||||
evidence always wins, and the tag's other users ship no invisible watermark
|
||||
this profile targets), and `--vendor meta` / `InvisibleOptions.vendor` names the
|
||||
cohort explicitly on stripped files, implying the scrub runs (naming the cohort
|
||||
asserts the watermark is present). sdxl-zimage has no measured Meta rung and an
|
||||
explicit meta vendor there falls to the conservative unknown 0.25. The default
|
||||
resolution-adaptive curve (~0.1305 at 2.56 MP) also clears every measured
|
||||
source, so default behavior needed no change. Oracle verdicts carry a
|
||||
generation ID and creation timestamp embedded in the watermark payload; both
|
||||
survived the 512 px resize and JPEG q85 rows, so payload recovery outlives the
|
||||
detection threshold. Oracle session limits are per-IP, server-side, and
|
||||
sliding-window: clearing cookies and storage does not reset them, and a burst
|
||||
exhausts the window minutes after it reopens.
|
||||
|
||||
### Static prompt embeddings
|
||||
|
||||
|
||||
@@ -112,6 +112,18 @@ raiw.remove_all(
|
||||
)
|
||||
```
|
||||
|
||||
`vendor="meta"` names a strength cohort explicitly (the measured Content Seal
|
||||
floor) for a stripped file whose provenance no longer carries the AI IPTC tag,
|
||||
and implies the scrub runs:
|
||||
|
||||
```python
|
||||
raiw.remove_all(
|
||||
"muse_output.webp",
|
||||
"clean.png",
|
||||
invisible=InvisibleOptions(vendor="meta"),
|
||||
)
|
||||
```
|
||||
|
||||
`InvisibleOptions` carries only what `InvisibleEngine` itself takes, and uses the
|
||||
engine's own parameter names and defaults. `force`, which decides whether the
|
||||
engine runs at all, is a parameter of `remove_all` and `remove_batch` alongside
|
||||
|
||||
@@ -152,7 +152,11 @@ inspection cannot independently verify the output. Microsoft's official
|
||||
[Content Provenance Detection API](https://learn.microsoft.com/en-us/azure/ai-services/content-safety/how-to/how-to-provenance-detection)
|
||||
is the external oracle: it reports pixel `Watermark` and embedded `C2PA` results
|
||||
separately; a control-positive, output-negative pair is the available per-file
|
||||
verification path.
|
||||
verification path. The API needs Azure credentials; the page a human can check
|
||||
without an account is <https://ai.azure.com/nextgen/validate>. Its verdict is
|
||||
weaker than the API's: it collapses watermark and C2PA into one rendered result
|
||||
and tops out at `Inconclusive` rather than a watermark-negative, so treat
|
||||
`Inconclusive` on a processed file as "not confirmed", not as "detected still".
|
||||
|
||||
Meta Muse Image stamps every output with Content Seal, a proprietary invisible
|
||||
pixel watermark, and ships no visible mark (the legacy `Imagined with AI`
|
||||
@@ -161,7 +165,12 @@ This project has no local Content Seal decoder. Meta Model API outputs and
|
||||
Meta CDN copies carry an XMP `iptcExt:DigitalSourceType =
|
||||
trainedAlgorithmicMedia` companion tag, which `identify` reports through the
|
||||
existing Made-with-AI path; that IPTC code is a standard, not a Meta-exclusive
|
||||
signal, so it cannot key a strength cohort the way the C2PA issuer does. The
|
||||
signal, so it cannot key a strength cohort the way the C2PA issuer does. Since
|
||||
0.33.0 a standalone AI digital-source tag (no C2PA manifest) additionally emits
|
||||
the additive `content_seal` signal - the strength router's Meta bet as evidence,
|
||||
medium confidence, with the same caveat - so clients select pixel removal from
|
||||
the signal list exactly the way InvisMark is additive over `soft_binding`. It
|
||||
is an attribution, never a decode. The
|
||||
external oracle is `https://meta.ai/identification`: anonymous, no login,
|
||||
accepts image, video, and audio, enforces an unspecified daily identification
|
||||
limit, and answers with model attribution (`Muse Image 1 - Meta`) plus a
|
||||
@@ -172,9 +181,15 @@ documentation never mentions the seal. The
|
||||
default `qwen-zimage` profile clears Content Seal at the default
|
||||
resolution-adaptive strength (oracle-verified on 2.56 MP generations); measured
|
||||
strength boundaries are recorded in `data/contentseal/manifest.csv` and
|
||||
[module internals](module-internals.md#meta-content-seal-boundaries-for-qwen-zimage)
|
||||
(derived Meta floor 0.1 by the standard spread method, not shipped as a constant
|
||||
because no provenance signal can route Muse output onto a vendor cohort).
|
||||
[module internals](module-internals.md#meta-content-seal-boundaries-for-qwen-zimage).
|
||||
The derived Meta floor (0.1 by the standard spread method) ships as a measured
|
||||
cohort: auto mode routes a file whose only provenance is the standalone AI IPTC
|
||||
tag to it (the tag is not Meta-exclusive; other tag users ship no invisible
|
||||
watermark this profile targets, and Google/OpenAI/Microsoft C2PA evidence
|
||||
always wins first), and `invisible --vendor meta` (also `all` and `batch`, and
|
||||
`InvisibleOptions.vendor` in the API) names the cohort explicitly on stripped
|
||||
files. An explicit vendor implies the scrub runs: naming the cohort asserts the
|
||||
pixel watermark is present.
|
||||
The seal survives resizing, JPEG recompression, and metadata stripping; it dies
|
||||
to center crops of a third to a half, matching the Reuters 2026-07-11 finding
|
||||
that Meta's detector missed 55% of cropped Muse images.
|
||||
|
||||
@@ -66,7 +66,7 @@ payloads. Removal remuxes either container through ffmpeg with stream copy.
|
||||
encoder and the carrier remains decodable.
|
||||
- **Invisible but NOT locally detectable (proprietary, API/oracle only — same wall as SynthID):** Amazon Titan Image Generator + Nova Canvas (Bedrock `DetectGeneratedContent` API), Kakao (new SynthID image adopter, May 2026), NVIDIA Cosmos (SynthID video). No local detector possible; treat like SynthID.
|
||||
- **C2PA 2.4 "Durable Content Credentials" (April 2026; verified against the spec) raise the bar for metadata stripping.** 2.4 defines soft bindings (an invisible watermark or a content fingerprint) plus a server-side manifest repository and a new `c2pa.repository-receipt` assertion. Per the spec: "if a C2PA manifest is removed from an asset, but a copy of that manifest remains in a provenance store elsewhere, the manifest and asset may be matched using available soft bindings." So our local `metadata --remove` deletes the *embedded* manifest, but a fingerprint/watermark soft binding can still re-link the image to its manifest in a repository server-side. Stripping the file is becoming necessary-but-not-sufficient against durable provenance. (Our parsers target the stable embedded-manifest format documented in C2PA 2.1 §11; that format is unchanged in 2.4 -- the new pieces are repository/soft-binding infra, not the on-file box layout, so no parser change is implied.) Spec: https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html We now READ the soft-binding `alg` (`C2PA_SOFT_BINDINGS` / `soft_binding_vendors_in`) to name the forensic-watermark vendor, preserve the structured assertion's signed `value`, and locally DECODE the one open scheme, Adobe TrustMark (`trustmark_detector`); the rest stay name-only unless a compatible decoder is independently verified.
|
||||
- **Microsoft Paint and Photos InvisMark (reverse-engineered 2026-08-20):** Paint receives a per-generation GUID from remote prompt moderation, embeds it into locally generated pixels, and records the same value in `c2pa.soft-binding` under `com.microsoft.invismark.1`. The C2PA soft-binding registry independently identifies that algorithm as Microsoft Responsible AI InvisMark for image and video. Paint's 144-bit writer framing does not match the public repository's 100-bit pretrained checkpoint interface, so compatibility is not assumed. The parser reports the signed identifier; there is no validated local pixel decoder. Microsoft's external Content Provenance Detection API is the removal oracle because it reports `Watermark` separately from `C2PA`; a metadata-stripped, pixel-identical control must remain watermark-positive before an output-negative result is attributed to pixel removal. Sources: https://xusheng.dev/posts/reversing/mspaint_invisible_watermark/main/, https://github.com/c2pa-org/softbinding-algorithm-list/blob/main/softbinding-algorithm-list.json, and https://learn.microsoft.com/en-us/azure/ai-services/content-safety/how-to/how-to-provenance-detection
|
||||
- **Microsoft Paint and Photos InvisMark (reverse-engineered 2026-08-20):** Paint receives a per-generation GUID from remote prompt moderation, embeds it into locally generated pixels, and records the same value in `c2pa.soft-binding` under `com.microsoft.invismark.1`. The C2PA soft-binding registry independently identifies that algorithm as Microsoft Responsible AI InvisMark for image and video. Paint's 144-bit writer framing does not match the public repository's 100-bit pretrained checkpoint interface, so compatibility is not assumed. The parser reports the signed identifier; there is no validated local pixel decoder. Microsoft's external Content Provenance Detection API is the removal oracle because it reports `Watermark` separately from `C2PA`; a metadata-stripped, pixel-identical control must remain watermark-positive before an output-negative result is attributed to pixel removal. The no-account web page for the same check is <https://ai.azure.com/nextgen/validate> (its `Inconclusive` verdict is weaker than the API's separate watermark result). Sources: https://xusheng.dev/posts/reversing/mspaint_invisible_watermark/main/, https://github.com/c2pa-org/softbinding-algorithm-list/blob/main/softbinding-algorithm-list.json, and https://learn.microsoft.com/en-us/azure/ai-services/content-safety/how-to/how-to-provenance-detection
|
||||
- **Built in the dated batch:** soft-binding vendor detection, IPTC Photo
|
||||
Metadata AI-disclosure fields, C2PA detection and stripping for supported
|
||||
ISOBMFF video, the optional Adobe TrustMark decoder, and temporally stabilized
|
||||
@@ -161,9 +161,9 @@ Two consequences we can exploit: (1) the 5% floor is a **scale prior** -- a comp
|
||||
|
||||
**Tencent Yuanbao: RESOLVED 2026-07-25, registered (`yuanbao_engine.py`).** The standard mark is a compact two-line italic `元宝` over `AI生成` block at bottom-right. It switches between light and dark strokes with the scene, so detection uses polarity-independent local contrast rather than a white top-hat. The separate one-line overlay variant remains evidence-limited to one example.
|
||||
|
||||
**Meta Muse Image / Content Seal: current state VERIFIED 2026-08-26 against live artifacts.** Muse Image (launched 2026-07-07, first image model from Meta Superintelligence Labs) puts no visible mark on output. Every output carries Content Seal, a proprietary invisible pixel watermark; research lineage is open (`github.com/facebookresearch/content-seal`, Pixel Seal / VideoSeal / Watermark Anything), but the deployed implementation is proprietary and unpublished. API outputs and Meta CDN copies also carry XMP `iptcExt:DigitalSourceType = trainedAlgorithmicMedia` (a standard IPTC code, not Meta-exclusive). The only reader is the anonymous web oracle `meta.ai/identification`, which returns model attribution plus a per-generation ID and timestamp embedded in the payload. Verified robustness (our corpus, `data/contentseal/`): seal survives CDN WebP transcode, 512 px resize, JPEG q85, and full metadata stripping; it is lost to center crops of 33-50% linear size (Reuters measured 55% detector misses on cropped images, 2026-07-11). The default `qwen-zimage` profile clears it (oracle-verified). Legacy paragraph retained below.
|
||||
**Meta Muse Image / Content Seal: current state VERIFIED 2026-08-26 against live artifacts.** Muse Image (launched 2026-07-07, first image model from Meta Superintelligence Labs) puts no visible mark on output. Every output carries Content Seal, a proprietary invisible pixel watermark; research lineage is open (`github.com/facebookresearch/content-seal`, Pixel Seal / VideoSeal / Watermark Anything), but the deployed implementation is proprietary and unpublished. API outputs and Meta CDN copies also carry XMP `iptcExt:DigitalSourceType = trainedAlgorithmicMedia` (a standard IPTC code, not Meta-exclusive). The only reader is the anonymous web oracle `meta.ai/identification`, which returns model attribution plus a per-generation ID and timestamp embedded in the payload. Verified robustness (our corpus, `data/contentseal/`): seal survives CDN WebP transcode, 512 px resize, JPEG q85, and full metadata stripping; it is lost to center crops of 33-50% linear size (Reuters measured 55% detector misses on cropped images, 2026-07-11). Removal is fully supported and oracle-verified on the default profile, with a measured Meta strength floor (0.1) shipped behind auto IPTC routing and an explicit `--vendor meta` override; see [supported signals](supported-signals.md) and [module internals](module-internals.md). Legacy paragraph retained below.
|
||||
|
||||
**Meta `Imagined with AI` (string VERIFIED (a) from Meta's own newsroom; POSITION NOT VERIFIED).** Sources conflict on placement. Do not encode a corner without a verified sample. `identify` reads the supported IPTC disclosure; it does not decode Meta's proprietary invisible watermark. Source: `https://about.fb.com/news/2024/02/labeling-ai-generated-images-on-facebook-instagram-and-threads/`.
|
||||
**Meta `Imagined with AI` (string VERIFIED (a) from Meta's own newsroom; POSITION NOT VERIFIED).** Sources conflict on placement. Do not encode a corner without a verified sample. A dedicated 2026-08-27 sample hunt failed to obtain one: the Feb-2024 newsroom images are UI mockups whose photos carry no in-pixel mark; community posts (Threads `C8_rS_MuEId` titled "lower left corner", a Facebook share) corroborate bottom-LEFT verbally but their files defeat pixel verification; press screenshots predate the mark (Dec 2023); `imagine.meta.com` is dead (redirects to meta.ai) and its Wayback captures are broken SPA error pages. The mark's generator no longer exists (Muse output has no visible mark), so no fresh sample can be made. Removal stays on the generic `erase --region` path until a legacy capture surfaces. Source: `https://about.fb.com/news/2024/02/labeling-ai-generated-images-on-facebook-instagram-and-threads/`.
|
||||
|
||||
**Samsung English/other locales: still not established.** Samsung's own support page says only that "A Galaxy AI watermark will appear on AI-generated images" -- no string, no corner. Every community thread carrying the exact English string returned HTTP 403 to WebFetch, so the search paraphrase (bottom-left) is deliberately NOT recorded as fact. Feature-tier detail (b): the mark is applied by Generative Edit / sketch-to-image but reportedly NOT by Object Eraser, so Samsung absence is feature-dependent. The four-star icon variant: nothing found.
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
# on it, including the ComfyUI node package. The console script below carries
|
||||
# the same weight, since users have it on PATH.
|
||||
name = "remove-ai-watermarks"
|
||||
version = "0.31.2"
|
||||
version = "0.33.0"
|
||||
description = "AI watermark remover for visible, invisible, and provenance marks in images and video"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11,<3.15"
|
||||
|
||||
@@ -32,7 +32,7 @@ _os.environ.setdefault("TRANSFORMERS_VERBOSITY", "error")
|
||||
_warnings.filterwarnings("ignore", message=r".*ImageProcessorFast.*")
|
||||
|
||||
|
||||
__version__ = "0.31.2"
|
||||
__version__ = "0.33.0"
|
||||
|
||||
__all__ = [
|
||||
"BatchSummary",
|
||||
|
||||
@@ -426,6 +426,11 @@ def _structured_manifest_fields(store: dict[str, Any]) -> dict[str, Any]:
|
||||
source_types: list[str] = []
|
||||
soft_binding_algorithms: list[str] = []
|
||||
soft_binding_values: list[str] = []
|
||||
# Raw signer/generator identity strings, used to scope SynthID evidence to
|
||||
# the vendor that actually asserted the manifest. A vendor token appearing
|
||||
# anywhere else in the chain (e.g. Microsoft Designer's "Azure OpenAI
|
||||
# ImageGen" softwareAgent) is a service name, not that vendor's provenance.
|
||||
identity_strings: list[str] = []
|
||||
claim_generator_asserts_ai = False
|
||||
|
||||
def add_tool_matches(value: str, *, asserts_ai: bool = False) -> None:
|
||||
@@ -443,9 +448,11 @@ def _structured_manifest_fields(store: dict[str, Any]) -> dict[str, Any]:
|
||||
value = signature.get(key)
|
||||
if isinstance(value, str):
|
||||
issuers.extend(_ordered_matches(value.encode(), C2PA_ISSUERS))
|
||||
identity_strings.append(value)
|
||||
|
||||
direct_generator = manifest.get("claim_generator")
|
||||
if isinstance(direct_generator, str):
|
||||
identity_strings.append(direct_generator)
|
||||
add_tool_matches(direct_generator, asserts_ai=True)
|
||||
|
||||
candidates = manifest.get("claim_generator_info")
|
||||
@@ -456,6 +463,7 @@ def _structured_manifest_fields(store: dict[str, Any]) -> dict[str, Any]:
|
||||
name = cast("dict[object, object]", candidate_value).get("name")
|
||||
if isinstance(name, str):
|
||||
add_tool_matches(name, asserts_ai=True)
|
||||
identity_strings.append(name)
|
||||
|
||||
assertions = manifest.get("assertions")
|
||||
if not isinstance(assertions, list):
|
||||
@@ -528,9 +536,14 @@ def _structured_manifest_fields(store: dict[str, Any]) -> dict[str, Any]:
|
||||
has_watermark_action = any(action.startswith("watermarked") for action in actions)
|
||||
if has_watermark_action:
|
||||
info["watermarked"] = True
|
||||
if info.get("ai_source_kind"):
|
||||
selected_bytes = json.dumps(chain, ensure_ascii=False).encode()
|
||||
synthid = synthid_evidence_vendors_in(selected_bytes, has_watermark_action=has_watermark_action)
|
||||
if info.get("ai_source_kind") and not soft_binding_algorithms:
|
||||
# Evidence scope: only the signer/generator identity strings above, never
|
||||
# the whole chain - a vendor named inside another vendor's manifest (the
|
||||
# Designer case) must not turn into that vendor's SynthID provenance. A
|
||||
# manifest that names its own forensic soft-binding algorithm carries
|
||||
# that vendor's mark and is excluded from the generic inference entirely.
|
||||
identity_bytes = json.dumps(identity_strings, ensure_ascii=False).encode()
|
||||
synthid = synthid_evidence_vendors_in(identity_bytes, has_watermark_action=has_watermark_action)
|
||||
if synthid:
|
||||
info["synthid_vendors"] = synthid
|
||||
info["synthid_watermark"] = synthid_verdict(", ".join(synthid))
|
||||
@@ -611,7 +624,11 @@ def _populate_registry_fields(buffer: bytes, info: dict[str, Any]) -> bool:
|
||||
|
||||
if b"c2pa.watermarked" in buffer:
|
||||
info["watermarked"] = True
|
||||
synthid = synthid_evidence_vendors_in(buffer, has_watermark_action=info.get("watermarked", False))
|
||||
synthid = (
|
||||
[]
|
||||
if soft_binding_vendors_in(buffer)
|
||||
else synthid_evidence_vendors_in(buffer, has_watermark_action=info.get("watermarked", False))
|
||||
)
|
||||
if ai_source and synthid:
|
||||
info["synthid_vendors"] = synthid
|
||||
info["synthid_watermark"] = synthid_verdict(", ".join(synthid))
|
||||
|
||||
@@ -79,17 +79,34 @@ QWEN_ZIMAGE_GOOGLE_STRENGTH = 0.27
|
||||
# cross-source spread (0.00725) to the worst clean boundary: 0.0695 + 0.00725.
|
||||
QWEN_ZIMAGE_OPENAI_STRENGTH = 0.07675
|
||||
|
||||
# Microsoft's public detector returned Inconclusive rather than an API-level
|
||||
# watermark-negative verdict. Three valid Paint sources first cleared at 0.04125,
|
||||
# Microsoft's public detector (https://ai.azure.com/nextgen/validate) returned
|
||||
# Inconclusive rather than an API-level watermark-negative verdict. Three valid
|
||||
# Paint sources first cleared at 0.04125,
|
||||
# 0.055, and 0.095. Add one full observed cross-source spread to the worst clean
|
||||
# boundary: 0.095 + (0.095 - 0.04125) = 0.14875, rounded up to 0.15. This is a
|
||||
# measured corpus margin, not a universal InvisMark threshold.
|
||||
QWEN_ZIMAGE_MICROSOFT_STRENGTH = 0.15
|
||||
|
||||
# Meta Muse Image stamps every output with Content Seal, but no provenance signal
|
||||
# survives to route it: the outputs carry no C2PA, and their IPTC
|
||||
# trainedAlgorithmicMedia companion tag is a standard code many platforms use, so
|
||||
# it cannot key this cohort the way an issuer keys the others. The floor is
|
||||
# therefore selected by an explicit --vendor meta override, never by detection.
|
||||
# Derivation (oracle meta.ai/identification, 2026-08-26/27, corpus in
|
||||
# data/contentseal/): five independent 2.56 MP generations bracketed at
|
||||
# lighthouse (0.0525, 0.06], fox (0.03, 0.0375], night_city (0.03, 0.0375],
|
||||
# mug <= 0.03, text <= 0.015. Worst clean boundary plus one full observed
|
||||
# cross-source spread: 0.06 + (0.0525 - 0.015) = 0.0975, rounded up to 0.1.
|
||||
# sdxl-zimage has no measured Meta floor; its vendor map stays without a meta
|
||||
# entry so an explicit --vendor meta there falls to the unknown 0.25, which is
|
||||
# above this floor and therefore conservative.
|
||||
QWEN_ZIMAGE_META_STRENGTH = 0.1
|
||||
|
||||
_QWEN_ZIMAGE_FLAT_STRENGTH_BY_VENDOR: dict[str, float] = {
|
||||
"google": QWEN_ZIMAGE_GOOGLE_STRENGTH,
|
||||
"openai": QWEN_ZIMAGE_OPENAI_STRENGTH,
|
||||
"microsoft": QWEN_ZIMAGE_MICROSOFT_STRENGTH,
|
||||
"meta": QWEN_ZIMAGE_META_STRENGTH,
|
||||
}
|
||||
|
||||
|
||||
@@ -129,7 +146,8 @@ def strength_default_help() -> str:
|
||||
return (
|
||||
"profile-adaptive (qwen-zimage uses resolution-adaptive denoise, with a "
|
||||
f"flat OpenAI {QWEN_ZIMAGE_OPENAI_STRENGTH} / Google {QWEN_ZIMAGE_GOOGLE_STRENGTH} / "
|
||||
f"Microsoft InvisMark {QWEN_ZIMAGE_MICROSOFT_STRENGTH} floors; sdxl-zimage "
|
||||
f"Microsoft InvisMark {QWEN_ZIMAGE_MICROSOFT_STRENGTH} / Meta Content Seal "
|
||||
f"{QWEN_ZIMAGE_META_STRENGTH} floors; sdxl-zimage "
|
||||
f"uses OpenAI {SDXL_ZIMAGE_OPENAI_STRENGTH} / Google {SDXL_ZIMAGE_GEMINI_STRENGTH} / "
|
||||
f"unknown {SDXL_ZIMAGE_UNKNOWN_STRENGTH}, from the C2PA issuer)"
|
||||
)
|
||||
@@ -167,8 +185,18 @@ def resolve_strength(
|
||||
return resolution_adaptive_denoise(*size)
|
||||
|
||||
|
||||
def vendor_for_strength(image_path: Path) -> Literal["openai", "google", "microsoft"] | None:
|
||||
"""Select the strength cohort from non-invalid pixel-watermark provenance."""
|
||||
def vendor_for_strength(image_path: Path) -> Literal["openai", "google", "microsoft", "meta"] | None:
|
||||
"""Select the strength cohort from non-invalid pixel-watermark provenance.
|
||||
|
||||
OpenAI / Google / Microsoft come from their C2PA issuers. Meta is the
|
||||
fallback cohort: Muse Image carries no C2PA at all, and its only readable
|
||||
companion is the IPTC ``trainedAlgorithmicMedia`` XMP tag -- a standard code
|
||||
other platforms also use. Attributing that tag to Meta is a measured bet,
|
||||
not an identification: the other tag users in this project's model (ByteDance
|
||||
products, X) ship no invisible pixel watermark this profile targets, so the
|
||||
worst misroute spends the Meta floor (0.1) where the resolution curve would
|
||||
have spent a similar amount, and Google/OpenAI files never reach this arm
|
||||
because their C2PA matched first."""
|
||||
try:
|
||||
from remove_ai_watermarks._internal.c2pa import (
|
||||
c2pa_info_has_invalid_credential,
|
||||
@@ -187,4 +215,25 @@ def vendor_for_strength(image_path: Path) -> Literal["openai", "google", "micros
|
||||
return "openai"
|
||||
if not c2pa_info_has_invalid_credential(info) and c2pa_info_has_invismark(info):
|
||||
return "microsoft"
|
||||
if _standalone_iptc_ai_tag(image_path):
|
||||
return "meta"
|
||||
return None
|
||||
|
||||
|
||||
def _standalone_iptc_ai_tag(image_path: Path) -> bool:
|
||||
"""True when the file carries an AI IPTC marker with no C2PA around it.
|
||||
|
||||
Mirrors identify's ``standalone_iptc`` condition (the tag is only
|
||||
trustworthy as platform evidence when no manifest supersedes it) without
|
||||
importing the heavy identify module: the shared chunk-aware
|
||||
:func:`metadata.scan_head` window -- Muse WebP outputs place their XMP
|
||||
packet in a tail chunk up to hundreds of KB past a plain head read, which
|
||||
is exactly what scan_head's extensions exist to catch.
|
||||
"""
|
||||
try:
|
||||
from remove_ai_watermarks.metadata import IPTC_AI_MARKERS, c2pa_marker_in, scan_head
|
||||
|
||||
scan = scan_head(image_path)
|
||||
except Exception:
|
||||
return False
|
||||
return any(marker in scan for marker in IPTC_AI_MARKERS) and not c2pa_marker_in(scan)
|
||||
@@ -232,6 +232,7 @@ class InvisibleOptions:
|
||||
|
||||
strength: float | None = None
|
||||
pipeline: str = "qwen-zimage"
|
||||
vendor: str | None = None
|
||||
seed: int | None = None
|
||||
hf_token: str | None = None
|
||||
humanize: float = 0.0
|
||||
@@ -487,13 +488,17 @@ def _run_invisible(
|
||||
if not is_available():
|
||||
say("invisible", "unavailable")
|
||||
return "unavailable"
|
||||
if not (force or evidence.has_invisible_target()):
|
||||
if not (force or opts.vendor is not None or evidence.has_invisible_target()):
|
||||
say("invisible", "no-signal")
|
||||
return "no-signal"
|
||||
|
||||
from remove_ai_watermarks._internal.watermark_profiles import resolve_strength, vendor_for_strength
|
||||
|
||||
vendor = vendor_for_strength(vendor_source)
|
||||
# An explicit vendor override wins over detection and implies the scrub runs:
|
||||
# naming the cohort (e.g. "meta" for Muse Image Content Seal, which carries no
|
||||
# provenance to detect) asserts the pixel watermark is present, so the no-signal
|
||||
# gate must not skip it.
|
||||
vendor = opts.vendor or vendor_for_strength(vendor_source)
|
||||
# Report the strength the engine will actually execute, resolved the same way it
|
||||
# resolves it, so the reported value cannot drift from the executed one.
|
||||
with suppress(Exception):
|
||||
|
||||
@@ -274,6 +274,32 @@ _strength_option = click.option(
|
||||
default=None,
|
||||
help=f"Denoising strength (0.0-1.0). Default: {strength_default_help()}.",
|
||||
)
|
||||
# Explicit strength-cohort override. Auto-detection reads the C2PA issuer, so it
|
||||
# covers OpenAI / Google / Microsoft; Meta Content Seal has no provenance signal
|
||||
# (no C2PA; the IPTC tag is a standard code), and an unknown or stripped manifest
|
||||
# also leaves the resolution-adaptive curve in charge -- this flag is the way to
|
||||
# name the cohort when the user knows what the file does not say.
|
||||
_vendor_option = click.option(
|
||||
"--vendor",
|
||||
type=click.Choice(["auto", "openai", "google", "microsoft", "meta"]),
|
||||
default="auto",
|
||||
help=(
|
||||
"Strength cohort for the invisible-removal default, and it implies the scrub "
|
||||
"runs even without a local signal: naming the cohort asserts the pixel "
|
||||
"watermark is present. auto: derive from C2PA provenance, else "
|
||||
"resolution-adaptive. Set explicitly when the source is known but unreadable "
|
||||
"(e.g. meta for Muse Image Content Seal, which never carries C2PA)."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _explicit_vendor(vendor: str | None) -> str | None:
|
||||
"""Normalize --vendor's ``auto`` default to None for the engine/API seam.
|
||||
|
||||
One helper so the three diffusion commands cannot drift on the spelling."""
|
||||
return None if vendor in (None, "auto") else vendor
|
||||
|
||||
|
||||
_seed_option = click.option(
|
||||
"--seed",
|
||||
type=int,
|
||||
@@ -796,6 +822,7 @@ def cmd_erase(
|
||||
@click.argument("source", type=click.Path(exists=True, dir_okay=False, path_type=Path))
|
||||
@_output_option
|
||||
@_strength_option
|
||||
@_vendor_option
|
||||
@_pipeline_option
|
||||
@_seed_option
|
||||
@_hf_token_option
|
||||
@@ -815,6 +842,7 @@ def cmd_invisible(
|
||||
source: Path,
|
||||
output: Path | None,
|
||||
strength: float | None,
|
||||
vendor: str | None,
|
||||
pipeline: str,
|
||||
seed: int | None,
|
||||
hf_token: str | None,
|
||||
@@ -851,11 +879,17 @@ def cmd_invisible(
|
||||
if output is None:
|
||||
output = source.with_stem(source.stem + "_clean")
|
||||
|
||||
# An explicit --vendor wins over detection (see the option help) and implies the
|
||||
# scrub runs: naming the cohort asserts the pixel watermark is present, so the
|
||||
# no-signal gate must not skip it. Resolved BEFORE the gate for the same reason.
|
||||
resolved_vendor = _explicit_vendor(vendor)
|
||||
|
||||
# Gate BEFORE building the engine: skip the destructive regeneration when no
|
||||
# invisible AI watermark is locally detectable (it would only degrade a clean
|
||||
# image -- dominant paid score-0 cause), so the common skip path pays nothing for
|
||||
# engine construction. A skip never claims the image is clean; --force overrides.
|
||||
if _should_skip_invisible_scrub(force, source):
|
||||
# engine construction. A skip never claims the image is clean; --force and an
|
||||
# explicit --vendor override.
|
||||
if _should_skip_invisible_scrub(force or resolved_vendor is not None, source):
|
||||
_no_invisible_signal_exit(source)
|
||||
|
||||
def progress_cb(msg: str) -> None:
|
||||
@@ -870,11 +904,18 @@ def cmd_invisible(
|
||||
)
|
||||
|
||||
# Detect the SynthID vendor from the ORIGINAL (before processing strips C2PA) so the
|
||||
# displayed and executed strength agree on the vendor-adaptive default.
|
||||
vendor = vendor_for_strength(source)
|
||||
# displayed and executed strength agree on the vendor-adaptive default. An explicit
|
||||
# --vendor override wins over detection: it names a cohort the file cannot prove
|
||||
# (Meta Content Seal never carries C2PA; a stripped manifest proves nothing).
|
||||
detected_vendor = vendor_for_strength(source) if resolved_vendor is None else None
|
||||
vendor_label = resolved_vendor or detected_vendor
|
||||
vendor_note = " (override)" if resolved_vendor else ""
|
||||
console.print(f" Input: {source.name}")
|
||||
console.print(f" Pipeline: {pipeline}")
|
||||
console.print(f" Strength: {_resolved_strength_for_display(source, strength, vendor, pipeline)}")
|
||||
console.print(
|
||||
f" Strength: {_resolved_strength_for_display(source, strength, vendor_label, pipeline)}"
|
||||
+ (f" [vendor: {vendor_label}{vendor_note}]" if vendor_label else "")
|
||||
)
|
||||
|
||||
t0 = time.monotonic()
|
||||
try:
|
||||
@@ -887,7 +928,7 @@ def cmd_invisible(
|
||||
unsharp=unsharp,
|
||||
adaptive_polish=adaptive_polish,
|
||||
max_resolution=max_resolution,
|
||||
vendor=vendor,
|
||||
vendor=vendor_label,
|
||||
tile=tile,
|
||||
tile_size=tile_size,
|
||||
tile_overlap=tile_overlap,
|
||||
@@ -1448,6 +1489,7 @@ def cmd_identify(ctx: click.Context, source: Path, no_visible: bool, as_json: bo
|
||||
@_visible_backend_option
|
||||
@_visible_sensitivity_option
|
||||
@_strength_option
|
||||
@_vendor_option
|
||||
@_pipeline_option
|
||||
@_seed_option
|
||||
@_hf_token_option
|
||||
@@ -1469,6 +1511,7 @@ def cmd_all(
|
||||
backend: str,
|
||||
sensitivity: str,
|
||||
strength: float | None,
|
||||
vendor: str | None,
|
||||
pipeline: str,
|
||||
seed: int | None,
|
||||
hf_token: str | None,
|
||||
@@ -1549,6 +1592,7 @@ def cmd_all(
|
||||
sensitivity=_parse_sensitivity(sensitivity),
|
||||
invisible=InvisibleOptions(
|
||||
strength=strength,
|
||||
vendor=_explicit_vendor(vendor),
|
||||
pipeline=pipeline,
|
||||
seed=seed,
|
||||
hf_token=hf_token,
|
||||
@@ -1643,6 +1687,7 @@ def _batch_engine(mode: str, options: InvisibleOptions) -> object | None:
|
||||
@_visible_backend_option
|
||||
@_visible_sensitivity_option
|
||||
@_humanize_option
|
||||
@_vendor_option
|
||||
@_pipeline_option
|
||||
@_seed_option
|
||||
@_hf_token_option
|
||||
@@ -1660,6 +1705,7 @@ def cmd_batch(
|
||||
mode: str,
|
||||
output_dir: Path | None,
|
||||
strength: float | None,
|
||||
vendor: str | None,
|
||||
pipeline: str,
|
||||
seed: int | None,
|
||||
hf_token: str | None,
|
||||
@@ -1697,6 +1743,7 @@ def cmd_batch(
|
||||
|
||||
invisible_options = InvisibleOptions(
|
||||
strength=strength,
|
||||
vendor=_explicit_vendor(vendor),
|
||||
pipeline=pipeline,
|
||||
seed=seed,
|
||||
hf_token=hf_token,
|
||||
|
||||
@@ -135,6 +135,11 @@ _C2PA_INVALID_CAVEAT = (
|
||||
"are retained only as removal hints, not as verified provenance."
|
||||
)
|
||||
_IPTC_ONLY_CAVEAT = "The IPTC 'Made with AI' tag flags AI provenance but does not identify the specific platform."
|
||||
_CONTENT_SEAL_CAVEAT = (
|
||||
"Meta Muse Image outputs carry the invisible Content Seal pixel watermark, which has no "
|
||||
"local decoder; `invisible` removes it (auto when this tag is present, or `--vendor meta` "
|
||||
"on stripped files) and meta.ai/identification verifies it."
|
||||
)
|
||||
_INVISIBLE_WM_CAVEAT = (
|
||||
"The open invisible watermark is fragile: it does not survive JPEG re-encoding "
|
||||
"or resizing, so it confirms origin only on a pristine (un-re-encoded) file."
|
||||
@@ -1268,7 +1273,19 @@ def _identify_from_evidence(
|
||||
# reusing the derived `has_c2pa` / `source_kind` above, which are broader:
|
||||
# the file path's answer must not move.
|
||||
trained_source = b"trainedAlgorithmicMedia" in head or b"TrainedAlgorithmicMedia" in head
|
||||
if not synthid and trained_source and c2pa_marker_in(head) and (vendors := synthid_evidence_vendors_in(region)):
|
||||
# Same suppression as every other inference site: bytes that name their own
|
||||
# forensic soft-binding algorithm carry that vendor's mark, and the generic
|
||||
# vendor-token inference must not add a second, differently-attributed
|
||||
# invisible watermark (Microsoft Designer: "Azure OpenAI ImageGen" agent +
|
||||
# the InvisMark watermarked action read as "SynthID per OpenAI").
|
||||
soft_binding_vendors = soft_binding_vendors_in(region)
|
||||
if (
|
||||
not synthid
|
||||
and trained_source
|
||||
and c2pa_marker_in(head)
|
||||
and not soft_binding_vendors
|
||||
and (vendors := synthid_evidence_vendors_in(region))
|
||||
):
|
||||
synthid = synthid_verdict(", ".join(vendors))
|
||||
if synthid:
|
||||
watermarks.append(
|
||||
@@ -1283,7 +1300,7 @@ def _identify_from_evidence(
|
||||
# ── C2PA soft-binding: a named forensic/third-party watermark vendor ─
|
||||
# (Adobe TrustMark, Digimarc, Imatag, ...). Present in the manifest even when
|
||||
# the watermark itself can't be decoded; names whose watermark stamped the pixels.
|
||||
soft_binding = meta.get("soft_binding") or (", ".join(v) if (v := soft_binding_vendors_in(region)) else None)
|
||||
soft_binding = meta.get("soft_binding") or (", ".join(soft_binding_vendors) if soft_binding_vendors else None)
|
||||
if soft_binding:
|
||||
soft_binding_algorithm = meta.get("soft_binding_algorithm") or info.get("soft_binding_algorithm")
|
||||
soft_binding_value = meta.get("soft_binding_value") or info.get("soft_binding_value")
|
||||
@@ -1317,7 +1334,23 @@ def _identify_from_evidence(
|
||||
if standalone_iptc:
|
||||
signals.append(Signal("iptc", "digitalSourceType (Made with AI)", "high"))
|
||||
watermarks.append("IPTC digitalSourceType (Made with AI)")
|
||||
# Muse Image stamps every output with the invisible Content Seal, and this
|
||||
# tag is the only provenance such a file carries - the same measured bet
|
||||
# the strength router makes (vendor_for_strength -> "meta"). Emit the seal
|
||||
# as its own stable signal, the way InvisMark is additive over
|
||||
# soft_binding, so clients select pixel removal from the signal list
|
||||
# instead of parsing caveats. It is an attribution, not a decode: no
|
||||
# public Content Seal decoder exists, hence "medium".
|
||||
signals.append(
|
||||
Signal(
|
||||
"content_seal",
|
||||
"Meta Muse Content Seal pixel watermark (attributed by the standalone AI digital-source tag)",
|
||||
"medium",
|
||||
)
|
||||
)
|
||||
watermarks.append("Invisible Content Seal watermark (Meta Muse attribution)")
|
||||
caveats.append(_IPTC_ONLY_CAVEAT)
|
||||
caveats.append(_CONTENT_SEAL_CAVEAT)
|
||||
if platform is None:
|
||||
# Apple Photos Clean Up (Apple Intelligence object removal) marks
|
||||
# the edit with photoshop:Credit / IPTC "Apple Photos Clean Up"
|
||||
|
||||
@@ -839,6 +839,13 @@ def synthid_source(image_path: Path, *, c2pa_info: dict[str, Any] | None = None)
|
||||
ai_source = b"trainedAlgorithmicMedia" in data or b"TrainedAlgorithmicMedia" in data
|
||||
if not (has_c2pa and ai_source):
|
||||
return None
|
||||
from remove_ai_watermarks._internal.c2pa import soft_binding_vendors_in
|
||||
|
||||
# A scan that names its own forensic soft-binding algorithm carries that
|
||||
# vendor's mark; the generic vendor-token inference must not add a second,
|
||||
# differently-attributed invisible watermark from the same bytes.
|
||||
if soft_binding_vendors_in(data):
|
||||
return None
|
||||
matched = synthid_evidence_vendors_in(data)
|
||||
return ", ".join(matched) if matched else None
|
||||
|
||||
|
||||
@@ -426,6 +426,39 @@ class TestInvisibleCommand:
|
||||
assert result.exit_code == 0, result.output
|
||||
mock_engine.remove_watermark.assert_called_once()
|
||||
|
||||
def test_invisible_explicit_vendor_implies_force_and_sets_cohort(self, runner, sample_png, tmp_path):
|
||||
"""--vendor meta names a cohort the file cannot prove (Content Seal carries
|
||||
no C2PA), so it must both bypass the no-signal skip and arrive at the engine
|
||||
as the vendor, where it resolves to the measured Meta floor (not the
|
||||
area-curve value the same size would otherwise get)."""
|
||||
mock_cls, mock_engine = _mock_invisible_engine()
|
||||
output = tmp_path / "clean.png"
|
||||
with (
|
||||
patch("remove_ai_watermarks.invisible_engine.is_available", return_value=True),
|
||||
patch("remove_ai_watermarks.cli.InvisibleEngine", mock_cls, create=True),
|
||||
patch("remove_ai_watermarks.invisible_engine.InvisibleEngine", mock_cls),
|
||||
):
|
||||
result = runner.invoke(main, ["invisible", str(sample_png), "-o", str(output), "--vendor", "meta"])
|
||||
assert result.exit_code == 0, result.output
|
||||
kwargs = mock_engine.remove_watermark.call_args.kwargs
|
||||
assert kwargs["vendor"] == "meta"
|
||||
assert "0.1" in result.output # the resolved Meta floor, printed
|
||||
assert "0.094" not in result.output # not the sample's area-curve value (200x200 -> ~0.0944)
|
||||
|
||||
def test_invisible_vendor_auto_keeps_detection_semantics(self, runner, sample_png, tmp_path):
|
||||
"""--vendor auto is the default spelled out: detection still runs and a
|
||||
no-signal file still skips."""
|
||||
mock_cls, mock_engine = _mock_invisible_engine()
|
||||
output = tmp_path / "clean.png"
|
||||
with (
|
||||
patch("remove_ai_watermarks.invisible_engine.is_available", return_value=True),
|
||||
patch("remove_ai_watermarks.cli.InvisibleEngine", mock_cls, create=True),
|
||||
patch("remove_ai_watermarks.invisible_engine.InvisibleEngine", mock_cls),
|
||||
):
|
||||
result = runner.invoke(main, ["invisible", str(sample_png), "-o", str(output), "--vendor", "auto"])
|
||||
assert result.exit_code == 2, result.output
|
||||
mock_engine.remove_watermark.assert_not_called()
|
||||
|
||||
def test_invisible_runs_without_force_when_signal_present(self, runner, tmp_path):
|
||||
"""An image carrying an AI metadata signal IS a scrub target, so the run
|
||||
proceeds with no --force needed."""
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
"""Regression: a manifest that names its own forensic soft binding must not
|
||||
also report a SynthID watermark from the generic vendor-token inference.
|
||||
|
||||
Microsoft Designer manifests sign as Microsoft, carry the InvisMark
|
||||
``c2pa.watermarked`` action, and name their generation agent
|
||||
"Azure OpenAI ImageGen". The OpenAI issuer token inside that agent name plus
|
||||
the watermarked action used to satisfy the OpenAI SynthID-evidence rule,
|
||||
double-counting one forensic mark as two pixel watermarks.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from remove_ai_watermarks._internal.c2pa import c2pa_info_from_manifest_store
|
||||
|
||||
DESIGNER_STORE = {
|
||||
"active_manifest": "designer",
|
||||
"manifests": {
|
||||
"designer": {
|
||||
"signature_info": {"issuer": "Microsoft Corporation", "common_name": "Microsoft Corporation"},
|
||||
"claim_generator_info": [{"name": "Microsoft Responsible AI Provenance", "version": "1.0"}],
|
||||
"assertions": [
|
||||
{
|
||||
"label": "c2pa.actions",
|
||||
"data": {
|
||||
"actions": [
|
||||
{
|
||||
"action": "c2pa.created",
|
||||
"softwareAgent": {"name": "Azure OpenAI ImageGen"},
|
||||
"digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia",
|
||||
},
|
||||
{"action": "c2pa.watermarked"},
|
||||
]
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "c2pa.soft-binding",
|
||||
"data": {"alg": "com.microsoft.invismark.1", "blocks": [{"value": "bf7a2993-cc1f-47e1-b1f0-cd8839aabb22"}]},
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_named_soft_binding_suppresses_generic_synthid_evidence() -> None:
|
||||
info = c2pa_info_from_manifest_store(DESIGNER_STORE)
|
||||
assert info["ai_source_kind"] == "generated"
|
||||
assert info["soft_binding_algorithm"] == "com.microsoft.invismark.1"
|
||||
assert info.get("synthid_watermark") is None
|
||||
assert info.get("synthid_vendors") is None
|
||||
|
||||
|
||||
def test_vendor_agent_name_alone_is_not_the_vendors_provenance() -> None:
|
||||
"""The identity-scoped inference must not fire on a service name either.
|
||||
|
||||
Same manifest without the soft binding: the "Azure OpenAI ImageGen" agent
|
||||
is not an OpenAI signature or claim generator, so no OpenAI SynthID
|
||||
evidence may be derived from it.
|
||||
"""
|
||||
store = {
|
||||
"active_manifest": "designer",
|
||||
"manifests": {
|
||||
"designer": {
|
||||
"signature_info": {"issuer": "Microsoft Corporation", "common_name": "Microsoft Corporation"},
|
||||
"claim_generator_info": [{"name": "Microsoft Responsible AI Provenance", "version": "1.0"}],
|
||||
"assertions": [
|
||||
{
|
||||
"label": "c2pa.actions",
|
||||
"data": {
|
||||
"actions": [
|
||||
{
|
||||
"action": "c2pa.created",
|
||||
"softwareAgent": {"name": "Azure OpenAI ImageGen"},
|
||||
"digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia",
|
||||
},
|
||||
{"action": "c2pa.watermarked"},
|
||||
]
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
}
|
||||
info = c2pa_info_from_manifest_store(store)
|
||||
assert info.get("synthid_watermark") is None
|
||||
@@ -666,6 +666,35 @@ class TestIdentifyRealSamples:
|
||||
assert r.is_ai_generated is True
|
||||
assert r.ai_source_kind == "enhanced"
|
||||
|
||||
def test_standalone_ai_tag_attributes_the_content_seal(self, tmp_path: Path):
|
||||
"""A standalone AI digital-source tag emits the seal as its own signal.
|
||||
|
||||
Muse Image outputs carry no C2PA; this tag is their only provenance, and
|
||||
Muse stamps every output with the invisible Content Seal. The signal is
|
||||
the strength router's Meta bet as evidence - an attribution, not a decode
|
||||
(no public decoder exists), so its confidence is medium and the caveat
|
||||
still points at the oracle.
|
||||
"""
|
||||
p = tmp_path / "muse-tag.jpg"
|
||||
p.write_bytes(
|
||||
b'\xff\xd8\xff\xe1<x:xmpmeta Iptc4xmpExt:DigitalSourceType="trainedAlgorithmicMedia"></x:xmpmeta>\xff\xd9'
|
||||
)
|
||||
|
||||
r = identify(p, check_visible=False, check_invisible=False)
|
||||
|
||||
names = [s.name for s in r.signals]
|
||||
assert "iptc" in names
|
||||
assert "content_seal" in names
|
||||
seal = next(s for s in r.signals if s.name == "content_seal")
|
||||
assert seal.confidence == "medium"
|
||||
assert "Invisible Content Seal watermark (Meta Muse attribution)" in r.watermarks
|
||||
assert any("meta.ai/identification" in c for c in r.caveats)
|
||||
|
||||
def test_c2pa_backed_file_gets_no_content_seal_attribution(self):
|
||||
"""C2PA issuers win first: a manifest-backed file is not Meta-routed."""
|
||||
r = identify(SAMPLES_DIR / "flux-1.png", check_visible=False, check_invisible=False)
|
||||
assert "content_seal" not in [s.name for s in r.signals]
|
||||
|
||||
def test_flux_bfl_c2pa_png(self):
|
||||
# flux-1.png: real Black Forest Labs FLUX.2 Playground output (signed C2PA).
|
||||
r = identify(SAMPLES_DIR / "flux-1.png", check_visible=False)
|
||||
|
||||
+51
-1
@@ -6,6 +6,7 @@ answer and a clean refusal rather than a fallback ladder.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import struct
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
@@ -15,6 +16,7 @@ from remove_ai_watermarks._internal.utils import get_image_format, is_supported_
|
||||
from remove_ai_watermarks._internal.watermark_profiles import (
|
||||
PROFILE_CHOICES,
|
||||
QWEN_ZIMAGE_GOOGLE_STRENGTH,
|
||||
QWEN_ZIMAGE_META_STRENGTH,
|
||||
QWEN_ZIMAGE_OPENAI_STRENGTH,
|
||||
REMOVAL_MODULES,
|
||||
SDXL_ZIMAGE_GEMINI_STRENGTH,
|
||||
@@ -198,6 +200,30 @@ class TestNoReembeddedWatermark:
|
||||
assert "add_watermarker" not in calls["controlnet"]
|
||||
|
||||
|
||||
# Minimal WebP stub whose XMP chunk carries the IPTC trainedAlgorithmicMedia
|
||||
# tag exactly as Muse outputs place it (built inline so the test has no binary
|
||||
# fixture dependency).
|
||||
_XMP_PAYLOAD = (
|
||||
b'<x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax#">'
|
||||
b'<rdf:Description rdf:about="" xmlns:iptcExt="http://iptc.org/std/Iptc4xmpExt/2008-02-29/" '
|
||||
b'iptcExt:DigitalSourceType="http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia"/>'
|
||||
b"</rdf:RDF></x:xmpmeta>"
|
||||
)
|
||||
|
||||
|
||||
def _webp_stub(xmp: bytes | None) -> bytes:
|
||||
def chunk(cid: bytes, data: bytes) -> bytes:
|
||||
return cid + struct.pack("<I", len(data)) + data + (b"\x00" if len(data) % 2 else b"")
|
||||
|
||||
body = b"WEBP" + chunk(b"VP8L", b"\x00" * 20)
|
||||
if xmp is not None:
|
||||
body += chunk(b"XMP ", xmp)
|
||||
return b"RIFF" + struct.pack("<I", len(body)) + body
|
||||
|
||||
|
||||
_MUSE_WEBP_WITH_IPTC_TAG = _webp_stub(_XMP_PAYLOAD)
|
||||
|
||||
|
||||
class TestResolveStrength:
|
||||
"""resolve_strength owns the qwen-zimage and sdxl-zimage policies."""
|
||||
|
||||
@@ -222,12 +248,36 @@ class TestResolveStrength:
|
||||
with pytest.raises(ValueError, match="size is required"):
|
||||
resolve_strength(None, "openai", "qwen-zimage")
|
||||
|
||||
@pytest.mark.parametrize(("vendor", "expected"), [("microsoft", 0.15), ("openai", 0.07675)])
|
||||
@pytest.mark.parametrize(("vendor", "expected"), [("microsoft", 0.15), ("openai", 0.07675), ("meta", 0.1)])
|
||||
def test_qwen_zimage_measured_vendors_use_flat_cross_source_margins(self, vendor, expected):
|
||||
"""Measured providers must not fall below their operating points on small files."""
|
||||
assert resolve_strength(None, vendor, "qwen-zimage", size=(600, 500)) == pytest.approx(expected)
|
||||
assert resolve_strength(None, vendor, "qwen-zimage", size=(4000, 3000)) == pytest.approx(expected)
|
||||
|
||||
def test_meta_floor_is_size_independent_and_sdxl_falls_to_unknown(self):
|
||||
"""The Meta floor bypasses the area curve at every size (Content Seal removal
|
||||
was bracketed on 2.56 MP generations and derived as a flat cross-source
|
||||
margin, like the other measured cohorts), while sdxl-zimage has no measured
|
||||
Meta rung and must fall to the conservative unknown value, not invent one."""
|
||||
for size in ((600, 500), (1600, 1600), (1920, 1280), (4000, 3000)):
|
||||
assert resolve_strength(None, "meta", "qwen-zimage", size=size) == pytest.approx(QWEN_ZIMAGE_META_STRENGTH)
|
||||
assert resolve_strength(None, "meta", "sdxl-zimage") == SDXL_ZIMAGE_UNKNOWN_STRENGTH
|
||||
|
||||
def test_vendor_for_strength_routes_standalone_iptc_to_meta(self, tmp_path):
|
||||
"""Auto mode: a file whose only provenance is the AI IPTC tag routes to the
|
||||
meta cohort (Muse carries no C2PA; the tag is its fallback companion), while
|
||||
a file without the tag stays on the resolution curve and a C2PA issuer still
|
||||
wins over the tag."""
|
||||
from remove_ai_watermarks._internal.watermark_profiles import vendor_for_strength
|
||||
|
||||
tagged = tmp_path / "tagged.webp"
|
||||
tagged.write_bytes(_MUSE_WEBP_WITH_IPTC_TAG)
|
||||
assert vendor_for_strength(tagged) == "meta"
|
||||
|
||||
stripped = tmp_path / "stripped.webp"
|
||||
stripped.write_bytes(b"RIFF\x24\x00\x00\x00WEBPVP8 \x18\x00\x00\x00" + b"\x00" * 16)
|
||||
assert vendor_for_strength(stripped) is None
|
||||
|
||||
def test_sdxl_zimage_uses_its_flat_vendor_ladder(self):
|
||||
|
||||
assert SDXL_ZIMAGE_OPENAI_STRENGTH == 0.15
|
||||
|
||||
@@ -288,7 +288,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "c2pa-python"
|
||||
version = "0.37.7"
|
||||
version = "0.37.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cryptography" },
|
||||
@@ -298,15 +298,15 @@ dependencies = [
|
||||
{ name = "toml" },
|
||||
{ name = "wheel" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/58/74/291b19f26fe628ca6bf001be741dfe634f8689756cf8495499e02ab2f631/c2pa_python-0.37.7.tar.gz", hash = "sha256:66114c3bc6b073249cb33043e491cf292e052ff770c1aefaed66a5ca2d8aaa0c", size = 112987, upload-time = "2026-08-13T21:25:27.464Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1f/32/e93959d17db5af4078532f587b9638e87b6429ca0b51348a8ac6ad99e471/c2pa_python-0.37.8.tar.gz", hash = "sha256:c52eb07244558a5997fe61b6ed8d879b4f46f38642dd11956bcedf888c58649c", size = 113008, upload-time = "2026-08-27T20:40:06.081Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/ee/be1b567bd85563ee72f7826df23c1fa13886f086b58714dda9bb2f9491a7/c2pa_python-0.37.7-py3-none-macosx_10_9_universal2.whl", hash = "sha256:fde655c31e31015b1474a06f85728e878258e1fe7f2fff6cf4924e4985479da9", size = 16187849, upload-time = "2026-08-13T21:25:06.779Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/69/50dea7bcbfd68ce7f6734735a1fb9e80ac15498520946a2ff1598d7fef5b/c2pa_python-0.37.7-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:d2ee0682f00c3a361d6401a6d2ae249bf953c2375c6945e5ebcf3cbd8022e8e4", size = 14228094, upload-time = "2026-08-13T21:25:09.687Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/c2/2c8a58a88488b30f9f96454eee26f815622cfca00b965d60b7a639c7d94d/c2pa_python-0.37.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:dae0e1177919a2b89b8d951d525421b35d67b21bc38049d544dbcfd881555a44", size = 13869736, upload-time = "2026-08-13T21:25:12.051Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/a6/9a4eafdcc836694542eaa3bade9e994248fa777ae4c0eac2bc4f1f199121/c2pa_python-0.37.7-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:c6b2e2b4cb7571e4b8ee76b6f9c472c44aa5650702207db06057d03a14ca2fd2", size = 14085896, upload-time = "2026-08-13T21:25:14.349Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/c2/983bcdc449e2c4384f0a7299859ec578365c54a2c3ca42030724ab21751e/c2pa_python-0.37.7-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:36bb8eff8904869d8fdf00f6aabe7ba41389d3dbffba02bae324c690450019c2", size = 14714332, upload-time = "2026-08-13T21:25:16.718Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/0e/86d47b5dba0a960bac3225e16f7aaa202ea894efed683801a9b99fc33092/c2pa_python-0.37.7-py3-none-win_amd64.whl", hash = "sha256:8054383a53d50dd86197f391bb2e91871284eaecf94410e6d3050803d02fd5e3", size = 86460712, upload-time = "2026-08-13T21:25:20.269Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/30/4e95f1a9be67bb66690cde0b6143f8761d294d67919cf99016850d7963d7/c2pa_python-0.37.7-py3-none-win_arm64.whl", hash = "sha256:a0980f7d94459a2569c0338af9cdd5b7301ded6578eab2701ed61f8590d3fca4", size = 83941581, upload-time = "2026-08-13T21:25:24.573Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/6a/252d8d072ac4192df8da28771be64a54cb4ea73f08f3ca1f2b7fafa823a5/c2pa_python-0.37.8-py3-none-macosx_10_9_universal2.whl", hash = "sha256:8dae7d198dd583de3963529b60665e611e36ddca677f85700147d3bdbc885b27", size = 16186835, upload-time = "2026-08-27T20:39:35.421Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/b7/3519bbb3735adce56eb0836d25fcf4e00c36c4e5cd480ee5277aadba18ed/c2pa_python-0.37.8-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:d3ff8da9be41c6f74218872f8ebf0b0020dbc00202bb30fda751422418f679c5", size = 14230208, upload-time = "2026-08-27T20:39:38.587Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/5c/ed3000ca3de522c17797482a90cd64f57cdd2fb9b21a49910c04327954bf/c2pa_python-0.37.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ee4fbbe3b98dd4349cc9c60db3c4fb1c349c65b5c72fb89a0de403ca778d3d0d", size = 13865982, upload-time = "2026-08-27T20:39:41.837Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/bd/934002a34aa76e5136508b524734dc3c4719f6d87b5a98cf93cbf4917d05/c2pa_python-0.37.8-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:e8c04f4220194a3392dda290be96181ffc94ee5008e87e1fa96c390ec4f9c53c", size = 14077063, upload-time = "2026-08-27T20:39:45.069Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/e8/ca03c21e253e95a3aaab472697f1c0c766af852cb643879b4928f6c57298/c2pa_python-0.37.8-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:20742bd702d09476390dc73a375883990291ea458af8f3e61ef48fa9928f2313", size = 14715089, upload-time = "2026-08-27T20:39:48.275Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/cd/376d90d3073ad975d38a42bdbcb42cec071bff3b6831dd5a6b807526a1df/c2pa_python-0.37.8-py3-none-win_amd64.whl", hash = "sha256:5cf1dadd1c8de7da298db9c62c432180d7185a31e81a96dfae7bce7944b21ddd", size = 86478089, upload-time = "2026-08-27T20:39:55.511Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/df/3e59b7813be65678d28f88a7a0c7a13ba8c983fe2a1ecf406959dc7b88df/c2pa_python-0.37.8-py3-none-win_arm64.whl", hash = "sha256:af827698ead013ca20e829e3c9fe1846b548ae77d1ab84762118fbde340e3f0a", size = 83936189, upload-time = "2026-08-27T20:40:03.148Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -546,14 +546,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.4.2"
|
||||
version = "8.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -919,7 +916,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "diffusers"
|
||||
version = "0.39.0"
|
||||
version = "0.40.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "filelock" },
|
||||
@@ -933,9 +930,9 @@ dependencies = [
|
||||
{ name = "requests" },
|
||||
{ name = "safetensors" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1a/81/6095237b86a3116c4789f28c4435d5296c00c0fc74ffde99008fd6b3a36c/diffusers-0.39.0.tar.gz", hash = "sha256:14bb1d98c85a0e463d734c99aaa73b480a7bc9bad22af30fbf730ef8f09c1d67", size = 4651240, upload-time = "2026-07-03T08:48:47.904Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/59/32/b9c6a198812e2a7d2c2828a850dba384306f9af285eac23e35e89d5b008a/diffusers-0.40.0.tar.gz", hash = "sha256:49f112ce52ff6d332ab68afec01f79a53e6489b42cb69104444c98b4a2f64af8", size = 4886420, upload-time = "2026-08-20T14:30:40.923Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/3f/7469c46e9d22307ea686bab687d70e6bf328722952f9d10339f5e913e608/diffusers-0.39.0-py3-none-any.whl", hash = "sha256:912aca51b5787365110806e984d5555735bf8a461073bb8459029d0bca7870ef", size = 5631176, upload-time = "2026-07-03T08:48:45.337Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/df/ffb593ebed2a068d2d6be44261283f39a6b809c0fcdfdcafbd448cbeec77/diffusers-0.40.0-py3-none-any.whl", hash = "sha256:5b5da7c3ddb62152fa4afc577f02e050af688c797375c34fb2d01006da3f3541", size = 5911654, upload-time = "2026-08-20T14:30:38.422Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1185,7 +1182,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "huggingface-hub"
|
||||
version = "1.27.0"
|
||||
version = "1.28.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
@@ -1198,9 +1195,9 @@ dependencies = [
|
||||
{ name = "tqdm" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3e/9b/ddf3d02a8681f1b9ce52fda03d755dad6b74c4f8172304c4c8d2975450f9/huggingface_hub-1.27.0.tar.gz", hash = "sha256:c1fed40ea82a6b41b477f5243546549b792ae0a93abcea608cff66089bf8f8df", size = 942668, upload-time = "2026-08-07T12:48:05.161Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c6/ae/222a91937ebee7f62c0ca8f5ee0afd97577caf24c0abb927d1f5c7e9f6d2/huggingface_hub-1.28.0.tar.gz", hash = "sha256:46a2e950c09234de54093d587d1675382f0d08dbd600d9fb599b5932f5b2c6cb", size = 959609, upload-time = "2026-08-18T12:27:15.101Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/de/d8/95b735e183957c1f26d94c52977f09d466d55119cbbc1558ea4975e4c216/huggingface_hub-1.27.0-py3-none-any.whl", hash = "sha256:7df6827c2f956c60fbaa64646e979e566db76f619dd0a9729dfb8c5a3eb4f68d", size = 784926, upload-time = "2026-08-07T12:48:02.905Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/0e/eafef18f1a75e125e68395db21131db0cf868a128ecd2fce69b4df6c584b/huggingface_hub-1.28.0-py3-none-any.whl", hash = "sha256:58a8bacb03072edfc38067065e9dc24bbb34805410fcd36a1632de0b329660bb", size = 793202, upload-time = "2026-08-18T12:27:12.719Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1925,7 +1922,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "onnxruntime"
|
||||
version = "1.28.0"
|
||||
version = "1.29.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "flatbuffers" },
|
||||
@@ -1935,30 +1932,30 @@ dependencies = [
|
||||
{ name = "protobuf" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/17/4d/5014667e2a3a77d6e1b74cc3d88948d06163b8e0a33a84c85073322b5dec/onnxruntime-1.28.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f5c5daabd28aad610f83fdcf32acec8fb57e6adc6c6a39fe2a3c755db957b410", size = 19130506, upload-time = "2026-07-25T01:22:34.489Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/97/b7ce1bc8bb6048b5fe9129f55d6506dc19499068ef2e0a0af1ae3c8aa4e7/onnxruntime-1.28.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8d66f9ceb29909c70839e4e4fb3435c7b490050d8f162bd5f3aba4ca01ee517f", size = 17039880, upload-time = "2026-07-25T01:21:37.538Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/17/4e5ecd8764f87573c495d834ce79e61ecca47f7a01d1e444a606e570edcb/onnxruntime-1.28.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a166b78ee04f3a37fa1ef82034b6a3ce96d9684e582d4d30b296de83e9998bb5", size = 19193162, upload-time = "2026-07-25T01:21:59.151Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/10/3d946d5d5f2cdcc3c8da36cae63190c516d16349edaffd944bda60ca4c3e/onnxruntime-1.28.0-cp311-cp311-win_amd64.whl", hash = "sha256:0d650aeee29368414367b65529e90afe4bf1bab76254789063b8b2f7ea3013c8", size = 13752539, upload-time = "2026-07-25T01:22:24.524Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/74/1c440be7af1e026280b139caa1be5d11bd4dc368011ddbe8f5362b58e12f/onnxruntime-1.28.0-cp311-cp311-win_arm64.whl", hash = "sha256:0faf85fb447a663c9cdadc39bd6b19bdf7bedded6699e45731b9b36c46fd993d", size = 13449940, upload-time = "2026-07-25T01:22:14.97Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/f8/dcbe7700dca82fa540035abd3c868fe5ad0f86af00b9a3db7c2e27d15c7d/onnxruntime-1.28.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:26ff0fdd06efb6c155bae95387a09db1a2be89c7a03e4d0bffd5a171cc2826da", size = 19141362, upload-time = "2026-07-25T01:22:36.965Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/5b/1d77e62097fdbe07e2dc827f389b1c4c0c275f6fab0369a8f46d2461af27/onnxruntime-1.28.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e81a23df16e7acb9d51b06d30cc098e49315ef9180f97bc2221d167b4b04d9c", size = 17050628, upload-time = "2026-07-25T01:21:40.481Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/df/5486ab03e9be288d5268867054c8b04bebcf95bfd12e801c05cc67703dab/onnxruntime-1.28.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0a83bdb70d143cede762b677789bf2a7acca54b3fb82565601d5c30695aa933c", size = 19214257, upload-time = "2026-07-25T01:22:01.695Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/3b/986ca67c274932ba9ac5332fb10de56f643dfd433c74e33f8ae8f847cf24/onnxruntime-1.28.0-cp312-cp312-win_amd64.whl", hash = "sha256:c35064f9b3c43c81c5d5d282091401d0f1ff22796d93ccade4ea2ece5e137ab8", size = 13755036, upload-time = "2026-07-25T01:22:26.89Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/46/059dba81d46c6ba88e0c2d1c64321ac8098847d678423300a183d42ecbd6/onnxruntime-1.28.0-cp312-cp312-win_arm64.whl", hash = "sha256:e02feeb0165c5f13b4cc954738078d59b90128516ac12b671ee24a530242bf02", size = 13454462, upload-time = "2026-07-25T01:22:17.38Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/12/3807e2b17d9eb71d3cb78ed2ba76869b05c637c9b9d6112e636098b0c97a/onnxruntime-1.28.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:31410f544674f534c2f27348af52ef81682ca9c8719154bf4d48f0ef23823b1e", size = 19141759, upload-time = "2026-07-25T01:21:53.765Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/23/b46045c3bf67a9cf54c12f5df0f018a422c65fbb9d6072b10071bebfaae2/onnxruntime-1.28.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f649dd6f6452d12a8059888aa489fe519e062e18793dac72b9efa0f9fdb64135", size = 17049339, upload-time = "2026-07-25T01:21:43.005Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/b6/8c5396e7894e77c5a7d1e026f3acb9dd39c4b5644e412e37a0055eaa3bc5/onnxruntime-1.28.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54fa221d669282bd8f582708ce4c96010a7e9fb0661f9006b37fe2fedafb73fe", size = 19214329, upload-time = "2026-07-25T01:22:04.133Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/f1/51225c202edba4dfc94e1ea03f3d78f1aaf307da75fd792c0ce1946b2514/onnxruntime-1.28.0-cp313-cp313-win_amd64.whl", hash = "sha256:1a1a19175464665c9b8d50bc916f216cc0b569110045b7bbca8f9f290b186f58", size = 13755033, upload-time = "2026-07-25T01:22:29.302Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/db/f59f715edfdd96a051f32b5ef0e680a20a8755d4ecd75f63090e960e347a/onnxruntime-1.28.0-cp313-cp313-win_arm64.whl", hash = "sha256:cfab507abe09d6ffeb817eee07944d452fdc0b00fdcef34cab4db10a45e378c7", size = 13454175, upload-time = "2026-07-25T01:22:19.912Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/28/810314fa88647af9f4cdaf438a30ad1cfebebb53ded55499232d7a0094e6/onnxruntime-1.28.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac301f53b1930402fc46c368e268acfed02f3207272aaff05070d7e09f96f031", size = 17057307, upload-time = "2026-07-25T01:21:45.492Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/cc/9e9f193cc0f29f263a8f09ec08487aed6c96ee856d5fd77da32a425c1949/onnxruntime-1.28.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7f022a1103cae591c75fc4565589a515f2ddd14a6ac8e8a05812dfeda142e28", size = 19222954, upload-time = "2026-07-25T01:22:06.952Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/eb/952314c451d9463e5c9aed9978eec76cf32930d407d9ab8700dd0f4ea1ea/onnxruntime-1.28.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:8adff67a3f28257b37cfe945a7e952e4122666aa8c91a0380862e9fd4c2ed19f", size = 19143748, upload-time = "2026-07-25T01:21:56.297Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/e9/139180b4dd810329aaa42c238b4e6383c906202d98609ae29d66eb7c32b1/onnxruntime-1.28.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc2565e487b4896fb988d6383577d875d958e071fc5f6c3550bd5d02ae98264b", size = 17051950, upload-time = "2026-07-25T01:21:48.606Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/88/9432428273356ad3c8aa01f52c1b3e7f53c4c0192748f41ad983872b436b/onnxruntime-1.28.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6afdc83f1317c136e92fc29f5ee9f058de59d87c0b22cee3fdbfbaa0ccc2098a", size = 19214924, upload-time = "2026-07-25T01:22:09.727Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/e2/6feb3a43517aaf2b1bf7e46897ba5eb81a29717f7d7901420614d5ee4653/onnxruntime-1.28.0-cp314-cp314-win_amd64.whl", hash = "sha256:f2a3b9e30ce880d4ca54999cb313569e36da4f62eefe25f87be18f43e9a3a4d5", size = 14093738, upload-time = "2026-07-25T01:22:31.629Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/8f/83974a1e201dc2e58e5e7111bcaeb1ca2413e9c41f505d26419ee9e3dddf/onnxruntime-1.28.0-cp314-cp314-win_arm64.whl", hash = "sha256:07fb3cbe990d6bf0ab3c22bfbbfb0e314151266046ea6edb4a07f556b4258c5f", size = 13821117, upload-time = "2026-07-25T01:22:22.387Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/83/00e606bc25c756d76a267370c39b7516ad52f9cf134d7ff2bff8b6108bc4/onnxruntime-1.28.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e562d6e36a749f6764481c0ddb0f2af3d0b5a3c164291361d08803c557f369af", size = 17055518, upload-time = "2026-07-25T01:21:51.08Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/a9/68707e1ce345cbdbcd4df65932ebc82a673e917d63eda0007ebcff948691/onnxruntime-1.28.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f6e92367ddce1e4d33cf295024f40192be6c6171a09208f515ba169ced06c8e", size = 19222976, upload-time = "2026-07-25T01:22:12.474Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/a8/0520890321b8ff40b908cf165a93eb58fbc8f85c14db637277ea866c9544/onnxruntime-1.29.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:07c5907474dec4a2792fd7626b753dc66707808385a6d9eecf993db0066a9d0f", size = 21420890, upload-time = "2026-08-17T22:53:33.429Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/77/8bd3e0008ff8d386305351109a7329ea57e51a3ab57bc92340f29c4a5b5d/onnxruntime-1.29.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:16925ef8497e2c07e4b5ae15b504079b3ab3f65e22c58efd10dde0f3caea969a", size = 20803602, upload-time = "2026-08-17T22:53:36.47Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/91/a66cd77f28379ede419672edda3184f1eb286db215dce1e7b976fae2d63b/onnxruntime-1.29.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:85f8e8406c52658735fe5c7fbfd3ebaa1ed340768324f6252e4274e374580a23", size = 23113193, upload-time = "2026-08-17T22:53:39.732Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/82/2da968405c42340f03de0bcdb63be09ae1004f820b2295590d48951b5cf2/onnxruntime-1.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:0d4f427afac434b0070fe992b540ddf20a7aff2265f760f314d91331935b6b98", size = 13999253, upload-time = "2026-08-17T22:53:43.184Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/7a/70c9c893bf732ee66124c2d8de6a21fc9361ec62cf378f857043efcbf0eb/onnxruntime-1.29.0-cp311-cp311-win_arm64.whl", hash = "sha256:4eae472cf7dc3107dec1bb53cd6d142d1964616d08aae48654cd4254b2363c4b", size = 13741410, upload-time = "2026-08-17T22:53:45.521Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/80/381c1e9efed9cc32d00aa7cab0547dc84116cec906c3ffe3613686d6963a/onnxruntime-1.29.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3a3814c041251d6a77fdf513fb282056538ee826d2f1178a0df3c549d3fff6ba", size = 21430049, upload-time = "2026-08-17T22:53:48.286Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/12/4be0e345d38fe707a701ca07e8f63c05b152a2e6285d1e43a7faf63fedd2/onnxruntime-1.29.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d2fb19e848f7c33ed8d3182b52504aaa11c5e8da438bbb47296f85b133cbcf6b", size = 20816870, upload-time = "2026-08-17T22:53:51.169Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/eb/e6968f5e41aac3125f2ff5708855f09cb0b70d85ed3115b625b0b58305ba/onnxruntime-1.29.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:2b80d8c7ec2cc7438e4da3760b88c24568cba72c9ace96d668800a6c79419acb", size = 23136745, upload-time = "2026-08-17T22:53:53.92Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/80/5b28f1f1111210fc4a336ddbc6950f468ebf9a6a265420568f4f43fa33ce/onnxruntime-1.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:4acf2b4948b7ede87221ca6332344b8facdc8059d6ac751a7d367d04532b02dd", size = 14001407, upload-time = "2026-08-17T22:53:56.486Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/d6/6883f89ea4b044e6e8447ebfaf9bcecdf457b7d80a683635e130b25498e0/onnxruntime-1.29.0-cp312-cp312-win_arm64.whl", hash = "sha256:dc61a79cb39afd66ab3f01fd2c23591a7f01de89c1668e1fb6315067fc279164", size = 13746981, upload-time = "2026-08-17T22:53:58.977Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/f8/d375facf60edaf41f5732f9f689c98a800fcc52df5cf6ddfb406703eb5a1/onnxruntime-1.29.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:be0f8ed688cfb1d4d5765a137193b7bfab0c8ea214eed99260b380bb525a3a7f", size = 21429708, upload-time = "2026-08-17T22:54:01.44Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/17/b9ad04051a8c4f504852ce0e8e10f9a6b2f1a331eedcdcc503df776dd0ea/onnxruntime-1.29.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:d67673c5367727860922c5262d724472f1b5539fb7ccf4c81a638f9b71719803", size = 20816263, upload-time = "2026-08-17T22:54:04.088Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/2c/d8eb945d2a372149df9705a8d5c8d7c6c46c987c5446dbcea9e1ea7f6556/onnxruntime-1.29.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e2128f31f449e922c62dbe5d8b6b7b079f0bcaf2d56a102fa203cb6e5bb5ab19", size = 23136817, upload-time = "2026-08-17T22:54:06.714Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/3b/66b424c63fa92dfaa48d1719efaae66fc8c256b9426a832eda51d8dfe1e9/onnxruntime-1.29.0-cp313-cp313-win_amd64.whl", hash = "sha256:2945e1f82f81f27e88decea88c7861f45baea23818950d467bf3909aa303119e", size = 14001310, upload-time = "2026-08-17T22:54:09.13Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/22/d6a700e3a6322fa3d56fbe7cee9ffc53f35e77ffcd6b7e97f4b7722a27ab/onnxruntime-1.29.0-cp313-cp313-win_arm64.whl", hash = "sha256:4b940b0d777590c7e20bf298f5c16af1ea6ad1b400a1c822a6be192f64f4d954", size = 13747112, upload-time = "2026-08-17T22:54:11.608Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/89/c4af146de3d60a32c89fea48d5d34bfd044faaf8957270043a03bd1b462b/onnxruntime-1.29.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:533f8370ce124304e5cb08ab961836cf755631e3dd77adc5f3bbdab70c2b7d99", size = 20826136, upload-time = "2026-08-17T22:54:14.315Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/f2/e6bbacd11dfe8d070613261a758795ea128b9fc9bea391a2a7da2e4c7a08/onnxruntime-1.29.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:c1ad3f437153fe77f9d01a08fbaac0beb030e09b8a80ace1603bcf69b6c95481", size = 23138951, upload-time = "2026-08-17T22:54:17.154Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/a3/718e1b83096a1bc7b0fc8014c23d4cf795559fe666961cfac4fc038a4871/onnxruntime-1.29.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:e74b278af1d949876f5d91d1268fd6c680e79f2bac194967394eaba9fdf69e7e", size = 21431104, upload-time = "2026-08-17T22:54:20.118Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/17/c75e78ddc1fe69b6ebaef7fe88ac83f29bfe10955e3a0d2436d93473c91c/onnxruntime-1.29.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:939e5d65f332e6d399774b2bd0d3559fd8fa629c1e77833db29d968d2384f23d", size = 20818488, upload-time = "2026-08-17T22:54:23.147Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/54/9f197c578d3d3d7bea16971e233e5483981228eec73748585cf7b5933403/onnxruntime-1.29.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:6c0c37b92f67ed68dd36221ce0403e1d9bd4f7efce724439978a2597848530e5", size = 23136994, upload-time = "2026-08-17T22:54:26.321Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/53/4616a55d2495679cfd0195f968feb3d74fe30e26467d168ee243ac97c089/onnxruntime-1.29.0-cp314-cp314-win_amd64.whl", hash = "sha256:4a3129ae56e70d2618ff773920166916310370a7e3cacb60b9e0e8910092725f", size = 14350643, upload-time = "2026-08-17T22:54:28.794Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/0f/c338cb5500a522c7e671a3bb1276f4562404fbecce8a0e274565aa968484/onnxruntime-1.29.0-cp314-cp314-win_arm64.whl", hash = "sha256:e417ef8628dcce310d2d53023e750ea298ec14d4341ae6dc3a572bfd9bc7fa97", size = 14124294, upload-time = "2026-08-17T22:54:31.015Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/e7/61064289a9a1301b25c1f0f574fe98aba31c2d388db3c1dbec664f78621f/onnxruntime-1.29.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:11264bb58f7b7cf6af835ab10d36838d73680580820fd6f51d90124a1ca8f449", size = 20826174, upload-time = "2026-08-17T22:54:34.283Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/21/d0c04b561b46e9bff89b5f500fb7415b8ca0669f7902204f76ab06bb0c7e/onnxruntime-1.29.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1ea91cef3b971506e51ae9c37c16d027774ec64994a524ec1bdfb027d68a9832", size = 23138547, upload-time = "2026-08-17T22:54:37.491Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3172,7 +3169,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "remove-ai-watermarks"
|
||||
version = "0.31.2"
|
||||
version = "0.33.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "c2pa-python" },
|
||||
@@ -3392,27 +3389,27 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.16.3"
|
||||
version = "0.16.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/61/b3/3213589383f8f1b3938781bd1278713f6d18621a14992b3e81fefb8a5ef9/ruff-0.16.3.tar.gz", hash = "sha256:e76d33a347661a84b5be6d043d0347fdc745dfdcf825a8f4fed64b5e26eebdf2", size = 4891904, upload-time = "2026-08-13T15:17:13.381Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/00/8f/d8074b1f25e003164087a8bfe79a0f1a3945135764dbb6aaab04103dcaf9/ruff-0.16.4.tar.gz", hash = "sha256:13171aa9d9af2240ee3504e639de73122c67e74036de5ba2e1d01422cd17e3dc", size = 4899731, upload-time = "2026-08-20T17:43:59.196Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/96/493770daebd68c0a67f1549fdf519f53be51fc435186c0585bcc272fd76c/ruff-0.16.3-py3-none-linux_armv6l.whl", hash = "sha256:0c5710e247a58a4521e66e124ba9a74655b414f61ba3a2e9e3811e11098f48f7", size = 10902799, upload-time = "2026-08-13T15:16:27.382Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/e6/2becf3942fddc29a29b8df47691d456fb1085391a694f74d84513251418c/ruff-0.16.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fe155130631a2471fd2e14a7a664a4dfbd7194b8229c3d7b2a40b21178639081", size = 11135539, upload-time = "2026-08-13T15:16:30.87Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/1e/4b8b72f0d006dbf19326aa99f9ca0ee2ff374187c4d301cf529a51aa06fe/ruff-0.16.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e2ed719e14aa64d895c2ee922594a90a43c861a93f0575a95ff8c47cdbd13eb9", size = 10475095, upload-time = "2026-08-13T15:16:33.259Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/32/2201fa49ba1f6c101ee321e83f051ac7a4b8d07b0ef6b4d3f2772b302275/ruff-0.16.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e0b1da805eb043654645d74d5de1e5ce2edc686e40790d2b86f56d71cc06a84", size = 10668771, upload-time = "2026-08-13T15:16:35.65Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/66/4afc5c8363bd04d45effce1b7c8713ca037d7a6740b7451a2403a6e3a972/ruff-0.16.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a37bdea0bbe21780f590bf437d6412c8c4e1b6cd010f91a65c2c40c5e5f5f870", size = 10699568, upload-time = "2026-08-13T15:16:38.195Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/fd/c67d246bf36bf1698551c56de39e95cd07f70e64433e0098e6267d77061b/ruff-0.16.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09571e6d1288ed9be475207a3ac04ada404f1cd898104be0f6ab8d7df438575b", size = 11499365, upload-time = "2026-08-13T15:16:40.623Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/0b/00ecbceb99a263af7b12f6f05ac3c92bc47b905e91adc3f207a836e3bc01/ruff-0.16.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c18c5a101eb540010638cc1ff3c84944d3adb3df62b8d98ca8f22ba484d3413", size = 12311728, upload-time = "2026-08-13T15:16:43.564Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/b2/b7b3bb54f4d3f7db504e476ad4ab8de530dceebe2c061384b2757ee419e8/ruff-0.16.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8457c44f15033c85ddbb77b15d451df9e24e4bd03b628396dd3610cedc3b8f82", size = 11699896, upload-time = "2026-08-13T15:16:46.209Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/30/4c468429ac195addc5ee1b717b6ab1b66632786737ca3b2ed3443fb0c26a/ruff-0.16.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:294b95c4ae0cda9388525c2047778aa758d6b8d4bb876fd4e9eaa3ebc92343eb", size = 11058736, upload-time = "2026-08-13T15:16:48.823Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/67/7a113cdaddf24b64d7f75b1242a99d04c82fcef4f6921fdbb832beaffb5f/ruff-0.16.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:3d0c7c40c87c2a820509c31ba007968da6e1306468c067b2d82fbfdbcd0e8474", size = 11586911, upload-time = "2026-08-13T15:16:51.913Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/c1/2e66f24c0f3ead25a5e660111778685e505e5da353c82802bf49f0cbe7b9/ruff-0.16.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9f738c0fdfa8eed0b2ce7fb27ee7258208a92a68d7949e62aa15164bc7b389da", size = 10954265, upload-time = "2026-08-13T15:16:54.763Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/ba/4cee23bf52cba9a058d3726de623624daf50ef9638868edd86f4126157f6/ruff-0.16.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fb785f0be25abe69d320415cd4f833b59e17ba7613d9ba6a958023b6bceb0a50", size = 10709886, upload-time = "2026-08-13T15:16:57.339Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/df/7da7194fa5d9dc0a285f7e6fa5a4722e7c63faac0b45b614ded9314363a1/ruff-0.16.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c5536e3acfbf9563085aa2be7b13c629c3077e902afc5b941ac44024dbb9f506", size = 11210392, upload-time = "2026-08-13T15:17:00.171Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/85/7795f6e817af050e7517bf3e7aa9b061cce70ef33d280aad902c956c1ecf/ruff-0.16.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a2d85c02f9b8e165d85e6779184d38c4132de12603dab59c51c28e22584f9e4d", size = 11626910, upload-time = "2026-08-13T15:17:03.299Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/9b/475b927cf27a5cbbda3c7bafb69ed6ff77e1d7923d5d85f17c2749d7ae32/ruff-0.16.3-py3-none-win32.whl", hash = "sha256:388cdf2166642bd9b13d52b5932d3170f34f8abed7e8d9a855f1d84b83645a0a", size = 10931415, upload-time = "2026-08-13T15:17:05.726Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/99/e2a2bfc4fbf0a1e8a916bc9ebe6fe6c58cc34c28e0ffc6ce281d572d1c2e/ruff-0.16.3-py3-none-win_amd64.whl", hash = "sha256:e80a7d69ca2a6d1c4d352ec91458cdca6e56c83cdbcabd93e4abe1e53591d948", size = 11445993, upload-time = "2026-08-13T15:17:08.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/3e/4132e539aed78c148854d4997a2685b0ed4dc4e87110b59ce528564e184e/ruff-0.16.3-py3-none-win_arm64.whl", hash = "sha256:b8ca152da82c1acc1fa8d5874b15951935f0eef46f10e6954c83859011b6178a", size = 11399302, upload-time = "2026-08-13T15:17:10.908Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/80/779895ef584e089d22f2c6df0d0e99a65ec2df0805f1fffd439415b8c1f0/ruff-0.16.4-py3-none-linux_armv6l.whl", hash = "sha256:df4075f71ddac40b9934af60c3ec8a53047dd5a5fdc43224e6e4e8e9a27cb6f7", size = 10006909, upload-time = "2026-08-20T17:43:16.888Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/e6/f553199b5e8927a05cb5c422d921fd0656b29ab976e91c44802107c6b0da/ruff-0.16.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0c95538517af68004306b0fb3214ff2f2af67a65092aee77cd9eb86db6656604", size = 10240201, upload-time = "2026-08-20T17:43:19.337Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/70/4a6dc4bb34da4dee35e30f09bbd1bfbdd26f33b62fb9b8df31f08a199cd2/ruff-0.16.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:963f83df8e69e575b64d67dd447ebbc917db41a14bf38d4593a4183e7aaa8255", size = 9835122, upload-time = "2026-08-20T17:43:21.708Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/12/c6e22d686372c15bcb7af99831f1a1be96df696491babf4f24e4f942c527/ruff-0.16.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32a5057c7ff3f6e6480a48fccfb3a412a690f48a3d03ac5cf08177d6c2da3ade", size = 9977162, upload-time = "2026-08-20T17:43:24.236Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/49/72b10ec912f5ab5854992eaf7aa7cd36729b6937d9dc4e0fb41b3bf428ec/ruff-0.16.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b3dce8d9b0c57c265b91885a66a567d8ea1372e8eb4e250fa8e5e3f579e99cff", size = 9829789, upload-time = "2026-08-20T17:43:26.966Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/80/0f30e32e7f6ee26edc39075502db9d368d788a44a79b55f763eb4ab03796/ruff-0.16.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7dc651db49283c69f8e72c834eec4fe5573e4c646856aebece0ce385dceb2a80", size = 10527949, upload-time = "2026-08-20T17:43:29.384Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/3d/86e8ad3542169e56cac3859a343afdb9df2ad54d35a59ce1e67baee83421/ruff-0.16.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3817b87dbcabc92f13b05019257c5b89b5b4d51b5fb20f56fb5235ceb723cd07", size = 11333695, upload-time = "2026-08-20T17:43:31.872Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/16/481c29b380c20a0054a8261066665e1b3488e23636c49d0a43e75975b9bb/ruff-0.16.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9fce1499134b2c8c68e5166f95705a5812062bb93aacc5f9873bb1a27084bc7", size = 10727741, upload-time = "2026-08-20T17:43:34.596Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/b6/56bc0b8cf45b54b28b3a5e6381c8945d51b5b18adf659454c32295209a31/ruff-0.16.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2d812e482f5a7e02eee26cd73d2a37ebbdf47d795ea63ba1b89110ae93e9fb3", size = 10286522, upload-time = "2026-08-20T17:43:37.288Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/8b/b345b4fb110f2fbe2bd31eabd271e5e8b3b7e4ee6c0e02f2dc6be78db000/ruff-0.16.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:6baaf984aa7976edf93d3b627fe2d1d22ee94bbca05fa6f90fc76d73924e3454", size = 10584182, upload-time = "2026-08-20T17:43:39.984Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/e5/827b34041c35f58774a9681a4213994c164fc987800f4dddabcf451da0bf/ruff-0.16.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:bdfcf0b28662eb890372d50f92c283bb94e67e7635ed93c7fd533970acff7b2b", size = 10134195, upload-time = "2026-08-20T17:43:42.351Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/10/d0bffcdd6729b87afc82ba0ef377173356a7dc8e972f5179968cf2fdf98c/ruff-0.16.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b66b02cb9b04f537643cadf5768e5f98dc461890d530cb67113d71c8c76e605d", size = 9825821, upload-time = "2026-08-20T17:43:44.532Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/32/0db2a863b796ca62d83e92a07a3ccf00921b14db02059347576a2fda3d4b/ruff-0.16.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8528bf9a4b291a60bf02ea453511e8ce6215bd2b982ee80405b66b008b6c30a0", size = 10267658, upload-time = "2026-08-20T17:43:46.989Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/a0/fbdeb59e48c6261f523e56c8f12e9c08fbe693786595cc7e3959207a9232/ruff-0.16.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:fbd85d2875fdd67e833213a651f613bbf25303abf6aa822a5121f4531195678d", size = 10697071, upload-time = "2026-08-20T17:43:49.891Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/28/0c6dd865859c6d17bc8ccc34cb72b0e02d6c7eb25e8a1e22b5bea681e2c0/ruff-0.16.4-py3-none-win32.whl", hash = "sha256:312769988007aaeb8e189b443ccdd03c0e6374489e053467be6d96518ebff76e", size = 10021687, upload-time = "2026-08-20T17:43:52.281Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/03/e724450f621698117f9aa6dd241c94d0274ae96781378dc86745ae29f0e7/ruff-0.16.4-py3-none-win_amd64.whl", hash = "sha256:05d9d27a18c4bcbefada602480ec9e01e0bc949d432e0ced5df77edac195919c", size = 10567657, upload-time = "2026-08-20T17:43:54.78Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/fe/da8b9e1347696bb22120b77280ec5ce25d500ca5cb39d5ad6e5c18de19c1/ruff-0.16.4-py3-none-win_arm64.whl", hash = "sha256:a3a61621c9b6f6a89573e938a080e648f1695baa3f58570a3a707bc51ff65a21", size = 10451579, upload-time = "2026-08-20T17:43:57.135Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3782,7 +3779,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "transformers"
|
||||
version = "5.15.0"
|
||||
version = "5.15.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "huggingface-hub" },
|
||||
@@ -3796,9 +3793,9 @@ dependencies = [
|
||||
{ name = "tqdm" },
|
||||
{ name = "typer" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a6/3f/d89353267d511e18f137dfd7769d07837350c11b88408ce1dfe2e93e56c7/transformers-5.15.0.tar.gz", hash = "sha256:bbf98f57b2ddd7c4ecbccfa2c0069017aa6fd01cc204bd50cbc0eeadcf2a13b8", size = 9377983, upload-time = "2026-08-10T10:27:23.261Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2a/92/c50c61da7046bbb59a4d011291aeadcfb4d7980ab36fdb31e93823a3fb93/transformers-5.15.1.tar.gz", hash = "sha256:27c996bd9075ddc82d40f8590dfdc81ea45f611bfca477e0db5d7fd257a482f7", size = 9378434, upload-time = "2026-08-19T11:28:20.33Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/43/81355710a4c84e9420e11a86d41a5364deb561f2ef36dfdf254a07371bbb/transformers-5.15.0-py3-none-any.whl", hash = "sha256:d7f007736f67749ae9490c4f8cb5d30b452ae2d68c8675e50ba8d63ea7feb107", size = 11749280, upload-time = "2026-08-10T10:27:20.416Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/c4/a12e1d9b387fb0c40a57116db82b457e8c771cb419163cda29204d74a595/transformers-5.15.1-py3-none-any.whl", hash = "sha256:b7cdf238ff583e3a58dbc7fa34da1aaf091ce063141f65a30538160bd5afe93f", size = 11749582, upload-time = "2026-08-19T11:28:16.726Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in new issue
Block a user