From 015df40f4994e9a3c94ccbe40aa3c9cadf87360a Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Sun, 9 Aug 2026 00:27:10 -0700 Subject: [PATCH 01/44] Release 0.26.2 and document distribution surfaces --- CLAUDE.md | 2 +- docs/index.md | 2 +- docs/release-and-distribution.md | 18 +++++++++++++++++- pyproject.toml | 2 +- src/remove_ai_watermarks/__init__.py | 2 +- tests/test_distribution_boundary.py | 10 ++++++++++ uv.lock | 2 +- 7 files changed, 32 insertions(+), 6 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index e946356..577d9b3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -33,7 +33,7 @@ Optional features and installation groups are documented in [`docs/installation. Command, gate, typing, and model-test invariants auto-load from [`.claude/rules/development.md`](.claude/rules/development.md). Environment recovery, CI behavior, and fixture policy live in [`docs/development.md`](docs/development.md). -Before a release, read [`docs/release-and-distribution.md`](docs/release-and-distribution.md). Keep the source-distribution public allowlist. +Before a release, read [`docs/release-and-distribution.md`](docs/release-and-distribution.md). Treat the release as complete only after PyPI, Homebrew, the Hugging Face Space, and the ComfyUI Registry are verified; conda is not published. Keep the source-distribution public allowlist. ## Module architecture diff --git a/docs/index.md b/docs/index.md index d73188b..5fed2f8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,7 +22,7 @@ to run the tool. Use the maintainer references only when changing the code. | [Development](development.md) | Environment setup, dependency recovery, CI behavior, and fixture policy. | | [Code provenance](code-provenance.md) | Required notices for licensed derivative work. | | [Verification plan](verification-plan.md) | Verification methods, completed measurements, and remaining validation gaps. | -| [Release and distribution](release-and-distribution.md) | PyPI, Homebrew, Hugging Face Space, and release workflow. | +| [Release and distribution](release-and-distribution.md) | PyPI, Homebrew, Hugging Face Space, ComfyUI Registry, and the release workflow. | | [Watermarking landscape](watermarking-landscape.md) | Vendor signals and detection approaches. | | [SynthID technical reference](synthid.md) | Mechanism, detector access, robustness, and implications for this project. | diff --git a/docs/release-and-distribution.md b/docs/release-and-distribution.md index 44e923e..347beb2 100644 --- a/docs/release-and-distribution.md +++ b/docs/release-and-distribution.md @@ -3,6 +3,21 @@ This page describes the release behavior defined in this repository. External registry state can change independently, so verify it during a release. +## Published surfaces + +A release is complete only after all four published surfaces are verified: + +| Surface | Published result | Automation | +|---|---|---| +| PyPI | The `remove-ai-watermarks` wheel and source distribution | `publish.yml` | +| Homebrew | The `remove-ai-watermarks` formula in `wiltodelta/homebrew-tap` | `distribute.yml` | +| Hugging Face Space | A factory rebuild of `wiltodelta/remove-ai-watermarks` against the new PyPI release | `distribute.yml` | +| ComfyUI Registry | A compatible release of `wiltodelta/ComfyUI-remove-ai-watermarks` with its own node version | `distribute.yml` and the node repository's workflows | + +The GitHub Release is the trigger and release record for this flow. Conda is +not a supported publishing surface: this repository has no conda recipe or +conda publication job. + ## Release sources of truth The package version appears in: @@ -43,7 +58,8 @@ PyPI API token from the repository. waits for the matching source distribution to appear on PyPI, then: - updates the Homebrew tap formula URL and SHA-256; -- triggers a factory rebuild of the Hugging Face Space. +- triggers a factory rebuild of the Hugging Face Space; +- synchronizes, tests, versions, and publishes the ComfyUI nodes. The workflow can also be started manually with an optional version input. diff --git a/pyproject.toml b/pyproject.toml index df686a9..71987b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "remove-ai-watermarks" -version = "0.26.1" +version = "0.26.2" description = "AI watermark remover for visible, invisible, and provenance marks in images and video" readme = "README.md" requires-python = ">=3.10.1" diff --git a/src/remove_ai_watermarks/__init__.py b/src/remove_ai_watermarks/__init__.py index a34122d..7b59708 100644 --- a/src/remove_ai_watermarks/__init__.py +++ b/src/remove_ai_watermarks/__init__.py @@ -32,7 +32,7 @@ _os.environ.setdefault("TRANSFORMERS_VERBOSITY", "error") _warnings.filterwarnings("ignore", message=r".*ImageProcessorFast.*") -__version__ = "0.26.1" +__version__ = "0.26.2" __all__ = [ "BatchSummary", diff --git a/tests/test_distribution_boundary.py b/tests/test_distribution_boundary.py index ce6993b..8c4c914 100644 --- a/tests/test_distribution_boundary.py +++ b/tests/test_distribution_boundary.py @@ -20,3 +20,13 @@ def test_sdist_has_explicit_public_boundary() -> None: assert _array_values(sdist_config, "include") == {"/src", "/LICENSE", "/README.md", "/pyproject.toml"} assert {"/data", "/tmp", "/.sc"} <= _array_values(sdist_config, "exclude") + + +def test_release_guide_names_every_published_surface() -> None: + """A release checklist must not silently forget a downstream surface.""" + root = Path(__file__).resolve().parents[1] + guide = (root / "docs" / "release-and-distribution.md").read_text(encoding="utf-8") + + for surface in ("PyPI", "Homebrew", "Hugging Face Space", "ComfyUI Registry"): + assert surface in guide + assert re.search(r"Conda is\s+not a supported publishing surface", guide) diff --git a/uv.lock b/uv.lock index 9cdaff2..8df8c5b 100644 --- a/uv.lock +++ b/uv.lock @@ -3331,7 +3331,7 @@ wheels = [ [[package]] name = "remove-ai-watermarks" -version = "0.26.1" +version = "0.26.2" source = { editable = "." } dependencies = [ { name = "c2pa-python" }, From 14a27d224d9769ad7d8db90a5a89618807f14678 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Sun, 9 Aug 2026 16:51:37 -0700 Subject: [PATCH 02/44] Drop the settings.json that only repeats global defaults All four plugins it enabled are enabled globally, and its allow list says nothing under the global defaultMode: bypassPermissions. Co-Authored-By: Claude Opus 5 --- .claude/settings.json | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 .claude/settings.json diff --git a/.claude/settings.json b/.claude/settings.json deleted file mode 100644 index 1fb8139..0000000 --- a/.claude/settings.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "permissions": { - "allow": [ - "WebSearch", - "WebFetch" - ] - }, - "enabledPlugins": { - "pyright-lsp@claude-plugins-official": true, - "context7@claude-plugins-official": true, - "code-simplifier@claude-plugins-official": true, - "claude-md-management@claude-plugins-official": true - } -} From 370556ffe66709cd6c33c1b2667ae19c32c42f8a Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Sun, 9 Aug 2026 17:04:05 -0700 Subject: [PATCH 03/44] Ignore other harnesses' generated config before it appears These directories are empty today, so git shows nothing and the gap is invisible. The moment the sync writes into one, a routine running git add -A commits another harness's generated config, and the harness audit then fails after the fact rather than before it. Paths are the canonical untrackedConsumerPaths list, anchored to the repo root so a same-named source file deeper in the tree is not caught. Co-Authored-By: Claude Opus 5 --- .gitignore | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.gitignore b/.gitignore index 598c94d..5690373 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,12 @@ gfpgan/ # Local evaluation data .local-eval/ + +# Other harnesses' generated config. Claude's sync owns these; a committed copy +# is a second source nobody reconciles. Anchored so a same-named source file +# deeper in the tree is not caught. +/.agents/ +/.codex/ +/.opencode/ +/opencode.json +/opencode.jsonc From 353bc5f12c13f1e4ee481b21026a76dd27e65821 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Sun, 9 Aug 2026 21:08:09 -0700 Subject: [PATCH 04/44] Harden TrustMark detection with an official fixture --- data/README.md | 5 +- data/fixtures/README.md | 15 ++++++ .../fixtures/provenance/adobe-trustmark-p.png | Bin 0 -> 368519 bytes data/licenses/adobe-trustmark-MIT.txt | 25 +++++++++ docs/code-provenance.md | 7 +++ docs/known-limitations.md | 7 ++- docs/module-internals.md | 8 ++- docs/supported-signals.md | 6 ++- src/remove_ai_watermarks/identify.py | 23 ++++---- .../trustmark_detector.py | 33 +++++++----- tests/test_trustmark_detector.py | 51 ++++++++++++++---- 11 files changed, 143 insertions(+), 37 deletions(-) create mode 100644 data/fixtures/README.md create mode 100644 data/fixtures/provenance/adobe-trustmark-p.png create mode 100644 data/licenses/adobe-trustmark-MIT.txt diff --git a/data/README.md b/data/README.md index 4a4667a..a76aadd 100644 --- a/data/README.md +++ b/data/README.md @@ -6,6 +6,7 @@ Tracked data is organized by purpose: data/ fixtures/ provenance/ Real format and provenance fixtures used by tests + (source records live in fixtures/README.md) calibration/ / Minimal controlled inputs needed to rebuild detector assets synthid/ @@ -31,7 +32,9 @@ data/ commands, hashes, and oracle verdicts instead of committing another corpus copy. A small curated before-and-after example may live in `docs/images/` when it is part of the public documentation. -6. Runtime detector assets belong in `src/remove_ai_watermarks/assets/`. +6. Keep third-party fixture license notices in `licenses/`, outside directories + that tests enumerate as media inputs. +7. Runtime detector assets belong in `src/remove_ai_watermarks/assets/`. Unregistered research candidates belong in `scripts/assets/visible-mark-candidates/` so they are not shipped in the wheel. diff --git a/data/fixtures/README.md b/data/fixtures/README.md new file mode 100644 index 0000000..c71559f --- /dev/null +++ b/data/fixtures/README.md @@ -0,0 +1,15 @@ +# Provenance fixtures + +These files exercise real container, metadata, and pixel-provenance formats. +They are test inputs, not evaluation corpora. + +## Adobe TrustMark + +`adobe-trustmark-p.png` is Adobe's official `images/ghost_P.png` Variant P +example from [`adobe/trustmark`](https://github.com/adobe/trustmark) at commit +`0ed40cbe8188f664fd9cbbeacd969807de27440a`. + +- Source: `https://raw.githubusercontent.com/adobe/trustmark/0ed40cbe8188f664fd9cbbeacd969807de27440a/images/ghost_P.png` +- SHA-256: `e58c5825ed7e5d9fb04710ea541b61bd55879cad65554c0d46260aa24b3d0755` +- Expected signal: TrustMark Variant P, schema 1 +- License: MIT, reproduced in `../licenses/adobe-trustmark-MIT.txt` diff --git a/data/fixtures/provenance/adobe-trustmark-p.png b/data/fixtures/provenance/adobe-trustmark-p.png new file mode 100644 index 0000000000000000000000000000000000000000..8aabf5fd1f318ef49dd67b1f5ef83fb11634702f GIT binary patch literal 368519 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

~9Z9cl+W`qYzn^6_W! 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

VQt|;peVEooptU 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 bQUL#dTLHb@AfuhL00000NkvXXu0mjf>1HT| literal 0 HcmV?d00001 diff --git a/data/licenses/adobe-trustmark-MIT.txt b/data/licenses/adobe-trustmark-MIT.txt new file mode 100644 index 0000000..aa449b5 --- /dev/null +++ b/data/licenses/adobe-trustmark-MIT.txt @@ -0,0 +1,25 @@ +Copyright 2023 Adobe +All Rights Reserved. + +NOTICE: Adobe permits you to use, modify, and distribute this file in +accordance with the terms of the license agreement accompanying it. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/code-provenance.md b/docs/code-provenance.md index 0d7e98a..d26ba2c 100644 --- a/docs/code-provenance.md +++ b/docs/code-provenance.md @@ -7,3 +7,10 @@ This page records notices required by source dependencies and licensed derivativ - The DWT-DCT implementation derives from ShieldMnt's [`invisible-watermark`](https://github.com/ShieldMnt/invisible-watermark), licensed under MIT. Its notice ships in `src/remove_ai_watermarks/licenses/invisible-watermark-MIT.txt`. + +## Licensed test fixtures + +- `data/fixtures/provenance/adobe-trustmark-p.png` is Adobe's official + TrustMark Variant P example, licensed under MIT. Its source commit, digest, + and reproduced license are recorded beside the fixture in + `data/fixtures/README.md` and `data/licenses/adobe-trustmark-MIT.txt`. diff --git a/docs/known-limitations.md b/docs/known-limitations.md index 4d48ace..2596ea9 100644 --- a/docs/known-limitations.md +++ b/docs/known-limitations.md @@ -344,8 +344,11 @@ Diffusion, SDXL, and FLUX workflows. That decoder is sensitive to the carrier and transformations. A negative result is not a universal negative. The `trustmark` extra adds Adobe TrustMark decoding. The implementation retains -an additional JPEG re-encode gate because isolated decoder hits can otherwise -be content noise. +an additional JPEG re-encode gate and requires the binary payload and schema to +remain identical because isolated decoder hits can otherwise be content noise. +It accepts Variant P schemas 0-2. Variant Q requires a different model, and +schema 3 is rejected at the measured precision threshold. The calibration +history is in [module internals](module-internals.md#metadata-and-provenance). External AI versus real image classifiers are out of scope. The project identifies concrete local provenance signals instead of shipping a generic diff --git a/docs/module-internals.md b/docs/module-internals.md index 4ceb242..69c7784 100644 --- a/docs/module-internals.md +++ b/docs/module-internals.md @@ -564,7 +564,13 @@ human-made verdict. `ai_source_kind` distinguishes fully generated content from AI-enhanced composites when the source metadata provides that distinction. TrustMark is reported as a watermark signal but does not by itself assert AI -origin because it can also protect human-authored content. +origin because it can also protect human-authored content. The decoder requests +binary mode, matching Adobe's Durable Content Credentials example, then requires +the same payload and schema after a quality-95 JPEG round-trip. Only Variant P +schemas 0-2 count as positives. Schema 3 is below the precision threshold: all +38 measured historical false-positive candidates used it, and six retained the +same false payload after re-encoding. The official Adobe Variant P schema-1 +fixture in `data/fixtures/provenance/` is the positive regression control. Regression coverage: diff --git a/docs/supported-signals.md b/docs/supported-signals.md index 752fb40..dd0a85a 100644 --- a/docs/supported-signals.md +++ b/docs/supported-signals.md @@ -80,7 +80,9 @@ The inspection and stripping code handles signals in these groups: - Samsung AI editing markers; - Hugging Face job metadata; - open Stable Diffusion style DWT-DCT watermarks with the `detect` extra; -- Adobe TrustMark with the `trustmark` extra. +- Adobe TrustMark Variant P schemas 0-2 with the `trustmark` extra. Variant Q + needs a different model, while schema 3 is deliberately rejected because it + produced persistent false positives on unrelated generators. `identify` combines detected signals into a `ProvenanceReport`. It reports unknown when evidence is absent. It never treats missing metadata as proof that @@ -154,7 +156,7 @@ not a universal clean verdict. | OpenAI image generators | None registered | Diffusion regeneration for supported invisible signals | C2PA and generator provenance | | Stable Diffusion and SDXL | None registered | Diffusion regeneration; optional open decoder | Embedded parameters and text metadata | | FLUX | None registered | Diffusion regeneration; optional open decoder | C2PA for supported sources | -| Adobe Firefly | None registered | No proprietary local decoder | C2PA; optional TrustMark decoder | +| Adobe Firefly | None registered | Optional TrustMark Variant P decoder | C2PA | | Midjourney | None registered | No registered pixel decoder | EXIF, XMP, and IPTC signals | | ByteDance generators | Doubao and Jimeng marks | No registered pixel decoder | TC260 AIGC, supported C2PA, and exact app-export AIGC disclosures | | Qwen | Qwen mark | No registered pixel decoder | TC260 AIGC | diff --git a/src/remove_ai_watermarks/identify.py b/src/remove_ai_watermarks/identify.py index d765028..d59ced1 100644 --- a/src/remove_ai_watermarks/identify.py +++ b/src/remove_ai_watermarks/identify.py @@ -450,10 +450,11 @@ class ProvenanceReport: ai_source_kind: str | None = None # True when the AI verdict rests on a metadata or embedded-invisible signal # (C2PA AI issuer / SynthID provenance, IPTC, AIGC, local gen params, EXIF/xAI, or - # an open DWT-DCT / TrustMark decode) -- as opposed to a visible mark or a - # weak medium-confidence hint (hf-job, Samsung genAIType). It is exactly the - # set of signals an invisible/diffusion scrub targets: a visible-only or - # no-signal image has it False. Equivalent to ``confidence == "high"``; + # an open DWT-DCT decode) -- as opposed to a visible mark, provenance-only + # TrustMark, or a weak medium-confidence hint (hf-job, Samsung genAIType). This + # is exactly the set of signals an invisible/diffusion scrub targets: a + # visible-only or no-signal image has it False. Equivalent to + # ``confidence == "high"``; # surfaced as a field so callers gate on intent, not on the string. ai_from_metadata: bool = False watermarks: list[str] = field(default_factory=list[str]) @@ -1431,13 +1432,15 @@ def has_invisible_target(image_path: Path) -> bool: """True when a locally-detectable invisible/metadata AI signal is present. The decision gate for the diffusion scrub (``invisible`` / ``all`` / ``batch``): - regenerating pixels removes an invisible watermark (SynthID, open DWT-DCT, - TrustMark) but degrades a real photo, so it must not run when there is nothing - to remove. Runs :func:`identify` with ``check_visible=False`` -- a visible mark - is handled by the separate visible pass and is NOT a diffusion target -- and - ``check_invisible=True`` so an open watermark counts. Returns + regenerating pixels removes an AI-specific invisible watermark (SynthID, + open DWT-DCT) but degrades a real photo, so it must not run when there is + nothing to remove. Runs :func:`identify` with ``check_visible=False`` -- a + visible mark is handled by the separate visible pass and is NOT a diffusion + target -- and ``check_invisible=True`` so an open watermark counts. Returns ``report.ai_from_metadata`` (C2PA AI issuer / SynthID provenance, IPTC, AIGC, local - gen params, EXIF/xAI, open DWT-DCT / TrustMark). + gen params, EXIF/xAI, or open DWT-DCT). TrustMark alone does not trigger the + scrub because it also protects human-authored work and therefore is not an AI + signal by itself. IMPORTANT -- this cannot prove a pixel SynthID is absent: SynthID is detectable only through its C2PA proxy, so a metadata-stripped AI image reads as no signal diff --git a/src/remove_ai_watermarks/trustmark_detector.py b/src/remove_ai_watermarks/trustmark_detector.py index a846430..375c11d 100644 --- a/src/remove_ai_watermarks/trustmark_detector.py +++ b/src/remove_ai_watermarks/trustmark_detector.py @@ -32,6 +32,9 @@ logger = logging.getLogger(__name__) # Adobe ships Variant P in production (com.adobe.trustmark.P). _MODEL_TYPE = "P" +# Schema 3 (BCH_3) is below the detector's measured precision threshold; the +# calibration history is canonical in docs/module-internals.md. +_SUPPORTED_SCHEMAS = frozenset({0, 1, 2}) # Lazily constructed singleton -- model load + first-use download is expensive. # Guarded by a lock so concurrent callers don't double-construct/double-download. _tm: Any = None @@ -57,8 +60,9 @@ def _decoder() -> Any: # JPEG quality for the false-positive durability gate (see detect_trustmark). -# Deliberately mild: a genuine TrustMark survives far harsher, while every -# observed false positive collapsed even at this quality. +# Deliberately mild: a genuine TrustMark survives far harsher. The round-trip +# still needs payload and schema validation because content-correlated false +# positives can survive this compression level. _REENCODE_QUALITY = 95 @@ -79,8 +83,8 @@ def detect_trustmark(image_path: Path) -> str | None: cannot carry Adobe's watermark, and decoded a random-bytes secret). A genuine TrustMark is a *durable* soft binding engineered to survive re-encoding (that is its entire purpose once C2PA is stripped), so we re-decode after a mild - JPEG round-trip and require the same schema both times. Every observed false - positive collapsed under this gate. + JPEG round-trip and require the same binary payload and schema both times. + Only the calibrated schemas 0-2 count as high-precision positives. """ if not is_available(): return None @@ -90,10 +94,17 @@ def detect_trustmark(image_path: Path) -> str | None: with Image.open(image_path) as img: cover = img.convert("RGB") decoder = _decoder() - _wm_secret, wm_present, wm_schema = decoder.decode(cover) + wm_secret, wm_present, wm_schema = decoder.decode(cover, "binary") if not wm_present: return None - if not _survives_reencode(decoder, cover, wm_schema): + if wm_schema not in _SUPPORTED_SCHEMAS: + logger.debug( + "TrustMark decode for %s used weak schema %s; treating as false positive", + image_path, + wm_schema, + ) + return None + if not _survives_reencode(decoder, cover, wm_secret, wm_schema): logger.debug("TrustMark decode for %s did not survive re-encode; treating as false positive", image_path) return None except Exception as exc: # model download / decode failure / unreadable image @@ -102,10 +113,8 @@ def detect_trustmark(image_path: Path) -> str | None: return f"Adobe TrustMark (variant {_MODEL_TYPE}, schema {wm_schema})" -def _survives_reencode(decoder: Any, cover: Any, schema: int) -> bool: - """True if the watermark re-decodes with the same schema after a mild JPEG - round-trip -- the durability a genuine TrustMark guarantees, which a BCH - false positive (content noise) does not.""" +def _survives_reencode(decoder: Any, cover: Any, payload: str, schema: int) -> bool: + """True if the same watermark re-decodes after a mild JPEG round-trip.""" import io from PIL import Image @@ -114,5 +123,5 @@ def _survives_reencode(decoder: Any, cover: Any, schema: int) -> bool: cover.save(buffer, "JPEG", quality=_REENCODE_QUALITY) buffer.seek(0) with Image.open(buffer) as reencoded: - _secret, present, reencoded_schema = decoder.decode(reencoded.convert("RGB")) - return bool(present) and reencoded_schema == schema + reencoded_payload, present, reencoded_schema = decoder.decode(reencoded.convert("RGB"), "binary") + return bool(present) and reencoded_schema == schema and reencoded_payload == payload diff --git a/tests/test_trustmark_detector.py b/tests/test_trustmark_detector.py index f2fec3d..9c6a106 100644 --- a/tests/test_trustmark_detector.py +++ b/tests/test_trustmark_detector.py @@ -8,15 +8,19 @@ absent/error behaviour: detect must return None, never raise. from __future__ import annotations -from typing import TYPE_CHECKING +import hashlib +from pathlib import Path import pytest from remove_ai_watermarks import trustmark_detector +from remove_ai_watermarks.identify import identify from remove_ai_watermarks.trustmark_detector import detect_trustmark, is_available -if TYPE_CHECKING: - from pathlib import Path +_OFFICIAL_FIXTURE = ( + Path(__file__).resolve().parent.parent / "data" / "fixtures" / "provenance" / "adobe-trustmark-p.png" +) +_OFFICIAL_FIXTURE_SHA256 = "e58c5825ed7e5d9fb04710ea541b61bd55879cad65554c0d46260aa24b3d0755" class _FakeDecoder: @@ -24,11 +28,12 @@ class _FakeDecoder: ``(secret, present, schema)`` tuples -- the first for the original image, the second for the re-encoded copy used by the false-positive durability gate.""" - def __init__(self, *results: tuple[bytes, bool, int]): + def __init__(self, *results: tuple[str, bool, int]): self._results = list(results) self.calls = 0 - def decode(self, _img: object) -> tuple[bytes, bool, int]: + def decode(self, _img: object, mode: str = "binary") -> tuple[str, bool, int]: + assert mode == "binary" result = self._results[min(self.calls, len(self._results) - 1)] self.calls += 1 return result @@ -64,7 +69,7 @@ class TestFalsePositiveGate: monkeypatch.setattr(trustmark_detector, "_decoder", lambda: decoder) def test_durable_watermark_survives_and_is_reported(self, monkeypatch, tmp_clean_png: Path): - decoder = _FakeDecoder((b"secret", True, 2), (b"secret", True, 2)) + decoder = _FakeDecoder(("secret", True, 2), ("secret", True, 2)) self._patch_decoder(monkeypatch, decoder) result = detect_trustmark(tmp_clean_png) assert result == "Adobe TrustMark (variant P, schema 2)" @@ -72,18 +77,46 @@ class TestFalsePositiveGate: def test_false_positive_collapsing_on_reencode_is_dropped(self, monkeypatch, tmp_clean_png: Path): # Present on the original, absent after re-encode -> content-noise FP. - decoder = _FakeDecoder((b"\x00\x01", True, 3), (b"", False, -1)) + decoder = _FakeDecoder(("01", True, 2), ("", False, -1)) self._patch_decoder(monkeypatch, decoder) assert detect_trustmark(tmp_clean_png) is None def test_schema_drift_on_reencode_is_dropped(self, monkeypatch, tmp_clean_png: Path): # Present both times but the schema changes -> not a stable watermark. - decoder = _FakeDecoder((b"\x00", True, 2), (b"\x00", True, 3)) + decoder = _FakeDecoder(("01", True, 2), ("01", True, 3)) self._patch_decoder(monkeypatch, decoder) assert detect_trustmark(tmp_clean_png) is None + def test_payload_drift_on_reencode_is_dropped(self, monkeypatch, tmp_clean_png: Path): + decoder = _FakeDecoder(("first", True, 2), ("second", True, 2)) + self._patch_decoder(monkeypatch, decoder) + assert detect_trustmark(tmp_clean_png) is None + + def test_weak_schema_three_is_dropped(self, monkeypatch, tmp_clean_png: Path): + decoder = _FakeDecoder(("secret", True, 3)) + self._patch_decoder(monkeypatch, decoder) + assert detect_trustmark(tmp_clean_png) is None + assert decoder.calls == 1 + def test_absent_skips_reencode(self, monkeypatch, tmp_clean_png: Path): - decoder = _FakeDecoder((b"", False, -1)) + decoder = _FakeDecoder(("", False, -1)) self._patch_decoder(monkeypatch, decoder) assert detect_trustmark(tmp_clean_png) is None assert decoder.calls == 1 # no second decode when the first is absent + + +@pytest.mark.skipif(not is_available(), reason="trustmark not installed") +def test_official_adobe_variant_p_fixture(): + assert detect_trustmark(_OFFICIAL_FIXTURE) == "Adobe TrustMark (variant P, schema 1)" + + +@pytest.mark.skipif(not is_available(), reason="trustmark not installed") +def test_official_adobe_variant_p_fixture_is_provenance_not_ai(): + report = identify(_OFFICIAL_FIXTURE, check_visible=False) + assert report.is_ai_generated is None + assert report.ai_from_metadata is False + assert any(signal.name == "trustmark" for signal in report.signals) + + +def test_official_adobe_variant_p_fixture_digest(): + assert hashlib.sha256(_OFFICIAL_FIXTURE.read_bytes()).hexdigest() == _OFFICIAL_FIXTURE_SHA256 From b1319b1d5a4f976acbc491ad1379a24be13be670 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Sun, 9 Aug 2026 21:15:15 -0700 Subject: [PATCH 05/44] Release 0.26.3 --- pyproject.toml | 2 +- src/remove_ai_watermarks/__init__.py | 2 +- uv.lock | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 71987b6..7eb7abf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "remove-ai-watermarks" -version = "0.26.2" +version = "0.26.3" description = "AI watermark remover for visible, invisible, and provenance marks in images and video" readme = "README.md" requires-python = ">=3.10.1" diff --git a/src/remove_ai_watermarks/__init__.py b/src/remove_ai_watermarks/__init__.py index 7b59708..857937a 100644 --- a/src/remove_ai_watermarks/__init__.py +++ b/src/remove_ai_watermarks/__init__.py @@ -32,7 +32,7 @@ _os.environ.setdefault("TRANSFORMERS_VERBOSITY", "error") _warnings.filterwarnings("ignore", message=r".*ImageProcessorFast.*") -__version__ = "0.26.2" +__version__ = "0.26.3" __all__ = [ "BatchSummary", diff --git a/uv.lock b/uv.lock index 8df8c5b..140c995 100644 --- a/uv.lock +++ b/uv.lock @@ -3331,7 +3331,7 @@ wheels = [ [[package]] name = "remove-ai-watermarks" -version = "0.26.2" +version = "0.26.3" source = { editable = "." } dependencies = [ { name = "c2pa-python" }, From b08899dc00ce53be02de8932aa959778337e200d Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Mon, 10 Aug 2026 13:52:48 -0700 Subject: [PATCH 06/44] Note that the package name is the published distribution Renaming it strands every pip install and every pinned dependency on it, including the ComfyUI node package, and the console script has the same property since users have it on PATH. Recorded at the name itself, where whoever considers changing it will be looking. Co-Authored-By: Claude Opus 5 --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 7eb7abf..74942ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,4 +1,8 @@ [project] +# This name is the published PyPI distribution, not a local label: changing it +# strands every `pip install remove-ai-watermarks` and every pinned dependency +# 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.26.3" description = "AI watermark remover for visible, invisible, and provenance marks in images and video" From 58146dbc6d6e1c8dda88cf2ad34da800a951ddf3 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Wed, 12 Aug 2026 14:24:56 -0700 Subject: [PATCH 07/44] Say what the hosted service is instead of offering it as a fallback Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 03030ac..09d489d 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,9 @@ cleaning, directory batches, visible Sora, Veo, Seedance, Dola, Hailuo, and Kling mark removal, and oracle-certified VAE regeneration for video SynthID removal. -> Try it online at [raiw.cc](https://raiw.cc) if you do not want to install Python -> or run diffusion models locally. +> [raiw.cc](https://raiw.cc) runs this library as a hosted service, with the GPU +> included and nothing to install. Visible mark and metadata removal are free +> there; invisible watermark removal is paid. [![PyPI](https://img.shields.io/pypi/v/remove-ai-watermarks?logo=pypi&logoColor=white)](https://pypi.org/project/remove-ai-watermarks/) [![Python](https://img.shields.io/pypi/pyversions/remove-ai-watermarks?logo=python&logoColor=white)](https://pypi.org/project/remove-ai-watermarks/) From 25eaa7458c5f7574b41430c546f519e835c06a25 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Wed, 12 Aug 2026 15:52:11 -0700 Subject: [PATCH 08/44] Support Python 3.11 through 3.14 --- .github/workflows/test.yml | 27 +- README.md | 4 +- docs/cli.md | 6 +- docs/development.md | 7 +- docs/installation.md | 14 +- docs/known-limitations.md | 6 +- pyproject.toml | 34 +- scripts/fidelity_metrics.py | 2 +- scripts/qwen_scrub_prototype.py | 2 +- scripts/synthid_corpus.py | 4 +- tests/test_packaging.py | 17 +- uv.lock | 1410 +++++-------------------------- 12 files changed, 293 insertions(+), 1240 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b51c172..3a692dd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,13 +28,26 @@ jobs: strategy: fail-fast: false matrix: - # Cross-OS x Python-floor/recent. The default-install surface only - # needs the core deps plus dev tooling (no GPU extra): the model-running - # paths are availability-gated and skip when torch/diffusers are absent, - # so this matrix exercises metadata, identify, visible, and the cv2 - # eraser on every OS without pulling the heavy ML stack. - os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.10", "3.12"] + # Exercise every supported minor. The floor and latest minor run on all + # operating systems; intermediate minors run on Linux to bound the + # matrix without leaving a declared Python version untested. + include: + - os: ubuntu-latest + python-version: "3.11" + - os: macos-latest + python-version: "3.11" + - os: windows-latest + python-version: "3.11" + - os: ubuntu-latest + python-version: "3.12" + - os: ubuntu-latest + python-version: "3.13" + - os: ubuntu-latest + python-version: "3.14" + - os: macos-latest + python-version: "3.14" + - os: windows-latest + python-version: "3.14" steps: - uses: actions/checkout@v7 - uses: astral-sh/setup-uv@v7 diff --git a/README.md b/README.md index 09d489d..477e79f 100644 --- a/README.md +++ b/README.md @@ -54,12 +54,12 @@ removal. | Video SynthID removal | `remove-ai-watermarks[video,diffusion]` | | Torch-free DWT-DCT detection | `remove-ai-watermarks[detect]` | | Invisible image removal (needs CUDA) | `remove-ai-watermarks[qwen-zimage]` | -| Every production feature | `remove-ai-watermarks[all]` | +| Every production feature available on the active Python | `remove-ai-watermarks[all]` | Lower-level and specialized extras include `pixels`, `heif`, `trustmark`, `migan`, `lama`, and `diffusion`. The [installation guide](docs/installation.md#feature-extras) documents their exact -dependency composition and model requirements. +dependency composition, Python compatibility, and model requirements. ## Quick start diff --git a/docs/cli.md b/docs/cli.md index 270ba39..1d3ad1e 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -16,7 +16,7 @@ defaults. This page focuses on choosing the right command. | `metadata` and metadata-only `identify` | Default package | | Visible signals in `identify` | `remove-ai-watermarks[visible]` (`pixels` is the minimal runtime) | | Open DWT-DCT signals in `identify` | `remove-ai-watermarks[detect]` | -| Adobe TrustMark signals in `identify` | `remove-ai-watermarks[trustmark]` | +| Adobe TrustMark signals in `identify` on Python 3.11-3.12 | `remove-ai-watermarks[trustmark]` | | `visible` and `erase` with OpenCV | `remove-ai-watermarks[visible]` (`pixels` is the minimal runtime) | | `visible` or `erase` with MI-GAN | `remove-ai-watermarks[migan]` | | `visible` or `erase` with big-LaMa | `remove-ai-watermarks[lama]` | @@ -26,7 +26,7 @@ defaults. This page focuses on choosing the right command. | `video visible`, `video all`, and visible/all batch modes | `remove-ai-watermarks[video]` plus ffmpeg on PATH | | `video invisible` and `video all --invisible` | `remove-ai-watermarks[video,diffusion]` plus ffmpeg on PATH | | HEIC/HEIF/AVIF pixel input | Add `remove-ai-watermarks[heif]` | -| Every production command and backend | `remove-ai-watermarks[all]` | +| Every production command and Python-compatible backend | `remove-ai-watermarks[all]` | `batch` requires the same extra as its selected mode. Extras can be combined in one installation, for example `remove-ai-watermarks[visible,detect,heif]`. @@ -426,7 +426,7 @@ It is a memory strategy, not a guarantee of better quality. The `all` command and the `all` installation extra are separate concepts. The command runs every applicable stage. Installing `remove-ai-watermarks[all]` -makes every production backend available; a smaller installation such as +makes every backend compatible with the active Python available; a smaller installation such as `remove-ai-watermarks[visible,qwen-zimage]` can also run the command with fewer optional backends. diff --git a/docs/development.md b/docs/development.md index c3450be..24cff4e 100644 --- a/docs/development.md +++ b/docs/development.md @@ -14,7 +14,12 @@ The optional TrustMark decoder downloads weights into its installed package dire ## CI -`.github/workflows/test.yml` runs Ruff, a cross-platform test matrix over the supported-Python floor and the latest supported minor with default plus development dependencies, and a separate job that installs ffmpeg on Ubuntu to run the full-clip video test. Diffusion and model-running tests skip in that matrix; metadata, identification, visible removal, the DWT-DCT decoder, and the OpenCV eraser remain covered across operating systems. +`.github/workflows/test.yml` runs Ruff, a test matrix over every supported Python +minor with default plus development dependencies, cross-platform coverage at the +oldest and newest minors, and a separate job that installs ffmpeg on Ubuntu to +run the full-clip video test. Diffusion and model-running tests skip in that +matrix; metadata, identification, visible removal, the DWT-DCT decoder, and the +OpenCV eraser remain covered across operating systems. Keep `uv.lock` compatible with `uv sync --frozen`. Dependency pull-request checks use GitHub's merge result against current `main`; if `main` moves, merge it locally and rerun the full gate because a newer linter can expose stale directives in later code. diff --git a/docs/installation.md b/docs/installation.md index f709c3d..dcca6ef 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -1,6 +1,6 @@ # Installation -Python 3.10.1 or newer is required. +Python 3.11 through 3.14 are supported. ## Default metadata mode @@ -99,12 +99,12 @@ application actually uses: | `visible` | Visible mark detection, OpenCV inpainting, and manual erasing | `pixels` | No | | `video` | Visible video identification/removal and timestamp preservation | `visible`, PyAV | No | | `detect` | Open DWT-DCT detection for Stable Diffusion, SDXL, and FLUX | `pixels`, PyWavelets | No | -| `trustmark` | Adobe TrustMark detection | trustmark | Yes | +| `trustmark` | Adobe TrustMark detection on Python 3.11-3.12 | trustmark | Yes | | `diffusion` | Torch and Diffusers runtime; video SynthID regeneration | `pixels`, Torch, Diffusers | Yes | | `migan` | MI-GAN ONNX fill backend | `visible`, ONNX Runtime | Model download, no Torch | | `lama` | big-LaMa ONNX fill backend | `visible`, ONNX Runtime | Model download, no Torch | | `qwen-zimage` | Invisible image-watermark removal, both CUDA-only profiles | `diffusion`, DiffSynth | Yes | -| `all` | Every production feature | All rows above | Yes | +| `all` | Every production feature available on the active Python | All compatible rows above | Yes | | `dev` | Tests, linting, typing, and upstream parity checks | `video`, `detect`, upstream invisible-watermark | Yes, for parity tests | Dependency composition: @@ -123,8 +123,10 @@ flowchart LR ``` `heif` and `trustmark` are independent branches. Combine them explicitly with -another feature when required. The `all` bundle contains every production -branch but never includes `dev`. +another feature when required. TrustMark requires NumPy 1.x, which has no +CPython 3.13 or 3.14 wheels, so that branch is available only on Python +3.11-3.12. The `all` bundle contains every production branch compatible with +the active Python and never includes `dev`. Examples: @@ -141,7 +143,7 @@ uv tool install --force "remove-ai-watermarks[video]" # DWT-DCT and TrustMark detection without diffusion removal uv tool install --force "remove-ai-watermarks[detect,trustmark]" -# Every production capability +# Every production capability compatible with this Python uv tool install --force "remove-ai-watermarks[all]" # An arbitrary minimal combination diff --git a/docs/known-limitations.md b/docs/known-limitations.md index 2596ea9..8b7a73a 100644 --- a/docs/known-limitations.md +++ b/docs/known-limitations.md @@ -347,8 +347,10 @@ The `trustmark` extra adds Adobe TrustMark decoding. The implementation retains an additional JPEG re-encode gate and requires the binary payload and schema to remain identical because isolated decoder hits can otherwise be content noise. It accepts Variant P schemas 0-2. Variant Q requires a different model, and -schema 3 is rejected at the measured precision threshold. The calibration -history is in [module internals](module-internals.md#metadata-and-provenance). +schema 3 is rejected at the measured precision threshold. Its NumPy 1.x runtime +limits the extra to Python 3.11-3.12; the rest of the package remains supported +through Python 3.14. The calibration history is in +[module internals](module-internals.md#metadata-and-provenance). External AI versus real image classifiers are out of scope. The project identifies concrete local provenance signals instead of shipping a generic diff --git a/pyproject.toml b/pyproject.toml index 74942ac..cf59807 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "remove-ai-watermarks" version = "0.26.3" description = "AI watermark remover for visible, invisible, and provenance marks in images and video" readme = "README.md" -requires-python = ">=3.10.1" +requires-python = ">=3.11,<3.15" license = {text = "Apache-2.0"} keywords = [ "ai-watermark", @@ -37,7 +37,6 @@ classifiers = [ "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", @@ -65,7 +64,10 @@ dependencies = [ [project.optional-dependencies] pixels = [ - "numpy>=1.24.0", + "numpy>=1.24.0; python_version < '3.13'", + # NumPy 1.x has no CPython 3.13+ wheels. Requiring 2.x prevents installers + # from attempting an unsupported source build on the newer Python lines. + "numpy>=2; python_version >= '3.13'", "opencv-python-headless>=4.8.0", ] # Optional HEIC/AVIF pixel decode. Metadata scanning handles these containers @@ -75,12 +77,10 @@ heif = [ ] visible = ["remove-ai-watermarks[pixels]"] # Video visible removal uses the shared pixel runtime. PyAV packetizes processed -# VFR frames with explicit PTS before system ffmpeg encodes them. PyAV 18 requires -# Python 3.11; the 16.x wheel line still covers Python 3.10. +# VFR frames with explicit PTS before system ffmpeg encodes them. video = [ "remove-ai-watermarks[visible]", - "av>=16,<17; python_version < '3.11'", - "av>=18,<19; python_version >= '3.11'", + "av>=18,<19", ] # Open DWT-DCT watermarks used by Stable Diffusion / SDXL / FLUX. The in-tree # decoder avoids the upstream invisible-watermark package's mandatory torch and @@ -125,7 +125,9 @@ qwen-zimage = [ # because it pulls torch and downloads model weights on first use. identify() # guards the import and skips the TrustMark signal when absent. trustmark = [ - "trustmark>=0.8.0", + # TrustMark requires NumPy <2, whose last release has no CPython 3.13+ + # wheels. Keep this feature installable only on its real runtime range. + "trustmark>=0.8.0; python_version < '3.13'", ] # Universal region eraser backend -- big-LaMa via onnxruntime (Carve/LaMa-ONNX, # Apache-2.0). CPU, no torch. Model (~200 MB) is downloaded on first use and @@ -133,10 +135,7 @@ trustmark = [ # eraser backend needs none of this. lama = [ "remove-ai-watermarks[visible]", - # ONNX Runtime 1.24 dropped CPython 3.10 wheels; keep the project's - # supported 3.10 line on the last compatible release series. - "onnxruntime>=1.16.0,<1.24; python_version < '3.11'", - "onnxruntime>=1.16.0; python_version >= '3.11'", + "onnxruntime>=1.24.0", "huggingface-hub>=0.20.0", ] # Lightweight inpaint backend -- MI-GAN via onnxruntime (andraniksargsyan/migan, @@ -146,8 +145,7 @@ lama = [ # LaMa, the quality-first `auto` choice, is too large. Same runtime as `lama`. migan = [ "remove-ai-watermarks[visible]", - "onnxruntime>=1.16.0,<1.24; python_version < '3.11'", - "onnxruntime>=1.16.0; python_version >= '3.11'", + "onnxruntime>=1.24.0", "huggingface-hub>=0.20.0", ] dev = [ @@ -160,10 +158,6 @@ dev = [ "ruff>=0.4.0", "pyright>=1.1.0", "invisible-watermark>=0.2.0", - # maintain.sh helpers; they only support newer Pythons, so gate them by - # marker to keep the py3.10 resolution (and CI matrix) solvable. - "uv-outdated>=0.1.0; python_version >= '3.12'", - "uv-secure>=0.12.0; python_version >= '3.12'", ] # ``qwen-zimage`` already pulls ``diffusion``; naming both would suggest diffusion is # independently sufficient for a removal, which it is not. @@ -204,7 +198,7 @@ pythonpath = ["src"] addopts = "-v --tb=short" [tool.ruff] -target-version = "py310" +target-version = "py311" line-length = 120 exclude = ["_refs"] extend-exclude = ["*.md"] @@ -227,7 +221,7 @@ quote-style = "double" indent-style = "space" [tool.pyright] -pythonVersion = "3.10" +pythonVersion = "3.11" typeCheckingMode = "strict" exclude = ["_refs"] diff --git a/scripts/fidelity_metrics.py b/scripts/fidelity_metrics.py index 5578010..56db610 100644 --- a/scripts/fidelity_metrics.py +++ b/scripts/fidelity_metrics.py @@ -1,5 +1,5 @@ # /// script -# requires-python = ">=3.10" +# requires-python = ">=3.11" # dependencies = [ # "click", # "numpy", diff --git a/scripts/qwen_scrub_prototype.py b/scripts/qwen_scrub_prototype.py index 5b6a86e..ecae2b5 100644 --- a/scripts/qwen_scrub_prototype.py +++ b/scripts/qwen_scrub_prototype.py @@ -1,5 +1,5 @@ # /// script -# requires-python = ">=3.10" +# requires-python = ">=3.11" # dependencies = [ # "diffusers>=0.35.0", # "transformers>=4.51.0", diff --git a/scripts/synthid_corpus.py b/scripts/synthid_corpus.py index 6e007f4..0dd4c59 100644 --- a/scripts/synthid_corpus.py +++ b/scripts/synthid_corpus.py @@ -22,7 +22,7 @@ import shutil import subprocess import sys from collections import Counter -from datetime import datetime, timezone +from datetime import UTC, datetime from pathlib import Path import click @@ -180,7 +180,7 @@ def ingest( "c2pa_issuer": issuer, "synthid_metadata": "yes" if synthid else "", "verified_via": verified_via, - "added": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), + "added": datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ"), "notes": notes, } ) diff --git a/tests/test_packaging.py b/tests/test_packaging.py index 1b52e6b..42f087a 100644 --- a/tests/test_packaging.py +++ b/tests/test_packaging.py @@ -5,19 +5,27 @@ from __future__ import annotations from importlib.metadata import metadata, requires from packaging.requirements import Requirement +from packaging.specifiers import SpecifierSet from packaging.utils import canonicalize_name -def _requirement_names(extra: str | None = None) -> set[str]: +def _requirement_names(extra: str | None = None, *, python_version: str | None = None) -> set[str]: selected_extra = extra or "" + environment = {"extra": selected_extra} + if python_version is not None: + environment["python_version"] = python_version parsed = (Requirement(value) for value in requires("remove-ai-watermarks") or []) return { canonicalize_name(requirement.name) for requirement in parsed - if requirement.marker is None or requirement.marker.evaluate({"extra": selected_extra}) + if requirement.marker is None or requirement.marker.evaluate(environment) } +def test_supported_python_floor_is_published(): + assert SpecifierSet(metadata("remove-ai-watermarks")["Requires-Python"]) == SpecifierSet(">=3.11,<3.15") + + def test_default_install_is_metadata_focused(): default = _requirement_names() @@ -55,6 +63,11 @@ def test_file_format_and_detector_dependencies_are_independent(): assert "pywavelets" in _requirement_names("detect") +def test_trustmark_is_limited_to_its_numpy_compatible_python_range(): + assert "trustmark" in _requirement_names("trustmark", python_version="3.12") + assert "trustmark" not in _requirement_names("trustmark", python_version="3.13") + + def test_extras_use_capability_names_without_legacy_aliases(): extras = set(metadata("remove-ai-watermarks").get_all("Provides-Extra") or []) diff --git a/uv.lock b/uv.lock index 140c995..9574316 100644 --- a/uv.lock +++ b/uv.lock @@ -1,25 +1,27 @@ version = 1 revision = 3 -requires-python = ">=3.10.1" +requires-python = ">=3.11, <3.15" resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'darwin'", - "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and sys_platform == 'darwin'", "python_full_version >= '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.13.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", "python_full_version >= '3.14' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform == 'emscripten'", "(python_full_version >= '3.14' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", - "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", - "(python_full_version >= '3.12' and python_full_version < '3.14' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", - "python_full_version == '3.11.*' and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and sys_platform == 'win32'", - "python_full_version == '3.11.*' and sys_platform == 'emscripten'", - "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", - "python_full_version < '3.11' and sys_platform == 'darwin'", - "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "(python_full_version == '3.13.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", + "python_full_version == '3.12.*' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "(python_full_version == '3.12.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", + "python_full_version < '3.12' and sys_platform == 'darwin'", + "python_full_version < '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version < '3.12' and sys_platform == 'win32'", + "python_full_version < '3.12' and sys_platform == 'emscripten'", + "(python_full_version < '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", ] [[package]] @@ -28,7 +30,8 @@ version = "1.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "huggingface-hub" }, - { name = "numpy" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "packaging" }, { name = "psutil" }, { name = "pyyaml" }, @@ -56,7 +59,6 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, { name = "aiosignal" }, - { name = "async-timeout", marker = "python_full_version < '3.11'" }, { name = "attrs" }, { name = "frozenlist" }, { name = "multidict" }, @@ -66,24 +68,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/58/d9/22ce5786ac0c1653ae8b6c23bded02c1686d11f0dbb45b31ce128e0df985/aiohttp-3.14.3.tar.gz", hash = "sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc", size = 7971213, upload-time = "2026-07-23T01:57:27.037Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/4d/4a99fb425c5e0cad715eea7bd190aff46f38b959a0a2dadb993705d34b26/aiohttp-3.14.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:eb0495d778817619273c108784292be161a924b9f5ae5cbbc70a2caa6838250b", size = 765848, upload-time = "2026-07-23T01:52:08.217Z" }, - { url = "https://files.pythonhosted.org/packages/74/e8/43b85dc55b8e950dc644babe762add781319ea881b57b33d2cce12017d12/aiohttp-3.14.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c3c200cf9757edd785051dc699c7ecbec22110dbfcb3fefc7a9f9695eda8ea7a", size = 517476, upload-time = "2026-07-23T01:52:10.846Z" }, - { url = "https://files.pythonhosted.org/packages/7f/9e/73b582c4dbbc3c12ef4473822475effaabf1f934b56f14f5b03fe5d3a2af/aiohttp-3.14.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd51ebf9d3a00c074df4ede271023f4d2dba289bcc740b88191872716014e3c5", size = 515334, upload-time = "2026-07-23T01:52:12.636Z" }, - { url = "https://files.pythonhosted.org/packages/79/03/e98c3c9e05a5bdf97defe5ff9169baba4f0ec9a901f2d60e0f060c2f051e/aiohttp-3.14.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:134ac5ddcf61c6fad984b9a5727d83492ada43d63471db20fb73042c13fca62f", size = 1708830, upload-time = "2026-07-23T01:52:14.538Z" }, - { url = "https://files.pythonhosted.org/packages/d7/2c/26e60b694844dfd2176c57f913a22d0cd6a16f9ff202cbda7580d0328b98/aiohttp-3.14.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:70c987b27534f9ae1a723f47ae921571d616da21d3208282bf4c52af5164ac43", size = 1674012, upload-time = "2026-07-23T01:52:16.486Z" }, - { url = "https://files.pythonhosted.org/packages/38/65/672df92e3172cd876aacfa97a952ac560877eb169384b2991ac5b273de4c/aiohttp-3.14.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1b59533861b70a2185c8f4f350f791f39d64358ef6944ce71c5240c9ec0982c9", size = 1767015, upload-time = "2026-07-23T01:52:18.28Z" }, - { url = "https://files.pythonhosted.org/packages/9e/c5/228dec7bfec1c373cc2217cdeb47d6456dcd7a13a4c55144930a75ae3851/aiohttp-3.14.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1c5281acc88b92396f88c7e1e2748f8466689df22b80170e4f51efa712fb47a8", size = 1858700, upload-time = "2026-07-23T01:52:20.08Z" }, - { url = "https://files.pythonhosted.org/packages/bd/ff/cb36724e8c8d17f90ada567a9ff3efe1d6e9b549fba697a242aece180f21/aiohttp-3.14.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48d67b87db6279c044760787eb01f6413032c2e6f3ba1cafaa492b1c8e578479", size = 1714075, upload-time = "2026-07-23T01:52:22.071Z" }, - { url = "https://files.pythonhosted.org/packages/9f/3a/296a4135c6366376263aeef54b15caca1f07676c2ae0c525d7832f2f808a/aiohttp-3.14.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f53bcd52f585e1ac3e590d61434eb61f9a88c38df041b4ea126d97144344a77b", size = 1588234, upload-time = "2026-07-23T01:52:23.757Z" }, - { url = "https://files.pythonhosted.org/packages/7d/81/9d5d853ef892dc066d1eb6db0e87a47348b920c1c879aa554612fdbd9d79/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0fdea2281997af69da84c77ffa6f5938a0285f21fb3887c249d67419ca865b3d", size = 1677300, upload-time = "2026-07-23T01:52:25.861Z" }, - { url = "https://files.pythonhosted.org/packages/68/96/021d386ae32d9b26d4b88df2e794546232ff56bb6be952bf6be227c0bbc7/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:cda5fd5c95ad7a125a2e8464acc78b98b94c475a3780d6aa0aa157c93f470f4d", size = 1691501, upload-time = "2026-07-23T01:52:28Z" }, - { url = "https://files.pythonhosted.org/packages/29/9f/af66adce26a14af135c003cbd0f44ccaa68cebd30ff8ac99ca47fb4958f7/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:6debfa7312ff9d4c124dc71d72e9a0a4b9e0879e48ba6fcb42bef5c3300289e2", size = 1735113, upload-time = "2026-07-23T01:52:29.995Z" }, - { url = "https://files.pythonhosted.org/packages/2f/90/28c390d4c9851effe52ac25b5a2e1d92246acd00728b4fc7975dafb67484/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:f4e05329faa0ea1a404b37de4f034fd2c2defcca06a68dc6745e4e56c88e8a48", size = 1577486, upload-time = "2026-07-23T01:52:31.937Z" }, - { url = "https://files.pythonhosted.org/packages/db/c2/00e23a1bf2abb70dd353f6987db7e7f2491d0261f7363997738c71c98f95/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a3a8296e7ab5c295f53f1041487cb088e1480775aafbf7fe545d93b770a0f96f", size = 1751353, upload-time = "2026-07-23T01:52:33.688Z" }, - { url = "https://files.pythonhosted.org/packages/6e/7d/d51a706a8cbfa57f0611127daf61ab3ae02ab8420b0407412079227d1c65/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5373dc80ad1aa2fb9ad95c83f24eef418bbda3a61375f128e5b0192e4f3f9b32", size = 1698681, upload-time = "2026-07-23T01:52:38.167Z" }, - { url = "https://files.pythonhosted.org/packages/ec/b0/90bd5cd9fdd9787cb4211d284d1fb8401339a933cb0227a15b71e789232f/aiohttp-3.14.3-cp310-cp310-win32.whl", hash = "sha256:a3e22975f905b89a55a488c2a08f2fdb2186175349e917d48985cc468a3d4c6e", size = 456733, upload-time = "2026-07-23T01:52:41.823Z" }, - { url = "https://files.pythonhosted.org/packages/d8/15/fe5b8f6a71ae112bc677163d0b0701bda5dc15005249582258ede0eb88c7/aiohttp-3.14.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdd0e2834dce1a26c1bbe26464861e16bbe217042cbff619247c11594472518c", size = 480460, upload-time = "2026-07-23T01:52:43.905Z" }, - { url = "https://files.pythonhosted.org/packages/54/00/45e98b6645cd7f00a4b78b749ebd309094b0eaeb2d2e96157eadbc0d0050/aiohttp-3.14.3-cp310-cp310-win_arm64.whl", hash = "sha256:eac645b09bcfdf73df7536331f0678c1086ea250981118ddb5199e17ccef72bb", size = 453479, upload-time = "2026-07-23T01:52:46.075Z" }, { url = "https://files.pythonhosted.org/packages/f8/5c/b3e4ff8ad43a8afef9602c5e90285936da1beaea8b029016b793891f03c3/aiohttp-3.14.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e568e14940c09955aa51f4e645b6daa18a581c5dcfcd73744dcc86a856e3ced3", size = 764250, upload-time = "2026-07-23T01:52:48.525Z" }, { url = "https://files.pythonhosted.org/packages/0e/da/f1b384465e51449d844056b75070461da03a9a23e6c1747003695bf4172a/aiohttp-3.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:54cfcdee2770dac994417cbb0ee1f3eb0e7cb6b30c79bf44f2c02ff79ec5124a", size = 516281, upload-time = "2026-07-23T01:52:51.047Z" }, { url = "https://files.pythonhosted.org/packages/b9/3f/01264f820ee2e3712a827892b1cd6ff80f3300c1fcbffbb45714a915d47a/aiohttp-3.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:21c016079415ed3fd676963e9793700a566d85dbbd6bfc564b9b2d209147dcc8", size = 514742, upload-time = "2026-07-23T01:52:53.779Z" }, @@ -208,15 +192,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302, upload-time = "2026-07-28T13:50:57.239Z" }, ] -[[package]] -name = "annotated-types" -version = "0.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" }, -] - [[package]] name = "antlr4-python3-runtime" version = "4.9.3" @@ -228,7 +203,6 @@ name = "anyio" version = "4.14.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "idna" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] @@ -237,15 +211,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" }, ] -[[package]] -name = "async-timeout" -version = "5.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, -] - [[package]] name = "attrs" version = "26.1.0" @@ -255,89 +220,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] -[[package]] -name = "av" -version = "16.1.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11' and sys_platform == 'darwin'", - "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')", -] -sdist = { url = "https://files.pythonhosted.org/packages/78/cd/3a83ffbc3cc25b39721d174487fb0d51a76582f4a1703f98e46170ce83d4/av-16.1.0.tar.gz", hash = "sha256:a094b4fd87a3721dacf02794d3d2c82b8d712c85b9534437e82a8a978c175ffd", size = 4285203, upload-time = "2026-01-11T07:31:33.772Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/97/51/2217a9249409d2e88e16e3f16f7c0def9fd3e7ffc4238b2ec211f9935bdb/av-16.1.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:2395748b0c34fe3a150a1721e4f3d4487b939520991b13e7b36f8926b3b12295", size = 26942590, upload-time = "2026-01-09T20:17:58.588Z" }, - { url = "https://files.pythonhosted.org/packages/bf/cd/a7070f4febc76a327c38808e01e2ff6b94531fe0b321af54ea3915165338/av-16.1.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:72d7ac832710a158eeb7a93242370aa024a7646516291c562ee7f14a7ea881fd", size = 21507910, upload-time = "2026-01-09T20:18:02.309Z" }, - { url = "https://files.pythonhosted.org/packages/ae/30/ec812418cd9b297f0238fe20eb0747d8a8b68d82c5f73c56fe519a274143/av-16.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6cbac833092e66b6b0ac4d81ab077970b8ca874951e9c3974d41d922aaa653ed", size = 38738309, upload-time = "2026-01-09T20:18:04.701Z" }, - { url = "https://files.pythonhosted.org/packages/3a/b8/6c5795bf1f05f45c5261f8bce6154e0e5e86b158a6676650ddd77c28805e/av-16.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:eb990672d97c18f99c02f31c8d5750236f770ffe354b5a52c5f4d16c5e65f619", size = 40293006, upload-time = "2026-01-09T20:18:07.238Z" }, - { url = "https://files.pythonhosted.org/packages/a7/44/5e183bcb9333fc3372ee6e683be8b0c9b515a506894b2d32ff465430c074/av-16.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:05ad70933ac3b8ef896a820ea64b33b6cca91a5fac5259cb9ba7fa010435be15", size = 40123516, upload-time = "2026-01-09T20:18:09.955Z" }, - { url = "https://files.pythonhosted.org/packages/12/1d/b5346d582a3c3d958b4d26a2cc63ce607233582d956121eb20d2bbe55c2e/av-16.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d831a1062a3c47520bf99de6ec682bd1d64a40dfa958e5457bb613c5270e7ce3", size = 41463289, upload-time = "2026-01-09T20:18:12.459Z" }, - { url = "https://files.pythonhosted.org/packages/fa/31/acc946c0545f72b8d0d74584cb2a0ade9b7dfe2190af3ef9aa52a2e3c0b1/av-16.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:358ab910fef3c5a806c55176f2b27e5663b33c4d0a692dafeb049c6ed71f8aff", size = 31754959, upload-time = "2026-01-09T20:18:14.718Z" }, - { url = "https://files.pythonhosted.org/packages/48/d0/b71b65d1b36520dcb8291a2307d98b7fc12329a45614a303ff92ada4d723/av-16.1.0-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:e88ad64ee9d2b9c4c5d891f16c22ae78e725188b8926eb88187538d9dd0b232f", size = 26927747, upload-time = "2026-01-09T20:18:16.976Z" }, - { url = "https://files.pythonhosted.org/packages/2f/79/720a5a6ccdee06eafa211b945b0a450e3a0b8fc3d12922f0f3c454d870d2/av-16.1.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:cb296073fa6935724de72593800ba86ae49ed48af03960a4aee34f8a611f442b", size = 21492232, upload-time = "2026-01-09T20:18:19.266Z" }, - { url = "https://files.pythonhosted.org/packages/8e/4f/a1ba8d922f2f6d1a3d52419463ef26dd6c4d43ee364164a71b424b5ae204/av-16.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:720edd4d25aa73723c1532bb0597806d7b9af5ee34fc02358782c358cfe2f879", size = 39291737, upload-time = "2026-01-09T20:18:21.513Z" }, - { url = "https://files.pythonhosted.org/packages/1a/31/fc62b9fe8738d2693e18d99f040b219e26e8df894c10d065f27c6b4f07e3/av-16.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:c7f2bc703d0df260a1fdf4de4253c7f5500ca9fc57772ea241b0cb241bcf972e", size = 40846822, upload-time = "2026-01-09T20:18:24.275Z" }, - { url = "https://files.pythonhosted.org/packages/53/10/ab446583dbce730000e8e6beec6ec3c2753e628c7f78f334a35cad0317f4/av-16.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d69c393809babada7d54964d56099e4b30a3e1f8b5736ca5e27bd7be0e0f3c83", size = 40675604, upload-time = "2026-01-09T20:18:26.866Z" }, - { url = "https://files.pythonhosted.org/packages/31/d7/1003be685277005f6d63fd9e64904ee222fe1f7a0ea70af313468bb597db/av-16.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:441892be28582356d53f282873c5a951592daaf71642c7f20165e3ddcb0b4c63", size = 42015955, upload-time = "2026-01-09T20:18:29.461Z" }, - { url = "https://files.pythonhosted.org/packages/2f/4a/fa2a38ee9306bf4579f556f94ecbc757520652eb91294d2a99c7cf7623b9/av-16.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:273a3e32de64819e4a1cd96341824299fe06f70c46f2288b5dc4173944f0fd62", size = 31750339, upload-time = "2026-01-09T20:18:32.249Z" }, - { url = "https://files.pythonhosted.org/packages/9c/84/2535f55edcd426cebec02eb37b811b1b0c163f26b8d3f53b059e2ec32665/av-16.1.0-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:640f57b93f927fba8689f6966c956737ee95388a91bd0b8c8b5e0481f73513d6", size = 26945785, upload-time = "2026-01-09T20:18:34.486Z" }, - { url = "https://files.pythonhosted.org/packages/b6/17/ffb940c9e490bf42e86db4db1ff426ee1559cd355a69609ec1efe4d3a9eb/av-16.1.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:ae3fb658eec00852ebd7412fdc141f17f3ddce8afee2d2e1cf366263ad2a3b35", size = 21481147, upload-time = "2026-01-09T20:18:36.716Z" }, - { url = "https://files.pythonhosted.org/packages/15/c1/e0d58003d2d83c3921887d5c8c9b8f5f7de9b58dc2194356a2656a45cfdc/av-16.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:27ee558d9c02a142eebcbe55578a6d817fedfde42ff5676275504e16d07a7f86", size = 39517197, upload-time = "2026-01-11T09:57:31.937Z" }, - { url = "https://files.pythonhosted.org/packages/32/77/787797b43475d1b90626af76f80bfb0c12cfec5e11eafcfc4151b8c80218/av-16.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7ae547f6d5fa31763f73900d43901e8c5fa6367bb9a9840978d57b5a7ae14ed2", size = 41174337, upload-time = "2026-01-11T09:57:35.792Z" }, - { url = "https://files.pythonhosted.org/packages/8e/ac/d90df7f1e3b97fc5554cf45076df5045f1e0a6adf13899e10121229b826c/av-16.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8cf065f9d438e1921dc31fc7aa045790b58aee71736897866420d80b5450f62a", size = 40817720, upload-time = "2026-01-11T09:57:39.039Z" }, - { url = "https://files.pythonhosted.org/packages/80/6f/13c3a35f9dbcebafd03fe0c4cbd075d71ac8968ec849a3cfce406c35a9d2/av-16.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a345877a9d3cc0f08e2bc4ec163ee83176864b92587afb9d08dff50f37a9a829", size = 42267396, upload-time = "2026-01-11T09:57:42.115Z" }, - { url = "https://files.pythonhosted.org/packages/c8/b9/275df9607f7fb44317ccb1d4be74827185c0d410f52b6e2cd770fe209118/av-16.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:f49243b1d27c91cd8c66fdba90a674e344eb8eb917264f36117bf2b6879118fd", size = 31752045, upload-time = "2026-01-11T09:57:45.106Z" }, - { url = "https://files.pythonhosted.org/packages/75/2a/63797a4dde34283dd8054219fcb29294ba1c25d68ba8c8c8a6ae53c62c45/av-16.1.0-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:ce2a1b3d8bf619f6c47a9f28cfa7518ff75ddd516c234a4ee351037b05e6a587", size = 26916715, upload-time = "2026-01-11T09:57:47.682Z" }, - { url = "https://files.pythonhosted.org/packages/d2/c4/0b49cf730d0ae8cda925402f18ae814aef351f5772d14da72dd87ff66448/av-16.1.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:408dbe6a2573ca58a855eb8cd854112b33ea598651902c36709f5f84c991ed8e", size = 21452167, upload-time = "2026-01-11T09:57:50.606Z" }, - { url = "https://files.pythonhosted.org/packages/51/23/408806503e8d5d840975aad5699b153aaa21eb6de41ade75248a79b7a37f/av-16.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:57f657f86652a160a8a01887aaab82282f9e629abf94c780bbdbb01595d6f0f7", size = 39215659, upload-time = "2026-01-11T09:57:53.757Z" }, - { url = "https://files.pythonhosted.org/packages/c4/19/a8528d5bba592b3903f44c28dab9cc653c95fcf7393f382d2751a1d1523e/av-16.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:adbad2b355c2ee4552cac59762809d791bda90586d134a33c6f13727fb86cb3a", size = 40874970, upload-time = "2026-01-11T09:57:56.802Z" }, - { url = "https://files.pythonhosted.org/packages/e8/24/2dbcdf0e929ad56b7df078e514e7bd4ca0d45cba798aff3c8caac097d2f7/av-16.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f42e1a68ec2aebd21f7eb6895be69efa6aa27eec1670536876399725bbda4b99", size = 40530345, upload-time = "2026-01-11T09:58:00.421Z" }, - { url = "https://files.pythonhosted.org/packages/54/27/ae91b41207f34e99602d1c72ab6ffd9c51d7c67e3fbcd4e3a6c0e54f882c/av-16.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58fe47aeaef0f100c40ec8a5de9abbd37f118d3ca03829a1009cf288e9aef67c", size = 41972163, upload-time = "2026-01-11T09:58:03.756Z" }, - { url = "https://files.pythonhosted.org/packages/fc/7a/22158fb923b2a9a00dfab0e96ef2e8a1763a94dd89e666a5858412383d46/av-16.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:565093ebc93b2f4b76782589564869dadfa83af5b852edebedd8fee746457d06", size = 31729230, upload-time = "2026-01-11T09:58:07.254Z" }, - { url = "https://files.pythonhosted.org/packages/7f/f1/878f8687d801d6c4565d57ebec08449c46f75126ebca8e0fed6986599627/av-16.1.0-cp313-cp313t-macosx_11_0_x86_64.whl", hash = "sha256:574081a24edb98343fd9f473e21ae155bf61443d4ec9d7708987fa597d6b04b2", size = 27008769, upload-time = "2026-01-11T09:58:10.266Z" }, - { url = "https://files.pythonhosted.org/packages/30/f1/bd4ce8c8b5cbf1d43e27048e436cbc9de628d48ede088a1d0a993768eb86/av-16.1.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:9ab00ea29c25ebf2ea1d1e928d7babb3532d562481c5d96c0829212b70756ad0", size = 21590588, upload-time = "2026-01-11T09:58:12.629Z" }, - { url = "https://files.pythonhosted.org/packages/1d/dd/c81f6f9209201ff0b5d5bed6da6c6e641eef52d8fbc930d738c3f4f6f75d/av-16.1.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:a84a91188c1071f238a9523fd42dbe567fb2e2607b22b779851b2ce0eac1b560", size = 40638029, upload-time = "2026-01-11T09:58:15.399Z" }, - { url = "https://files.pythonhosted.org/packages/15/4d/07edff82b78d0459a6e807e01cd280d3180ce832efc1543de80d77676722/av-16.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:c2cd0de4dd022a7225ff224fde8e7971496d700be41c50adaaa26c07bb50bf97", size = 41970776, upload-time = "2026-01-11T09:58:19.075Z" }, - { url = "https://files.pythonhosted.org/packages/da/9d/1f48b354b82fa135d388477cd1b11b81bdd4384bd6a42a60808e2ec2d66b/av-16.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0816143530624a5a93bc5494f8c6eeaf77549b9366709c2ac8566c1e9bff6df5", size = 41764751, upload-time = "2026-01-11T09:58:22.788Z" }, - { url = "https://files.pythonhosted.org/packages/2f/c7/a509801e98db35ec552dd79da7bdbcff7104044bfeb4c7d196c1ce121593/av-16.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e3a28053af29644696d0c007e897d19b1197585834660a54773e12a40b16974c", size = 43034355, upload-time = "2026-01-11T09:58:26.125Z" }, - { url = "https://files.pythonhosted.org/packages/36/8b/e5f530d9e8f640da5f5c5f681a424c65f9dd171c871cd255d8a861785a6e/av-16.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e3e67144a202b95ed299d165232533989390a9ea3119d37eccec697dc6dbb0c", size = 31947047, upload-time = "2026-01-11T09:58:31.867Z" }, - { url = "https://files.pythonhosted.org/packages/df/18/8812221108c27d19f7e5f486a82c827923061edf55f906824ee0fcaadf50/av-16.1.0-cp314-cp314-macosx_11_0_x86_64.whl", hash = "sha256:39a634d8e5a87e78ea80772774bfd20c0721f0d633837ff185f36c9d14ffede4", size = 26916179, upload-time = "2026-01-11T09:58:36.506Z" }, - { url = "https://files.pythonhosted.org/packages/38/ef/49d128a9ddce42a2766fe2b6595bd9c49e067ad8937a560f7838a541464e/av-16.1.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:0ba32fb9e9300948a7fa9f8a3fc686e6f7f77599a665c71eb2118fdfd2c743f9", size = 21460168, upload-time = "2026-01-11T09:58:39.231Z" }, - { url = "https://files.pythonhosted.org/packages/e6/a9/b310d390844656fa74eeb8c2750e98030877c75b97551a23a77d3f982741/av-16.1.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:ca04d17815182d34ce3edc53cbda78a4f36e956c0fd73e3bab249872a831c4d7", size = 39210194, upload-time = "2026-01-11T09:58:42.138Z" }, - { url = "https://files.pythonhosted.org/packages/0c/7b/e65aae179929d0f173af6e474ad1489b5b5ad4c968a62c42758d619e54cf/av-16.1.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:ee0e8de2e124a9ef53c955fe2add6ee7c56cc8fd83318265549e44057db77142", size = 40811675, upload-time = "2026-01-11T09:58:45.871Z" }, - { url = "https://files.pythonhosted.org/packages/54/3f/5d7edefd26b6a5187d6fac0f5065ee286109934f3dea607ef05e53f05b31/av-16.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:22bf77a2f658827043a1e184b479c3bf25c4c43ab32353677df2d119f080e28f", size = 40543942, upload-time = "2026-01-11T09:58:49.759Z" }, - { url = "https://files.pythonhosted.org/packages/1b/24/f8b17897b67be0900a211142f5646a99d896168f54d57c81f3e018853796/av-16.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2dd419d262e6a71cab206d80bbf28e0a10d0f227b671cdf5e854c028faa2d043", size = 41924336, upload-time = "2026-01-11T09:58:53.344Z" }, - { url = "https://files.pythonhosted.org/packages/1c/cf/d32bc6bbbcf60b65f6510c54690ed3ae1c4ca5d9fafbce835b6056858686/av-16.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:53585986fd431cd436f290fba662cfb44d9494fbc2949a183de00acc5b33fa88", size = 31735077, upload-time = "2026-01-11T09:58:56.684Z" }, - { url = "https://files.pythonhosted.org/packages/53/f4/9b63dc70af8636399bd933e9df4f3025a0294609510239782c1b746fc796/av-16.1.0-cp314-cp314t-macosx_11_0_x86_64.whl", hash = "sha256:76f5ed8495cf41e1209a5775d3699dc63fdc1740b94a095e2485f13586593205", size = 27014423, upload-time = "2026-01-11T09:58:59.703Z" }, - { url = "https://files.pythonhosted.org/packages/d1/da/787a07a0d6ed35a0888d7e5cfb8c2ffa202f38b7ad2c657299fac08eb046/av-16.1.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:8d55397190f12a1a3ae7538be58c356cceb2bf50df1b33523817587748ce89e5", size = 21595536, upload-time = "2026-01-11T09:59:02.508Z" }, - { url = "https://files.pythonhosted.org/packages/d8/f4/9a7d8651a611be6e7e3ab7b30bb43779899c8cac5f7293b9fb634c44a3f3/av-16.1.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:9d51d9037437218261b4bbf9df78a95e216f83d7774fbfe8d289230b5b2e28e2", size = 40642490, upload-time = "2026-01-11T09:59:05.842Z" }, - { url = "https://files.pythonhosted.org/packages/6b/e4/eb79bc538a94b4ff93cd4237d00939cba797579f3272490dd0144c165a21/av-16.1.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:0ce07a89c15644407f49d942111ca046e323bbab0a9078ff43ee57c9b4a50dad", size = 41976905, upload-time = "2026-01-11T09:59:09.169Z" }, - { url = "https://files.pythonhosted.org/packages/5e/f5/f6db0dd86b70167a4d55ee0d9d9640983c570d25504f2bde42599f38241e/av-16.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:cac0c074892ea97113b53556ff41c99562db7b9f09f098adac1f08318c2acad5", size = 41770481, upload-time = "2026-01-11T09:59:12.74Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8b/33651d658e45e16ab7671ea5fcf3d20980ea7983234f4d8d0c63c65581a5/av-16.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7dec3dcbc35a187ce450f65a2e0dda820d5a9e6553eea8344a1459af11c98649", size = 43036824, upload-time = "2026-01-11T09:59:16.507Z" }, - { url = "https://files.pythonhosted.org/packages/83/41/7f13361db54d7e02f11552575c0384dadaf0918138f4eaa82ea03a9f9580/av-16.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6f90dc082ff2068ddbe77618400b44d698d25d9c4edac57459e250c16b33d700", size = 31948164, upload-time = "2026-01-11T09:59:19.501Z" }, -] - [[package]] name = "av" version = "18.0.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'darwin'", - "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'darwin'", - "python_full_version >= '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "(python_full_version >= '3.14' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", - "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", - "(python_full_version >= '3.12' and python_full_version < '3.14' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", - "python_full_version == '3.11.*' and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and sys_platform == 'win32'", - "python_full_version == '3.11.*' and sys_platform == 'emscripten'", - "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", -] sdist = { url = "https://files.pythonhosted.org/packages/ae/a4/570a5a35c8638aba01e739925846c35fdd6b0756a15526766d0a4dd3b7df/av-18.0.0.tar.gz", hash = "sha256:4ef7e72c3d3a872584a1215173b16e0226811037f40dcdbf75992631098df1ba", size = 4340222, upload-time = "2026-07-02T06:37:58.907Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/18/4a/9e3463df030e063d757fa12f0f39be6541b45b06b5bad48c2ce361b924bf/av-18.0.0-cp311-abi3-macosx_11_0_x86_64.whl", hash = "sha256:149289d40e732a6e49c9530bc245b49d9964cfd1c8c9e06778703b7d5bba6b25", size = 22499354, upload-time = "2026-07-02T06:36:58.751Z" }, @@ -401,18 +287,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/9e/ef/008a1939e372c06329a3fce4279c02f328488f3526744906eeec3da7ad5f/cffi-2.1.1.tar.gz", hash = "sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be", size = 530807, upload-time = "2026-08-03T21:21:18.939Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/d2/2cde336b375f55c76ca670f0be3978cc048e31e24f3b4d7ce8473150a388/cffi-2.1.1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:baed1e86cc735622097354b9d1281406caf42ff42a886d29faa8e8d1630333be", size = 183779, upload-time = "2026-08-03T21:19:15.602Z" }, - { url = "https://files.pythonhosted.org/packages/94/1a/4b2f7c92293ba05cbd4a9a1b28faaf0326272d9488e6354657571c48a7aa/cffi-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca82be1a1d406ecfe1d25dc16cb33488e5a16bf4438c9fb590484ea29d92478b", size = 184178, upload-time = "2026-08-03T21:19:16.67Z" }, - { url = "https://files.pythonhosted.org/packages/17/0b/ba385d8ccedf926c3cd06e8e2f327027da5afe5f0eb30f1f7bc43ac55125/cffi-2.1.1-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:42e2f76b9455f5a9a844f770bf3e200ed3da0e15f5df3db9c31fe80b04b3d004", size = 211037, upload-time = "2026-08-03T21:19:17.705Z" }, - { url = "https://files.pythonhosted.org/packages/a3/b9/0f2e58b2cefa33255bff36935d42b13180fe559bba82596540eb404bde7d/cffi-2.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5a59cc1c4442bc3d5c703bf720b51138d0bfc173618807c9ee2490a7541dd3d9", size = 218652, upload-time = "2026-08-03T21:19:18.735Z" }, - { url = "https://files.pythonhosted.org/packages/37/15/180e0dab27b9312c7479003d14c9e547634b7dcb934e2cc4650e1b131a7a/cffi-2.1.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:9f8d177621de5cb38ee3e731eda45d421db093ec0739f46a5594babda7987a98", size = 205422, upload-time = "2026-08-03T21:19:19.96Z" }, - { url = "https://files.pythonhosted.org/packages/18/d4/03026f0c850cbbaa9030750490225b4a7f4d524ea4df72c3cc740a90f4ef/cffi-2.1.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:75f80557d1389eddbd0de2681f6a390a0c5338c31ddaa821381c203fc3fd50d9", size = 205444, upload-time = "2026-08-03T21:19:21.246Z" }, - { url = "https://files.pythonhosted.org/packages/75/77/60bebf6f818bec84210ac5b6979ce4eeadce6fbbaabc9c7ab23e506d1ce5/cffi-2.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:194cffa889098ced9976c3fc6340305e43f6303657d298da55366907c05c22d6", size = 218742, upload-time = "2026-08-03T21:19:22.523Z" }, - { url = "https://files.pythonhosted.org/packages/b0/ae/679bf47e73fd77b352171727f07de559a003f14de5d02b904a6ec1fa73ca/cffi-2.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5bb4e7ea95dcd6a014a6fef62e62467d67d8e582326443f3d68e71d6320a9fcf", size = 221054, upload-time = "2026-08-03T21:19:23.694Z" }, - { url = "https://files.pythonhosted.org/packages/09/b8/eefc0e06913b70aa153bf74c946094a18f58fd4aff11b7f372bfdfdca050/cffi-2.1.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3d22a20b1fb1632cc72c22f95f7b0d2961c3e1c235f245ba4c606c4771035659", size = 213489, upload-time = "2026-08-03T21:19:24.922Z" }, - { url = "https://files.pythonhosted.org/packages/6f/13/4e56852824a03cdf68523a35686f1c28eacd4bd30a7b0a78e682e6e6e1d3/cffi-2.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1dea0e4d7d4f11f619fe8c1d76caf49e24405b4b5743c0e3be16a500ecd930c9", size = 220241, upload-time = "2026-08-03T21:19:26.214Z" }, - { url = "https://files.pythonhosted.org/packages/99/7f/040f9e163e4acac3ee3d85b02d00b2576e7ca980d8785f0a3a5f1a9bf7f5/cffi-2.1.1-cp310-cp310-win32.whl", hash = "sha256:7ce713ace7c0e4520535b42b77eaa742c16dab813978064913e5a3cf82973b41", size = 174578, upload-time = "2026-08-03T21:19:27.338Z" }, - { url = "https://files.pythonhosted.org/packages/ba/0b/644a2ec1a4eaba49c2939410bb1eb1d25b09d6d0582f5d2f95c537043725/cffi-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:a48d62ab9d6f4f98c983223a547af44be6ca3691074c31cecced6facd3ba2dc1", size = 185082, upload-time = "2026-08-03T21:19:28.409Z" }, { url = "https://files.pythonhosted.org/packages/70/d2/16d99a0c4948febc0ebd133a13b2f688ff7f8cb04da971e1128872ce0c03/cffi-2.1.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:c8d2c9fd1f2d16f780d15127abb050d13d1a76c03a4bd87d7e4980e45e511e12", size = 183838, upload-time = "2026-08-03T21:19:29.637Z" }, { url = "https://files.pythonhosted.org/packages/cd/95/31b535a9f0220ae9f357de4a08d57ce89cb417653c2fd9f075f50822a388/cffi-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:398aff33cee2767e3e781d2554c54bd0dff386bb437581e0d8011fde1a942ec1", size = 184168, upload-time = "2026-08-03T21:19:30.764Z" }, { url = "https://files.pythonhosted.org/packages/ad/5a/4707a0dc1f203f5dde5a907b0d4e3c25d71120241048bd5bc6f1bb9d4e71/cffi-2.1.1-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:154852545011f779917b11c78db2358d095da62a9a172b78ad0a583ee5adc0d0", size = 211805, upload-time = "2026-08-03T21:19:31.867Z" }, @@ -476,30 +350,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/eb/d2/3b7176cb570a1d3e27faf67b72f591af508036e0d8b2be2ef9af9e8c84bb/cffi-2.1.1-cp314-cp314t-win32.whl", hash = "sha256:8ef53b2de9bcb9197d31854256575d59dbac0cba72ac627bb291ef5eceb74be4", size = 182868, upload-time = "2026-08-03T21:20:40.388Z" }, { url = "https://files.pythonhosted.org/packages/56/78/31f00c1bcd97c9bbf55f1bfdf5bc809a5de8887473e90bb9960dca825e80/cffi-2.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:616f097f2fe415bc92a247f02e11f634e1f9e9a83d327e3c915c15089c87869e", size = 194104, upload-time = "2026-08-03T21:20:41.725Z" }, { url = "https://files.pythonhosted.org/packages/7b/1b/58496f2ed0a35de575250c02a43ab3cc2c04d494a88fed31c1cabc0fd176/cffi-2.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ad2c86c495b899d862ea0f4b42891b8713a3bd45dd4105c7fd51c2a72f39f3a5", size = 186402, upload-time = "2026-08-03T21:20:43.042Z" }, - { url = "https://files.pythonhosted.org/packages/c1/8f/9ebe220eab48a093d1a5a5e339ab0dc7316eef3bb04d63c42f0251b61f50/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:dddad92b554513a31f272570678ba307fb9f618f05e3d4a5eacafff9eae03e1d", size = 194043, upload-time = "2026-08-03T21:20:48.179Z" }, - { url = "https://files.pythonhosted.org/packages/ff/69/844bad3ece306c4782c2ecb93597035b6690d48704b803914c199da1e8b3/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:da0e573f9f97159390c89d9f1a9e41908b66d408cc5b58d08cf3847d844c531b", size = 196737, upload-time = "2026-08-03T21:20:49.457Z" }, - { url = "https://files.pythonhosted.org/packages/1b/8a/af668013284634733f02d683458a0728739c7d6ddb5e14cb0c20832266fe/cffi-2.1.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:fb92203a88b3d3053034db775110081c49d28be6551923805e039924093761e4", size = 184933, upload-time = "2026-08-03T21:20:50.639Z" }, - { url = "https://files.pythonhosted.org/packages/0c/75/2f5207ff6d1a613133b23a5203cc0c2a628313b5eb3974d7956ae3c57950/cffi-2.1.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2ae64be792b8966f2c69538199728b290e34726562896df1e5dc8ffd8d8188e8", size = 185002, upload-time = "2026-08-03T21:20:52.173Z" }, - { url = "https://files.pythonhosted.org/packages/e2/31/9e1313b0a6e30e91b3b3d3fff51ae99c857c07738e3afcce1f7334e1b7ab/cffi-2.1.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:507a24c282e0f42f8ed737cf048572cbf580468da5555764a8331735e9c736b6", size = 222271, upload-time = "2026-08-03T21:20:53.462Z" }, - { url = "https://files.pythonhosted.org/packages/50/e3/f6234a833e6e08c7007003074723c406559eecf9b48dfc97471e5a8eb7a0/cffi-2.1.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:246fa40ce8645a614ff682e0b70f37134e460eaf93a775e0cbe3cca585a67a80", size = 209919, upload-time = "2026-08-03T21:20:54.783Z" }, - { url = "https://files.pythonhosted.org/packages/0d/fc/5f74e293fced6edb51af3a46c4ccf6c23c9943774ecb375ddbd522c76add/cffi-2.1.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:471cee653ae88de62096552e6d24ccb4a5adb8c8c9f10b5054d0122c15bf2779", size = 208529, upload-time = "2026-08-03T21:20:56.066Z" }, - { url = "https://files.pythonhosted.org/packages/44/16/29e6d01b388bef055ecd6ca8244b3f4d336bd09e92d5d892187b9601084e/cffi-2.1.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeae0e330c9f6acd681f647d46cefd30c29f93e3392882e792e82080c9691399", size = 221630, upload-time = "2026-08-03T21:20:57.336Z" }, - { url = "https://files.pythonhosted.org/packages/a4/18/fa7f1f6857d5eb88a4ca99ffcbfb7c387a287ccc154c64a73e86314745d7/cffi-2.1.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:42a494cee34437f05546455144f2b5d9ac09b1face62bcfce597d2e521066688", size = 225134, upload-time = "2026-08-03T21:20:58.675Z" }, - { url = "https://files.pythonhosted.org/packages/e0/9f/e8e3dfa04a1b4c241f8c91faacad872b4d4efd051d49764ad4e2fd4b9fea/cffi-2.1.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:cc572dace3f60ef98d7b12ff411d20f5362feb31a0439eab0085bbfd349982d7", size = 223197, upload-time = "2026-08-03T21:20:59.968Z" }, - { url = "https://files.pythonhosted.org/packages/f8/7e/8debeb04f1ab9fe2a6963964cd6f1aaf7192627b83926586a6a4e089c9fa/cffi-2.1.1-cp315-cp315-win32.whl", hash = "sha256:4f42141fc14250de6dde5ee7ea4432be017252d91f19c5ad043c084cea629cac", size = 177683, upload-time = "2026-08-03T21:21:14.901Z" }, - { url = "https://files.pythonhosted.org/packages/e0/31/5158704cc474ab65c1647932e88be78dc0873f47130e253be38bcaf13d01/cffi-2.1.1-cp315-cp315-win_amd64.whl", hash = "sha256:e6e8cff14d6fb0be70a09c0bdc58096f501952d04624ebf867e0e56da2df8960", size = 187897, upload-time = "2026-08-03T21:21:16.108Z" }, - { url = "https://files.pythonhosted.org/packages/cc/4b/b3a2da8570c704ffc0f9762cdc3ec0f02c8573798e0b5cf7f11c82bbb70f/cffi-2.1.1-cp315-cp315-win_arm64.whl", hash = "sha256:27350daa11d4f10c540e6e89dada4c54feb7256ad03e9a4dc075ebad7ba360d1", size = 182935, upload-time = "2026-08-03T21:21:17.271Z" }, - { url = "https://files.pythonhosted.org/packages/d0/ef/5443574510a1207e6f6bc38ba6e1f1de36cb48fef07b2728bb896a21f430/cffi-2.1.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c26608d2222fb1e94487e4a387d85f13eb55d5ed725cb25a0c589ac4ee60e7bc", size = 188464, upload-time = "2026-08-03T21:21:01.163Z" }, - { url = "https://files.pythonhosted.org/packages/7e/ae/a56fa8c4686ad50e148fcbc8d3ae0d03915ff5c30d795058988c24118cef/cffi-2.1.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:4be96343e422f2dfcd12ab5c9f5aebe03f82f737c6bffeca6830b3875cb44aab", size = 188262, upload-time = "2026-08-03T21:21:02.382Z" }, - { url = "https://files.pythonhosted.org/packages/53/b2/6187f46f2912276a3ae284076109cc5c8680482f11f766ccf26db4a86427/cffi-2.1.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:937c0052c05a31ca1daf18de3158eed4dbfcb9cc107adbea227728d647be701e", size = 223779, upload-time = "2026-08-03T21:21:03.553Z" }, - { url = "https://files.pythonhosted.org/packages/8a/f6/c3ad28bd19f77047a03084424fbd4cbe997303267c14423737324be0385d/cffi-2.1.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:df423d40ee8654634421812bc3b196da3f9bd7d32929da813f8394c4348a5358", size = 211520, upload-time = "2026-08-03T21:21:04.863Z" }, - { url = "https://files.pythonhosted.org/packages/a0/cd/ccac9013a5bd9fd764de118674ab9c805b5ca10c19270d90ee273f8b2240/cffi-2.1.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a730a083190634c65cca36ba5f489531576ebd79bcd5c8e172130f6453127231", size = 210673, upload-time = "2026-08-03T21:21:06.223Z" }, - { url = "https://files.pythonhosted.org/packages/52/86/2976131c639aead931c5bee5aba67e4b09fbeb8018b6f282f70803f923a7/cffi-2.1.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:363e05fa78e15116c3c32c210ee36884fd6b9afa6d440e47112c3bd511d64cb6", size = 223835, upload-time = "2026-08-03T21:21:07.539Z" }, - { url = "https://files.pythonhosted.org/packages/ac/0c/33a7aeab2f9c76918c52e084beb39c570db3588133412929e8ec06fab90b/cffi-2.1.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:770de9db11e84213beec501cfcaa013b019820ca881e03344dea5844f7876d94", size = 226705, upload-time = "2026-08-03T21:21:08.774Z" }, - { url = "https://files.pythonhosted.org/packages/e3/26/2cde30fdde421130bfc18f70395731a6e6b2053c6a1978a5258ff04e72fa/cffi-2.1.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:7da0c5eff80f0197f3b3d1232ec5a682a9325f4ae9016a78f5f5ca35f9ced1f5", size = 225539, upload-time = "2026-08-03T21:21:09.911Z" }, - { url = "https://files.pythonhosted.org/packages/6d/cd/a361394c94b2129d604bb846f624a8e88255a3ee33129c434a00d715e64f/cffi-2.1.1-cp315-cp315t-win32.whl", hash = "sha256:06c72bb76605a4b0cd0aad6930b69d4baf7dd5d806cfc409b824191099700e66", size = 182707, upload-time = "2026-08-03T21:21:11.226Z" }, - { url = "https://files.pythonhosted.org/packages/9b/b5/ba2b299993c26577d529b6ae29841f9e15b9fcf004d65f423f4fcf94ade9/cffi-2.1.1-cp315-cp315t-win_amd64.whl", hash = "sha256:d9c275eaacd24aa73f94ffd6de08fc3f932424d8b6c376f4bed7cde376fe7bc3", size = 193772, upload-time = "2026-08-03T21:21:12.39Z" }, - { url = "https://files.pythonhosted.org/packages/aa/29/35e016098c814cd93de9cd320c66b5bfba14dc6ecedd3cb518fa7c408c69/cffi-2.1.1-cp315-cp315t-win_arm64.whl", hash = "sha256:d18e5ac0f2f03f4f518d3e23db0f0cad7faa1da8620e9c09461d443bbf6e6692", size = 186360, upload-time = "2026-08-03T21:21:13.636Z" }, ] [[package]] @@ -508,19 +358,6 @@ version = "3.4.9" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/81/8e983840c6e5b93b33c2ba81aa3d52c2e42f0e9a690ce7607a2e61da4a5c/charset_normalizer-3.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd6280cf040f233bd7d3407b743b4b4c74f70e8e1c4199cb112a62c941c0772a", size = 322240, upload-time = "2026-07-07T14:32:36.236Z" }, - { url = "https://files.pythonhosted.org/packages/de/d1/b4319dc3229d8272fba305e206fc0a148e2de8d4087917ce62ae6382f359/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa99adc8f081b475a12843953db36831eaf83ec33eb46a90629ca6a5de45a616", size = 216475, upload-time = "2026-07-07T14:32:38.142Z" }, - { url = "https://files.pythonhosted.org/packages/80/33/6c99c1b3e6b8bf730e1bc809b9a2608f224145069114c479a2e9e1494346/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c1225416b463483160e4af85d5fc3a9690ccb53fd4b1865a6437825f5ede3209", size = 238670, upload-time = "2026-07-07T14:32:39.658Z" }, - { url = "https://files.pythonhosted.org/packages/7f/f4/ffbb83546e1f198ecc70ecd372b65cf2b50f9068b380abd67640f17a8e18/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:16d10d789dd9bcca1173c95af82c58433122564b7bc39385124be735a35cbe99", size = 233476, upload-time = "2026-07-07T14:32:41.155Z" }, - { url = "https://files.pythonhosted.org/packages/e8/5f/b98b8da398637b551e427e7be922bdec19177dc54d6811dcdaa503f23aac/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bb41182d93ea91f60b4bc8fbf4c820c69ef8a12ab2d917f3f1834f1acad07e8", size = 223817, upload-time = "2026-07-07T14:32:42.592Z" }, - { url = "https://files.pythonhosted.org/packages/36/31/a276bb2e66243072a3fd06fdcab9cbb61a305b02143d70d2bda21d888fa8/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:bcf74c1df76758a395bf0af608c04c82257523f55c9868b334f06270d0f2112b", size = 207974, upload-time = "2026-07-07T14:32:44.258Z" }, - { url = "https://files.pythonhosted.org/packages/5e/be/7ee4453d7e88dfbc4104ccd34900b9f2c7c17dac22881865fe0e82424a25/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b5314963fce9b0b12743891de876e724997864ee22aa496f903f426c7e2fa5b2", size = 221655, upload-time = "2026-07-07T14:32:45.64Z" }, - { url = "https://files.pythonhosted.org/packages/1d/85/181c652953eb5276d198f375b1dd641047392050098100a3a02d6534f657/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e9701d0049d92c16703a42771b98d560b95248949f23f8cf7b4eddd201814fb9", size = 219229, upload-time = "2026-07-07T14:32:47.376Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e7/aaf6da33fc9f4691cda8f7efbc9f69179d3d39ec8a4799baf273ee1d8db0/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:65a7ff3f705e57d392f7261b6d0550fe137c3019477431f1c355e0db0a7d3e15", size = 209704, upload-time = "2026-07-07T14:32:48.855Z" }, - { url = "https://files.pythonhosted.org/packages/63/01/f2fb3bd3a73be48b173ee0c6aa8d2497af97d5663a8c4c4b491de4c62f7a/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79580094b00d1789d1f93ea55bc43cb2f611910c72235b7657f3482ddcc1b22d", size = 226243, upload-time = "2026-07-07T14:32:50.239Z" }, - { url = "https://files.pythonhosted.org/packages/c4/02/c57a22739fe05246b0b5783b3bfb6afaac4eebb46f3ececdfb2f048f780e/charset_normalizer-3.4.9-cp310-cp310-win32.whl", hash = "sha256:432786d3561e69aeeae6c7e8648964ce0ad05736120135601f87ac26b9c83381", size = 150935, upload-time = "2026-07-07T14:32:51.676Z" }, - { url = "https://files.pythonhosted.org/packages/37/8d/ca39a7559a4797505530d084fd3a49a2c959efbbbff146302fb7be4e3b35/charset_normalizer-3.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:8c041122946b7ba21bb32c45b1aa57b1be35527690aeb3c5c234521085632eee", size = 162314, upload-time = "2026-07-07T14:32:53.193Z" }, - { url = "https://files.pythonhosted.org/packages/01/da/a44bd7a13d426e69e4894557106cd58669097bfad4a8681123b618fbfc5d/charset_normalizer-3.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:375b83ed0aecfce76c16d198fbc21f3b11b337d68662bea0a995046682a11419", size = 153075, upload-time = "2026-07-07T14:32:54.554Z" }, { url = "https://files.pythonhosted.org/packages/0b/e3/85ec501f206fb049259288c1f3506e53876937fb00edb47009348e66756b/charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5", size = 317075, upload-time = "2026-07-07T14:32:56.021Z" }, { url = "https://files.pythonhosted.org/packages/c3/69/2a5385192e67175f7d8bd5ce4f57c24bc956439adeae5c13a99aa28a53d1/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2", size = 213837, upload-time = "2026-07-07T14:32:57.78Z" }, { url = "https://files.pythonhosted.org/packages/b3/46/03ddc7da576d814fe0a36dd1f0fd3258e95404b4b2e3c026b7923d7e133f/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a", size = 235503, upload-time = "2026-07-07T14:32:59.205Z" }, @@ -610,38 +447,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] -[[package]] -name = "coloredlogs" -version = "15.0.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "humanfriendly" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520, upload-time = "2021-06-11T10:22:45.202Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018, upload-time = "2021-06-11T10:22:42.561Z" }, -] - [[package]] name = "coverage" version = "7.15.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f4/45/78dbf9604ee5b3db24efbf26bed1cb58862fb40480cba821963c69348751/coverage-7.15.3.tar.gz", hash = "sha256:ae7ea5a4614acf399ef0483c4cb34f8f8f01df848d8fcbe7d3ce0865733f1c4d", size = 935592, upload-time = "2026-08-02T18:50:17.006Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/d9/01d8e19b2c0e55903bfb540c9f6bd32326f1d5b2fcb5a7dd8648ae2dd9c5/coverage-7.15.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3a82b2ceee91ba353e59fe2436d8a9eae799ff9825e5385423ea205d693e2949", size = 222202, upload-time = "2026-08-02T18:47:25.951Z" }, - { url = "https://files.pythonhosted.org/packages/1e/92/1c23aeb83c7239af07061abc6e96f00f9b62deec8fae022cab1b353e6d46/coverage-7.15.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3088cce65e54c2eefc08e7e1ca0b0acec1e95e8cf084ac848599103ed0367f74", size = 222723, upload-time = "2026-08-02T18:47:28.359Z" }, - { url = "https://files.pythonhosted.org/packages/b9/76/186f60bae815941553b70877d814c45994db8198bb76933bd062c18ee437/coverage-7.15.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a65e09efb0b5ab21fc54a8a65c5b2e533c0a4c0d064af0259a005dc656dc1b13", size = 249461, upload-time = "2026-08-02T18:47:29.802Z" }, - { url = "https://files.pythonhosted.org/packages/88/70/53e010accfea3340905c5bb9207a2e461bba9b372621f1b88c1bd0e1392a/coverage-7.15.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b51f279a2477b0e1f288b98f141fd227acfdd1d3f0370400e473788879b47871", size = 251290, upload-time = "2026-08-02T18:47:31.445Z" }, - { url = "https://files.pythonhosted.org/packages/5b/13/d916056137fb6969e9d9f58ee11d1ef56778673843828315030733a3a0b6/coverage-7.15.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7835176988cbcf1f014db683bc33aa15e0558e412bf08deaa99757335b88df15", size = 253156, upload-time = "2026-08-02T18:47:33.033Z" }, - { url = "https://files.pythonhosted.org/packages/cf/72/0a4198d82e765f3351a91714d42526eb765e5c97776f7674489acbe7d062/coverage-7.15.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f24896dc8863167f6732f4142f5d37e6195eccc8fe5fe528d35d49597d29fdb3", size = 255068, upload-time = "2026-08-02T18:47:34.852Z" }, - { url = "https://files.pythonhosted.org/packages/00/d9/ebe4e0751e3637d87162887d0d3cdf4716f96782ab6face09a295e74cba4/coverage-7.15.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9490d43e5d041fdf376770a886a29722adb05f6b9c21a65c48c81fc8f1c33fd7", size = 250142, upload-time = "2026-08-02T18:47:36.588Z" }, - { url = "https://files.pythonhosted.org/packages/a2/a2/12977c74fcf92f9b1da45fb9576c5f593a2c47bde04e937a8bb32dd56bfa/coverage-7.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:225e359bd5dedaff6d68e36091af20555866c557d968167308b677379bf575c3", size = 251195, upload-time = "2026-08-02T18:47:38.168Z" }, - { url = "https://files.pythonhosted.org/packages/2f/c8/42bd9aa40386c0fbcc7af221ab4737dad10d27bb4971ca2783676619a79b/coverage-7.15.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:22119e2e3b2ac5ac024d50131fdd4b22ab4c6cf8aa2fc792cce73c0d94c5812d", size = 249200, upload-time = "2026-08-02T18:47:39.773Z" }, - { url = "https://files.pythonhosted.org/packages/0a/52/f1ce0dd8a2ec5c3911f1bc98b859be09cc4bbd705ed74ceb905729837c79/coverage-7.15.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:12d555badc462b0f6037ce8bec8b4af8d71f90eb55b57d0a358731f7ee7883e2", size = 253013, upload-time = "2026-08-02T18:47:41.372Z" }, - { url = "https://files.pythonhosted.org/packages/fd/2c/9a642c4cf7b6992b2eba75359b6cb548bd437001d6083fd0ffe492b80d38/coverage-7.15.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c4e2cf9cf774939b3dc581c6e31dfe7e8d7608b24f0f17524d6161f8235c3d2c", size = 249470, upload-time = "2026-08-02T18:47:42.997Z" }, - { url = "https://files.pythonhosted.org/packages/89/32/271d85639ac5de099046f7418e850047b1e964f893535128997b5cddde8d/coverage-7.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cea1b3e19d710f67e2ba9ce0b0b51032c2a9b4808a65ced48ddf336ef7e58058", size = 250073, upload-time = "2026-08-02T18:47:44.576Z" }, - { url = "https://files.pythonhosted.org/packages/15/71/6216430095c5437f83d7bfa7c1adb0965e26ada88d9fff49bf55e2cab154/coverage-7.15.3-cp310-cp310-win32.whl", hash = "sha256:25c77560309f157e7b7ee8fe0bf78d047ba900b7ae42f0e50e559305b366fea2", size = 224263, upload-time = "2026-08-02T18:47:46.078Z" }, - { url = "https://files.pythonhosted.org/packages/53/8a/f1032fb2714c28fedf00d73562c4bb9f713fa8a90593ed577bbb708a7de1/coverage-7.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:179fbf847e6c3d90ea71bfd570fe57f1ddb1c51474754894871c1e11099efaa0", size = 224886, upload-time = "2026-08-02T18:47:47.668Z" }, { url = "https://files.pythonhosted.org/packages/b3/9c/c8a3a923c24f631695cea2d5e2f02e776bc0af6e03800626e13a6c05a615/coverage-7.15.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5f3f854ab4599d98f7799ac9b91e34e8ec9ebc9a6372ee8c1f3413a68cc8b5e9", size = 222328, upload-time = "2026-08-02T18:47:49.228Z" }, { url = "https://files.pythonhosted.org/packages/92/51/dda77f34cbd2513d6ffb898c901d19e9ca55f48c0cbc4a1eb173a97d157a/coverage-7.15.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75268348fee1f199653b8a846262aec5581c6bb008c4f58824959fb708cc688f", size = 222832, upload-time = "2026-08-02T18:47:51.219Z" }, { url = "https://files.pythonhosted.org/packages/78/59/e0faafc4c6e23bd76c76148875ee9ec5781b8f1cd62cea2bc4ca0f0f0e5d/coverage-7.15.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:21081739f6264cc594cad2d42b62befbd17633824022866c68720eb0c4b8d6b4", size = 253250, upload-time = "2026-08-02T18:47:52.737Z" }, @@ -731,7 +542,6 @@ version = "50.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/de/41/6cbdcf9142d00fe82836fbb51e503e58088575cf7a0fe1dbff6695bf0840/cryptography-50.0.0.tar.gz", hash = "sha256:eeac2acb5a20ed25e0ad6d1df9891a520b78b404266b6d11778f25d5d691a6c9", size = 880201, upload-time = "2026-07-31T14:25:10.11Z" } wheels = [ @@ -787,11 +597,9 @@ name = "cuda-bindings" version = "13.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cuda-pathfinder" }, + { name = "cuda-pathfinder", marker = "sys_platform != 'darwin'" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/21/8464d133752951c154feafb3b65c297e7d80f301183d220bec4c830f1441/cuda_bindings-13.3.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:120fcc53d57903df529c3486962c56528cba5b7d6c57c99537320ed9922c8b86", size = 6073403, upload-time = "2026-05-29T23:11:36.22Z" }, - { url = "https://files.pythonhosted.org/packages/a8/1f/5ef51f5fbaa5d4d3201bb3d7555af028ec1aa4416275ccbf73c9e34e3d2d/cuda_bindings-13.3.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9851b0caa8bfd3bc6fa054eaf57bea7c8e9c3a62db2d2621224677f49f3c53d0", size = 6675244, upload-time = "2026-05-29T23:11:38.664Z" }, { url = "https://files.pythonhosted.org/packages/51/6b/457ca12dad3ee9bfcc9a545cfd6b64b359ba49de40f776f6e028e678f262/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5879712accf6e14bb01aa5e67440eb84998b8d104b509cc7a6dc0b8f656a474", size = 6053539, upload-time = "2026-05-29T23:11:43.19Z" }, { url = "https://files.pythonhosted.org/packages/95/7a/c5e3c34a409b148f5c0f5a4ea374158f95d488862c1dffedf9aa5c639df9/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04436a9364059c84b8f9636f359eccda1cf814341f5b670c71d80d2f79dbc708", size = 6674166, upload-time = "2026-05-29T23:11:45.478Z" }, { url = "https://files.pythonhosted.org/packages/ce/67/5e7dba1ba576dd73da5dee894ca076ca5e959450dfff66d6d510a255d1f7/cuda_bindings-13.3.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7855c4868aabc0cfae28abbe83d56734bdfbd08f08fc234ac1912a12858bf49", size = 6025351, upload-time = "2026-05-29T23:11:49.685Z" }, @@ -822,52 +630,43 @@ wheels = [ [package.optional-dependencies] cublas = [ - { name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, - { name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, ] cudart = [ - { name = "nvidia-cuda-runtime", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cuda-runtime", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, ] cufft = [ - { name = "nvidia-cufft", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, - { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cufft", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, ] cufile = [ - { name = "nvidia-cufile", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cufile", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] cupti = [ - { name = "nvidia-cuda-cupti", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cuda-cupti", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, ] curand = [ - { name = "nvidia-curand", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-curand", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, ] cusolver = [ - { name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, - { name = "nvidia-cusolver", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, - { name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, - { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-cusolver", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, ] cusparse = [ - { name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, - { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, ] nvjitlink = [ - { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, ] nvrtc = [ - { name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, ] nvtx = [ - { name = "nvidia-nvtx", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, -] - -[[package]] -name = "cvss" -version = "3.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a5/f6/1f7d315de23f39bbc32b94bb6b33a1b6124856037bfaa3f8bdb1a49584fa/cvss-3.6.tar.gz", hash = "sha256:f21d18224efcd3c01b44ff1b37dec2e3208d29a6d0ce6c87a599c73c21ee1a99", size = 30047, upload-time = "2025-08-04T10:50:13.753Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/63/6d/fe2c65b94a28ae0481dc254e8cd664b82390069003bea945076d8a445f2b/cvss-3.6-py2.py3-none-any.whl", hash = "sha256:e342c6ad9c7eb69d2aebbbc2768a03cabd57eb947c806e145de5b936219833ea", size = 31154, upload-time = "2025-08-04T10:50:12.328Z" }, + { name = "nvidia-nvtx", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, ] [[package]] @@ -881,10 +680,10 @@ dependencies = [ { name = "httpx" }, { name = "huggingface-hub" }, { name = "multiprocess" }, - { name = "numpy" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "packaging" }, - { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or python_full_version >= '3.14'" }, - { name = "pandas", version = "3.0.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "pandas" }, { name = "pyarrow" }, { name = "pyyaml" }, { name = "requests" }, @@ -907,8 +706,7 @@ dependencies = [ { name = "ftfy" }, { name = "imageio", extra = ["ffmpeg"] }, { name = "modelscope" }, - { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or python_full_version >= '3.14'" }, - { name = "pandas", version = "3.0.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "pandas" }, { name = "peft" }, { name = "protobuf" }, { name = "safetensors" }, @@ -931,7 +729,8 @@ dependencies = [ { name = "httpx" }, { name = "huggingface-hub" }, { name = "importlib-metadata" }, - { name = "numpy" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "pillow" }, { name = "regex" }, { name = "requests" }, @@ -951,15 +750,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019, upload-time = "2026-01-19T02:36:55.663Z" }, ] -[[package]] -name = "dnspython" -version = "2.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, -] - [[package]] name = "einops" version = "0.8.2" @@ -969,31 +759,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/09/f8d8f8f31e4483c10a906437b4ce31bdf3d6d417b73fe33f1a8b59e34228/einops-0.8.2-py3-none-any.whl", hash = "sha256:54058201ac7087911181bfec4af6091bb59380360f069276601256a76af08193", size = 65638, upload-time = "2026-01-26T04:13:18.546Z" }, ] -[[package]] -name = "email-validator" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "dnspython" }, - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238, upload-time = "2025-08-26T13:09:06.831Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" }, -] - -[[package]] -name = "exceptiongroup" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, -] - [[package]] name = "execnet" version = "2.1.2" @@ -1026,22 +791,6 @@ version = "1.8.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/4a/557715d5047da48d54e659203b9335be7bfaafda2c3f627b7c47e0b3aaf3/frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011", size = 86230, upload-time = "2025-10-06T05:35:23.699Z" }, - { url = "https://files.pythonhosted.org/packages/a2/fb/c85f9fed3ea8fe8740e5b46a59cc141c23b842eca617da8876cfce5f760e/frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565", size = 49621, upload-time = "2025-10-06T05:35:25.341Z" }, - { url = "https://files.pythonhosted.org/packages/63/70/26ca3f06aace16f2352796b08704338d74b6d1a24ca38f2771afbb7ed915/frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad", size = 49889, upload-time = "2025-10-06T05:35:26.797Z" }, - { url = "https://files.pythonhosted.org/packages/5d/ed/c7895fd2fde7f3ee70d248175f9b6cdf792fb741ab92dc59cd9ef3bd241b/frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2", size = 219464, upload-time = "2025-10-06T05:35:28.254Z" }, - { url = "https://files.pythonhosted.org/packages/6b/83/4d587dccbfca74cb8b810472392ad62bfa100bf8108c7223eb4c4fa2f7b3/frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186", size = 221649, upload-time = "2025-10-06T05:35:29.454Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c6/fd3b9cd046ec5fff9dab66831083bc2077006a874a2d3d9247dea93ddf7e/frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e", size = 219188, upload-time = "2025-10-06T05:35:30.951Z" }, - { url = "https://files.pythonhosted.org/packages/ce/80/6693f55eb2e085fc8afb28cf611448fb5b90e98e068fa1d1b8d8e66e5c7d/frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450", size = 231748, upload-time = "2025-10-06T05:35:32.101Z" }, - { url = "https://files.pythonhosted.org/packages/97/d6/e9459f7c5183854abd989ba384fe0cc1a0fb795a83c033f0571ec5933ca4/frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef", size = 236351, upload-time = "2025-10-06T05:35:33.834Z" }, - { url = "https://files.pythonhosted.org/packages/97/92/24e97474b65c0262e9ecd076e826bfd1d3074adcc165a256e42e7b8a7249/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4", size = 218767, upload-time = "2025-10-06T05:35:35.205Z" }, - { url = "https://files.pythonhosted.org/packages/ee/bf/dc394a097508f15abff383c5108cb8ad880d1f64a725ed3b90d5c2fbf0bb/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff", size = 235887, upload-time = "2025-10-06T05:35:36.354Z" }, - { url = "https://files.pythonhosted.org/packages/40/90/25b201b9c015dbc999a5baf475a257010471a1fa8c200c843fd4abbee725/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c", size = 228785, upload-time = "2025-10-06T05:35:37.949Z" }, - { url = "https://files.pythonhosted.org/packages/84/f4/b5bc148df03082f05d2dd30c089e269acdbe251ac9a9cf4e727b2dbb8a3d/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f", size = 230312, upload-time = "2025-10-06T05:35:39.178Z" }, - { url = "https://files.pythonhosted.org/packages/db/4b/87e95b5d15097c302430e647136b7d7ab2398a702390cf4c8601975709e7/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7", size = 217650, upload-time = "2025-10-06T05:35:40.377Z" }, - { url = "https://files.pythonhosted.org/packages/e5/70/78a0315d1fea97120591a83e0acd644da638c872f142fd72a6cebee825f3/frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a", size = 39659, upload-time = "2025-10-06T05:35:41.863Z" }, - { url = "https://files.pythonhosted.org/packages/66/aa/3f04523fb189a00e147e60c5b2205126118f216b0aa908035c45336e27e4/frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6", size = 43837, upload-time = "2025-10-06T05:35:43.205Z" }, - { url = "https://files.pythonhosted.org/packages/39/75/1135feecdd7c336938bd55b4dc3b0dfc46d85b9be12ef2628574b28de776/frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e", size = 39989, upload-time = "2025-10-06T05:35:44.596Z" }, { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, @@ -1248,27 +997,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/97/bb/63a644c75b545f3ff394b822e9bd1c4a9586489c618b77a4d8a44a33a23b/huggingface_hub-1.26.0-py3-none-any.whl", hash = "sha256:e8cca670caa5d8dfa7e45bf45e86b466698198cd8150c021bcdb4a86b9252364", size = 780357, upload-time = "2026-07-30T14:12:01.998Z" }, ] -[[package]] -name = "humanfriendly" -version = "10.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyreadline3", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702, upload-time = "2021-09-17T21:40:43.31Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794, upload-time = "2021-09-17T21:40:39.897Z" }, -] - -[[package]] -name = "humanize" -version = "4.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0a/ea/13a1ef3c12d12662905801495283530251918b70d62d368f1d2e0272c70d/humanize-4.16.0.tar.gz", hash = "sha256:7dc2244a2f84a4bfb1d36c37bac80cd78e35cdc5c119206d87b018e1445f3a3f", size = 89515, upload-time = "2026-06-30T16:17:29.859Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/aa/0b7365d30fed43e7a3449aba1fe20a0a7174d9cf13e282af4e69ac825441/humanize-4.16.0-py3-none-any.whl", hash = "sha256:353eb2f34c09d098b2880eee8bef21832eae6d174f48c5762fff7e5fcb74d01d", size = 137209, upload-time = "2026-06-30T16:17:28.36Z" }, -] - [[package]] name = "idna" version = "3.18" @@ -1283,7 +1011,8 @@ name = "imageio" version = "2.37.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "pillow" }, ] sdist = { url = "https://files.pythonhosted.org/packages/48/62/aa770a9307508d2a2a2c62d536a49347bffe9e55322db27838d3c93d0b07/imageio-2.37.4.tar.gz", hash = "sha256:e45cbc5e83502047fb138f7f585f7f105a136a57eea5f4b3cfc6ce1b52720bd3", size = 390173, upload-time = "2026-07-20T05:26:11.369Z" } @@ -1323,19 +1052,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl", hash = "sha256:2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7", size = 27789, upload-time = "2026-03-20T06:42:55.665Z" }, ] -[[package]] -name = "inflect" -version = "7.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "more-itertools" }, - { name = "typeguard" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/78/c6/943357d44a21fd995723d07ccaddd78023eace03c1846049a2645d4324a3/inflect-7.5.0.tar.gz", hash = "sha256:faf19801c3742ed5a05a8ce388e0d8fe1a07f8d095c82201eb904f5d27ad571f", size = 73751, upload-time = "2024-12-28T17:11:18.897Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/eb/427ed2b20a38a4ee29f24dbe4ae2dafab198674fe9a85e3d6adf9e5f5f41/inflect-7.5.0-py3-none-any.whl", hash = "sha256:2aea70e5e70c35d8350b8097396ec155ffd68def678c7ff97f51aa69c1d92344", size = 35197, upload-time = "2024-12-28T17:11:15.931Z" }, -] - [[package]] name = "iniconfig" version = "2.3.0" @@ -1350,11 +1066,11 @@ name = "invisible-watermark" version = "0.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "opencv-python" }, { name = "pillow" }, - { name = "pywavelets", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "pywavelets", version = "1.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pywavelets" }, { name = "torch" }, ] wheels = [ @@ -1378,15 +1094,15 @@ name = "lightning" version = "2.6.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "fsspec", extra = ["http"] }, - { name = "lightning-utilities" }, - { name = "packaging" }, - { name = "pytorch-lightning" }, - { name = "pyyaml" }, - { name = "torch" }, - { name = "torchmetrics" }, - { name = "tqdm" }, - { name = "typing-extensions" }, + { name = "fsspec", extra = ["http"], marker = "python_full_version < '3.13'" }, + { name = "lightning-utilities", marker = "python_full_version < '3.13'" }, + { name = "packaging", marker = "python_full_version < '3.13'" }, + { name = "pytorch-lightning", marker = "python_full_version < '3.13'" }, + { name = "pyyaml", marker = "python_full_version < '3.13'" }, + { name = "torch", marker = "python_full_version < '3.13'" }, + { name = "torchmetrics", marker = "python_full_version < '3.13'" }, + { name = "tqdm", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c9/1d/83be8536bec71a0173e762a9a1fd92a24a5ad0d0f74c59550c3c4e6c103b/lightning-2.6.5.tar.gz", hash = "sha256:16a30310ed69afde3748491feb5d13508908effd70390d2bfc203dc0812a4b4a", size = 659201, upload-time = "2026-05-27T14:33:41.806Z" } wheels = [ @@ -1398,8 +1114,8 @@ name = "lightning-utilities" version = "0.15.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "packaging" }, - { name = "typing-extensions" }, + { name = "packaging", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f1/45/7fa8f56b17dc0f0a41ec70dd307ecd6787254483549843bef4c30ab5adce/lightning_utilities-0.15.3.tar.gz", hash = "sha256:792ae0204c79f6859721ac7f386c237a33b0ed06ba775009cb894e010a842033", size = 33553, upload-time = "2026-02-22T14:48:53.348Z" } wheels = [ @@ -1424,17 +1140,6 @@ version = "3.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, - { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, - { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, - { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, - { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, - { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, - { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, - { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, - { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, @@ -1545,15 +1250,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/be/3b0e14c296a622c920287fc6d2c1b5a8668c8e0d98662f7b2bd8f3fcc779/modelscope_hub-0.2.0-py3-none-any.whl", hash = "sha256:84668c80c1d36d1fb1ea1ce17b707d77e044b92255288618c3b7de5b13834418", size = 156023, upload-time = "2026-08-01T15:00:07.335Z" }, ] -[[package]] -name = "more-itertools" -version = "11.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/de/1d/f4da6f02cdffe04d6362210b807146a26044c88d839208aec273bb0d9184/more_itertools-11.1.0.tar.gz", hash = "sha256:48e8f4d9e7e5878571ecf6f2b4e57634f93cd474cc8cfbd2376f2d11b396e30d", size = 145772, upload-time = "2026-05-22T14:14:29.909Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl", hash = "sha256:4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192", size = 72226, upload-time = "2026-05-22T14:14:28.824Z" }, -] - [[package]] name = "mpmath" version = "1.3.0" @@ -1567,29 +1263,8 @@ wheels = [ name = "multidict" version = "6.7.1" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/0b/19348d4c98980c4851d2f943f8ebafdece2ae7ef737adcfa5994ce8e5f10/multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5", size = 77176, upload-time = "2026-01-26T02:42:59.784Z" }, - { url = "https://files.pythonhosted.org/packages/ef/04/9de3f8077852e3d438215c81e9b691244532d2e05b4270e89ce67b7d103c/multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8", size = 44996, upload-time = "2026-01-26T02:43:01.674Z" }, - { url = "https://files.pythonhosted.org/packages/31/5c/08c7f7fe311f32e83f7621cd3f99d805f45519cd06fafb247628b861da7d/multidict-6.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdea2e7b2456cfb6694fb113066fd0ec7ea4d67e3a35e1f4cbeea0b448bf5872", size = 44631, upload-time = "2026-01-26T02:43:03.169Z" }, - { url = "https://files.pythonhosted.org/packages/b7/7f/0e3b1390ae772f27501199996b94b52ceeb64fe6f9120a32c6c3f6b781be/multidict-6.7.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17207077e29342fdc2c9a82e4b306f1127bf1ea91f8b71e02d4798a70bb99991", size = 242561, upload-time = "2026-01-26T02:43:04.733Z" }, - { url = "https://files.pythonhosted.org/packages/dd/f4/8719f4f167586af317b69dd3e90f913416c91ca610cac79a45c53f590312/multidict-6.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4f49cb5661344764e4c7c7973e92a47a59b8fc19b6523649ec9dc4960e58a03", size = 242223, upload-time = "2026-01-26T02:43:06.695Z" }, - { url = "https://files.pythonhosted.org/packages/47/ab/7c36164cce64a6ad19c6d9a85377b7178ecf3b89f8fd589c73381a5eedfd/multidict-6.7.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a9fc4caa29e2e6ae408d1c450ac8bf19892c5fca83ee634ecd88a53332c59981", size = 222322, upload-time = "2026-01-26T02:43:08.472Z" }, - { url = "https://files.pythonhosted.org/packages/f5/79/a25add6fb38035b5337bc5734f296d9afc99163403bbcf56d4170f97eb62/multidict-6.7.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c5f0c21549ab432b57dcc82130f388d84ad8179824cc3f223d5e7cfbfd4143f6", size = 254005, upload-time = "2026-01-26T02:43:10.127Z" }, - { url = "https://files.pythonhosted.org/packages/4a/7b/64a87cf98e12f756fc8bd444b001232ffff2be37288f018ad0d3f0aae931/multidict-6.7.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7dfb78d966b2c906ae1d28ccf6e6712a3cd04407ee5088cd276fe8cb42186190", size = 251173, upload-time = "2026-01-26T02:43:11.731Z" }, - { url = "https://files.pythonhosted.org/packages/4b/ac/b605473de2bb404e742f2cc3583d12aedb2352a70e49ae8fce455b50c5aa/multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b0d9b91d1aa44db9c1f1ecd0d9d2ae610b2f4f856448664e01a3b35899f3f92", size = 243273, upload-time = "2026-01-26T02:43:13.063Z" }, - { url = "https://files.pythonhosted.org/packages/03/65/11492d6a0e259783720f3bc1d9ea55579a76f1407e31ed44045c99542004/multidict-6.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dd96c01a9dcd4889dcfcf9eb5544ca0c77603f239e3ffab0524ec17aea9a93ee", size = 238956, upload-time = "2026-01-26T02:43:14.843Z" }, - { url = "https://files.pythonhosted.org/packages/5f/a7/7ee591302af64e7c196fb63fe856c788993c1372df765102bd0448e7e165/multidict-6.7.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:067343c68cd6612d375710f895337b3a98a033c94f14b9a99eff902f205424e2", size = 233477, upload-time = "2026-01-26T02:43:16.025Z" }, - { url = "https://files.pythonhosted.org/packages/9c/99/c109962d58756c35fd9992fed7f2355303846ea2ff054bb5f5e9d6b888de/multidict-6.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5884a04f4ff56c6120f6ccf703bdeb8b5079d808ba604d4d53aec0d55dc33568", size = 243615, upload-time = "2026-01-26T02:43:17.84Z" }, - { url = "https://files.pythonhosted.org/packages/d5/5f/1973e7c771c86e93dcfe1c9cc55a5481b610f6614acfc28c0d326fe6bfad/multidict-6.7.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8affcf1c98b82bc901702eb73b6947a1bfa170823c153fe8a47b5f5f02e48e40", size = 249930, upload-time = "2026-01-26T02:43:19.06Z" }, - { url = "https://files.pythonhosted.org/packages/5d/a5/f170fc2268c3243853580203378cd522446b2df632061e0a5409817854c7/multidict-6.7.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0d17522c37d03e85c8098ec8431636309b2682cf12e58f4dbc76121fb50e4962", size = 243807, upload-time = "2026-01-26T02:43:20.286Z" }, - { url = "https://files.pythonhosted.org/packages/de/01/73856fab6d125e5bc652c3986b90e8699a95e84b48d72f39ade6c0e74a8c/multidict-6.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24c0cf81544ca5e17cfcb6e482e7a82cd475925242b308b890c9452a074d4505", size = 239103, upload-time = "2026-01-26T02:43:21.508Z" }, - { url = "https://files.pythonhosted.org/packages/e7/46/f1220bd9944d8aa40d8ccff100eeeee19b505b857b6f603d6078cb5315b0/multidict-6.7.1-cp310-cp310-win32.whl", hash = "sha256:d82dd730a95e6643802f4454b8fdecdf08667881a9c5670db85bc5a56693f122", size = 41416, upload-time = "2026-01-26T02:43:22.703Z" }, - { url = "https://files.pythonhosted.org/packages/68/00/9b38e272a770303692fc406c36e1a4c740f401522d5787691eb38a8925a8/multidict-6.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cf37cbe5ced48d417ba045aca1b21bafca67489452debcde94778a576666a1df", size = 46022, upload-time = "2026-01-26T02:43:23.77Z" }, - { url = "https://files.pythonhosted.org/packages/64/65/d8d42490c02ee07b6bbe00f7190d70bb4738b3cce7629aaf9f213ef730dd/multidict-6.7.1-cp310-cp310-win_arm64.whl", hash = "sha256:59bc83d3f66b41dac1e7460aac1d196edc70c9ba3094965c467715a70ecb46db", size = 43238, upload-time = "2026-01-26T02:43:24.882Z" }, { url = "https://files.pythonhosted.org/packages/ce/f1/a90635c4f88fb913fbf4ce660b83b7445b7a02615bda034b2f8eb38fd597/multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d", size = 76626, upload-time = "2026-01-26T02:43:26.485Z" }, { url = "https://files.pythonhosted.org/packages/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e", size = 44706, upload-time = "2026-01-26T02:43:27.607Z" }, { url = "https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855", size = 44356, upload-time = "2026-01-26T02:43:28.661Z" }, @@ -1710,9 +1385,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/a2/f2/e783ac7f2aeeed14e9e12801f22529cc7e6b7ab80928d6dcce4e9f00922d/multiprocess-0.70.19.tar.gz", hash = "sha256:952021e0e6c55a4a9fe4cd787895b86e239a40e76802a789d6305398d3975897", size = 2079989, upload-time = "2026-01-19T06:47:39.744Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/b6/10832f96b499690854e574360be342a282f5f7dba58eff791299ff6c0637/multiprocess-0.70.19-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:02e5c35d7d6cd2bdc89c1858867f7bde4012837411023a4696c148c1bdd7c80e", size = 135131, upload-time = "2026-01-19T06:47:20.479Z" }, - { url = "https://files.pythonhosted.org/packages/99/50/faef2d8106534b0dc4a0b772668a1a99682696ebf17d3c0f13f2ed6a656a/multiprocess-0.70.19-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:79576c02d1207ec405b00cabf2c643c36070800cca433860e14539df7818b2aa", size = 135131, upload-time = "2026-01-19T06:47:21.879Z" }, - { url = "https://files.pythonhosted.org/packages/94/b1/0b71d18b76bf423c2e8ee00b31db37d17297ab3b4db44e188692afdca628/multiprocess-0.70.19-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c6b6d78d43a03b68014ca1f0b7937d965393a670c5de7c29026beb2258f2f896", size = 135134, upload-time = "2026-01-19T06:47:23.262Z" }, { url = "https://files.pythonhosted.org/packages/7e/aa/714635c727dbfc251139226fa4eaf1b07f00dc12d9cd2eb25f931adaf873/multiprocess-0.70.19-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1bbf1b69af1cf64cd05f65337d9215b88079ec819cd0ea7bac4dab84e162efe7", size = 144743, upload-time = "2026-01-19T06:47:24.562Z" }, { url = "https://files.pythonhosted.org/packages/0f/e1/155f6abf5e6b5d9cef29b6d0167c180846157a4aca9b9bee1a217f67c959/multiprocess-0.70.19-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5be9ec7f0c1c49a4f4a6fd20d5dda4aeabc2d39a50f4ad53720f1cd02b3a7c2e", size = 144738, upload-time = "2026-01-19T06:47:26.636Z" }, { url = "https://files.pythonhosted.org/packages/af/cb/f421c2869d75750a4f32301cc20c4b63fab6376e9a75c8e5e655bdeb3d9b/multiprocess-0.70.19-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1c3dce098845a0db43b32a0b76a228ca059a668071cfeaa0f40c36c0b1585d45", size = 144741, upload-time = "2026-01-19T06:47:27.985Z" }, @@ -1724,41 +1396,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/82/69e539c4c2027f1e1697e09aaa2449243085a0edf81ae2c6341e84d769b6/multiprocess-0.70.19-py39-none-any.whl", hash = "sha256:0d4b4397ed669d371c81dcd1ef33fd384a44d6c3de1bd0ca7ac06d837720d3c5", size = 133477, upload-time = "2026-01-19T06:47:38.619Z" }, ] -[[package]] -name = "networkx" -version = "3.4.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11' and sys_platform == 'darwin'", - "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')", -] -sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" }, -] - [[package]] name = "networkx" version = "3.6.1" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'darwin'", - "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'darwin'", - "python_full_version >= '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "(python_full_version >= '3.14' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", - "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", - "(python_full_version >= '3.12' and python_full_version < '3.14' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", - "python_full_version == '3.11.*' and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and sys_platform == 'win32'", - "python_full_version == '3.11.*' and sys_platform == 'emscripten'", - "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", -] sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, @@ -1777,16 +1418,20 @@ wheels = [ name = "numpy" version = "1.26.4" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.12.*' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "(python_full_version == '3.12.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", + "python_full_version < '3.12' and sys_platform == 'darwin'", + "python_full_version < '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version < '3.12' and sys_platform == 'win32'", + "python_full_version < '3.12' and sys_platform == 'emscripten'", + "(python_full_version < '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", +] sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129, upload-time = "2024-02-06T00:26:44.495Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/94/ace0fdea5241a27d13543ee117cbc65868e82213fb31a8eb7fe9ff23f313/numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0", size = 20631468, upload-time = "2024-02-05T23:48:01.194Z" }, - { url = "https://files.pythonhosted.org/packages/20/f7/b24208eba89f9d1b58c1668bc6c8c4fd472b20c45573cb767f59d49fb0f6/numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a", size = 13966411, upload-time = "2024-02-05T23:48:29.038Z" }, - { url = "https://files.pythonhosted.org/packages/fc/a5/4beee6488160798683eed5bdb7eead455892c3b4e1f78d79d8d3f3b084ac/numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4", size = 14219016, upload-time = "2024-02-05T23:48:54.098Z" }, - { url = "https://files.pythonhosted.org/packages/4b/d7/ecf66c1cd12dc28b4040b15ab4d17b773b87fa9d29ca16125de01adb36cd/numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f", size = 18240889, upload-time = "2024-02-05T23:49:25.361Z" }, - { url = "https://files.pythonhosted.org/packages/24/03/6f229fe3187546435c4f6f89f6d26c129d4f5bed40552899fcf1f0bf9e50/numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a", size = 13876746, upload-time = "2024-02-05T23:49:51.983Z" }, - { url = "https://files.pythonhosted.org/packages/39/fe/39ada9b094f01f5a35486577c848fe274e374bbf8d8f472e1423a0bbd26d/numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2", size = 18078620, upload-time = "2024-02-05T23:50:22.515Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ef/6ad11d51197aad206a9ad2286dc1aac6a378059e06e8cf22cd08ed4f20dc/numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07", size = 5972659, upload-time = "2024-02-05T23:50:35.834Z" }, - { url = "https://files.pythonhosted.org/packages/19/77/538f202862b9183f54108557bfda67e17603fc560c384559e769321c9d92/numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5", size = 15808905, upload-time = "2024-02-05T23:51:03.701Z" }, { url = "https://files.pythonhosted.org/packages/11/57/baae43d14fe163fa0e4c47f307b6b2511ab8d7d30177c491960504252053/numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", size = 20630554, upload-time = "2024-02-05T23:51:50.149Z" }, { url = "https://files.pythonhosted.org/packages/1a/2e/151484f49fd03944c4a3ad9c418ed193cfd02724e138ac8a9505d056c582/numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", size = 13997127, upload-time = "2024-02-05T23:52:15.314Z" }, { url = "https://files.pythonhosted.org/packages/79/ae/7e5b85136806f9dadf4878bf73cf223fe5c2636818ba3ab1c585d0403164/numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", size = 14222994, upload-time = "2024-02-05T23:52:47.569Z" }, @@ -1805,12 +1450,75 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754, upload-time = "2024-02-05T23:58:36.364Z" }, ] +[[package]] +name = "numpy" +version = "2.5.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and sys_platform == 'darwin'", + "python_full_version >= '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.13.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "(python_full_version >= '3.14' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "(python_full_version == '3.13.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/80/db0b4559e57ec36362bedbb05530a87fafbcb6067708c946967a41d449e7/numpy-2.5.2.tar.gz", hash = "sha256:d482d171c406ae88c5b19cad3b6a1c4c5209f886ab74bc44c2c865c23f52d860", size = 20773161, upload-time = "2026-08-09T13:48:27.962Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/72/dccb0aaf40972777283303919f613964227266d0c13adebb79ac124f1c3e/numpy-2.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:14e373cfc6387177e8409dac3c7159be8eb05cd77096cd7c950268b86f62831c", size = 16891693, upload-time = "2026-08-09T13:44:51.702Z" }, + { url = "https://files.pythonhosted.org/packages/60/2e/b5aee50a1f74ac815cf8331812cb8251e29024025de462e0c047641c614c/numpy-2.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bbd96c833ecc8cc069ce518078fc8c60cb9cbfb0fea5b7a803ad65035596d03", size = 11903109, upload-time = "2026-08-09T13:44:55.501Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f4/29e78102a80601cf034d4e9767022cffeca2c3b4c926e1754572ca95593d/numpy-2.5.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:6e8172ddfcf5cf74b811d372b570b83c60bd2de87a6fbfbebdadb4a9bd9c6cbb", size = 5350202, upload-time = "2026-08-09T13:44:58.401Z" }, + { url = "https://files.pythonhosted.org/packages/11/4b/dcd3b7eadaf4035d2c7a4289d232523a6964f602598ef7674e4bd7291f93/numpy-2.5.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f188481f1669e26f62b701e8205d19e460fa4a9b52a1414ba382330e4a3414", size = 6687736, upload-time = "2026-08-09T13:45:00.813Z" }, + { url = "https://files.pythonhosted.org/packages/e5/21/4947e0e9d6c9fc2e2ff15b8949049ee44f63adb9cacc729ab8793f97e712/numpy-2.5.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ee9c4eeb8454b3660a8b53493563c3e121c2fc94fbd72b848ef814ed7b676a9", size = 15612696, upload-time = "2026-08-09T13:45:04.151Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5f/62d28cf019460c7f1394105b4d49d9911a9c444cb77ab0bd95a204c5a6de/numpy-2.5.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3cdec01fa790a186d430433fdd4d4ffb70eed6f0eeb4bf05c8dbe2dce0a9bcb8", size = 16722264, upload-time = "2026-08-09T13:45:07.714Z" }, + { url = "https://files.pythonhosted.org/packages/14/25/3f0be4c1b9fdf5dd5e708a6806978564d7c46a055c000496309ff2a2f8af/numpy-2.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7999d4ddb0c4025018373fd787510d46e04c769467af22869707b3c1cfd459ab", size = 16974396, upload-time = "2026-08-09T13:45:11.316Z" }, + { url = "https://files.pythonhosted.org/packages/22/72/6262cbdeeb45da9d971e40715f579d791603ba8ec0b5e2db1ac55454421d/numpy-2.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c1f017dc0875c9209d219f97feceb7d54c2661bb243deb4114478e1295808af7", size = 18476044, upload-time = "2026-08-09T13:45:14.869Z" }, + { url = "https://files.pythonhosted.org/packages/36/33/29208b8b075bde62d26a81d14b358c42b0f69b6cabd98d4ff97f37f22b05/numpy-2.5.2-cp312-cp312-win32.whl", hash = "sha256:d6a48072864e3324e194a8fbb3c657bcc5b5c869dbc64c9537b1d5c862572c0a", size = 6072817, upload-time = "2026-08-09T13:45:17.867Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/87fea2769fe1c47c1b5b01d8310772c9d1a85d485de7cf386ef7a3332b02/numpy-2.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:28ac63476ec7651484215ee7fa15a1f78b57c14621f01e392afe17b9a1390ce4", size = 12464674, upload-time = "2026-08-09T13:45:20.734Z" }, + { url = "https://files.pythonhosted.org/packages/14/52/032b97e00461ab0809bbe4c588b035620e5a14b8cdee47ecddefc7b17d33/numpy-2.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:27650bb0e7140fa3d37b9923b4803645e0b125d190f326eecfd3f4dad8e8ade1", size = 10397131, upload-time = "2026-08-09T13:45:23.73Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d2/6b24738a0ef4557d189b150046cd07823c50e4273e8aebd651222e24306f/numpy-2.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8e4cb9a754c8a0c62eaa88273a5fba3391f4a610d1dee893c0755da31c083f15", size = 16886595, upload-time = "2026-08-09T13:45:27.323Z" }, + { url = "https://files.pythonhosted.org/packages/65/60/f2d208d366f263f39c6e69ed309290717aab41078b6d04c9be2a84fa2a07/numpy-2.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:52c808f96484f5571a5cc863775ce50247c17dfb3b0361f8ed6b4b0456f80080", size = 11896845, upload-time = "2026-08-09T13:45:31.638Z" }, + { url = "https://files.pythonhosted.org/packages/3c/79/81e0bf24f4d020a2b1d5cd297a9f60c3f24eeb116f9bba5870443f7b6a4a/numpy-2.5.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:29d81e97f668489cba8ebfd796b9bdd453525d35dd9e162e2daec94bf3fc7740", size = 5343880, upload-time = "2026-08-09T13:45:34.373Z" }, + { url = "https://files.pythonhosted.org/packages/ba/cc/e3141cf06d1a8a2c7e107543fe1269c1d1af760d4d683c0794a4ee1127c2/numpy-2.5.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afb3f0632d6b2e3ba04dbce8d1e48d321b369138b73830b5ca371a0e8d479d56", size = 6682264, upload-time = "2026-08-09T13:45:36.7Z" }, + { url = "https://files.pythonhosted.org/packages/29/f1/2a64a307d92c5d98f5255a4014eb43bb6103ee477087b61ecae44a3aa9b9/numpy-2.5.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0aadf13b60048d501e05fa699efaf7734e2494f3498a4c2a5521d822640324f3", size = 15609566, upload-time = "2026-08-09T13:45:39.518Z" }, + { url = "https://files.pythonhosted.org/packages/7b/44/59a1eb68e773c4098d107ef34a0dbdeca501d72ffcfbff9a7707343921ce/numpy-2.5.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29b86ff8a6cc556b47ec6b64b194815cc80e6bf5eedcc6cddfd65318cb0b4eee", size = 16709995, upload-time = "2026-08-09T13:45:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/8a/4c/3e54d4ddbc359a1295f8b633e8106bcd4d7d4a206e82df051bdfb3058755/numpy-2.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6950c4b7dd562453090548ba7f5da7e59f57f85663f15d5dcc60e249192f7e59", size = 16972511, upload-time = "2026-08-09T13:45:47.094Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9f/02e371638ebf19b66d46231e4be52999e87f32d1961b113bc45656608b22/numpy-2.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9727f472d2f3888053b8a75ab0cb94745a9de224bb5846dbadc0092101bc71d", size = 18465609, upload-time = "2026-08-09T13:45:50.808Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ae/ad6645abc7a3510fe48e8ea1ab4598166f500057ef4ebf38bfad4f1577de/numpy-2.5.2-cp313-cp313-win32.whl", hash = "sha256:4f9744f9fbdcea0bc552e8f19e1f141f811a3f9bc2be2cc6e86d982cab23e3f4", size = 6070204, upload-time = "2026-08-09T13:45:54.111Z" }, + { url = "https://files.pythonhosted.org/packages/15/20/f3489f86d81ea460b2bcdceaed094142ca6579f6be0ec527b781d39afe68/numpy-2.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:85aaccb24182c25df891ad0ec333585967e115269d5f1b17f2c9ae005bc96657", size = 12460532, upload-time = "2026-08-09T13:45:57.167Z" }, + { url = "https://files.pythonhosted.org/packages/d5/21/35b31dde1b283b79de828b80f876afd8c94e28fe1e9c375f89e261cc4c0d/numpy-2.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:bd68ece1553d2023c09a4226d9e41c586ad2d20594d1a456186c33513d2cb3f2", size = 10396725, upload-time = "2026-08-09T13:46:00.478Z" }, + { url = "https://files.pythonhosted.org/packages/ac/f8/c3b222bf075b50afd8e949a07a15c4b312a4a84bd8102a332bcd953cbbb4/numpy-2.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d787cf769c3baeb5f6235e778edb52c08dfa923789b5958f28e6450f96107cb1", size = 16885180, upload-time = "2026-08-09T13:46:03.939Z" }, + { url = "https://files.pythonhosted.org/packages/17/e1/2c1d4b1987795a92b5bbf7c24fe249ab96aa2573ab0d7604802c189d7b86/numpy-2.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:24b9dc2e3d84aa58523798805194e23e736f3f6ce2d1a5b92583ae734e6dbda8", size = 11907878, upload-time = "2026-08-09T13:46:07.045Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ee/d08226fc858044355983a6e5b94f08ff6f3969e0a2b160a4a89f0ddb3445/numpy-2.5.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:9e9413326d726c2545bfa65d2c0876871e8d8386e77f992c1d426e180bbd4323", size = 5354922, upload-time = "2026-08-09T13:46:10.04Z" }, + { url = "https://files.pythonhosted.org/packages/94/f0/6d3d933056440ebbc5e6bad92065fc6c26a48a84a36b1208580e94eea76c/numpy-2.5.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:60e902ac295855348a5ca2ea4c89108989a9f5fddfad3dfc0a8f36b10358567e", size = 6679168, upload-time = "2026-08-09T13:46:12.275Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3b/ecd49dd90033cceb2704d88ca905d4d7d89b0e8c739608754ffd325fa820/numpy-2.5.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50e500dc868e9313530ce12ba470fe50ff3afe3d62993ed6eff652dacd555b65", size = 15624501, upload-time = "2026-08-09T13:46:15.322Z" }, + { url = "https://files.pythonhosted.org/packages/c7/99/461bd36dbdfac6c1c53efa370bd55a83227542d0d118f1677dbf1a3dacd5/numpy-2.5.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318b9a4c845dbea06708a29c84ee429cc3065048db34cdb799047643492050ee", size = 16713701, upload-time = "2026-08-09T13:46:18.949Z" }, + { url = "https://files.pythonhosted.org/packages/f9/9c/2b251df9e8a5d647b62b0cbc1b90a91850c1cf4859ecb532fd0b4eacff6c/numpy-2.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:34c319e2963be042673fb46570501b2f06c41924e17e3563d58646b4380dfb68", size = 16986065, upload-time = "2026-08-09T13:46:23.006Z" }, + { url = "https://files.pythonhosted.org/packages/8f/25/20de43f53ff1390534a124475055a19f01fe10c920a0fd11b8e18d6d6052/numpy-2.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f06571a052127dc1b4e8b83029b4d1b20daa2b64a31cdd181fc6bc774e9000eb", size = 18470031, upload-time = "2026-08-09T13:46:27.102Z" }, + { url = "https://files.pythonhosted.org/packages/56/5e/0c577ca308d6da5eb79b546ba10bbe5b60148192194e2da060913b1de4f1/numpy-2.5.2-cp314-cp314-win32.whl", hash = "sha256:2cc779226e476d1e1f08c74068c419e60f41a9e0e069c92f6671d31d5c985e98", size = 6121028, upload-time = "2026-08-09T13:46:30.046Z" }, + { url = "https://files.pythonhosted.org/packages/15/5c/7bcbd5b11f94199073320410cddcbb80cee62415bfeb540874b265c2d922/numpy-2.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:7587f53dfbd5edc0f7b87c6217b4c6d2d1f2ef9c3da70bc1315e7db5f8d7ec9d", size = 12597627, upload-time = "2026-08-09T13:46:32.886Z" }, + { url = "https://files.pythonhosted.org/packages/87/bc/4d0b06fba0da90ccc75af62823cb9dcedb6c9ea0cffa058cb2c9ee773a77/numpy-2.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:3e4c367352d3747784248a227fbec218e193b56f7e6692e3b64fc805478ecfdf", size = 10680414, upload-time = "2026-08-09T13:46:36.036Z" }, + { url = "https://files.pythonhosted.org/packages/cd/17/f429aac9dc08833a0d0f188eba38c532a751b1a1f2ca6018a37b455cb321/numpy-2.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b879fb674276e331513fb136b78dbc6bd3c848309e0d841cfd63be3896c4cfc1", size = 12026967, upload-time = "2026-08-09T13:46:39.084Z" }, + { url = "https://files.pythonhosted.org/packages/ca/9f/d0849de96a2a4ceaa16662f18ee13eaa9c0aa418269fdc8c4857c56b11da/numpy-2.5.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:fd0d703772bba096843785bd38371e31bb4a0c1151497ad5739d182114a73f7f", size = 5473874, upload-time = "2026-08-09T13:46:42.075Z" }, + { url = "https://files.pythonhosted.org/packages/89/3c/8df216d4a4a5422a3de045301cf7df8ea47286d76f5cb7160b0128ac26b7/numpy-2.5.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:3a2f061cebd9e3d23bdcfaaded5e2293a4c6a5b60fa42df85d410a725ce621bf", size = 6789276, upload-time = "2026-08-09T13:46:44.387Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3a/20d7e9891c4ddfadd6ff8d95bf4b29f353d8e1770553de2099880551dfb9/numpy-2.5.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6df895598c0edcb41030126c89e0f353b07d93238116143b7405e937359736c4", size = 15659154, upload-time = "2026-08-09T13:46:47.538Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d6/f3aa3d2688bf501b858835c6bd087ae9b51a56ae6fca8e2b0990abd177af/numpy-2.5.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ab3d4a901f844ea836c3e80bf463c6a27d7f3c14e8e292fcf28d348b25b9bce", size = 16748909, upload-time = "2026-08-09T13:46:51.442Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8f/1c5cae8d2baf86ab802ae97a00be55bc7e21ebc11b12bbc33376c5f05342/numpy-2.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:cebc2d6dbb605a7703d59751dea4bd6b0ab127a5a4338a6f432df1936fef8b26", size = 17027685, upload-time = "2026-08-09T13:46:55.095Z" }, + { url = "https://files.pythonhosted.org/packages/5c/27/71d3467404aedc1c24ce79610f91b52b0b0f466c43a701aa56fc75c145ab/numpy-2.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eaca7ff36f0f52e2111ec71f169d8fd3e889e7ddc0d2592e0d703fd8d3ce8fac", size = 18501181, upload-time = "2026-08-09T13:46:59.09Z" }, + { url = "https://files.pythonhosted.org/packages/14/2f/42921d27c40aea7e077f4a423ae509fd9220b028cd787bafefd8ab2b3a5f/numpy-2.5.2-cp314-cp314t-win32.whl", hash = "sha256:ddf47472af2e4280d79bac82304f5e80150211f1b9e614b760061d5fdfbb6eba", size = 6271085, upload-time = "2026-08-09T13:47:01.903Z" }, + { url = "https://files.pythonhosted.org/packages/75/e6/bad5f5d56de9b1971bac959963dda276d35c40f1854475005434bbe08692/numpy-2.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:44ef9675d908e65f9953063837c3277730f3f4437615a4cdab67b366cabaf884", size = 12787971, upload-time = "2026-08-09T13:47:04.963Z" }, + { url = "https://files.pythonhosted.org/packages/df/05/f608795cb34391acd67e38d94a3c36abd8d8576293a3a80727d7595c372c/numpy-2.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:eaa088384c46f519dacb93b7ec483a6d6b19a4a2085ae4f25ab9b1c43d387d1e", size = 10750306, upload-time = "2026-08-09T13:47:07.976Z" }, +] + [[package]] name = "nvidia-cublas" version = "13.1.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cuda-nvrtc" }, + { name = "nvidia-cuda-nvrtc", marker = "sys_platform != 'darwin'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" }, @@ -1849,7 +1557,7 @@ name = "nvidia-cudnn-cu13" version = "9.20.0.48" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas" }, + { name = "nvidia-cublas", marker = "sys_platform != 'darwin'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/56/c5/83384d846b2fd17c44bd499b36c75a45ed4f095fbbb2252294e89cea5c5c/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:e31454ae00094b0c55319d9d15b6fa2fc50a9e1c0f5c8c80fb75258234e731e1", size = 444574296, upload-time = "2026-03-09T19:28:27.751Z" }, @@ -1861,7 +1569,7 @@ name = "nvidia-cufft" version = "12.0.0.61" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink" }, + { name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, @@ -1891,9 +1599,9 @@ name = "nvidia-cusolver" version = "12.0.4.66" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas" }, - { name = "nvidia-cusparse" }, - { name = "nvidia-nvjitlink" }, + { name = "nvidia-cublas", marker = "sys_platform != 'darwin'" }, + { name = "nvidia-cusparse", marker = "sys_platform != 'darwin'" }, + { name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, @@ -1905,7 +1613,7 @@ name = "nvidia-cusparse" version = "12.6.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink" }, + { name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, @@ -1962,80 +1670,22 @@ name = "omegaconf" version = "2.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "antlr4-python3-runtime" }, - { name = "pyyaml" }, + { name = "antlr4-python3-runtime", marker = "python_full_version < '3.13'" }, + { name = "pyyaml", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ce/3d/e4b57b8d9008c6ebe0d5eff901f91d5700cf7bdb8c8863df817463a7fd5e/omegaconf-2.3.1.tar.gz", hash = "sha256:e5e7de64aeebeddaf8e6d3f7a783b32ac2a01c0fbd9c878012caecb891a1f42a", size = 3298472, upload-time = "2026-06-11T05:05:12.885Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a4/0e/152509871bf30df6fc38569f52a2db9b55dd41aae957adae50a053ac7778/omegaconf-2.3.1-py3-none-any.whl", hash = "sha256:3d701d14e9a8828f1edd28bb70b725908b34277cdd72cf7d6a83f94dadc6b6a0", size = 79502, upload-time = "2026-06-11T05:05:09.954Z" }, ] -[[package]] -name = "onnxruntime" -version = "1.23.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11' and sys_platform == 'darwin'", - "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')", -] -dependencies = [ - { name = "coloredlogs" }, - { name = "flatbuffers" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "protobuf" }, - { name = "sympy" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/35/d6/311b1afea060015b56c742f3531168c1644650767f27ef40062569960587/onnxruntime-1.23.2-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:a7730122afe186a784660f6ec5807138bf9d792fa1df76556b27307ea9ebcbe3", size = 17195934, upload-time = "2025-10-27T23:06:14.143Z" }, - { url = "https://files.pythonhosted.org/packages/db/db/81bf3d7cecfbfed9092b6b4052e857a769d62ed90561b410014e0aae18db/onnxruntime-1.23.2-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:b28740f4ecef1738ea8f807461dd541b8287d5650b5be33bca7b474e3cbd1f36", size = 19153079, upload-time = "2025-10-27T23:05:57.686Z" }, - { url = "https://files.pythonhosted.org/packages/2e/4d/a382452b17cf70a2313153c520ea4c96ab670c996cb3a95cc5d5ac7bfdac/onnxruntime-1.23.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f7d1fe034090a1e371b7f3ca9d3ccae2fabae8c1d8844fb7371d1ea38e8e8d2", size = 15219883, upload-time = "2025-10-22T03:46:21.66Z" }, - { url = "https://files.pythonhosted.org/packages/fb/56/179bf90679984c85b417664c26aae4f427cba7514bd2d65c43b181b7b08b/onnxruntime-1.23.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4ca88747e708e5c67337b0f65eed4b7d0dd70d22ac332038c9fc4635760018f7", size = 17370357, upload-time = "2025-10-22T03:46:57.968Z" }, - { url = "https://files.pythonhosted.org/packages/cd/6d/738e50c47c2fd285b1e6c8083f15dac1a5f6199213378a5f14092497296d/onnxruntime-1.23.2-cp310-cp310-win_amd64.whl", hash = "sha256:0be6a37a45e6719db5120e9986fcd30ea205ac8103fd1fb74b6c33348327a0cc", size = 13467651, upload-time = "2025-10-27T23:06:11.904Z" }, - { url = "https://files.pythonhosted.org/packages/44/be/467b00f09061572f022ffd17e49e49e5a7a789056bad95b54dfd3bee73ff/onnxruntime-1.23.2-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:6f91d2c9b0965e86827a5ba01531d5b669770b01775b23199565d6c1f136616c", size = 17196113, upload-time = "2025-10-22T03:47:33.526Z" }, - { url = "https://files.pythonhosted.org/packages/9f/a8/3c23a8f75f93122d2b3410bfb74d06d0f8da4ac663185f91866b03f7da1b/onnxruntime-1.23.2-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:87d8b6eaf0fbeb6835a60a4265fde7a3b60157cf1b2764773ac47237b4d48612", size = 19153857, upload-time = "2025-10-22T03:46:37.578Z" }, - { url = "https://files.pythonhosted.org/packages/3f/d8/506eed9af03d86f8db4880a4c47cd0dffee973ef7e4f4cff9f1d4bcf7d22/onnxruntime-1.23.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bbfd2fca76c855317568c1b36a885ddea2272c13cb0e395002c402f2360429a6", size = 15220095, upload-time = "2025-10-22T03:46:24.769Z" }, - { url = "https://files.pythonhosted.org/packages/e9/80/113381ba832d5e777accedc6cb41d10f9eca82321ae31ebb6bcede530cea/onnxruntime-1.23.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da44b99206e77734c5819aa2142c69e64f3b46edc3bd314f6a45a932defc0b3e", size = 17372080, upload-time = "2025-10-22T03:47:00.265Z" }, - { url = "https://files.pythonhosted.org/packages/3a/db/1b4a62e23183a0c3fe441782462c0ede9a2a65c6bbffb9582fab7c7a0d38/onnxruntime-1.23.2-cp311-cp311-win_amd64.whl", hash = "sha256:902c756d8b633ce0dedd889b7c08459433fbcf35e9c38d1c03ddc020f0648c6e", size = 13468349, upload-time = "2025-10-22T03:47:25.783Z" }, - { url = "https://files.pythonhosted.org/packages/1b/9e/f748cd64161213adeef83d0cb16cb8ace1e62fa501033acdd9f9341fff57/onnxruntime-1.23.2-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:b8f029a6b98d3cf5be564d52802bb50a8489ab73409fa9db0bf583eabb7c2321", size = 17195929, upload-time = "2025-10-22T03:47:36.24Z" }, - { url = "https://files.pythonhosted.org/packages/91/9d/a81aafd899b900101988ead7fb14974c8a58695338ab6a0f3d6b0100f30b/onnxruntime-1.23.2-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:218295a8acae83905f6f1aed8cacb8e3eb3bd7513a13fe4ba3b2664a19fc4a6b", size = 19157705, upload-time = "2025-10-22T03:46:40.415Z" }, - { url = "https://files.pythonhosted.org/packages/3c/35/4e40f2fba272a6698d62be2cd21ddc3675edfc1a4b9ddefcc4648f115315/onnxruntime-1.23.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76ff670550dc23e58ea9bc53b5149b99a44e63b34b524f7b8547469aaa0dcb8c", size = 15226915, upload-time = "2025-10-22T03:46:27.773Z" }, - { url = "https://files.pythonhosted.org/packages/ef/88/9cc25d2bafe6bc0d4d3c1db3ade98196d5b355c0b273e6a5dc09c5d5d0d5/onnxruntime-1.23.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f9b4ae77f8e3c9bee50c27bc1beede83f786fe1d52e99ac85aa8d65a01e9b77", size = 17382649, upload-time = "2025-10-22T03:47:02.782Z" }, - { url = "https://files.pythonhosted.org/packages/c0/b4/569d298f9fc4d286c11c45e85d9ffa9e877af12ace98af8cab52396e8f46/onnxruntime-1.23.2-cp312-cp312-win_amd64.whl", hash = "sha256:25de5214923ce941a3523739d34a520aac30f21e631de53bba9174dc9c004435", size = 13470528, upload-time = "2025-10-22T03:47:28.106Z" }, - { url = "https://files.pythonhosted.org/packages/3d/41/fba0cabccecefe4a1b5fc8020c44febb334637f133acefc7ec492029dd2c/onnxruntime-1.23.2-cp313-cp313-macosx_13_0_arm64.whl", hash = "sha256:2ff531ad8496281b4297f32b83b01cdd719617e2351ffe0dba5684fb283afa1f", size = 17196337, upload-time = "2025-10-22T03:46:35.168Z" }, - { url = "https://files.pythonhosted.org/packages/fe/f9/2d49ca491c6a986acce9f1d1d5fc2099108958cc1710c28e89a032c9cfe9/onnxruntime-1.23.2-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:162f4ca894ec3de1a6fd53589e511e06ecdc3ff646849b62a9da7489dee9ce95", size = 19157691, upload-time = "2025-10-22T03:46:43.518Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a1/428ee29c6eaf09a6f6be56f836213f104618fb35ac6cc586ff0f477263eb/onnxruntime-1.23.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45d127d6e1e9b99d1ebeae9bcd8f98617a812f53f46699eafeb976275744826b", size = 15226898, upload-time = "2025-10-22T03:46:30.039Z" }, - { url = "https://files.pythonhosted.org/packages/f2/2b/b57c8a2466a3126dbe0a792f56ad7290949b02f47b86216cd47d857e4b77/onnxruntime-1.23.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8bace4e0d46480fbeeb7bbe1ffe1f080e6663a42d1086ff95c1551f2d39e7872", size = 17382518, upload-time = "2025-10-22T03:47:05.407Z" }, - { url = "https://files.pythonhosted.org/packages/4a/93/aba75358133b3a941d736816dd392f687e7eab77215a6e429879080b76b6/onnxruntime-1.23.2-cp313-cp313-win_amd64.whl", hash = "sha256:1f9cc0a55349c584f083c1c076e611a7c35d5b867d5d6e6d6c823bf821978088", size = 13470276, upload-time = "2025-10-22T03:47:31.193Z" }, - { url = "https://files.pythonhosted.org/packages/7c/3d/6830fa61c69ca8e905f237001dbfc01689a4e4ab06147020a4518318881f/onnxruntime-1.23.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9d2385e774f46ac38f02b3a91a91e30263d41b2f1f4f26ae34805b2a9ddef466", size = 15229610, upload-time = "2025-10-22T03:46:32.239Z" }, - { url = "https://files.pythonhosted.org/packages/b6/ca/862b1e7a639460f0ca25fd5b6135fb42cf9deea86d398a92e44dfda2279d/onnxruntime-1.23.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2b9233c4947907fd1818d0e581c049c41ccc39b2856cc942ff6d26317cee145", size = 17394184, upload-time = "2025-10-22T03:47:08.127Z" }, -] - [[package]] name = "onnxruntime" version = "1.28.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'darwin'", - "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'darwin'", - "python_full_version >= '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "(python_full_version >= '3.14' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", - "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", - "(python_full_version >= '3.12' and python_full_version < '3.14' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", - "python_full_version == '3.11.*' and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and sys_platform == 'win32'", - "python_full_version == '3.11.*' and sys_platform == 'emscripten'", - "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", -] dependencies = [ { name = "flatbuffers" }, - { name = "numpy" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "packaging" }, { name = "protobuf" }, ] @@ -2071,7 +1721,8 @@ name = "opencv-python" version = "4.11.0.86" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/17/06/68c27a523103dad5837dc5b87e71285280c4f098c60e4fe8a8db6486ab09/opencv-python-4.11.0.86.tar.gz", hash = "sha256:03d60ccae62304860d232272e4a4fda93c39d595780cb40b161b310244b736a4", size = 95171956, upload-time = "2025-01-16T13:52:24.737Z" } wheels = [ @@ -2088,7 +1739,8 @@ name = "opencv-python-headless" version = "4.11.0.86" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/36/2f/5b2b3ba52c864848885ba988f24b7f105052f68da9ab0e693cc7c25b0b30/opencv-python-headless-4.11.0.86.tar.gz", hash = "sha256:996eb282ca4b43ec6a3972414de0e2331f5d9cda2b41091a49739c19fb843798", size = 95177929, upload-time = "2025-01-16T13:53:40.22Z" } wheels = [ @@ -2100,87 +1752,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/86/8a/69176a64335aed183529207ba8bc3d329c2999d852b4f3818027203f50e6/opencv_python_headless-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:6c304df9caa7a6a5710b91709dd4786bf20a74d57672b3c31f7033cc638174ca", size = 39402386, upload-time = "2025-01-16T13:52:56.418Z" }, ] -[[package]] -name = "orjson" -version = "3.11.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/0c/964746fcafbd16f8ff53219ad9f6b412b34f345c75f384ad434ceaadb538/orjson-3.11.9.tar.gz", hash = "sha256:4fef17e1f8722c11587a6ef18e35902450221da0028e65dbaaa543619e68e48f", size = 5599163, upload-time = "2026-05-06T15:11:08.309Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/10/5d/b95ca542a001135cc250a49370f282f578c8f4e46cc8617d73775297eea8/orjson-3.11.9-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:135869ef917b8704ea0a94e01620e0c05021c15c52036e4663baffe75e72f8ce", size = 228986, upload-time = "2026-05-06T15:09:14.765Z" }, - { url = "https://files.pythonhosted.org/packages/80/01/be33fbff646e22f93398429ea645f20d2097aea1a6cdc1e6628e70125f83/orjson-3.11.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:115ab5f5f4a0f203cc2a5f0fb09aee503a3f771aa08392949ab5ca230c4fbdbd", size = 132558, upload-time = "2026-05-06T15:09:17.431Z" }, - { url = "https://files.pythonhosted.org/packages/4e/61/73d49333bba660a075daccca10970dc6409ce1cf42ae4046646a19468aad/orjson-3.11.9-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4da3c38a2083ca4aaf9c2a36776cce3e9328e6647b10d118948f3cfb4913ffe4", size = 128213, upload-time = "2026-05-06T15:09:18.719Z" }, - { url = "https://files.pythonhosted.org/packages/1f/7d/30e844b3dac3f74aed66b1f984daf9db3c98c0328c03d965a9e8dc06449e/orjson-3.11.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53b50b0e14084b8f7e29c5ce84c5af0f1160169b30d8a6914231d97d2fe297d4", size = 135430, upload-time = "2026-05-06T15:09:20.257Z" }, - { url = "https://files.pythonhosted.org/packages/16/64/bd815f5c610b3facc204f26ba94e87a9eb49b0d83de3d5fc1eee2402d91b/orjson-3.11.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:231742b4a11dad8d5380a435962c57e91b7c37b79be858f4ef1c0df1a259897e", size = 146178, upload-time = "2026-05-06T15:09:21.616Z" }, - { url = "https://files.pythonhosted.org/packages/c7/35/e744fd36c79b339d27beb06068b5a08a8882ef5418804d0ce545a31f718d/orjson-3.11.9-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34fd2317602587321faab75ab76c623a0117e80841a6413654f04e47f339a8fb", size = 133068, upload-time = "2026-05-06T15:09:23.228Z" }, - { url = "https://files.pythonhosted.org/packages/2a/56/d54152b67b63a0b3e556cfc549d6ce84f74d7f425ddeadc6c8a74d913da7/orjson-3.11.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71f3db16e69b667b132e0f305a833d5497da302d801508cbb051ed9a9819da47", size = 134217, upload-time = "2026-05-06T15:09:24.847Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ee/66154baf69f71c7164a268a5e888908aec5a0819d13c81d5e2755a257758/orjson-3.11.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0b34789fa0da61cf7bef0546b09c738fb195331e017e477096d129e9105ab03d", size = 141917, upload-time = "2026-05-06T15:09:26.647Z" }, - { url = "https://files.pythonhosted.org/packages/09/d3/c5824260ca8b9d7ba82648d042a3f8f4815d18c15bb98a1f30edd1bb2d83/orjson-3.11.9-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:87e4d4ab280b0c87424d47695bec2182caf8cfc17879ea78dab76680194abc13", size = 415356, upload-time = "2026-05-06T15:09:28.252Z" }, - { url = "https://files.pythonhosted.org/packages/64/cb/509c2e816fe4df641d93dc92f6a89adc8df3ada8ebdee2bd44aba3264c3c/orjson-3.11.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ace6c58523302d3b97b6ac5c38a5298a54b473762b6be82726b4265c41029f92", size = 148112, upload-time = "2026-05-06T15:09:29.783Z" }, - { url = "https://files.pythonhosted.org/packages/db/b5/3ceae56d2e4962979eedb023ba6a46a4bb65f333960379be0ca470686220/orjson-3.11.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:97d0d932803c1b164fde11cb542a9efcb1e0f63b184537cca65887147906ff48", size = 137112, upload-time = "2026-05-06T15:09:31.432Z" }, - { url = "https://files.pythonhosted.org/packages/d7/7a/81fa3f2c7bef79b04cf2ab7838e5ac74b1f12511ceab979759b0275d6bb4/orjson-3.11.9-cp310-cp310-win32.whl", hash = "sha256:b3afcf569c15577a9fe64627292daa3e6b3a70f4fb77a5df246a87ec21681b94", size = 131706, upload-time = "2026-05-06T15:09:32.707Z" }, - { url = "https://files.pythonhosted.org/packages/ae/d8/b64600f9083c7f151ad39717a5877fccbeb0ef6d7efcb55f971ce00b6bee/orjson-3.11.9-cp310-cp310-win_amd64.whl", hash = "sha256:8697ab6a080a5c46edaad50e2bc5bd8c7ca5c66442d24104fa44ec74910a8244", size = 127282, upload-time = "2026-05-06T15:09:33.955Z" }, - { url = "https://files.pythonhosted.org/packages/1e/51/3fb9e65ae76ee97bd611869a503fa3fc0a6e81dd8b737cf3003f682df7ff/orjson-3.11.9-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f01c4818b3fc9b0da8e096722a84318071eaa118df35f6ed2344da0e73a5444f", size = 228522, upload-time = "2026-05-06T15:09:35.362Z" }, - { url = "https://files.pythonhosted.org/packages/16/fa/9d54b07cb3f3b0bfd57841478e42d7a0ece4a9f49f9907eecf5a45461687/orjson-3.11.9-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:3ebca4179031ee716ed076ffadc29428e900512f6fccee8614c9983157fcf19c", size = 128463, upload-time = "2026-05-06T15:09:37.063Z" }, - { url = "https://files.pythonhosted.org/packages/88/b1/6ceafc2eefd0a553e3be77ce6c49d107e772485d9568629376171c50e634/orjson-3.11.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48ee05097750de0ff69ed5b7bbcf0732182fd57a24043dcc2a1da780a5ead3a5", size = 132306, upload-time = "2026-05-06T15:09:38.299Z" }, - { url = "https://files.pythonhosted.org/packages/ea/76/f11311285324a40aab1e3031385c50b635a7cd0734fdaf60c7e89a696f60/orjson-3.11.9-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6082706765a95a6680d812e1daf1c0cfe8adec7831b3ff3b625693f3b461b1c", size = 127988, upload-time = "2026-05-06T15:09:39.597Z" }, - { url = "https://files.pythonhosted.org/packages/9e/85/0ef63bcf1337f44031ce9b91b1919563f62a37527b3ea4368bb15a22e5d7/orjson-3.11.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:277fefe9d76ee17eb14debf399e3533d4d63b5f677a4d3719eb763536af1f4bd", size = 135188, upload-time = "2026-05-06T15:09:40.957Z" }, - { url = "https://files.pythonhosted.org/packages/05/94/b0d27090ea8a2095db3c2bd1b1c96f96f19bbb494d7fef33130e846e613d/orjson-3.11.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03db380e3780fa0015ed776a90f20e8e20bb11dde13b216ce19e5718e3dfba62", size = 145937, upload-time = "2026-05-06T15:09:42.249Z" }, - { url = "https://files.pythonhosted.org/packages/09/eb/75d50c29c05b8054013e221e598820a365c8e64065312e75e202ed880709/orjson-3.11.9-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33d7d766701847dc6729846362dc27895d2f2d2251264f9d10e7cb9878194877", size = 132758, upload-time = "2026-05-06T15:09:43.945Z" }, - { url = "https://files.pythonhosted.org/packages/49/bd/360686f39348aa88827cb6fbf7dc606fd41c831a35235e1abf1db8e3a9e6/orjson-3.11.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:147302878da387104b66bb4a8b0227d1d487e976ce41a8501916161072ed87b1", size = 133971, upload-time = "2026-05-06T15:09:45.239Z" }, - { url = "https://files.pythonhosted.org/packages/0e/30/3178eb16f3221aeef068b6f1f1ebe05f656ea5c6dffe9f6c917329fe17a3/orjson-3.11.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3513550321f8c8c811a7c3297b8a630e82dc08e4c10216d07703c997776236cd", size = 141685, upload-time = "2026-05-06T15:09:46.858Z" }, - { url = "https://files.pythonhosted.org/packages/5f/f1/ff2f19ed0225f9680fafa42febca3570dd59444ebf190980738d376214c2/orjson-3.11.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c5d001196b89fa9cf0a4ab79766cd835b991a166e4b621ba95089edc50c429ff", size = 415167, upload-time = "2026-05-06T15:09:48.312Z" }, - { url = "https://files.pythonhosted.org/packages/9b/61/863bddf0da6e9e586765414debd54b4e58db05f560902b6d00658cb88636/orjson-3.11.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:16969c9d369c98eb084889c6e4d2d39b77c7eb38ceccf8da2a9fff62ae908980", size = 147913, upload-time = "2026-05-06T15:09:49.733Z" }, - { url = "https://files.pythonhosted.org/packages/b6/8a/4081492586d75b073d60c5271a8d0f05a0955cabf1e34c8473f6fcd84235/orjson-3.11.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:63e0efbc991250c0b3143488fa57d95affcabbfc63c99c48d625dd37779aafe2", size = 136959, upload-time = "2026-05-06T15:09:51.311Z" }, - { url = "https://files.pythonhosted.org/packages/0d/bd/70b6ab193594d7abb875320c0a7c8335e846f28968c432c31042409c3c8d/orjson-3.11.9-cp311-cp311-win32.whl", hash = "sha256:14ed654580c1ed2bc217352ec82f91b047aef82951aa71c7f64e0dcb03c0e180", size = 131533, upload-time = "2026-05-06T15:09:52.637Z" }, - { url = "https://files.pythonhosted.org/packages/3f/17/1a1a228183d62d1b77e2c30d210f47dd4768b310ebe1607c63e3c0e3a71e/orjson-3.11.9-cp311-cp311-win_amd64.whl", hash = "sha256:57ea77fb70a448ce87d18fca050193202a3da5e54598f6501ca5476fb66cfe02", size = 127106, upload-time = "2026-05-06T15:09:54.204Z" }, - { url = "https://files.pythonhosted.org/packages/b8/95/285de5fa296d09681ee9c546cd4a8aeb773b701cf343dc125994f4d52953/orjson-3.11.9-cp311-cp311-win_arm64.whl", hash = "sha256:19b72ed11572a2ee51a67a903afbe5af504f84ed6f529c0fe44b0ab3fb5cc697", size = 126848, upload-time = "2026-05-06T15:09:55.551Z" }, - { url = "https://files.pythonhosted.org/packages/16/6d/11867a3ffa3a3608d84a4de51ef4dd0896d6b5cc9132fbe1daf593e677bc/orjson-3.11.9-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9ef6fe90aadef185c7b128859f40beb24720b4ecea95379fc9000931179c3a49", size = 228515, upload-time = "2026-05-06T15:09:57.265Z" }, - { url = "https://files.pythonhosted.org/packages/24/75/05912954c8b288f34fcf5cd4b9b071cb4f6e77b9961e175e56ebb258089f/orjson-3.11.9-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:e5c9b8f28e726e97d97696c826bc7bea5d71cecd63576dba92924a32c1961291", size = 128409, upload-time = "2026-05-06T15:09:59.063Z" }, - { url = "https://files.pythonhosted.org/packages/ab/86/1c3a47df3bc8191ea9ac51603bbb872a95167a364320c269f2557911f406/orjson-3.11.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a473dbb4162108b27901492546f83c76fdcea3d0eadff00ae7a07e18dcce09", size = 132106, upload-time = "2026-05-06T15:10:00.798Z" }, - { url = "https://files.pythonhosted.org/packages/d7/cf/b33b5f3e695ae7d63feef9d915c37cc3b8f465493dcd4f8e0b4c697a2366/orjson-3.11.9-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:011382e2a60fda9d46f1cdee31068cfc52ffe952b587d683ec0463002802a0f4", size = 127864, upload-time = "2026-05-06T15:10:02.15Z" }, - { url = "https://files.pythonhosted.org/packages/31/6a/6cf69385a58208024fcb8c014e2141b8ce838aba6492b589f8acfff97fab/orjson-3.11.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2d3dc759490128c5c1711a53eeaa8ee1d437fd0038ffd2b6008abf46db3f882", size = 135213, upload-time = "2026-05-06T15:10:03.515Z" }, - { url = "https://files.pythonhosted.org/packages/e8/f8/0b1bd3e8f2efcdd376af5c8cfd79eaf13f018080c0089c80ebd724e3c7fb/orjson-3.11.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8ea516b3726d190e1b4297e6f4e7a8650347ae053868a18163b4dd3641d1fff", size = 145994, upload-time = "2026-05-06T15:10:05.083Z" }, - { url = "https://files.pythonhosted.org/packages/f3/59/dab79f61044c529d2c81aecdc589b1f833a1c8dec11ba3b1c2498a02ca7e/orjson-3.11.9-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380cdce7ba24989af81d0a7013d0aaec5d0e2a21734c0e2681b1bc4f141957fe", size = 132744, upload-time = "2026-05-06T15:10:06.853Z" }, - { url = "https://files.pythonhosted.org/packages/0e/a4/82b7a2fe5d8a67a59ed831b24d59a3d46ea7d207b66e1602d376541d94a6/orjson-3.11.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4fa4f0af7fa18951f7ab3fc2148e223af211bf03f59e1c6034ec3f97f21d61", size = 134014, upload-time = "2026-05-06T15:10:08.213Z" }, - { url = "https://files.pythonhosted.org/packages/50/c7/375e83a76851b73b2e39f3bcf0e5a19e2b89bad13e5bca97d0b293d27f24/orjson-3.11.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a8f5f8bc7ce7d59f08d9f99fa510c06496164a24cb5f3d34537dbd9ca30132e2", size = 141509, upload-time = "2026-05-06T15:10:09.595Z" }, - { url = "https://files.pythonhosted.org/packages/7f/7c/49d5d82a3d3097f641f094f552131f1e2723b0b8cb0fa2874ab65ecfffa6/orjson-3.11.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:4d7fde5501b944f83b3e665e1b31343ff6e154b15560a16b7130ea1e594a4206", size = 415127, upload-time = "2026-05-06T15:10:11.049Z" }, - { url = "https://files.pythonhosted.org/packages/3a/dc/7446c538590d55f455647e5f3c61fc33f7108714e7afcffa6a2a033f8350/orjson-3.11.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cde1a448023ba7d5bb4c01c5afb48894380b5e4956e0627266526587ef4e535f", size = 148025, upload-time = "2026-05-06T15:10:12.842Z" }, - { url = "https://files.pythonhosted.org/packages/df/e5/4d2d8af06f788329b4f78f8cc3679bb395392fcaa1e4d8d3c33e85308fa4/orjson-3.11.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:71e63adb0e1f1ed5d9e168f50a91ceb93ae6420731d222dc7da5c69409aa47aa", size = 136943, upload-time = "2026-05-06T15:10:14.405Z" }, - { url = "https://files.pythonhosted.org/packages/06/69/850264ccf6d80f6b174620d30a87f65c9b1490aba33fe6b62798e618cad3/orjson-3.11.9-cp312-cp312-win32.whl", hash = "sha256:2d057a602cdd19a0ad680417527c45b6961a095081c0f46fe0e03e304aac6470", size = 131606, upload-time = "2026-05-06T15:10:15.791Z" }, - { url = "https://files.pythonhosted.org/packages/b9/d5/973a43fc9c55e20f2051e9830997649f669be0cb3ca52192087c0143f118/orjson-3.11.9-cp312-cp312-win_amd64.whl", hash = "sha256:59e403b1cc5a676da8eaf31f6254801b7341b3e29efa85f92b48d272637e77be", size = 127101, upload-time = "2026-05-06T15:10:17.129Z" }, - { url = "https://files.pythonhosted.org/packages/fe/ae/495470f0e4a18f73fa10b7f6b84b464ec4cc5291c4e0c7c2a6c400bef006/orjson-3.11.9-cp312-cp312-win_arm64.whl", hash = "sha256:9af678d6488357948f1f84c6cd1c1d397c014e1ae2f98ae082a44eb48f602624", size = 126736, upload-time = "2026-05-06T15:10:18.645Z" }, - { url = "https://files.pythonhosted.org/packages/32/33/93fcc25907235c344ae73122f8a4e01d2d393ef062b4af7d2e2487a32c37/orjson-3.11.9-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4bab1b2d6141fe7b32ae71dac905666ece4f94936efbfb13d55bb7739a3a6021", size = 228458, upload-time = "2026-05-06T15:10:20.079Z" }, - { url = "https://files.pythonhosted.org/packages/8f/27/b1e6dadb3c080313c03fdd8067b85e6a0460c7d8d6a1c3984ef77b904e4d/orjson-3.11.9-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:844417969855fc7a41be124aafe83dc424592a7f77cd4501900c67307122b92c", size = 128368, upload-time = "2026-05-06T15:10:21.549Z" }, - { url = "https://files.pythonhosted.org/packages/21/0f/c9ede0bf052f6b4051e64a7d4fa91b725cccf8321a6a786e86eb03519f00/orjson-3.11.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffe02797b5e9f3a9d8292ddcd289b474ad13e81ad83cd1891a240811f1d2cb81", size = 132070, upload-time = "2026-05-06T15:10:23.371Z" }, - { url = "https://files.pythonhosted.org/packages/fd/26/d398e28048dc18205bbe812f2c88cb9b40313db2470778e25964796458fe/orjson-3.11.9-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e4eed3b200023042814d2fc8a5d2e880f13b52e1ed2485e83da4f3962f7dc1a", size = 127892, upload-time = "2026-05-06T15:10:24.714Z" }, - { url = "https://files.pythonhosted.org/packages/66/60/52b0054c4c700d5aa7fc5b7ca96917400d8f061307778578e67a10e25852/orjson-3.11.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aff7da9952a5ad1cef8e68017724d96c7b9a66e99e91d6252e1b133d67a7b10", size = 135217, upload-time = "2026-05-06T15:10:26.084Z" }, - { url = "https://files.pythonhosted.org/packages/d5/97/1e3dc2b2a28b7b2528f403d2fc1d79ec5f39af3bc143ab65d3ec26426385/orjson-3.11.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d4e98d6f3b8afed8bc8cd9718ec0cdf46661826beefb53fe8eafb37f2bf0362", size = 145980, upload-time = "2026-05-06T15:10:28.062Z" }, - { url = "https://files.pythonhosted.org/packages/fc/39/31fbfe7850f2de32dee7e7e5c09f26d403ab01e440ac96001c6b01ad3c99/orjson-3.11.9-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a81d52442a7c99b3662333235b3adf96a1715864658b35bb797212be7bddb97", size = 132738, upload-time = "2026-05-06T15:10:29.727Z" }, - { url = "https://files.pythonhosted.org/packages/a1/08/dca0082dd2a194acb93e5457e73455388e2e2ca464a2672449a9ddbb679d/orjson-3.11.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e39364e726a8fff737309aff059ff67d8a8c8d5b677be7bb49a8b3e84b7e218", size = 134033, upload-time = "2026-05-06T15:10:31.152Z" }, - { url = "https://files.pythonhosted.org/packages/11/d4/5bdb0626801230139987385554c5d4c42255218ac906525bf4347f22cd95/orjson-3.11.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4fd66214623f1b17501df9f0543bef0b833979ab5b6ded1e1d123222866aa8c9", size = 141492, upload-time = "2026-05-06T15:10:32.641Z" }, - { url = "https://files.pythonhosted.org/packages/fa/88/a21fb53b3ede6703aede6dce4710ed4111e5b201cfa6bbff5e544f9d47d7/orjson-3.11.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8ecc30f10465fa1e0ce13fd01d9e22c316e5053a719a8d915d4545a09a5ff677", size = 415087, upload-time = "2026-05-06T15:10:34.438Z" }, - { url = "https://files.pythonhosted.org/packages/3d/57/1b30daf70f0d8180e9a73cefbfbdd99e4bf19eb020466502b01fba7e0e50/orjson-3.11.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:97db4c94a7db398a5bd636273324f0b3fd58b350bbbac8bb380ceb825a9b40f4", size = 148031, upload-time = "2026-05-06T15:10:36.358Z" }, - { url = "https://files.pythonhosted.org/packages/04/83/45fbb6d962e260807f99441db9613cee868ceda4baceda59b3720a563f97/orjson-3.11.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f78cf8fec5bd627f4082b8dfeac7871b43d7f3274904492a43dab39f18a19a0", size = 136915, upload-time = "2026-05-06T15:10:38.013Z" }, - { url = "https://files.pythonhosted.org/packages/5f/cc/2d10025f9056d376e4127ec05a5808b218d46f035fdc08178a5411b34250/orjson-3.11.9-cp313-cp313-win32.whl", hash = "sha256:d4087e5c0209a0a8efe4de3303c234b9c44d1174161dcd851e8eea07c7560b32", size = 131613, upload-time = "2026-05-06T15:10:39.569Z" }, - { url = "https://files.pythonhosted.org/packages/67/bd/2775ff28bfe883b9aa1ff348300542eb2ef1ee18d8ae0e3a49846817a865/orjson-3.11.9-cp313-cp313-win_amd64.whl", hash = "sha256:051b102c93b4f634e89f3866b07b9a9a98915ada541f4ec30f177067b2694979", size = 127086, upload-time = "2026-05-06T15:10:41.262Z" }, - { url = "https://files.pythonhosted.org/packages/91/2b/d26799e580939e32a7da9a39531bc9e58e15ca32ffaa6a8cb3e9bb0d22cd/orjson-3.11.9-cp313-cp313-win_arm64.whl", hash = "sha256:cce9127885941bd28f080cecf1f1d288336b7e0d812c345b08be88b572796254", size = 126696, upload-time = "2026-05-06T15:10:42.651Z" }, - { url = "https://files.pythonhosted.org/packages/8e/eb/5da01e356015aee6ecfa1187ced87aef51364e306f5e695dd52719bf0e78/orjson-3.11.9-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b6ef1979adc4bc243523f1a2ba91418030a8e29b0a99cbe7e0e2d6807d4dce6e", size = 228465, upload-time = "2026-05-06T15:10:44.097Z" }, - { url = "https://files.pythonhosted.org/packages/64/62/3e0e0c14c957133bcd855395c62b55ed4e3b0af23ffea11b032cb1dcbdb1/orjson-3.11.9-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:f36b7f32c7c0db4a719f1fc5824db4a9c6f8bd1a354debb91faf26ebf3a4c71e", size = 128364, upload-time = "2026-05-06T15:10:45.839Z" }, - { url = "https://files.pythonhosted.org/packages/5a/5a/07d8aa117211a8ed7630bda80c8c0b14d04e0f8dcf99bcf49656e4a710eb/orjson-3.11.9-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08f4d8ebb44925c794e535b2bebc507cebf32209df81de22ae285fb0d8d66de0", size = 132063, upload-time = "2026-05-06T15:10:47.267Z" }, - { url = "https://files.pythonhosted.org/packages/d6/ec/4acaf21483e18aa945be74a474c74b434f284b549f275a0a39b9f98956e9/orjson-3.11.9-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6cc7923789694fd58f001cbcac7e47abc13af4d560ebbfcf3b41a8b1a0748124", size = 122356, upload-time = "2026-05-06T15:10:48.765Z" }, - { url = "https://files.pythonhosted.org/packages/13/d8/5f0555e7638801323b7a75850f92e7dfa891bc84fe27a1ba4449170d1200/orjson-3.11.9-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea5c46eb2d3af39e806b986f4b09d5c2706a1f5afde3cbf7544ce6616127173c", size = 129592, upload-time = "2026-05-06T15:10:50.13Z" }, - { url = "https://files.pythonhosted.org/packages/b6/30/ed9860412a3603ceb3c5955bfd72d28b9d0e7ba6ed81add14f83d7114236/orjson-3.11.9-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5d89a2ed90731df3be64bab0aa44f78bff39fdc9d71c291f4a8023aa46425b7", size = 140491, upload-time = "2026-05-06T15:10:51.582Z" }, - { url = "https://files.pythonhosted.org/packages/d0/17/adc514dea7ac7c505527febf884934b815d34f0c7b8693c1a8b39c5c4a57/orjson-3.11.9-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25e4aed0312d292c09f61af25bba34e0b2c88546041472b09088c39a4d828af1", size = 127309, upload-time = "2026-05-06T15:10:53.329Z" }, - { url = "https://files.pythonhosted.org/packages/76/3e/c0b690253f0b82d86e99949af13533363acfb5432ecb5d53dd5b3bce9c34/orjson-3.11.9-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaea64f3f467d22e70eeed68bdccb3bc4f83f650446c4a03c59f2cba28a108db", size = 134030, upload-time = "2026-05-06T15:10:54.988Z" }, - { url = "https://files.pythonhosted.org/packages/c1/7a/bc82a0bb25e9faaf92dc4d9ef002732efc09737706af83e346788641d4a7/orjson-3.11.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a028425d1b440c5d92a6be1e1a020739dfe67ea87d96c6dbe828c1b30041728b", size = 141482, upload-time = "2026-05-06T15:10:56.663Z" }, - { url = "https://files.pythonhosted.org/packages/01/55/e69188b939f77d5d32a9833745ace31ea5ccae3ab613a1ec185d3cd2c4fb/orjson-3.11.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5b192c6cf397e4455b11523c5cf2b18ed084c1bbd61b6c0926344d2129481972", size = 415178, upload-time = "2026-05-06T15:10:58.446Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1a/b8a5a7ac527e80b9cb11d51e3f6689b709279183264b9ec5c7bc680bb8b5/orjson-3.11.9-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea407d4ccf5891d667d045fecae97a7a1e5e87b3b97f97ae1803c2e741130be0", size = 148089, upload-time = "2026-05-06T15:11:00.441Z" }, - { url = "https://files.pythonhosted.org/packages/97/4e/00503f64204bf859b37213a63927028f30fb6268cd8677fb0a5ad48155e1/orjson-3.11.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5f63aaf97afd9f6dec5b1a68e1b8da12bfccb4cb9a9a65c3e0b6c847849e7586", size = 136921, upload-time = "2026-05-06T15:11:02.176Z" }, - { url = "https://files.pythonhosted.org/packages/0d/ba/a23b82a0a8d0ed7bed4e5f5035aae751cad4ff6a1e8d2ecd14d8860f5929/orjson-3.11.9-cp314-cp314-win32.whl", hash = "sha256:e30ab17845bb9fa54ccf67fa4f9f5282652d54faa6d17452f47d0f369d038673", size = 131638, upload-time = "2026-05-06T15:11:03.696Z" }, - { url = "https://files.pythonhosted.org/packages/f3/c3/0c6798456bade745c75c452342dabacce5798196483e77e643be1f53877d/orjson-3.11.9-cp314-cp314-win_amd64.whl", hash = "sha256:32ef5f4283a3be81913947d19608eacb7c6608026851123790cd9cc8982af34b", size = 127078, upload-time = "2026-05-06T15:11:05.123Z" }, - { url = "https://files.pythonhosted.org/packages/16/21/5a3f1e8913103b703a436a5664238e5b965ec392b555fe68943ea3691e6b/orjson-3.11.9-cp314-cp314-win_arm64.whl", hash = "sha256:eebdbdeef0094e4f5aefa20dcd4eb2368ab5e7a3b4edea27f1e7b2892e009cf9", size = 126687, upload-time = "2026-05-06T15:11:06.602Z" }, -] - [[package]] name = "packaging" version = "26.2" @@ -2190,95 +1761,13 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, ] -[[package]] -name = "pandas" -version = "2.3.3" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'darwin'", - "python_full_version >= '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "(python_full_version >= '3.14' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", - "python_full_version < '3.11' and sys_platform == 'darwin'", - "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')", -] -dependencies = [ - { name = "numpy" }, - { name = "python-dateutil" }, - { name = "pytz" }, - { name = "tzdata" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763, upload-time = "2025-09-29T23:16:53.287Z" }, - { url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217, upload-time = "2025-09-29T23:17:04.522Z" }, - { url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791, upload-time = "2025-09-29T23:17:18.444Z" }, - { url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373, upload-time = "2025-09-29T23:17:35.846Z" }, - { url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444, upload-time = "2025-09-29T23:17:49.341Z" }, - { url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459, upload-time = "2025-09-29T23:18:03.722Z" }, - { url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086, upload-time = "2025-09-29T23:18:18.505Z" }, - { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, - { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, - { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, - { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, - { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, - { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, - { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, - { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, - { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, - { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, - { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, - { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, - { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, - { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, - { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, - { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, - { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, - { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, - { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, - { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, - { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, - { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, - { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, - { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, - { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, - { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, - { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, - { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, - { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, - { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, - { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, - { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, - { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, - { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, - { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, - { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, - { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, - { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, - { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, -] - [[package]] name = "pandas" version = "3.0.5" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'darwin'", - "python_full_version >= '3.12' and python_full_version < '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", - "(python_full_version >= '3.12' and python_full_version < '3.14' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", - "python_full_version == '3.11.*' and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and sys_platform == 'win32'", - "python_full_version == '3.11.*' and sys_platform == 'emscripten'", - "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", -] dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "python-dateutil" }, { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, ] @@ -2334,7 +1823,8 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "accelerate" }, { name = "huggingface-hub" }, - { name = "numpy" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "packaging" }, { name = "psutil" }, { name = "pyyaml" }, @@ -2363,15 +1853,6 @@ version = "12.3.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/c2/669d88644cddb1485bd9534e63e8cf476c8e51cb3c3a1297677023505c0e/pillow-12.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6c0016e7b354317c4e9e525b937ac8596c38d2d232b419529b9cd7a1cd46e39a", size = 5392418, upload-time = "2026-07-01T11:53:27.808Z" }, - { url = "https://files.pythonhosted.org/packages/6b/ba/3762f376a2948e3036488d773a146e0ae6ecc2ca03ac20e2615bd0b2ba02/pillow-12.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bcc33feacfaefce60c12fd500a277533bdc02b10a19f7f6d348763d8140bbba7", size = 4785287, upload-time = "2026-07-01T11:53:29.761Z" }, - { url = "https://files.pythonhosted.org/packages/07/50/b5d688cc9c52d4482f3d5bcab6ce20bc2a74a85d2343841c907444a3be2c/pillow-12.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5594fc43d548a7ed94949d139aa1341b270f1863f11cfd37f5a6c8b778a6b67f", size = 6253754, upload-time = "2026-07-01T11:53:32.298Z" }, - { url = "https://files.pythonhosted.org/packages/4e/89/36f4cd76cf4baf05c50ababb976249153f18c959171c7f6ba09a6f217260/pillow-12.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0606c8bf2cdefea14a43530f7657cbbb7ecf1c4222512492ef4a4434a9501ec", size = 6925605, upload-time = "2026-07-01T11:53:34.487Z" }, - { url = "https://files.pythonhosted.org/packages/eb/c0/4de58cf6633b9e3a6061ef4be6fb91fc3c90b812ece886f531e3c523d777/pillow-12.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:85f998ea1848bc6757289e739cfbdda3a04adfd58b02fc018ce54d754a5ce468", size = 6327788, upload-time = "2026-07-01T11:53:36.433Z" }, - { url = "https://files.pythonhosted.org/packages/87/3c/14d53682a19550dbbaf3b598f807d5457646c510805a44c7d7891cd1cd1a/pillow-12.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:25b9b82bb22e6e2b3cd07b39c68b7b862001226cb3dff7130d1cb914121b39ed", size = 7036288, upload-time = "2026-07-01T11:53:38.712Z" }, - { url = "https://files.pythonhosted.org/packages/38/1d/36279e3c77efe034e4cc2b0393ee74ffdb5a62391dacbf9b916154f5f0b8/pillow-12.3.0-cp310-cp310-win32.whl", hash = "sha256:37dc8f7bbb66efe481bb60defacef820c950c24713fb44962ed6aa2a50966de1", size = 6472396, upload-time = "2026-07-01T11:53:40.781Z" }, - { url = "https://files.pythonhosted.org/packages/48/7c/8fa0039574c476d7c6fa57dd7c32a130436877c6ec1e5ce1cc8ec44878c1/pillow-12.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:300557495eb45ebb8aec96c2da9c4be642fbf7cd937278b4013ba894ea8eb0eb", size = 7226887, upload-time = "2026-07-01T11:53:42.764Z" }, - { url = "https://files.pythonhosted.org/packages/fa/17/e324be141d173c1c919428066c3259f21c1b8982e564e01a4a81e96dbdcf/pillow-12.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:514435a37670e3e5e08f3945b68718b6ed329bb84367777e16f9f4dfe1e61a0f", size = 2568039, upload-time = "2026-07-01T11:53:45.372Z" }, { url = "https://files.pythonhosted.org/packages/fb/c8/0a78b0e02d7ac54bc03e5321c9220da52f0c2ea83b21f7c40e7f3169c502/pillow-12.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756", size = 5392415, upload-time = "2026-07-01T11:53:47.162Z" }, { url = "https://files.pythonhosted.org/packages/b2/5b/a02d30018abd97ced9f5a6c63d28597694a00d066516b9c1c6de45859fc9/pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6", size = 4785266, upload-time = "2026-07-01T11:53:49.079Z" }, { url = "https://files.pythonhosted.org/packages/c8/98/766667a4be768150a202836acd9fad19c06824ca86c4286d3cf6b274964e/pillow-12.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd", size = 6263814, upload-time = "2026-07-01T11:53:51.32Z" }, @@ -2423,27 +1904,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/23/26/fcb2f6e37175b04f53570b59937867e2b80ee1685e744023153028fc14f9/pillow-12.3.0-cp314-cp314t-win32.whl", hash = "sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7", size = 6474125, upload-time = "2026-07-01T11:55:30.956Z" }, { url = "https://files.pythonhosted.org/packages/90/de/3634abee5f1c9e13c56787b7d5517b0ba8d6de51700b95578cf338349c9f/pillow-12.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c", size = 7242939, upload-time = "2026-07-01T11:55:34.044Z" }, { url = "https://files.pythonhosted.org/packages/ce/2a/fd13f8eb24de5714a6eb444a3d67e2842c6c576e159a43793adf23051351/pillow-12.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45", size = 2567506, upload-time = "2026-07-01T11:55:35.988Z" }, - { url = "https://files.pythonhosted.org/packages/5d/dc/8fdce34ec725a33c81c6ba122b904d6b9024e50ea9ac7bede62fab54506c/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139", size = 4162063, upload-time = "2026-07-01T11:55:37.941Z" }, - { url = "https://files.pythonhosted.org/packages/76/66/2044b9a63d3b84ff048228dfcb7cd9bf0df983e8470971bf7d4c57b693de/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402", size = 4255549, upload-time = "2026-07-01T11:55:40.022Z" }, - { url = "https://files.pythonhosted.org/packages/52/7e/1f67e6f4ece6b582ee4b539decbcc9f848dc245a93ed8cd7338bafef72f1/pillow-12.3.0-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c", size = 3696331, upload-time = "2026-07-01T11:55:41.98Z" }, - { url = "https://files.pythonhosted.org/packages/12/40/d306fc2c8e4d45d7f175c77edca7063be7b86fe7fe6e68f4353bf71d808c/pillow-12.3.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:dc624f6bc473dacdf7ef7eb8678d0d08edf15cd94fad6ae5c7d6cc67a4e4902f", size = 5350370, upload-time = "2026-07-01T11:55:44.028Z" }, - { url = "https://files.pythonhosted.org/packages/dd/44/668fb1437e8ce420f62d6106eb66e44a5971602a4d794615bdf79315d82d/pillow-12.3.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:71d6097b330eea8fd15097780c8e89cb1a8ce7838669f48c5bacd6f663dd4701", size = 4780147, upload-time = "2026-07-01T11:55:46.073Z" }, - { url = "https://files.pythonhosted.org/packages/0c/08/93fa2e70e30a2d81547e481b6ee2bb9522117221fb1e0ce4b5df70967677/pillow-12.3.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:28ce87c5ab450a9dd970b52e5aca5fe63ed432d18a2eaddd1979a00a1ba24ace", size = 6273659, upload-time = "2026-07-01T11:55:48.264Z" }, - { url = "https://files.pythonhosted.org/packages/f8/6d/043e96ff814fc31a33077e4cba86082167db520c93632afdf2042febbb0c/pillow-12.3.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b02afb9b97f65fbca5f31db6a2a3ba21aa93030225f150fa3f249717e938fb4", size = 6947439, upload-time = "2026-07-01T11:55:50.503Z" }, - { url = "https://files.pythonhosted.org/packages/af/92/ba71d2ee2ac0edf3fa33bd9d5ee9ee080da70b1766f3ca3934f9938ddac9/pillow-12.3.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:1182d52bc2d5e5d7d0949503aa7e36d12f42205dc287e4883f407b1988820d39", size = 6353577, upload-time = "2026-07-01T11:55:52.697Z" }, - { url = "https://files.pythonhosted.org/packages/0f/ce/e63064e2122923ff687c8ad792d0d736a7b3920a56a46982e81a7fdd25d6/pillow-12.3.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e795b7eb908249c4e43c7c99fac7c2c75dab0c43566e37db472a355f63693d71", size = 7060394, upload-time = "2026-07-01T11:55:55.149Z" }, - { url = "https://files.pythonhosted.org/packages/54/76/a09cc3ccc8d773a7283d34c38bec1708f9e3cc932093cbc4c5e71ac4060b/pillow-12.3.0-cp315-cp315-win32.whl", hash = "sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827", size = 6467375, upload-time = "2026-07-01T11:55:57.769Z" }, - { url = "https://files.pythonhosted.org/packages/3e/03/1846c49ba3b1d5550392a4bbd06d6fb4578e1cd91a803198b5c90f5f7d53/pillow-12.3.0-cp315-cp315-win_amd64.whl", hash = "sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5", size = 7237048, upload-time = "2026-07-01T11:55:59.975Z" }, - { url = "https://files.pythonhosted.org/packages/fb/bb/89f35dcc79610423f9f195504d7def7f0d1416a711541b42867e25fe3412/pillow-12.3.0-cp315-cp315-win_arm64.whl", hash = "sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658", size = 2566006, upload-time = "2026-07-01T11:56:02.143Z" }, - { url = "https://files.pythonhosted.org/packages/30/88/707027ba09942dfa2c28759b5c222d769290a41c6d20ea60ec250801941f/pillow-12.3.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:e9871b1ffbfa9656b60aeee92ed5136a5742696006fa322b29ea3d8da0ecc9cf", size = 5352509, upload-time = "2026-07-01T11:56:04.2Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6d/00352fa25332c2569cd387851f568cc5a4b75a9adbfb37ac4fbce4c02eec/pillow-12.3.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:53aa02d20d10c3d814d536aa4e5ac9b84ca0ff5a88377963b085ad6822f93e64", size = 4783167, upload-time = "2026-07-01T11:56:06.631Z" }, - { url = "https://files.pythonhosted.org/packages/13/4f/9e049dfa21af7c22427275720e2490267ba8138120add5c4c574deb69782/pillow-12.3.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:446c34dcc4324b084a53b705127dc15717b22c5e140ae0a3c38349d4efec071e", size = 6329237, upload-time = "2026-07-01T11:56:08.868Z" }, - { url = "https://files.pythonhosted.org/packages/36/16/cf6eeaae8d0fce8dd390a33437cf68c5d5bd73834a2bc6e2f14efda0ab45/pillow-12.3.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf1845d02ad822a369a49f2bb9345b1614744267682e7a03527dc3bf6eea1777", size = 6997047, upload-time = "2026-07-01T11:56:11.379Z" }, - { url = "https://files.pythonhosted.org/packages/1e/69/dbf769bdd55f48bf5733cac28edc6364ffaa072ec9ba336266e4fe66be55/pillow-12.3.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:186941b6aef820ad110fb01fb06eb925374dc3a21b17e37ec9a53b250c6fe2d1", size = 6400440, upload-time = "2026-07-01T11:56:13.908Z" }, - { url = "https://files.pythonhosted.org/packages/a0/e1/ffc9cfc2eea0d178da8018e18e959301ad9d6bc9f3edb7181e748a474b97/pillow-12.3.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f13c32a3abd6079a66d9526e18dad9b6d280384d49d7c54040cd57b6424041d9", size = 7105895, upload-time = "2026-07-01T11:56:16.575Z" }, - { url = "https://files.pythonhosted.org/packages/18/f0/a5595c1e8c3ae44b9828cb2f0fa8155e5095ef04d6327b8f61cf44a3df85/pillow-12.3.0-cp315-cp315t-win32.whl", hash = "sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8", size = 6474384, upload-time = "2026-07-01T11:56:18.855Z" }, - { url = "https://files.pythonhosted.org/packages/e4/04/62bcd9f844984c5938d3b05264a61d797a29d3e0812341a8204af70bbdee/pillow-12.3.0-cp315-cp315t-win_amd64.whl", hash = "sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418", size = 7243537, upload-time = "2026-07-01T11:56:21.214Z" }, - { url = "https://files.pythonhosted.org/packages/3d/68/1f3066acedf37673694a7141381d8f811ae97f30d34413d236abe7d489f1/pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59", size = 2567491, upload-time = "2026-07-01T11:56:23.506Z" }, { url = "https://files.pythonhosted.org/packages/75/18/2e8b40223153ccbc60df07f9e8928dc0c76202aa4e55ae9f53962b6510d6/pillow-12.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468", size = 5302510, upload-time = "2026-07-01T11:56:25.736Z" }, { url = "https://files.pythonhosted.org/packages/46/3e/51fabf59d5ab801ceab709453d3ab6b180083496579549de4c45ced6528a/pillow-12.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94", size = 4736058, upload-time = "2026-07-01T11:56:28.041Z" }, { url = "https://files.pythonhosted.org/packages/bf/20/22fe9384b7949e25fb1293bcfc84fb82590ff4ea6b37c95b24d26d793d86/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e", size = 5237776, upload-time = "2026-07-01T11:56:30.263Z" }, @@ -2460,14 +1920,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/08/dc/6ab7a469ced899fbdbbc27b00067e4da02c21ff1ce7e9c6110f14e8fea6c/pillow_heif-1.5.0.tar.gz", hash = "sha256:16b11a37b762ff42da2d36527bb5cb14bd9194c24c389ee911155d6e23c53065", size = 17114105, upload-time = "2026-07-22T14:27:50.571Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/6a/2977e00f8130cad97b1841f70edf021d2feca5626e0f586c5ade29244d58/pillow_heif-1.5.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:f4675dcb6c9ada002f1180d1c7bfadcf98aa2865c6f5f4d2d3cc6f51d8126435", size = 4742817, upload-time = "2026-07-22T14:26:22.934Z" }, - { url = "https://files.pythonhosted.org/packages/d6/fe/97a08343cda727b74dcf34755dc197f820e139fde239d860b379a9ba3cab/pillow_heif-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9650363fd4be654222f12bb487e914a21f898d00b844adb120606df210f4dc6c", size = 4262195, upload-time = "2026-07-22T14:26:24.875Z" }, - { url = "https://files.pythonhosted.org/packages/2f/a1/543540c0d42e00c900607df57ff48a29b0a5e8d4cec0e7d8a42ca9e805e7/pillow_heif-1.5.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1793bb34067857a733439aa276195d112aee44a3854991b8c18a5597620ea80", size = 6346283, upload-time = "2026-07-22T14:26:26.775Z" }, - { url = "https://files.pythonhosted.org/packages/b0/5b/eb32e2a55902350347f9a7a7895fa3a6cf6003198c3d8d5436d59711e840/pillow_heif-1.5.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eb59104001afbf9263f261f2bea9d7481c3e99effd8b01e647a9138d562ddb48", size = 5601200, upload-time = "2026-07-22T14:26:28.262Z" }, - { url = "https://files.pythonhosted.org/packages/72/35/7e099dffe7c3345b7c9d0791aeed0fd24e741a3a73620373d3fb026f7694/pillow_heif-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7c839fa42bc3a6115f863335313d69bb74882d79f4739e2535fedc48806ea39b", size = 7374295, upload-time = "2026-07-22T14:26:30.055Z" }, - { url = "https://files.pythonhosted.org/packages/9d/97/cada6dcd8b364780bdcb439ada9e66360233f4130e993c175402c6e47090/pillow_heif-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a23507bfcb230fe99a5ca5298e018985e2954a3e3ff2033a66dbb9b74fd93789", size = 6632790, upload-time = "2026-07-22T14:26:31.883Z" }, - { url = "https://files.pythonhosted.org/packages/4f/d1/08c06ac30ff261fa7e582489f4f9a29c4ae13976f3f0637906017b8d6dad/pillow_heif-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:24e62ce775177bf6b039028c1b445893ec95a0b19771a458e36ff2f41c8ab44c", size = 6520153, upload-time = "2026-07-22T14:26:33.427Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4a/4f372c4163c6da00faedeb0ff885f35045e87b560cce012f993ab08e3f76/pillow_heif-1.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:5e01e4c3867852b4a3ae974580741986733d46b9626e7491868fc97f277804df", size = 3810991, upload-time = "2026-07-22T14:26:35.151Z" }, { url = "https://files.pythonhosted.org/packages/42/ff/c9c74db32f93b890a9c90ee75e00e9a6e40f781417bc12acd1c1f0613c7a/pillow_heif-1.5.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:e07ad41fde21396c01b14f2f3026b26c85a4d8696e7b9514b6f43b29f9cc08fa", size = 4742816, upload-time = "2026-07-22T14:26:36.684Z" }, { url = "https://files.pythonhosted.org/packages/34/f3/c3b63ff41ddd65bea40b0e1a176ae08c613e9948ca1a45afca10207dd19d/pillow_heif-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:88d74a74b144c2e6e99e18e23a1637aef5f63ad32c718c8358da87ea81f94194", size = 4262190, upload-time = "2026-07-22T14:26:38.242Z" }, { url = "https://files.pythonhosted.org/packages/9a/19/aa5c298a1b9dc41cfc0a846cfc10f1aaac9b72b9eb27abfc954575bb8929/pillow_heif-1.5.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fece01cccd4faccadc912760cdae3e3a87b66fc2bda36e44d3bff79549c83d1c", size = 6348002, upload-time = "2026-07-22T14:26:39.653Z" }, @@ -2530,23 +1982,6 @@ version = "0.5.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ec/44/c87281c333769159c50594f22610f77398a47ccbfbbf23074e744e86f87c/propcache-0.5.2.tar.gz", hash = "sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427", size = 50208, upload-time = "2026-05-08T21:02:12.199Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/56/030b7b4719d53085722893e0009dffb9236aa10bca1b12121bdc5626ef16/propcache-0.5.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5a81be28596d6559f6131ef33e10200de6e17643b3c74ce03f9eb103be6ae8b", size = 93417, upload-time = "2026-05-08T20:59:15.597Z" }, - { url = "https://files.pythonhosted.org/packages/1a/55/1140a8e067b8ec093a18a4ae7bb0045d9db65da38a08618ddc5e2f1994aa/propcache-0.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29cbaac5ea0212663e6845e04b5e188d5a6ae6dd919810ac835bf1d3b42c3f4c", size = 53847, upload-time = "2026-05-08T20:59:17.096Z" }, - { url = "https://files.pythonhosted.org/packages/20/42/0e7443c90310498561addf346e7d57fe3c6ba1914e1ba938b5464c7bbfd2/propcache-0.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6bf3be92233808fcd338eba0fb4d0b59ec5772af4f4ecfcec450d1bfc0f8b5eb", size = 53512, upload-time = "2026-05-08T20:59:18.64Z" }, - { url = "https://files.pythonhosted.org/packages/b7/db/cf51a71bab2009517d1a7f0ee07657e3bd446c4d69f67e6966cf17bcf956/propcache-0.5.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f8ea531c794b9d6274acd4e8d2c2ebcac590a4361d27482edd3010b79f1325e", size = 58068, upload-time = "2026-05-08T20:59:20.683Z" }, - { url = "https://files.pythonhosted.org/packages/b7/43/39b6bdee9699fa1e1641c519feeb64a67e2a9f93bb465c70776b37a7333f/propcache-0.5.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:decfca4c79dd53ebab484b00cc4b6717d8c369f86e74aa4ca395a64ac651495e", size = 61020, upload-time = "2026-05-08T20:59:22.112Z" }, - { url = "https://files.pythonhosted.org/packages/26/0b/843726fbb0a29a8c5684fdb25971823638399f31e52e9d1f06a02dc9aa6b/propcache-0.5.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4621064bbf28fa77ff64dd5d94367c04684c67d3a5bf1dff25f0cd0d98a38f3b", size = 62732, upload-time = "2026-05-08T20:59:23.805Z" }, - { url = "https://files.pythonhosted.org/packages/39/6e/899fed76dc1942b8a64193a4f059d7f1a2c7ef65085e8a9366ed8ec0d199/propcache-0.5.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b96db7141a592cbc968daf1feea83a118e6ab378af4abbc72b248c895414c22d", size = 60140, upload-time = "2026-05-08T20:59:25.389Z" }, - { url = "https://files.pythonhosted.org/packages/ab/09/3da4be9b5b879219ad234aa535b3dd4a080ed1ad48d3a73ca07a9e798f22/propcache-0.5.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1ca071adabaab6e9219924bbe00af821f1ee7de113a9eca1cdc292de3d120f4d", size = 60400, upload-time = "2026-05-08T20:59:27.238Z" }, - { url = "https://files.pythonhosted.org/packages/60/2f/09b72b874a9aa0044faf52a69807a6ed618e267ceaa9ec4a63195fa5b504/propcache-0.5.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e4294d04a94dcab1b3bccd8b66d962dcad411a1d19414b2a41d1445f1de32ad0", size = 58155, upload-time = "2026-05-08T20:59:28.48Z" }, - { url = "https://files.pythonhosted.org/packages/8a/37/97489848c54c95578045473954f10956d619ce6a09e7ac137b71cdcb698b/propcache-0.5.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a0e399a2eccb91ed18721f86aa85757727400b6865c89e88934781deb9c8498b", size = 57037, upload-time = "2026-05-08T20:59:30.146Z" }, - { url = "https://files.pythonhosted.org/packages/22/db/6c695285ccfc49012743ee9c98212b8c5dd0aed7b63cfd816d4a0f7a1601/propcache-0.5.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:823581fd5cb08b12a48bfa11fe962a7916766b6170c17b028fbdf762b85eb9bf", size = 61103, upload-time = "2026-05-08T20:59:31.626Z" }, - { url = "https://files.pythonhosted.org/packages/98/a9/1e500401ca593b0bdb6bf75a70bc2d723835fd53360edff6af70692c7546/propcache-0.5.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:949c91d1a990cf3b2e8188dfcfb25005e0b834a06c63fa4ef9f360878ce21ecf", size = 60394, upload-time = "2026-05-08T20:59:32.829Z" }, - { url = "https://files.pythonhosted.org/packages/1f/87/f638b6e375eae0f30a1a2325d8b34fd85fdc785bb9960cf805f3bf1ec69a/propcache-0.5.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:cc1177027eda740fdb152706bd215a3f124e3eea15afc39f2cb9fe351b50619e", size = 63084, upload-time = "2026-05-08T20:59:35.964Z" }, - { url = "https://files.pythonhosted.org/packages/f6/18/884573f5d97b6d9eba68de759a82c901b7e39d7904d30f7b8d58d42d2a12/propcache-0.5.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b05d643f944a8c3c4bd86d65ffd87bf3264b617f87791940302bc474d2ff5274", size = 60999, upload-time = "2026-05-08T20:59:38.481Z" }, - { url = "https://files.pythonhosted.org/packages/8f/1a/c3915eb059ceec9e758a56e4cfd955292bc0f201be2176a46b76d94b303a/propcache-0.5.2-cp310-cp310-win32.whl", hash = "sha256:8114f28879e0904748e831c3a7774261bd9e75f49be089f389a76f959dcd13fe", size = 39036, upload-time = "2026-05-08T20:59:40.323Z" }, - { url = "https://files.pythonhosted.org/packages/5b/02/1dfd5607501a602d19c1c449d2d193b7d1c611f9246b4059026a1189a80e/propcache-0.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:5fcb98e7598b1ee0addab320d90f65b530297a867dbfe9de52ea838077e16e3d", size = 42190, upload-time = "2026-05-08T20:59:42.232Z" }, - { url = "https://files.pythonhosted.org/packages/57/93/f71588ad08b3e6f4b555b5ef215808a3c02b042d0151ad82fa6f15be677a/propcache-0.5.2-cp310-cp310-win_arm64.whl", hash = "sha256:04dc2390d9edbbaef7461f33322555976ffddf0b650a038649d026358714e6c5", size = 38545, upload-time = "2026-05-08T20:59:44.087Z" }, { url = "https://files.pythonhosted.org/packages/e7/f1/8a8cc1c2c7e7934ab77e0163414f736fadbc0f5e8dd9673b952355ac175b/propcache-0.5.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74b70780220e2dd89175ca24b81b68b67c83db499ae611e7f2313cb329801c78", size = 90744, upload-time = "2026-05-08T20:59:45.799Z" }, { url = "https://files.pythonhosted.org/packages/c2/f4/651b1225e976bd1a2ba5cfba0c29d096581c2636b437e3a9a7ab6276270a/propcache-0.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4840ab0ae0216d952f4b53dc6d0b992bfc2bedbfe360bdd9b548bc184c08959", size = 52033, upload-time = "2026-05-08T20:59:47.408Z" }, { url = "https://files.pythonhosted.org/packages/15/a8/8ede85d6aa1f79fc7dc2f8fd2c8d65920b8272c3892903c8a1affde48cfb/propcache-0.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c6844ba6364fb12f403928a82cfd295ab103a2b315c77c747b2dbe4a41894ea7", size = 52754, upload-time = "2026-05-08T20:59:49.202Z" }, @@ -2701,13 +2136,6 @@ version = "25.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/27/f3/95428098d1fa7d04432fb750eed06b41304c2f6a5d3319985e64db2d9d41/pyarrow-25.0.0.tar.gz", hash = "sha256:d2d697008b5ec06d75952ef260c2e9a8a0f6ccfce24266c04c9c8ade927cb3b4", size = 1199181, upload-time = "2026-07-10T08:29:50.116Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/2a/eaa70e6d6ed430c2e90c0599e2831a41a50251879e44788ccdbc73115af1/pyarrow-25.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ce0ca222802087b9a8cb031a6468442cb6b67c290a45a601cac64753d34954d3", size = 35945551, upload-time = "2026-07-10T08:25:23.153Z" }, - { url = "https://files.pythonhosted.org/packages/df/e0/917086af6b246143012cdc8a7c886b018b53204f3d69fc5f9be5857a8b80/pyarrow-25.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:7d6da02ffc7a3a9bda3b7ded4cc2a27ff73969ab37153f3afd46bbbc1ba4f0f7", size = 37636698, upload-time = "2026-07-10T08:25:28.031Z" }, - { url = "https://files.pythonhosted.org/packages/68/6a/c87829f92503f84993721791c942f3d9aa81044de51a8cfb1da5810e5345/pyarrow-25.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:dbf9fa5d4bde73b1cc16377dcaaa010f971e6fa7f5083f5d44f34b50bc1d74af", size = 46858364, upload-time = "2026-07-10T08:25:34.527Z" }, - { url = "https://files.pythonhosted.org/packages/cc/ba/2030d454c2747e26cce23e4a0338067ee0830a155b7894da04caa96783a5/pyarrow-25.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b72d943ff4e10fec8d48aedb23322d8f6ea8bc2d698b81db37e73730f69e4862", size = 50056398, upload-time = "2026-07-10T08:25:40.785Z" }, - { url = "https://files.pythonhosted.org/packages/78/ce/ba7a5ce7bf0cfc372ec48203a34ece42f73aa2f3231706f61c55e105ecd0/pyarrow-25.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5fb2d837960f1df7f679ff9f1a55065e306347d379e0768cebf14781254d6194", size = 49958146, upload-time = "2026-07-10T08:25:46.98Z" }, - { url = "https://files.pythonhosted.org/packages/75/eb/c34a29fb7a70dca2f903c7d85a928928ef55af20cd56e99de6b4c0d897bc/pyarrow-25.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:add690feafa0953c443cdba9e9e87f5eaa198f1ea2e43a3b146ea83f202262d0", size = 53096264, upload-time = "2026-07-10T08:25:53.925Z" }, - { url = "https://files.pythonhosted.org/packages/36/f9/35b1f83a0727d84951588e4034aca2feb76dfb45b0725918c0037b0a48f7/pyarrow-25.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:d293e9959b29a24c82d936d04ab2b7fd8b8d334030de2e56a99aba94f008ad7a", size = 27840572, upload-time = "2026-07-10T08:25:58.966Z" }, { url = "https://files.pythonhosted.org/packages/a7/98/ae2b5acf9876dbeffa6f320776242c52caab062df55c8ac5501ed2679e74/pyarrow-25.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:2e3b6544e26e393fe2cd530f523e36c1c8d3c345bbbb60cca3fd866be8322517", size = 35939080, upload-time = "2026-07-10T08:26:04.53Z" }, { url = "https://files.pythonhosted.org/packages/80/09/3de2a968edbd496c86cb8b932cdbee2d4b08c4a28e9884a15e5c705a646b/pyarrow-25.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:b724d127783b4c19f088fcdfc844cbc318809246a30307bcabd5ed02045e890e", size = 37633420, upload-time = "2026-07-10T08:26:10.354Z" }, { url = "https://files.pythonhosted.org/packages/19/86/8399243a4ce080426ec37db18d5e29148b7ec960a8a8c7f9059a7bf6ef0a/pyarrow-25.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:244f98a595f70fa4fd35faa7508c4ae67e14a173397a4b3b49d2b3c360fb0062", size = 46861050, upload-time = "2026-07-10T08:26:16.397Z" }, @@ -2754,142 +2182,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] -[[package]] -name = "pydantic" -version = "2.13.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, -] - -[package.optional-dependencies] -email = [ - { name = "email-validator" }, -] - -[[package]] -name = "pydantic-core" -version = "2.46.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/08/f1ba952f1c8ae5581c70fa9c6da89f247b83e3dd8c09c035d5d7931fc23d/pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4", size = 2113146, upload-time = "2026-05-06T13:37:36.537Z" }, - { url = "https://files.pythonhosted.org/packages/56/c6/65f646c7ff09bd257f660434adb45c4dfcbbcebcc030562fecf6f5bf887d/pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5", size = 1949769, upload-time = "2026-05-06T13:37:46.365Z" }, - { url = "https://files.pythonhosted.org/packages/64/ba/bfb1d928fd5b49e1258935ff104ae356e9fd89384a55bf9f847e9193ad40/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba", size = 1974958, upload-time = "2026-05-06T13:37:28.611Z" }, - { url = "https://files.pythonhosted.org/packages/4e/74/76223bfb117b64af743c9b6670d1364516f5c0604f96b48f3272f6af6cc6/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b", size = 2042118, upload-time = "2026-05-06T13:36:55.216Z" }, - { url = "https://files.pythonhosted.org/packages/cb/7b/848732968bc8f48f3187542f08358b9d842db564147b256669426ebb1652/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c", size = 2222876, upload-time = "2026-05-06T13:38:25.455Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2f/e90b63ee2e14bd8d3db8f705a6d75d64e6ee1b7c2c8833747ce706e1e0ce/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50", size = 2286703, upload-time = "2026-05-06T13:37:53.304Z" }, - { url = "https://files.pythonhosted.org/packages/ba/1e/acc4d70f88a0a277e4a1fa77ebb985ceabaf900430f875bf9338e11c9420/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd", size = 2092042, upload-time = "2026-05-06T13:38:46.981Z" }, - { url = "https://files.pythonhosted.org/packages/a9/da/0a422b57bf8504102bf3c4ccea9c41bab5a5cee6a54650acf8faf67f5a24/pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01", size = 2117231, upload-time = "2026-05-06T13:39:23.146Z" }, - { url = "https://files.pythonhosted.org/packages/bd/2a/2ac13c3af305843e23c5078c53d135656b3f05a2fd78cb7bbbb12e97b473/pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d", size = 2168388, upload-time = "2026-05-06T13:40:08.06Z" }, - { url = "https://files.pythonhosted.org/packages/72/04/2beacf7e1607e93eefe4aed1b4709f079b905fb77530179d4f7c71745f22/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4", size = 2184769, upload-time = "2026-05-06T13:38:13.901Z" }, - { url = "https://files.pythonhosted.org/packages/9e/29/d2b9fd9f539133548eaf622c06a4ce176cb46ac59f32d0359c4abc0de047/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f", size = 2319312, upload-time = "2026-05-06T13:39:08.24Z" }, - { url = "https://files.pythonhosted.org/packages/7c/af/0f7a5b85fec6075bea96e3ef9187de38fccced0de92c1e7feda8d5cc7bb9/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39", size = 2361817, upload-time = "2026-05-06T13:38:43.2Z" }, - { url = "https://files.pythonhosted.org/packages/25/a4/73363fec545fd3ec025490bdda2743c56d0dd5b6266b1a53bbe9e4265375/pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d", size = 1987085, upload-time = "2026-05-06T13:39:25.497Z" }, - { url = "https://files.pythonhosted.org/packages/01/aa/62f082da2c91fac1c234bc9ee0066257ce83f0604abd72e4c9d5991f2d84/pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf", size = 2074311, upload-time = "2026-05-06T13:39:59.922Z" }, - { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" }, - { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" }, - { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" }, - { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" }, - { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" }, - { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" }, - { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" }, - { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" }, - { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" }, - { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" }, - { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" }, - { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" }, - { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" }, - { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" }, - { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" }, - { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, - { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, - { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, - { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, - { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, - { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, - { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, - { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, - { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, - { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, - { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, - { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, - { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, - { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, - { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, - { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, - { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, - { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, - { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, - { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, - { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, - { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, - { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, - { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, - { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, - { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, - { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, - { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, - { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, - { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, - { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, - { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, - { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, - { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, - { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, - { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, - { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, - { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, - { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, - { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, - { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, - { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, - { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, - { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, - { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, - { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, - { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, - { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, - { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, - { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, - { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, - { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, - { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, - { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, - { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, - { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, - { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, - { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, - { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, - { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" }, - { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" }, - { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" }, - { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" }, - { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, - { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, - { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, - { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" }, - { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" }, - { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" }, - { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" }, - { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" }, - { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" }, - { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" }, -] - [[package]] name = "pygments" version = "2.20.0" @@ -2899,15 +2191,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] -[[package]] -name = "pyreadline3" -version = "3.5.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/6d/f94028646d7bbe6d9d873c47ee7c246f2d29129d253f0d96cb6fcab70733/pyreadline3-3.5.6.tar.gz", hash = "sha256:61e53218b99656091ddb077df9e71f25850e72e030b6183b39c9b7e6e4f4a9bf", size = 100368, upload-time = "2026-05-14T17:55:04.471Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/5e/35c856e186b74678c24927847ad9895a51f1bc02a0c6126477a6c6040064/pyreadline3-3.5.6-py3-none-any.whl", hash = "sha256:8449b734232e42a5dcd74048e39b60db2839a4c38cf3ae2bf7707d58b5389c0d", size = 85243, upload-time = "2026-05-14T17:55:03.262Z" }, -] - [[package]] name = "pyright" version = "1.1.411" @@ -2927,12 +2210,10 @@ version = "9.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, { name = "pygments" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } wheels = [ @@ -2992,105 +2273,27 @@ name = "pytorch-lightning" version = "2.6.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "fsspec", extra = ["http"] }, - { name = "lightning-utilities" }, - { name = "packaging" }, - { name = "pyyaml" }, - { name = "torch" }, - { name = "torchmetrics" }, - { name = "tqdm" }, - { name = "typing-extensions" }, + { name = "fsspec", extra = ["http"], marker = "python_full_version < '3.13'" }, + { name = "lightning-utilities", marker = "python_full_version < '3.13'" }, + { name = "packaging", marker = "python_full_version < '3.13'" }, + { name = "pyyaml", marker = "python_full_version < '3.13'" }, + { name = "torch", marker = "python_full_version < '3.13'" }, + { name = "torchmetrics", marker = "python_full_version < '3.13'" }, + { name = "tqdm", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/52/2c/8e73a3929b4c4bd600cafd38a97aaf7242a8cf518fb9f33d27c274ec898f/pytorch_lightning-2.6.5.tar.gz", hash = "sha256:1c32cefa76a1a9c4c5250338272d961d1e48b180e68396849efe128538ddb28e", size = 661673, upload-time = "2026-05-27T14:33:41.961Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/8b/4d/5740c27110b83634d8491c3b5facf0111b3e554c3164f4fb953be9bddaf6/pytorch_lightning-2.6.5-py3-none-any.whl", hash = "sha256:62d9c8549b2278fedc3364f0a5607a56c6063d18635008f8cf3fae8d802b0d76", size = 852407, upload-time = "2026-05-27T14:33:39.856Z" }, ] -[[package]] -name = "pytz" -version = "2026.3.post1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fb/48/fb042503b6ca6cd271261dc559fd6432f7d8c713153e9ec5c591af4dfc1c/pytz-2026.3.post1.tar.gz", hash = "sha256:2211d3fcf9a797d3405cac96ac7f61d80e6a644f72a3309607282fe8a2010c5d", size = 319745, upload-time = "2026-07-25T15:12:07.385Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/7b/39c34ca613b0b198cb866466651b26b045e2009864c5183c979a3b83f383/pytz-2026.3.post1-py2.py3-none-any.whl", hash = "sha256:dd95840dd199baea12d9cc096a1d452caa6596a1c1e4b5f3dbd1541855d5e815", size = 508283, upload-time = "2026-07-25T15:12:05.782Z" }, -] - -[[package]] -name = "pywavelets" -version = "1.8.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11' and sys_platform == 'darwin'", - "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')", -] -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/48/45/bfaaab38545a33a9f06c61211fc3bea2e23e8a8e00fedeb8e57feda722ff/pywavelets-1.8.0.tar.gz", hash = "sha256:f3800245754840adc143cbc29534a1b8fc4b8cff6e9d403326bd52b7bb5c35aa", size = 3935274, upload-time = "2024-12-04T19:54:20.593Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/65/7e/c5e398f25c70558ca195dd4144ee004666401f6167084c1e76059d7e68d8/pywavelets-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f5c86fcb203c8e61d1f3d4afbfc08d626c64e4e3708207315577264c724632bf", size = 4323291, upload-time = "2024-12-04T19:53:01.836Z" }, - { url = "https://files.pythonhosted.org/packages/d0/d7/2fc8067c3520ce25f7632b0f47b89d1b75653cab804a42700e95126f2679/pywavelets-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fafb5fa126277e1690c3d6329287122fc08e4d25a262ce126e3d81b1f5709308", size = 4291864, upload-time = "2024-12-04T19:53:04.659Z" }, - { url = "https://files.pythonhosted.org/packages/2f/17/a868aa26e45c104613d9069f9d8ec0123687cb6945062d274f20a3992436/pywavelets-1.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dec23dfe6d5a3f4312b12456b8c546aa90a11c1138e425a885987505f0658ae0", size = 4447532, upload-time = "2024-12-04T19:53:06.383Z" }, - { url = "https://files.pythonhosted.org/packages/53/7a/7f5889a57177e2b1182080fc2c52236d1e03a0fad5e0b3d7c5312070c0be/pywavelets-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:880a0197e9fa108939af50a95e97c1bf9b7d3e148e0fad92ea60a9ed8c8947c0", size = 4487695, upload-time = "2024-12-04T19:53:08.84Z" }, - { url = "https://files.pythonhosted.org/packages/f9/e6/04d76d93c158919ef0d8e1d40d1d453168305031eca6733fdc844f7cbb07/pywavelets-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8bfa833d08b60d0bf53a7939fbbf3d98015dd34efe89cbe4e53ced880d085fc1", size = 4473752, upload-time = "2024-12-04T19:53:10.848Z" }, - { url = "https://files.pythonhosted.org/packages/3b/a7/42ea5bbb6055abd312e45b27d931200fd6eed5414a87ec5d62020a4c651b/pywavelets-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e10c3fc7f4a796e94da4bca9871be2186a7bb7a3b3536a0ca9376d84263140f0", size = 4504191, upload-time = "2024-12-04T19:53:13.912Z" }, - { url = "https://files.pythonhosted.org/packages/8c/7e/52df87a9e77adfb12c1b8be79a2053f2eb4c2507dec96ebfd2333b15ff03/pywavelets-1.8.0-cp310-cp310-win32.whl", hash = "sha256:31baf4be6940fde72cc85663154360857ac1b93c251822deaf72bb804da95031", size = 4143794, upload-time = "2024-12-04T19:53:16.296Z" }, - { url = "https://files.pythonhosted.org/packages/01/e2/06e08230c26049740b2773952fbb12cc7186e5df655a73b1c30ba493e864/pywavelets-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:560c39f1ff8cb37f8b8ea4b7b6eb8a14f6926c11f5cf8c09f013a58f895ed5bc", size = 4214262, upload-time = "2024-12-04T19:53:17.998Z" }, - { url = "https://files.pythonhosted.org/packages/6c/8a/9f8e794120b55caa1c4ae8d72696111bc408251615f351a8e54a5d8c4d4e/pywavelets-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e8dd5be4faed994581a8a4b3c0169be20567a9346e523f0b57f903c8f6722bce", size = 4324170, upload-time = "2024-12-04T19:53:19.66Z" }, - { url = "https://files.pythonhosted.org/packages/3e/b8/f6246be5c78e9fa73fcbba9ab4cbfe0d4dcb79ea5491f28d673a53466134/pywavelets-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d8abaf7c120b151ef309c9ff57e0a44ba9febf49045056dbc1577526ecec6c8", size = 4294254, upload-time = "2024-12-04T19:53:21.767Z" }, - { url = "https://files.pythonhosted.org/packages/2c/dc/ba1f212e9b43117ed28e0fd092e72e817790427400f88937ea742d260153/pywavelets-1.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b43a4c58707b1e8d941bec7f1d83e67c482278575ff0db3189d5c0dfae23a57", size = 4447178, upload-time = "2024-12-04T19:53:23.525Z" }, - { url = "https://files.pythonhosted.org/packages/58/10/e59c162a11d2fedb4454abbf7b74a52390aba5edc9605bf829bfa8708dac/pywavelets-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1aad0b97714e3079a2bfe48e4fb8ccd60778d0427e9ee5e0a9ff922e6c61e4", size = 4486799, upload-time = "2024-12-04T19:53:25.238Z" }, - { url = "https://files.pythonhosted.org/packages/03/ee/90c3d0a0a3bda74e6e097e4c06bff9446ff2a4c90b8617aaf4902c46966b/pywavelets-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0e1db96dcf3ce08156859df8b359e9ff66fa15061a1b90e70e020bf4cd077a0", size = 4486403, upload-time = "2024-12-04T19:53:26.954Z" }, - { url = "https://files.pythonhosted.org/packages/05/54/58b87f8b636a9f044f3f9814d2ec696cf25f3b33af97c11811f13c364085/pywavelets-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e62c8fb52ab0e8ff212fff9acae681a8f12d68b76c36fe24cc48809d5b6825ba", size = 4515011, upload-time = "2024-12-04T19:53:28.832Z" }, - { url = "https://files.pythonhosted.org/packages/a1/d0/f755cee11ff20668114942d0e777e2b502a8e4665e1fdb2553b587aac637/pywavelets-1.8.0-cp311-cp311-win32.whl", hash = "sha256:bf327528d10de471b04bb725c4e10677fac5a49e13d41bf0d0b3a1f6d7097abf", size = 4139934, upload-time = "2024-12-04T19:53:31.421Z" }, - { url = "https://files.pythonhosted.org/packages/7b/0b/f4b92d4f00565280ea3e62a8e3dc81a667d67ed7bd59232f2f18d55f9aff/pywavelets-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3814d354dd109e244ffaac3d480d29a5202212fe24570c920268237c8d276f95", size = 4214321, upload-time = "2024-12-04T19:53:33.183Z" }, - { url = "https://files.pythonhosted.org/packages/2d/8b/4870f11559307416470158a5aa6f61e5c2a910f1645a7a836ffae580b7ad/pywavelets-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3f431c9e2aff1a2240765eff5e804975d0fcc24c82d6f3d4271243f228e5963b", size = 4326187, upload-time = "2024-12-04T19:53:35.19Z" }, - { url = "https://files.pythonhosted.org/packages/c4/35/66835d889fd7fbf3119c7a9bd9d9bd567fc0bb603dfba408e9226db7cb44/pywavelets-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e39b0e2314e928cb850ee89b9042733a10ea044176a495a54dc84d2c98407a51", size = 4295428, upload-time = "2024-12-04T19:53:36.962Z" }, - { url = "https://files.pythonhosted.org/packages/63/1c/42e5130226538c70d4bbbaee00eb1bc06ec3287f7ea43d5fcf85bfc761ce/pywavelets-1.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cae701117f5c7244b7c8d48b9e92a0289637cdc02a9c205e8be83361f0c11fae", size = 4421259, upload-time = "2024-12-04T19:53:39.119Z" }, - { url = "https://files.pythonhosted.org/packages/6f/c5/1ce93657432e22a5debc21e8b52ec6980f819ecb7fa727bb86744224d967/pywavelets-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:649936baee933e80083788e0adc4d8bc2da7cdd8b10464d3b113475be2cc5308", size = 4447650, upload-time = "2024-12-04T19:53:41.589Z" }, - { url = "https://files.pythonhosted.org/packages/b9/d6/b54ef30daca71824f811f9d2322a978b0a58d27674b8e3af6520f67e9ec6/pywavelets-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8c68e9d072c536bc646e8bdce443bb1826eeb9aa21b2cb2479a43954dea692a3", size = 4448538, upload-time = "2024-12-04T19:53:44.308Z" }, - { url = "https://files.pythonhosted.org/packages/ce/8c/1688b790e55674667ad644262f174405c2c9873cb13e773432e78b1b33e4/pywavelets-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:63f67fa2ee1610445de64f746fb9c1df31980ad13d896ea2331fc3755f49b3ae", size = 4485228, upload-time = "2024-12-04T19:53:46.778Z" }, - { url = "https://files.pythonhosted.org/packages/c9/9b/69de31c3b663dadd76d1da6bf8af68d8cefff55df8e880fe96a94bb8c9ac/pywavelets-1.8.0-cp312-cp312-win32.whl", hash = "sha256:4b3c2ab669c91e3474fd63294355487b7dd23f0b51d32f811327ddf3546f4f3d", size = 4134850, upload-time = "2024-12-04T19:53:49.101Z" }, - { url = "https://files.pythonhosted.org/packages/1c/88/9e2aa9d5fde08bfc0fb18ffb1b5307c1ed49c24930b4147e5f48571a7251/pywavelets-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:810a23a631da596fef7196ddec49b345b1aab13525bb58547eeebe1769edbbc1", size = 4210786, upload-time = "2024-12-04T19:53:51.546Z" }, - { url = "https://files.pythonhosted.org/packages/94/73/7ff347d77c6bda11330565050c3346c54bc210086380abeb84e402c1c9cd/pywavelets-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:441ba45c8dff8c6916dbe706958d0d7f91da675695ca0c0d75e483f6f52d0a12", size = 4321474, upload-time = "2024-12-04T19:53:53.369Z" }, - { url = "https://files.pythonhosted.org/packages/b0/70/c58937ebbca1aba3475ca5ee63c7bcebf09f3c93891ae5942eaec7e95707/pywavelets-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:24bb282bab09349d9d128ed0536fa50fff5c2147891971a69c2c36155dfeeeac", size = 4291502, upload-time = "2024-12-04T19:53:55.396Z" }, - { url = "https://files.pythonhosted.org/packages/da/55/87b4ad6128b2e85985908e958e856e0b680cdcc03cc490e2cc995164b13a/pywavelets-1.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:426ff3799446cb4da1db04c2084e6e58edfe24225596805665fd39c14f53dece", size = 4412669, upload-time = "2024-12-04T19:53:57.393Z" }, - { url = "https://files.pythonhosted.org/packages/bf/1a/bfca9eab23bd7b27843b0ce95c47796033a7b2c93048315f5fc5d6ac6428/pywavelets-1.8.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa0607a9c085b8285bc0d04e33d461a6c80f8c325389221ffb1a45141861138e", size = 4454604, upload-time = "2024-12-04T19:53:59.105Z" }, - { url = "https://files.pythonhosted.org/packages/c3/23/9ce38829f57159e812c469c4f9d7b5a16c1ba922c1802985e8c504468206/pywavelets-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d31c36a39110e8fcc7b1a4a11cfed7d22b610c285d3e7f4fe73ec777aa49fa39", size = 4445507, upload-time = "2024-12-04T19:54:00.78Z" }, - { url = "https://files.pythonhosted.org/packages/e5/d2/e78a976b0600a6ef7a70f4430122d6ad11b3e1cbda3c8b3565661d094678/pywavelets-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa7c68ed1e5bab23b1bafe60ccbcf709b878652d03de59e961baefa5210fcd0a", size = 4479078, upload-time = "2024-12-04T19:54:02.46Z" }, - { url = "https://files.pythonhosted.org/packages/85/4d/1c4f870010368f3aeb0bdd72929376a1988e4a122e76545bd8c56e549c96/pywavelets-1.8.0-cp313-cp313-win32.whl", hash = "sha256:2c6b359b55d713ef683e9da1529181b865a80d759881ceb9adc1c5742e4da4d8", size = 4133763, upload-time = "2024-12-04T19:54:04.016Z" }, - { url = "https://files.pythonhosted.org/packages/c9/4f/0a709a5732e6cf9297fc87bf545cb879997cde204115f8c0cbc296c5bdd3/pywavelets-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:4dbebcfd55ea8a85b7fc8802d411e75337170422abf6e96019d7e46c394e80e5", size = 4209548, upload-time = "2024-12-04T19:54:06.61Z" }, - { url = "https://files.pythonhosted.org/packages/de/2a/4cac0bba67d3bc0ad697d0680539864db0a6964c7ad953d8d9d887f360b3/pywavelets-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:2e1c79784bebeafd3715c1bea6621daa2e2e6ed37b687719322e2078fb35bb70", size = 4335183, upload-time = "2024-12-04T19:54:08.349Z" }, - { url = "https://files.pythonhosted.org/packages/58/d1/3abe4cf34a35b09ad847f0e9a85f340c1988611222926d295fa8710659e7/pywavelets-1.8.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f489380c95013cc8fb3ef338f6d8c1a907125db453cc4dc739e2cca06fcd8b6", size = 4454723, upload-time = "2024-12-04T19:54:11.007Z" }, - { url = "https://files.pythonhosted.org/packages/d5/62/f05dd191232ae94e0b48509bb0ee65c9d45abf5e8f3612b09fd309b41384/pywavelets-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:06786201a91b5e74540f4f3c115c49a29190de2eb424823abbd3a1fd75ea3e28", size = 4472192, upload-time = "2024-12-04T19:54:12.754Z" }, - { url = "https://files.pythonhosted.org/packages/20/6a/257c95ad1e0fd395cbccd4ffec0d01cc9b51a3bb91e67d8fa10ffebc9c72/pywavelets-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:f2877fb7b58c94211257dcf364b204d6ed259146fc87d5a90bf9d93c97af6226", size = 4183968, upload-time = "2024-12-04T19:54:15.099Z" }, - { url = "https://files.pythonhosted.org/packages/6c/58/7179fd6f87153f2e339171e8cfe9bf901398a89045eefd7a3911bb9b47ad/pywavelets-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ec5d723c3335ff8aa630fd4b14097077f12cc02893c91cafd60dd7b1730e780f", size = 4265431, upload-time = "2024-12-04T19:54:16.928Z" }, -] - [[package]] name = "pywavelets" version = "1.9.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'darwin'", - "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'darwin'", - "python_full_version >= '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "(python_full_version >= '3.14' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", - "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", - "(python_full_version >= '3.12' and python_full_version < '3.14' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", - "python_full_version == '3.11.*' and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and sys_platform == 'win32'", - "python_full_version == '3.11.*' and sys_platform == 'emscripten'", - "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", -] dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5a/75/50581633d199812205ea8cdd0f6d52f12a624886b74bf1486335b67f01ff/pywavelets-1.9.0.tar.gz", hash = "sha256:148d12203377772bea452a59211d98649c8ee4a05eff019a9021853a36babdc8", size = 3938340, upload-time = "2025-08-04T16:20:04.978Z" } wheels = [ @@ -3150,15 +2353,6 @@ version = "6.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, - { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, - { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, - { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, - { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, - { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, - { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, @@ -3214,23 +2408,6 @@ version = "2026.7.19" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/20/98/04b13f1ddfb63158025291c02e03eb42fbb7acb51d091d541050eb4e35e8/regex-2026.7.19.tar.gz", hash = "sha256:7e77b324909c1617cbb4c668677e2c6ae13f44d7c1de0d4f15f2e3c10f3315b5", size = 416440, upload-time = "2026-07-19T00:19:48.923Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/24/13/bbf7d9d1887fe4a3693527c6caa232c197ea9da91f1212e9672eff60329d/regex-2026.7.19-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:555497390743af1a65045fa4527782d10ff5b88970359412baa4a1e628fe393b", size = 494009, upload-time = "2026-07-19T00:16:13.602Z" }, - { url = "https://files.pythonhosted.org/packages/a3/19/783688e75a2bec15d50aec0d5e7e317d363808bc82a6eb6750b897bfcd7b/regex-2026.7.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:343a4504e3fb688c47cad451221ca5d4814f42b1e16c0065bde9cbf7f473bd52", size = 295287, upload-time = "2026-07-19T00:16:15.702Z" }, - { url = "https://files.pythonhosted.org/packages/16/01/cefe4f051302ca298d3f3e79ed6dbd933ac84485b9515acdd6a52d70cef7/regex-2026.7.19-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ebee1ee89c39c953baac6924fcde08c5bb427c4057510862f9d7c7bdb3d8665", size = 290633, upload-time = "2026-07-19T00:16:17.182Z" }, - { url = "https://files.pythonhosted.org/packages/ae/1e/1045ca2cabb12e8ec41ad0d138e9f3ff1eb079d30a6f51ccf7d709b44aad/regex-2026.7.19-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:062f8cb7a9739c4835d22bd96f370c59aba89f257adcfa53be3cc209e08d3ae0", size = 785300, upload-time = "2026-07-19T00:16:18.505Z" }, - { url = "https://files.pythonhosted.org/packages/d5/4a/20e5bca184e90bf1bd187efdb53363f4a7b7b34f01d54ced5740caf104bd/regex-2026.7.19-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1123ef4211d763ee771d47916a1596e2f4915794f7aabdc1adcb20e4249a6951", size = 854079, upload-time = "2026-07-19T00:16:19.909Z" }, - { url = "https://files.pythonhosted.org/packages/09/9b/5a2e59678be3b24aa6a42b2c6d66a48daa212593e9f4096fe7ba577fa9b1/regex-2026.7.19-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6e44c0e7c5664be20aee92085153150c0a7967310a73a43c0f832b7cd35d0dd3", size = 899496, upload-time = "2026-07-19T00:16:21.453Z" }, - { url = "https://files.pythonhosted.org/packages/48/9a/7317f14ed8ed9fd998d1978b4802b07bc4d79216353c435dbcc1ddd1301f/regex-2026.7.19-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98c6ac18480fcdb33f35439183f1d2e79760ab41930309c6d951cb1f8e46694c", size = 793541, upload-time = "2026-07-19T00:16:22.991Z" }, - { url = "https://files.pythonhosted.org/packages/6f/53/833c2db3e274d3c191f4c42fe5bfa358e4c8b617d5d7312d31334965fc46/regex-2026.7.19-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4458124d71339f505bf1fb94f69fd1bb8fa9d2481eebfef27c10ef4f2b9e12f6", size = 785515, upload-time = "2026-07-19T00:16:24.654Z" }, - { url = "https://files.pythonhosted.org/packages/8d/b9/efb2f9fa151d71db09d4015e1fb92fee47416f01c12164836bbd23e2f3c2/regex-2026.7.19-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbf300e2070bb35038660b3be1be4b91b0024edb41517e6996320b49b92b4175", size = 769556, upload-time = "2026-07-19T00:16:26.207Z" }, - { url = "https://files.pythonhosted.org/packages/81/4d/45610c263f8eadb84e4a1fabd904d81d5176226faa9104ef498bf8a8b285/regex-2026.7.19-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b2b506b1788df5fecd270a10d5e70a95fe77b87ea2b370a318043f6f5f817ee6", size = 774130, upload-time = "2026-07-19T00:16:27.786Z" }, - { url = "https://files.pythonhosted.org/packages/40/95/1b40d87c7a9e5480bec7a87bce9fd67fc3f14b5f106c8ee66d660249072f/regex-2026.7.19-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:52579c60a6078be70a0e49c81d6e56d677f34cd439af281a0083b8c7bc75c095", size = 848694, upload-time = "2026-07-19T00:16:29.412Z" }, - { url = "https://files.pythonhosted.org/packages/17/8b/bb45968addd5b394ef9cd9184bd9c65ade1a819dbb2b92b71ad52a0c7907/regex-2026.7.19-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:2955907b7157a6660f27079edf7e0229e9c9c5325c77a2ef6a890cba91efa6f0", size = 758505, upload-time = "2026-07-19T00:16:31.006Z" }, - { url = "https://files.pythonhosted.org/packages/bf/6f/33386c672fbf43e21602135a0f29a97ee251a483f007fe51d10e9b2dbc93/regex-2026.7.19-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:89dfee3319f5ae3f75ebd5c2445a809bb320252ba5529ffdafea4ef25d79cf1a", size = 836985, upload-time = "2026-07-19T00:16:32.459Z" }, - { url = "https://files.pythonhosted.org/packages/22/f1/9112b86e9bb075619862e8e42b604794389f1958faa68fb69bde505dd90e/regex-2026.7.19-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d3143f159261b1ce5b24c261c590e5913370c3200c5e9ebbb92b5aa5e111902", size = 782610, upload-time = "2026-07-19T00:16:33.857Z" }, - { url = "https://files.pythonhosted.org/packages/3a/f9/13d460d8a385ca0b0be9e6be80a90968b9293b3e30895543ad2d1d1653e4/regex-2026.7.19-cp310-cp310-win32.whl", hash = "sha256:64729333167c2dcaaa56a331d40ee097bd9c5617ffd51dabb09eaddafb1b532e", size = 266772, upload-time = "2026-07-19T00:16:35.194Z" }, - { url = "https://files.pythonhosted.org/packages/9f/90/29addd7a03e1aea402c1f31467e25c80caabc8c3735b88a23cf73b0aa9c2/regex-2026.7.19-cp310-cp310-win_amd64.whl", hash = "sha256:1c398716054621aa300b3d411f467dda903806c5da0df6945ab73982b8d115db", size = 277967, upload-time = "2026-07-19T00:16:36.847Z" }, - { url = "https://files.pythonhosted.org/packages/8c/ba/ecfce06fe66c122bc6f77ae284887a9282e4411fe1e6268c5266611ca054/regex-2026.7.19-cp310-cp310-win_arm64.whl", hash = "sha256:064f1760a5a4ade65c5419be23e782f29147528e8a66e0c42dd4cedb8d4e9fc6", size = 276963, upload-time = "2026-07-19T00:16:38.315Z" }, { url = "https://files.pythonhosted.org/packages/05/e5/cef4de2bac939280b68d32adc659478845238a8274f2f79c465063f590ad/regex-2026.7.19-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ac777001cdfc28b72477d93c8564bb7583081ea8fb45cdca3d568e0a4f87183c", size = 494012, upload-time = "2026-07-19T00:16:39.927Z" }, { url = "https://files.pythonhosted.org/packages/ff/87/e86f51eb117457bb7803132ffe5cb6e2841e2b5bea4cc85d397f3c6e257d/regex-2026.7.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:59787bd5f8c70aa339084e961d2996b53fbdeab4d5393bba5c1fe1fc32e02bae", size = 295281, upload-time = "2026-07-19T00:16:41.433Z" }, { url = "https://files.pythonhosted.org/packages/41/2e/2360c41d8080a3d9ec7e5c90fad6eab3b50192869d10e9a5609e48c8177b/regex-2026.7.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:90c633e7e8d6bf4e992b8b36ce69e018f834b641dd6de8cea6d78c06ffa119c5", size = 290615, upload-time = "2026-07-19T00:16:43.058Z" }, @@ -3344,52 +2521,48 @@ dependencies = [ [package.optional-dependencies] all = [ { name = "accelerate" }, - { name = "av", version = "16.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "av", version = "18.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "av" }, { name = "diffsynth" }, { name = "diffusers" }, { name = "huggingface-hub" }, - { name = "numpy" }, - { name = "onnxruntime", version = "1.23.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "onnxruntime", version = "1.28.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "onnxruntime" }, { name = "opencv-python-headless" }, { name = "pillow-heif" }, - { name = "pywavelets", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "pywavelets", version = "1.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pywavelets" }, { name = "safetensors" }, { name = "tokenizers" }, { name = "torch" }, { name = "torchvision" }, { name = "transformers" }, - { name = "trustmark" }, + { name = "trustmark", marker = "python_full_version < '3.13'" }, ] detect = [ - { name = "numpy" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "opencv-python-headless" }, - { name = "pywavelets", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "pywavelets", version = "1.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pywavelets" }, ] dev = [ - { name = "av", version = "16.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "av", version = "18.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "av" }, { name = "invisible-watermark" }, - { name = "numpy" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "opencv-python-headless" }, { name = "packaging" }, { name = "pyright" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-xdist" }, - { name = "pywavelets", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "pywavelets", version = "1.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pywavelets" }, { name = "ruff" }, - { name = "uv-outdated", marker = "python_full_version >= '3.12'" }, - { name = "uv-secure", marker = "python_full_version >= '3.12'" }, ] diffusion = [ { name = "accelerate" }, { name = "diffusers" }, - { name = "numpy" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "opencv-python-headless" }, { name = "safetensors" }, { name = "tokenizers" }, @@ -3401,27 +2574,29 @@ heif = [ ] lama = [ { name = "huggingface-hub" }, - { name = "numpy" }, - { name = "onnxruntime", version = "1.23.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "onnxruntime", version = "1.28.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "onnxruntime" }, { name = "opencv-python-headless" }, ] migan = [ { name = "huggingface-hub" }, - { name = "numpy" }, - { name = "onnxruntime", version = "1.23.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "onnxruntime", version = "1.28.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "onnxruntime" }, { name = "opencv-python-headless" }, ] pixels = [ - { name = "numpy" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "opencv-python-headless" }, ] qwen-zimage = [ { name = "accelerate" }, { name = "diffsynth" }, { name = "diffusers" }, - { name = "numpy" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "opencv-python-headless" }, { name = "safetensors" }, { name = "tokenizers" }, @@ -3430,24 +2605,24 @@ qwen-zimage = [ { name = "transformers" }, ] trustmark = [ - { name = "trustmark" }, + { name = "trustmark", marker = "python_full_version < '3.13'" }, ] video = [ - { name = "av", version = "16.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "av", version = "18.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "numpy" }, + { name = "av" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "opencv-python-headless" }, ] visible = [ - { name = "numpy" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "opencv-python-headless" }, ] [package.metadata] requires-dist = [ { name = "accelerate", marker = "extra == 'diffusion'", specifier = ">=0.25.0" }, - { name = "av", marker = "python_full_version >= '3.11' and extra == 'video'", specifier = ">=18,<19" }, - { name = "av", marker = "python_full_version < '3.11' and extra == 'video'", specifier = ">=16,<17" }, + { name = "av", marker = "extra == 'video'", specifier = ">=18,<19" }, { name = "c2pa-python", specifier = ">=0.35.0" }, { name = "click", specifier = ">=8.0.0" }, { name = "diffsynth", marker = "extra == 'qwen-zimage'", specifier = ">=2.0.17,<3" }, @@ -3455,11 +2630,10 @@ requires-dist = [ { name = "huggingface-hub", marker = "extra == 'lama'", specifier = ">=0.20.0" }, { name = "huggingface-hub", marker = "extra == 'migan'", specifier = ">=0.20.0" }, { name = "invisible-watermark", marker = "extra == 'dev'", specifier = ">=0.2.0" }, - { name = "numpy", marker = "extra == 'pixels'", specifier = ">=1.24.0" }, - { name = "onnxruntime", marker = "python_full_version >= '3.11' and extra == 'lama'", specifier = ">=1.16.0" }, - { name = "onnxruntime", marker = "python_full_version >= '3.11' and extra == 'migan'", specifier = ">=1.16.0" }, - { name = "onnxruntime", marker = "python_full_version < '3.11' and extra == 'lama'", specifier = ">=1.16.0,<1.24" }, - { name = "onnxruntime", marker = "python_full_version < '3.11' and extra == 'migan'", specifier = ">=1.16.0,<1.24" }, + { name = "numpy", marker = "python_full_version >= '3.13' and extra == 'pixels'", specifier = ">=2" }, + { name = "numpy", marker = "python_full_version < '3.13' and extra == 'pixels'", specifier = ">=1.24.0" }, + { name = "onnxruntime", marker = "extra == 'lama'", specifier = ">=1.24.0" }, + { name = "onnxruntime", marker = "extra == 'migan'", specifier = ">=1.24.0" }, { name = "opencv-python-headless", marker = "extra == 'pixels'", specifier = ">=4.8.0" }, { name = "packaging", marker = "extra == 'dev'", specifier = ">=24.0" }, { name = "piexif", specifier = ">=1.1.3" }, @@ -3487,9 +2661,7 @@ requires-dist = [ { name = "torch", marker = "extra == 'diffusion'", specifier = ">=2.0.0" }, { name = "torchvision", marker = "extra == 'qwen-zimage'", specifier = ">=0.20.0" }, { name = "transformers", marker = "extra == 'diffusion'", specifier = ">=5,<6" }, - { name = "trustmark", marker = "extra == 'trustmark'", specifier = ">=0.8.0" }, - { name = "uv-outdated", marker = "python_full_version >= '3.12' and extra == 'dev'", specifier = ">=0.1.0" }, - { name = "uv-secure", marker = "python_full_version >= '3.12' and extra == 'dev'", specifier = ">=0.12.0" }, + { name = "trustmark", marker = "python_full_version < '3.13' and extra == 'trustmark'", specifier = ">=0.8.0" }, ] provides-extras = ["pixels", "heif", "visible", "video", "detect", "diffusion", "qwen-zimage", "trustmark", "lama", "migan", "dev", "all"] @@ -3576,13 +2748,6 @@ version = "0.2.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/cc/33/ea3cb3839607eb175da835244a798f797f478c5ddf0e8ecdf57ea85a4c70/sentencepiece-0.2.2.tar.gz", hash = "sha256:3d2b5e824b5622038dc7b490897efe05ebbbb9e7350fc142f3ecc8789ef9bdf6", size = 8218435, upload-time = "2026-07-12T08:39:34.701Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/71/1b/e6c69e4c2026ed575d68dda2847a404468ca7b5fa684bb0b19f71d82d29d/sentencepiece-0.2.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bc7b0b1da20f856bfac5f84b2673fe534b167e41980b27442ca8f78c2b7eb77e", size = 2180607, upload-time = "2026-07-12T08:38:01.018Z" }, - { url = "https://files.pythonhosted.org/packages/36/5a/2a1d84c87dc075d4f8cf1a2470a95399e59834e219ffb5f4285533e750d0/sentencepiece-0.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8b2db2056c97224e122054fd794543cde5d24b7cae28424f6e3eb79bbe08e42b", size = 1437502, upload-time = "2026-07-12T08:38:02.899Z" }, - { url = "https://files.pythonhosted.org/packages/1b/39/3d43a75dd5a22503ca5074d0d37707cabb2e4a71b4bc6e6c61be3643cc7a/sentencepiece-0.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f1f61592e7cabd45d49ce8cc0ef42ca655c091e037153754fb3fa59725b5914", size = 1345667, upload-time = "2026-07-12T08:38:04.657Z" }, - { url = "https://files.pythonhosted.org/packages/90/d5/a69a8cc896e7de3fe2061b08c2f33e28656f243bed8af6a2df9f5d8c3124/sentencepiece-0.2.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c798f0b327bac10dc95cdac77b9a197ab2bd7dd1e60ebd7586a12d918d4be711", size = 1322864, upload-time = "2026-07-12T08:38:06.49Z" }, - { url = "https://files.pythonhosted.org/packages/e4/79/dd1836df32971d4eb14ff5cb4a8b3fe4419adbeada8e81d09dc53c5c0ef0/sentencepiece-0.2.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44284adc6fbe9d5bdd480541431a3d93f674fa44736714d3ad4bcee8283ace7d", size = 1392757, upload-time = "2026-07-12T08:38:08.559Z" }, - { url = "https://files.pythonhosted.org/packages/26/83/c3547715c29b7e4c84a180a240267f7685dde6f9b981396f16b95405ec9d/sentencepiece-0.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:1120e0791540615e650b2e9bea835bf38a7362455d8ab62dee7968219c2d79a0", size = 1245044, upload-time = "2026-07-12T08:38:10.21Z" }, - { url = "https://files.pythonhosted.org/packages/1f/55/7da03b35582a4eb276f99051109f3e3e8f176835b6d6837422e4c3a013dd/sentencepiece-0.2.2-cp310-cp310-win_arm64.whl", hash = "sha256:524e2a85c028a0d2f9935191fa751e5ef9d9bcc39616f70ab14b28d0369c9936", size = 1190467, upload-time = "2026-07-12T08:38:12.07Z" }, { url = "https://files.pythonhosted.org/packages/20/31/f23a2efaa0210b883574001b88fa64e499f798f0848a0b610fb9b384d162/sentencepiece-0.2.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:69e9dc8078e128286ed3b975e37c837ba96e215a50c3ef9f3f8b7ab9e5a832a0", size = 2184255, upload-time = "2026-07-12T08:38:14.855Z" }, { url = "https://files.pythonhosted.org/packages/96/f2/1ee0ccb772d71e822f625d6cb5f0ea825835e877f28a9ef299a1291df19e/sentencepiece-0.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6dd76f3e5c8b2eb8a3a3efee787bbf5b9a66e52a048fe09cab85eca33fec6790", size = 1438545, upload-time = "2026-07-12T08:38:16.674Z" }, { url = "https://files.pythonhosted.org/packages/2a/92/3a6ea4a2c6dd9e7062698a5a33534ca0e20844883338ae9c6b9c122c1a9f/sentencepiece-0.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:443ac618c7a2a1377cf5c82581fbb849591d14e656d5e5a3e4682d4e36a34e4e", size = 1346997, upload-time = "2026-07-12T08:38:18.499Z" }, @@ -3654,18 +2819,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] -[[package]] -name = "stamina" -version = "26.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "tenacity" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/80/bd/b2f71ae14368a066f103d182f25bbc6c3bf4aa695889f3ed3cba026d6f36/stamina-26.1.0.tar.gz", hash = "sha256:0214d05fdf5102c518194a4aac7520ce53cf660550ae3b940701aad88cf50c17", size = 568171, upload-time = "2026-04-13T17:44:31.012Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/f0/1ff90a1d1dd02de23feafdf9dffaecef3958348be5c192df56670ccb4f86/stamina-26.1.0-py3-none-any.whl", hash = "sha256:62e06829bec87c06d4cafde520b32a6097d1017c378a9eb63253c5bf5ebbbb88", size = 18508, upload-time = "2026-04-13T17:44:29.545Z" }, -] - [[package]] name = "sympy" version = "1.14.0" @@ -3678,15 +2831,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, ] -[[package]] -name = "tenacity" -version = "9.1.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, -] - [[package]] name = "tokenizers" version = "0.22.2" @@ -3711,10 +2855,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fd/18/a545c4ea42af3df6effd7d13d250ba77a0a86fb20393143bbb9a92e434d4/tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92", size = 2502363, upload-time = "2026-01-05T10:45:20.593Z" }, { url = "https://files.pythonhosted.org/packages/65/71/0670843133a43d43070abeb1949abfdef12a86d490bea9cd9e18e37c5ff7/tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48", size = 2747786, upload-time = "2026-01-05T10:45:18.411Z" }, { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133, upload-time = "2026-01-05T10:45:17.232Z" }, - { url = "https://files.pythonhosted.org/packages/84/04/655b79dbcc9b3ac5f1479f18e931a344af67e5b7d3b251d2dcdcd7558592/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:753d47ebd4542742ef9261d9da92cd545b2cacbb48349a1225466745bb866ec4", size = 3282301, upload-time = "2026-01-05T10:40:34.858Z" }, - { url = "https://files.pythonhosted.org/packages/46/cd/e4851401f3d8f6f45d8480262ab6a5c8cb9c4302a790a35aa14eeed6d2fd/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e10bf9113d209be7cd046d40fbabbaf3278ff6d18eb4da4c500443185dc1896c", size = 3161308, upload-time = "2026-01-05T10:40:40.737Z" }, - { url = "https://files.pythonhosted.org/packages/6f/6e/55553992a89982cd12d4a66dddb5e02126c58677ea3931efcbe601d419db/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64d94e84f6660764e64e7e0b22baa72f6cd942279fdbb21d46abd70d179f0195", size = 3718964, upload-time = "2026-01-05T10:40:46.56Z" }, - { url = "https://files.pythonhosted.org/packages/59/8c/b1c87148aa15e099243ec9f0cf9d0e970cc2234c3257d558c25a2c5304e6/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f01a9c019878532f98927d2bacb79bbb404b43d3437455522a00a30718cdedb5", size = 3373542, upload-time = "2026-01-05T10:40:52.803Z" }, ] [[package]] @@ -3780,41 +2920,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, ] -[[package]] -name = "tomlkit" -version = "0.15.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/96/e07752635b98536177fa1f37671c8f3cdde2e724c6bcf6034b2cfb571565/tomlkit-0.15.1.tar.gz", hash = "sha256:e25bbf38843005246210a12982776f27f99cb9be67160e14434d0c0d21ee1e97", size = 180129, upload-time = "2026-07-17T01:48:04.562Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/13/bc/8c13eb66537dce1d2bd3a57132902f38d0e7f5bb46fa9f4daed9fe9d76ee/tomlkit-0.15.1-py3-none-any.whl", hash = "sha256:177a05aece5a8ca5266fd3c448abb47b8d352f09d477d3ca8332db4d89b24304", size = 49449, upload-time = "2026-07-17T01:48:05.728Z" }, -] - [[package]] name = "torch" version = "2.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cuda-bindings", marker = "python_full_version < '3.15' and sys_platform == 'linux'" }, + { name = "cuda-bindings", marker = "sys_platform == 'linux'" }, { name = "cuda-toolkit", extra = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "sys_platform == 'linux'" }, { name = "filelock" }, { name = "fsspec" }, { name = "jinja2" }, - { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "networkx" }, { name = "nvidia-cudnn-cu13", marker = "sys_platform == 'linux'" }, { name = "nvidia-cusparselt-cu13", marker = "sys_platform == 'linux'" }, { name = "nvidia-nccl-cu13", marker = "sys_platform == 'linux'" }, { name = "nvidia-nvshmem-cu13", marker = "sys_platform == 'linux'" }, { name = "setuptools" }, { name = "sympy" }, - { name = "triton", marker = "python_full_version < '3.15' and sys_platform == 'linux'" }, + { name = "triton", marker = "sys_platform == 'linux'" }, { name = "typing-extensions" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/e7/19894fdb51c7dbaf94f5a79bb0871da0992e8e4241e579cb006da46d2e58/torch-2.13.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:94f0de129916f77b8dc2c7a8eff644cfeddfe59e39c9f55e9f6e17543410281d", size = 111178962, upload-time = "2026-07-08T16:05:49.855Z" }, - { url = "https://files.pythonhosted.org/packages/d1/5c/b1d5de470c54e339b30a92d96683a71bcebd78f5f2a7fc714cd6dc6bbd68/torch-2.13.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:0ab4b69f3ee03a62a002cfbf77b1ca5e88aceb4ea64cb4388bb28f638ddbb045", size = 427198333, upload-time = "2026-07-08T16:05:36.847Z" }, - { url = "https://files.pythonhosted.org/packages/50/c0/68a84105e1fcb8970144b388ff3d3e5dc15a3be28c1e247841f7d7247e41/torch-2.13.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c78b7b4d04461855a764cf01bae9a462bb88bc93defcfa11235cbc8fdf3e12c4", size = 526555154, upload-time = "2026-07-08T16:05:06.507Z" }, - { url = "https://files.pythonhosted.org/packages/2e/c9/0bb9d097b03cbaf96bb75b15e867347b8e41bfcdfe0539452d17d9e63993/torch-2.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:2bd30b6b730d987fa386ce3898933762c5cb8cc82eb0535211d787cc3ce2dfeb", size = 122015602, upload-time = "2026-07-08T16:05:45.25Z" }, { url = "https://files.pythonhosted.org/packages/5b/fe/cba54dc58523434919b66f13a667e36e436deddd77ca519e96553617d4ec/torch-2.13.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:e76f9bcecc52b8ff711239a2f7547d5353df95878ab232f0773c1d95928b92f8", size = 111187938, upload-time = "2026-07-08T16:05:17.065Z" }, { url = "https://files.pythonhosted.org/packages/c2/59/1e3160e18e12aa3038390efab3ce02b36a9d4d6a527ecdd8520dca2e68d8/torch-2.13.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:092790c696a760c729fd5722835f50b9d81fd7c8f141571f3f3cf4081a8f664c", size = 427199369, upload-time = "2026-07-08T16:04:51.054Z" }, { url = "https://files.pythonhosted.org/packages/01/79/1f2d34ad7034ee1c7ffc1cf8bf0f8213af2a81df6ecdb3997ecec107c09d/torch-2.13.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:60fcdcb2f3876e21146cb4524ef06397d727ca9ad5f020818547e25075fe3cb7", size = 526574961, upload-time = "2026-07-08T16:04:07.075Z" }, @@ -3842,10 +2968,10 @@ name = "torchmetrics" version = "1.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "lightning-utilities" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "torch" }, + { name = "lightning-utilities", marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "packaging", marker = "python_full_version < '3.13'" }, + { name = "torch", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/81/34/39b8b749333db56c0585d7a11fa62a283c087bb1dfc897d69fb8cedbefb1/torchmetrics-1.9.0.tar.gz", hash = "sha256:a488609948600df52d3db4fcdab02e62aab2a85ef34da67037dc3e65b8512faa", size = 581765, upload-time = "2026-03-09T17:41:22.443Z" } wheels = [ @@ -3857,15 +2983,12 @@ name = "torchvision" version = "0.28.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "pillow" }, { name = "torch" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/df/1ba039ad6cfe6e69209c36766b9b6e8c6fe92481c6d4e4ca52296f5f699d/torchvision-0.28.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:2a1ef4b6f4bf5828b48cfad97372c8982db906830884b2868ba5c3df937a7d81", size = 1856019, upload-time = "2026-07-08T16:07:59.283Z" }, - { url = "https://files.pythonhosted.org/packages/88/ea/5c70ecf86f8e95174a85061cea78683a7bb7f422f09c3f3d4f30b7600fa9/torchvision-0.28.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:546fd85345cf8652f6cd099d4f9884b0ca5c2f3fae78689a21dd2f35ea6b622f", size = 7838211, upload-time = "2026-07-08T16:07:27.023Z" }, - { url = "https://files.pythonhosted.org/packages/46/22/2f7ff1997d793e45d85fafa8374ee25348b7dae9ac521ba8751d7e1c75d5/torchvision-0.28.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6dfb0f45e2b4ceb4e76f158c3fbb5f44387099f3c466e3423a09ab665a194aba", size = 7669419, upload-time = "2026-07-08T16:07:41.648Z" }, - { url = "https://files.pythonhosted.org/packages/42/d0/2b3c30834ff23acd3854d0ff59bc580711f4b36d725de40105a852ed3719/torchvision-0.28.0-cp310-cp310-win_amd64.whl", hash = "sha256:7fad44dc9582570c7d92c4487d36ac46998f40cc39b438e8b8f5111a935ce4e8", size = 3500355, upload-time = "2026-07-08T16:07:56.865Z" }, { url = "https://files.pythonhosted.org/packages/7a/b2/1e010052079e4c577007b789db336ea7075f1a426e84d17121fbc3745516/torchvision-0.28.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:83fe6c020866a85acd7d97deccc45ff11d66daf42916d04396a4309c66c0ccb8", size = 1856017, upload-time = "2026-07-08T16:07:55.533Z" }, { url = "https://files.pythonhosted.org/packages/27/be/1b9c5de9c655ca2df4a74100fa671a7b848532ff787e077ccde14a7dea2a/torchvision-0.28.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5a38bc6da3d72621be003400b66f66a2b4c6d644fde05f680c2cb7ca8cf8dd6c", size = 7841822, upload-time = "2026-07-08T16:07:49.207Z" }, { url = "https://files.pythonhosted.org/packages/0b/9b/f1e68e861d4462e3e195a642c2b448e7b7d3fad5f209487162b9a2133d9b/torchvision-0.28.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:7e80f543b22503d9415e126db5f0ff3917036925e38560ee6b9ae38c571a4002", size = 7670718, upload-time = "2026-07-08T16:07:46.525Z" }, @@ -3906,7 +3029,8 @@ version = "5.14.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "huggingface-hub" }, - { name = "numpy" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "packaging" }, { name = "pyyaml" }, { name = "regex" }, @@ -3925,8 +3049,6 @@ name = "triton" version = "3.7.1" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/ea/629cc37436ca5df93ce98956d09cd2ca1498bfee8ef4972d2fe48b9f958c/triton-3.7.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3daf64305d6cea88d3334c65ebc9bcd0c64c9564a977084366aa768d57cbcf64", size = 184551013, upload-time = "2026-06-17T20:03:37.551Z" }, - { url = "https://files.pythonhosted.org/packages/15/76/c79c34311625227a288df3e483fc5cdf3d596624cbd4b4758c4cbdc14af3/triton-3.7.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee89fbf782ec2ad50391dd1cf26cbea4f4467154c37f4773026da8fc31c0f58e", size = 197596267, upload-time = "2026-06-17T19:53:06.898Z" }, { url = "https://files.pythonhosted.org/packages/7b/f9/19d842d06a08559534fa1eaab6ca551b1bcf40f06620bddec1babaa2772d/triton-3.7.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4a0e1cd4c4a76370ed74a8432a53cea28716827d19e40ffc732233e35ceb3f6", size = 184664887, upload-time = "2026-06-17T20:03:42.913Z" }, { url = "https://files.pythonhosted.org/packages/cd/5e/fce69606f7f240297f163e25539906732b199530d486ce67ae319877e821/triton-3.7.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6744957e9fd610a29680ec2346057d0c86948ed3812468670719f391e94b44a5", size = 197701306, upload-time = "2026-06-17T19:53:13.673Z" }, { url = "https://files.pythonhosted.org/packages/94/fa/f856e24deb462d5f18bd4b5a746957862ab9b6ee5834bda60605ec348366/triton-3.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9497f2e696ee368862a181a90b2dcc03ca978cc4f602abd67c7d81022a6988e1", size = 184692359, upload-time = "2026-06-17T20:03:48.288Z" }, @@ -3944,28 +3066,16 @@ name = "trustmark" version = "0.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "einops" }, - { name = "lightning" }, - { name = "numpy" }, - { name = "omegaconf" }, - { name = "six" }, - { name = "torch" }, - { name = "torchvision" }, + { name = "einops", marker = "python_full_version < '3.13'" }, + { name = "lightning", marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "omegaconf", marker = "python_full_version < '3.13'" }, + { name = "six", marker = "python_full_version < '3.13'" }, + { name = "torch", marker = "python_full_version < '3.13'" }, + { name = "torchvision", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/87/0a/0a4232030c6a62d12b6a02ae73bdce6e99c8532bc8f05a5a2e6ce103da82/trustmark-0.9.1.tar.gz", hash = "sha256:dc79e3fb070f5d94765acf8868a51f50a612cc05b53223cf1e6b605d4ff1e0ae", size = 63949, upload-time = "2026-04-09T08:59:52.472Z" } -[[package]] -name = "typeguard" -version = "4.6.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b4/de/4420db493fa8fc0856d5e5c1b159c63a323d2de2317babe36b01568928e8/typeguard-4.6.0.tar.gz", hash = "sha256:e7414f09111317de3e335de92cd397c5c0ca00b1cc1676de12e1d444a79b3f21", size = 82330, upload-time = "2026-07-26T08:40:23.207Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/eb/461d5f167b6f5c7d97696f397c82f82e3480e003fce3f0a1cd1dd26e2eb2/typeguard-4.6.0-py3-none-any.whl", hash = "sha256:79878165bb86f2cf5d41d159a0ff1792a796cf496882d2fe1b1c6c7049b9cdd7", size = 36884, upload-time = "2026-07-26T08:40:21.868Z" }, -] - [[package]] name = "typer" version = "0.27.1" @@ -3990,18 +3100,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, ] -[[package]] -name = "typing-inspection" -version = "0.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, -] - [[package]] name = "tzdata" version = "2026.3" @@ -4020,44 +3118,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, ] -[[package]] -name = "uv-outdated" -version = "1.0.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, - { name = "pydantic" }, - { name = "rich" }, - { name = "typer" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/38/84/78736b81c0e6ebefd3810b04a3bc6cb82bf7ea63474821b02d5cd9040439/uv_outdated-1.0.4.tar.gz", hash = "sha256:126745028823d8d452a82faaf53ea1d4ab5cdea7bba3159fc2ce7e5d0443146c", size = 19176, upload-time = "2025-12-25T10:54:22.77Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/5e/ac80d652ced66120f2d6254d827dbddd8322608911100eba7b8e214f992d/uv_outdated-1.0.4-py3-none-any.whl", hash = "sha256:2b664b62836dee5653b8a42212210b76edc195dba9105e9da230a7a5d22dc5ee", size = 12897, upload-time = "2025-12-25T10:54:21.811Z" }, -] - -[[package]] -name = "uv-secure" -version = "0.17.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "cvss" }, - { name = "httpx" }, - { name = "humanize" }, - { name = "inflect" }, - { name = "orjson" }, - { name = "packaging" }, - { name = "pydantic", extra = ["email"] }, - { name = "rich" }, - { name = "stamina" }, - { name = "tomlkit" }, - { name = "typer" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/71/99/29318cedfc5583cf2d503f0eedb9c4e96829541c356ce5d2aacfe09ef67f/uv_secure-0.17.2.tar.gz", hash = "sha256:e394939e0872df392d8f650d15ac1571b9267fc2f3671a183aa73c0977f0f402", size = 47240, upload-time = "2026-04-18T08:45:38.185Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/e0/917c79d13d7c34cd24e6b91ed76a1e580864790ebe50845dfe269bd29174/uv_secure-0.17.2-py3-none-any.whl", hash = "sha256:7d8e05eb2d80075458a5d575c65999edd94a3ca58c4d3e8e06172b295cb4ebe6", size = 54992, upload-time = "2026-04-18T08:45:39.338Z" }, -] - [[package]] name = "wcwidth" version = "0.8.2" @@ -4085,25 +3145,6 @@ version = "3.8.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/8e/63/71aa56b151a1b28770037a61bd4e461c2619cfc8866a4fcaf1548605e325/xxhash-3.8.1.tar.gz", hash = "sha256:b0de4bf3aa66363552d52c6a89003c479911f12098cd48a53d44a0f7a25f7c46", size = 86223, upload-time = "2026-07-06T10:49:58.937Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/55/97/1a8cebf0a6650417f08a18231590e2515aacd5ce39c3ad8b9e013ebd437d/xxhash-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:27a9e475157f7315826118e3f3127909a0fe25f1b43d3d3be9c584f9d265f937", size = 34695, upload-time = "2026-07-06T10:43:40.248Z" }, - { url = "https://files.pythonhosted.org/packages/2f/cf/745b9bc0dd9c341bc074b5fc700db7bbef0f3b69ab21446492296ab37e50/xxhash-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b2ce44bf8f4a1d01f418b3110ff8dff32fd3f3e836c0e06333c3725f243fa6c", size = 32376, upload-time = "2026-07-06T10:43:41.97Z" }, - { url = "https://files.pythonhosted.org/packages/65/a4/8512a901b1d6ad4a9838d1b40385907a879d7e005a5afbec5d39526b69f6/xxhash-3.8.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:942bc86e9be6fdd6e1175048f5fe8f8fdaaf2309dd1323ef1e155a69cd346780", size = 217470, upload-time = "2026-07-06T10:43:43.572Z" }, - { url = "https://files.pythonhosted.org/packages/a0/ad/0ffd8094ea29579bb2dc42fa74d08570e9ea3d95db561e6b1105e69b9ca6/xxhash-3.8.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0204701e6d01f64254e0e5ff4255812b1febe027ddd7dda63372e27f98b5e91f", size = 237799, upload-time = "2026-07-06T10:43:45.248Z" }, - { url = "https://files.pythonhosted.org/packages/b3/90/783c6b3f9336bd07449fe672be32cef6833633936bbfda8d3b23ee18d202/xxhash-3.8.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7dc4bdf008f77c88d544849c48c1a40faf25a5eff6cc466de2e8edc37c191fce", size = 262587, upload-time = "2026-07-06T10:43:46.733Z" }, - { url = "https://files.pythonhosted.org/packages/c4/77/ba0316a7c3e661b86830a47ae4987798616ce1b15af8d2a6358e2d89ef60/xxhash-3.8.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c566b123dce7e4867ca518434cdfb9f84e5023771235b2e3107a26c9a41cbd8", size = 238484, upload-time = "2026-07-06T10:43:48.453Z" }, - { url = "https://files.pythonhosted.org/packages/09/79/33001037c1cba90f4ced38b257161c13452024c0db44208f883e2e47f3fc/xxhash-3.8.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9f23083e1bd9d901f844af7a126727c486e7eada9a1a6791c8f7e73f94fac656", size = 469909, upload-time = "2026-07-06T10:43:50.188Z" }, - { url = "https://files.pythonhosted.org/packages/45/90/237eded9dd6ae638083294e5a9f77b317aaebd480a330806b39c192a0de1/xxhash-3.8.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64af54dd1c3a45a27c04942f9a1a4683322bdd127f4745cca4e02549c1d2d2bb", size = 217166, upload-time = "2026-07-06T10:43:51.816Z" }, - { url = "https://files.pythonhosted.org/packages/0b/6a/8cb439dc9920e1468e1c2d69ef77cbeb4be3b1ae9f4b5344c07a2b59af18/xxhash-3.8.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8ea8a141eeced4f6262ab6dd71c681ac546a558c30bb586abe087d814b5f85ea", size = 307593, upload-time = "2026-07-06T10:43:53.436Z" }, - { url = "https://files.pythonhosted.org/packages/ec/c6/c0607d373c8affea92101a3926c4fc8b026bcf8983e05fd58f3a0380ebf8/xxhash-3.8.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a98b2f95cab589e0f5e92c48431afb4d56238b8bf6668edcc66166180e9b509b", size = 234702, upload-time = "2026-07-06T10:43:55.042Z" }, - { url = "https://files.pythonhosted.org/packages/5b/cb/f4cfd456624c1f017858168b7ba9443dad810da8aac779a612658450e827/xxhash-3.8.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1b86ae798a976ccbc1d02af6ccb98f5b4d24756b1f65e995f11d10fe071f486f", size = 265749, upload-time = "2026-07-06T10:43:56.749Z" }, - { url = "https://files.pythonhosted.org/packages/33/f3/9006669c04b01206e21b2177425c649461ba188930a052c2f1728d6ec6a8/xxhash-3.8.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:81f4ed9ca9644bc95cd976bfe10f7a4cafab8ffdc3aed52877d4600e445be7ef", size = 221992, upload-time = "2026-07-06T10:43:58.12Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0b/7e6f3eaa05df5e0b6c94aa452b0672801f7031e602081f07fd441aaaaed5/xxhash-3.8.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:cb3fe820c27593f170770d6c8d791936cf6275d9269405fbb7b30a55363c10c8", size = 236899, upload-time = "2026-07-06T10:43:59.562Z" }, - { url = "https://files.pythonhosted.org/packages/da/cc/bbaee4987f3aab1d7b33bb430bb49e940646160af448b9167431c931126d/xxhash-3.8.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:7345007c12780985de4fd740148776d1eee18c0d41407c6fa1e48c5450304fe5", size = 297934, upload-time = "2026-07-06T10:44:01.132Z" }, - { url = "https://files.pythonhosted.org/packages/a7/97/6bee358660eb8b4f73c00b00b00bc616ebde00e1ab4b67c63486ce360648/xxhash-3.8.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:12eaeaa9ab8b9e6033a1fa5f6b338aaf55ff4df4bee11b59fd6ee03b19186ee4", size = 439315, upload-time = "2026-07-06T10:44:02.878Z" }, - { url = "https://files.pythonhosted.org/packages/c6/50/7e35275f39256bedace0c3cd5be3c72d4ac9d5aecf5e5fdc3530337cd263/xxhash-3.8.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2a845687219ba3214126f14a8a5861f97c9e065a7d0b8252adb6df13eea86fb", size = 214038, upload-time = "2026-07-06T10:44:04.504Z" }, - { url = "https://files.pythonhosted.org/packages/59/2d/69d02d096ee50bdf3ef0d208d874f52c71b1aa6906066bce3c52fedb8bc6/xxhash-3.8.1-cp310-cp310-win32.whl", hash = "sha256:656256c9f9303e47f07d5cb8ae4468285370adfafd7ba48aea33a458e7697626", size = 31939, upload-time = "2026-07-06T10:44:06.213Z" }, - { url = "https://files.pythonhosted.org/packages/c9/1d/e06fca9844919ca91c6587d530cfa1e745830ec73ad38f44f04b25d1bfb7/xxhash-3.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:27cfc2f1ed76f956f36dfe0c56e5f5a3e94cd91eb78b893f63e2ef2ae404fcdf", size = 32729, upload-time = "2026-07-06T10:44:07.621Z" }, - { url = "https://files.pythonhosted.org/packages/8c/c2/800648d99039927b5a86d8ae02cd86a556a5ee1678d388216f6b44c8966c/xxhash-3.8.1-cp310-cp310-win_arm64.whl", hash = "sha256:c85949d02c85adf6d786eb94858e124989a632a4e65739835b2fc5761827fac3", size = 29215, upload-time = "2026-07-06T10:44:08.916Z" }, { url = "https://files.pythonhosted.org/packages/8a/5a/05eaa129555f85476a3e16ff869e95f81a78bbe4647eef9d0229f515a317/xxhash-3.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602efcad4a42c184e81d43a2b7e6e4f524d619878f2b6ee2ba469011f47c8147", size = 34699, upload-time = "2026-07-06T10:44:10.14Z" }, { url = "https://files.pythonhosted.org/packages/80/59/0df1133958b2228929355e022aab1e958c7b2c43e27bf7f59bc9edfa8a54/xxhash-3.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:131324f719957b988861714de7d6ddf57b47abec3b0cc691302ffeaba0e05e10", size = 32373, upload-time = "2026-07-06T10:44:11.353Z" }, { url = "https://files.pythonhosted.org/packages/3e/bf/1cfda5b5e6bf26617812b4a31662ef2220d2ad04e0a55b8ff9eb36e56a5c/xxhash-3.8.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:db77278a6eddadbf44ce5aae2fee5ebb4d061f026b1ce2130d058cd4d7a7b670", size = 220284, upload-time = "2026-07-06T10:44:12.683Z" }, @@ -4247,23 +3288,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/31/33/ebe9e3d1f86c7a0b51094c0a146392045ca1631d2664889539dec8088a33/yarl-1.24.5.tar.gz", hash = "sha256:e81b83143bee16329c23db3c1b2d82b29892fcbcb849186d2f6e98a5abe9a57f", size = 228679, upload-time = "2026-07-20T02:07:45.435Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/ac/cacdda1f0a90441297210bc34cf7e4ac1b7318c8030ebd83bdf6fe82f1db/yarl-1.24.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88f50c94e21a0a7f14042c015b0eba1881af78562e7bf007e0033e624da59750", size = 135466, upload-time = "2026-07-20T02:04:21.695Z" }, - { url = "https://files.pythonhosted.org/packages/c0/a5/1b2ceace0230e40c52ab1b263148059a43a6303219b996affc68f8381836/yarl-1.24.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6efbccc3d7f75d5b03105172a8dc86d82ba4da86817952529dd93185f4a88be2", size = 97291, upload-time = "2026-07-20T02:04:24.045Z" }, - { url = "https://files.pythonhosted.org/packages/59/1d/340d1a0db7bbce1f291afc044255ebf4ebbce2b25ab1b3f7d3d069080f5d/yarl-1.24.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0ebfaffe1a16cb72141c8e09f18cc76856dbe58639f393a4f2b26e474b96b871", size = 97154, upload-time = "2026-07-20T02:04:25.761Z" }, - { url = "https://files.pythonhosted.org/packages/05/41/25596a33c2fb5098dca8dc3773b04221db64ded0b7f8f09885647d864610/yarl-1.24.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ac73abdc7ab75610f95a8fd994c6457e87752b02a63987e188f937a1fc180f0", size = 109196, upload-time = "2026-07-20T02:04:27.543Z" }, - { url = "https://files.pythonhosted.org/packages/f2/df/dd9f2fb8a5c6054fbefd1538d2b9b1127e612d2ee64b307a070173b57afd/yarl-1.24.5-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4d97a951a81039050e45f04e96689b58b8243fa5e62aa14fe67cb6075300885e", size = 102556, upload-time = "2026-07-20T02:04:29.16Z" }, - { url = "https://files.pythonhosted.org/packages/cb/57/4754b9d2c8945880290ecba0864e8b0441e117bba70534fe819e3645e174/yarl-1.24.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fe7b7bb170daccbba19ad33012d2b15f1e7942296fd4d45fc1b79013da8cc0f2", size = 117965, upload-time = "2026-07-20T02:04:30.845Z" }, - { url = "https://files.pythonhosted.org/packages/74/b5/6a9ece27d2043c3386f902dd078ab35d29ef5126b3206ebffb673283a7cb/yarl-1.24.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:89a1bbb58e0e3f7a283653d854b1e95d65e5cfd4af224dac5f02629ec1a3e621", size = 116266, upload-time = "2026-07-20T02:04:32.573Z" }, - { url = "https://files.pythonhosted.org/packages/9e/bc/a6653249f6ee59ec85dcfec008d9cbc16586dad613963bb17a91b2b993a5/yarl-1.24.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7fa5e51397466ea7e98de493fa2ff1b8193cfef8a7b0f9b4842f92d342df0dba", size = 110758, upload-time = "2026-07-20T02:04:34.235Z" }, - { url = "https://files.pythonhosted.org/packages/65/7e/c5a12fb8208df7b981bc82256e7831ce428eeaf893f7bbe6179c57bb9252/yarl-1.24.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4103b77b8a8225e413107d2349b65eb3c1c52627b5cc5c3c4c1c6a798b218950", size = 110120, upload-time = "2026-07-20T02:04:35.85Z" }, - { url = "https://files.pythonhosted.org/packages/04/6c/1b659b964626694667b3ec01bf4bcff564b73ae7c48ea1fbfe588b78b461/yarl-1.24.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f9f3e9c8a9ecffa57bef8fb4fa19e5fa4d2d8307cf6bac5b1fca5e5860f4ba00", size = 108834, upload-time = "2026-07-20T02:04:37.67Z" }, - { url = "https://files.pythonhosted.org/packages/74/a6/bf48f55c2104e40c15b7b13fad0a5756a11552a55f01c90bc90a66ab81c3/yarl-1.24.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c0ebc836c47a6477e182169c6a476fc691d12b518894bf7dd2572f0d59f1c7ed", size = 103442, upload-time = "2026-07-20T02:04:39.576Z" }, - { url = "https://files.pythonhosted.org/packages/37/ac/84b273ac133ecdce598fc1f4140a08a1bf2044048bff8106371d207d105f/yarl-1.24.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:96d30286dd02679e32a39aa8f0b7498fc847fcda46cfc09df5513e82ce252440", size = 117413, upload-time = "2026-07-20T02:04:41.549Z" }, - { url = "https://files.pythonhosted.org/packages/a4/55/9307e03977d3b290dfa42e5d2bae7b6140808fd1786fbe70cd9d3bee53c5/yarl-1.24.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:fd8c81f346b58f45818d09ea11db69a8d5fd34a224b79871f6d44f12cd7977b1", size = 109498, upload-time = "2026-07-20T02:04:43.468Z" }, - { url = "https://files.pythonhosted.org/packages/fc/be/791a6f314cb4c989c19f8e3a10271f1e469c077143915e52474d80f26b4b/yarl-1.24.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5c55256dee8f4b27bfbf636c8363383c7c8db7890c7cba5217d7bd5f5f21dab6", size = 116062, upload-time = "2026-07-20T02:04:45.319Z" }, - { url = "https://files.pythonhosted.org/packages/19/1a/ddd3807b86055010e2f99aa89b3c640effdb65696766c20597f696f48a1c/yarl-1.24.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9f4d8cf085a4c6a40fb97ea0f46938a8df43c85d31f9d45e2a8867ea9293790d", size = 110941, upload-time = "2026-07-20T02:04:47.054Z" }, - { url = "https://files.pythonhosted.org/packages/6d/03/f34271bba042d2187508bf62aea20a14129efb5a1acfc6a2efe7544630b4/yarl-1.24.5-cp310-cp310-win_amd64.whl", hash = "sha256:240cbec09667c1fed4c6cd0060b9ec57332427d7441289a2ed8875dc9fb2b224", size = 97534, upload-time = "2026-07-20T02:04:48.774Z" }, - { url = "https://files.pythonhosted.org/packages/e4/02/ecc8dc31b9f355731e700f8402b8075d2ea1737dbc4baf4abf0f0fc64288/yarl-1.24.5-cp310-cp310-win_arm64.whl", hash = "sha256:8a6987eaad834cb32dd57d9d582225f0054a5d1af706ccfbbdba735af4927e13", size = 93603, upload-time = "2026-07-20T02:04:50.686Z" }, { url = "https://files.pythonhosted.org/packages/fe/db/3cb5df059756a45761cc3dee8fd25ec82b83a6585ea3542b969fda850f99/yarl-1.24.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2c1fe720934a16ea8e7146175cba2126f87f54912c8c5435e7f7c7a51ef808d3", size = 135043, upload-time = "2026-07-20T02:04:52.39Z" }, { url = "https://files.pythonhosted.org/packages/44/f8/767d6bd5a03db63bc467df2fb56d6fafeae9667d74aea92cd6af399f828b/yarl-1.24.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c687ed078e145f5fd53a14854beff320e1d2ab76df03e2009c98f39a0f68f39a", size = 96942, upload-time = "2026-07-20T02:04:54.26Z" }, { url = "https://files.pythonhosted.org/packages/ce/97/10b939c44d7b28d1dbc389cfc7012306d1ea8dba01eaef44b39fffaee52a/yarl-1.24.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:709f1efed56c4a145793c046cd4939f9959bcd818979a787b77d8e09c57a0840", size = 97046, upload-time = "2026-07-20T02:04:56.638Z" }, From acbf6afac34c7ca1bb471affc76f0e947370c1c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Aug 2026 22:57:56 +0000 Subject: [PATCH 09/44] Bump the minor-and-patch group across 1 directory with 5 updates Bumps the minor-and-patch group with 5 updates in the / directory: | Package | From | To | | --- | --- | --- | | [c2pa-python](https://github.com/contentauth/c2pa-python) | `0.37.2` | `0.37.5` | | diffsynth | `2.0.18` | `2.1.0` | | [huggingface-hub](https://github.com/huggingface/huggingface_hub) | `1.26.0` | `1.27.0` | | [packaging](https://github.com/pypa/packaging) | `26.2` | `26.3` | | [ruff](https://github.com/astral-sh/ruff) | `0.16.1` | `0.16.2` | Updates `c2pa-python` from 0.37.2 to 0.37.5 - [Release notes](https://github.com/contentauth/c2pa-python/releases) - [Changelog](https://github.com/contentauth/c2pa-python/blob/main/docs/release-notes.md) - [Commits](https://github.com/contentauth/c2pa-python/compare/v0.37.2...v0.37.5) Updates `diffsynth` from 2.0.18 to 2.1.0 Updates `huggingface-hub` from 1.26.0 to 1.27.0 - [Release notes](https://github.com/huggingface/huggingface_hub/releases) - [Commits](https://github.com/huggingface/huggingface_hub/compare/v1.26.0...v1.27.0) Updates `packaging` from 26.2 to 26.3 - [Release notes](https://github.com/pypa/packaging/releases) - [Changelog](https://github.com/pypa/packaging/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pypa/packaging/compare/26.2...26.3) Updates `ruff` from 0.16.1 to 0.16.2 - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.16.1...0.16.2) --- updated-dependencies: - dependency-name: c2pa-python dependency-version: 0.37.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-and-patch - dependency-name: diffsynth dependency-version: 2.1.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: huggingface-hub dependency-version: 1.27.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: packaging dependency-version: '26.3' dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: ruff dependency-version: 0.16.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-and-patch ... Signed-off-by: dependabot[bot] --- uv.lock | 188 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 94 insertions(+), 94 deletions(-) diff --git a/uv.lock b/uv.lock index 9574316..9b3d6e1 100644 --- a/uv.lock +++ b/uv.lock @@ -248,7 +248,7 @@ wheels = [ [[package]] name = "c2pa-python" -version = "0.37.2" +version = "0.37.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, @@ -258,15 +258,15 @@ dependencies = [ { name = "toml" }, { name = "wheel" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9a/97/5f432ad0c570f6d408d42b21fa977ecc9157098b108b4a3ce54453a67b3b/c2pa_python-0.37.2.tar.gz", hash = "sha256:a076f539612157cd0f4b448085581bfd93a92a93dcd1e6a49329973817aed4ab", size = 94888, upload-time = "2026-07-27T21:48:08.085Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/ec/416d05dc0cffc362f24cb33121aeddb621cfe5f06b239b0051aa25b28079/c2pa_python-0.37.5.tar.gz", hash = "sha256:185cd00c18d8386a0c6c8c5af19434005406626545e6250e1273f4cc14389739", size = 112944, upload-time = "2026-08-05T23:09:56.409Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/1a/5fb31c15b474e7ea8b7dfefa6fbff31994657e40668df9abedbadd784b76/c2pa_python-0.37.2-py3-none-macosx_10_9_universal2.whl", hash = "sha256:a312de48c997f499cea5d1c74fc8d1835a8e952758bb99c95bb1dd53e2eb1773", size = 16248838, upload-time = "2026-07-27T21:47:46.365Z" }, - { url = "https://files.pythonhosted.org/packages/e6/f9/8288785b70541fc50bb4102898c88c215ff11bd3a6a1d724e5b9999c0c85/c2pa_python-0.37.2-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:e191a8d28823392d70c61beafc0fc9186cf6081f82575e1c8130fd3d5c119143", size = 14178813, upload-time = "2026-07-27T21:47:48.898Z" }, - { url = "https://files.pythonhosted.org/packages/5d/69/e9809ef4bdb30e77fe789c9f98f6e12a5ccdffad76f2992c57e2447b76c2/c2pa_python-0.37.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8ee2e044072875bc413deb24a1dceaae1dd666224c777605a2921f64a8eabe46", size = 13810426, upload-time = "2026-07-27T21:47:51.282Z" }, - { url = "https://files.pythonhosted.org/packages/f2/b8/05249650fc9b2a3e77fea055a7673767e62a6b31ab58f928a7f2c404651a/c2pa_python-0.37.2-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:f5cacd1902b22a625e3faca59f761879d29fc3d1bd08503898edc86c9cde48ad", size = 14028399, upload-time = "2026-07-27T21:47:53.592Z" }, - { url = "https://files.pythonhosted.org/packages/c1/17/21039eec70c5c8c2541c114a3c5e6496780a7234c72fc862ba2bf5f3e856/c2pa_python-0.37.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:0ccda18b76cf82908686eb899fb420272753f8a1c263ac98c858ea27d2ee92c5", size = 14680570, upload-time = "2026-07-27T21:47:56.291Z" }, - { url = "https://files.pythonhosted.org/packages/1c/99/29b9e62fece34eded4eab5d9b0779cf531393180995f35b37f167d3ef11d/c2pa_python-0.37.2-py3-none-win_amd64.whl", hash = "sha256:6ed91c19ad5e70bcc30000a578a254cfcc334f19c6251744b05972f87f2901d5", size = 87472586, upload-time = "2026-07-27T21:48:00.43Z" }, - { url = "https://files.pythonhosted.org/packages/26/27/d665c0875a48b45ba937e9c38a693969bcb7594d68c719a834d66cb37c10/c2pa_python-0.37.2-py3-none-win_arm64.whl", hash = "sha256:67b904cdc225fbd828bddb3202b00cd93920661b6646f21b7b30247abe1e25bd", size = 85002829, upload-time = "2026-07-27T21:48:05.388Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f2/344074f26373f3c596a98549b0a7755b1cf6036ed4a831ad74526ac33d80/c2pa_python-0.37.5-py3-none-macosx_10_9_universal2.whl", hash = "sha256:8f719bd53a08bb54a4e8b761825db426d153430199821a4fcac1c13efe26d309", size = 16150883, upload-time = "2026-08-05T23:09:21.934Z" }, + { url = "https://files.pythonhosted.org/packages/f3/38/bd924e7f829b156fcf967923d26918fd4cc3435860055ae8b1b46d97d982/c2pa_python-0.37.5-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:72a9b074f3fecf67fb1740003869fe17c2d664ab79e6fc0669d3a77dc01e6699", size = 14116139, upload-time = "2026-08-05T23:09:25.589Z" }, + { url = "https://files.pythonhosted.org/packages/74/50/1128ae57644c079c75dcfeeaf5b51e199246b17f745d42e9a929d224b199/c2pa_python-0.37.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:95e446663cd53d9292fd04afb84996c3be9a4aeea1ac076440d9a6c6477da921", size = 13785203, upload-time = "2026-08-05T23:09:28.895Z" }, + { url = "https://files.pythonhosted.org/packages/a8/f0/f5291ba6f3bc778575af06d9a6630d841432b7cf8136ae57e8889b212d8d/c2pa_python-0.37.5-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:8fc4ce81ae5449a65981ce10e33c91c33a85a10b3aed1124d580ad5ce15b1ea7", size = 14006289, upload-time = "2026-08-05T23:09:32.566Z" }, + { url = "https://files.pythonhosted.org/packages/57/33/e258809ad6ae1ee7c9af29117bd8addf87db9e0a0b53354ac668b2f2e4a4/c2pa_python-0.37.5-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:82bf2dcc5f184a40d7b65a5804b3e6251d52e6e65958cfbc0dc80321eac4bb33", size = 14652564, upload-time = "2026-08-05T23:09:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/95/2f/5c2ebd8baa36c4422f4827323a6a67c07ed57898f3500f689ab0d7b31435/c2pa_python-0.37.5-py3-none-win_amd64.whl", hash = "sha256:c122680aa05ea12bbe061c69333c0137f4f2f8c7906ae06fec255db346d788b8", size = 86368147, upload-time = "2026-08-05T23:09:45.232Z" }, + { url = "https://files.pythonhosted.org/packages/39/6f/f54775038b6a469951fe9899249ff3f8545421f64cc5e6a718af092f383d/c2pa_python-0.37.5-py3-none-win_arm64.whl", hash = "sha256:1ed2f1bd1d69e1ac243a9d769e5fa1047d5f9be3fed3206bb3c0a68c3c9e0bce", size = 83916232, upload-time = "2026-08-05T23:09:53.31Z" }, ] [[package]] @@ -597,7 +597,7 @@ name = "cuda-bindings" version = "13.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cuda-pathfinder", marker = "sys_platform != 'darwin'" }, + { name = "cuda-pathfinder" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/51/6b/457ca12dad3ee9bfcc9a545cfd6b64b359ba49de40f776f6e028e678f262/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5879712accf6e14bb01aa5e67440eb84998b8d104b509cc7a6dc0b8f656a474", size = 6053539, upload-time = "2026-05-29T23:11:43.19Z" }, @@ -630,43 +630,43 @@ wheels = [ [package.optional-dependencies] cublas = [ - { name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, - { name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] cudart = [ - { name = "nvidia-cuda-runtime", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-cuda-runtime", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] cufft = [ - { name = "nvidia-cufft", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, - { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-cufft", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] cufile = [ - { name = "nvidia-cufile", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cufile", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] cupti = [ - { name = "nvidia-cuda-cupti", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-cuda-cupti", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] curand = [ - { name = "nvidia-curand", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-curand", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] cusolver = [ - { name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, - { name = "nvidia-cusolver", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, - { name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, - { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cusolver", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] cusparse = [ - { name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, - { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] nvjitlink = [ - { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] nvrtc = [ - { name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] nvtx = [ - { name = "nvidia-nvtx", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-nvtx", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] [[package]] @@ -697,7 +697,7 @@ wheels = [ [[package]] name = "diffsynth" -version = "2.0.18" +version = "2.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "accelerate" }, @@ -715,9 +715,9 @@ dependencies = [ { name = "torchvision" }, { name = "transformers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/61/6f/e193941ae17f7c7990a766afc933ad4dc3ee4e8c1a26c5293fbeb2120b60/diffsynth-2.0.18.tar.gz", hash = "sha256:fa027cccd15f6b989ddfcedd0ebfac522233e64bbc002560c7aeba4423a6ba30", size = 583188, upload-time = "2026-07-21T07:37:20.707Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/86/3665041e0ef267fae10b6191afdf0cb51f08177cffa4c27e02088bcb9e7b/diffsynth-2.1.0.tar.gz", hash = "sha256:af568befac3276beca2c102b398c34ba77e08595c0e9ec00c79cc1aa33f59d76", size = 640801, upload-time = "2026-08-04T08:54:34.624Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/dd/69a822181f9ebeb233cae89e76d15424ff37f3cfec7d6c405184c08fca1e/diffsynth-2.0.18-py3-none-any.whl", hash = "sha256:c265ab1426534bb06e55aa941a33c2f3f4fb34161dc5e534c4800b675855bfd4", size = 661750, upload-time = "2026-07-21T07:37:19.008Z" }, + { url = "https://files.pythonhosted.org/packages/35/1a/5d8907b2e1090756f3454fbd0af9a6bbfdde0c28c4ad59d7d559d1c5bfd5/diffsynth-2.1.0-py3-none-any.whl", hash = "sha256:40960d5c9089464a35f3c437564075127631957f9ea42143c5ef5400fc55dfdc", size = 728228, upload-time = "2026-08-04T08:54:32.846Z" }, ] [[package]] @@ -979,7 +979,7 @@ wheels = [ [[package]] name = "huggingface-hub" -version = "1.26.0" +version = "1.27.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -992,9 +992,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/82/db/3582597f8be0d34bd6881365a26d390854f12893eabdd62dd36de9df5a47/huggingface_hub-1.26.0.tar.gz", hash = "sha256:c8cd4e2df1ba9402f77fce9b509ec1d52debb502551789473f34016acc14e361", size = 936665, upload-time = "2026-07-30T14:12:04.156Z" } +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" } wheels = [ - { url = "https://files.pythonhosted.org/packages/97/bb/63a644c75b545f3ff394b822e9bd1c4a9586489c618b77a4d8a44a33a23b/huggingface_hub-1.26.0-py3-none-any.whl", hash = "sha256:e8cca670caa5d8dfa7e45bf45e86b466698198cd8150c021bcdb4a86b9252364", size = 780357, upload-time = "2026-07-30T14:12:01.998Z" }, + { 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" }, ] [[package]] @@ -1094,15 +1094,15 @@ name = "lightning" version = "2.6.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "fsspec", extra = ["http"], marker = "python_full_version < '3.13'" }, - { name = "lightning-utilities", marker = "python_full_version < '3.13'" }, - { name = "packaging", marker = "python_full_version < '3.13'" }, - { name = "pytorch-lightning", marker = "python_full_version < '3.13'" }, - { name = "pyyaml", marker = "python_full_version < '3.13'" }, - { name = "torch", marker = "python_full_version < '3.13'" }, - { name = "torchmetrics", marker = "python_full_version < '3.13'" }, - { name = "tqdm", marker = "python_full_version < '3.13'" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "fsspec", extra = ["http"] }, + { name = "lightning-utilities" }, + { name = "packaging" }, + { name = "pytorch-lightning" }, + { name = "pyyaml" }, + { name = "torch" }, + { name = "torchmetrics" }, + { name = "tqdm" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c9/1d/83be8536bec71a0173e762a9a1fd92a24a5ad0d0f74c59550c3c4e6c103b/lightning-2.6.5.tar.gz", hash = "sha256:16a30310ed69afde3748491feb5d13508908effd70390d2bfc203dc0812a4b4a", size = 659201, upload-time = "2026-05-27T14:33:41.806Z" } wheels = [ @@ -1114,8 +1114,8 @@ name = "lightning-utilities" version = "0.15.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "packaging", marker = "python_full_version < '3.13'" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "packaging" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f1/45/7fa8f56b17dc0f0a41ec70dd307ecd6787254483549843bef4c30ab5adce/lightning_utilities-0.15.3.tar.gz", hash = "sha256:792ae0204c79f6859721ac7f386c237a33b0ed06ba775009cb894e010a842033", size = 33553, upload-time = "2026-02-22T14:48:53.348Z" } wheels = [ @@ -1518,7 +1518,7 @@ name = "nvidia-cublas" version = "13.1.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cuda-nvrtc", marker = "sys_platform != 'darwin'" }, + { name = "nvidia-cuda-nvrtc" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" }, @@ -1557,7 +1557,7 @@ name = "nvidia-cudnn-cu13" version = "9.20.0.48" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas", marker = "sys_platform != 'darwin'" }, + { name = "nvidia-cublas" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/56/c5/83384d846b2fd17c44bd499b36c75a45ed4f095fbbb2252294e89cea5c5c/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:e31454ae00094b0c55319d9d15b6fa2fc50a9e1c0f5c8c80fb75258234e731e1", size = 444574296, upload-time = "2026-03-09T19:28:27.751Z" }, @@ -1569,7 +1569,7 @@ name = "nvidia-cufft" version = "12.0.0.61" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin'" }, + { name = "nvidia-nvjitlink" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, @@ -1599,9 +1599,9 @@ name = "nvidia-cusolver" version = "12.0.4.66" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas", marker = "sys_platform != 'darwin'" }, - { name = "nvidia-cusparse", marker = "sys_platform != 'darwin'" }, - { name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin'" }, + { name = "nvidia-cublas" }, + { name = "nvidia-cusparse" }, + { name = "nvidia-nvjitlink" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, @@ -1613,7 +1613,7 @@ name = "nvidia-cusparse" version = "12.6.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin'" }, + { name = "nvidia-nvjitlink" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, @@ -1670,8 +1670,8 @@ name = "omegaconf" version = "2.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "antlr4-python3-runtime", marker = "python_full_version < '3.13'" }, - { name = "pyyaml", marker = "python_full_version < '3.13'" }, + { name = "antlr4-python3-runtime" }, + { name = "pyyaml" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ce/3d/e4b57b8d9008c6ebe0d5eff901f91d5700cf7bdb8c8863df817463a7fd5e/omegaconf-2.3.1.tar.gz", hash = "sha256:e5e7de64aeebeddaf8e6d3f7a783b32ac2a01c0fbd9c878012caecb891a1f42a", size = 3298472, upload-time = "2026-06-11T05:05:12.885Z" } wheels = [ @@ -1754,11 +1754,11 @@ wheels = [ [[package]] name = "packaging" -version = "26.2" +version = "26.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, ] [[package]] @@ -2273,14 +2273,14 @@ name = "pytorch-lightning" version = "2.6.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "fsspec", extra = ["http"], marker = "python_full_version < '3.13'" }, - { name = "lightning-utilities", marker = "python_full_version < '3.13'" }, - { name = "packaging", marker = "python_full_version < '3.13'" }, - { name = "pyyaml", marker = "python_full_version < '3.13'" }, - { name = "torch", marker = "python_full_version < '3.13'" }, - { name = "torchmetrics", marker = "python_full_version < '3.13'" }, - { name = "tqdm", marker = "python_full_version < '3.13'" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "fsspec", extra = ["http"] }, + { name = "lightning-utilities" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "torch" }, + { name = "torchmetrics" }, + { name = "tqdm" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/52/2c/8e73a3929b4c4bd600cafd38a97aaf7242a8cf518fb9f33d27c274ec898f/pytorch_lightning-2.6.5.tar.gz", hash = "sha256:1c32cefa76a1a9c4c5250338272d961d1e48b180e68396849efe128538ddb28e", size = 661673, upload-time = "2026-05-27T14:33:41.961Z" } wheels = [ @@ -2695,27 +2695,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.16.1" +version = "0.16.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/70/25/7113f6d5498888c5fb7db34081cba7d5971c4cb1bfb26819966eee68f003/ruff-0.16.1.tar.gz", hash = "sha256:fedad7c801dabd3fb9741d76aca39246e6ddd9ca446a015875207bf19f1e6bc7", size = 4877500, upload-time = "2026-07-30T19:37:01.379Z" } +sdist = { url = "https://files.pythonhosted.org/packages/73/e1/4508a569211b35599016e84ba65c1a992b7a4004b4b6c4bea02a851cba1b/ruff-0.16.2.tar.gz", hash = "sha256:c3d7828d12e8927a6fc65fe38e2c2541b9e762d360a1786d752cb1b8883b3c9c", size = 4885811, upload-time = "2026-08-07T13:31:01.432Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/bd/694da69368e0973de65df2ddc73ab18d43c469d5963d9b150911de6bc513/ruff-0.16.1-py3-none-linux_armv6l.whl", hash = "sha256:58edb313b88f0c5460a26adf5f39a37a3be789494a15e3e411e35fa78b89f9a0", size = 10839126, upload-time = "2026-07-30T19:36:13.697Z" }, - { url = "https://files.pythonhosted.org/packages/3f/f0/b626e5d5bd0dd9576263658ef12885e2288afd1029a48e26ffed65ec1ac1/ruff-0.16.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fde5a99e2f97479af66edd6622c6d5a2a7592c77cf4153d9e4428f5eeb55b60c", size = 11070253, upload-time = "2026-07-30T19:36:17.14Z" }, - { url = "https://files.pythonhosted.org/packages/83/63/f40acfb6b35b88623e71684942b552c3edd96035f5d98f313815f7b277de/ruff-0.16.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e0d4c20532fca4f7fa609369161d968dd28f65d83dabbd61d8e9c7edbf7001f6", size = 10561425, upload-time = "2026-07-30T19:36:20.04Z" }, - { url = "https://files.pythonhosted.org/packages/aa/dd/14ec0e9c2b4d315547dd38765004b4863e354e1b52cb308272215d9f6f6d/ruff-0.16.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30affbcedf59ad5703d9c91f82266e02b47739f797e1a7b6e158e5526a6dae38", size = 10948879, upload-time = "2026-07-30T19:36:22.476Z" }, - { url = "https://files.pythonhosted.org/packages/33/e9/9d870cbae575030fdef595f04b4b97573c525b5497cce4f4498cf2f85446/ruff-0.16.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:24e9c631573cbca9d20f1283f8f479b2afa4a8503504822bd71a293889f16743", size = 10643691, upload-time = "2026-07-30T19:36:24.914Z" }, - { url = "https://files.pythonhosted.org/packages/c4/09/12743d544e2173f53ecd27217c65f90d2bc0f8424a66a60339e56bbc0457/ruff-0.16.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b41bdd48fb420987a9b5212e4957c26ad4abce401fa9ea9d4d85843727945f4f", size = 11435354, upload-time = "2026-07-30T19:36:28.447Z" }, - { url = "https://files.pythonhosted.org/packages/7f/89/a1652b2daee52083c9554a6333b678a8b01d0400f976827bb87857f9449a/ruff-0.16.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b0d1e1393b7648079e13669de1c1f4fde06d4583e84d8fd5c1551e0a77a2aa75", size = 12259033, upload-time = "2026-07-30T19:36:31.326Z" }, - { url = "https://files.pythonhosted.org/packages/16/96/ecdcb8c54ee7b123b487f807eb014e6e019155a0b81dfb669acd52f28ce3/ruff-0.16.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07bf434b1c95f4e093be4532068ef4fcf00924eb2ade8796075980902d6fd54a", size = 11667981, upload-time = "2026-07-30T19:36:34.394Z" }, - { url = "https://files.pythonhosted.org/packages/cd/90/c52e12e0d862e9572f2a33aa227409143520abe53111e9a6babbac7b4af8/ruff-0.16.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39897739f112253ee4fdd2e8aa9a4f9ded99fb2be367d5f31dfa4ded6025584c", size = 11468183, upload-time = "2026-07-30T19:36:37.339Z" }, - { url = "https://files.pythonhosted.org/packages/2c/6b/4ffb7ad1d83eb16cf8cbb3c8815d3f11c88460fd162d4b372a2059be1c2a/ruff-0.16.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:82ae3c0c0d74daf17b968a10b7b3bb3ef297ab7de0c1f749646b25e690ccb150", size = 11470071, upload-time = "2026-07-30T19:36:39.91Z" }, - { url = "https://files.pythonhosted.org/packages/9c/72/32ae7db4c0b5e32ab611787caa19d1546800676d79f7483b7100a3561bf4/ruff-0.16.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4d5f2ed10f8242d83fc08d521301089364e3375375705356f20c0e31606ef3ef", size = 10919503, upload-time = "2026-07-30T19:36:42.65Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ca/3d901ba6ad6fc38da39c3448fc6c59ac945679293a17c3ceb6d6c1cba13e/ruff-0.16.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a4665b309891f83f3e3c25447935f1213e9abbd4b5640af7a1f2def9f8d413c1", size = 10649861, upload-time = "2026-07-30T19:36:45.18Z" }, - { url = "https://files.pythonhosted.org/packages/92/79/894ef1ced26552d5f8c9cf6d85b0687840e1128c55aeab7b9c2d54a0d880/ruff-0.16.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:26e9ca5c9bc3971f20d3cf18a957f52ffd6a5f6564ff15c4912a144dcac22494", size = 11148137, upload-time = "2026-07-30T19:36:47.936Z" }, - { url = "https://files.pythonhosted.org/packages/2d/69/3609a09fa1cb46cc28b762363e440a354204e5dff01bd0c8d7437874d6b9/ruff-0.16.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:67e1e1e3fa4f0c82f0e36d4cd61e661f6e7a6196cb1aa92fe0828fa7b8f257cd", size = 11559211, upload-time = "2026-07-30T19:36:50.448Z" }, - { url = "https://files.pythonhosted.org/packages/fc/8a/fb22af2fd78a736e241fabf67e30ce1799a64244026377a49e133af90762/ruff-0.16.1-py3-none-win32.whl", hash = "sha256:d31765e131295b8445caf301e3e8a85b34d1b9b211b4109b7ba457888b051806", size = 10838258, upload-time = "2026-07-30T19:36:53.298Z" }, - { url = "https://files.pythonhosted.org/packages/d4/35/e57fd9fb5d423961df087a00b12d42c0a830288dc2f3b45ecca299158b4f/ruff-0.16.1-py3-none-win_amd64.whl", hash = "sha256:09b05e8b90c2cb06ad63464350e7a45e8e44a2dfe52072ebfba6666ca8d3f596", size = 11961111, upload-time = "2026-07-30T19:36:56.107Z" }, - { url = "https://files.pythonhosted.org/packages/cb/46/240ea004bf6dc4feb40e9832f2205a476a47dd5b8a3f8211a5fc5f95e20e/ruff-0.16.1-py3-none-win_arm64.whl", hash = "sha256:dbaadaac38c70239f056d306b7476f246b0bf000fa6b3876402acbf5b227eaf8", size = 11309414, upload-time = "2026-07-30T19:36:58.79Z" }, + { url = "https://files.pythonhosted.org/packages/14/57/db19951540f98859c956b50bdb4d31089b4d91e9f15e2968e7d5193806d5/ruff-0.16.2-py3-none-linux_armv6l.whl", hash = "sha256:3c8de4cf2181f01d57946d87d777aa52916976fc09942aed89938fab5e013318", size = 10847925, upload-time = "2026-08-07T13:30:14.468Z" }, + { url = "https://files.pythonhosted.org/packages/13/5a/995fe85a8470d3e391ac0f7fa8054bb454eaf33ee138196d6172ed1079c0/ruff-0.16.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a48cc05c6fbc811ca81b5d7ba95375affea6582d1b8024e455e41afbbf55344", size = 11072662, upload-time = "2026-08-07T13:30:18.143Z" }, + { url = "https://files.pythonhosted.org/packages/32/53/370d767c61c71a971a4ace36703a7ecd8c393956349a7325d7fab2b56827/ruff-0.16.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a2c0d14fcbb26c91f0f867a6dc9bd71bbc30b1b6151829c884f23faeab2e5700", size = 10566771, upload-time = "2026-08-07T13:30:20.899Z" }, + { url = "https://files.pythonhosted.org/packages/85/d6/9d96948caf5a632be62d62202d5ec914d6856f204fd79eb036e5915e79ea/ruff-0.16.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:335c621622c4650330be50842561c6586ac6971bb8ab5407fe34dcc9efb16bbe", size = 10975825, upload-time = "2026-08-07T13:30:23.517Z" }, + { url = "https://files.pythonhosted.org/packages/3b/92/ea87129b3414acb0b5770563779c51804d37ac67675c7ba35447ddb14773/ruff-0.16.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20e66910f2c37cc753f9ef6580c914a621b80c4fa3549d3e3521e29d0f5bfc3f", size = 10649437, upload-time = "2026-08-07T13:30:26.097Z" }, + { url = "https://files.pythonhosted.org/packages/ac/43/f8f291dcd4af5bb7872b74fdfa41a7cd7c856ca1d4069670971cf1b9f5cb/ruff-0.16.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7e36fbfba65510548156902bcf1350a979a958ce0347ce0f90d73894036b39f", size = 11446761, upload-time = "2026-08-07T13:30:28.752Z" }, + { url = "https://files.pythonhosted.org/packages/71/4a/ef991fb2fcf516ab71f0808adcdd8da5e18c8cde447f4ceaf5f47a5132a5/ruff-0.16.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f0eab35f80df8f134aae5d1630e751901321d317cc8e50dc39e36fa3ed34cd12", size = 12336364, upload-time = "2026-08-07T13:30:31.468Z" }, + { url = "https://files.pythonhosted.org/packages/f3/24/f615e74f307e6ca0e56a482872477b856c70d530aa356abfb6dfe5ca8a80/ruff-0.16.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ea8c0594feb894e89c8c61ab9c103d38b0ea72dfde6c594107147ca31b1140", size = 11630720, upload-time = "2026-08-07T13:30:34.426Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d3/8ef50149e8412a77f7ab409efdef0e2b23803707a3863da4fc64cb23d459/ruff-0.16.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab3d62dde0b19facdd632008cc4827fc28ada7736c6bd35ab6f1050f0bfed53f", size = 11466130, upload-time = "2026-08-07T13:30:36.958Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a7/a19334985c4dea8c381981fa252cd854c7ee52dc4b1686dc16f4a911c702/ruff-0.16.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e43e1f5b8388da9eca1b9e88328d47a5cec794633ccf6f7484ac2dd15eee92c0", size = 11523634, upload-time = "2026-08-07T13:30:39.822Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6c/96d192b0e742412ceda08c0a50f9669b253dde9fd6a60ea1a10c9fa79a63/ruff-0.16.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c24788a980581e1d7ea3a0cbe4344c4fbeb0a6a9b1f4713aa46bb104f8294690", size = 10949807, upload-time = "2026-08-07T13:30:42.745Z" }, + { url = "https://files.pythonhosted.org/packages/fa/51/e26599ceca11e79ee255c7df515995561edf87e9ca1893284e44d98f5a86/ruff-0.16.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:81806b08329130005dd4a8a8394a0c9da8c6f4cafb16ba438d2a2ee6a18bedf1", size = 10646891, upload-time = "2026-08-07T13:30:45.522Z" }, + { url = "https://files.pythonhosted.org/packages/68/01/800c4b1f97bc8d7c6029e06b1f20473a3cf1e13c4933d8f3342add83fc55/ruff-0.16.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4ce4e02bad779bef557f541a1b31f20d6abeae1cc05ed1b1ac019d4ffd1044c8", size = 11162063, upload-time = "2026-08-07T13:30:48.131Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d0/1477ea50fc5a0d4b0b71d1d63d50770bdd794d90b43e37a7618e63ec9894/ruff-0.16.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e0422abdf70070255fc4073ce9dfc814cc03db577013761ddd09bc1e4a9a4fbd", size = 11556038, upload-time = "2026-08-07T13:30:50.686Z" }, + { url = "https://files.pythonhosted.org/packages/b8/76/a7776f32048d991e16d4fa8ff91790b877342d3596cc3ed04acdbf1aaedc/ruff-0.16.2-py3-none-win32.whl", hash = "sha256:bf3a63d78fb39f4bf5ac8ae52051c5520505301abe19ba4e204c453b3f09bb0b", size = 10872850, upload-time = "2026-08-07T13:30:53.471Z" }, + { url = "https://files.pythonhosted.org/packages/00/0d/929c800d920e61397d82a01b60bffc68da3052c17d31de59efaad2e4ed75/ruff-0.16.2-py3-none-win_amd64.whl", hash = "sha256:bcabe2f6d0fc7819f1431793005af4e4de7371927d037345bf941252b195b9fa", size = 12023338, upload-time = "2026-08-07T13:30:56.193Z" }, + { url = "https://files.pythonhosted.org/packages/5b/6c/93e26c22c5f78ff87363e07da49c84955affbeb1098bd1936bf3b3f293bf/ruff-0.16.2-py3-none-win_arm64.whl", hash = "sha256:d614e95cedf38a2053fd351c55b103ba30d017d61688fdbfd40ee0412852a99f", size = 11374065, upload-time = "2026-08-07T13:30:58.775Z" }, ] [[package]] @@ -2968,10 +2968,10 @@ name = "torchmetrics" version = "1.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "lightning-utilities", marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "packaging", marker = "python_full_version < '3.13'" }, - { name = "torch", marker = "python_full_version < '3.13'" }, + { name = "lightning-utilities" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "torch" }, ] sdist = { url = "https://files.pythonhosted.org/packages/81/34/39b8b749333db56c0585d7a11fa62a283c087bb1dfc897d69fb8cedbefb1/torchmetrics-1.9.0.tar.gz", hash = "sha256:a488609948600df52d3db4fcdab02e62aab2a85ef34da67037dc3e65b8512faa", size = 581765, upload-time = "2026-03-09T17:41:22.443Z" } wheels = [ @@ -3066,13 +3066,13 @@ name = "trustmark" version = "0.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "einops", marker = "python_full_version < '3.13'" }, - { name = "lightning", marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "omegaconf", marker = "python_full_version < '3.13'" }, - { name = "six", marker = "python_full_version < '3.13'" }, - { name = "torch", marker = "python_full_version < '3.13'" }, - { name = "torchvision", marker = "python_full_version < '3.13'" }, + { name = "einops" }, + { name = "lightning" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, + { name = "omegaconf" }, + { name = "six" }, + { name = "torch" }, + { name = "torchvision" }, ] sdist = { url = "https://files.pythonhosted.org/packages/87/0a/0a4232030c6a62d12b6a02ae73bdce6e99c8532bc8f05a5a2e6ce103da82/trustmark-0.9.1.tar.gz", hash = "sha256:dc79e3fb070f5d94765acf8868a51f50a612cc05b53223cf1e6b605d4ff1e0ae", size = 63949, upload-time = "2026-04-09T08:59:52.472Z" } From 37789e02f695dc3e2bbc3f3c6a70c8f397af41e8 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Thu, 13 Aug 2026 11:59:56 -0700 Subject: [PATCH 10/44] Vectorize the DWT-DCT decode path, 15x on the decoder Output stays bit-identical: decoder bits and detector verdicts recorded over 200 sampled data/ images plus two synthesized carriers before and after, and the record is byte-identical. Measured on a 1536x2816 image -- decoder 0.280s to 0.016s, warm identify() 1.757s to 1.365s with both arms timed in one process. Co-Authored-By: Claude Opus 5 --- .claude/rules/development.md | 9 +++ docs/code-provenance.md | 5 ++ docs/module-internals.md | 48 +++++++++++++-- src/remove_ai_watermarks/dwt_dct.py | 95 ++++++++++++++++++----------- 4 files changed, 117 insertions(+), 40 deletions(-) diff --git a/.claude/rules/development.md b/.claude/rules/development.md index c2a95aa..9db0237 100644 --- a/.claude/rules/development.md +++ b/.claude/rules/development.md @@ -98,6 +98,15 @@ record is byte-identical, and a green test suite does not establish that on its change that is meant to FIX detection is the exception that proves the rule: the diff must then be exactly the files you intended to change, named in advance. +Two corollaries in `dwt_dct.py`, where "close enough" has an exact meaning. The bit test +is `peak % 36 > 18.0` and for uint8 input the exact Haar value is a multiple of 0.5, so it +lands ON the threshold once per 72 blocks and a 1-ulp difference flips real bits: leave the +transform to `pywt` however slow it looks, because its C convolution contracts into an FMA +that numpy has no ufunc for (`docs/module-internals.md` carries what that costs). And +`tests/test_invisible_watermark.py` is `skipif(not is_available())` -- without the `detect` +extra the upstream-parity test never runs, so a green suite is not evidence here and the +verdict record is not optional. + ## A certified operating point is data, not a constant The video SynthID default is only meaningful as a row in diff --git a/docs/code-provenance.md b/docs/code-provenance.md index d26ba2c..eb5de34 100644 --- a/docs/code-provenance.md +++ b/docs/code-provenance.md @@ -7,6 +7,11 @@ This page records notices required by source dependencies and licensed derivativ - The DWT-DCT implementation derives from ShieldMnt's [`invisible-watermark`](https://github.com/ShieldMnt/invisible-watermark), licensed under MIT. Its notice ships in `src/remove_ai_watermarks/licenses/invisible-watermark-MIT.txt`. + The decode path is a vectorized reformulation, not a transcription: it produces + the same bits and is checked against upstream's own decoder, but it no longer + corresponds line by line to `imwatermark/maxDct.py`. Diffing the two files will + show structurally different code, which is expected and does not mean the + derivation notice is stale. ## Licensed test fixtures diff --git a/docs/module-internals.md b/docs/module-internals.md index 69c7784..cfb2fa3 100644 --- a/docs/module-internals.md +++ b/docs/module-internals.md @@ -555,9 +555,11 @@ the function it replaces does — no provenance means no relaxation, and an unkn invisible target means scrub rather than skip. The `detect` extra composes the shared `pixels` runtime with PyWavelets. Its -in-tree [`dwt_dct.py`](../src/remove_ai_watermarks/dwt_dct.py) decoder preserves -the upstream matrix algorithm without installing Torch or non-headless OpenCV. -The upstream MIT notice ships inside the wheel under `licenses/`. +in-tree [`dwt_dct.py`](../src/remove_ai_watermarks/dwt_dct.py) decoder reproduces +the upstream algorithm's output bit for bit without installing Torch or +non-headless OpenCV; the block scan is vectorized rather than transcribed, so +the file no longer reads line by line against `maxDct.py`. The upstream MIT +notice ships inside the wheel under `licenses/`. `is_ai_generated` is `True` or `None`; absence of evidence is not reported as a human-made verdict. `ai_source_kind` distinguishes fully generated content from @@ -572,11 +574,49 @@ schemas 0-2 count as positives. Schema 3 is below the precision threshold: all same false payload after re-encoding. The official Adobe Variant P schema-1 fixture in `data/fixtures/provenance/` is the positive regression control. +#### Why the DWT-DCT decoder looks the way it does + +Two things in `dwt_dct.py` are load-bearing and neither is obvious from the code. + +`_approximation` calls `pywt.dwt` twice instead of `pywt.dwt2`, and transposes +before each pass. A factorial ablation over both axes separates the two: +skipping the three detail bands `dwt2` computes and this code discards is worth +**4%**, while the transposes are worth **2.8x**, because pywt walks the axis it +transforms and on axis 0 of a C-contiguous plane that is a column walk. The +arithmetic saving is the intuitive explanation and it is the small term; four +independent profiles named it as the mechanism before the ablation contradicted +them. + +Bit-identity is a hard requirement, not a preference. For uint8 input the exact +Haar LL value is a multiple of 0.5 and the bit test is `peak % 36 > 18.0`, a +threshold sitting exactly on a representable value that ~1 block in 72 lands on, +so a 1-ulp difference deterministically flips real bits. That is why the ~16x +available from a hand-rolled numpy Haar is unreachable rather than merely +untaken: pywt's C convolution contracts into an FMA that numpy has no ufunc for, +and `np.longdouble` is 64-bit on arm64 macOS. + +Measured on a 1536x2816 image: the decoder went 0.280 s to 0.019 s, and a warm +`identify()` on the same file 1.757 s to 1.365 s (-22.3%), both arms timed in one +process. Verified by recording decoder output and detector verdict over 200 +sampled `data/` images plus two synthesized carriers before and after the change: +the record is byte-identical, as are five degenerate shapes (`1x65536` through +`8x8192`) that clear the caller's area check. + +Two further optimizations were identified and deliberately not taken, because +each buys speed with a new precondition rather than with less work: +`pywt.downcoef("a", ...)` on a flattened plane (valid only while the last axis +stays even and C-contiguous), and a single-pass `np.abs(..., out=)` over the +block gather. Both would need their own ablation. + Regression coverage: - [`test_identify.py`](../tests/test_identify.py) - [`test_trustmark_detector.py`](../tests/test_trustmark_detector.py) -- [`test_invisible_watermark.py`](../tests/test_invisible_watermark.py) +- [`test_invisible_watermark.py`](../tests/test_invisible_watermark.py) -- + note `test_in_tree_decoder_matches_upstream` is the parity guard against + upstream's own decoder, and the whole module is `skipif(not is_available())`. + A green run without the `detect` extra installed has not checked parity at + all, so a decoder change still owes the before/after verdict record. ## Visible mark removal diff --git a/src/remove_ai_watermarks/dwt_dct.py b/src/remove_ai_watermarks/dwt_dct.py index 7631a7d..5f45898 100644 --- a/src/remove_ai_watermarks/dwt_dct.py +++ b/src/remove_ai_watermarks/dwt_dct.py @@ -1,7 +1,11 @@ """DWT-DCT decoder compatible with invisible-watermark's ``dwtDct`` path. Derived from ShieldMnt/invisible-watermark ``imwatermark/maxDct.py`` (MIT), -trimmed to the matrix path used by Stable Diffusion, SDXL, and FLUX. +trimmed to the matrix path used by Stable Diffusion, SDXL, and FLUX. The block +scan is vectorized rather than transcribed, so the file no longer reads line by +line against upstream; what it preserves is the output, bit for bit. See +[`docs/module-internals.md`](../../docs/module-internals.md) for the +measurements and for why a faster hand-rolled transform is not available. Copyright (c) 2021 ShieldMnt @@ -42,45 +46,64 @@ class _DecodeMaxDct: def decode(self, bgr: NDArray[Any]) -> dict[int, NDArray[Any]]: row, col, _channels = bgr.shape yuv = cv2.cvtColor(bgr, cv2.COLOR_BGR2YUV) + trimmed = yuv[: row // 4 * 4, : col // 4 * 4] - scores_by_length = {wm_len: ([0] * wm_len, [0] * wm_len) for wm_len in self._wm_lengths} - for channel in range(2): - if self._scales[channel] <= 0: - continue - ca1, _detail = pywt.dwt2(yuv[: row // 4 * 4, : col // 4 * 4, channel], "haar") - self._decode_frame(ca1, self._scales[channel], scores_by_length) + per_channel = [ + self._frame_bits(self._approximation(trimmed, channel), self._scales[channel]) + for channel in range(2) + if self._scales[channel] > 0 + ] + # Each channel restarts the bit index at 0, so the buckets come from a + # per-channel arange rather than one running counter. + index = np.concatenate([np.arange(bits.size) for bits in per_channel] or [np.zeros(0, dtype=np.int64)]) + weights = np.concatenate(per_channel or [np.zeros(0)]) - return { - wm_len: np.asarray(sums) * 255 > np.asarray(counts) * 127 - for wm_len, (sums, counts) in scores_by_length.items() - } + decoded: dict[int, NDArray[Any]] = {} + for wm_len in self._wm_lengths: + bucket = index % wm_len + sums = np.bincount(bucket, weights=weights, minlength=wm_len) + counts = np.bincount(bucket, minlength=wm_len) + decoded[wm_len] = sums * 255 > counts * 127 + return decoded - def _decode_frame( - self, - frame: NDArray[Any], - scale: int, - scores_by_length: dict[int, tuple[list[int], list[int]]], - ) -> None: - row, col = frame.shape - bit_index = 0 - for i in range(row // self._block): - for j in range(col // self._block): - block = frame[ - i * self._block : i * self._block + self._block, - j * self._block : j * self._block + self._block, - ] - inferred = self._infer_bit(block, scale) - for wm_len, (sums, counts) in scores_by_length.items(): - bucket = bit_index % wm_len - sums[bucket] += inferred - counts[bucket] += 1 - bit_index += 1 + @staticmethod + def _approximation(trimmed: NDArray[Any], channel: int) -> NDArray[Any]: + """The Haar approximation band, and only it. - def _infer_bit(self, block: NDArray[Any], scale: int) -> int: - position = int(np.argmax(np.abs(block.flatten()[1:]))) + 1 - i, j = position // self._block, position % self._block - value = abs(float(block[i][j])) - return int((value % scale) > 0.5 * scale) + ``dwt2`` is ``dwtn``: it transforms along axis 0, then along axis 1 over + both halves, and three of the four bands it returns are discarded here. + Two ``dwt`` calls keeping ``[0]`` skip that, and transposing between them + lets pywt walk a contiguous axis instead of a column. + + The result must stay bit-identical to ``dwt2``'s, which is why the + transform is left to pywt however slow that is: the caller's threshold is + ``peak % 36 > 18.0``, and for uint8 input the exact value is a multiple + of 0.5, so it lands exactly on the threshold often enough that a 1-ulp + difference flips real bits. + """ + if trimmed.shape[0] == 0 or trimmed.shape[1] == 0: + # Reachable: a 1x65536 image clears the caller's area check. Left to + # dwt2 so the exception stays the one this module has always raised. + return pywt.dwt2(trimmed[:, :, channel], "haar")[0] + columns = cv2.transpose(cv2.extractChannel(trimmed, channel)) + along_rows = pywt.dwt(columns, "haar", axis=1)[0] + return pywt.dwt(cv2.transpose(along_rows), "haar", axis=1)[0] + + def _frame_bits(self, frame: NDArray[Any], scale: int) -> NDArray[Any]: + """One bit per 4x4 block, in row-major block order. + + Upstream's per-block loop, said to numpy once instead of to the + interpreter ~135k times per image. + """ + block = self._block + rows = frame.shape[0] // block + cols = frame.shape[1] // block + if rows == 0 or cols == 0: + return np.zeros(0, dtype=np.float64) + aligned = frame[: rows * block, : cols * block] + blocks = aligned.reshape(rows, block, cols, block).swapaxes(1, 2) + peak = np.abs(blocks.reshape(rows * cols, block * block)[:, 1:]).max(axis=1) + return ((peak % scale) > 0.5 * scale).astype(np.float64) def decode_dwt_dct(bgr: NDArray[Any], wm_len: int) -> NDArray[Any]: From 4855586834fe78499f06295ab24d2b3f3a2bf4b9 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Thu, 13 Aug 2026 13:33:34 -0700 Subject: [PATCH 11/44] Halve decoder memory with strip processing and a raveled Haar pass Each Haar pass is one flat pywt.downcoef call over a raveled strip instead of pywt.dwt(..., axis=1)[0], and the plane is walked in strips so no full-plane float64 intermediate exists. Exact only while the last axis is even, so _approximation raises on an odd width rather than returning wrong bits, and TestRaveledHaarPass pins both that raise and the downcoef/dwt equivalence a pywt upgrade could take away. Drops the block constructor knob: the fold chains are written for 4, nothing ever passed another value, and a knob that silently decodes wrong is worse than no knob. Peak RSS 111 MB to 21 MB on a 4.3 MP image; the decoder itself 0.011s to 0.007s, which is only 0.4% of identify() now that it is under 2% of the run. Output bits and detector verdicts over 200 sampled data/ images, two synthesized carriers and eight degenerate shapes are byte-identical to the pre-vectorization decoder. Co-Authored-By: Claude Opus 5 --- docs/module-internals.md | 36 +++++++--- src/remove_ai_watermarks/dwt_dct.py | 100 ++++++++++++++++++++-------- tests/test_invisible_watermark.py | 41 ++++++++++++ 3 files changed, 138 insertions(+), 39 deletions(-) diff --git a/docs/module-internals.md b/docs/module-internals.md index cfb2fa3..89bce5a 100644 --- a/docs/module-internals.md +++ b/docs/module-internals.md @@ -595,18 +595,32 @@ available from a hand-rolled numpy Haar is unreachable rather than merely untaken: pywt's C convolution contracts into an FMA that numpy has no ufunc for, and `np.longdouble` is 64-bit on arm64 macOS. -Measured on a 1536x2816 image: the decoder went 0.280 s to 0.019 s, and a warm -`identify()` on the same file 1.757 s to 1.365 s (-22.3%), both arms timed in one -process. Verified by recording decoder output and detector verdict over 200 -sampled `data/` images plus two synthesized carriers before and after the change: -the record is byte-identical, as are five degenerate shapes (`1x65536` through -`8x8192`) that clear the caller's area check. +Each pass is one flat `pywt.downcoef` call over a raveled strip rather than +`pywt.dwt(..., axis=1)[0]`, and the plane is processed in strips of `_STRIP` +block-rows so no full-plane float64 intermediate is ever materialized. The strip +height is not a tuned value: 8 through 64 all scored inside each other's noise +with unstable ordering, and only "strips at all" versus whole-plane matters. -Two further optimizations were identified and deliberately not taken, because -each buys speed with a new precondition rather than with less work: -`pywt.downcoef("a", ...)` on a flattened plane (valid only while the last axis -stays even and C-contiguous), and a single-pass `np.abs(..., out=)` over the -block gather. Both would need their own ablation. +`_approximation`'s even-last-axis check is load-bearing, not defensive. Haar's +filter is length 2, so an even row length keeps every pair inside its own row; +on an odd width the pairs walk across row boundaries and the reshape still +succeeds whenever the total is even, which would be wrong bits with no +exception. Both call sites are even by construction today, and +`TestRaveledHaarPass` pins both halves -- the `downcoef`/`dwt` equivalence, +which a pywt upgrade could take away, and the raise on an odd width. + +Measured on a 1536x2816 image, all arms timed in one process: the decoder went +0.112 s (the original per-block Python loop) to 0.011 s vectorized to 0.007 s +with strips, and a warm `identify()` 1.757 s to 1.365 s on the first step and a +further 0.4% on the second. That last figure is the point at which this target +is finished: the decoder is now under 2% of `identify()`, so speed here has +stopped buying anything. What the strips buy is peak RSS in the stage, 111 MB to +21 MB on a 4.3 MP image, which is what matters on the memory-limited Space. + +Both steps were verified by recording decoder output and detector verdict over +200 sampled `data/` images plus two synthesized carriers before and after: the +record is byte-identical, as are seven degenerate shapes (`1x65536` through +`8x8192`, plus an odd width) that clear the caller's area check. Regression coverage: diff --git a/src/remove_ai_watermarks/dwt_dct.py b/src/remove_ai_watermarks/dwt_dct.py index 5f45898..ba6fe13 100644 --- a/src/remove_ai_watermarks/dwt_dct.py +++ b/src/remove_ai_watermarks/dwt_dct.py @@ -27,7 +27,39 @@ if TYPE_CHECKING: from numpy.typing import NDArray _DEFAULT_SCALES = (0, 36, 36) -_DEFAULT_BLOCK = 4 +# Fixed by the format being decoded, and the 4-way fold chains in `_frame_bits` +# are written for it. It was a constructor parameter while the block scan was a +# generic Python loop; nothing ever passed another value, and a knob that now +# silently returns wrong bits is worse than no knob. +_BLOCK = 4 + +# Block-rows of the approximation band handled per strip. Not a tuned value: +# every height measured landed inside the others' noise. What matters is strips +# at all rather than a full-plane intermediate, not this number. +_STRIP = 16 + + +def _approximation(rows: NDArray[Any]) -> NDArray[Any]: + """One Haar pass along the last axis, approximation band only. + + ``pywt.dwt(x, "haar", axis=1)[0]`` computes and allocates the detail band as + well, and dispatches per row. Flattening lets one ``downcoef`` call do the + whole plane, and it is the same numbers in the same order **only while the + last axis is even**: Haar's filter is length 2, so an even row length makes + every pair fall inside its own row with no boundary extension. On an odd + width the pairs walk across row boundaries and the reshape below still + succeeds whenever the total is even -- wrong bits, no exception, and the + upstream-parity test is `skipif`-gated. Hence the explicit check. + + The transform itself stays inside pywt however slow that looks. Its C + convolution contracts into an FMA that no numpy expression reproduces, and + the caller's threshold is ``peak % 36 > 18.0`` against values that are exact + multiples of 0.5, so a 1-ulp difference flips real bits. + """ + height, width = rows.shape + if width % 2: + raise RuntimeError(f"row length {width} is odd; the raveled Haar pass requires an even last axis") + return pywt.downcoef("a", rows.ravel(), "haar").reshape(height, width // 2) class _DecodeMaxDct: @@ -37,11 +69,9 @@ class _DecodeMaxDct: self, wm_lengths: tuple[int, ...], scales: tuple[int, int, int] = _DEFAULT_SCALES, - block: int = _DEFAULT_BLOCK, ) -> None: self._wm_lengths = wm_lengths self._scales = scales - self._block = block def decode(self, bgr: NDArray[Any]) -> dict[int, NDArray[Any]]: row, col, _channels = bgr.shape @@ -49,7 +79,7 @@ class _DecodeMaxDct: trimmed = yuv[: row // 4 * 4, : col // 4 * 4] per_channel = [ - self._frame_bits(self._approximation(trimmed, channel), self._scales[channel]) + self._plane_bits(trimmed, channel, self._scales[channel]) for channel in range(2) if self._scales[channel] > 0 ] @@ -66,43 +96,57 @@ class _DecodeMaxDct: decoded[wm_len] = sums * 255 > counts * 127 return decoded - @staticmethod - def _approximation(trimmed: NDArray[Any], channel: int) -> NDArray[Any]: - """The Haar approximation band, and only it. + def _plane_bits(self, trimmed: NDArray[Any], channel: int, scale: int) -> NDArray[Any]: + """Block bits for one colour plane, a strip of block-rows at a time. ``dwt2`` is ``dwtn``: it transforms along axis 0, then along axis 1 over both halves, and three of the four bands it returns are discarded here. - Two ``dwt`` calls keeping ``[0]`` skip that, and transposing between them - lets pywt walk a contiguous axis instead of a column. + Only the approximation is ever asked for, and transposing between the + two passes lets pywt walk a contiguous axis instead of a column -- that + access pattern, not the arithmetic saved, is where the time goes. - The result must stay bit-identical to ``dwt2``'s, which is why the - transform is left to pywt however slow that is: the caller's threshold is - ``peak % 36 > 18.0``, and for uint8 input the exact value is a multiple - of 0.5, so it lands exactly on the threshold often enough that a 1-ulp - difference flips real bits. + Strips mean no full-plane float64 intermediate is ever materialized, and + they are seam-free for the reason ``_approximation`` documents: output + row ``k`` reads input rows ``2k`` and ``2k + 1`` only. Strips start on + multiples of ``2 * _BLOCK``, so neither a pair nor a 4x4 block straddles + one. """ if trimmed.shape[0] == 0 or trimmed.shape[1] == 0: - # Reachable: a 1x65536 image clears the caller's area check. Left to - # dwt2 so the exception stays the one this module has always raised. + # Reachable: a 1x65536 image clears the caller's area check and + # trims to an empty plane. Left to dwt2 so the exception stays the + # one this module has always raised -- returning empty bits here + # instead would silently turn a raise into an all-false verdict. return pywt.dwt2(trimmed[:, :, channel], "haar")[0] - columns = cv2.transpose(cv2.extractChannel(trimmed, channel)) - along_rows = pywt.dwt(columns, "haar", axis=1)[0] - return pywt.dwt(cv2.transpose(along_rows), "haar", axis=1)[0] + rows = trimmed.shape[0] // (2 * _BLOCK) + cols = trimmed.shape[1] // (2 * _BLOCK) + if rows == 0 or cols == 0: + return np.zeros(0, dtype=np.float64) + + width = cols * 2 * _BLOCK + pieces: list[NDArray[Any]] = [] + for start in range(0, rows, _STRIP): + stop = min(start + _STRIP, rows) + strip = trimmed[start * 2 * _BLOCK : stop * 2 * _BLOCK, :width] + columns = cv2.transpose(cv2.extractChannel(strip, channel)) + band = _approximation(cv2.transpose(_approximation(columns))) + pieces.append(self._frame_bits(band, scale)) + return np.concatenate(pieces) def _frame_bits(self, frame: NDArray[Any], scale: int) -> NDArray[Any]: """One bit per 4x4 block, in row-major block order. Upstream's per-block loop, said to numpy once instead of to the - interpreter ~135k times per image. + interpreter ~135k times per image. Zeroing the DC term in the absolute + band says "ignore index 0" without materializing a ``(nblocks, 16)`` + copy, and the 4-way ``np.maximum`` chains reduce over contiguous rows. """ - block = self._block - rows = frame.shape[0] // block - cols = frame.shape[1] // block - if rows == 0 or cols == 0: - return np.zeros(0, dtype=np.float64) - aligned = frame[: rows * block, : cols * block] - blocks = aligned.reshape(rows, block, cols, block).swapaxes(1, 2) - peak = np.abs(blocks.reshape(rows * cols, block * block)[:, 1:]).max(axis=1) + rows = frame.shape[0] // _BLOCK + cols = frame.shape[1] // _BLOCK + band = np.abs(frame[: rows * _BLOCK, : cols * _BLOCK]) + band[::_BLOCK, ::_BLOCK] = 0.0 + folded = np.maximum(np.maximum(band[0::4], band[1::4]), np.maximum(band[2::4], band[3::4])) + folded = folded.reshape(rows * cols, _BLOCK) + peak = np.maximum(np.maximum(folded[:, 0], folded[:, 1]), np.maximum(folded[:, 2], folded[:, 3])) return ((peak % scale) > 0.5 * scale).astype(np.float64) diff --git a/tests/test_invisible_watermark.py b/tests/test_invisible_watermark.py index a516146..db0568c 100644 --- a/tests/test_invisible_watermark.py +++ b/tests/test_invisible_watermark.py @@ -59,6 +59,47 @@ class TestHelpers: assert _bytes_match_frac(b"abc", b"abcd") == 0.0 +class TestRaveledHaarPass: + """The precondition that makes the decoder's flat Haar pass legitimate. + + `_approximation` replaces `pywt.dwt(x, "haar", axis=1)[0]` with one + `downcoef` call over `x.ravel()`. That is exact only while the last axis is + even. Neither half of this is checked anywhere else: the equivalence is a + property of pywt's implementation that an upgrade could take away, and an + odd width produces wrong bits with no exception, since the reshape still + succeeds whenever the total length is even. + """ + + @pytest.mark.parametrize("shape", [(64, 64), (7, 128), (129, 2), (2, 2), (33, 400)]) + def test_matches_pywt_dwt_on_even_widths(self, shape: tuple[int, int]): + import pywt + + from remove_ai_watermarks.dwt_dct import _approximation + + rng = np.random.default_rng(0) + # uint8 is what the FIRST production pass receives -- `decode` builds its + # plane with cvtColor, and extractChannel and transpose preserve the + # dtype -- so a divergence in how downcoef coerces integers would be + # invisible to a float-only parametrization. + for array in ( + rng.random(shape), + rng.integers(0, 256, shape).astype(np.float64), + rng.integers(0, 256, shape).astype(np.uint8), + ): + expected = pywt.dwt(array, "haar", axis=1)[0] + got = _approximation(array) + assert got.shape == expected.shape + assert np.array_equal(got, expected), "downcoef diverged from dwt -- a pywt upgrade may have changed it" + + def test_odd_width_raises_instead_of_returning_wrong_bits(self): + from remove_ai_watermarks.dwt_dct import _approximation + + # 4x6 ravels to 24, an even total, so the reshape would happily produce + # a 4x3 array of numbers that pair across row boundaries. + with pytest.raises(RuntimeError, match="odd"): + _approximation(np.zeros((4, 6))[:, :5]) + + class TestDetect: def test_in_tree_decoder_matches_upstream(self, tmp_path: Path): from imwatermark import WatermarkDecoder From e8f65022cc019700898f511fe99ac8ad42c10684 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Sat, 15 Aug 2026 11:01:14 -0700 Subject: [PATCH 12/44] Record the video SynthID measurements and park the investigation Everything here is evidence and state, not behavior: the shipped operating point is untouched at 512 px / 12 fps / noise_std=0.15. The carrier was downloaded and its sha256 matched the manifest, so its properties are now measured rather than assumed: 1920x1080 at 24 fps with an AAC audio track. Those three fields go into the two 2026-07-31 rows, which could not previously tell a reader what the downscale factor even was. The geometry prize is measured end to end for the first time. Against the untouched source, 1024 scores +3.46 dB over the shipped 512, while the entire noise_std axis is worth 1.92 dB. Two readings that the table alone hides are recorded with it: the temporal residual IMPROVES with resolution, because the shared noise field lives on the latent grid and is four times finer relative to the frame at 1920; and the frame-rate arm cannot be judged by these metrics at all, since they price its cost and not the smoothness it buys. Two findings that outrank the quality question. The certified row does not reproduce -- a rerun of the same configuration on a different device and dtype gives a different hash and different metrics, and the manifest records neither. And the pipeline copies audio byte for byte while stripping every metadata marker, so it can emit a file our own detector calls clean with an untouched Google-generated audio track inside it. The mechanism is proven on two carriers; whether that audio carries a mark the verifier reads is not, and only the oracle can say. The six prepared oracle submissions were never run: file upload to the verifier failed at the tool level. Their artifacts are gone with the scratch directory, which is the intended lifecycle for generated media, and the document says what rebuilding costs. Co-Authored-By: Claude Opus 5 --- data/evaluations/video-synthid-oracle.csv | 4 +- docs/video-synthid-quality-research.md | 114 ++++++++++++++++++++++ 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/data/evaluations/video-synthid-oracle.csv b/data/evaluations/video-synthid-oracle.csv index 74b90f8..a740419 100644 --- a/data/evaluations/video-synthid-oracle.csv +++ b/data/evaluations/video-synthid-oracle.csv @@ -1,3 +1,3 @@ date,source_url,source_sha256,source_width,source_height,source_fps,duration_seconds,source_verdict,vae,noise_std,long_side,fps,seed,output_sha256,output_verdict,output_verdict_text,output_detected_range,track,session_id,stratum,psnr_db,temporal_residual_ratio -2026-07-31,https://storage.googleapis.com/gdm-deepmind-com-prod-public/media/media/veo__veo-3__off-road.mp4,79a552b9406a079682440c31f14d33a10ba8e1b8b2e96425f5de70f63350299d,,,,8,detected_all_frames,,0.10,512,12,0,079165105d4c56e1612091987c08c2627049423025f74c0d4e245fb47c2ff0e3,detected,,,,,,26.2932,1.0072 -2026-07-31,https://storage.googleapis.com/gdm-deepmind-com-prod-public/media/media/veo__veo-3__off-road.mp4,79a552b9406a079682440c31f14d33a10ba8e1b8b2e96425f5de70f63350299d,,,,8,detected_all_frames,,0.15,512,12,0,1c4046bcfdead138353b4e2a73339ba227bb5e544878d80c5bc6cd8427c7b00e,not_detected,,,,,,25.3911,1.0578 +2026-07-31,https://storage.googleapis.com/gdm-deepmind-com-prod-public/media/media/veo__veo-3__off-road.mp4,79a552b9406a079682440c31f14d33a10ba8e1b8b2e96425f5de70f63350299d,1920,1080,24,8,detected_all_frames,,0.10,512,12,0,079165105d4c56e1612091987c08c2627049423025f74c0d4e245fb47c2ff0e3,detected,,,,,,26.2932,1.0072 +2026-07-31,https://storage.googleapis.com/gdm-deepmind-com-prod-public/media/media/veo__veo-3__off-road.mp4,79a552b9406a079682440c31f14d33a10ba8e1b8b2e96425f5de70f63350299d,1920,1080,24,8,detected_all_frames,,0.15,512,12,0,1c4046bcfdead138353b4e2a73339ba227bb5e544878d80c5bc6cd8427c7b00e,not_detected,,,,,,25.3911,1.0578 diff --git a/docs/video-synthid-quality-research.md b/docs/video-synthid-quality-research.md index f1ac015..dc2e4c0 100644 --- a/docs/video-synthid-quality-research.md +++ b/docs/video-synthid-quality-research.md @@ -171,6 +171,120 @@ Three readings, all MEASURED, none of them about SynthID: so the `max(temporal_baseline, 1e-6)` denominator collapses and the ratio inflates. This is the predicted defect, now observed rather than argued. +## Measurements on the real carrier (2026-08-06) + +The public Veo 3 off-road sample was downloaded and its sha256 matched the manifest +exactly, so everything below is measured on the same file the 2026-07-31 +calibration used. Its properties, none of which the manifest recorded at the time: +H.264 1920x1080 at 24 fps, 8.000 s, 5,155,063 bps video, plus an **AAC 48 kHz +stereo 192,241 bps audio track of 7.914 s**. + +The shipped profile therefore delivers 512x288 at 12 fps: **7.1% of the frame area +and 3.6% of the pixels per second.** + +### The geometry prize, measured end to end + +Scored with `scripts/video_fidelity_probe.py` against the untouched 1920x1080 +source, all at `noise_std=0.15`: + +| Geometry | Frame area | PSNR | SSIM | Temporal | Bitrate | +| --- | --- | --- | --- | --- | --- | +| 512x288 @12 (shipped) | 7.1% | 25.3971 | 0.8215 | 1.0407 | 2187 kbps | +| 768x432 @12 | 16.0% | 27.3796 | 0.8556 | 1.0323 | 3782 kbps | +| 1024x576 @12 | 28.4% | 28.8542 | 0.8819 | 1.0262 | 5604 kbps | +| 512x288 @24 | 7.1% | 25.6142 | 0.8270 | 1.0995 | 3181 kbps | + +**512 to 1024 is worth +3.46 dB, against 1.92 dB for the entire `noise_std` axis.** +The geometry lever is roughly twice the noise lever, which is the first hard number +this investigation has for the size of the prize. What it still costs in removal is +unknown: none of these was ever submitted. + +Two readings that are not obvious from the table: + +- **Temporal residual improves as resolution rises** (1.0407 to 1.0262). The shared + noise field lives on the latent grid, so at 1920 it is four times finer relative + to the frame than at 512, where it stretches into low-frequency blotches. Higher + resolution plausibly makes the perturbation *less* visible, not more. +- **The frame-rate arm looks bad here and the metric is the wrong judge.** 24 fps + buys +0.22 dB and worsens the temporal ratio to 1.0995, but what 24 fps actually + buys is smoothness, which neither PSNR nor SSIM measures at all. The metric sees + only the cost. Do not decide the fps question from this table. + +Encoder cost on an M-series MPS device, 8 seconds of source: 51 s at 512/12, 180 s +at 768/12, 380 s at 1024/12, 190 s at 512/24. Native 1920x1080 at 24 fps +extrapolates to roughly 46 minutes for the same 8 seconds, around 350x real time. A +native run was started and killed before producing anything, so that figure remains +an extrapolation. + +### The certified row does not reproduce + +Re-running the certified configuration (512/12/0.15, seed 0) on this machine gave +25.5002 dB and 1.0675 against the manifest's 25.3911 and 1.0578, and a different +output sha256. The manifest records neither device nor dtype, and the original was +almost certainly CUDA fp16 while this was MPS fp32. **The oracle certified a +specific file that cannot be rebuilt**, so any re-run produces an artifact the +verifier never saw. Device and dtype belong in the schema. + +### Audio passes through untouched, and the output still reads clean + +Established on two carriers, by comparing the extracted audio bitstream sha256 of +input and output: + +- Veo carrier: `674063df...` in, `674063df...` out. +- A Lyria 3 track generated for this purpose: `6ee4c041...` in, `6ee4c041...` out. + +The Lyria case is the sharper one because its provenance is known. Google's own +generator stamps it: `get_ai_metadata` reads a C2PA manifest naming +`Google C2PA Core Generator Library`, issuer `Google LLC`, `trainedAlgorithmicMedia`. +After the shipped path runs, that output reports `{}` -- every metadata marker +stripped, `remaining_metadata` empty, visuals regenerated -- while the audio the +generator produced is bit-for-bit intact. + +So the pipeline can produce a file that **our own detector calls clean while an +untouched Google-generated audio track sits inside it**. The mechanism is proven; +whether that audio carries an audible-to-the-verifier SynthID mark is not, and only +the oracle can say. If it does, this is a shipped defect rather than a quality +question, and it outranks everything else in this document. + +Worth recording alongside it: the Veo carrier itself returns `{}` from +`get_ai_metadata`. The public sample carries no C2PA at all, which is exactly why it +needed an oracle verdict in the first place. + +## State when this work was parked (2026-08-06) + +Landed in `main` through PR #78: the manifest-backed default pin, the +scaling-factor gate and its single seam, the sweep loading through the engine's +loader, `scripts/video_fidelity_probe.py` with its tests, the extended manifest +schema, and this document. **The shipped operating point did not move**: it is +still 512 px / 12 fps / `noise_std=0.15`. + +A product requirement arrived late and reframes the target: output resolution, +frame rate and bitrate must not be reduced. Resolution, frame rate and crf are +parameters and can be preserved, but zero quality loss is not achievable by this +mechanism at all -- removal works by rebuilding pixels, so a regeneration can never +return its input. The honest goal is to preserve container properties and make the +perceptual loss imperceptible, judged by DISTS and VMAF rather than PSNR. Two +consequences follow: crf 18 on this path against crf 14 on the visible path is an +unexplained asymmetry and a free quality win, and the oracle's 100 MB limit means a +higher-quality profile is *harder* to certify than the one it replaces. + +Nothing is blocked on analysis. Six oracle submissions were prepared and never run, +because file upload to the verifier failed at the tool level: + +1. A Lyria carrier and the pipeline's output from it, answering the audio question. +2. A multiplexed control-plus-candidate file at the shipped geometry, validating + whether the verifier reads a concatenated pair by time range. Verified locally + frame by frame: half one is bit-identical to the control, half two to the + candidate. +3. Three more multiplexed pairs for 768, 1024 and 24 fps, worth three queries + instead of six if the instrument validates. + +Every generated artifact for those submissions has since been lost with its +scratch directory, which is the intended lifecycle for generated media. Rebuilding +costs one download and roughly 25 minutes of encoding; the Lyria track is the +exception, since regenerating it would produce a different track, and the copies +that survive are the only ones. + ## The single most important unknown **How much the 512 px downscale contributes to removal.** Every quality gain routes From b7d44ff1a3888fec970c1f6b38ea5f9956bfb5a9 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Sat, 15 Aug 2026 11:08:09 -0700 Subject: [PATCH 13/44] Warn users that the invisible video path leaves audio untouched The finding lived only in the research archive, whose own banner says it may describe prototypes that were later removed. It belongs where users read limitations: the path strips every metadata marker while copying the audio bitstream byte for byte, so a clean local report on a clip with generated audio is unproven rather than a guarantee. Co-Authored-By: Claude Opus 5 --- docs/known-limitations.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/known-limitations.md b/docs/known-limitations.md index 8b7a73a..9bbdeba 100644 --- a/docs/known-limitations.md +++ b/docs/known-limitations.md @@ -112,6 +112,21 @@ measured 25.39 dB paired PSNR and a 1.058 motion-compensated temporal-residual ratio. This is one carrier, not a universal guarantee; hashes and exact verdicts are tracked in `data/evaluations/video-synthid-oracle.csv`. +### The invisible video path does not touch the audio track + +`remove_video_invisible` regenerates the video stream and copies the source audio +verbatim: the extracted audio bitstream of input and output has an identical +sha256, measured on two carriers. It also strips every metadata marker, so the +output can report clean from `get_ai_metadata` and `identify_video` while carrying +an untouched generated audio track. Google's verifier scores audio and visual +tracks separately, and a track this path never modifies is a track it cannot have +cleaned. + +Whether a given carrier's audio actually holds a mark the verifier reads has not +been established -- that needs a provider verdict, which has not been obtained. +Treat a clean local report on a clip with generated audio as unproven, not as a +guarantee, and check the audio separately when it matters. + The shipped engine streams sampled frames in bounded batches, computes its fidelity metrics incrementally, and pipes regenerated pixels directly to ffmpeg. Its frame and latent memory is therefore bounded by `--batch-size` From 2eab24a2e1225bbcca95825adb04da2e91c34186 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Sat, 15 Aug 2026 11:31:59 -0700 Subject: [PATCH 14/44] Validate C2PA credentials before attribution --- docs/cli.md | 5 +- docs/module-internals.md | 24 ++ docs/python-api.md | 9 + docs/supported-signals.md | 6 +- docs/verification-plan.md | 9 + src/remove_ai_watermarks/_internal/c2pa.py | 316 +++++++++++++++++- .../_internal/constants.py | 4 + src/remove_ai_watermarks/api.py | 3 +- src/remove_ai_watermarks/cli.py | 7 + src/remove_ai_watermarks/identify.py | 180 ++++++++-- src/remove_ai_watermarks/metadata.py | 21 +- tests/conftest.py | 26 +- tests/test_api.py | 9 + tests/test_cli.py | 2 + tests/test_identify.py | 117 ++++++- tests/test_metadata.py | 4 + tests/test_metadata_internals.py | 123 +++++++ tests/test_metadata_record.py | 8 + 18 files changed, 826 insertions(+), 47 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index 1d3ad1e..e296578 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -40,7 +40,10 @@ remove-ai-watermarks identify image.png `identify` always inspects supported metadata. When pixel extras are installed, it also evaluates supported visible and invisible pixel signals. When no signal is found, it reports the origin as unknown. It does not claim the image is -clean. +clean. For C2PA files, the text report shows asset integrity, claim-signature, +signer-trust, and signer-validity results separately. An intact claim from an +untrusted or expired signer is reported at medium confidence; a failed asset +binding or signature does not confirm the claimed origin. Machine readable output: diff --git a/docs/module-internals.md b/docs/module-internals.md index 89bce5a..c3a4061 100644 --- a/docs/module-internals.md +++ b/docs/module-internals.md @@ -364,6 +364,30 @@ Regression coverage: official `c2pa-python` reader first. Its byte-level PNG parser remains a fallback for partial and synthetic fixtures that the official reader rejects. +Structured extraction is limited to the active manifest and the ingredient +manifests reachable from it. Validation is preserved as separate dimensions: +asset binding integrity, claim signature, signer trust, and signer certificate +validity. A matching asset hash and valid claim signature do not make an +untrusted or expired signer trusted. `identify` therefore assigns high confidence +only when all four dimensions validate, medium confidence to an intact but +untrusted or unvalidated claim, and no origin verdict to a hash/signature failure. +The failed claim remains in the marker inventory and keeps the removal gate +fail-safe because a post-signing container edit can invalidate C2PA without +removing a declared pixel watermark. + +The structured walk also treats an exact known AI product in a reachable +`claim_generator` as an AI assertion. This covers update chains where the active +manifest names only `c2pa-tool` while a validated ingredient names Dreamina, and +Firefly chains that identify `Adobe_Firefly` without repeating a digital source +type. Unreachable manifests remain excluded. + +The SDK default enables trust verification but supplies no production trust +anchors. Consequently, an installation without an explicitly maintained C2PA +trust bundle reports otherwise valid signer chains as untrusted and keeps their +attribution at medium confidence. Shipping or fetching an official trust bundle +requires a separate update, provenance, and availability policy; do not silently +convert `signingCredential.untrusted` into trusted based on a vendor-name match. + Vendor attribution comes from the registry in [`_internal/constants.py`](../src/remove_ai_watermarks/_internal/constants.py). Derived issuer and platform maps should not be maintained separately. diff --git a/docs/python-api.md b/docs/python-api.md index 8839c7d..36efb46 100644 --- a/docs/python-api.md +++ b/docs/python-api.md @@ -155,8 +155,17 @@ from remove_ai_watermarks.identify import identify report = identify(Path("input.png")) print(report.platform) print(report.signals) +print(report.c2pa_validation) ``` +`c2pa_validation`, when present, reports `integrity`, `signature`, +`signer_trust`, and `signer_validity` independently, plus the reader status +codes. A valid hash and signature with an untrusted or expired signer is a +medium-confidence signed claim. A hash or signature failure does not confirm the +claimed platform or AI origin. Fallback parsing reports unknown validation +dimensions, while a raw marker in an unsupported or malformed container can +leave `c2pa_validation` as `None`. + Use `check_visible=False` and `check_invisible=False` for metadata-only inspection through the compatible path-based API: diff --git a/docs/supported-signals.md b/docs/supported-signals.md index dd0a85a..e77398a 100644 --- a/docs/supported-signals.md +++ b/docs/supported-signals.md @@ -86,7 +86,11 @@ The inspection and stripping code handles signals in these groups: `identify` combines detected signals into a `ProvenanceReport`. It reports unknown when evidence is absent. It never treats missing metadata as proof that -an image is human made. +an image is human made. C2PA presence alone is not a verified identity: the +report distinguishes asset binding, claim signature, signer trust, and signer +validity. High-confidence C2PA attribution requires all four; intact but +untrusted or fallback claims are medium-confidence, while a failed binding or +signature contributes no origin verdict. ## File and container formats diff --git a/docs/verification-plan.md b/docs/verification-plan.md index 99e19b5..f249757 100644 --- a/docs/verification-plan.md +++ b/docs/verification-plan.md @@ -39,6 +39,15 @@ confidence), reviewed once, then re-baselined. Lost detections are the alarm. Implemented as `scripts/sidecar_regression.py` (resumable, ~1.5 h at 8 workers). +A 2026-08-15 metadata-only C2PA regression audit over the local historical +corpus caught two structured-parser gaps before release: an exact Firefly claim +generator without a repeated source type, and a Dreamina generator carried by a +reachable ingredient under a generic update manifest. Both now have focused +tests. The same audit confirmed that invalid hash/signature claims lose origin +attribution without losing the C2PA inventory, and exposed the absence of +production trust anchors in the SDK defaults. Dataset-derived counts and +identifiers remain in the gitignored audit output. + #### Local run protocol Re-run `identify` against locally recorded sidecars, classify losses separately from intended new detections, and keep generated reports under `.local-eval/`. Do not commit dataset-derived counts or identifiers. diff --git a/src/remove_ai_watermarks/_internal/c2pa.py b/src/remove_ai_watermarks/_internal/c2pa.py index 3fe975c..0cb3918 100644 --- a/src/remove_ai_watermarks/_internal/c2pa.py +++ b/src/remove_ai_watermarks/_internal/c2pa.py @@ -8,6 +8,7 @@ import json import logging import re import struct +from collections import deque from dataclasses import dataclass from pathlib import Path from typing import TYPE_CHECKING, Any, cast @@ -37,6 +38,31 @@ with contextlib.suppress(Exception): _C2PA_READER_AVAILABLE = _C2paReader is not None _PNG_HEADER = struct.Struct(">I4s") +_CONTENT_BINDING_MATCHES = ( + "assertion.dataHash.match", + "assertion.bmffHash.match", + "assertion.boxesHash.match", + "assertion.collectionHash.match", + "assertion.multiAssetHash.match", +) +_CONTENT_BINDING_FAILURE_MARKERS = ( + "Hash.incorrectFileCount", + "Hash.malformed", + "Hash.mismatch", + "Hash.missingPart", + "assertion.hashedURI.mismatch", + "claim.hardBindings.missing", + "hashedUri.mismatch", + "ingredient.manifest.mismatch", +) +_SIGNATURE_FAILURES = frozenset( + { + "claimSignature.mismatch", + "claimSignature.missing", + "claimSignature.outsideValidity", + } +) + @dataclass(frozen=True) class _PngChunk: @@ -185,6 +211,159 @@ def _active_manifest(store: dict[str, Any]) -> dict[str, Any]: return cast("dict[str, Any]", active) if isinstance(active, dict) else {} +def _manifest_chain(store: dict[str, Any]) -> list[dict[str, Any]]: + """Return the active manifest followed by credential-usable reachable ingredients.""" + manifests = store.get("manifests") + active_label = store.get("active_manifest") + if not isinstance(manifests, dict) or not isinstance(active_label, str): + return [] + typed_manifests = cast("dict[object, object]", manifests) + pending = deque([active_label]) + seen: set[str] = set() + chain: list[dict[str, Any]] = [] + while pending: + label = pending.popleft() + if label in seen: + continue + seen.add(label) + manifest_value = typed_manifests.get(label) + if not isinstance(manifest_value, dict): + continue + manifest = cast("dict[str, Any]", manifest_value) + chain.append(manifest) + ingredients = manifest.get("ingredients") + if not isinstance(ingredients, list): + continue + for ingredient_value in cast("list[object]", ingredients): + if not isinstance(ingredient_value, dict): + continue + ingredient = cast("dict[object, object]", ingredient_value) + child = ingredient.get("active_manifest") + if ( + isinstance(child, str) + and child not in seen + and not _store_has_invalid_credential(cast("dict[str, Any]", ingredient)) + ): + pending.append(child) + return chain + + +def _status_codes(store: dict[str, Any]) -> tuple[list[str], list[str], list[str]]: + """Return ordered status codes for this store's active credential.""" + successes: list[str] = [] + informationals: list[str] = [] + failures: list[str] = [] + + def extend(status_set: object) -> None: + if not isinstance(status_set, dict): + return + mapping = cast("dict[object, object]", status_set) + for key, target in ( + ("success", successes), + ("informational", informationals), + ("failure", failures), + ): + values = mapping.get(key) + if not isinstance(values, list): + continue + for value in cast("list[object]", values): + if not isinstance(value, dict): + continue + code = cast("dict[object, object]", value).get("code") + if isinstance(code, str) and code: + target.append(code) + + validation_results = store.get("validation_results") + if isinstance(validation_results, dict): + results = cast("dict[object, object]", validation_results) + extend(results.get("activeManifest")) + + if not (successes or informationals or failures): + # Older readers expose only the flat non-success list. It still carries the + # exact codes needed to reject a mismatched binding or invalid credential. + values = store.get("validation_status") + if isinstance(values, list): + for value in cast("list[object]", values): + if not isinstance(value, dict): + continue + code = cast("dict[object, object]", value).get("code") + if isinstance(code, str) and code: + failures.append(code) + return successes, informationals, failures + + +def _store_has_invalid_credential(store: dict[str, Any]) -> bool: + """Return whether this store's own active credential failed validation.""" + _, _, failures = _status_codes(store) + return any(marker in code for code in failures for marker in _CONTENT_BINDING_FAILURE_MARKERS) or any( + code in _SIGNATURE_FAILURES for code in failures + ) + + +def c2pa_info_has_invalid_credential(info: dict[str, Any]) -> bool: + """Return whether parsed C2PA info reports a failed binding or signature.""" + return info.get("c2pa_integrity") == "invalid" or info.get("c2pa_signature") == "invalid" + + +def c2pa_info_has_removal_hint(info: dict[str, Any]) -> bool: + """Return whether a C2PA AI or watermark claim should keep removal fail-safe.""" + return bool(info.get("ai_source_kind") or info.get("synthid_watermark")) + + +def _has_suffix(codes: list[str], suffixes: tuple[str, ...]) -> bool: + return any(code.endswith(suffix) for code in codes for suffix in suffixes) + + +def _validation_fields(store: dict[str, Any]) -> dict[str, Any]: + successes, informationals, failures = _status_codes(store) + all_codes = list(dict.fromkeys([*successes, *informationals, *failures])) + + binding_failed = any(marker in code for code in failures for marker in _CONTENT_BINDING_FAILURE_MARKERS) + binding_matched = _has_suffix(successes, _CONTENT_BINDING_MATCHES) + signature_failed = any(code in _SIGNATURE_FAILURES for code in failures) + signature_validated = "claimSignature.validated" in successes + + if binding_failed: + integrity = "invalid" + elif binding_matched: + integrity = "valid" + else: + integrity = "unknown" + if signature_failed: + signature = "invalid" + elif signature_validated: + signature = "valid" + else: + signature = "unknown" + + if "signingCredential.trusted" in successes: + signer_trust = "trusted" + elif "signingCredential.untrusted" in failures: + signer_trust = "untrusted" + else: + signer_trust = "unknown" + + if any(code in failures for code in ("signingCredential.invalid", "signingCredential.ocsp.revoked")): + signer_validity = "invalid" + elif "signingCredential.expired" in failures: + signer_validity = "expired" + elif "claimSignature.insideValidity" in successes: + signer_validity = "valid" + else: + signer_validity = "unknown" + + state = store.get("validation_state") + return { + "c2pa_validation_source": "reader", + "c2pa_validation_state": state if isinstance(state, str) and state else "unknown", + "c2pa_integrity": integrity, + "c2pa_signature": signature, + "c2pa_signer_trust": signer_trust, + "c2pa_signer_validity": signer_validity, + "c2pa_validation_codes": all_codes, + } + + def _claim_generator_from_store(store: dict[str, Any]) -> str | None: active = _active_manifest(store) direct = active.get("claim_generator") @@ -199,6 +378,127 @@ def _claim_generator_from_store(store: dict[str, Any]) -> str | None: return None +def _structured_manifest_fields(store: dict[str, Any]) -> dict[str, Any]: + """Extract provenance only from the active manifest and its ingredient graph.""" + chain = _manifest_chain(store) + if not chain: + return {} + + info: dict[str, Any] = {} + issuers: list[str] = [] + tools: list[str] = [] + actions: list[str] = [] + source_types: list[str] = [] + soft_binding_algorithms: list[str] = [] + claim_generator_asserts_ai = False + + def add_tool_matches(value: str, *, asserts_ai: bool = False) -> None: + nonlocal claim_generator_asserts_ai + matches = _ordered_matches(value.encode(), C2PA_AI_TOOLS) + tools.extend(matches) + if asserts_ai and matches: + claim_generator_asserts_ai = True + + for manifest in chain: + signature_value = manifest.get("signature_info") + if isinstance(signature_value, dict): + signature = cast("dict[object, object]", signature_value) + for key in ("issuer", "common_name", "certificate_issuer"): + value = signature.get(key) + if isinstance(value, str): + issuers.extend(_ordered_matches(value.encode(), C2PA_ISSUERS)) + + direct_generator = manifest.get("claim_generator") + if isinstance(direct_generator, str): + add_tool_matches(direct_generator, asserts_ai=True) + + candidates = manifest.get("claim_generator_info") + if isinstance(candidates, list): + for candidate_value in cast("list[object]", candidates): + if not isinstance(candidate_value, dict): + continue + name = cast("dict[object, object]", candidate_value).get("name") + if isinstance(name, str): + add_tool_matches(name, asserts_ai=True) + + assertions = manifest.get("assertions") + if not isinstance(assertions, list): + continue + for assertion_value in cast("list[object]", assertions): + if not isinstance(assertion_value, dict): + continue + assertion = cast("dict[object, object]", assertion_value) + label = assertion.get("label") + data = assertion.get("data") + if isinstance(label, str) and label.startswith("c2pa.soft-binding") and isinstance(data, dict): + algorithm = cast("dict[object, object]", data).get("alg") + if isinstance(algorithm, str): + soft_binding_algorithms.append(algorithm) + if not (isinstance(label, str) and label.startswith("c2pa.actions") and isinstance(data, dict)): + continue + action_values = cast("dict[object, object]", data).get("actions") + if not isinstance(action_values, list): + continue + for action_value in cast("list[object]", action_values): + if not isinstance(action_value, dict): + continue + action = cast("dict[object, object]", action_value) + action_name = action.get("action") + if isinstance(action_name, str): + actions.append(C2PA_ACTIONS.get(action_name.encode(), action_name.removeprefix("c2pa."))) + source_type = action.get("digitalSourceType") + if isinstance(source_type, str): + source_types.append(source_type) + software_agent = action.get("softwareAgent") + if isinstance(software_agent, dict): + name = cast("dict[object, object]", software_agent).get("name") + if isinstance(name, str): + add_tool_matches(name) + + issuers = list(dict.fromkeys(issuers)) + tools = list(dict.fromkeys(tools)) + actions = list(dict.fromkeys(actions)) + if issuers: + info["issuer"] = ", ".join(issuers) + if tools: + info["ai_tool"] = ", ".join(tools) + if actions: + info["actions"] = ", ".join(actions) + if claim_generator_asserts_ai: + info["c2pa_identity_ai"] = True + + generated = any( + "trainedAlgorithmicMedia" in value and "compositeWithTrainedAlgorithmicMedia" not in value + for value in source_types + ) + enhanced = any("compositeWithTrainedAlgorithmicMedia" in value for value in source_types) + procedural = any("algorithmicMedia" in value for value in source_types) + if generated: + info.update(source_type="trainedAlgorithmicMedia (AI-generated)", ai_source_kind="generated") + elif enhanced: + info.update(source_type="compositeWithTrainedAlgorithmicMedia (AI-enhanced)", ai_source_kind="enhanced") + elif procedural: + info["source_type"] = "algorithmicMedia" + + 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 synthid: + info["synthid_vendors"] = synthid + info["synthid_watermark"] = synthid_verdict(", ".join(synthid)) + + if soft_binding_algorithms: + algorithms = "\n".join(soft_binding_algorithms).encode() + soft_bindings = soft_binding_vendors_in(algorithms) + if soft_bindings: + info["soft_binding_vendors"] = soft_bindings + info["soft_binding"] = ", ".join(soft_bindings) + return info + + def synthid_verdict(vendors: str) -> str: """Describe why supported provenance establishes a SynthID watermark.""" return f"present according to {vendors} provenance" @@ -276,16 +576,28 @@ def _populate_registry_fields(buffer: bytes, info: dict[str, Any]) -> bool: def _base_info(byte_count: int, *, fallback: bool = False) -> dict[str, Any]: container = "C2PA manifest" if fallback else "C2PA manifest store" - return { + info: dict[str, Any] = { "has_c2pa": True, "type": "C2PA (Coalition for Content Provenance and Authenticity)", "c2pa_manifest": f"{container} ({byte_count} bytes)", } + if fallback: + info.update( + c2pa_validation_source="fallback", + c2pa_validation_state="unknown", + c2pa_integrity="unknown", + c2pa_signature="unknown", + c2pa_signer_trust="unknown", + c2pa_signer_validity="unknown", + c2pa_validation_codes=[], + ) + return info def _info_from_store(store: dict[str, Any], encoded: bytes) -> dict[str, Any]: info = _base_info(len(encoded)) - _populate_registry_fields(encoded, info) + info.update(_structured_manifest_fields(store)) + info.update(_validation_fields(store)) generator = _claim_generator_from_store(store) if generator is not None: info["claim_generator"] = generator diff --git a/src/remove_ai_watermarks/_internal/constants.py b/src/remove_ai_watermarks/_internal/constants.py index 01c3482..e0f70b0 100644 --- a/src/remove_ai_watermarks/_internal/constants.py +++ b/src/remove_ai_watermarks/_internal/constants.py @@ -107,6 +107,9 @@ C2PA_IDENTITY_AI_ORGS = frozenset(vendor.org for vendor in C2PA_AI_VENDORS if ve # Keep this attribution beside the issuer registry so every C2PA consumer has one # canonical source rather than maintaining a derived product map in identify.py. C2PA_CLAIM_GENERATOR_PLATFORMS: tuple[tuple[str, str], ...] = ( + ("adobe_firefly", "Adobe Firefly"), + ("firefly", "Adobe Firefly"), + ("dreamina", "ByteDance (Doubao / Jimeng / Volcano Engine)"), ("higgsfield ai", "Higgsfield AI"), ("topaz labs image api", "Topaz Labs"), ("tiktok ad creative toolbox", "TikTok Ad Creative Toolbox"), @@ -122,6 +125,7 @@ C2PA_AI_TOOLS = { ("DALL", "DALL-E"), ("Imagen", "Imagen"), ("Firefly", "Firefly"), + ("Dreamina", "Dreamina"), ) } diff --git a/src/remove_ai_watermarks/api.py b/src/remove_ai_watermarks/api.py index 8d27eaf..9a8d178 100644 --- a/src/remove_ai_watermarks/api.py +++ b/src/remove_ai_watermarks/api.py @@ -329,12 +329,13 @@ class _SourceEvidence: if evidence is None: return True try: + from remove_ai_watermarks._internal.c2pa import c2pa_info_has_removal_hint from remove_ai_watermarks.identify import identify_from_evidence report = identify_from_evidence(evidence, image_path=self._path, check_invisible=True) except Exception: return True - return bool(report.ai_from_metadata) + return report.ai_from_metadata or c2pa_info_has_removal_hint(evidence.c2pa_info) def _provenance_from_report(report: Any, path: Path) -> frozenset[str]: diff --git a/src/remove_ai_watermarks/cli.py b/src/remove_ai_watermarks/cli.py index 5adecb6..21313de 100644 --- a/src/remove_ai_watermarks/cli.py +++ b/src/remove_ai_watermarks/cli.py @@ -1356,6 +1356,13 @@ def cmd_identify(ctx: click.Context, source: Path, no_visible: bool, as_json: bo verdict = "AI-generated (fully synthetic)" console.print(f"\n Verdict: {verdict} (confidence: {report.confidence})") console.print(f" Platform: {report.platform or 'undetermined'}") + if report.c2pa_validation is not None: + validation = report.c2pa_validation + console.print( + " C2PA validation: " + f"integrity={validation['integrity']}, signature={validation['signature']}, " + f"signer trust={validation['signer_trust']}, signer validity={validation['signer_validity']}" + ) if report.is_ai_generated is None: console.print( diff --git a/src/remove_ai_watermarks/identify.py b/src/remove_ai_watermarks/identify.py index d59ced1..ea54f0a 100644 --- a/src/remove_ai_watermarks/identify.py +++ b/src/remove_ai_watermarks/identify.py @@ -28,6 +28,8 @@ from typing import TYPE_CHECKING, Any, cast from remove_ai_watermarks._internal.c2pa import ( c2pa_info_from_manifest_store, + c2pa_info_has_invalid_credential, + c2pa_info_has_removal_hint, cbor_text_after, extract_c2pa_info, soft_binding_vendors_in, @@ -114,6 +116,18 @@ _SYNTHID_CAVEAT = ( "SynthID presence comes from supported provenance here; the pixel watermark is not locally " "decoded (proprietary decoder). Confirm via the Gemini app or openai.com/verify." ) +_C2PA_UNTRUSTED_CAVEAT = ( + "The C2PA claim signature and asset binding validate, but the signer identity is not anchored " + "to a trusted credential here; treat the named platform as a signed claim, not a verified identity." +) +_C2PA_UNVALIDATED_CAVEAT = ( + "The C2PA marker was parsed without cryptographic validation; treat its origin and watermark " + "assertions as unverified claims." +) +_C2PA_INVALID_CAVEAT = ( + "The embedded C2PA claim no longer validates against this asset. Its origin and watermark assertions " + "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." _INVISIBLE_WM_CAVEAT = ( "The open invisible watermark is fragile: it does not survive JPEG re-encoding " @@ -452,10 +466,10 @@ class ProvenanceReport: # (C2PA AI issuer / SynthID provenance, IPTC, AIGC, local gen params, EXIF/xAI, or # an open DWT-DCT decode) -- as opposed to a visible mark, provenance-only # TrustMark, or a weak medium-confidence hint (hf-job, Samsung genAIType). This - # is exactly the set of signals an invisible/diffusion scrub targets: a - # visible-only or no-signal image has it False. Equivalent to - # ``confidence == "high"``; - # surfaced as a field so callers gate on intent, not on the string. + # is the set of signals an invisible/diffusion scrub targets: a visible-only + # or no-signal image has it False. An intact C2PA AI claim from an untrusted + # signer is deliberately medium-confidence while this remains True; callers + # should gate on intent, not on the confidence string. ai_from_metadata: bool = False watermarks: list[str] = field(default_factory=list[str]) signals: list[Signal] = field(default_factory=list["Signal"]) @@ -465,6 +479,11 @@ class ProvenanceReport: # AI-generation markers). Non-empty means the provenance is internally # inconsistent -- a strong tell of spoofed, transplanted, or laundered metadata. integrity_clashes: list[str] = field(default_factory=list[str]) + # Orthogonal C2PA checks. A valid content binding does not make an untrusted + # signer identity trusted, and an expired credential does not by itself mean the + # signed bytes changed. Fallback parsing reports each dimension as unknown; + # None means no C2PA result exists. + c2pa_validation: dict[str, Any] | None = None def to_dict( self, @@ -500,9 +519,40 @@ class ProvenanceReport: ], "caveats": list(self.caveats), "integrity_clashes": list(self.integrity_clashes), + "c2pa_validation": self.c2pa_validation, } +def _c2pa_validation(info: dict[str, Any]) -> dict[str, Any] | None: + source = info.get("c2pa_validation_source") + if not isinstance(source, str): + return None + codes = info.get("c2pa_validation_codes") + return { + "source": source, + "state": str(info.get("c2pa_validation_state", "unknown")), + "integrity": str(info.get("c2pa_integrity", "unknown")), + "signature": str(info.get("c2pa_signature", "unknown")), + "signer_trust": str(info.get("c2pa_signer_trust", "unknown")), + "signer_validity": str(info.get("c2pa_signer_validity", "unknown")), + "codes": list(cast("list[object]", codes)) if isinstance(codes, list) else [], + } + + +def _c2pa_credential_level(info: dict[str, Any]) -> str: + """Return invalid, verified, or unverified for provenance attribution.""" + if c2pa_info_has_invalid_credential(info): + return "invalid" + if ( + info.get("c2pa_integrity") == "valid" + and info.get("c2pa_signature") == "valid" + and info.get("c2pa_signer_trust") == "trusted" + and info.get("c2pa_signer_validity") == "valid" + ): + return "verified" + return "unverified" + + def extract_provenance_evidence(image_path: Path) -> ProvenanceEvidence: """Read all file-backed metadata needed by provenance verdict logic once.""" return ProvenanceEvidence( @@ -1085,10 +1135,18 @@ def _identify_from_evidence( # ── C2PA Content Credentials ──────────────────────────────────── has_c2pa = bool(info) or c2pa_marker_in(head) + c2pa_validation = _c2pa_validation(info) + c2pa_level = _c2pa_credential_level(info) + c2pa_usable = c2pa_level != "invalid" + failed_c2pa_codes = [ + str(code) + for code in cast("list[object]", info.get("c2pa_validation_codes", [])) + if "mismatch" in str(code) or "invalid" in str(code) + ] issuers = [info["issuer"]] if info.get("issuer") else _issuers_in(region) # Full AI generation (trainedAlgorithmicMedia) vs an AI-enhanced real photo # (compositeWithTrainedAlgorithmicMedia). The structured kind is parsed once in - # _internal.c2pa._populate_registry_fields (covers PNG + any container the c2pa-python + # _internal.c2pa._structured_manifest_fields (covers PNG + any container the c2pa-python # reader handles); fall back to a raw head scan for the non-PNG raw-blob path # where extract_c2pa_info returns {}. Full generation wins when both appear. source_kind = _metadata_source_kind(info, head) @@ -1098,8 +1156,11 @@ def _identify_from_evidence( # Restricted to the ``asserts_ai`` vendors (distinctive brand strings), so it # does not reopen the incidental-mention problem the common-word issuers have. issuer_blob = " ".join(issuers) - c2pa_identity_ai = has_c2pa and any(org in issuer_blob for org in C2PA_IDENTITY_AI_ORGS) - c2pa_is_ai = source_kind is not None or c2pa_identity_ai + c2pa_identity_ai = has_c2pa and ( + bool(info.get("c2pa_identity_ai")) or any(org in issuer_blob for org in C2PA_IDENTITY_AI_ORGS) + ) + c2pa_claims_ai = source_kind is not None or c2pa_identity_ai + c2pa_is_ai = c2pa_usable and c2pa_claims_ai # Generator string (for the signal detail): structured for PNG, CBOR-scanned # for other containers. Best-effort -- some manifests key it as # `claim_generator_info` (Pixel), so this can be None even when a device is @@ -1119,20 +1180,41 @@ def _identify_from_evidence( camera_label or signer_label or (_claim_generator_platform(generator) if c2pa_is_ai else None) + or (_claim_generator_platform(str(info.get("ai_tool"))) if c2pa_is_ai and info.get("ai_tool") else None) or _attribute_platform(issuers, is_ai=c2pa_is_ai) ) - if has_c2pa + if has_c2pa and c2pa_usable else None ) if has_c2pa: detail = ", ".join(filter(None, [", ".join(issuers), generator, info.get("source_type")])) - signals.append(Signal("c2pa", detail or "C2PA manifest present", "high")) - watermarks.append(f"C2PA Content Credentials ({', '.join(issuers) or 'unknown signer'})") + if c2pa_level == "invalid": + suffix = f"; {', '.join(failed_c2pa_codes)}" if failed_c2pa_codes else "" + signals.append(Signal("c2pa", f"C2PA manifest present, credential integrity invalid{suffix}", "medium")) + watermarks.append("C2PA Content Credentials (invalid asset binding or signature)") + caveats.append(_C2PA_INVALID_CAVEAT) + else: + signals.append( + Signal("c2pa", detail or "C2PA manifest present", "high" if c2pa_level == "verified" else "medium") + ) + watermarks.append(f"C2PA Content Credentials ({', '.join(issuers) or 'unknown signer'})") + if c2pa_level == "unverified": + caveats.append( + _C2PA_UNTRUSTED_CAVEAT + if info.get("c2pa_integrity") == "valid" and info.get("c2pa_signature") == "valid" + else _C2PA_UNVALIDATED_CAVEAT + ) # Record the AI-origin vendor for clash detection only when the source is # actually AI -- classify the issuer attribution / generator, NOT the # resolved `platform` (which may be a camera device token whose label, # e.g. "Google Pixel", would mis-normalize to an AI vendor). - if c2pa_is_ai and (v := (_vendor_of(_attribute_platform(issuers, is_ai=True)) or _vendor_of(generator))): + if c2pa_is_ai and ( + v := ( + _vendor_of(_attribute_platform(issuers, is_ai=True)) + or _vendor_of(generator) + or _vendor_of(str(info.get("ai_tool", ""))) + ) + ): ai_vendor_claims["c2pa"] = v # ── C2PA cloud-manifest reference (Durable Content Credentials) ─ @@ -1170,9 +1252,13 @@ def _identify_from_evidence( if not synthid and trained_source and c2pa_marker_in(head) and (vendors := synthid_evidence_vendors_in(region)): synthid = synthid_verdict(", ".join(vendors)) if synthid: - watermarks.append(f"SynthID watermark ({synthid})") + watermarks.append( + f"SynthID watermark ({synthid})" + if c2pa_usable + else f"SynthID watermark claimed by invalid C2PA credentials ({synthid})" + ) caveats.append(_SYNTHID_CAVEAT) - if v := _vendor_of(synthid): + if c2pa_usable and (v := _vendor_of(synthid)): ai_vendor_claims["synthid"] = v # ── C2PA soft-binding: a named forensic/third-party watermark vendor ─ @@ -1180,12 +1266,17 @@ def _identify_from_evidence( # 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) if soft_binding: - signals.append(Signal("soft_binding", f"C2PA soft binding: {soft_binding}", "high")) + signals.append( + Signal( + "soft_binding", f"C2PA soft binding: {soft_binding}", "high" if c2pa_level == "verified" else "medium" + ) + ) watermarks.append(f"Forensic watermark soft binding ({soft_binding})") # ── IPTC "Made with AI" (Meta etc.), only meaningful without C2PA ─ iptc = any(m in head for m in IPTC_AI_MARKERS) - if iptc and not has_c2pa: + standalone_iptc = iptc and not has_c2pa + if standalone_iptc: signals.append(Signal("iptc", "digitalSourceType (Made with AI)", "high")) watermarks.append("IPTC digitalSourceType (Made with AI)") caveats.append(_IPTC_ONLY_CAVEAT) @@ -1311,8 +1402,18 @@ def _identify_from_evidence( exif_gen = any(s.name == "exif_generator" for s in signals) xai_sig = any(s.name == "xai_signature" for s in signals) ai_from_metadata = bool( - (has_c2pa and (c2pa_is_ai or synthid)) - or iptc + (has_c2pa and c2pa_usable and (c2pa_is_ai or synthid)) + or standalone_iptc + or iptc_ai + or aigc + or local_keys + or invisible_wm + or exif_gen + or xai_sig + ) + high_ai_from_metadata = bool( + (has_c2pa and c2pa_level == "verified" and (c2pa_is_ai or synthid)) + or standalone_iptc or iptc_ai or aigc or local_keys @@ -1330,7 +1431,7 @@ def _identify_from_evidence( if ai_from_metadata: is_ai: bool | None = True - confidence = "high" + confidence = "high" if high_ai_from_metadata else "medium" elif visible_only or hf_only or samsung_only: is_ai = True confidence = "medium" @@ -1339,7 +1440,17 @@ def _identify_from_evidence( confidence = "none" # ── Integrity clashes: contradictions between independent signals ─ - clashes = _integrity_clashes(ai_vendor_claims, camera_label, camera_has_ai_marker=bool(ai_vendor_claims)) + clashes = _integrity_clashes( + ai_vendor_claims, + camera_label if c2pa_usable else None, + camera_has_ai_marker=bool(ai_vendor_claims), + ) + if c2pa_level == "invalid": + clashes.insert( + 0, + "C2PA credentials failed integrity validation" + + (f": {', '.join(failed_c2pa_codes)}" if failed_c2pa_codes else "."), + ) caveats.append(_STRIP_CAVEAT) # De-duplicate while preserving order. @@ -1352,12 +1463,13 @@ def _identify_from_evidence( confidence=confidence, # Meaningful for the same digitalSourceType whether carried by C2PA or a # standalone IPTC/XMP label. Other AI signals leave it None. - ai_source_kind=source_kind if (is_ai and (has_c2pa or iptc)) else None, + ai_source_kind=(source_kind if (is_ai and ((has_c2pa and c2pa_usable) or standalone_iptc)) else None), ai_from_metadata=ai_from_metadata, watermarks=watermarks, signals=signals, caveats=caveats, integrity_clashes=clashes, + c2pa_validation=c2pa_validation, ) @@ -1434,13 +1546,14 @@ def has_invisible_target(image_path: Path) -> bool: The decision gate for the diffusion scrub (``invisible`` / ``all`` / ``batch``): regenerating pixels removes an AI-specific invisible watermark (SynthID, open DWT-DCT) but degrades a real photo, so it must not run when there is - nothing to remove. Runs :func:`identify` with ``check_visible=False`` -- a - visible mark is handled by the separate visible pass and is NOT a diffusion - target -- and ``check_invisible=True`` so an open watermark counts. Returns - ``report.ai_from_metadata`` (C2PA AI issuer / SynthID provenance, IPTC, AIGC, local - gen params, EXIF/xAI, or open DWT-DCT). TrustMark alone does not trigger the - scrub because it also protects human-authored work and therefore is not an AI - signal by itself. + nothing to remove. It runs the same evidence pipeline as :func:`identify` + with visible checks disabled and invisible checks enabled, so a visible mark + is handled by the separate visible pass and is NOT a diffusion target. Returns + True for ``report.ai_from_metadata`` (C2PA AI issuer / SynthID provenance, + IPTC, AIGC, local gen params, EXIF/xAI, or open DWT-DCT), and also when an + invalid C2PA claim retains an AI or watermark removal hint. TrustMark alone + does not trigger the scrub because it also protects human-authored work and + therefore is not an AI signal by itself. IMPORTANT -- this cannot prove a pixel SynthID is absent: SynthID is detectable only through its C2PA proxy, so a metadata-stripped AI image reads as no signal @@ -1451,8 +1564,17 @@ def has_invisible_target(image_path: Path) -> bool: watermark on a paid removal is worse than over-regenerating a clean image. """ try: - report = identify(image_path, check_visible=False, check_invisible=True) + evidence = extract_provenance_evidence(image_path) + report = _identify_from_evidence( + evidence, + image_path=image_path, + check_visible=False, + check_invisible=True, + ) except Exception: # unreadable / detector error -> do not skip the removal logger.debug("has_invisible_target: identify failed, defaulting to run", exc_info=True) return True - return report.ai_from_metadata + # An asset edit can invalidate the C2PA binding while leaving the declared pixel + # watermark intact. Keep the removal gate fail-safe without promoting that broken + # claim back into the provenance verdict. + return report.ai_from_metadata or c2pa_info_has_removal_hint(evidence.c2pa_info) diff --git a/src/remove_ai_watermarks/metadata.py b/src/remove_ai_watermarks/metadata.py index 50f146e..8de83a0 100644 --- a/src/remove_ai_watermarks/metadata.py +++ b/src/remove_ai_watermarks/metadata.py @@ -812,10 +812,19 @@ def synthid_source(image_path: Path) -> str | None: Returns: Comma-joined vendor name(s) (e.g. ``"OpenAI"``) or None. """ - from remove_ai_watermarks._internal.c2pa import extract_c2pa_info, synthid_evidence_vendors_in + from remove_ai_watermarks._internal.c2pa import ( + c2pa_info_has_invalid_credential, + extract_c2pa_info, + synthid_evidence_vendors_in, + ) - # PNG: the caBX chunk parser gives a clean, structured issuer. - vendors = extract_c2pa_info(image_path).get("synthid_vendors") + # Prefer the official reader's structured result. A failed asset binding or + # signature cannot establish the claim, and the raw fallback below must not + # resurrect it from the same damaged manifest bytes. + c2pa = extract_c2pa_info(image_path) + if c2pa_info_has_invalid_credential(c2pa): + return None + vendors = c2pa.get("synthid_vendors") if vendors: return ", ".join(vendors) @@ -1151,6 +1160,12 @@ def get_ai_metadata(image_path: Path) -> dict[str, str]: "actions", "synthid_watermark", "soft_binding", + "c2pa_validation_source", + "c2pa_validation_state", + "c2pa_integrity", + "c2pa_signature", + "c2pa_signer_trust", + "c2pa_signer_validity", ): if key in c2pa: result.setdefault(key, str(c2pa[key])) diff --git a/tests/conftest.py b/tests/conftest.py index be01cd4..e6541ff 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,7 +2,9 @@ from __future__ import annotations -from typing import TYPE_CHECKING +import struct +import zlib +from pathlib import Path import cv2 import numpy as np @@ -10,9 +12,6 @@ import pytest from PIL import Image from PIL.PngImagePlugin import PngInfo -if TYPE_CHECKING: - from pathlib import Path - @pytest.fixture def clean_photo(tmp_path: Path) -> Path: @@ -76,3 +75,22 @@ def tmp_clean_png(tmp_path: Path) -> Path: path = tmp_path / "clean.png" img.save(path, pnginfo=pnginfo) return path + + +@pytest.fixture +def tampered_chatgpt_png(tmp_path: Path) -> Path: + """Add valid PNG metadata after signing so the C2PA asset hash no longer matches.""" + source = Path(__file__).resolve().parents[1] / "data" / "fixtures" / "provenance" / "chatgpt-1.png" + data = source.read_bytes() + iend = data.rfind(b"\x00\x00\x00\x00IEND") + assert iend >= 0 + + kind = b"tEXt" + payload = b"c2pa-test\x00benign post-signing metadata mutation" + chunk = ( + struct.pack(">I", len(payload)) + kind + payload + struct.pack(">I", zlib.crc32(kind + payload) & 0xFFFFFFFF) + ) + target = tmp_path / "tampered-chatgpt.png" + target.write_bytes(data[:iend] + chunk + data[iend:]) + assert target.read_bytes() != data + return target diff --git a/tests/test_api.py b/tests/test_api.py index 2b0f35f..b5c6753 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -511,6 +511,15 @@ class TestSourceEvidenceHolder: assert holder.visible_provenance() == api.visible_provenance(DOUBAO) assert holder.has_invisible_target() == identify.has_invisible_target(DOUBAO) + def test_holder_preserves_invalid_c2pa_removal_hint(self, tampered_chatgpt_png): + from remove_ai_watermarks import api, identify + + holder = api._SourceEvidence(tampered_chatgpt_png) + + assert identify.identify(tampered_chatgpt_png, check_visible=False).ai_from_metadata is False + assert holder.has_invisible_target() is True + assert holder.has_invisible_target() == identify.has_invisible_target(tampered_chatgpt_png) + def test_extraction_failure_fails_safe_in_both_directions(self, monkeypatch, tmp_path): """No provenance means no relaxation; an unknown invisible target means SCRUB. Leaving a watermark on a paid removal is worse than over-regenerating.""" diff --git a/tests/test_cli.py b/tests/test_cli.py index cf482b2..bb6a259 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -723,6 +723,8 @@ class TestIdentifyCommand: result = runner.invoke(main, ["identify", str(sample), "--no-visible"]) assert result.exit_code == 0 assert "AI-generated (fully synthetic)" in result.output + assert "C2PA validation: integrity=valid, signature=valid" in result.output + assert "signer trust=untrusted, signer validity=expired" in result.output def test_identify_json_is_valid(self, runner, tmp_png_with_ai_metadata): result = runner.invoke(main, ["identify", str(tmp_png_with_ai_metadata), "--no-visible", "--json"]) diff --git a/tests/test_identify.py b/tests/test_identify.py index 02b894f..25f89cf 100644 --- a/tests/test_identify.py +++ b/tests/test_identify.py @@ -39,6 +39,79 @@ SAMPLES_DIR = Path(__file__).resolve().parent.parent / "data" / "fixtures" / "pr class TestProvenanceEvidence: + def test_exact_ai_claim_generator_can_assert_ai_without_source_type(self, tmp_path: Path): + path = tmp_path / "firefly.png" + info = { + "has_c2pa": True, + "issuer": "Adobe", + "claim_generator": "Adobe_Firefly", + "ai_tool": "Firefly", + "c2pa_identity_ai": True, + "c2pa_validation_source": "reader", + "c2pa_validation_state": "Valid", + "c2pa_integrity": "valid", + "c2pa_signature": "valid", + "c2pa_signer_trust": "untrusted", + "c2pa_signer_validity": "valid", + "c2pa_validation_codes": ["assertion.dataHash.match", "claimSignature.validated"], + } + evidence = ProvenanceEvidence( + path=path, + c2pa_info=info, + ai_metadata={}, + scan=b"jumb c2pa Adobe_Firefly", + iptc_ai_system=None, + aigc_label=None, + exif_generator=None, + xai_signature=False, + huggingface_job=None, + samsung_genai=None, + ) + + report = identify_from_evidence(evidence) + + assert report.is_ai_generated is True + assert report.confidence == "medium" + assert report.platform == "Adobe Firefly" + + def test_fully_validated_c2pa_claim_is_high_confidence(self, tmp_path: Path): + path = tmp_path / "validated.png" + info = { + "has_c2pa": True, + "issuer": "OpenAI", + "source_type": "trainedAlgorithmicMedia (AI-generated)", + "ai_source_kind": "generated", + "c2pa_validation_source": "reader", + "c2pa_validation_state": "Valid", + "c2pa_integrity": "valid", + "c2pa_signature": "valid", + "c2pa_signer_trust": "trusted", + "c2pa_signer_validity": "valid", + "c2pa_validation_codes": [ + "assertion.dataHash.match", + "claimSignature.validated", + "signingCredential.trusted", + ], + } + evidence = ProvenanceEvidence( + path=path, + c2pa_info=info, + ai_metadata={}, + scan=b"jumb c2pa OpenAI trainedAlgorithmicMedia", + iptc_ai_system=None, + aigc_label=None, + exif_generator=None, + xai_signature=False, + huggingface_job=None, + samsung_genai=None, + ) + + report = identify_from_evidence(evidence) + + assert report.is_ai_generated is True + assert report.confidence == "high" + assert report.platform == "OpenAI (ChatGPT / gpt-image / DALL-E / Sora)" + def test_external_metadata_record_builds_equivalent_evidence(self, tmp_path: Path): path = tmp_path / "external.jpg" signature = "A" * 64 @@ -428,12 +501,14 @@ class TestIdentifySamsungGalaxy: path.write_bytes(b"\xff\xd8\xff\xe1jumbc2pa" + blob + b"\xff\xd9") return path - def test_galaxy_trained_source_is_high_ai(self, tmp_path: Path): + def test_galaxy_trained_source_is_unverified_ai(self, tmp_path: Path): path = self._jpeg(tmp_path, "s25.jpg", b"Samsung Galaxy Galaxy S25 c2pa-rs trainedAlgorithmicMedia") r = identify(path, check_visible=False, check_invisible=False) assert r.is_ai_generated is True - assert r.confidence == "high" + assert r.confidence == "medium" assert r.platform == "Samsung Galaxy (C2PA)" + assert r.c2pa_validation is None + assert any("without cryptographic validation" in caveat for caveat in r.caveats) assert r.integrity_clashes == [] # device cert + AI source-type is legitimate, not a clash def test_galaxy_genai_only_is_medium_ai(self, tmp_path: Path): @@ -477,7 +552,7 @@ class TestIdentifyRealSamples: def test_openai_chatgpt(self): r = identify(SAMPLES_DIR / "chatgpt-1.png", check_visible=False) assert r.is_ai_generated is True - assert r.confidence == "high" + assert r.confidence == "medium" assert r.platform assert "OpenAI" in r.platform assert any("C2PA" in w for w in r.watermarks) @@ -546,9 +621,39 @@ class TestIdentifyRealSamples: # both invisible/metadata targets, so the diffusion scrub should run. assert has_invisible_target(SAMPLES_DIR / "chatgpt-1.png") is True assert has_invisible_target(SAMPLES_DIR / "mj-1.png") is True - # ai_from_metadata mirrors confidence == "high" and backs the helper. + # ai_from_metadata records scrub intent even when an untrusted signer + # makes the provenance verdict medium-confidence. assert identify(SAMPLES_DIR / "chatgpt-1.png", check_visible=False).ai_from_metadata is True + def test_untrusted_but_intact_c2pa_is_medium_confidence(self): + report = identify(SAMPLES_DIR / "chatgpt-1.png", check_visible=False, check_invisible=False) + + assert report.is_ai_generated is True + assert report.confidence == "medium" + assert report.ai_from_metadata is True + assert report.c2pa_validation is not None + assert report.c2pa_validation["source"] == "reader" + assert report.c2pa_validation["state"] == "Invalid" + assert report.c2pa_validation["integrity"] == "valid" + assert report.c2pa_validation["signature"] == "valid" + assert report.c2pa_validation["signer_trust"] == "untrusted" + assert report.c2pa_validation["signer_validity"] == "expired" + assert "assertion.dataHash.match" in report.c2pa_validation["codes"] + assert any("not anchored" in caveat for caveat in report.caveats) + + def test_hash_mismatch_does_not_confirm_origin_but_keeps_scrub_fail_safe(self, tampered_chatgpt_png: Path): + report = identify(tampered_chatgpt_png, check_visible=False, check_invisible=False) + + assert report.is_ai_generated is None + assert report.platform is None + assert report.confidence == "none" + assert report.ai_source_kind is None + assert report.ai_from_metadata is False + assert report.c2pa_validation is not None + assert report.c2pa_validation["integrity"] == "invalid" + assert any("dataHash.mismatch" in clash for clash in report.integrity_clashes) + assert has_invisible_target(tampered_chatgpt_png) is True + def test_has_invisible_target_false_on_clean_photo(self, clean_photo: Path): # No detectable invisible signal -> skip the scrub (do not degrade a clean image). assert has_invisible_target(clean_photo) is False @@ -566,13 +671,13 @@ class TestHasInvisibleTargetFailSafe: """The scrub gate fails SAFE: when a detector errors, it runs the removal.""" def test_detector_error_defaults_to_run(self, tmp_path: Path): - # If identify raises (a detector crash), the gate must return True so the + # If evidence evaluation raises (a detector crash), the gate must return True so the # caller still attempts removal -- leaving a watermark on a paid removal is # worse than over-regenerating. (Garbage bytes do NOT raise; identify returns # a clean None verdict there, so that path correctly skips -- see below.) bad = tmp_path / "x.png" bad.write_bytes(b"not image bytes") - with patch("remove_ai_watermarks.identify.identify", side_effect=RuntimeError("boom")): + with patch("remove_ai_watermarks.identify._identify_from_evidence", side_effect=RuntimeError("boom")): assert has_invisible_target(bad) is True def test_unreadable_bytes_are_not_a_target(self, tmp_path: Path): diff --git a/tests/test_metadata.py b/tests/test_metadata.py index bd369ff..20349e9 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -500,6 +500,10 @@ class TestGetAiMetadataRealSample: assert "OpenAI" in meta["issuer"] assert "synthid_watermark" not in meta assert "trainedAlgorithmicMedia" in meta["source_type"] + assert meta["c2pa_integrity"] == "valid" + assert meta["c2pa_signature"] == "valid" + assert meta["c2pa_signer_trust"] == "untrusted" + assert meta["c2pa_signer_validity"] == "expired" @pytest.mark.parametrize( diff --git a/tests/test_metadata_internals.py b/tests/test_metadata_internals.py index db51115..5a4e6ef 100644 --- a/tests/test_metadata_internals.py +++ b/tests/test_metadata_internals.py @@ -12,6 +12,7 @@ from PIL import Image from remove_ai_watermarks._internal.c2pa import ( _parse_c2pa_chunk, + c2pa_info_from_manifest_store, cbor_text_after, extract_c2pa_chunk, extract_c2pa_info, @@ -153,6 +154,110 @@ class TestC2PA: def test_c2pa_returns_false_for_non_png(self, tmp_jpeg_path): assert not has_c2pa_metadata(tmp_jpeg_path) + def test_structured_extraction_ignores_unreachable_manifests(self): + store = { + "active_manifest": "active", + "manifests": { + "active": { + "signature_info": {"issuer": "Adobe"}, + "assertions": [], + }, + "unreachable": { + "signature_info": {"issuer": "OpenAI"}, + "assertions": [ + { + "label": "c2pa.actions.v2", + "data": { + "actions": [ + { + "action": "c2pa.created", + "digitalSourceType": "trainedAlgorithmicMedia", + } + ] + }, + } + ], + }, + }, + } + + info = c2pa_info_from_manifest_store(store) + + assert info["issuer"] == "Adobe" + assert "source_type" not in info + assert "ai_source_kind" not in info + assert "c2pa_identity_ai" not in info + + def test_reachable_ingredient_claim_generator_can_assert_ai(self): + store = { + "active_manifest": "update", + "manifests": { + "update": { + "claim_generator": "c2pa-tool/0.1.0", + "ingredients": [{"active_manifest": "created"}], + "assertions": [], + }, + "created": { + "claim_generator": "Dreamina/7.5.0", + "assertions": [], + }, + }, + } + + info = c2pa_info_from_manifest_store(store) + + assert info["ai_tool"] == "Dreamina" + assert info["c2pa_identity_ai"] is True + + def test_invalid_ingredient_does_not_taint_active_validation_or_supply_claims(self): + store = { + "active_manifest": "update", + "validation_results": { + "activeManifest": { + "success": [ + {"code": "assertion.dataHash.match"}, + {"code": "claimSignature.validated"}, + ], + "failure": [{"code": "signingCredential.untrusted"}], + }, + "ingredientDeltas": [ + { + "validationDeltas": { + "failure": [{"code": "assertion.dataHash.mismatch"}], + } + } + ], + }, + "manifests": { + "update": { + "claim_generator": "c2pa-tool/0.1.0", + "ingredients": [ + { + "active_manifest": "created", + "validation_results": { + "activeManifest": { + "failure": [{"code": "assertion.dataHash.mismatch"}], + } + }, + } + ], + "assertions": [], + }, + "created": { + "claim_generator": "Dreamina/7.5.0", + "assertions": [], + }, + }, + } + + info = c2pa_info_from_manifest_store(store) + + assert info["c2pa_integrity"] == "valid" + assert info["c2pa_signature"] == "valid" + assert info["c2pa_signer_trust"] == "untrusted" + assert "ai_tool" not in info + assert "c2pa_identity_ai" not in info + SAMPLES_DIR = Path(__file__).resolve().parent.parent / "data" / "fixtures" / "provenance" CURRENT_OPENAI_SAMPLE = ( @@ -227,6 +332,22 @@ class TestC2PARealSamples: # Structured claim generator is exact, not a CBOR-scanned best-effort. assert info["claim_generator"] == "ChatGPT" + def test_reader_reports_intact_but_untrusted_credentials(self): + info = extract_c2pa_info(SAMPLES_DIR / "chatgpt-1.png") + + assert info["c2pa_integrity"] == "valid" + assert info["c2pa_signature"] == "valid" + assert info["c2pa_signer_trust"] == "untrusted" + assert info["c2pa_signer_validity"] == "expired" + assert "assertion.dataHash.match" in info["c2pa_validation_codes"] + + def test_reader_reports_post_signing_container_mutation(self, tampered_chatgpt_png): + info = extract_c2pa_info(tampered_chatgpt_png) + + assert info["c2pa_integrity"] == "invalid" + assert info["c2pa_signature"] == "valid" + assert "assertion.dataHash.mismatch" in info["c2pa_validation_codes"] + def test_fallback_to_png_parser_when_reader_unavailable(self, monkeypatch): """With the reader disabled, the hand-rolled PNG parser still works.""" from remove_ai_watermarks._internal import c2pa @@ -237,6 +358,8 @@ class TestC2PARealSamples: assert "OpenAI" in info["issuer"] assert "trainedAlgorithmicMedia" in info["source_type"] assert "synthid_watermark" not in info + assert info["c2pa_integrity"] == "unknown" + assert info["c2pa_validation_source"] == "fallback" class TestC2PAInjectValidation: diff --git a/tests/test_metadata_record.py b/tests/test_metadata_record.py index 88819b8..130be6f 100644 --- a/tests/test_metadata_record.py +++ b/tests/test_metadata_record.py @@ -293,6 +293,14 @@ class TestReportTransport: assert convenience == explicit + def test_c2pa_validation_survives_the_portable_record(self, tampered_chatgpt_png: Path): + direct = identify(tampered_chatgpt_png, check_visible=False, check_invisible=False) + portable = identify_metadata_record(collect_metadata_record(tampered_chatgpt_png), path=tampered_chatgpt_png) + + assert portable == direct + assert portable.c2pa_validation is not None + assert portable.c2pa_validation["integrity"] == "invalid" + def test_a_webp_record_matches(tmp_path: Path): """RIFF has its own walk; a chunk kept or dropped wrongly shows up here.""" From 8c00525946cf4299870954d6b48ea448716e3e04 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Sat, 15 Aug 2026 11:59:00 -0700 Subject: [PATCH 15/44] Evaluate selective text restoration --- data/evaluations/fidelity/README.md | 359 +++++++++- .../anytext2-restoration-2026-08-13.csv | 4 + .../face-stage-isolation-2026-08-13.csv | 5 + data/evaluations/fidelity/ground-truth.json | 1 + .../selective-text-restoration-2026-08-13.csv | 14 + data/evaluations/fidelity/text-lines.json | 51 ++ .../fidelity/text-preservation-2026-08-13.csv | 25 + .../fidelity/text-restoration-2026-08-13.csv | 7 + .../upstream-v2-reproduction-2026-08-13.csv | 6 + docs/known-limitations.md | 29 +- docs/module-internals.md | 28 + docs/text-protection-research.md | 200 ++++++ scripts/_text_eval.py | 34 + scripts/fidelity_metrics.py | 15 +- scripts/infer_text_lines.py | 128 ++++ scripts/selective_text_restoration.py | 671 ++++++++++++++++++ tests/test_fidelity_matching.py | 12 +- tests/test_infer_text_lines.py | 27 + tests/test_selective_text_restoration.py | 177 +++++ 19 files changed, 1779 insertions(+), 14 deletions(-) create mode 100644 data/evaluations/fidelity/anytext2-restoration-2026-08-13.csv create mode 100644 data/evaluations/fidelity/face-stage-isolation-2026-08-13.csv create mode 100644 data/evaluations/fidelity/selective-text-restoration-2026-08-13.csv create mode 100644 data/evaluations/fidelity/text-lines.json create mode 100644 data/evaluations/fidelity/text-preservation-2026-08-13.csv create mode 100644 data/evaluations/fidelity/text-restoration-2026-08-13.csv create mode 100644 data/evaluations/fidelity/upstream-v2-reproduction-2026-08-13.csv create mode 100644 scripts/_text_eval.py create mode 100644 scripts/infer_text_lines.py create mode 100644 scripts/selective_text_restoration.py create mode 100644 tests/test_infer_text_lines.py create mode 100644 tests/test_selective_text_restoration.py diff --git a/data/evaluations/fidelity/README.md b/data/evaluations/fidelity/README.md index 9896e21..73e1af7 100644 --- a/data/evaluations/fidelity/README.md +++ b/data/evaluations/fidelity/README.md @@ -7,31 +7,384 @@ ground truth and instructions. | Original | Provider | Content | Exercises | | --- | --- | --- | --- | +| `ChatGPT Image May 31, 2026, 02_02_23 PM.png` | OpenAI | Light multilingual typography | Text preservation | | `ChatGPT Image May 31, 2026, 02_03_55 PM.png` | OpenAI | Multilingual typography | Text preservation | | `Gemini_Generated_Image_633uuy633uuy633u.png` | Google | Landscape with a Chinese sign | CJK text preservation | | `Gemini_Generated_Image_y48j3cy48j3cy48j.png` | Google | Portrait grid | Face identity and skin texture | ## Text ground truth -`ground-truth.json` contains hand-verified OCR for the two text-bearing +`ground-truth.json` contains hand-verified OCR for the three text-bearing originals. To regenerate an OCR seed: ```bash uv run scripts/fidelity_metrics.py ocr \ "data/synthid/originals/ChatGPT Image May 31, 2026, 02_03_55 PM.png" \ + "data/synthid/originals/ChatGPT Image May 31, 2026, 02_02_23 PM.png" \ data/synthid/originals/Gemini_Generated_Image_633uuy633uuy633u.png \ --langs en,ru,ch \ --out data/evaluations/fidelity/ground-truth.json ``` Verify and correct the generated text by hand before using it as ground truth. +`text-lines.json` contains the verified per-line strings and source-space boxes +used by the evaluation-only selective-restoration experiment. It is not an +automatic scene-text annotation set. + +`scripts/infer_text_lines.py` can generate a draft from source pixels without +existing annotations. On the two posters it proposed 20 and 18 lines at the +default threshold, but exact-text precision was only 90.0% and 94.4% because +high-confidence OCR still dropped an English comma and replaced a Chinese comma +with ASCII. Its output therefore requires manual verification of every line; +`accepted` means crop-stable, not ground-truth-correct. + +## Text-preservation benchmark + +`text-preservation-2026-08-13.csv` records a fixed-seed comparison of the two +current profiles and a global Z-Image Turbo prototype on all three text +fixtures. Each candidate ran through the +complete `visible -> invisible -> metadata` route with its profile default +strength and adaptive-polish setting. The Z-Image prototype instead sweeps +0.08, 0.10, 0.15, 0.20, and 0.25 without polish because its provider-specific +removal floor is not known. The output hash identifies the exact bytes +measured; generated outputs remain outside the repository. + +The character-weighted added CER is 0.262 for `qwen-zimage` and 0.256 for +`sdxl-zimage`. That 0.006 absolute difference is not a +stable ordering: SDXL wins the light poster, Qwen wins the dark poster, and the +Chinese sign is tied. A paired sign test on the two non-ties is 1-1 (`p=1.0`). +The measured sample therefore does not support a general text-preservation +winner. Both profiles substantially degrade the smallest multilingual poster +text and preserve the larger Chinese sign. + +Z-Image Turbo is the clear fidelity lead. At strength 0.10 its +character-weighted CER is 0.093, against the unchanged sources' 0.124 OCR +floor, while whole-image LPIPS stays between 0.047 and 0.086. Visual inspection +still finds substitutions in the smallest Cyrillic and Chinese poster lines, +so a favorable OCR score does not mean pixel-exact text. Quality drops quickly +above 0.15; at 0.25 the weighted CER is 0.428. + +Provider-oracle checks on 2026-08-13 bracket the OpenAI requirement at no more +than 0.10 for this sample. Both original OpenAI controls were detected by +`openai.com/verify`; the light poster was clean at 0.08, while the dark poster +was detected at 0.08 and clean at 0.10. The light 0.10 output was not separately +checked. Google fails the entire tested sweep. The original Gemini image was +detected through C2PA, and a pixel-identical copy with AI metadata stripped was +separately detected by Gemini's built-in SynthID verifier. Z-Image outputs at +0.08, 0.10, 0.15, 0.20, and 0.25 were all still detected. At 0.25 the weighted +CER has already risen to 0.428, worse than the raw weighted CER of both current +profiles (Qwen 0.387, SDXL 0.381). Increasing strength beyond the measured grid +would therefore no longer serve the text-preservation objective without a new +mechanism or hypothesis. Z-Image is not a viable global replacement on this +evidence: no clean Google operating point was found before it lost its fidelity +advantage. + +Qwen-Image-2.0 was not added to the numeric comparison. Its weights are not +published, and its hosted edit API exposes an editing instruction and seed but +no low-strength denoise control. It can be evaluated as a separate hosted edit +strategy when credentials are available, but it is not a drop-in replacement +for the partial-regeneration mechanism measured here. + +The OCR floor is the source image scored against the hand-verified text. Use +`added_cer = text_cer - ocr_floor` when interpreting pipeline damage, because the +unchanged poster sources already score 0.127 CER. `oracle_rechecked=true` marks +the exact Z-Image bytes checked above; the remaining rows were not rechecked. +The table does not certify other seeds, content classes, or strengths beyond +the recorded provider verdicts. + +## Text-restoration prototype + +`text-restoration-2026-08-13.csv` evaluates an OCR-driven post-pass on the exact +Qwen outputs above. The prototype recognizes English and Russian with macOS +Vision and CJK with PaddleOCR, derives glyph masks independently from the +source and Qwen output, removes both sets of glyphs with block-wise LaMa, and +draws the recognized strings with new system-font pixels. It never composites +source pixels back into the result. + +On the two multilingual posters, character-weighted CER fell from 0.338 and +0.305 to 0.007 on both. OpenAI Verify reported no OpenAI signals for both Qwen +controls and both restored outputs in the same run. The improvement comes with +a substantial whole-image fidelity cost: LPIPS rose from 0.107 to 0.174 and +from 0.095 to 0.162, while PSNR fell by about 10.5-11.4 dB. Visual inspection +found one residual shadow in the smallest English line of the light poster; +the dark poster was clean but the substitute fonts visibly changed typography. + +The Chinese sign did not improve: CER rose from 0.074 to 0.111 because the OCR +and renderer changed punctuation. The Gemini verifier returned detected for +both the restored output and its byte-identical Qwen control on a third work +account, although that Qwen hash had previously returned clean on another work +account. This run therefore does not isolate a restoration-stage regression; +its Google verdict is inconclusive until a source-positive, Qwen-negative +control can be reproduced in the same available account. + +This prototype is not ready to ship. Its strongest result establishes that +fresh-glyph reconstruction can recover literal text without reintroducing an +OpenAI signal, but portable OCR, font/style reconstruction, a tighter mask, and +a reproducible Google oracle control remain prerequisites. + +### Selective restoration follow-up + +`selective-text-restoration-2026-08-13.csv` compares that full compositor with +a selective prototype on the same two posters. The prototype leaves a Qwen line +unchanged when padded source and output recognition agree, and applies the same +LaMa plus fresh-system-font reconstruction only to lines whose recognized text +changed. This reduced the edited area from 15.4% and 17.0% to 5.5% on both +posters. + +Under one consistent Paddle `en+ru+ch` measurement route, selective restoration +reduced Qwen CER from 0.378 to 0.101 on the light poster and from 0.413 to 0.112 +on the dark poster. Its image LPIPS was 0.120 and 0.103, substantially closer to +Qwen than the full compositor's 0.174 and 0.162. These CER values must not be +mixed with the preceding table's Vision/Paddle hybrid values: the comparison +file remeasures all three variants through Paddle so their relative result is +valid on one OCR route. + +OpenAI Verify returned `No OpenAI signals detected` for both selective outputs, +then detected the original light poster as `Generated with OpenAI tools` in the +same Chrome sequence. Visual inspection found the dark output clean, but the +light output still retained a local shadow around one replaced fine-text line. +Selective restoration is therefore the strongest current direction, not a +production-ready default. The next implementation needs automatic line matching +and a tighter source-plus-candidate glyph mask before it can be proposed for the +pipeline. + +A mask-only follow-up added two pixels of dilation around every selected glyph. +It visually removed the light poster's shadow and improved its CER from 0.101 to +0.081 with effectively unchanged LPIPS, but increased the edited area from 5.5% +to 7.0%. Applying the same rule to the dark poster worsened CER from 0.112 to +0.147. These expanded-mask rows were not resubmitted to the provider oracle. +The opposing result rules out one global dilation constant and makes per-line +residual detection the next fidelity gate. + +An automatic follow-up removed the hand-selected line list. For each known +source line, it runs a recognition-only model on padded, upscaled source and +Qwen crops. It preserves a line only when both confidence scores are at least +0.75, the source recognition is within 0.25 normalized edit distance of the +verified line, and normalized source and candidate recognition match exactly. +This reproduced the manual dark-poster line selection and additionally kept one +correct large Russian line on the light poster. + +The automatic prototype also replaced uniform dilation with a second measured +LaMa pass. After the first erase, it extracts contrast remaining at the original +glyph positions, dilates only that residual, and inpaints it before drawing fresh +text. Visual inspection found no remaining double glyphs on either output. The +effective edited fractions were 4.1% and 5.7%. Image LPIPS was 0.113 and 0.104, +against 0.120 and 0.103 for the manual selective version. Detector-based page CER +was 0.123 and 0.119; the light value is inflated by the detector splitting the +preserved Cyrillic line into Latin-like fragments even though the padded Russian +recognizer reads it exactly. This is another reason that line decisions use +recognition-only crops rather than the page detector's aggregate CER. + +Both exact automatic output hashes returned `No OpenAI signals detected`, and +the original light poster returned `Generated with OpenAI tools` immediately +afterward in the same Chrome sequence. The tracked script reproduced the dark +hash exactly. Its light output differed from the initial prototype in only 386 +pixels by at most one channel value because separate ONNX environments rounded +differently; the script's exact `900def5a...` hash was therefore submitted and +also returned clean before the positive source control. + +The script and its required verified line annotations are tracked as +`scripts/selective_text_restoration.py` and `text-lines.json`. For example: + +```bash +PADDLE_PDX_DISABLE_MODEL_SOURCE_CHECK=True \ + uv run scripts/selective_text_restoration.py \ + data/synthid/originals/Gemini_Generated_Image_633uuy633uuy633u.png \ + path/to/qwen-candidate.png \ + --restoration vae-glyphs \ + --glyph-donor path/to/qwen-vae-reconstruction.png \ + --glyph-feather 0.5 \ + --output path/to/selective-output.png \ + --manifest path/to/manifest.json +``` + +The restoration mode is deliberately explicit. `vae-glyphs` requires a +separately generated VAE reconstruction in `--glyph-donor`; it takes exact +thresholded glyph cores from that donor, uses a fresh silhouette beneath them, +and defaults to a narrow 0.5-pixel donor edge. The donor and the resulting exact bytes still require +full-pipeline oracle evaluation. `rerender` reproduces the tracked +fresh-system-font experiment. `source-glyphs` is only for compositing a +separately regenerated, oracle-evaluated layer; feeding it the watermarked +original would paste provenance-bearing pixels back into the result. The +`--keep-background --composite-mask boxes` combination is an aligned-layer +experiment and is not a production text restorer. + +The `source-silhouette` follow-up keeps the source glyph geometry but discards +its pixel amplitudes: it thresholds each source line to a binary shape, samples +one foreground color, and synthesizes new antialiasing over the scrubbed image. +On a 24-image matrix spanning serif, Latin-diacritic, Cyrillic, CJK, tiny-UI, +and rotated text, the original outer-feather compositor preserved all 34 +source-readable lines exactly under crop OCR, with median text-box SSIM 0.859. +An inner-antialias variant raised median text-box SSIM to 0.902 and left +whole-image SSIM unchanged at 0.672, but preserved 33/34 lines: OCR read one +middle dot as a colon. The higher-fidelity antialiasing is retained in the +evaluation script, with that punctuation miss recorded as a caveat. Its exact +outputs were not submitted to the provider oracle because the +public verifier reached its request limit; a clean verdict from the earlier +outer-feather bytes does not transfer to the new hashes. + +Follow-up visual review on a typography-rich control rejected both silhouette +compositors despite their OCR and SSIM scores. They preserved literal content +and approximate glyph geometry, but changed stroke weight, color variation, +edge antialiasing, and small decorative details enough to be plainly different +from the source. OCR exactness and text-box SSIM are therefore screening metrics, +not acceptance gates for source-typography preservation. A candidate must also +retain source-like edge pixels and pass direct visual comparison at native size. + +A narrower Qwen-VAE donor follow-up keeps the scrubbed background, then copies +only VAE-reconstructed pixels through the source-silhouette mask with a +0.5-pixel feather. Across the 48-case typography matrix (548 annotated lines), +text-box SSIM improved in 47/48 cases and glyph-edge error improved in 48/48. +Median text-box SSIM rose from 0.854 to 0.914, while median glyph-edge MAE fell +from 37.59 to 32.42. The median nonzero alpha area was 3.62%; on dense or large +text this is still substantially wider than the target control. These are +fidelity results only; the 48 matrix outputs were not submitted to the provider +oracle. On a separate untracked dense-typography control, the exact core-only +Qwen-VAE donor with a 1.43% source-silhouette area returned `No OpenAI signals +detected`. Its 0.5-pixel feathered sibling, which raises the nonzero donor area +to 2.75%, returned the same verdict and improved mean text-box SSIM from 0.957 +to 0.960. Crop OCR recovered from 7/15 exact lines on the raw pass to 14/15, +matching the source OCR floor. Whole-image LPIPS was 0.082, but only 0.108% of +pixels were exactly unchanged and the detected face retained 0.670 of source +Laplacian variance. The tracked script reproduced the feathered file byte for +byte. These two exact-byte verdicts do not certify other images or the larger +matrix masks, and the global smoothing fails a strict unchanged-image criterion. + +The Google result is negative. On the synthetic CJK sign case, two separate +work-account runs both detected SynthID in the resaved source control and in the +exact Qwen-VAE donor output. The candidate improved mean text-box SSIM from +0.791 to 0.835 and glyph-edge MAE from 35.48 to 22.49 with a 3.67% donor area, +but Gemini still detected it. The intermediate Qwen silhouette base and the +earlier original-sign Qwen baseline were also detected in the same account. +This isolates the current blocker upstream of text restoration: the global +Google pass did not reach the SynthID removal floor, so the OpenAI-clean +`vae-glyphs` configuration cannot be used as a Google operating point. + +On the large Chinese-sign control, source and candidate recognition agreed on +all three lines. The script selected no changed lines, emitted a zero mask, and +copied the candidate byte for byte instead of re-encoding it. This corrects the +earlier unnecessary CJK rerender and proves the no-edit branch. It does not add +a Google-negative oracle result: the available Google account still cannot +reproduce a source-positive, Qwen-negative control sequence. + +The earlier automatic rerender was the first variant in the experiment to pass +the recorded visual, fidelity, and OpenAI-oracle gates on its two poster +fixtures. The later typography-rich control shows that result does not +generalize to source-typography preservation. It remains evaluation-only: it +depends on verified source text and source line boxes, uses macOS system fonts, +and has not been validated on natural scene text, rotated text, or automatic +line-box discovery. + +An opt-in `--detect-boxes` follow-up tested automatic geometry. Grouping Paddle +word detections by vertical overlap found exactly 20/20, 20/20, and 3/3 lines; +mean IoU with verified boxes was 0.857, 0.847, and 1.000. Reusing the annotation +crop padding was unstable and preserved only 4/20 dark-poster lines. Reducing +vertical recognition padding to 10% restored the exact 8/20 and 7/20 selection +decisions, but detector CER was 0.127 and 0.154 rather than 0.123 and 0.119. The +dark regression failed the fidelity gate, so these hashes were not submitted to +the provider oracle. The flag is retained only to reproduce that negative +evaluation and still requires verified strings and an exact line-count match. + +### AnyText2 glyph-conditioned follow-up + +`anytext2-restoration-2026-08-13.csv` tests the official Apache-2.0 +AnyText2 checkpoint as a local text-editing pass over the exact Qwen Chinese-sign +output. The checkpoint hash was verified against ModelScope. Its own edit example +successfully replaced a masked blackboard line with the requested `DADDY`, so the +runtime and checkpoint were functional before the tracked fixture was measured. + +AnyText2 failed the fidelity gate. The standard full-image detector scored the +default edit at CER 0.185 and the source-font mimic at 0.222, against 0.074 for +Qwen; image LPIPS moved from 0.289 to 0.338 and 0.345. A padded crop-recognition +cross-check removed the detector's punctuation misses: Qwen scored 0.000, the +default edit still scored 0.074 with two substituted characters, and the mimic +edit stayed at 0.222. Visual inspection agreed, with additional line-shape and +punctuation errors in the mimic output. No provider-oracle submission was made +because neither candidate preserved the requested content better than its +already-clean Qwen input. + +This result covers the published SD1.5 checkpoint, three Chinese lines of no more +than 20 characters, one seed, and one fixture. The repository still describes +AnyText2XL as unreleased, and the inference wrapper truncates each requested line +after 20 characters, so the published model cannot directly cover the longer +English and Russian poster lines. + +The padded cross-check also changes the interpretation of this fixture's earlier +restoration result. Its Qwen image already contains all three correct Chinese +lines; the reported 0.074 came from the detector cropping the final punctuation +from two lines. A deterministic rerender with horizontally padded recognition +and a mask-matched Hiragino Sans GB W6 font reached detector CER 0.000, but image +LPIPS worsened from 0.289 to 0.327 and the font was visibly heavier. The correct +policy on this large-text fixture is therefore to leave a Qwen line unchanged +when padded recognition confirms the source and candidate strings agree. + +## Face-stage isolation + +`face-stage-isolation-2026-08-13.csv` separates the two stages on the 18-face +Gemini portrait-grid fixture. All variants use the same visible-cleaned, +metadata-stripped input and seed 0. The prepared input remained positive in the +Gemini SynthID verifier, so the surrounding visible and metadata stages did not +create the clean verdicts. + +| Route | Gemini oracle | ID cosine | Face LPIPS | Laplacian ratio | Image LPIPS | +| --- | --- | ---: | ---: | ---: | ---: | +| Prepared control | detected | - | - | - | - | +| Z-Image face stage only | detected | 0.927 | 0.028 | 0.612 | 0.013 | +| Qwen global stage only | clean | 0.589 | 0.217 | 0.331 | 0.288 | +| Full Qwen then Z-Image | clean | 0.852 | 0.050 | 0.492 | 0.252 | + +This isolates the roles: the Qwen global pass removes the watermark, while the +masked Z-Image pass restores face identity and detail without reintroducing a +detectable whole-image signal. Z-Image face-only does not remove SynthID. The +result explains why the face stage succeeded while global Z-Image failed: the +earlier clean verdict belonged to the two-stage pipeline, not to Z-Image as a +standalone remover. This is one fixture, one seed, and one oracle pass per +variant; it does not establish a general reintroduction threshold for mask size. + +## Upstream Synthid-Bypass v2 reproduction + +`upstream-v2-reproduction-2026-08-13.csv` records a source-level audit and a +close reproduction of the public +[`Synthid-Bypass-v2.0.json`](https://github.com/cebeuq/Synthid-Bypass/blob/3007d0351596ae0a78b7074dae7ad179710b1e48/Synthid-Bypass-v2.0.json). +The audited workflow is SHA-256 +`41911b3b8e01bf51450361dc8beccd84c9513f78ddb160430cdd9bebc294adf6` at +upstream commit `3007d0351596ae0a78b7074dae7ad179710b1e48`. + +Tracing links backward from `SaveImage` confirms that the active global stage is +Qwen-Image-2512 Q4 with the Lightning four-step LoRA at 0.8, DiffSynth Canny at +1.0, AuraFlow shift 3, `dpmpp_2m` plus `sgm_uniform`, CFG 1, and four steps. Its +resolution helper uses adaptive level 6 over 0.08..0.15, which resolves to the +0.154 ceiling for the 2816x1536 portrait fixture. Z-Image Turbo is used only by +the face detailer: eight steps, CFG 1, `res_2s` plus `bong_tangent`, a 768 px +guide, 1024 px cap, and direct adaptive denoise +`clamp(0.10 * largest_face_ratio / 0.03, 0.05, 0.28)`. The active face regions +come from YOLOv8-face plus SAM. Although the README describes MediaPipe as part +of the strict path, the MediaPipe nodes do not reach the saved output. The +1.2-megapixel scaler is also bypassed (`mode=4`). + +The published upstream pair 12 changed from SynthID-positive to clean in the +Gemini verifier and scored 0.975 face identity. On the project portrait fixture, +the prepared control was positive, while both the close global reproduction and +the close full reproduction were clean. The global stage scored 0.589 identity +and 0.217 face LPIPS; the upstream-strength face stage improved those to 0.783 +and 0.083. The maintained profile's weaker face pass scored 0.852 and 0.050 on +the same control, so copying upstream's roughly doubled face strength would be a +quality regression on this fixture. + +The close reproduction is intentionally labeled rather than presented as an +exact ComfyUI run. It uses full-precision DiffSynth bf16 weights instead of the +Q4 GGUF files, the maintained DiffSynth Lightning scheduler approximation +instead of the ComfyUI sampler pair, YuNet plus SAM instead of YOLOv8-face plus +SAM, and fixed seed 0 instead of randomized seeds. The two independent oracle +controls and the published upstream pair establish the direction of the result; +they do not make the local output byte-equivalent to upstream. ## Compare ```bash uv run scripts/fidelity_metrics.py compare \ --original data/synthid/originals/Gemini_Generated_Image_y48j3cy48j3cy48j.png \ - --variant controlnet=.png \ - --variant qwen=.png \ + --variant qwen-zimage=.png \ + --variant sdxl-zimage=.png \ --ocr-langs "" ``` diff --git a/data/evaluations/fidelity/anytext2-restoration-2026-08-13.csv b/data/evaluations/fidelity/anytext2-restoration-2026-08-13.csv new file mode 100644 index 0000000..43df6e1 --- /dev/null +++ b/data/evaluations/fidelity/anytext2-restoration-2026-08-13.csv @@ -0,0 +1,4 @@ +date,source_commit,fixture,variant,model,model_revision,model_sha256,ocr_route,text_cer,img_lpips,ssim,psnr_db,output_sha256,provider_oracle,notes +2026-08-13,2d03a00,Gemini_Generated_Image_633uuy633uuy633u.png,qwen-zimage,,,,"Paddle ch",0.074,0.289,0.696,23.3,4b05489c7a79f993d35d32528de792baad5a3e33ee950afa147caf5d3e0621,not_run,Exact Qwen baseline reused from text-restoration benchmark +2026-08-13,2d03a00,Gemini_Generated_Image_633uuy633uuy633u.png,qwen-zimage-anytext2-default,AnyText2-SD1.5,b06c583a583818f3679665ef67b51363f107853c,7d5d593928d842500e0da101ab6a1ae28a8d21403441ca62c813c4f92529ace8,"Paddle ch",0.185,0.338,0.642,18.2,87729fe7e0482d2cead6d3b7c7dd386ecc75ab3d8658131c54e67528b8f9feeb,not_run,Fidelity gate failed; padded crop recognition CER 0.074 still shows two substituted characters; locally composited three-line edit; 20 DDIM steps; seed 0 +2026-08-13,2d03a00,Gemini_Generated_Image_633uuy633uuy633u.png,qwen-zimage-anytext2-mimic,AnyText2-SD1.5,b06c583a583818f3679665ef67b51363f107853c,7d5d593928d842500e0da101ab6a1ae28a8d21403441ca62c813c4f92529ace8,"Paddle ch",0.222,0.345,0.644,17.9,f5198d8f2365b79880aba8ea6b0c3885142b9de21c9f7f4f6712ac4a0db8dd85,not_run,Fidelity gate failed; source glyph masks supplied as font hints; 20 DDIM steps; seed 0 diff --git a/data/evaluations/fidelity/face-stage-isolation-2026-08-13.csv b/data/evaluations/fidelity/face-stage-isolation-2026-08-13.csv new file mode 100644 index 0000000..3082584 --- /dev/null +++ b/data/evaluations/fidelity/face-stage-isolation-2026-08-13.csv @@ -0,0 +1,5 @@ +date,source_commit,fixture,route,seed,global_strength,face_strength,face_count,id_cos,face_lpips,lapvar_ratio,img_lpips,ssim,psnr_db,output_sha256,gemini_oracle +2026-08-13,2d03a00,Gemini_Generated_Image_y48j3cy48j3cy48j.png,visible-metadata-control,0,,,18,,,,,,,9f67264beb1da66a710885464a11f2ae85989094149f6145521d302cd2db940b,synthid_detected +2026-08-13,2d03a00,Gemini_Generated_Image_y48j3cy48j3cy48j.png,zimage-face-only,0,,0.06604512532552083,18,0.927,0.028,0.612,0.013,0.959,34.7,c388e9bbf737bb9b22a7cb2216ffb8026321ca632f9db29e1e456b7c14a8553f,synthid_detected +2026-08-13,2d03a00,Gemini_Generated_Image_y48j3cy48j3cy48j.png,qwen-global-only,0,0.153999996,,18,0.589,0.217,0.331,0.288,0.646,24.2,281a1d28d786d157cbbd13d00a32b215de4dfb3174a3c5e4e83c843e5402138f,clean +2026-08-13,2d03a00,Gemini_Generated_Image_y48j3cy48j3cy48j.png,qwen-zimage-full,0,0.153999996,0.06604512532552083,18,0.852,0.050,0.492,0.252,0.671,24.7,69de191bddbfbf53348c7638bdba661b6d45d257c00a8a2646b2558670c5a4c2,clean diff --git a/data/evaluations/fidelity/ground-truth.json b/data/evaluations/fidelity/ground-truth.json index 240deff..76b9cd4 100644 --- a/data/evaluations/fidelity/ground-truth.json +++ b/data/evaluations/fidelity/ground-truth.json @@ -1,4 +1,5 @@ { + "ChatGPT Image May 31, 2026, 02_02_23 PM.png": "This is a longer sample text in English.\nTypography can flow smoothly from large to medium size.\nSmaller lines help demonstrate hierarchy, rhythm, and clarity.\nEven the finest text should remain clean and readable.\n这是一段较长的中文示例文本。\n排版可以从大字号逐渐过渡到中字号。\n更小的文字能够展示层次、节奏与清晰度。\n即使是最小的一行,也应该保持清楚易读。\nЭто более длинный пример текста на русском языке.\nТипографика может плавно переходить от крупного размера к среднему.\nБолее мелкие строки показывают иерархию, ритм и ясность.\nДаже самый маленький текст должен оставаться чистым и читаемым.", "ChatGPT Image May 31, 2026, 02_03_55 PM.png": "This is a longer sample text in English.\nTypography can flow smoothly from large to medium size.\nSmaller lines help demonstrate hierarchy, rhythm, and clarity.\nEven the finest text should remain clean and readable.\n这是一段较长的中文示例文本。\n排版可以从大字号逐渐过渡到中字号。\n更小的文字能够展示层次、节奏与清晰度。\n即使是最小的一行,也应该保持清楚易读。\nЭто более длинный пример текста на русском языке.\nТипографика может плавно переходить от крупного размера к среднему.\nБолее мелкие строки показывают иерархию, ритм и ясность.\nДаже самый маленький текст должен оставаться чистым и читаемым.", "Gemini_Generated_Image_633uuy633uuy633u.png": "每天都是一个新的机会。\n用微笑开始它。\n世界也会向你微笑。" } diff --git a/data/evaluations/fidelity/selective-text-restoration-2026-08-13.csv b/data/evaluations/fidelity/selective-text-restoration-2026-08-13.csv new file mode 100644 index 0000000..33432dc --- /dev/null +++ b/data/evaluations/fidelity/selective-text-restoration-2026-08-13.csv @@ -0,0 +1,14 @@ +date,source_commit,fixture,variant,ocr_route,text_cer,img_lpips,ssim,psnr_db,mask_fraction,output_sha256,provider_oracle,oracle_control +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_02_23 PM.png",qwen-zimage,"Paddle en+ru+ch",0.378,0.107,0.832,24.4,,bed68cf79a023cad582a11b41c4c2962bd90a218037ce931a2b3a8697e3bf4bd,no_openai_signals_detected,source-positive control established earlier +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_02_23 PM.png",qwen-zimage-text-restored,"Paddle en+ru+ch",0.048,0.174,0.703,13.9,0.154,3221b2694f39420d509dd37cdaf69581432f9a189e87397346de0c78762402c6,no_openai_signals_detected,qwen-zimage clean in same run +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_02_23 PM.png",qwen-zimage-text-selective,"Paddle en+ru+ch",0.101,0.120,0.796,19.5,0.054799,15b40b8cdf9e97558c66d4acdcb97d46769e64f04f143f8061746bc731fbbc21,no_openai_signals_detected,source detected in same browser sequence +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_02_23 PM.png",qwen-zimage-text-selective-expanded-mask,"Paddle en+ru+ch",0.081,0.119,0.796,19.5,0.070267,5a8a0bcbe5763d1b1af0d64149cec7e35b16c5751e995d3bacf17581d64177ec,not_checked,fidelity-only follow-up +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_02_23 PM.png",qwen-zimage-text-selective-automatic,"Paddle detector en+ru+ch; padded recognizer selection",0.123,0.113,0.808,21.2,0.041279,900def5a433c039aa474789eddc4dc4c63b8126ba1aff6829d2e8d600b9f9c71,no_openai_signals_detected,source detected immediately afterward in the same Chrome sequence +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_03_55 PM.png",qwen-zimage,"Paddle en+ru+ch",0.413,0.095,0.818,26.0,,83e777ad28bf80a8e5aa7d2e6ef2348337c667212e2c71075460352ad7423713,no_openai_signals_detected,source-positive control established earlier +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_03_55 PM.png",qwen-zimage-text-restored,"Paddle en+ru+ch",0.086,0.162,0.682,14.6,0.170,95812399424feb70fc56d3e518d1ae0a3a50a3c2049d5902b00686ede9d3022d,no_openai_signals_detected,qwen-zimage clean in same run +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_03_55 PM.png",qwen-zimage-text-selective,"Paddle en+ru+ch",0.112,0.103,0.780,20.8,0.054912,846f39db0ad4a48a734a5019f0b0ff5d110eecb3210223407e72529dfa4fcf54,no_openai_signals_detected,source detected in same browser sequence +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_03_55 PM.png",qwen-zimage-text-selective-expanded-mask,"Paddle en+ru+ch",0.147,0.104,0.779,20.8,0.072556,8ae76e1dedeec26bebb8d35e71fd92d77f142dbb0f3440b330cdf33b79558afe,not_checked,fidelity-only follow-up +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_03_55 PM.png",qwen-zimage-text-selective-automatic,"Paddle detector en+ru+ch; padded recognizer selection",0.119,0.104,0.779,20.8,0.057110,ac4201bd2e342b1a37737319438cedb2203f6bb58cd9113f825cf9b80d913755,no_openai_signals_detected,source detected in same browser sequence +2026-08-13,2d03a00,Gemini_Generated_Image_633uuy633uuy633u.png,qwen-zimage-text-selective-automatic-passthrough,"Paddle detector ch; horizontal-only padded recognizer selection",0.074,0.289,0.696,23.3,0.000000,4b05489c7a79f993d35d32528de792baad5a3e33ee950afa147caf5d3e0621a6,not_checked,all three source and candidate recognitions agreed exactly; output is byte-identical to candidate +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_02_23 PM.png",qwen-zimage-text-selective-detected-boxes,"Paddle ch detector boxes; 0.10-height recognizer padding",0.127,0.113,0.805,20.9,0.041280,70c52fed8b8a1d98f4cda8558ae08825313e510dfb372e448370d8dcf1da73b0,not_checked,fidelity-only automatic-box follow-up; same 8/20 preserve decisions +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_03_55 PM.png",qwen-zimage-text-selective-detected-boxes,"Paddle ch detector boxes; 0.10-height recognizer padding",0.154,0.104,0.778,20.8,0.057146,1fa01ce706042c003a61429d5d97fc6c928ec32278071a91926d588f1e760b00,not_checked,fidelity-only automatic-box follow-up; same 7/20 preserve decisions but worse CER diff --git a/data/evaluations/fidelity/text-lines.json b/data/evaluations/fidelity/text-lines.json new file mode 100644 index 0000000..251e4cb --- /dev/null +++ b/data/evaluations/fidelity/text-lines.json @@ -0,0 +1,51 @@ +{ + "ChatGPT Image May 31, 2026, 02_02_23 PM.png": [ + {"box": [156, 206, 709, 296], "text": "This is a longer", "script": "alphabetic"}, + {"box": [157, 293, 939, 379], "text": "sample text in English.", "script": "alphabetic"}, + {"box": [165, 393, 774, 434], "text": "Typography can flow smoothly from", "script": "alphabetic"}, + {"box": [161, 434, 521, 477], "text": "large to medium size.", "script": "alphabetic"}, + {"box": [165, 495, 566, 522], "text": "Smaller lines help demonstrate hierarchy,", "script": "alphabetic"}, + {"box": [165, 524, 354, 550], "text": "rhythm, and clarity.", "script": "alphabetic"}, + {"box": [165, 564, 448, 585], "text": "Even the finest text should remain", "script": "alphabetic"}, + {"box": [165, 593, 330, 613], "text": "clean and readable.", "script": "alphabetic"}, + {"box": [158, 673, 903, 740], "text": "这是一段较长的中文示例文本。", "script": "cjk"}, + {"box": [162, 753, 697, 793], "text": "排版可以从大字号逐渐过渡到中字号。", "script": "cjk"}, + {"box": [164, 807, 524, 829], "text": "更小的文字能够展示层次、节奏与清晰度。", "script": "cjk"}, + {"box": [163, 845, 483, 868], "text": "即使是最小的一行,也应该保持清楚易读。", "script": "cjk"}, + {"box": [162, 951, 780, 1009], "text": "Это более длинный пример", "script": "alphabetic"}, + {"box": [161, 1019, 723, 1062], "text": "текста на русском языке.", "script": "alphabetic"}, + {"box": [165, 1076, 639, 1104], "text": "Типографика может плавно переходить от", "script": "alphabetic"}, + {"box": [165, 1111, 505, 1133], "text": "крупного размера к среднему.", "script": "alphabetic"}, + {"box": [165, 1151, 513, 1172], "text": "Более мелкие строки показывают иерархию,", "script": "alphabetic"}, + {"box": [167, 1178, 287, 1192], "text": "ритм и ясность.", "script": "alphabetic"}, + {"box": [165, 1210, 417, 1227], "text": "Даже самый маленький текст должен", "script": "alphabetic"}, + {"box": [165, 1235, 381, 1247], "text": "оставаться чистым и читаемым.", "script": "alphabetic"} + ], + "ChatGPT Image May 31, 2026, 02_03_55 PM.png": [ + {"box": [165, 204, 754, 299], "text": "This is a longer", "script": "alphabetic"}, + {"box": [167, 295, 1006, 385], "text": "sample text in English.", "script": "alphabetic"}, + {"box": [171, 401, 831, 446], "text": "Typography can flow smoothly from", "script": "alphabetic"}, + {"box": [169, 446, 560, 491], "text": "large to medium size.", "script": "alphabetic"}, + {"box": [173, 507, 605, 536], "text": "Smaller lines help demonstrate hierarchy,", "script": "alphabetic"}, + {"box": [173, 538, 379, 567], "text": "rhythm, and clarity.", "script": "alphabetic"}, + {"box": [175, 583, 481, 603], "text": "Even the finest text should remain", "script": "alphabetic"}, + {"box": [173, 611, 352, 632], "text": "clean and readable.", "script": "alphabetic"}, + {"box": [170, 693, 956, 757], "text": "这是一段较长的中文示例文本。", "script": "cjk"}, + {"box": [174, 775, 740, 812], "text": "排版可以从大字号逐渐过渡到中字号。", "script": "cjk"}, + {"box": [175, 829, 561, 852], "text": "更小的文字能够展示层次、节奏与清晰度。", "script": "cjk"}, + {"box": [174, 869, 534, 892], "text": "即使是最小的一行,也应该保持清楚易读。", "script": "cjk"}, + {"box": [169, 958, 824, 1022], "text": "Это более длинный пример", "script": "alphabetic"}, + {"box": [171, 1029, 764, 1074], "text": "текста на русском языке.", "script": "alphabetic"}, + {"box": [175, 1090, 678, 1119], "text": "Типографика может плавно переходить от", "script": "alphabetic"}, + {"box": [175, 1125, 546, 1151], "text": "крупного размера к среднему.", "script": "alphabetic"}, + {"box": [175, 1169, 552, 1192], "text": "Более мелкие строки показывают иерархию,", "script": "alphabetic"}, + {"box": [175, 1196, 312, 1217], "text": "ритм и ясность.", "script": "alphabetic"}, + {"box": [175, 1233, 479, 1253], "text": "Даже самый маленький текст должен", "script": "alphabetic"}, + {"box": [177, 1259, 440, 1272], "text": "оставаться чистым и читаемым.", "script": "alphabetic"} + ], + "Gemini_Generated_Image_633uuy633uuy633u.png": [ + {"box": [1281, 650, 2357, 818], "text": "每天都是一个新的机会。", "script": "cjk"}, + {"box": [1466, 788, 2167, 957], "text": "用微笑开始它。", "script": "cjk"}, + {"box": [1367, 910, 2264, 1112], "text": "世界也会向你微笑。", "script": "cjk"} + ] +} diff --git a/data/evaluations/fidelity/text-preservation-2026-08-13.csv b/data/evaluations/fidelity/text-preservation-2026-08-13.csv new file mode 100644 index 0000000..ce99b14 --- /dev/null +++ b/data/evaluations/fidelity/text-preservation-2026-08-13.csv @@ -0,0 +1,25 @@ +date,source_commit,fixture,profile,route,seed,strength,adaptive_polish,text_cer,ocr_floor,added_cer,img_lpips,ssim,psnr_db,output_sha256,oracle_rechecked +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_02_23 PM.png",original,reference,,,,0.127,0.127,0.000,0.000,1.000,inf,,not_applicable +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_02_23 PM.png",qwen-zimage,visible-invisible-metadata,0,0.11020972541176471,false,0.378,0.127,0.251,0.107,0.832,24.4,bed68cf79a023cad582a11b41c4c2962bd90a218037ce931a2b3a8697e3bf4bd,false +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_02_23 PM.png",sdxl-zimage,visible-invisible-metadata,0,0.15,true,0.356,0.127,0.229,0.125,0.707,25.3,9bb932d6e66b52f34ff07c3fcdbdb9c33eb601ae2cd84bec6b8c7b5e30df9926,false +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_03_55 PM.png",original,reference,,,,0.127,0.127,0.000,0.000,1.000,inf,,not_applicable +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_03_55 PM.png",qwen-zimage,visible-invisible-metadata,0,0.11020972541176471,false,0.413,0.127,0.286,0.095,0.818,26.0,83e777ad28bf80a8e5aa7d2e6ef2348337c667212e2c71075460352ad7423713,false +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_03_55 PM.png",sdxl-zimage,visible-invisible-metadata,0,0.15,true,0.422,0.127,0.295,0.099,0.754,25.9,7605b1175b0c99f481ae6929acc488f34d98bc0f2571d42a1d2972b671d9b8cb,false +2026-08-13,2d03a00,Gemini_Generated_Image_633uuy633uuy633u.png,original,reference,,,,0.037,0.037,0.000,0.000,1.000,inf,,not_applicable +2026-08-13,2d03a00,Gemini_Generated_Image_633uuy633uuy633u.png,qwen-zimage,visible-invisible-metadata,0,0.153999996,false,0.074,0.037,0.037,0.289,0.696,23.3,4b05489c7a79f993d35d32528de792baad5a3e33ee950afa147caf5d3e0621a6,false +2026-08-13,2d03a00,Gemini_Generated_Image_633uuy633uuy633u.png,sdxl-zimage,visible-invisible-metadata,0,0.25,true,0.074,0.037,0.037,0.176,0.769,26.1,d9f89465c31bd2d38ffb111775b9bbea4577f40713fcbebf421e05d344ab27ad,false +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_02_23 PM.png",zimage-global,visible-invisible-metadata,0,0.08,false,0.068,0.127,-0.059,0.042,0.874,31.4,534de6422a3a03cfde44377aa9bf824a8267db3b73511240f8f07fb8dcb95eff,true +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_02_23 PM.png",zimage-global,visible-invisible-metadata,0,0.10,false,0.086,0.127,-0.041,0.047,0.862,30.8,4dba8211e8d84ebbfff72de00f54d4365d0d202394acd412d1419ae93d1e9f71,false +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_02_23 PM.png",zimage-global,visible-invisible-metadata,0,0.15,false,0.177,0.127,0.050,0.065,0.838,29.1,fc45c5fff6d33f15d30e05dd02580fe0addd97098b735b2894015f9abdaa43a7,false +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_02_23 PM.png",zimage-global,visible-invisible-metadata,0,0.20,false,0.248,0.127,0.121,0.093,0.813,27.3,1e55e11f0e289a752098370e9f1d53fed6cd4fab7ceff46fe86dd20c927f9ffb,false +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_02_23 PM.png",zimage-global,visible-invisible-metadata,0,0.25,false,0.343,0.127,0.216,0.120,0.787,25.7,f5a2d2829e88a790cff5bedc50fca1f878a92cdb392d530afb49d0d95c23b884,false +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_03_55 PM.png",zimage-global,visible-invisible-metadata,0,0.08,false,0.143,0.127,0.016,0.042,0.877,31.5,5cfa466e276ab37cf532f7458d6dd86ea4f0807e2cbdb6788b7c01ca6ee35a1a,true +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_03_55 PM.png",zimage-global,visible-invisible-metadata,0,0.10,false,0.101,0.127,-0.026,0.047,0.864,30.9,ade869ec7c5b08098495d9b9d67ee900773f189f94bbb411b043f242f5c2fa37,true +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_03_55 PM.png",zimage-global,visible-invisible-metadata,0,0.15,false,0.208,0.127,0.081,0.060,0.839,29.3,a11976963e3f2ca856ce66278e22064d51345d449fa6d672029039a79517d60a,false +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_03_55 PM.png",zimage-global,visible-invisible-metadata,0,0.20,false,0.341,0.127,0.214,0.070,0.819,27.6,1ab1ae075565e9d39960a80cf5d61e5b0c50d7e23283493ea0277056b3e964ab,false +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_03_55 PM.png",zimage-global,visible-invisible-metadata,0,0.25,false,0.538,0.127,0.411,0.084,0.799,25.6,5ff5a364d84f738de4c56ead7d4950a2f9a1aaf4bec16e97bda6a65915744a57,false +2026-08-13,2d03a00,Gemini_Generated_Image_633uuy633uuy633u.png,zimage-global,visible-invisible-metadata,0,0.08,false,0.074,0.037,0.037,0.068,0.865,30.8,81419eddc3e5150956859dd10c26dd841bb93d9c8b34c01f344e6d69bca6edbf,true +2026-08-13,2d03a00,Gemini_Generated_Image_633uuy633uuy633u.png,zimage-global,visible-invisible-metadata,0,0.10,false,0.074,0.037,0.037,0.086,0.844,29.8,4d1f32bd0dac0e05885ff943e7c10230b06de7d8547e6215f8e2941f732a1df5,true +2026-08-13,2d03a00,Gemini_Generated_Image_633uuy633uuy633u.png,zimage-global,visible-invisible-metadata,0,0.15,false,0.000,0.037,-0.037,0.135,0.793,27.6,526c7987b34896f9513f7439b63b8a87573b59d15930fa307f3bb924435f3cec,true +2026-08-13,2d03a00,Gemini_Generated_Image_633uuy633uuy633u.png,zimage-global,visible-invisible-metadata,0,0.20,false,0.000,0.037,-0.037,0.179,0.746,25.9,325364cf7a3b5d39ac0c44af82181753ca4d6fde0f1b522c896414191725e8fc,true +2026-08-13,2d03a00,Gemini_Generated_Image_633uuy633uuy633u.png,zimage-global,visible-invisible-metadata,0,0.25,false,0.000,0.037,-0.037,0.218,0.703,24.5,f01d64dc0cabd45d66a93c1be39b819b988f29cda1f0a7bc0d5a1954571d807e,true diff --git a/data/evaluations/fidelity/text-restoration-2026-08-13.csv b/data/evaluations/fidelity/text-restoration-2026-08-13.csv new file mode 100644 index 0000000..c6aa2b4 --- /dev/null +++ b/data/evaluations/fidelity/text-restoration-2026-08-13.csv @@ -0,0 +1,7 @@ +date,source_commit,fixture,variant,ocr_route,text_cer,img_lpips,ssim,psnr_db,mask_fraction,output_sha256,provider_oracle,oracle_control +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_02_23 PM.png",qwen-zimage,"Vision en+ru; Paddle ch",0.338,0.107,0.832,24.4,,bed68cf79a023cad582a11b41c4c2962bd90a218037ce931a2b3a8697e3bf4bd,no_openai_signals_detected,source-positive control established earlier +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_02_23 PM.png",qwen-zimage-text-restored,"Vision en+ru; Paddle ch",0.007,0.174,0.703,13.9,0.154,3221b2694f39420d509dd37cdaf69581432f9a189e87397346de0c78762402c6,no_openai_signals_detected,qwen-zimage clean in same run +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_03_55 PM.png",qwen-zimage,"Vision en+ru; Paddle ch",0.305,0.095,0.818,26.0,,83e777ad28bf80a8e5aa7d2e6ef2348337c667212e2c71075460352ad7423713,no_openai_signals_detected,source-positive control established earlier +2026-08-13,2d03a00,"ChatGPT Image May 31, 2026, 02_03_55 PM.png",qwen-zimage-text-restored,"Vision en+ru; Paddle ch",0.007,0.162,0.682,14.6,0.170,95812399424feb70fc56d3e518d1ae0a3a50a3c2049d5902b00686ede9d3022d,no_openai_signals_detected,qwen-zimage clean in same run +2026-08-13,2d03a00,Gemini_Generated_Image_633uuy633uuy633u.png,qwen-zimage,"Paddle ch",0.074,0.289,0.696,23.3,,4b05489c7a79f993d35d32528de792baad5a3e33ee950afa147caf5d3e0621a6,synthid_detected,inconclusive account: clean baseline not reproduced +2026-08-13,2d03a00,Gemini_Generated_Image_633uuy633uuy633u.png,qwen-zimage-text-restored,"Paddle ch",0.111,0.328,0.653,18.1,0.051,2ab7666a4161ff0e12c53d9412b23d013970c7613a8a55a1b1ae8a2744e3290d,synthid_detected,inconclusive account: qwen-zimage control also detected diff --git a/data/evaluations/fidelity/upstream-v2-reproduction-2026-08-13.csv b/data/evaluations/fidelity/upstream-v2-reproduction-2026-08-13.csv new file mode 100644 index 0000000..fc8a7fc --- /dev/null +++ b/data/evaluations/fidelity/upstream-v2-reproduction-2026-08-13.csv @@ -0,0 +1,6 @@ +date,upstream_commit,workflow_sha256,fixture,route,seed,global_strength,face_strength,face_count,id_cos,face_lpips,lapvar_ratio,img_lpips,ssim,psnr_db,output_sha256,gemini_oracle,implementation +2026-08-13,3007d0351596ae0a78b7074dae7ad179710b1e48,41911b3b8e01bf51450361dc8beccd84c9513f78ddb160430cdd9bebc294adf6,comparison/before/12.png,official-before,,,,10,,,,,,,056276464387c883f5fd89bd2f1415faeb425abb3153cd10efb25d1b9053bdc2,synthid_detected,upstream-published-bytes +2026-08-13,3007d0351596ae0a78b7074dae7ad179710b1e48,41911b3b8e01bf51450361dc8beccd84c9513f78ddb160430cdd9bebc294adf6,comparison/after/12.png,official-after,,,,10,0.975,0.093,0.354,0.172,0.820,23.8,a822d805666224adb3e360b3ee89e14f20a341f1e7c4d3a5d3d5251e9286aefe,clean,upstream-published-bytes +2026-08-13,3007d0351596ae0a78b7074dae7ad179710b1e48,41911b3b8e01bf51450361dc8beccd84c9513f78ddb160430cdd9bebc294adf6,Gemini_Generated_Image_y48j3cy48j3cy48j.png,visible-metadata-control,0,,,18,,,,,,,9f67264beb1da66a710885464a11f2ae85989094149f6145521d302cd2db940b,synthid_detected,project-runtime-control +2026-08-13,3007d0351596ae0a78b7074dae7ad179710b1e48,41911b3b8e01bf51450361dc8beccd84c9513f78ddb160430cdd9bebc294adf6,Gemini_Generated_Image_y48j3cy48j3cy48j.png,upstream-global-close-reproduction,0,0.153999996,,18,0.589,0.217,0.331,0.286,0.647,24.3,281a1d28d786d157cbbd13d00a32b215de4dfb3174a3c5e4e83c843e5402138f,clean,diffsynth-bf16-yunet-sam +2026-08-13,3007d0351596ae0a78b7074dae7ad179710b1e48,41911b3b8e01bf51450361dc8beccd84c9513f78ddb160430cdd9bebc294adf6,Gemini_Generated_Image_y48j3cy48j3cy48j.png,upstream-full-close-reproduction,0,0.153999996,0.13209025065104166,18,0.783,0.083,0.470,0.260,0.659,24.7,2d7a8f76f98b905f14d69e5f9e11979610c72c6be7b6ac298dec4659f17bd819,clean,diffsynth-bf16-yunet-sam diff --git a/docs/known-limitations.md b/docs/known-limitations.md index 9bbdeba..bb52aa4 100644 --- a/docs/known-limitations.md +++ b/docs/known-limitations.md @@ -61,8 +61,25 @@ change even when the watermark is successfully disrupted. Both are CUDA only and differ only in the global regeneration model: each conditions that stage on a canny edge map, which preserves structure but not identity or exact texture, and each then runs the same face stage. -`qwen-zimage` is the higher fidelity of the two. Both are large, slow, and may -still alter small text or difficult faces. +Existing face evaluations favor `qwen-zimage`, but there is no blanket fidelity +ordering across content types. A fixed-seed, three-scene text comparison at the +profile defaults found no stable winner: SDXL won one poster, Qwen won one, and +the Chinese sign tied. Both are large, slow, and may still alter small text or +difficult faces. The measurements and their OCR and oracle caveats are tracked +in [`data/evaluations/fidelity/`](../data/evaluations/fidelity/README.md). +A global Z-Image Turbo prototype preserved text substantially better at low +strength, but it has no useful cross-provider operating point and is not a +supported profile. The evaluated text restorers also remain research-only: +fresh-font and silhouette variants visibly changed typography, while the +higher-fidelity `vae-glyphs` route still requires verified strings, line +geometry, a separately generated donor, and an independently clean global +anchor. Automatic OCR and line-box proposals are not reliable enough to remove +those requirements, and the exact oracle results do not establish a general +mask, seed, or provider operating range. Qwen-Image-2.0 is hosted-only and +exposes no equivalent low-strength denoise control. Exact experiments, controls, +and pass rates are kept in +[`text-protection-research.md`](text-protection-research.md) and the +[`fidelity` evaluation record](../data/evaluations/fidelity/README.md). ### Removal cannot be verified locally for proprietary SynthID @@ -175,6 +192,14 @@ certified at a fixed seed. The live resolver is | `qwen-zimage` | CUDA only, large model stack, and limited broad certification across seeds and content. | | `sdxl-zimage` | CUDA only. Its strength ladder is flat per vendor, not a resolution curve, because flat values are what was measured. | +The evaluated text-restoration prototypes are not optional production stages. +OCR plus LaMa recovered literal poster text but changed fonts and worsened whole-image +fidelity. Restricting it to OCR-mismatched lines improved the tradeoff but still +left a local shadow on one poster. The published AnyText2 SD1.5 checkpoint +substituted Chinese characters and increased CER on the sign fixture. AnyText2XL +is not published, and the released wrapper truncates individual text lines after +20 characters. + The `controlnet`, `sdxl`, `qwen` and `default` profiles were removed, not aliased onward: a retired name is rejected at parse time rather than routed into a profile the caller never chose. There is no `--model`, `--steps`, `--guidance-scale`, diff --git a/docs/module-internals.md b/docs/module-internals.md index c3a4061..cdbeb16 100644 --- a/docs/module-internals.md +++ b/docs/module-internals.md @@ -949,6 +949,34 @@ orchestration, YuNet integration, SAM selection, masks, sizing helpers, and pixe compositing are implemented for this runtime. Changing a calibrated model input requires the same provider-oracle and identity evaluation as a model change. +A matched stage-isolation check on the 18-face Gemini portrait grid confirms the +division of responsibility. The visible-cleaned, metadata-stripped control and the +Z-Image face-only output were both SynthID-positive; Qwen global-only and the full +Qwen-then-Z-Image output were both clean. The face stage raised identity cosine from +0.589 to 0.852 and reduced face LPIPS from 0.217 to 0.050 without reintroducing a +detectable whole-image signal. Thus Z-Image is a masked fidelity repair stage here, +not the watermark-removal stage. Exact hashes, metrics, strengths, and the one-fixture, +one-seed caveat are recorded in +[`data/evaluations/fidelity/face-stage-isolation-2026-08-13.csv`](../data/evaluations/fidelity/face-stage-isolation-2026-08-13.csv). + +The public Synthid-Bypass v2 graph was subsequently audited at upstream commit +`3007d035`. Its saved-output path confirms the same division: Qwen-Image-2512 +Lightning plus Canny is global, and Z-Image Turbo exists only inside the masked +face detailer. The connected face path is YOLOv8-face plus SAM; the MediaPipe nodes +described by the upstream README and the 1.2-megapixel normalization node do not +reach `SaveImage`. Upstream also applies its adaptive face strength directly, +whereas this implementation multiplies it by `FACE_DENOISE_SCALE = 0.5`. + +A close reproduction on the same portrait fixture kept the control positive and +made both Qwen global-only and full Qwen-then-Z-Image outputs clean. Applying the +upstream-strength face pass raised identity from 0.589 to 0.783 and reduced face +LPIPS from 0.217 to 0.083, but remained worse than this profile's 0.852 and 0.050. +The published upstream pair 12 was also independently checked positive before and +clean after, with 0.975 identity. Exact workflow provenance, hashes, metrics, +oracle outcomes, and the DiffSynth/GGUF, scheduler, detector, and seed caveats are +recorded in +[`data/evaluations/fidelity/upstream-v2-reproduction-2026-08-13.csv`](../data/evaluations/fidelity/upstream-v2-reproduction-2026-08-13.csv). + ### SDXL plus Z-Image [`_internal/sdxl_zimage_pipeline.py`](../src/remove_ai_watermarks/_internal/sdxl_zimage_pipeline.py) diff --git a/docs/text-protection-research.md b/docs/text-protection-research.md index 6b3acb3..01bc1b4 100644 --- a/docs/text-protection-research.md +++ b/docs/text-protection-research.md @@ -143,3 +143,203 @@ Deep-research workflow run `wf_118b9a03-3eb` (2026-05-29). Findings adversariall (2/3 refutes required to kill a claim). This note records research only; no code change is implied until a prototype validates fidelity and the SynthID-scrub guarantee on the restored output. + +## 2026-08-13 OCR plus LaMa prototype + +A lightweight version of the recommended two-stage architecture was tested on +the three tracked text fixtures. It used the already-clean Qwen result as the +base, recognized the source text, removed source and Qwen glyph contours with +LaMa, and rendered the recognized strings as fresh pixels. No source pixels +were pasted back. + +The result validates only part of the hypothesis. Character-weighted CER on the +two multilingual posters fell from 0.338/0.305 to 0.007/0.007, and OpenAI Verify +remained clean for both matched Qwen and restored pairs. However, replacement +fonts changed the design, whole-image LPIPS worsened by 0.067 on each poster, +and the light poster retained a shadow in one small English line. On the Chinese +sign, punctuation errors moved CER from 0.074 to 0.111. The Google verifier run +was inconclusive because the available account detected both the restored image +and the byte-identical Qwen control that a previous account had called clean. + +The experiment supports a proper glyph-conditioned restorer, not shipping the +plain OCR/system-font compositor. Exact hashes, metrics, masks, and provider +verdicts are tracked in +[`data/evaluations/fidelity/text-restoration-2026-08-13.csv`](../data/evaluations/fidelity/text-restoration-2026-08-13.csv). + +## 2026-08-13 AnyText2 follow-up + +The official AnyText2 SD1.5 checkpoint was then tested as the glyph-conditioned +restorer. Its ModelScope entry and code are Apache 2.0, and the downloaded +checkpoint matched the published SHA-256. An official blackboard edit completed +successfully before the fixture run, establishing that the runtime reproduced +the model rather than silently exercising a fallback. + +On the Chinese sign, a three-line local edit over the exact Qwen output scored +CER 0.185 under the standard detector. Font mimic from the source glyph masks +scored 0.222. Whole-image LPIPS changed from 0.289 to 0.338 and 0.345 respectively. +A padded crop-recognition check showed why the comparison matters: Qwen contained +the full correct text and scored 0.000, while default AnyText2 still scored 0.074 +with two substituted characters and mimic remained at 0.222. The provider oracle +was deliberately not called because these variants had already failed the +content-fidelity gate. + +AnyText2 is therefore not the missing production restorer in its published form. +The negative result is narrower than the model family: only the SD1.5 checkpoint +is public, while AnyText2XL remains listed as future work. The current wrapper +also truncates each quoted text line to 20 characters, which prevents a direct +test of several tracked English and Russian poster lines. Exact model provenance, +output hashes, and metrics are recorded in +[`data/evaluations/fidelity/anytext2-restoration-2026-08-13.csv`](../data/evaluations/fidelity/anytext2-restoration-2026-08-13.csv). + +The same cross-check exposes a measurement bug in the earlier lightweight +restoration result. Paddle detection made tight boxes that omitted the final +Chinese full stop from two lines, producing Qwen CER 0.074 although the pixels +and padded recognition were correct. Adding 16-32 pixels of horizontal context +restored every punctuation mark. A deterministic rerender using the full OCR +strings and the closest of nine mask-scored CJK fonts also reached detector CER +0.000, but its heavier Hiragino Sans GB W6 glyphs raised LPIPS from 0.289 to 0.327. +The actionable design is selective restoration: compare padded source and output +recognition first, and preserve the Qwen output when they already match. + +That policy was tested on the two multilingual posters. A manual prototype kept +the lines whose padded source and Qwen recognition agreed and rerendered only the +changed lines. The edited mask fell from 15.4%/17.0% in the full compositor to +5.5%/5.5%. On a single Paddle `en+ru+ch` route, CER changed from 0.378/0.413 for +Qwen to 0.101/0.112, while LPIPS was only 0.120/0.103 rather than the full +compositor's 0.174/0.162. The different OCR route is intentional and all three +variants were remeasured through it; these CER values are not directly +comparable with the earlier Vision/Paddle hybrid results. + +Both selective outputs returned `No OpenAI signals detected` in OpenAI Verify, +and the original light poster returned `Generated with OpenAI tools` immediately +afterward as the positive control. The dark poster was visually clean. The light +poster still showed one local old-text shadow, so mask construction remains the +blocking defect. Exact hashes, metrics, mask fractions, and oracle controls are +in +[`data/evaluations/fidelity/selective-text-restoration-2026-08-13.csv`](../data/evaluations/fidelity/selective-text-restoration-2026-08-13.csv). + +Uniformly expanding every selected glyph mask by two pixels removed that light +poster shadow and moved CER from 0.101 to 0.081 without a meaningful LPIPS cost. +It also expanded the edited area from 5.5% to 7.0%, and the identical rule made +the dark poster worse at CER 0.147 rather than 0.112. Those follow-up bytes have +not been oracle-checked. The next prototype should detect residual source glyphs +per line and expand only the failing component, rather than introducing another +poster-wide tuning constant. + +The manual selection was then replaced with per-line padded recognition. A line +is left untouched only when the source recognizer is sufficiently consistent +with the verified line and normalized source/candidate recognition agree +exactly. The automatic rule reproduced the manual selection on the dark poster +and correctly kept one additional large Russian line on the light poster. It +reduced that poster's effective edited area from 5.5% to 4.1%. + +Mask cleanup became a measured second pass rather than a global dilation. After +the first LaMa erase, the prototype finds contrast that remains specifically at +the original glyph positions, dilates only that residual, and erases it before +fresh text is drawn. This removed the visible double glyphs from both posters. +The automatic outputs scored LPIPS 0.113/0.104 and detector CER 0.123/0.119, with +effective edited fractions of 4.1%/5.7%. The light aggregate CER is pessimistic: +the page detector fragments its untouched Cyrillic line into Latin-like pieces, +while the padded Russian recognizer returns the exact expected text. + +OpenAI Verify returned `No OpenAI signals detected` for both new hashes and then +`Generated with OpenAI tools` for the original light poster in the same browser +sequence. Reproducing the prototype from the tracked script exposed harmless +ONNX rounding on the light image: 386 pixels differed by at most one channel +value from the initially checked hash. The exact tracked output hash +`900def5a...` was therefore submitted separately, returned clean, and was +followed immediately by another positive source result. The dark tracked output +was byte-identical to the initially checked hash. + +The Chinese sign provides a no-edit control for the selector. Detector boxes for +its three large lines overlap vertically, so Latin-style vertical padding +incorrectly mixed neighboring lines. Horizontal-only CJK padding captured the +terminal punctuation without mixing neighbors and made source and candidate +recognition agree exactly on all three lines. +The script then produced a zero mask and copied the Qwen candidate byte for byte. +This prevents the heavier-font regression seen in the earlier CJK compositor, +but does not resolve the unavailable Google-negative oracle control. + +The result clears the measured OpenAI gate without a manual line selection, but +is not a production design yet. It still assumes verified source strings and +line boxes, relies on macOS system fonts, and needs evaluation on uncurated +layouts, rotated text, false OCR matches, automatic line-box discovery, and a +reproducible Google-negative control. + +The later source-silhouette experiment removed the font lookup but did not meet +the actual visual requirement. Native-size review found changed stroke weight, +color variation, edge antialiasing, and decorative details even where crop OCR +was exact. A narrower `vae-glyphs` experiment uses the silhouette only as a +support mask: exact pixels come from a Qwen-VAE reconstruction, while a fresh +flat-color silhouette supplies only the outer edge beneath that core. Across 48 +typography cases (548 annotated lines), this improved glyph-edge MAE in 48/48 +and text-box SSIM in 47/48. The median values moved from 37.59 to 32.42 and from +0.854 to 0.914 respectively. One separate untracked core-only output with a +1.43% donor area returned `No OpenAI signals detected`; the tracked script then +reproduced those exact bytes. A 0.5-pixel feathered sibling with 2.75% nonzero +donor area improved mean text-box SSIM from 0.957 to 0.960, with a 0.918 minimum +across 15 verified lines, while full-image RGB SSIM reached 0.864. Crop OCR +recovered from 7/15 exact lines on the raw pass to 14/15, equal to the source's +own recognizer floor. Whole-image LPIPS was 0.082, but only 0.108% of pixels +remained exactly equal to the source and the detected face's Laplacian-variance +ratio was 0.670, confirming residual global smoothing. A same-session +OpenAI Verify sequence detected the exact source control in 1/1 check, returned +clean for the raw global pass and clean-fidelity anchor in 1/1 each, and returned +clean for the feathered output in 3/3 repeats. The tracked script reproduced +that feathered file byte for byte. This proves two materially better exact-output +Pareto points, not a general operating range. The 48 matrix outputs and other +mask sizes remain oracle-unverified. + +The first Google oracle sample did not transfer. For the synthetic CJK sign, +two separate work accounts detected SynthID in both the resaved positive source +control and the exact Qwen-VAE donor candidate. The candidate's text-box SSIM +improved from 0.791 to 0.835 and its glyph-edge MAE from 35.48 to 22.49, but its +3.67% donor layer did not create a clean operating point. The Qwen silhouette +base was already detected, as was the earlier original-sign Qwen baseline in the +same account. The measured cause is therefore insufficient global Google +regeneration, not a demonstrated watermark regression from the text donor. +Google needs a stronger oracle-clean global anchor before the narrow donor can +be evaluated there. + +A targeted follow-up supplied that missing anchor for one CJK case. An explicit +Qwen global pass at strength 0.30 returned no reliable SynthID signal in Gemini, +while the matched source control was detected. Applying the 0.5-pixel +`vae-glyphs` layer to that clean anchor changed 1.17% of the image and raised +mean text-box SSIM from 0.765 to 0.964 (minimum 0.963), with mean text-box MAE +falling from 20.74 to 5.38. The post-donor output then returned clean in 3/3 +Gemini checks across two work accounts. A separate 18-face fixture with four +tiny UI-text lines also had a clean strength-0.30 anchor; its donor layer changed +0.70% of the image, raised mean text-box SSIM from 0.617 to 0.976 (minimum +0.972), and returned clean in 3/3 checks across the same two accounts. One +quota-exceeded response is excluded from both pass rates. These are two exact +oracle-certified outputs, not a general operating range: other layouts, masks, +strengths, seeds, and donor areas remain unverified. + +An automatic-box follow-up merged Paddle word detections by vertical overlap. +It found exactly 20/20 poster lines and 3/3 sign lines, with mean IoU 0.857, +0.847, and 1.000 against the verified boxes. That structural match was not +sufficient: reusing the annotation crop padding changed recognition decisions, +reduced preserved dark-poster lines from 7 to 4, and expanded its edited +fraction from 5.7% to 11.2%. + +A recognition-only sweep isolated the crop sensitivity. Limiting vertical +padding around detector boxes to 8-12% of line height reproduced the verified- +box decision vectors on both posters; 10% was used for a full follow-up. It kept +8/20 and 7/20 lines with edited fractions of 4.1% and 5.7%, and visual inspection +found no double glyphs. Whole-image LPIPS remained 0.113/0.104, but detector CER +was 0.127/0.154 instead of 0.123/0.119. The dark regression failed the fidelity +gate, so these hashes were not submitted to the provider oracle. The opt-in flag +remains only to reproduce the negative result. Count, IoU, and matching decision +vectors are therefore insufficient gates for automatic boxes; the next design +needs box rectification or recognition stability under crop jitter before it +can replace verified geometry. + +Removing verified strings was tested separately with an annotation-seed dry +run. It detects boxes, chooses `en`, `ru`, or `ch` from Unicode script, and +accepts a draft only when three crop paddings normalize identically and every +confidence is at least 0.85. One execution proposed 20 and 18 poster lines, but +exact-text precision against the held-out annotations was only 90.0% and 94.4%. +The stable errors were punctuation: one lost English comma and an ideographic +comma consistently replaced by ASCII. Thus crop agreement and confidence do +not verify literal text. The helper is retained only to seed annotations; every +proposal needs manual review before it can enter the restoration compositor. diff --git a/scripts/_text_eval.py b/scripts/_text_eval.py new file mode 100644 index 0000000..2b044d8 --- /dev/null +++ b/scripts/_text_eval.py @@ -0,0 +1,34 @@ +"""Pure text-normalization helpers shared by evaluation scripts.""" + +from __future__ import annotations + +import unicodedata + + +def normalize_text(text: str) -> str: + """Normalize text for layout-independent evaluation comparisons.""" + return "".join(unicodedata.normalize("NFC", text).casefold().split()) + + +def levenshtein_normalized(left: str, right: str) -> float: + """Return normalized Levenshtein distance without changing either input.""" + if not left and not right: + return 0.0 + previous = list(range(len(right) + 1)) + for left_index, left_character in enumerate(left, start=1): + current = [left_index] + for right_index, right_character in enumerate(right, start=1): + current.append( + min( + current[-1] + 1, + previous[right_index] + 1, + previous[right_index - 1] + (left_character != right_character), + ) + ) + previous = current + return previous[-1] / max(len(left), len(right)) + + +def normalized_edit_distance(left: str, right: str) -> float: + """Normalize text, then return Levenshtein distance over the result.""" + return levenshtein_normalized(normalize_text(left), normalize_text(right)) diff --git a/scripts/fidelity_metrics.py b/scripts/fidelity_metrics.py index 56db610..21f2a42 100644 --- a/scripts/fidelity_metrics.py +++ b/scripts/fidelity_metrics.py @@ -3,10 +3,9 @@ # dependencies = [ # "click", # "numpy", -# "opencv-python-headless", +# "opencv-python-headless<5", # "pillow", # "scikit-image", -# "rapidfuzz", # "torch", # "lpips", # "paddleocr", @@ -29,7 +28,7 @@ metrics run only where faces are detected, text metrics only where text is. Two subcommands: - ocr -- OCR images (PaddleOCR PP-OCRv6) into a JSON {basename: text} file. + ocr -- OCR images (PaddleOCR defaults) into a JSON {basename: text} file. Run this on the ORIGINALS, hand-verify/correct the file, and it becomes the ground truth for ``compare --ground-truth`` -- the clean way to score text, since OCR-vs-OCR is doubly noisy (errors on both @@ -54,6 +53,7 @@ Usage: from __future__ import annotations import json +import sys import unicodedata from dataclasses import dataclass, field from pathlib import Path @@ -65,7 +65,10 @@ import numpy as np from _plain_console import Console, Table console = Console() +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT)) +from scripts._text_eval import levenshtein_normalized # noqa: E402 # ── helpers ────────────────────────────────────────────────────────── @@ -89,7 +92,7 @@ def _norm(text: str) -> str: return "".join(unicodedata.normalize("NFC", text).split()) -# ── text: PaddleOCR (PP-OCRv6) ─────────────────────────────────────── +# ── text: PaddleOCR defaults ───────────────────────────────────────── # Our lang codes -> PaddleOCR lang. The 'ch' model also reads Latin; 'ru' reads # Cyrillic + Latin. Multiple langs in one image -> run each model, union detections. @@ -165,9 +168,7 @@ def _ocr_lines(bgr: np.ndarray, langs: list[str], min_score: float = 0.5) -> lis def _cer(ref: str, hyp: str) -> float: - from rapidfuzz.distance import Levenshtein - - return Levenshtein.normalized_distance(_norm(ref), _norm(hyp)) + return levenshtein_normalized(_norm(ref), _norm(hyp)) # ── face: detection + ArcFace + texture ────────────────────────────── diff --git a/scripts/infer_text_lines.py b/scripts/infer_text_lines.py new file mode 100644 index 0000000..2c6c6c9 --- /dev/null +++ b/scripts/infer_text_lines.py @@ -0,0 +1,128 @@ +# /// script +# requires-python = ">=3.11" +# dependencies = [ +# "click", +# "huggingface-hub>=0.20.0", +# "numpy", +# "onnxruntime>=1.24.0", +# "opencv-python-headless<5", +# "paddleocr>=3.3.3", +# "paddlepaddle", +# "pillow", +# ] +# /// +"""Infer stable source-text lines without modifying an image. + +This evaluation-only dry run proposes line annotations for selective text +restoration. Every proposal still needs human verification: stable OCR can lose +punctuation with high confidence. It separately flags lines whose recognition +changes under crop jitter or whose minimum confidence is below the threshold. +""" + +from __future__ import annotations + +import importlib.util +import json +import logging +import os +import sys +import unicodedata +from pathlib import Path +from typing import Any + +import click +import numpy as np +from PIL import Image + +log = logging.getLogger(__name__) +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT)) +RESTORATION_SCRIPT = ROOT / "scripts/selective_text_restoration.py" + +from scripts._text_eval import normalize_text # noqa: E402 + + +def _load_restoration_module() -> Any: + spec = importlib.util.spec_from_file_location("selective_text_restoration_for_inference", RESTORATION_SCRIPT) + if spec is None or spec.loader is None: + raise RuntimeError(f"unable to load {RESTORATION_SCRIPT}") + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + spec.loader.exec_module(module) + return module + + +def _has_script(text: str, script: str) -> bool: + return any(script in unicodedata.name(character, "") for character in text) + + +def choose_language(probes: dict[str, tuple[str, float]]) -> str: + if _has_script(probes["ch"][0], "CJK"): + return "ch" + if _has_script(probes["ru"][0], "CYRILLIC"): + return "ru" + return "en" + + +def stable_recognition(reads: list[tuple[str, float]], min_score: float = 0.85) -> str | None: + normalized = {normalize_text(text) for text, _score in reads} + if len(normalized) != 1 or min(score for _text, score in reads) < min_score: + return None + return reads[0][0] + + +@click.command() +@click.argument("source", type=click.Path(exists=True, dir_okay=False, path_type=Path)) +@click.option("--out", required=True, type=click.Path(dir_okay=False, path_type=Path)) +@click.option("--min-score", default=0.85, show_default=True, type=click.FloatRange(0.0, 1.0)) +def main(source: Path, out: Path, min_score: float) -> None: + """Write draft line text for SOURCE; manually verify every proposal.""" + logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s") + os.environ["PADDLE_PDX_DISABLE_MODEL_SOURCE_CHECK"] = "True" + from paddleocr import PaddleOCR, TextRecognition + + restoration = _load_restoration_module() + source_rgb = np.asarray(Image.open(source).convert("RGB")) + detector = PaddleOCR( + lang="ch", + use_doc_orientation_classify=False, + use_doc_unwarping=False, + use_textline_orientation=False, + ) + engines = { + "en": TextRecognition(model_name="en_PP-OCRv5_mobile_rec"), + "ru": TextRecognition(model_name="eslav_PP-OCRv5_mobile_rec"), + "ch": TextRecognition(model_name="PP-OCRv5_server_rec"), + } + boxes = restoration.detect_line_boxes(detector, source_rgb) + accepted = [] + rejected = [] + for box in boxes: + probes = {} + for language, engine in engines.items(): + script = "cjk" if language == "ch" else "alphabetic" + line = restoration.TextLine(box, "", script) + probes[language] = restoration._recognize(engine, source_rgb, line, 0.1) + language = choose_language(probes) + script = "cjk" if language == "ch" else "alphabetic" + line = restoration.TextLine(box, "", script) + reads = [restoration._recognize(engines[language], source_rgb, line, ratio) for ratio in (0.08, 0.12, 0.2)] + text = stable_recognition(reads, min_score) + result = { + "box": box, + "script": script, + "language": language, + "reads": [{"text": value, "score": score} for value, score in reads], + } + if text is None: + rejected.append(result) + else: + accepted.append({"box": box, "text": text, "script": script, "min_score": min(score for _, score in reads)}) + payload = {"source": source.name, "accepted": accepted, "rejected": rejected} + out.parent.mkdir(parents=True, exist_ok=True) + out.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8") + log.info("Accepted %s lines and rejected %s uncertain lines", len(accepted), len(rejected)) + + +if __name__ == "__main__": + main() diff --git a/scripts/selective_text_restoration.py b/scripts/selective_text_restoration.py new file mode 100644 index 0000000..80973ca --- /dev/null +++ b/scripts/selective_text_restoration.py @@ -0,0 +1,671 @@ +# /// script +# requires-python = ">=3.11" +# dependencies = [ +# "click", +# "huggingface-hub>=0.20.0", +# "numpy", +# "onnxruntime>=1.24.0", +# "opencv-python-headless<5", +# "paddleocr>=3.3.3", +# "paddlepaddle", +# "pillow", +# ] +# /// +"""Evaluation-only text restoration over a scrubbed image. + +``vae-glyphs`` composites only thresholded glyph-core pixels from a separately +generated VAE reconstruction over a fresh silhouette edge. ``source-glyphs`` +can preserve typeface, layout, color, and antialiasing when +SOURCE is itself a regenerated layer. It must not be treated as safe when SOURCE +is the watermarked original: a provider oracle detected SynthID after that exact +paste-back experiment. ``source-silhouette`` instead transfers only a +thresholded glyph shape, then synthesizes fresh flat-color pixels and +antialiasing. ``rerender`` retains the system-font negative control. This is +not a production stage and does not add PaddleOCR to the package graph. +""" + +from __future__ import annotations + +import json +import logging +import math +import os +import shutil +import sys +import unicodedata +from dataclasses import asdict, dataclass +from pathlib import Path +from typing import Any + +import click +import cv2 +import numpy as np +from PIL import Image, ImageDraw, ImageFont + +log = logging.getLogger(__name__) +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT)) +sys.path.insert(0, str(ROOT / "src")) + +from remove_ai_watermarks import region_eraser # noqa: E402 +from scripts._text_eval import normalize_text, normalized_edit_distance # noqa: E402 + +if ROOT not in Path(region_eraser.__file__).resolve().parents: + raise RuntimeError("selective_text_restoration imported outside the current worktree") + +REGULAR_FONT = Path("/System/Library/Fonts/Supplemental/Arial.ttf") +BOLD_FONT = Path("/System/Library/Fonts/Supplemental/Arial Bold.ttf") +CJK_FONT = Path("/System/Library/Fonts/STHeiti Medium.ttc") + + +@dataclass(frozen=True) +class TextLine: + box: tuple[int, int, int, int] + text: str + script: str + angle: float = 0.0 + + +def should_preserve_line( + expected: str, + source_text: str, + source_score: float, + candidate_text: str, + candidate_score: float, +) -> bool: + if min(source_score, candidate_score) < 0.75: + return False + if normalized_edit_distance(expected, source_text) > 0.25: + return False + return normalize_text(source_text) == normalize_text(candidate_text) + + +def residual_glyph_mask( + background_rgb: np.ndarray, + original_mask: np.ndarray, + box: tuple[int, int, int, int], +) -> np.ndarray: + residual = foreground_mask(background_rgb, box) + residual = cv2.bitwise_and(residual, original_mask) + return cv2.dilate(residual, np.ones((5, 5), np.uint8), iterations=1) + + +def composite_source_glyphs( + source_rgb: np.ndarray, + background_rgb: np.ndarray, + glyph_mask: np.ndarray, + *, + feather: float = 0.7, +) -> np.ndarray: + """Composite exact source pixels inside a glyph mask with an outer feather.""" + return _composite_exact_core( + source_rgb, + background_rgb, + glyph_mask, + feather=feather, + round_output=False, + ) + + +def source_silhouette_mask( + source_rgb: np.ndarray, + box: tuple[int, int, int, int], + angle: float = 0.0, +) -> np.ndarray: + """Recover the thresholded glyph shape without retaining source amplitudes.""" + height, width = source_rgb.shape[:2] + x1, y1, x2, y2 = _clip_box(box, width, height) + gray = cv2.cvtColor(source_rgb[y1:y2, x1:x2], cv2.COLOR_RGB2GRAY) + support = np.ones(gray.shape, dtype=np.uint8) + if angle: + box_width, box_height = x2 - x1, y2 - y1 + theta = math.radians(abs(angle)) + cosine, sine = math.cos(theta), math.sin(theta) + denominator = cosine * cosine - sine * sine + rect_width = (box_width * cosine - box_height * sine) / denominator + rect_height = (box_height * cosine - box_width * sine) / denominator + rotated = cv2.boxPoints( + ( + (box_width / 2, box_height / 2), + (max(1.0, rect_width * 0.92), max(1.0, rect_height * 0.62)), + -angle, + ) + ) + support.fill(0) + cv2.fillConvexPoly(support, np.rint(rotated).astype(np.int32), 1) + values = gray[support > 0] + background_luma = float(np.median(values)) + else: + ring_pad = max(6, min(20, (y2 - y1) // 4)) + rx1, ry1, rx2, ry2 = _clip_box((x1, y1, x2, y2), width, height, pad=ring_pad) + context = cv2.cvtColor(source_rgb[ry1:ry2, rx1:rx2], cv2.COLOR_RGB2GRAY) + ring = np.ones(context.shape, dtype=bool) + ring[y1 - ry1 : y2 - ry1, x1 - rx1 : x2 - rx1] = False + background_luma = float(np.median(context[ring])) if ring.any() else float(np.median(gray)) + values = gray.reshape(-1) + low, high = float(np.percentile(values, 2)), float(np.percentile(values, 98)) + dark_contrast, light_contrast = background_luma - low, high - background_luma + contrast = max(light_contrast, dark_contrast) + threshold = max(16.0, min(56.0, contrast * 0.22)) + if light_contrast > dark_contrast: + crop_mask = (gray.astype(np.float32) >= background_luma + threshold).astype(np.uint8) * 255 + else: + crop_mask = (gray.astype(np.float32) <= background_luma - threshold).astype(np.uint8) * 255 + crop_mask[support == 0] = 0 + result = np.zeros((height, width), dtype=np.uint8) + result[y1:y2, x1:x2] = crop_mask + return result + + +def composite_fresh_silhouette( + background_rgb: np.ndarray, + glyph_mask: np.ndarray, + color: tuple[int, int, int], + *, + feather: float = 0.35, +) -> np.ndarray: + """Render a binary source shape with fresh color and antialiasing.""" + if background_rgb.shape[:2] != glyph_mask.shape: + raise ValueError("background and glyph mask dimensions must match") + antialiased = cv2.GaussianBlur(glyph_mask, (0, 0), feather) if feather > 0 else glyph_mask + alpha = antialiased.astype(np.float32) / 255.0 + alpha = alpha[..., None] + foreground = np.empty_like(background_rgb) + foreground[:, :] = color + combined = foreground.astype(np.float32) * alpha + background_rgb.astype(np.float32) * (1.0 - alpha) + return np.clip(combined, 0, 255).astype(np.uint8) + + +def composite_fresh_text_edges( + source_rgb: np.ndarray, + background_rgb: np.ndarray, + lines: list[TextLine], + masks: list[np.ndarray], +) -> np.ndarray: + """Render fresh antialiased edges for a set of source-derived glyph masks.""" + restored = background_rgb + for line, mask in zip(lines, masks, strict=True): + color = _sample_text_color(source_rgb, mask, line.box) + restored = composite_fresh_silhouette(restored, mask, color) + return restored + + +def composite_reconstructed_glyphs( + donor_rgb: np.ndarray, + background_rgb: np.ndarray, + glyph_mask: np.ndarray, + *, + feather: float = 0.5, +) -> np.ndarray: + """Composite an exact reconstructed core with a narrow donor edge.""" + return _composite_exact_core( + donor_rgb, + background_rgb, + glyph_mask, + feather=feather, + round_output=True, + ) + + +def _composite_exact_core( + foreground_rgb: np.ndarray, + background_rgb: np.ndarray, + glyph_mask: np.ndarray, + *, + feather: float, + round_output: bool, +) -> np.ndarray: + if foreground_rgb.shape != background_rgb.shape or foreground_rgb.shape[:2] != glyph_mask.shape: + raise ValueError("foreground, background, and glyph mask dimensions must match") + blurred = cv2.GaussianBlur(glyph_mask, (0, 0), feather) if feather > 0 else glyph_mask + alpha = np.maximum(glyph_mask, blurred).astype(np.float32) / 255.0 + alpha = alpha[..., None] + combined = foreground_rgb.astype(np.float32) * alpha + background_rgb.astype(np.float32) * (1.0 - alpha) + output = np.rint(combined) if round_output else combined + return np.clip(output, 0, 255).astype(np.uint8) + + +def source_box_mask( + shape: tuple[int, int], + boxes: list[tuple[int, int, int, int]], +) -> np.ndarray: + """Build a padded text-line mask for aligned regenerated layer compositing.""" + height, width = shape + mask = np.zeros((height, width), dtype=np.uint8) + for x1, y1, x2, y2 in boxes: + pad = max(8, (y2 - y1) // 4) + x1, y1, x2, y2 = _clip_box((x1, y1, x2, y2), width, height, pad=pad) + mask[y1:y2, x1:x2] = 255 + return mask + + +def _clip_box(box: tuple[int, int, int, int], width: int, height: int, pad: int = 0) -> tuple[int, int, int, int]: + x1, y1, x2, y2 = box + return max(0, x1 - pad), max(0, y1 - pad), min(width, x2 + pad), min(height, y2 + pad) + + +def foreground_mask(source_rgb: np.ndarray, box: tuple[int, int, int, int]) -> np.ndarray: + height, width = source_rgb.shape[:2] + line_height = box[3] - box[1] + x1, y1, x2, y2 = _clip_box(box, width, height, pad=max(6, int(line_height * 0.12))) + gray = cv2.cvtColor(source_rgb[y1:y2, x1:x2], cv2.COLOR_RGB2GRAY) + ring_pad = max(8, min(24, (y2 - y1) // 5)) + rx1, ry1, rx2, ry2 = _clip_box((x1, y1, x2, y2), width, height, pad=ring_pad) + context = cv2.cvtColor(source_rgb[ry1:ry2, rx1:rx2], cv2.COLOR_RGB2GRAY) + ring = np.ones(context.shape, dtype=bool) + ring[y1 - ry1 : y2 - ry1, x1 - rx1 : x2 - rx1] = False + background_luma = float(np.median(context[ring])) if ring.any() else float(np.median(gray)) + low, high = float(np.percentile(gray, 4)), float(np.percentile(gray, 96)) + dark_contrast, light_contrast = background_luma - low, high - background_luma + contrast = max(light_contrast, dark_contrast) + threshold = max(24.0, min(72.0, contrast * 0.32)) + if light_contrast > dark_contrast: + mask = (gray.astype(np.float32) >= background_luma + threshold).astype(np.uint8) * 255 + else: + mask = (gray.astype(np.float32) <= background_luma - threshold).astype(np.uint8) * 255 + mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, np.ones((2, 2), np.uint8)) + dilation = 5 if line_height >= 48 else 3 + mask = cv2.dilate(mask, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2 * dilation + 1,) * 2)) + result = np.zeros((height, width), dtype=np.uint8) + result[y1:y2, x1:x2] = mask + return result + + +def _line_language(line: TextLine) -> str: + if line.script == "cjk": + return "ch" + return "ru" if any("CYRILLIC" in unicodedata.name(character, "") for character in line.text) else "en" + + +def _recognition_box( + line: TextLine, + width: int, + height: int, + vertical_pad_ratio: float | None = None, +) -> tuple[int, int, int, int]: + x1, y1, x2, y2 = line.box + line_height = y2 - y1 + if line.script == "cjk": + left_pad = max(16, round(line_height * 0.2)) + right_pad = max(16, round(line_height * 0.6)) + return max(0, x1 - left_pad), y1, min(width, x2 + right_pad), y2 + pad_x = max(16, line_height) + pad_y = max(8, line_height // 3) if vertical_pad_ratio is None else max(8, round(line_height * vertical_pad_ratio)) + return max(0, x1 - pad_x), max(0, y1 - pad_y), min(width, x2 + pad_x), min(height, y2 + pad_y) + + +def _recognize( + engine: Any, + image: np.ndarray, + line: TextLine, + vertical_pad_ratio: float | None = None, +) -> tuple[str, float]: + height, width = image.shape[:2] + x1, y1, x2, y2 = _recognition_box(line, width, height, vertical_pad_ratio) + crop = image[y1:y2, x1:x2] + if crop.shape[0] < 64: + scale = 64 / crop.shape[0] + crop = cv2.resize(crop, None, fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC) + result = next(iter(engine.predict(crop))) + return str(result.get("rec_text", "")), float(result.get("rec_score", 0.0)) + + +def _sample_text_color( + source_rgb: np.ndarray, + mask: np.ndarray, + box: tuple[int, int, int, int], +) -> tuple[int, int, int]: + height, width = source_rgb.shape[:2] + x1, y1, x2, y2 = _clip_box(box, width, height, pad=2) + crop = source_rgb[y1:y2, x1:x2] + active = mask[y1:y2, x1:x2] > 0 + pixels = crop[active] + luma = pixels.mean(axis=1) + background_luma = float(crop[[0, -1], :, :].reshape(-1, 3).mean(axis=1).mean()) + if background_luma >= 128: + selected = pixels[luma <= np.percentile(luma, 20)] + else: + selected = pixels[luma >= np.percentile(luma, 80)] + return tuple(int(value) for value in np.median(selected, axis=0)) + + +def _render_line(image: Image.Image, line: TextLine, color: tuple[int, int, int]) -> None: + font_path = CJK_FONT if line.script == "cjk" else (BOLD_FONT if line.box[3] - line.box[1] >= 55 else REGULAR_FONT) + target_width, target_height = line.box[2] - line.box[0], line.box[3] - line.box[1] + draw = ImageDraw.Draw(image) + low, high = 4, max(8, target_height * 2) + font = ImageFont.truetype(str(font_path), low) + while low <= high: + size = (low + high) // 2 + candidate = ImageFont.truetype(str(font_path), size) + bounds = draw.textbbox((0, 0), line.text, font=candidate) + if bounds[2] - bounds[0] <= target_width * 1.03 and bounds[3] - bounds[1] <= target_height * 1.08: + font, low = candidate, size + 1 + else: + high = size - 1 + bounds = draw.textbbox((0, 0), line.text, font=font) + y = line.box[1] + math.floor((target_height - (bounds[3] - bounds[1])) / 2) - bounds[1] + draw.text((line.box[0], y), line.text, fill=color, font=font) + + +def _write_manifest(path: Path | None, payload: dict[str, Any]) -> None: + if path is None: + return + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8") + + +def _groups(lines: list[TextLine]) -> list[list[int]]: + groups: list[list[int]] = [] + for index, line in enumerate(lines): + if not groups: + groups.append([index]) + continue + previous = lines[groups[-1][-1]] + gap = line.box[1] - previous.box[3] + if line.script != previous.script or gap > max(60, int((previous.box[3] - previous.box[1]) * 1.1)): + groups.append([index]) + else: + groups[-1].append(index) + return groups + + +def _vertical_overlap_ratio(left: tuple[int, int, int, int], right: tuple[int, int, int, int]) -> float: + overlap = max(0, min(left[3], right[3]) - max(left[1], right[1])) + return overlap / max(1, min(left[3] - left[1], right[3] - right[1])) + + +def group_word_boxes(boxes: list[tuple[int, int, int, int]]) -> list[tuple[int, int, int, int]]: + groups: list[tuple[int, int, int, int]] = [] + for box in sorted(boxes, key=lambda item: ((item[1] + item[3]) / 2, item[0])): + matches = [] + for index, group in enumerate(groups): + if _vertical_overlap_ratio(box, group) < 0.45: + continue + horizontal_gap = max(0, max(box[0], group[0]) - min(box[2], group[2])) + line_height = min(box[3] - box[1], group[3] - group[1]) + if horizontal_gap <= max(24, line_height * 3): + matches.append(index) + if not matches: + groups.append(box) + continue + index = max(matches, key=lambda item: _vertical_overlap_ratio(box, groups[item])) + x1, y1, x2, y2 = groups[index] + groups[index] = min(x1, box[0]), min(y1, box[1]), max(x2, box[2]), max(y2, box[3]) + return sorted(groups, key=lambda item: ((item[1] + item[3]) / 2, item[0])) + + +def detect_line_boxes( + engine: Any, + source_rgb: np.ndarray, + expected_count: int | None = None, +) -> list[tuple[int, int, int, int]]: + boxes: list[tuple[int, int, int, int]] = [] + for page in engine.predict(source_rgb): + detected = page.get("rec_boxes", None) + if detected is None or len(detected) == 0: + detected = page.get("rec_polys", []) + for score, raw_box in zip(page.get("rec_scores", []), detected, strict=False): + if float(score) < 0.5: + continue + points = np.asarray(raw_box, dtype=np.float32).reshape(-1) + if points.size == 4: + x1, y1, x2, y2 = points + else: + points = points.reshape(-1, 2) + x1, y1 = points.min(axis=0) + x2, y2 = points.max(axis=0) + boxes.append((round(float(x1)), round(float(y1)), round(float(x2)), round(float(y2)))) + lines = group_word_boxes(boxes) + if expected_count is not None and len(lines) != expected_count: + raise click.ClickException(f"detected {len(lines)} source lines; expected exactly {expected_count}") + return lines + + +def _load_lines(path: Path, key: str) -> list[TextLine]: + payload = json.loads(path.read_text(encoding="utf-8")) + return [ + TextLine(tuple(item["box"]), item["text"], item["script"], float(item.get("angle", 0.0))) + for item in payload[key] + ] + + +@click.command() +@click.argument("source", type=click.Path(exists=True, dir_okay=False, path_type=Path)) +@click.argument("candidate", type=click.Path(exists=True, dir_okay=False, path_type=Path)) +@click.option("--output", required=True, type=click.Path(dir_okay=False, path_type=Path)) +@click.option( + "--lines-json", + default=ROOT / "data/evaluations/fidelity/text-lines.json", + type=click.Path(exists=True, dir_okay=False, path_type=Path), +) +@click.option("--source-key", help="Key in lines JSON; defaults to source basename.") +@click.option( + "--detect-boxes", + is_flag=True, + help="Detect source boxes; fail unless their count matches verified lines.", +) +@click.option( + "--restoration", + type=click.Choice(("vae-glyphs", "source-glyphs", "source-silhouette", "rerender")), + required=True, + help="Choose VAE glyph cores, regenerated pixels, fresh source shapes, or the system-font control.", +) +@click.option( + "--glyph-donor", + type=click.Path(exists=True, dir_okay=False, path_type=Path), + help="VAE reconstruction used only by --restoration vae-glyphs.", +) +@click.option( + "--glyph-feather", + type=click.FloatRange(min=0.0), + default=0.5, + show_default=True, + help="Outer donor-edge feather used only by --restoration vae-glyphs.", +) +@click.option( + "--selection", + type=click.Choice(("all", "changed")), + default="all", + show_default=True, + help="Restore every verified line or only OCR-confirmed changes.", +) +@click.option( + "--erase-background/--keep-background", + default=True, + show_default=True, + help="Erase candidate glyphs before compositing, or directly blend an aligned regenerated glyph layer.", +) +@click.option( + "--composite-mask", + type=click.Choice(("glyphs", "boxes")), + default="glyphs", + show_default=True, + help="Composite isolated glyphs or complete aligned text-line boxes.", +) +@click.option("--manifest", type=click.Path(dir_okay=False, path_type=Path)) +def main( + source: Path, + candidate: Path, + output: Path, + lines_json: Path, + source_key: str | None, + detect_boxes: bool, + restoration: str, + glyph_donor: Path | None, + glyph_feather: float, + selection: str, + erase_background: bool, + composite_mask: str, + manifest: Path | None, +) -> None: + """Restore SOURCE text over the scrubbed CANDIDATE.""" + logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s") + if restoration == "rerender": + for font in (REGULAR_FONT, BOLD_FONT, CJK_FONT): + if not font.exists(): + raise click.ClickException(f"required evaluation font is unavailable: {font}") + if restoration == "vae-glyphs" and glyph_donor is None: + raise click.ClickException("--glyph-donor is required for --restoration vae-glyphs") + if restoration != "vae-glyphs" and glyph_donor is not None: + raise click.ClickException("--glyph-donor is only valid with --restoration vae-glyphs") + os.environ["PADDLE_PDX_DISABLE_MODEL_SOURCE_CHECK"] = "True" + source_rgb = np.asarray(Image.open(source).convert("RGB")) + candidate_rgb = np.asarray(Image.open(candidate).convert("RGB")) + if candidate_rgb.shape != source_rgb.shape: + raise click.ClickException("source and candidate dimensions must match") + donor_rgb = np.asarray(Image.open(glyph_donor).convert("RGB")) if glyph_donor else None + if donor_rgb is not None and donor_rgb.shape != source_rgb.shape: + raise click.ClickException("source and glyph donor dimensions must match") + lines = _load_lines(lines_json, source_key or source.name) + annotation_boxes = [line.box for line in lines] + if detect_boxes: + from paddleocr import PaddleOCR + + page_engine = PaddleOCR( + lang="ch", + use_doc_orientation_classify=False, + use_doc_unwarping=False, + use_textline_orientation=False, + ) + boxes = detect_line_boxes(page_engine, source_rgb, len(lines)) + lines = [TextLine(box, line.text, line.script) for line, box in zip(lines, boxes, strict=True)] + vertical_pad_ratio = 0.1 if detect_boxes else None + decisions = [] + selected = list(lines) + if selection == "all": + decisions = [{"line": asdict(line), "selected": True, "reason": "all-lines"} for line in lines] + else: + from paddleocr import TextRecognition + + engines = { + "en": TextRecognition(model_name="en_PP-OCRv5_mobile_rec"), + "ru": TextRecognition(model_name="eslav_PP-OCRv5_mobile_rec"), + "ch": TextRecognition(model_name="PP-OCRv5_server_rec"), + } + selected = [] + for line in lines: + language = _line_language(line) + source_text, source_score = _recognize(engines[language], source_rgb, line, vertical_pad_ratio) + candidate_text, candidate_score = _recognize(engines[language], candidate_rgb, line, vertical_pad_ratio) + preserve = should_preserve_line(line.text, source_text, source_score, candidate_text, candidate_score) + decisions.append( + { + "line": asdict(line), + "source_text": source_text, + "source_score": source_score, + "candidate_text": candidate_text, + "candidate_score": candidate_score, + "preserve": preserve, + "selected": not preserve, + } + ) + if not preserve: + selected.append(line) + output.parent.mkdir(parents=True, exist_ok=True) + mask_path = output.with_name(output.stem + "_mask.png") + manifest_common = { + "source": source.name, + "candidate": candidate.name, + "output": output.name, + "mask": mask_path.name, + "glyph_donor": glyph_donor.name if glyph_donor else None, + "glyph_feather": glyph_feather if restoration == "vae-glyphs" else None, + "restoration": restoration, + "selection": selection, + "erase_background": erase_background, + "composite_mask": composite_mask, + "box_source": "detector" if detect_boxes else "verified_annotations", + "annotation_boxes": annotation_boxes, + "decisions": decisions, + } + if not selected: + combined = np.zeros(source_rgb.shape[:2], dtype=np.uint8) + shutil.copyfile(candidate, output) + Image.fromarray(combined).save(mask_path) + payload = { + **manifest_common, + "mask_fraction": 0.0, + "source_glyph_fraction": 0.0, + "source_layer_fraction": 0.0, + } + _write_manifest(manifest, payload) + log.info("Copied %s unchanged because every line passed", output) + return + if restoration in {"source-silhouette", "vae-glyphs"}: + source_masks = [source_silhouette_mask(source_rgb, line.box, line.angle) for line in selected] + candidate_masks = [source_silhouette_mask(candidate_rgb, line.box, line.angle) for line in selected] + line_masks = [] + for line, source_mask, candidate_mask in zip(selected, source_masks, candidate_masks, strict=True): + radius = 5 if line.box[3] - line.box[1] >= 48 else 3 + kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2 * radius + 1,) * 2) + line_masks.append(cv2.dilate(np.maximum(source_mask, candidate_mask), kernel)) + masks = line_masks + else: + source_masks = [foreground_mask(source_rgb, line.box) for line in selected] + candidate_masks = [foreground_mask(candidate_rgb, line.box) for line in selected] + masks = [np.maximum(left, right) for left, right in zip(source_masks, candidate_masks, strict=True)] + del candidate_masks + groups = _groups(selected) + if erase_background: + background = cv2.cvtColor(candidate_rgb, cv2.COLOR_RGB2BGR) + for group in groups: + background = region_eraser.erase_lama( + background, + np.maximum.reduce([masks[index] for index in group]), + ) + background_rgb = cv2.cvtColor(background, cv2.COLOR_BGR2RGB) + residual_masks = [ + residual_glyph_mask(background_rgb, mask, line.box) for line, mask in zip(selected, masks, strict=True) + ] + for group in groups: + residual = np.maximum.reduce([residual_masks[index] for index in group]) + if np.any(residual): + background = region_eraser.erase_lama(background, residual) + background_rgb = cv2.cvtColor(background, cv2.COLOR_BGR2RGB) + else: + background_rgb = candidate_rgb + residual_masks = [] + source_glyph_mask = np.maximum.reduce(source_masks) + source_layer_mask = source_glyph_mask + if restoration == "source-glyphs": + source_layer_mask = ( + source_box_mask(source_rgb.shape[:2], [line.box for line in selected]) + if composite_mask == "boxes" + else source_glyph_mask + ) + restored = composite_source_glyphs(source_rgb, background_rgb, source_layer_mask, feather=3.0) + Image.fromarray(restored).save(output) + elif restoration in {"source-silhouette", "vae-glyphs"}: + restored = composite_fresh_text_edges(source_rgb, background_rgb, selected, source_masks) + if restoration == "vae-glyphs": + if donor_rgb is None: + raise RuntimeError("VAE glyph restoration requires a loaded donor") + restored = composite_reconstructed_glyphs( + donor_rgb, + restored, + source_layer_mask, + feather=glyph_feather, + ) + Image.fromarray(restored).save(output) + else: + rendered = Image.fromarray(background_rgb) + for line, source_mask in zip(selected, source_masks, strict=True): + _render_line(rendered, line, _sample_text_color(source_rgb, source_mask, line.box)) + rendered.save(output) + combined = np.maximum.reduce([*masks, *residual_masks]) + if restoration == "source-glyphs" and not erase_background: + combined = source_layer_mask + Image.fromarray(combined).save(mask_path) + payload = { + **manifest_common, + "mask_fraction": float((combined > 0).mean()), + "source_glyph_fraction": float((source_glyph_mask > 0).mean()), + "source_layer_fraction": float((source_layer_mask > 0).mean()), + } + _write_manifest(manifest, payload) + log.info("Wrote %s with %.4f edited fraction", output, payload["mask_fraction"]) + + +if __name__ == "__main__": + main() diff --git a/tests/test_fidelity_matching.py b/tests/test_fidelity_matching.py index 4c369cb..a0f1d6e 100644 --- a/tests/test_fidelity_matching.py +++ b/tests/test_fidelity_matching.py @@ -20,7 +20,7 @@ import pytest _SCRIPTS = Path(__file__).resolve().parent.parent / "scripts" -def _load_assign(): +def _load_module(): # fidelity_metrics is a standalone PEP723 script, not an installed module; load it by # path with scripts/ on sys.path so its `_plain_console` shim import resolves. sys.path.insert(0, str(_SCRIPTS)) @@ -35,7 +35,15 @@ def _load_assign(): pytest.skip(f"fidelity_metrics import deps missing: {exc}") finally: sys.path.remove(str(_SCRIPTS)) - return mod.assign_faces_one_to_one + return mod + + +def _load_assign(): + return _load_module().assign_faces_one_to_one + + +def test_cer_remains_case_sensitive() -> None: + assert _load_module()._cer("A", "a") == 1.0 def test_distinct_faces_match_nearest() -> None: diff --git a/tests/test_infer_text_lines.py b/tests/test_infer_text_lines.py new file mode 100644 index 0000000..89700ed --- /dev/null +++ b/tests/test_infer_text_lines.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +import importlib.util +import sys +from pathlib import Path + +SCRIPT = Path(__file__).parents[1] / "scripts/infer_text_lines.py" +SPEC = importlib.util.spec_from_file_location("infer_text_lines", SCRIPT) +assert SPEC is not None +assert SPEC.loader is not None +module = importlib.util.module_from_spec(SPEC) +sys.modules[SPEC.name] = module +SPEC.loader.exec_module(module) + + +def test_choose_language_prefers_observed_unicode_script() -> None: + probes = {"en": ("gibberish", 0.9), "ru": ("пример", 0.9), "ch": ("example", 0.9)} + assert module.choose_language(probes) == "ru" + + probes["ch"] = ("示例", 0.9) + assert module.choose_language(probes) == "ch" + + +def test_stable_recognition_requires_agreement_and_confidence() -> None: + assert module.stable_recognition([("Sample text", 0.9), ("sample text", 0.95)]) == "Sample text" + assert module.stable_recognition([("Sample", 0.9), ("Simple", 0.95)]) is None + assert module.stable_recognition([("Sample", 0.8), ("Sample", 0.95)]) is None diff --git a/tests/test_selective_text_restoration.py b/tests/test_selective_text_restoration.py new file mode 100644 index 0000000..ce30582 --- /dev/null +++ b/tests/test_selective_text_restoration.py @@ -0,0 +1,177 @@ +from __future__ import annotations + +import importlib.util +import json +import sys +from pathlib import Path + +import numpy as np +import pytest + +SCRIPT = Path(__file__).parents[1] / "scripts" / "selective_text_restoration.py" +SPEC = importlib.util.spec_from_file_location("selective_text_restoration", SCRIPT) +assert SPEC is not None +assert SPEC.loader is not None +module = importlib.util.module_from_spec(SPEC) +sys.modules[SPEC.name] = module +SPEC.loader.exec_module(module) + + +def test_normalized_edit_distance_ignores_case_and_whitespace() -> None: + assert module.normalized_edit_distance(" Sample text ", "sample\ntext") == 0.0 + + +def test_preserve_requires_source_candidate_agreement() -> None: + assert module.should_preserve_line("clean text", "clean text", 0.9, "clean text", 0.9) + assert not module.should_preserve_line("clean text", "clean text", 0.9, "damaged", 0.9) + + +def test_preserve_rejects_unreliable_source_recognition() -> None: + assert not module.should_preserve_line("expected", "unrelated", 0.9, "unrelated", 0.9) + assert not module.should_preserve_line("expected", "expected", 0.7, "expected", 0.9) + + +def test_cjk_recognition_box_excludes_overlapping_neighbor_lines() -> None: + line = module.TextLine((1281, 650, 2357, 818), "每天都是一个新的机会。", "cjk") + + assert module._recognition_box(line, 2816, 1536) == (1247, 650, 2458, 818) + + +def test_latin_recognition_box_keeps_context_padding() -> None: + line = module.TextLine((100, 200, 300, 260), "Sample text", "latin") + + assert module._recognition_box(line, 1000, 1000) == (40, 180, 360, 280) + assert module._recognition_box(line, 1000, 1000, 0.1) == (40, 192, 360, 268) + + +def test_verified_lines_cover_each_ground_truth_string() -> None: + root = Path(__file__).parents[1] + lines = json.loads((root / "data/evaluations/fidelity/text-lines.json").read_text(encoding="utf-8")) + ground_truth = json.loads((root / "data/evaluations/fidelity/ground-truth.json").read_text(encoding="utf-8")) + + assert lines.keys() == ground_truth.keys() + for source, expected in ground_truth.items(): + observed = " ".join(line["text"] for line in lines[source]) + assert module.normalize_text(observed) == module.normalize_text(expected) + + +def test_group_word_boxes_merges_words_but_not_neighboring_lines() -> None: + boxes = [(10, 10, 30, 30), (32, 12, 60, 29), (10, 35, 50, 55)] + + assert module.group_word_boxes(boxes) == [(10, 10, 60, 30), (10, 35, 50, 55)] + + +def test_group_word_boxes_does_not_merge_distant_columns() -> None: + boxes = [(10, 10, 60, 30), (500, 11, 560, 31)] + + assert module.group_word_boxes(boxes) == boxes + + +def test_source_glyph_composite_keeps_masked_pixels_exact() -> None: + source = np.zeros((9, 9, 3), dtype=np.uint8) + source[:, :] = (220, 180, 40) + background = np.zeros((9, 9, 3), dtype=np.uint8) + background[:, :] = (10, 20, 30) + mask = np.zeros((9, 9), dtype=np.uint8) + mask[3:6, 3:6] = 255 + + result = module.composite_source_glyphs(source, background, mask, feather=0.7) + + np.testing.assert_array_equal(result[3:6, 3:6], source[3:6, 3:6]) + np.testing.assert_array_equal(result[0, 0], background[0, 0]) + + +def test_fresh_silhouette_uses_new_color_instead_of_source_pixels() -> None: + background = np.zeros((9, 9, 3), dtype=np.uint8) + background[:, :] = (10, 20, 30) + mask = np.zeros((9, 9), dtype=np.uint8) + mask[3:6, 3:6] = 255 + + result = module.composite_fresh_silhouette(background, mask, (220, 180, 40), feather=0) + + assert np.all(result[3:6, 3:6] == (220, 180, 40)) + np.testing.assert_array_equal(result[0, 0], background[0, 0]) + + +def test_fresh_silhouette_antialiasing_softens_binary_edges() -> None: + background = np.zeros((9, 9, 3), dtype=np.uint8) + background[:, :] = (10, 20, 30) + mask = np.zeros((9, 9), dtype=np.uint8) + mask[3:6, 3:6] = 255 + + result = module.composite_fresh_silhouette(background, mask, (220, 180, 40), feather=1.0) + + assert np.all(result[3, 3] > background[3, 3]) + assert np.all(result[3, 3] < (220, 180, 40)) + + +def test_reconstructed_glyphs_keep_exact_donor_core_and_fresh_edge() -> None: + donor = np.zeros((9, 9, 3), dtype=np.uint8) + donor[:, :] = (180, 140, 60) + background = np.zeros((9, 9, 3), dtype=np.uint8) + background[:, :] = (10, 20, 30) + mask = np.zeros((9, 9), dtype=np.uint8) + mask[3:6, 3:6] = 255 + + fresh_edge = module.composite_fresh_silhouette(background, mask, (220, 180, 40)) + result = module.composite_reconstructed_glyphs(donor, fresh_edge, mask, feather=0.5) + + np.testing.assert_array_equal(result[3:6, 3:6], donor[3:6, 3:6]) + assert np.any(result[2, 3] != fresh_edge[2, 3]) + np.testing.assert_array_equal(result[0, 0], background[0, 0]) + + +def test_source_silhouette_discards_foreground_amplitudes() -> None: + source = np.full((15, 15, 3), 20, dtype=np.uint8) + source[5:10, 6:9] = 230 + source[6:9, 7] = 180 + + mask = module.source_silhouette_mask(source, (4, 4, 11, 11)) + + assert mask.dtype == np.uint8 + assert set(np.unique(mask)) <= {0, 255} + assert mask[7, 7] == 255 + assert mask[4, 4] == 0 + + +def test_rotated_source_silhouette_excludes_axis_aligned_corners() -> None: + source = np.full((80, 160, 3), 20, dtype=np.uint8) + source[10:70, 10:150] = 230 + + mask = module.source_silhouette_mask(source, (0, 0, 160, 80), angle=12) + + assert mask[0, 0] == 0 + assert mask[79, 159] == 0 + + +def test_source_box_mask_pads_and_clips_boxes() -> None: + mask = module.source_box_mask((20, 30), [(1, 2, 11, 10), (25, 15, 30, 20)]) + + assert mask.shape == (20, 30) + assert mask[0, 0] == 255 + assert mask[19, 29] == 255 + assert mask[0, 22] == 0 + + +def test_detect_line_boxes_fails_closed_on_count_mismatch() -> None: + class Engine: + def predict(self, _image): + return [{"rec_scores": [0.9], "rec_boxes": [[10, 10, 30, 30]]}] + + with pytest.raises(module.click.ClickException, match="detected 1 source lines; expected exactly 2"): + module.detect_line_boxes(Engine(), np.zeros((50, 50, 3), dtype=np.uint8), expected_count=2) + + +def test_residual_mask_is_limited_to_original_glyph_positions(monkeypatch) -> None: + background = np.zeros((8, 8, 3), dtype=np.uint8) + original = np.zeros((8, 8), dtype=np.uint8) + original[3, 3] = 255 + detected = np.zeros((8, 8), dtype=np.uint8) + detected[3, 3] = 255 + detected[6, 6] = 255 + monkeypatch.setattr(module, "foreground_mask", lambda _image, _box: detected) + + residual = module.residual_glyph_mask(background, original, (0, 0, 8, 8)) + + assert residual[3, 3] == 255 + assert residual[6, 6] == 0 From 432b63b6d7d34a5811fd87b3c4e4aca92fced6c5 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Sat, 15 Aug 2026 12:20:16 -0700 Subject: [PATCH 16/44] Add verified text restoration --- README.md | 13 + data/evaluations/fidelity/README.md | 10 + docs/cli.md | 31 ++ docs/installation.md | 12 + docs/known-limitations.md | 21 +- docs/module-internals.md | 23 ++ docs/python-api.md | 17 + pyproject.toml | 7 +- scripts/selective_text_restoration.py | 171 +-------- .../_internal/qwen_zimage_pipeline.py | 65 +++- .../_internal/text_restoration.py | 353 ++++++++++++++++++ .../_internal/watermark_remover.py | 8 + src/remove_ai_watermarks/api.py | 2 + src/remove_ai_watermarks/cli.py | 48 ++- src/remove_ai_watermarks/invisible_engine.py | 30 ++ tests/test_api.py | 1 + tests/test_invisible_engine.py | 70 ++++ tests/test_qwen_zimage_pipeline.py | 73 ++++ tests/test_selective_text_restoration.py | 12 +- tests/test_text_restoration.py | 128 +++++++ uv.lock | 134 ++++--- 21 files changed, 974 insertions(+), 255 deletions(-) create mode 100644 src/remove_ai_watermarks/_internal/text_restoration.py create mode 100644 tests/test_text_restoration.py diff --git a/README.md b/README.md index 477e79f..009b2b5 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,19 @@ image came from an AI generator, add `--force`: remove-ai-watermarks invisible image.png -o clean.png --force ``` +Typography-heavy images can opt into the experimental verified-text post-pass. +It requires manually reviewed strings and line boxes; it never trusts OCR as ground +truth or runs automatically: + +```bash +uv tool install --force "remove-ai-watermarks[text-restoration]" +remove-ai-watermarks invisible image.png -o clean.png \ + --text-manifest verified-lines.json --force +``` + +See the [CLI guide](docs/cli.md#restore-operator-verified-text) for the manifest +schema, compatibility restrictions, and oracle caveats. + See the [installation guide](docs/installation.md) for Homebrew, uv, optional features, and development setup. diff --git a/data/evaluations/fidelity/README.md b/data/evaluations/fidelity/README.md index 73e1af7..497b4bd 100644 --- a/data/evaluations/fidelity/README.md +++ b/data/evaluations/fidelity/README.md @@ -250,6 +250,16 @@ Laplacian variance. The tracked script reproduced the feathered file byte for byte. These two exact-byte verdicts do not certify other images or the larger matrix masks, and the global smoothing fails a strict unchanged-image criterion. +The opt-in production port was rechecked separately on 2026-08-15. Its current +LaMa runtime did not reproduce the earlier evaluation PNG byte for byte, but all +changed pixels were confined to the erased background outside the donor glyph +core. The exact production artifact returned `No OpenAI signals detected` in +3/3 OpenAI Verify runs, while the matched source control returned `Generated +with OpenAI tools` in 2/2 runs in the same Chrome session; expanded details +identified SynthID and no C2PA manifest on the control. The private control and +artifact hashes remain outside the public repository. This certifies only that +runtime, verified manifest, and output, not arbitrary text masks or images. + The Google result is negative. On the synthetic CJK sign case, two separate work-account runs both detected SynthID in the resaved source control and in the exact Qwen-VAE donor output. The candidate improved mean text-box SSIM from diff --git a/docs/cli.md b/docs/cli.md index e296578..116184c 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -398,6 +398,37 @@ schedule, CFG 1.0 and CUDA, so every one of those flags existed only to be refus several layers down. They are not parsed at all now, which fails at the point the user can act on rather than after a model load. +### Restore operator-verified text + +`--text-manifest` enables the experimental `vae-glyphs` post-pass. It reconstructs +the source with the Qwen VAE, blends 15% of that reconstruction into the normal +`qwen-zimage` result, erases the annotated candidate glyphs with LaMa, and composites +only the reconstructed glyph cores through source-derived silhouettes. It does not +run OCR or choose which strings are correct. + +Install the combined extra and run only with a manually reviewed manifest: + +```bash +uv tool install --force "remove-ai-watermarks[text-restoration]" +remove-ai-watermarks invisible image.png -o clean.png \ + --pipeline qwen-zimage --text-manifest verified-lines.json --force +``` + +The manifest is a JSON object with `schema_version: 1`, `verified: true`, decoded +RGB dimensions, `source_pixel_sha256`, and a non-empty `lines` array. Each line has +an integer `[x1, y1, x2, y2]` box, exact `text`, a non-empty `script`, and an optional +angle from -30 to 30 degrees. Lines must be in top-to-bottom, left-to-right order. +The hash binds the annotations to decoded RGB geometry and pixels, so metadata-only +container changes remain valid while a resized or edited source fails closed. The +experimental helper +`remove_ai_watermarks._internal.text_restoration.source_pixel_sha256` computes it. + +This mode is supported only by `qwen-zimage` at native untiled geometry with +`humanize=0`, `unsharp=0`, and adaptive polish disabled. `all` also accepts the flag, +but its manifest must match the pixels entering the invisible stage; if visible-mark +removal changes those pixels, the hash check rejects the run. One oracle verdict does +not certify another manifest, seed, model/runtime version, or output hash. + ### Work with limited memory Lower CUDA memory pressure: diff --git a/docs/installation.md b/docs/installation.md index dcca6ef..c249ef8 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -87,6 +87,15 @@ removal, metadata stripping and every `identify` command still run anywhere. Video SynthID regeneration is a separate VAE path and does still run on CPU or MPS; it needs the `diffusion` extra, not this one. +The experimental verified-text post-pass additionally needs LaMa: + +```bash +uv tool install --force "remove-ai-watermarks[text-restoration]" +``` + +That extra includes `qwen-zimage` and `lama`; it does not add OCR. Text strings and +line boxes must be reviewed before the run. + ## Feature extras Extras are composable. Install only the capabilities and file formats the @@ -104,6 +113,7 @@ application actually uses: | `migan` | MI-GAN ONNX fill backend | `visible`, ONNX Runtime | Model download, no Torch | | `lama` | big-LaMa ONNX fill backend | `visible`, ONNX Runtime | Model download, no Torch | | `qwen-zimage` | Invisible image-watermark removal, both CUDA-only profiles | `diffusion`, DiffSynth | Yes | +| `text-restoration` | Opt-in verified Qwen-VAE glyph restoration | `qwen-zimage`, `lama` | Yes | | `all` | Every production feature available on the active Python | All compatible rows above | Yes | | `dev` | Tests, linting, typing, and upstream parity checks | `video`, `detect`, upstream invisible-watermark | Yes, for parity tests | @@ -118,6 +128,8 @@ flowchart LR migan --> visible lama --> visible qwen["qwen-zimage"] --> diffusion + text["text-restoration"] --> qwen + text --> lama heif trustmark ``` diff --git a/docs/known-limitations.md b/docs/known-limitations.md index bb52aa4..bf66a78 100644 --- a/docs/known-limitations.md +++ b/docs/known-limitations.md @@ -69,15 +69,15 @@ difficult faces. The measurements and their OCR and oracle caveats are tracked in [`data/evaluations/fidelity/`](../data/evaluations/fidelity/README.md). A global Z-Image Turbo prototype preserved text substantially better at low strength, but it has no useful cross-provider operating point and is not a -supported profile. The evaluated text restorers also remain research-only: -fresh-font and silhouette variants visibly changed typography, while the -higher-fidelity `vae-glyphs` route still requires verified strings, line -geometry, a separately generated donor, and an independently clean global -anchor. Automatic OCR and line-box proposals are not reliable enough to remove -those requirements, and the exact oracle results do not establish a general -mask, seed, or provider operating range. Qwen-Image-2.0 is hosted-only and -exposes no equivalent low-strength denoise control. Exact experiments, controls, -and pass rates are kept in +supported profile. Automatic text restorers also remain research-only: +fresh-font and silhouette variants visibly changed typography. The higher-fidelity +`vae-glyphs` route is available only as an experimental opt-in with verified strings +and line geometry. It builds its donor internally but still requires an independently +clean global anchor. Automatic OCR and line-box proposals are not reliable enough to +remove those requirements, and exact oracle results do not establish a general mask, +seed, runtime, or provider operating range. Qwen-Image-2.0 is hosted-only and exposes +no equivalent low-strength denoise control. Exact experiments, controls, and pass +rates are kept in [`text-protection-research.md`](text-protection-research.md) and the [`fidelity` evaluation record](../data/evaluations/fidelity/README.md). @@ -192,7 +192,8 @@ certified at a fixed seed. The live resolver is | `qwen-zimage` | CUDA only, large model stack, and limited broad certification across seeds and content. | | `sdxl-zimage` | CUDA only. Its strength ladder is flat per vendor, not a resolution curve, because flat values are what was measured. | -The evaluated text-restoration prototypes are not optional production stages. +Only manually verified `vae-glyphs` is an optional production stage, and it is +experimental rather than a default. OCR plus LaMa recovered literal poster text but changed fonts and worsened whole-image fidelity. Restricting it to OCR-mismatched lines improved the tradeoff but still left a local shadow on one poster. The published AnyText2 SD1.5 checkpoint diff --git a/docs/module-internals.md b/docs/module-internals.md index cdbeb16..c715df0 100644 --- a/docs/module-internals.md +++ b/docs/module-internals.md @@ -949,6 +949,29 @@ orchestration, YuNet integration, SAM selection, masks, sizing helpers, and pixe compositing are implemented for this runtime. Changing a calibrated model input requires the same provider-oracle and identity evaluation as a model change. +#### Verified text restoration + +[`_internal/text_restoration.py`](../src/remove_ai_watermarks/_internal/text_restoration.py) +implements the opt-in `vae-glyphs` stage. A versioned manifest carries manually +reviewed strings and source-space line boxes, plus a SHA-256 over decoded RGB width, +height, and pixels. Validation happens before model loading. The product never treats +OCR confidence as verification. + +When enabled, `QwenZImagePipeline` reconstructs the source once through its already +loaded Qwen VAE, runs the ordinary global and face stages, blends 15% of the VAE +reconstruction into that clean result, and calls the shared restoration compositor. +The compositor derives binary source and candidate silhouettes, groups nearby lines, +uses LaMa for the initial and residual-glyph erase passes, paints fresh silhouette +edges, then copies the Qwen-VAE core with a 0.5-pixel feather. The evaluation script +imports these same mask and compositing helpers so the two implementations cannot +silently drift. + +The stage is deliberately narrower than the engine: it rejects `sdxl-zimage`, tiles, +resolution caps, humanize, unsharp, and adaptive polish. Those combinations change +geometry or final pixels after the verified layer and have no measured oracle result. +It remains opt-in because annotations are manual and provider verdicts apply only to +the exact tested output hashes, not to the mechanism in general. + A matched stage-isolation check on the 18-face Gemini portrait grid confirms the division of responsibility. The visible-cleaned, metadata-stripped control and the Z-Image face-only output were both SynthID-positive; Qwen global-only and the full diff --git a/docs/python-api.md b/docs/python-api.md index 36efb46..0f7d876 100644 --- a/docs/python-api.md +++ b/docs/python-api.md @@ -596,6 +596,23 @@ engine = InvisibleEngine(pipeline="sdxl-zimage") The `qwen-zimage` extra is required for both profiles: each runs the same DiffSynth Z-Image face stage. +The opt-in verified-text stage uses the same `text_manifest` argument as the CLI: + +```python +engine.remove_watermark( + Path("watermarked.png"), + Path("clean.png"), + text_manifest=Path("verified-lines.json"), +) +``` + +Install `remove-ai-watermarks[text-restoration]`. The manifest schema and safety +constraints are documented in the CLI guide. The engine verifies its decoded RGB +hash before loading the diffusion models and rejects SDXL, tiling, downscaling, and +postprocessing combinations that were not evaluated. `InvisibleOptions` exposes the +same field for `remove_all`; after a visible-stage edit, the manifest must be built +against the staged pixels rather than the pristine source. + `remove_watermark` takes strength, seed, tiling, resolution, and postprocessing controls. It takes no model id, step count or guidance scale, and neither does the constructor: each profile pins its model stack, its per-stage schedule and CFG diff --git a/pyproject.toml b/pyproject.toml index cf59807..cb1d37c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -120,6 +120,11 @@ qwen-zimage = [ "diffsynth>=2.0.17,<3", "torchvision>=0.20.0", ] +# Opt-in verified-text reconstruction over qwen-zimage. LaMa removes the changed +# candidate glyphs before exact Qwen-VAE cores are composited back. +text-restoration = [ + "remove-ai-watermarks[qwen-zimage,lama]", +] # Adobe TrustMark decoder -- the open, keyless watermark behind Adobe Durable # Content Credentials (soft-binding alg ``com.adobe.trustmark.P``). Optional # because it pulls torch and downloads model weights on first use. identify() @@ -161,7 +166,7 @@ dev = [ ] # ``qwen-zimage`` already pulls ``diffusion``; naming both would suggest diffusion is # independently sufficient for a removal, which it is not. -all = ["remove-ai-watermarks[video,heif,detect,trustmark,qwen-zimage,lama,migan]"] +all = ["remove-ai-watermarks[video,heif,detect,trustmark,text-restoration,migan]"] [project.scripts] remove-ai-watermarks = "remove_ai_watermarks.cli:main" diff --git a/scripts/selective_text_restoration.py b/scripts/selective_text_restoration.py index 80973ca..4c8c0fc 100644 --- a/scripts/selective_text_restoration.py +++ b/scripts/selective_text_restoration.py @@ -33,7 +33,7 @@ import os import shutil import sys import unicodedata -from dataclasses import asdict, dataclass +from dataclasses import asdict from pathlib import Path from typing import Any @@ -48,6 +48,14 @@ sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT / "src")) from remove_ai_watermarks import region_eraser # noqa: E402 +from remove_ai_watermarks._internal.text_restoration import VerifiedTextLine as TextLine # noqa: E402 +from remove_ai_watermarks._internal.text_restoration import ( # noqa: E402 + composite_fresh_text_edges, + composite_reconstructed_glyphs, + group_text_lines, + residual_glyph_mask, + source_silhouette_mask, +) from scripts._text_eval import normalize_text, normalized_edit_distance # noqa: E402 if ROOT not in Path(region_eraser.__file__).resolve().parents: @@ -58,14 +66,6 @@ BOLD_FONT = Path("/System/Library/Fonts/Supplemental/Arial Bold.ttf") CJK_FONT = Path("/System/Library/Fonts/STHeiti Medium.ttc") -@dataclass(frozen=True) -class TextLine: - box: tuple[int, int, int, int] - text: str - script: str - angle: float = 0.0 - - def should_preserve_line( expected: str, source_text: str, @@ -80,16 +80,6 @@ def should_preserve_line( return normalize_text(source_text) == normalize_text(candidate_text) -def residual_glyph_mask( - background_rgb: np.ndarray, - original_mask: np.ndarray, - box: tuple[int, int, int, int], -) -> np.ndarray: - residual = foreground_mask(background_rgb, box) - residual = cv2.bitwise_and(residual, original_mask) - return cv2.dilate(residual, np.ones((5, 5), np.uint8), iterations=1) - - def composite_source_glyphs( source_rgb: np.ndarray, background_rgb: np.ndarray, @@ -98,131 +88,13 @@ def composite_source_glyphs( feather: float = 0.7, ) -> np.ndarray: """Composite exact source pixels inside a glyph mask with an outer feather.""" - return _composite_exact_core( - source_rgb, - background_rgb, - glyph_mask, - feather=feather, - round_output=False, - ) - - -def source_silhouette_mask( - source_rgb: np.ndarray, - box: tuple[int, int, int, int], - angle: float = 0.0, -) -> np.ndarray: - """Recover the thresholded glyph shape without retaining source amplitudes.""" - height, width = source_rgb.shape[:2] - x1, y1, x2, y2 = _clip_box(box, width, height) - gray = cv2.cvtColor(source_rgb[y1:y2, x1:x2], cv2.COLOR_RGB2GRAY) - support = np.ones(gray.shape, dtype=np.uint8) - if angle: - box_width, box_height = x2 - x1, y2 - y1 - theta = math.radians(abs(angle)) - cosine, sine = math.cos(theta), math.sin(theta) - denominator = cosine * cosine - sine * sine - rect_width = (box_width * cosine - box_height * sine) / denominator - rect_height = (box_height * cosine - box_width * sine) / denominator - rotated = cv2.boxPoints( - ( - (box_width / 2, box_height / 2), - (max(1.0, rect_width * 0.92), max(1.0, rect_height * 0.62)), - -angle, - ) - ) - support.fill(0) - cv2.fillConvexPoly(support, np.rint(rotated).astype(np.int32), 1) - values = gray[support > 0] - background_luma = float(np.median(values)) - else: - ring_pad = max(6, min(20, (y2 - y1) // 4)) - rx1, ry1, rx2, ry2 = _clip_box((x1, y1, x2, y2), width, height, pad=ring_pad) - context = cv2.cvtColor(source_rgb[ry1:ry2, rx1:rx2], cv2.COLOR_RGB2GRAY) - ring = np.ones(context.shape, dtype=bool) - ring[y1 - ry1 : y2 - ry1, x1 - rx1 : x2 - rx1] = False - background_luma = float(np.median(context[ring])) if ring.any() else float(np.median(gray)) - values = gray.reshape(-1) - low, high = float(np.percentile(values, 2)), float(np.percentile(values, 98)) - dark_contrast, light_contrast = background_luma - low, high - background_luma - contrast = max(light_contrast, dark_contrast) - threshold = max(16.0, min(56.0, contrast * 0.22)) - if light_contrast > dark_contrast: - crop_mask = (gray.astype(np.float32) >= background_luma + threshold).astype(np.uint8) * 255 - else: - crop_mask = (gray.astype(np.float32) <= background_luma - threshold).astype(np.uint8) * 255 - crop_mask[support == 0] = 0 - result = np.zeros((height, width), dtype=np.uint8) - result[y1:y2, x1:x2] = crop_mask - return result - - -def composite_fresh_silhouette( - background_rgb: np.ndarray, - glyph_mask: np.ndarray, - color: tuple[int, int, int], - *, - feather: float = 0.35, -) -> np.ndarray: - """Render a binary source shape with fresh color and antialiasing.""" - if background_rgb.shape[:2] != glyph_mask.shape: - raise ValueError("background and glyph mask dimensions must match") - antialiased = cv2.GaussianBlur(glyph_mask, (0, 0), feather) if feather > 0 else glyph_mask - alpha = antialiased.astype(np.float32) / 255.0 - alpha = alpha[..., None] - foreground = np.empty_like(background_rgb) - foreground[:, :] = color - combined = foreground.astype(np.float32) * alpha + background_rgb.astype(np.float32) * (1.0 - alpha) - return np.clip(combined, 0, 255).astype(np.uint8) - - -def composite_fresh_text_edges( - source_rgb: np.ndarray, - background_rgb: np.ndarray, - lines: list[TextLine], - masks: list[np.ndarray], -) -> np.ndarray: - """Render fresh antialiased edges for a set of source-derived glyph masks.""" - restored = background_rgb - for line, mask in zip(lines, masks, strict=True): - color = _sample_text_color(source_rgb, mask, line.box) - restored = composite_fresh_silhouette(restored, mask, color) - return restored - - -def composite_reconstructed_glyphs( - donor_rgb: np.ndarray, - background_rgb: np.ndarray, - glyph_mask: np.ndarray, - *, - feather: float = 0.5, -) -> np.ndarray: - """Composite an exact reconstructed core with a narrow donor edge.""" - return _composite_exact_core( - donor_rgb, - background_rgb, - glyph_mask, - feather=feather, - round_output=True, - ) - - -def _composite_exact_core( - foreground_rgb: np.ndarray, - background_rgb: np.ndarray, - glyph_mask: np.ndarray, - *, - feather: float, - round_output: bool, -) -> np.ndarray: - if foreground_rgb.shape != background_rgb.shape or foreground_rgb.shape[:2] != glyph_mask.shape: - raise ValueError("foreground, background, and glyph mask dimensions must match") + if source_rgb.shape != background_rgb.shape or source_rgb.shape[:2] != glyph_mask.shape: + raise ValueError("source, background, and glyph mask dimensions must match") blurred = cv2.GaussianBlur(glyph_mask, (0, 0), feather) if feather > 0 else glyph_mask alpha = np.maximum(glyph_mask, blurred).astype(np.float32) / 255.0 alpha = alpha[..., None] - combined = foreground_rgb.astype(np.float32) * alpha + background_rgb.astype(np.float32) * (1.0 - alpha) - output = np.rint(combined) if round_output else combined - return np.clip(output, 0, 255).astype(np.uint8) + combined = source_rgb.astype(np.float32) * alpha + background_rgb.astype(np.float32) * (1.0 - alpha) + return np.clip(combined, 0, 255).astype(np.uint8) def source_box_mask( @@ -355,21 +227,6 @@ def _write_manifest(path: Path | None, payload: dict[str, Any]) -> None: path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8") -def _groups(lines: list[TextLine]) -> list[list[int]]: - groups: list[list[int]] = [] - for index, line in enumerate(lines): - if not groups: - groups.append([index]) - continue - previous = lines[groups[-1][-1]] - gap = line.box[1] - previous.box[3] - if line.script != previous.script or gap > max(60, int((previous.box[3] - previous.box[1]) * 1.1)): - groups.append([index]) - else: - groups[-1].append(index) - return groups - - def _vertical_overlap_ratio(left: tuple[int, int, int, int], right: tuple[int, int, int, int]) -> float: overlap = max(0, min(left[3], right[3]) - max(left[1], right[1])) return overlap / max(1, min(left[3] - left[1], right[3] - right[1])) @@ -606,7 +463,7 @@ def main( candidate_masks = [foreground_mask(candidate_rgb, line.box) for line in selected] masks = [np.maximum(left, right) for left, right in zip(source_masks, candidate_masks, strict=True)] del candidate_masks - groups = _groups(selected) + groups = group_text_lines(selected) if erase_background: background = cv2.cvtColor(candidate_rgb, cv2.COLOR_RGB2BGR) for group in groups: diff --git a/src/remove_ai_watermarks/_internal/qwen_zimage_pipeline.py b/src/remove_ai_watermarks/_internal/qwen_zimage_pipeline.py index 3a9969e..64671a9 100644 --- a/src/remove_ai_watermarks/_internal/qwen_zimage_pipeline.py +++ b/src/remove_ai_watermarks/_internal/qwen_zimage_pipeline.py @@ -30,6 +30,8 @@ from remove_ai_watermarks._internal.watermark_profiles import resolve_seed if TYPE_CHECKING: from collections.abc import Callable + from remove_ai_watermarks._internal.text_restoration import VerifiedTextManifest + log = logging.getLogger(__name__) QWEN_IMAGE_2512_MODEL_ID = "Qwen/Qwen-Image-2512" @@ -973,6 +975,30 @@ class QwenZImagePipeline: result = result.resize(image.size, Image.Resampling.LANCZOS) return result.convert("RGB") + def _qwen_vae_roundtrip(self, image: Image.Image) -> Image.Image: + """Reconstruct source pixels through the already loaded Qwen VAE.""" + import torch + + pipe, _controlnet_input_cls = self._load_qwen() + source_width, source_height = image.size + pad_width = (-source_width) % 8 + pad_height = (-source_height) % 8 + padded = image.convert("RGB") + if pad_width or pad_height: + padded = Image.fromarray( + np.pad( + np.asarray(padded), + ((0, pad_height), (0, pad_width), (0, 0)), + mode="edge", + ) + ) + pipe.load_models_to_device(["vae"]) + tensor = pipe.preprocess_image(padded).to(device=self.device, dtype=self.torch_dtype) + with torch.inference_mode(): + latents = pipe.vae.encode(tensor) + decoded = pipe.vae.decode(latents) + return pipe.vae_output_to_image(decoded).crop((0, 0, source_width, source_height)).convert("RGB") + @staticmethod def _detail_size( crop_size: tuple[int, int], @@ -1044,10 +1070,15 @@ class QwenZImagePipeline: tile: bool = False, tile_size: int = 1024, tile_overlap: int = 128, + text_manifest: VerifiedTextManifest | None = None, ) -> Image.Image: """Execute global regeneration and masked face repair.""" self._require_cuda() seed = resolve_seed(seed) + donor = None + if text_manifest is not None: + self._progress("Reconstructing the verified text donor with the Qwen VAE...") + donor = self._qwen_vae_roundtrip(image) global_strength = ( resolution_adaptive_denoise(image.width, image.height) if strength is None else float(strength) ) @@ -1068,14 +1099,28 @@ class QwenZImagePipeline: boxes = detect_faces(image) if not boxes: self._progress("No faces detected; keeping the Qwen global result.") - return global_result - masks = self._sam_masks(image, boxes) - face_strength = largest_face_denoise(boxes, image.size) * FACE_DENOISE_SCALE - return self._run_faces( - image, - global_result, - boxes, - masks, - strength=face_strength, - seed=seed, + result = global_result + else: + masks = self._sam_masks(image, boxes) + face_strength = largest_face_denoise(boxes, image.size) * FACE_DENOISE_SCALE + result = self._run_faces( + image, + global_result, + boxes, + masks, + strength=face_strength, + seed=seed, + ) + if text_manifest is None: + return result + if donor is None: + raise RuntimeError("Verified text restoration requires a Qwen-VAE donor") + from remove_ai_watermarks._internal.text_restoration import ( + blend_fidelity_anchor, + restore_verified_text, ) + + self._progress("Blending the Qwen-VAE fidelity anchor...") + anchor = blend_fidelity_anchor(result, donor) + self._progress(f"Restoring {len(text_manifest.lines)} verified text lines...") + return restore_verified_text(image, anchor, donor, text_manifest.lines) diff --git a/src/remove_ai_watermarks/_internal/text_restoration.py b/src/remove_ai_watermarks/_internal/text_restoration.py new file mode 100644 index 0000000..b956add --- /dev/null +++ b/src/remove_ai_watermarks/_internal/text_restoration.py @@ -0,0 +1,353 @@ +"""Opt-in restoration of verified text from a Qwen VAE reconstruction.""" + +# pyright: reportUnknownMemberType=false, reportUnknownArgumentType=false, reportUnknownVariableType=false, reportUnknownParameterType=false, reportMissingTypeArgument=false, reportMissingTypeStubs=false, reportMissingImports=false, reportArgumentType=false, reportAssignmentType=false, reportReturnType=false, reportCallIssue=false, reportIndexIssue=false, reportOperatorIssue=false +from __future__ import annotations + +import hashlib +import json +import math +from dataclasses import dataclass +from typing import TYPE_CHECKING, Any + +import cv2 +import numpy as np +from PIL import Image + +if TYPE_CHECKING: + from collections.abc import Sequence + from pathlib import Path + + from numpy.typing import NDArray + +TEXT_MANIFEST_SCHEMA = 1 +FIDELITY_BLEND_ALPHA = 0.15 +GLYPH_FEATHER = 0.5 + + +@dataclass(frozen=True) +class VerifiedTextLine: + """One operator-verified source line in source-pixel coordinates.""" + + box: tuple[int, int, int, int] + text: str + script: str + angle: float = 0.0 + + +@dataclass(frozen=True) +class VerifiedTextManifest: + """Text annotations cryptographically bound to one decoded RGB source.""" + + source_pixel_sha256: str + width: int + height: int + lines: tuple[VerifiedTextLine, ...] + + +def source_pixel_sha256(image: Image.Image) -> str: + """Hash decoded RGB geometry and bytes, independent of container metadata.""" + rgb = image.convert("RGB") + digest = hashlib.sha256() + digest.update(rgb.width.to_bytes(8, "big")) + digest.update(rgb.height.to_bytes(8, "big")) + digest.update(rgb.tobytes()) + return digest.hexdigest() + + +def load_verified_text_manifest(path: Path, source: Image.Image) -> VerifiedTextManifest: + """Load and validate a manually verified manifest for exactly ``source``.""" + try: + payload = json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as exc: + raise ValueError(f"Cannot read text manifest {path}: {exc}") from exc + if not isinstance(payload, dict): + raise ValueError("Text manifest must be a JSON object") + if payload.get("schema_version") != TEXT_MANIFEST_SCHEMA: + raise ValueError(f"Text manifest schema_version must be {TEXT_MANIFEST_SCHEMA}") + if payload.get("verified") is not True: + raise ValueError("Text manifest must contain verified=true after manual review") + + rgb = source.convert("RGB") + width = _manifest_integer(payload, "width") + height = _manifest_integer(payload, "height") + if (width, height) != rgb.size: + raise ValueError(f"Text manifest dimensions {width}x{height} do not match source {rgb.width}x{rgb.height}") + expected_hash = payload.get("source_pixel_sha256") + if not isinstance(expected_hash, str) or len(expected_hash) != 64: + raise ValueError("Text manifest source_pixel_sha256 must be a 64-character SHA-256") + actual_hash = source_pixel_sha256(rgb) + if expected_hash.casefold() != actual_hash: + raise ValueError("Text manifest source_pixel_sha256 does not match the decoded source pixels") + + raw_lines = payload.get("lines") + if not isinstance(raw_lines, list) or not raw_lines: + raise ValueError("Text manifest lines must be a non-empty list") + lines = tuple(_load_line(item, width, height, index) for index, item in enumerate(raw_lines)) + if list(lines) != sorted(lines, key=lambda line: (line.box[1], line.box[0])): + raise ValueError("Text manifest lines must be in top-to-bottom, left-to-right reading order") + return VerifiedTextManifest(actual_hash, width, height, lines) + + +def _manifest_integer(payload: dict[str, Any], key: str) -> int: + value = payload.get(key) + if isinstance(value, bool) or not isinstance(value, int) or value <= 0: + raise ValueError(f"Text manifest {key} must be a positive integer") + return value + + +def _load_line(item: Any, width: int, height: int, index: int) -> VerifiedTextLine: + if not isinstance(item, dict): + raise ValueError(f"Text manifest line {index} must be an object") + raw_box = item.get("box") + if ( + not isinstance(raw_box, list) + or len(raw_box) != 4 + or any(isinstance(value, bool) or not isinstance(value, int) for value in raw_box) + ): + raise ValueError(f"Text manifest line {index} box must contain four integers") + box = tuple(raw_box) + x1, y1, x2, y2 = box + if not (0 <= x1 < x2 <= width and 0 <= y1 < y2 <= height): + raise ValueError(f"Text manifest line {index} box is outside the source dimensions") + text = item.get("text") + script = item.get("script") + if not isinstance(text, str) or not text.strip(): + raise ValueError(f"Text manifest line {index} text must be non-empty") + if not isinstance(script, str) or not script.strip(): + raise ValueError(f"Text manifest line {index} script must be non-empty") + angle_value = item.get("angle", 0.0) + if isinstance(angle_value, bool) or not isinstance(angle_value, int | float): + raise ValueError(f"Text manifest line {index} angle must be numeric") + angle = float(angle_value) + if not math.isfinite(angle) or abs(angle) > 30.0: + raise ValueError(f"Text manifest line {index} angle must be between -30 and 30 degrees") + return VerifiedTextLine(box, text, script, angle) + + +def blend_fidelity_anchor(clean: Image.Image, donor: Image.Image) -> Image.Image: + """Blend 15% Qwen-VAE reconstruction into the oracle-clean pipeline output.""" + clean_rgb = np.asarray(clean.convert("RGB"), dtype=np.float32) + donor_rgb = np.asarray(donor.convert("RGB"), dtype=np.float32) + if clean_rgb.shape != donor_rgb.shape: + raise ValueError("Clean result and Qwen-VAE donor dimensions must match") + blended = np.rint(clean_rgb * (1.0 - FIDELITY_BLEND_ALPHA) + donor_rgb * FIDELITY_BLEND_ALPHA) + return Image.fromarray(np.clip(blended, 0, 255).astype(np.uint8)) + + +def restore_verified_text( + source: Image.Image, + candidate: Image.Image, + donor: Image.Image, + lines: tuple[VerifiedTextLine, ...], +) -> Image.Image: + """Erase candidate glyphs, then composite verified Qwen-VAE glyph cores.""" + from remove_ai_watermarks import region_eraser + + if not region_eraser.lama_available(): + raise RuntimeError( + "Verified text restoration requires LaMa. Install: pip install 'remove-ai-watermarks[text-restoration]'" + ) + source_rgb = np.asarray(source.convert("RGB")) + candidate_rgb = np.asarray(candidate.convert("RGB")) + donor_rgb = np.asarray(donor.convert("RGB")) + if source_rgb.shape != candidate_rgb.shape or source_rgb.shape != donor_rgb.shape: + raise ValueError("Source, candidate, and Qwen-VAE donor dimensions must match") + + source_masks = [source_silhouette_mask(source_rgb, line.box, line.angle) for line in lines] + for index, mask in enumerate(source_masks): + if not np.any(mask): + raise ValueError(f"Verified text line {index} produced no source glyph pixels") + candidate_masks = [source_silhouette_mask(candidate_rgb, line.box, line.angle) for line in lines] + erase_masks = [] + for line, source_mask, candidate_mask in zip(lines, source_masks, candidate_masks, strict=True): + radius = 5 if line.box[3] - line.box[1] >= 48 else 3 + kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2 * radius + 1,) * 2) + erase_masks.append(cv2.dilate(np.maximum(source_mask, candidate_mask), kernel)) + del candidate_masks + + groups = group_text_lines(lines) + background = cv2.cvtColor(candidate_rgb, cv2.COLOR_RGB2BGR) + for group in groups: + background = region_eraser.erase_lama(background, np.maximum.reduce([erase_masks[index] for index in group])) + background_rgb = cv2.cvtColor(background, cv2.COLOR_BGR2RGB) + residual_masks = [ + residual_glyph_mask(background_rgb, mask, line.box) for line, mask in zip(lines, erase_masks, strict=True) + ] + for group in groups: + residual = np.maximum.reduce([residual_masks[index] for index in group]) + if np.any(residual): + background = region_eraser.erase_lama(background, residual) + del erase_masks, residual_masks + restored = cv2.cvtColor(background, cv2.COLOR_BGR2RGB) + restored = composite_fresh_text_edges(source_rgb, restored, lines, source_masks) + source_glyph_mask = np.maximum.reduce(source_masks) + restored = composite_reconstructed_glyphs(donor_rgb, restored, source_glyph_mask) + return Image.fromarray(restored) + + +def source_silhouette_mask( + source_rgb: NDArray[Any], + box: tuple[int, int, int, int], + angle: float = 0.0, +) -> NDArray[Any]: + """Recover a thresholded glyph shape without retaining source amplitudes.""" + height, width = source_rgb.shape[:2] + x1, y1, x2, y2 = _clip_box(box, width, height) + gray = cv2.cvtColor(source_rgb[y1:y2, x1:x2], cv2.COLOR_RGB2GRAY) + support = np.ones(gray.shape, dtype=np.uint8) + if angle: + box_width, box_height = x2 - x1, y2 - y1 + theta = math.radians(abs(angle)) + cosine, sine = math.cos(theta), math.sin(theta) + denominator = cosine * cosine - sine * sine + rect_width = (box_width * cosine - box_height * sine) / denominator + rect_height = (box_height * cosine - box_width * sine) / denominator + rotated = cv2.boxPoints( + ((box_width / 2, box_height / 2), (max(1.0, rect_width * 0.92), max(1.0, rect_height * 0.62)), -angle) + ) + support.fill(0) + cv2.fillConvexPoly(support, np.rint(rotated).astype(np.int32), 1) + values = gray[support > 0] + background_luma = float(np.median(values)) + else: + ring_pad = max(6, min(20, (y2 - y1) // 4)) + rx1, ry1, rx2, ry2 = _clip_box((x1, y1, x2, y2), width, height, pad=ring_pad) + context = cv2.cvtColor(source_rgb[ry1:ry2, rx1:rx2], cv2.COLOR_RGB2GRAY) + ring = np.ones(context.shape, dtype=bool) + ring[y1 - ry1 : y2 - ry1, x1 - rx1 : x2 - rx1] = False + background_luma = float(np.median(context[ring])) if ring.any() else float(np.median(gray)) + values = gray.reshape(-1) + low, high = float(np.percentile(values, 2)), float(np.percentile(values, 98)) + dark_contrast, light_contrast = background_luma - low, high - background_luma + threshold = max(16.0, min(56.0, max(light_contrast, dark_contrast) * 0.22)) + if light_contrast > dark_contrast: + crop_mask = (gray.astype(np.float32) >= background_luma + threshold).astype(np.uint8) * 255 + else: + crop_mask = (gray.astype(np.float32) <= background_luma - threshold).astype(np.uint8) * 255 + crop_mask[support == 0] = 0 + result = np.zeros((height, width), dtype=np.uint8) + result[y1:y2, x1:x2] = crop_mask + return result + + +def residual_glyph_mask( + background_rgb: NDArray[Any], + original_mask: NDArray[Any], + box: tuple[int, int, int, int], +) -> NDArray[Any]: + """Find glyph-like contrast left after the first inpaint pass.""" + residual = _foreground_mask(background_rgb, box) + residual = cv2.bitwise_and(residual, original_mask) + return cv2.dilate(residual, np.ones((5, 5), np.uint8), iterations=1) + + +def composite_fresh_text_edges( + source_rgb: NDArray[Any], + background_rgb: NDArray[Any], + lines: tuple[VerifiedTextLine, ...], + masks: list[NDArray[Any]], +) -> NDArray[Any]: + """Render fresh antialiased edges for source-derived glyph masks.""" + restored = background_rgb + for line, mask in zip(lines, masks, strict=True): + color = _sample_text_color(source_rgb, mask, line.box) + restored = composite_fresh_silhouette(restored, mask, color) + return restored + + +def composite_reconstructed_glyphs( + donor_rgb: NDArray[Any], + background_rgb: NDArray[Any], + glyph_mask: NDArray[Any], + *, + feather: float = GLYPH_FEATHER, +) -> NDArray[Any]: + """Composite an exact reconstructed core with a narrow donor edge.""" + if donor_rgb.shape != background_rgb.shape or donor_rgb.shape[:2] != glyph_mask.shape: + raise ValueError("donor, background, and glyph mask dimensions must match") + blurred = cv2.GaussianBlur(glyph_mask, (0, 0), feather) if feather > 0 else glyph_mask + alpha = np.maximum(glyph_mask, blurred).astype(np.float32) / 255.0 + combined = donor_rgb.astype(np.float32) * alpha[..., None] + background_rgb.astype(np.float32) * ( + 1.0 - alpha[..., None] + ) + return np.clip(np.rint(combined), 0, 255).astype(np.uint8) + + +def composite_fresh_silhouette( + background_rgb: NDArray[Any], + glyph_mask: NDArray[Any], + color: tuple[int, int, int], + *, + feather: float = 0.35, +) -> NDArray[Any]: + """Render a binary source shape with fresh color and antialiasing.""" + if background_rgb.shape[:2] != glyph_mask.shape: + raise ValueError("background and glyph mask dimensions must match") + antialiased = cv2.GaussianBlur(glyph_mask, (0, 0), feather) if feather > 0 else glyph_mask + alpha = antialiased.astype(np.float32)[..., None] / 255.0 + foreground = np.empty_like(background_rgb) + foreground[:, :] = color + combined = foreground.astype(np.float32) * alpha + background_rgb.astype(np.float32) * (1.0 - alpha) + return np.clip(combined, 0, 255).astype(np.uint8) + + +def _clip_box(box: tuple[int, int, int, int], width: int, height: int, pad: int = 0) -> tuple[int, int, int, int]: + x1, y1, x2, y2 = box + return max(0, x1 - pad), max(0, y1 - pad), min(width, x2 + pad), min(height, y2 + pad) + + +def _foreground_mask(source_rgb: NDArray[Any], box: tuple[int, int, int, int]) -> NDArray[Any]: + height, width = source_rgb.shape[:2] + line_height = box[3] - box[1] + x1, y1, x2, y2 = _clip_box(box, width, height, pad=max(6, int(line_height * 0.12))) + gray = cv2.cvtColor(source_rgb[y1:y2, x1:x2], cv2.COLOR_RGB2GRAY) + ring_pad = max(8, min(24, (y2 - y1) // 5)) + rx1, ry1, rx2, ry2 = _clip_box((x1, y1, x2, y2), width, height, pad=ring_pad) + context = cv2.cvtColor(source_rgb[ry1:ry2, rx1:rx2], cv2.COLOR_RGB2GRAY) + ring = np.ones(context.shape, dtype=bool) + ring[y1 - ry1 : y2 - ry1, x1 - rx1 : x2 - rx1] = False + background_luma = float(np.median(context[ring])) if ring.any() else float(np.median(gray)) + low, high = float(np.percentile(gray, 4)), float(np.percentile(gray, 96)) + dark_contrast, light_contrast = background_luma - low, high - background_luma + threshold = max(24.0, min(72.0, max(light_contrast, dark_contrast) * 0.32)) + if light_contrast > dark_contrast: + mask = (gray.astype(np.float32) >= background_luma + threshold).astype(np.uint8) * 255 + else: + mask = (gray.astype(np.float32) <= background_luma - threshold).astype(np.uint8) * 255 + mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, np.ones((2, 2), np.uint8)) + dilation = 5 if line_height >= 48 else 3 + mask = cv2.dilate(mask, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2 * dilation + 1,) * 2)) + result = np.zeros((height, width), dtype=np.uint8) + result[y1:y2, x1:x2] = mask + return result + + +def _sample_text_color( + source_rgb: NDArray[Any], mask: NDArray[Any], box: tuple[int, int, int, int] +) -> tuple[int, int, int]: + height, width = source_rgb.shape[:2] + x1, y1, x2, y2 = _clip_box(box, width, height, pad=2) + crop = source_rgb[y1:y2, x1:x2] + pixels = crop[mask[y1:y2, x1:x2] > 0] + luma = pixels.mean(axis=1) + background_luma = float(crop[[0, -1], :, :].reshape(-1, 3).mean(axis=1).mean()) + selected = ( + pixels[luma <= np.percentile(luma, 20)] if background_luma >= 128 else pixels[luma >= np.percentile(luma, 80)] + ) + return tuple(int(value) for value in np.median(selected, axis=0)) + + +def group_text_lines(lines: Sequence[VerifiedTextLine]) -> list[list[int]]: + """Group nearby same-script lines for a shared LaMa erase pass.""" + groups: list[list[int]] = [] + for index, line in enumerate(lines): + if not groups: + groups.append([index]) + continue + previous = lines[groups[-1][-1]] + gap = line.box[1] - previous.box[3] + if line.script != previous.script or gap > max(60, int((previous.box[3] - previous.box[1]) * 1.1)): + groups.append([index]) + else: + groups[-1].append(index) + return groups diff --git a/src/remove_ai_watermarks/_internal/watermark_remover.py b/src/remove_ai_watermarks/_internal/watermark_remover.py index 1567798..b704e27 100644 --- a/src/remove_ai_watermarks/_internal/watermark_remover.py +++ b/src/remove_ai_watermarks/_internal/watermark_remover.py @@ -26,6 +26,8 @@ if TYPE_CHECKING: from collections.abc import Callable from pathlib import Path + from remove_ai_watermarks._internal.text_restoration import VerifiedTextManifest + logger = logging.getLogger(__name__) try: @@ -187,6 +189,7 @@ class WatermarkRemover: tile: bool = False, tile_size: int = 1024, tile_overlap: int = 128, + text_manifest: VerifiedTextManifest | None = None, ) -> Path: """Regenerate image pixels and write the result without AI metadata. @@ -203,6 +206,10 @@ class WatermarkRemover: resolved_strength = resolve_strength(strength, vendor, self.model_profile, size=source.size) if not 0.0 <= resolved_strength <= 1.0: raise ValueError(f"Strength must be between 0.0 and 1.0, got {resolved_strength}") + if text_manifest is not None and self.model_profile == SDXL_ZIMAGE_PROFILE: + raise ValueError("Verified text restoration is supported only by the qwen-zimage profile") + if text_manifest is not None and tile: + raise ValueError("Verified text restoration is not calibrated with tiled diffusion") result = self._load_qwen_zimage_pipeline().run( source, @@ -211,6 +218,7 @@ class WatermarkRemover: tile=tile, tile_size=tile_size, tile_overlap=tile_overlap, + text_manifest=text_manifest, ) self._write_output(result, destination) return destination diff --git a/src/remove_ai_watermarks/api.py b/src/remove_ai_watermarks/api.py index 9a8d178..117623f 100644 --- a/src/remove_ai_watermarks/api.py +++ b/src/remove_ai_watermarks/api.py @@ -243,6 +243,7 @@ class InvisibleOptions: tile: bool = False tile_size: int = 1024 tile_overlap: int = 128 + text_manifest: Path | None = None # What the invisible stage did. "unavailable" is the one outcome the caller must @@ -523,6 +524,7 @@ def _run_invisible( tile=opts.tile, tile_size=opts.tile_size, tile_overlap=opts.tile_overlap, + text_manifest=opts.text_manifest, ) say("invisible", "removed") return "removed" diff --git a/src/remove_ai_watermarks/cli.py b/src/remove_ai_watermarks/cli.py index 21313de..4849edb 100644 --- a/src/remove_ai_watermarks/cli.py +++ b/src/remove_ai_watermarks/cli.py @@ -311,6 +311,16 @@ _cpu_offload_option = click.option( ), ) +_text_manifest_option = click.option( + "--text-manifest", + type=click.Path(exists=True, dir_okay=False, path_type=Path), + default=None, + help=( + "Experimental verified-text restoration manifest. Requires qwen-zimage, " + "the text-restoration extra, native untiled geometry, and no postprocessing." + ), +) + _visible_backend_option = click.option( "--backend", @@ -787,6 +797,7 @@ def cmd_erase( @_tile_options @_force_option @_cpu_offload_option +@_text_manifest_option @click.pass_context def cmd_invisible( ctx: click.Context, @@ -806,6 +817,7 @@ def cmd_invisible( tile_overlap: int, force: bool, cpu_offload: bool, + text_manifest: Path | None, ) -> None: """Remove invisible AI watermarks (SynthID, StableSignature, TreeRing). @@ -853,20 +865,25 @@ def cmd_invisible( console.print(f" Strength: {_resolved_strength_for_display(source, strength, vendor, pipeline)}") t0 = time.monotonic() - result_path = engine.remove_watermark( - image_path=source, - output_path=output, - strength=strength, - seed=seed, - humanize=humanize, - unsharp=unsharp, - adaptive_polish=adaptive_polish, - max_resolution=max_resolution, - vendor=vendor, - tile=tile, - tile_size=tile_size, - tile_overlap=tile_overlap, - ) + try: + result_path = engine.remove_watermark( + image_path=source, + output_path=output, + strength=strength, + seed=seed, + humanize=humanize, + unsharp=unsharp, + adaptive_polish=adaptive_polish, + max_resolution=max_resolution, + vendor=vendor, + tile=tile, + tile_size=tile_size, + tile_overlap=tile_overlap, + text_manifest=text_manifest, + ) + except (OSError, RuntimeError, ValueError) as exc: + console.print(f" Error: {exc}") + raise SystemExit(1) from exc elapsed = time.monotonic() - t0 size_kb = result_path.stat().st_size / 1024 @@ -1410,6 +1427,7 @@ def cmd_identify(ctx: click.Context, source: Path, no_visible: bool, as_json: bo @_tile_options @_force_option @_cpu_offload_option +@_text_manifest_option @click.pass_context def cmd_all( ctx: click.Context, @@ -1431,6 +1449,7 @@ def cmd_all( tile_overlap: int, force: bool, cpu_offload: bool, + text_manifest: Path | None, ) -> None: """Remove ALL watermarks: visible + invisible + metadata. @@ -1508,6 +1527,7 @@ def cmd_all( tile=tile, tile_size=tile_size, tile_overlap=tile_overlap, + text_manifest=text_manifest, ), force=force, progress=progress, diff --git a/src/remove_ai_watermarks/invisible_engine.py b/src/remove_ai_watermarks/invisible_engine.py index ace5700..4ca8a59 100644 --- a/src/remove_ai_watermarks/invisible_engine.py +++ b/src/remove_ai_watermarks/invisible_engine.py @@ -18,6 +18,7 @@ from typing import TYPE_CHECKING from ._internal.watermark_profiles import ( DEFAULT_PROFILE, + QWEN_ZIMAGE_PROFILE, REMOVAL_MODULES, resolve_adaptive_polish, resolve_seed, @@ -148,6 +149,7 @@ class InvisibleEngine: tile: bool = False, tile_size: int = 1024, tile_overlap: int = 128, + text_manifest: Path | None = None, ) -> Path: """Remove invisible watermark from an image. @@ -180,6 +182,11 @@ class InvisibleEngine: Engages only when the long side exceeds ``tile_size``. tile_size: Tile dimension in px (default 1024). tile_overlap: Overlap between adjacent tiles in px (default 128). + text_manifest: Operator-verified text lines bound to the decoded source + pixels. Enables the experimental Qwen-VAE ``vae-glyphs`` post-pass. + Requires the ``text-restoration`` extra and the ``qwen-zimage`` + profile. Incompatible with tiling, downscaling, humanize, unsharp, + and adaptive polish because those combinations are not calibrated. Returns: Path to the cleaned image. @@ -189,6 +196,23 @@ class InvisibleEngine: seed = resolve_seed(seed) adaptive_polish = resolve_adaptive_polish(adaptive_polish, self._remover.model_profile) + if text_manifest is not None: + if self._remover.model_profile != QWEN_ZIMAGE_PROFILE: + raise ValueError("--text-manifest is supported only by the qwen-zimage profile") + if max_resolution != 0: + raise ValueError("--text-manifest requires --max-resolution 0") + if tile: + raise ValueError("--text-manifest is not calibrated with --tile") + if humanize > 0.0 or unsharp > 0.0 or adaptive_polish: + raise ValueError("--text-manifest requires humanize=0, unsharp=0, and adaptive polish disabled") + from remove_ai_watermarks import region_eraser + + if not region_eraser.lama_available(): + raise RuntimeError( + "Verified text restoration requires LaMa. Install: " + "pip install 'remove-ai-watermarks[text-restoration]'" + ) + from PIL import Image, ImageOps # Resolution policy: a max_resolution cap (0 = none) bounds memory on huge @@ -205,6 +229,11 @@ class InvisibleEngine: # Full-res original, kept for the adaptive-polish detail target (image is # reassigned to the resized copy below; PIL resize returns a new object). reference_pil = image + verified_text = None + if text_manifest is not None: + from remove_ai_watermarks._internal.text_restoration import load_verified_text_manifest + + verified_text = load_verified_text_manifest(text_manifest, reference_pil) # Both profiles run at the input's native geometry, so only the explicit max # cap can move it, and it can only ever scale down. @@ -240,6 +269,7 @@ class InvisibleEngine: tile=tile, tile_size=tile_size, tile_overlap=tile_overlap, + text_manifest=verified_text, ) # Post-processing chain: decode the diffusion output ONCE, apply the diff --git a/tests/test_api.py b/tests/test_api.py index b5c6753..ba8d519 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -245,6 +245,7 @@ class TestInvisibleOptionsMirrorTheEngine: tile=True, tile_size=768, tile_overlap=64, + text_manifest=tmp_path / "verified-lines.json", ) seen: dict[str, object] = {} diff --git a/tests/test_invisible_engine.py b/tests/test_invisible_engine.py index cd85e90..f459aa0 100644 --- a/tests/test_invisible_engine.py +++ b/tests/test_invisible_engine.py @@ -43,6 +43,76 @@ class TestInvisibleEngineInit: assert engine._preload_kwargs == {"global_only": True} +class TestVerifiedTextMode: + """The experimental mode must fail before loading models on unmeasured inputs.""" + + @staticmethod + def _engine(profile: str = "qwen-zimage") -> InvisibleEngine: + engine = object.__new__(InvisibleEngine) + engine._progress_callback = None + engine._remover = SimpleNamespace(model_profile=profile) + return engine + + def test_rejects_incompatible_pipeline_options(self, tmp_path): + import pytest + + manifest = tmp_path / "manifest.json" + manifest.write_text("{}", encoding="utf-8") + cases = ( + ("sdxl-zimage", {}, "qwen-zimage"), + ("qwen-zimage", {"max_resolution": 1024}, "max-resolution 0"), + ("qwen-zimage", {"tile": True}, "not calibrated"), + ("qwen-zimage", {"humanize": 1.0}, "humanize=0"), + ("qwen-zimage", {"adaptive_polish": True}, "polish disabled"), + ) + for profile, kwargs, message in cases: + with pytest.raises(ValueError, match=message): + self._engine(profile).remove_watermark( + tmp_path / "unused.png", + text_manifest=manifest, + **kwargs, + ) + + def test_loads_and_forwards_verified_manifest(self, tmp_path, monkeypatch): + import json + + from remove_ai_watermarks import region_eraser + from remove_ai_watermarks._internal.text_restoration import source_pixel_sha256 + + source = tmp_path / "source.png" + output = tmp_path / "output.png" + image = Image.new("RGB", (48, 32), (10, 20, 30)) + image.save(source) + manifest = tmp_path / "manifest.json" + manifest.write_text( + json.dumps( + { + "schema_version": 1, + "verified": True, + "source_pixel_sha256": source_pixel_sha256(image), + "width": 48, + "height": 32, + "lines": [{"box": [8, 8, 40, 24], "text": "Exact", "script": "alphabetic"}], + } + ), + encoding="utf-8", + ) + seen = {} + + def fake_remove(**kwargs): + seen.update(kwargs) + Image.open(kwargs["image_path"]).save(kwargs["output_path"]) + return kwargs["output_path"] + + engine = self._engine() + engine._remover.remove_watermark = fake_remove + monkeypatch.setattr(region_eraser, "lama_available", lambda: True) + + engine.remove_watermark(source, output, text_manifest=manifest) + + assert seen["text_manifest"].lines[0].text == "Exact" + + class TestNativeOutputSize: """Model-side latent-grid rounding must not change the public output size.""" diff --git a/tests/test_qwen_zimage_pipeline.py b/tests/test_qwen_zimage_pipeline.py index c4cce8b..e1b4824 100644 --- a/tests/test_qwen_zimage_pipeline.py +++ b/tests/test_qwen_zimage_pipeline.py @@ -579,6 +579,78 @@ def test_cli_qwen_zimage_keeps_profile_postprocess_default(tmp_image_path, monke assert mock_engine.remove_watermark.call_args.kwargs["adaptive_polish"] is True +def test_cli_forwards_verified_text_manifest(tmp_image_path, tmp_path, monkeypatch): + from remove_ai_watermarks import cli + + manifest = tmp_path / "manifest.json" + manifest.write_text("{}", encoding="utf-8") + mock_engine = MagicMock() + mock_engine.remove_watermark.return_value = tmp_image_path + monkeypatch.setattr("remove_ai_watermarks.invisible_engine.is_available", lambda: True) + monkeypatch.setattr("remove_ai_watermarks.invisible_engine.InvisibleEngine", MagicMock(return_value=mock_engine)) + + result = CliRunner().invoke( + cli.main, + ["invisible", str(tmp_image_path), "--text-manifest", str(manifest), "--force"], + ) + + assert result.exit_code == 0, result.output + assert mock_engine.remove_watermark.call_args.kwargs["text_manifest"] == manifest + + +def test_cli_reports_verified_text_manifest_errors(tmp_image_path, tmp_path, monkeypatch): + from remove_ai_watermarks import cli + + manifest = tmp_path / "manifest.json" + manifest.write_text("{}", encoding="utf-8") + mock_engine = MagicMock() + mock_engine.remove_watermark.side_effect = ValueError("manifest pixels do not match") + monkeypatch.setattr("remove_ai_watermarks.invisible_engine.is_available", lambda: True) + monkeypatch.setattr("remove_ai_watermarks.invisible_engine.InvisibleEngine", MagicMock(return_value=mock_engine)) + + result = CliRunner().invoke( + cli.main, + ["invisible", str(tmp_image_path), "--text-manifest", str(manifest), "--force"], + ) + + assert result.exit_code == 1 + assert "manifest pixels do not match" in result.output + + +def test_no_face_path_still_runs_verified_text_restoration(monkeypatch): + from remove_ai_watermarks._internal import qwen_zimage_pipeline, text_restoration + from remove_ai_watermarks._internal.qwen_zimage_pipeline import QwenZImagePipeline + from remove_ai_watermarks._internal.text_restoration import VerifiedTextLine, VerifiedTextManifest + + pipeline = object.__new__(QwenZImagePipeline) + pipeline.device = "cuda" + pipeline.progress_callback = None + source = Image.new("RGB", (32, 32), (10, 20, 30)) + donor = Image.new("RGB", (32, 32), (40, 50, 60)) + global_result = Image.new("RGB", (32, 32), (70, 80, 90)) + anchor = Image.new("RGB", (32, 32), (100, 110, 120)) + restored = Image.new("RGB", (32, 32), (130, 140, 150)) + pipeline._qwen_vae_roundtrip = MagicMock(return_value=donor) + pipeline._run_global = MagicMock(return_value=global_result) + monkeypatch.setattr(qwen_zimage_pipeline, "detect_faces", lambda _image: []) + blend = MagicMock(return_value=anchor) + restore = MagicMock(return_value=restored) + monkeypatch.setattr(text_restoration, "blend_fidelity_anchor", blend) + monkeypatch.setattr(text_restoration, "restore_verified_text", restore) + manifest = VerifiedTextManifest( + "0" * 64, + 32, + 32, + (VerifiedTextLine((4, 4, 20, 16), "Exact", "alphabetic"),), + ) + + result = pipeline.run(source, strength=0.1, seed=0, text_manifest=manifest) + + assert result is restored + blend.assert_called_once_with(global_result, donor) + restore.assert_called_once_with(source, anchor, donor, manifest.lines) + + def test_watermark_remover_dispatches_to_full_pipeline(tmp_path, monkeypatch): from remove_ai_watermarks._internal.watermark_remover import WatermarkRemover @@ -601,6 +673,7 @@ def test_watermark_remover_dispatches_to_full_pipeline(tmp_path, monkeypatch): _, kwargs = runtime.run.call_args assert kwargs["strength"] == pytest.approx(0.084) assert kwargs["seed"] == 0 + assert kwargs["text_manifest"] is None assert output.exists() diff --git a/tests/test_selective_text_restoration.py b/tests/test_selective_text_restoration.py index ce30582..b51a620 100644 --- a/tests/test_selective_text_restoration.py +++ b/tests/test_selective_text_restoration.py @@ -8,6 +8,8 @@ from pathlib import Path import numpy as np import pytest +from remove_ai_watermarks._internal import text_restoration + SCRIPT = Path(__file__).parents[1] / "scripts" / "selective_text_restoration.py" SPEC = importlib.util.spec_from_file_location("selective_text_restoration", SCRIPT) assert SPEC is not None @@ -87,7 +89,7 @@ def test_fresh_silhouette_uses_new_color_instead_of_source_pixels() -> None: mask = np.zeros((9, 9), dtype=np.uint8) mask[3:6, 3:6] = 255 - result = module.composite_fresh_silhouette(background, mask, (220, 180, 40), feather=0) + result = text_restoration.composite_fresh_silhouette(background, mask, (220, 180, 40), feather=0) assert np.all(result[3:6, 3:6] == (220, 180, 40)) np.testing.assert_array_equal(result[0, 0], background[0, 0]) @@ -99,7 +101,7 @@ def test_fresh_silhouette_antialiasing_softens_binary_edges() -> None: mask = np.zeros((9, 9), dtype=np.uint8) mask[3:6, 3:6] = 255 - result = module.composite_fresh_silhouette(background, mask, (220, 180, 40), feather=1.0) + result = text_restoration.composite_fresh_silhouette(background, mask, (220, 180, 40), feather=1.0) assert np.all(result[3, 3] > background[3, 3]) assert np.all(result[3, 3] < (220, 180, 40)) @@ -113,7 +115,7 @@ def test_reconstructed_glyphs_keep_exact_donor_core_and_fresh_edge() -> None: mask = np.zeros((9, 9), dtype=np.uint8) mask[3:6, 3:6] = 255 - fresh_edge = module.composite_fresh_silhouette(background, mask, (220, 180, 40)) + fresh_edge = text_restoration.composite_fresh_silhouette(background, mask, (220, 180, 40)) result = module.composite_reconstructed_glyphs(donor, fresh_edge, mask, feather=0.5) np.testing.assert_array_equal(result[3:6, 3:6], donor[3:6, 3:6]) @@ -163,13 +165,15 @@ def test_detect_line_boxes_fails_closed_on_count_mismatch() -> None: def test_residual_mask_is_limited_to_original_glyph_positions(monkeypatch) -> None: + from remove_ai_watermarks._internal import text_restoration + background = np.zeros((8, 8, 3), dtype=np.uint8) original = np.zeros((8, 8), dtype=np.uint8) original[3, 3] = 255 detected = np.zeros((8, 8), dtype=np.uint8) detected[3, 3] = 255 detected[6, 6] = 255 - monkeypatch.setattr(module, "foreground_mask", lambda _image, _box: detected) + monkeypatch.setattr(text_restoration, "_foreground_mask", lambda _image, _box: detected) residual = module.residual_glyph_mask(background, original, (0, 0, 8, 8)) diff --git a/tests/test_text_restoration.py b/tests/test_text_restoration.py new file mode 100644 index 0000000..9ea4887 --- /dev/null +++ b/tests/test_text_restoration.py @@ -0,0 +1,128 @@ +"""Verified-text manifest and compositor tests without model downloads.""" + +from __future__ import annotations + +import json + +import numpy as np +import pytest +from PIL import Image, PngImagePlugin + +from remove_ai_watermarks._internal.text_restoration import ( + FIDELITY_BLEND_ALPHA, + VerifiedTextLine, + blend_fidelity_anchor, + load_verified_text_manifest, + restore_verified_text, + source_pixel_sha256, +) + + +def _manifest(image: Image.Image) -> dict[str, object]: + return { + "schema_version": 1, + "verified": True, + "source_pixel_sha256": source_pixel_sha256(image), + "width": image.width, + "height": image.height, + "lines": [ + { + "box": [8, 8, 40, 24], + "text": "Exact text", + "script": "alphabetic", + "angle": 0.0, + } + ], + } + + +def test_pixel_hash_ignores_container_metadata(tmp_path) -> None: + image = Image.new("RGB", (48, 32), (10, 20, 30)) + plain = tmp_path / "plain.png" + tagged = tmp_path / "tagged.png" + image.save(plain) + metadata = PngImagePlugin.PngInfo() + metadata.add_text("note", "different container bytes") + image.save(tagged, pnginfo=metadata) + + with Image.open(plain) as left, Image.open(tagged) as right: + assert plain.read_bytes() != tagged.read_bytes() + assert source_pixel_sha256(left) == source_pixel_sha256(right) + + +def test_verified_manifest_is_bound_to_source_pixels(tmp_path) -> None: + source = Image.new("RGB", (48, 32), (10, 20, 30)) + path = tmp_path / "lines.json" + path.write_text(json.dumps(_manifest(source)), encoding="utf-8") + + loaded = load_verified_text_manifest(path, source) + + assert loaded.width == 48 + assert loaded.height == 32 + assert loaded.lines == (VerifiedTextLine((8, 8, 40, 24), "Exact text", "alphabetic", 0.0),) + + +@pytest.mark.parametrize( + ("mutation", "message"), + [ + ({"verified": False}, "verified=true"), + ({"source_pixel_sha256": "0" * 64}, "does not match"), + ({"width": 49}, "dimensions"), + ({"lines": []}, "non-empty"), + ], +) +def test_manifest_rejects_unverified_or_unbound_input(tmp_path, mutation, message) -> None: + source = Image.new("RGB", (48, 32), (10, 20, 30)) + payload = _manifest(source) + payload.update(mutation) + path = tmp_path / "lines.json" + path.write_text(json.dumps(payload), encoding="utf-8") + + with pytest.raises(ValueError, match=message): + load_verified_text_manifest(path, source) + + +def test_fidelity_anchor_uses_the_calibrated_rounding() -> None: + clean = Image.fromarray(np.array([[[1, 2, 3], [100, 150, 200]]], dtype=np.uint8)) + donor = Image.fromarray(np.array([[[255, 254, 253], [200, 100, 50]]], dtype=np.uint8)) + + result = np.asarray(blend_fidelity_anchor(clean, donor)) + expected = np.rint( + np.asarray(clean, dtype=np.float32) * (1.0 - FIDELITY_BLEND_ALPHA) + + np.asarray(donor, dtype=np.float32) * FIDELITY_BLEND_ALPHA + ).astype(np.uint8) + + assert np.array_equal(result, expected) + + +def test_restoration_uses_lama_and_qwen_vae_core(monkeypatch) -> None: + from remove_ai_watermarks import region_eraser + + source = np.full((40, 64, 3), 20, dtype=np.uint8) + source[12:24, 12:44] = 235 + candidate = np.full_like(source, 30) + candidate[12:24, 12:44] = 150 + donor = np.full_like(source, 40) + donor[12:24, 12:44] = (210, 220, 230) + calls: list[np.ndarray] = [] + + def fake_erase(image_bgr, mask): + calls.append(mask.copy()) + output = image_bgr.copy() + output[mask > 0] = (30, 30, 30) + return output + + monkeypatch.setattr(region_eraser, "lama_available", lambda: True) + monkeypatch.setattr(region_eraser, "erase_lama", fake_erase) + + result = restore_verified_text( + Image.fromarray(source), + Image.fromarray(candidate), + Image.fromarray(donor), + (VerifiedTextLine((8, 8, 48, 28), "Exact text", "alphabetic"),), + ) + + restored = np.asarray(result) + assert calls + assert np.all(restored[16, 20] == donor[16, 20]) + assert np.all(restored[0, 0] == candidate[0, 0]) diff --git a/uv.lock b/uv.lock index 9b3d6e1..4fa8b82 100644 --- a/uv.lock +++ b/uv.lock @@ -597,7 +597,7 @@ name = "cuda-bindings" version = "13.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cuda-pathfinder" }, + { name = "cuda-pathfinder", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/51/6b/457ca12dad3ee9bfcc9a545cfd6b64b359ba49de40f776f6e028e678f262/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5879712accf6e14bb01aa5e67440eb84998b8d104b509cc7a6dc0b8f656a474", size = 6053539, upload-time = "2026-05-29T23:11:43.19Z" }, @@ -630,43 +630,43 @@ wheels = [ [package.optional-dependencies] cublas = [ - { name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, - { name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] cudart = [ - { name = "nvidia-cuda-runtime", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cuda-runtime", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] cufft = [ - { name = "nvidia-cufft", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, - { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cufft", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] cufile = [ - { name = "nvidia-cufile", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cufile", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] cupti = [ - { name = "nvidia-cuda-cupti", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cuda-cupti", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] curand = [ - { name = "nvidia-curand", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-curand", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] cusolver = [ - { name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, - { name = "nvidia-cusolver", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, - { name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, - { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cusolver", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] cusparse = [ - { name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, - { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] nvjitlink = [ - { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] nvrtc = [ - { name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] nvtx = [ - { name = "nvidia-nvtx", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-nvtx", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] [[package]] @@ -1094,15 +1094,15 @@ name = "lightning" version = "2.6.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "fsspec", extra = ["http"] }, - { name = "lightning-utilities" }, - { name = "packaging" }, - { name = "pytorch-lightning" }, - { name = "pyyaml" }, - { name = "torch" }, - { name = "torchmetrics" }, - { name = "tqdm" }, - { name = "typing-extensions" }, + { name = "fsspec", extra = ["http"], marker = "python_full_version < '3.13'" }, + { name = "lightning-utilities", marker = "python_full_version < '3.13'" }, + { name = "packaging", marker = "python_full_version < '3.13'" }, + { name = "pytorch-lightning", marker = "python_full_version < '3.13'" }, + { name = "pyyaml", marker = "python_full_version < '3.13'" }, + { name = "torch", marker = "python_full_version < '3.13'" }, + { name = "torchmetrics", marker = "python_full_version < '3.13'" }, + { name = "tqdm", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c9/1d/83be8536bec71a0173e762a9a1fd92a24a5ad0d0f74c59550c3c4e6c103b/lightning-2.6.5.tar.gz", hash = "sha256:16a30310ed69afde3748491feb5d13508908effd70390d2bfc203dc0812a4b4a", size = 659201, upload-time = "2026-05-27T14:33:41.806Z" } wheels = [ @@ -1114,8 +1114,8 @@ name = "lightning-utilities" version = "0.15.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "packaging" }, - { name = "typing-extensions" }, + { name = "packaging", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f1/45/7fa8f56b17dc0f0a41ec70dd307ecd6787254483549843bef4c30ab5adce/lightning_utilities-0.15.3.tar.gz", hash = "sha256:792ae0204c79f6859721ac7f386c237a33b0ed06ba775009cb894e010a842033", size = 33553, upload-time = "2026-02-22T14:48:53.348Z" } wheels = [ @@ -1518,7 +1518,7 @@ name = "nvidia-cublas" version = "13.1.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cuda-nvrtc" }, + { name = "nvidia-cuda-nvrtc", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" }, @@ -1557,7 +1557,7 @@ name = "nvidia-cudnn-cu13" version = "9.20.0.48" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas" }, + { name = "nvidia-cublas", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/56/c5/83384d846b2fd17c44bd499b36c75a45ed4f095fbbb2252294e89cea5c5c/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:e31454ae00094b0c55319d9d15b6fa2fc50a9e1c0f5c8c80fb75258234e731e1", size = 444574296, upload-time = "2026-03-09T19:28:27.751Z" }, @@ -1569,7 +1569,7 @@ name = "nvidia-cufft" version = "12.0.0.61" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink" }, + { name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, @@ -1599,9 +1599,9 @@ name = "nvidia-cusolver" version = "12.0.4.66" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas" }, - { name = "nvidia-cusparse" }, - { name = "nvidia-nvjitlink" }, + { name = "nvidia-cublas", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "nvidia-cusparse", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, @@ -1613,7 +1613,7 @@ name = "nvidia-cusparse" version = "12.6.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink" }, + { name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, @@ -1670,8 +1670,8 @@ name = "omegaconf" version = "2.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "antlr4-python3-runtime" }, - { name = "pyyaml" }, + { name = "antlr4-python3-runtime", marker = "python_full_version < '3.13'" }, + { name = "pyyaml", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ce/3d/e4b57b8d9008c6ebe0d5eff901f91d5700cf7bdb8c8863df817463a7fd5e/omegaconf-2.3.1.tar.gz", hash = "sha256:e5e7de64aeebeddaf8e6d3f7a783b32ac2a01c0fbd9c878012caecb891a1f42a", size = 3298472, upload-time = "2026-06-11T05:05:12.885Z" } wheels = [ @@ -2273,14 +2273,14 @@ name = "pytorch-lightning" version = "2.6.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "fsspec", extra = ["http"] }, - { name = "lightning-utilities" }, - { name = "packaging" }, - { name = "pyyaml" }, - { name = "torch" }, - { name = "torchmetrics" }, - { name = "tqdm" }, - { name = "typing-extensions" }, + { name = "fsspec", extra = ["http"], marker = "python_full_version < '3.13'" }, + { name = "lightning-utilities", marker = "python_full_version < '3.13'" }, + { name = "packaging", marker = "python_full_version < '3.13'" }, + { name = "pyyaml", marker = "python_full_version < '3.13'" }, + { name = "torch", marker = "python_full_version < '3.13'" }, + { name = "torchmetrics", marker = "python_full_version < '3.13'" }, + { name = "tqdm", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/52/2c/8e73a3929b4c4bd600cafd38a97aaf7242a8cf518fb9f33d27c274ec898f/pytorch_lightning-2.6.5.tar.gz", hash = "sha256:1c32cefa76a1a9c4c5250338272d961d1e48b180e68396849efe128538ddb28e", size = 661673, upload-time = "2026-05-27T14:33:41.961Z" } wheels = [ @@ -2604,6 +2604,21 @@ qwen-zimage = [ { name = "torchvision" }, { name = "transformers" }, ] +text-restoration = [ + { name = "accelerate" }, + { name = "diffsynth" }, + { name = "diffusers" }, + { name = "huggingface-hub" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "onnxruntime" }, + { name = "opencv-python-headless" }, + { name = "safetensors" }, + { name = "tokenizers" }, + { name = "torch" }, + { name = "torchvision" }, + { name = "transformers" }, +] trustmark = [ { name = "trustmark", marker = "python_full_version < '3.13'" }, ] @@ -2650,8 +2665,9 @@ requires-dist = [ { name = "remove-ai-watermarks", extras = ["pixels"], marker = "extra == 'detect'" }, { name = "remove-ai-watermarks", extras = ["pixels"], marker = "extra == 'diffusion'" }, { name = "remove-ai-watermarks", extras = ["pixels"], marker = "extra == 'visible'" }, + { name = "remove-ai-watermarks", extras = ["qwen-zimage", "lama"], marker = "extra == 'text-restoration'" }, { name = "remove-ai-watermarks", extras = ["video"], marker = "extra == 'dev'" }, - { name = "remove-ai-watermarks", extras = ["video", "heif", "detect", "trustmark", "qwen-zimage", "lama", "migan"], marker = "extra == 'all'" }, + { name = "remove-ai-watermarks", extras = ["video", "heif", "detect", "trustmark", "text-restoration", "migan"], marker = "extra == 'all'" }, { name = "remove-ai-watermarks", extras = ["visible"], marker = "extra == 'lama'" }, { name = "remove-ai-watermarks", extras = ["visible"], marker = "extra == 'migan'" }, { name = "remove-ai-watermarks", extras = ["visible"], marker = "extra == 'video'" }, @@ -2663,7 +2679,7 @@ requires-dist = [ { name = "transformers", marker = "extra == 'diffusion'", specifier = ">=5,<6" }, { name = "trustmark", marker = "python_full_version < '3.13' and extra == 'trustmark'", specifier = ">=0.8.0" }, ] -provides-extras = ["pixels", "heif", "visible", "video", "detect", "diffusion", "qwen-zimage", "trustmark", "lama", "migan", "dev", "all"] +provides-extras = ["pixels", "heif", "visible", "video", "detect", "diffusion", "qwen-zimage", "text-restoration", "trustmark", "lama", "migan", "dev", "all"] [[package]] name = "requests" @@ -2968,10 +2984,10 @@ name = "torchmetrics" version = "1.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "lightning-utilities" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, - { name = "packaging" }, - { name = "torch" }, + { name = "lightning-utilities", marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "packaging", marker = "python_full_version < '3.13'" }, + { name = "torch", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/81/34/39b8b749333db56c0585d7a11fa62a283c087bb1dfc897d69fb8cedbefb1/torchmetrics-1.9.0.tar.gz", hash = "sha256:a488609948600df52d3db4fcdab02e62aab2a85ef34da67037dc3e65b8512faa", size = 581765, upload-time = "2026-03-09T17:41:22.443Z" } wheels = [ @@ -3066,13 +3082,13 @@ name = "trustmark" version = "0.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "einops" }, - { name = "lightning" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, - { name = "omegaconf" }, - { name = "six" }, - { name = "torch" }, - { name = "torchvision" }, + { name = "einops", marker = "python_full_version < '3.13'" }, + { name = "lightning", marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "omegaconf", marker = "python_full_version < '3.13'" }, + { name = "six", marker = "python_full_version < '3.13'" }, + { name = "torch", marker = "python_full_version < '3.13'" }, + { name = "torchvision", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/87/0a/0a4232030c6a62d12b6a02ae73bdce6e99c8532bc8f05a5a2e6ce103da82/trustmark-0.9.1.tar.gz", hash = "sha256:dc79e3fb070f5d94765acf8868a51f50a612cc05b53223cf1e6b605d4ff1e0ae", size = 63949, upload-time = "2026-04-09T08:59:52.472Z" } From 21ce1dfadd29fbd9256302b747992decb7d27463 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Mon, 17 Aug 2026 19:38:37 -0700 Subject: [PATCH 17/44] Cover QuickTime TC260 placements, Luma AI tags, and MiniMax video provenance --- README.md | 4 +- docs/cli.md | 4 +- docs/development.md | 4 + docs/module-internals.md | 9 +- docs/python-api.md | 3 +- docs/supported-signals.md | 3 +- docs/watermarking-landscape.md | 2 +- .../_internal/constants.py | 5 + src/remove_ai_watermarks/_internal/isobmff.py | 165 ++++++++++++------ src/remove_ai_watermarks/identify.py | 2 + src/remove_ai_watermarks/metadata.py | 5 + src/remove_ai_watermarks/video.py | 2 + src/remove_ai_watermarks/video_visible.py | 29 ++- tests/test_metadata.py | 36 ++++ tests/test_video.py | 121 +++++++++++++ 15 files changed, 327 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index 009b2b5..bfb677e 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,9 @@ The `video metadata` command does not transcode video or audio streams. Unlike the image command above, when `-o` is omitted it writes `_clean` and preserves the original. MP4 and MOV inspection includes the native TC260 `AIGC` tag in -`moov.udta.meta.keys/ilst`, including a `moov` placed after the media payload. +`moov.udta.meta.keys/ilst`, including a `moov` placed after the media payload, +plus the QuickTime-form `meta` variants Doubao's iOS export writes (a bare +`meta` box as a direct `moov` child, and a keyless `hdlr=mdir` metadata list). MKV and WebM inspection reads the normative `Segment.Tags.Tag.SimpleTag` placement. AVI uses `LIST/INFO/AIGC`, while FLV uses `script.onMetaData.AIGC`. The non-ISOBMFF formats are remuxed with stream diff --git a/docs/cli.md b/docs/cli.md index 116184c..a3386a5 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -229,7 +229,9 @@ Supported containers are MP4, MOV, M4V, WebM, MKV, AVI, and FLV. The operation delegates to the same verified metadata scanner and stripper as the generic `metadata` command, so detection and removal stay in parity. Video and audio streams are not transcoded. For MP4 and MOV, this includes the native TC260 -`AIGC` key and JSON value stored in `moov.udta.meta.keys/ilst`. The inspector +`AIGC` key and JSON value stored in `moov.udta.meta.keys/ilst`, plus the +QuickTime-form `meta` variants Doubao's iOS export writes (a bare `meta` box +as a direct `moov` child, and a keyless `hdlr=mdir` metadata list). The inspector seeks past a large `mdat` to find a tail `moov`. Removal stream-copies the container in bounded chunks, converts supported top-level provenance boxes to same-size `free` boxes, and blanks the TC260 key/value in place. Box sizes, diff --git a/docs/development.md b/docs/development.md index 24cff4e..e102eba 100644 --- a/docs/development.md +++ b/docs/development.md @@ -12,6 +12,10 @@ Read this reference for environment setup, dependency recovery, CI behavior, and The optional TrustMark decoder downloads weights into its installed package directory. After pruning that extra, a leftover weights directory can make availability checks see an empty namespace package. If Pyright reports an unknown `TrustMark` import and `find_spec("trustmark")` returns a loader-less spec, remove that regenerable remnant from the active virtual environment and resync. +### Known security-gate blocks + +`maintain.sh` fails while PyPI ships no fixed release for a transitive CVE. Current case (triaged 2026-08-17): `lightning` 2.6.5, pulled only by the optional `trustmark` extra, carries PYSEC-2026-3624 (RCE via `load_from_checkpoint` on an attacker-crafted checkpoint). The vulnerable API is unreachable here, because the TrustMark decoder loads only its own pinned weights downloaded from the TrustMark release, never a user-supplied checkpoint. The upstream fix is merged but unreleased, and ignores are never added, so run and report the remaining core checks (Ruff, Pyright, tests) separately until a fixed `lightning` release lands; bump it with `uv lock --upgrade-package lightning` as soon as one does. + ## CI `.github/workflows/test.yml` runs Ruff, a test matrix over every supported Python diff --git a/docs/module-internals.md b/docs/module-internals.md index c715df0..dbde242 100644 --- a/docs/module-internals.md +++ b/docs/module-internals.md @@ -158,7 +158,14 @@ Native MP4/MOV TC260 labels follow TC260-PG-20257A: `moov.udta.meta.keys` maps an `AIGC` key to a raw JSON value in `ilst`. [`_internal/isobmff.py`](../src/remove_ai_watermarks/_internal/isobmff.py) walks those nested boxes by seeking, so detection reaches a tail `moov` without reading the -preceding `mdat`. The MP4/MOV/M4V/M4A removal path first validates the top-level +preceding `mdat`. Two Doubao iOS variants sit outside that normative placement +and are covered by the same walker (2026-08-17 corpus findings, both previously +undetected): a QuickTime-form `meta` box as a *direct* `moov` child (no FullBox +header, disambiguated by probing the child-box offset), and a QuickTime +`hdlr=mdir` metadata list under `udta.meta` whose `ilst` data items carry the +validated JSON with no `keys` box at all (content-validated, so only genuine +TC260 JSON matches; the keyless entry has no key name to blank, so its value +alone is spaced out). The MP4/MOV/M4V/M4A removal path first validates the top-level box walk, then copies the source to a sibling temporary file in bounded chunks. Supported C2PA/JUMBF/AI-label boxes become same-size `free` boxes with blank payloads; TC260 removal changes the four-byte key to `free` and blanks only the diff --git a/docs/python-api.md b/docs/python-api.md index 0f7d876..385ea0e 100644 --- a/docs/python-api.md +++ b/docs/python-api.md @@ -431,7 +431,8 @@ must use the same container extension as the source. The returned `VideoMetadataResult` records the source, output, metadata detected before removal, and any markers remaining after the verified strip. MP4/MOV inspection recognizes the native TC260 `AIGC` entry in -`moov.udta.meta.keys/ilst`; its removal preserves container size and encoded +`moov.udta.meta.keys/ilst` and the QuickTime-form `meta` variants Doubao's iOS +export writes; its removal preserves container size and encoded stream bytes. MP4/MOV/M4V are copied in bounded chunks, so a large `mdat` is not loaded into memory; publication is atomic. MKV/WebM inspection recognizes the corresponding `Segment.Tags.Tag.SimpleTag` representation; its removal requires ffmpeg for a diff --git a/docs/supported-signals.md b/docs/supported-signals.md index e77398a..0082b8a 100644 --- a/docs/supported-signals.md +++ b/docs/supported-signals.md @@ -37,7 +37,7 @@ when you can select the affected area yourself. | `veo` | Current four-point diamond and legacy `Veo` text | Fixed bottom-right corner | Uses separate silhouettes and requires a recurring match; learned fill is preferable on structured backgrounds. | | `seedance` | Boxed `AI` label | Fixed bottom-right corner | Requires an anchored recurring match; the full localized box is filled because a thinner synthetic shape mask leaves the real translucent rim behind. | | `dola` | `Dola AI` text | Fixed bottom-right corner | Requires an anchored recurring match; ByteDance or BytePlus provenance can relax only an existing visual run. | -| `hailuo` | `MINIMAX \| hailuo AI` composite label | Fixed lower edge | Uses a synthetic waveform, text, separator, and ring silhouette; the complete recurring label box is filled. | +| `hailuo` | `MINIMAX \| hailuo AI` composite label | Fixed lower edge | Uses a synthetic waveform, text, separator, and ring silhouette; the complete recurring label box is filled. A TC260 label naming MiniMax as producer can relax only an existing stable run. | | `kling` | Kling swirl, `KLING AI`, version, and optional `PRO` suffix | Fixed bottom-right edge | Combines a synthetic logo rescue with font variants, an edge gate, a white-label gate, and anchored temporal recurrence. | `video identify`, `video visible`, and `video all` share this registry and the @@ -162,6 +162,7 @@ not a universal clean verdict. | FLUX | None registered | Diffusion regeneration; optional open decoder | C2PA for supported sources | | Adobe Firefly | None registered | Optional TrustMark Variant P decoder | C2PA | | Midjourney | None registered | No registered pixel decoder | EXIF, XMP, and IPTC signals | +| Luma AI | None registered | No registered pixel decoder | PNG text generator tags (Uni-1) | | ByteDance generators | Doubao and Jimeng marks | No registered pixel decoder | TC260 AIGC, supported C2PA, and exact app-export AIGC disclosures | | Qwen | Qwen mark | No registered pixel decoder | TC260 AIGC | | Kling | Kling image and video marks | No registered pixel decoder | TC260 AIGC | diff --git a/docs/watermarking-landscape.md b/docs/watermarking-landscape.md index 86ff339..90798fb 100644 --- a/docs/watermarking-landscape.md +++ b/docs/watermarking-landscape.md @@ -45,7 +45,7 @@ pair. On the ISOBMFF path, `blank_ai_exif_tokens` provides the corresponding in-place scrub for supported EXIF values, TC260 AIGC blocks, and the xAI pair. - **China TC260 AIGC label (caught by `AIGC_MARKERS` / `metadata.aigc_label`, surfaced by `identify` as the `aigc` signal):** China-served generators embed an XMP `{"Label":"1","ContentProducer":...}` block — China's mandatory AI-content labeling (TC260 namespace `tc260.org.cn/ns/AIGC`). The label says only "this is AI", but its `ContentProducer` names the signing entity — `001` + `1` + an 18-char Unified Social Credit Code + a 5-digit product suffix, normalized by `metadata.uscc_of`, or for a few generators a bare product name. `KnownMark.tc260_producer_codes` maps the codes settled per vendor by `scripts/vendor_cohort_harvest.py` to registry mark keys, so an AIGC image relaxes the detector of the vendor it actually carries rather than a guessed pair; an unmapped or absent producer falls back to ByteDance's two products. A code identifies a legal entity, not necessarily one brand, so a hosting or aggregating platform that signs for several apps is a recall bet rather than a proof. -**Doubao** (ByteDance) uses it (verified on a public issue sample; `ContentProducer` `001191110102MACQD9K64010000`, no C2PA/SynthID/imwatermark — the XMP block is the only signal; GitHub attachment upload did NOT strip it). The same standard is mandatory for Jimeng/Kling/Qwen/Ernie etc., so the one marker covers the whole China-AIGC-labeled ecosystem. `aigc_label` reads **four image serializations** through a shared `_parse` helper: the HTML-entity-encoded XMP `TC260:AIGC` block in **either RDF form** — the nested element `{...}` (Doubao) or the attribute `TC260:AIGC="{...}"` (**PicWish**, `ContentProducer="picwish"`, verified on compatible samples) — via a container-agnostic raw-byte scan (any JSON object accepted), a raw-JSON PNG `AIGC` tEXt chunk (Doubao also writes the label this way, no namespaced marker at all — confirmed on compatible samples, `ContentProducer="doubao"`), a bare raw-JSON `{"AIGC":{...}}` object embedded in **JPEG EXIF (UserComment)** by some China-served generators, brace-matched from the scan head with `json.JSONDecoder().raw_decode` (no namespaced marker, no PNG chunk — confirmed on compatible samples, `ContentProducer="001191440300708461136T1308L"`), **and** a bare `AIGC{...}` blob (the label glued straight to its JSON, no `"AIGC":` key wrapper) embedded in a **JPEG APP segment near the JFIF header** — confirmed on compatible samples. The two raw-JSON forms are scanned in one loop (`'"AIGC"'` then `AIGC{`) that **falls through on a non-TC260 / undecodable hit instead of returning** — a quoted `"AIGC"` can appear later in an XMP packet while the real label is a bare `AIGC{...}` earlier in the file, so an unconditional early return on the quoted form would shadow the bare form (the exact bug behind the 06-10 misses). Native MP4/MOV is a fifth serialization: TC260-PG-20257A stores an `AIGC` key in `moov.udta.meta.keys` and the raw JSON in the matching `ilst` item. The seeking parser reaches a tail `moov` without reading `mdat`; removal replaces the key with `free` and blanks the validated value at the same length so every box size and stream offset stays fixed. All generic forms are gated on at least one TC260 field (`TC260_AIGC_FIELDS`) so a generic `AIGC` key cannot false-positive; the namespaced XMP element is unambiguous and needs no gate. `TC260_AIGC_FIELDS` covers **two schemas**: the producer-side one (`Label` / `ContentProducer` / `ProduceID` / `ContentPropagator` / `PropagateID`, Doubao and most China gens) and the **service-provider** one (`ServiceProvider` / `ServiceUser`, plus generic `Time` / `ContentId` which are NOT gated on) — **Tencent Cloud's** AIGC variant (`ServiceProvider` = `腾讯云`), embedded in **EXIF `ImageDescription`**, verified on compatible samples. In `identify`, `aigc` fires on the parsed label **or** the `AIGC_MARKERS` byte scan (the latter preserves the laundering-tell case where the JSON payload is truncated). +**Doubao** (ByteDance) uses it (verified on a public issue sample; `ContentProducer` `001191110102MACQD9K64010000`, no C2PA/SynthID/imwatermark — the XMP block is the only signal; GitHub attachment upload did NOT strip it). The same standard is mandatory for Jimeng/Kling/Qwen/Ernie etc., so the one marker covers the whole China-AIGC-labeled ecosystem. `aigc_label` reads **four image serializations** through a shared `_parse` helper: the HTML-entity-encoded XMP `TC260:AIGC` block in **either RDF form** — the nested element `{...}` (Doubao) or the attribute `TC260:AIGC="{...}"` (**PicWish**, `ContentProducer="picwish"`, verified on compatible samples) — via a container-agnostic raw-byte scan (any JSON object accepted), a raw-JSON PNG `AIGC` tEXt chunk (Doubao also writes the label this way, no namespaced marker at all — confirmed on compatible samples, `ContentProducer="doubao"`), a bare raw-JSON `{"AIGC":{...}}` object embedded in **JPEG EXIF (UserComment)** by some China-served generators, brace-matched from the scan head with `json.JSONDecoder().raw_decode` (no namespaced marker, no PNG chunk — confirmed on compatible samples, `ContentProducer="001191440300708461136T1308L"`), **and** a bare `AIGC{...}` blob (the label glued straight to its JSON, no `"AIGC":` key wrapper) embedded in a **JPEG APP segment near the JFIF header** — confirmed on compatible samples. The two raw-JSON forms are scanned in one loop (`'"AIGC"'` then `AIGC{`) that **falls through on a non-TC260 / undecodable hit instead of returning** — a quoted `"AIGC"` can appear later in an XMP packet while the real label is a bare `AIGC{...}` earlier in the file, so an unconditional early return on the quoted form would shadow the bare form (the exact bug behind the 06-10 misses). Native MP4/MOV is a fifth serialization: TC260-PG-20257A stores an `AIGC` key in `moov.udta.meta.keys` and the raw JSON in the matching `ilst` item; Doubao's iOS export additionally writes two QuickTime-form variants (a bare `meta` box as a direct `moov` child, and a keyless `hdlr=mdir` metadata list whose `ilst` data items carry the JSON with no key name) — both verified on retained carriers (2026-08-17). The seeking parser reaches a tail `moov` without reading `mdat`; removal replaces the key with `free` and blanks the validated value at the same length so every box size and stream offset stays fixed. All generic forms are gated on at least one TC260 field (`TC260_AIGC_FIELDS`) so a generic `AIGC` key cannot false-positive; the namespaced XMP element is unambiguous and needs no gate. `TC260_AIGC_FIELDS` covers **two schemas**: the producer-side one (`Label` / `ContentProducer` / `ProduceID` / `ContentPropagator` / `PropagateID`, Doubao and most China gens) and the **service-provider** one (`ServiceProvider` / `ServiceUser`, plus generic `Time` / `ContentId` which are NOT gated on) — **Tencent Cloud's** AIGC variant (`ServiceProvider` = `腾讯云`), embedded in **EXIF `ImageDescription`**, verified on compatible samples. In `identify`, `aigc` fires on the parsed label **or** the `AIGC_MARKERS` byte scan (the latter preserves the laundering-tell case where the JSON payload is truncated). Native MKV/WebM is a sixth serialization. TC260-PG-20257A stores `TagName=AIGC` and the raw JSON `TagString` in diff --git a/src/remove_ai_watermarks/_internal/constants.py b/src/remove_ai_watermarks/_internal/constants.py index e0f70b0..0dcbea7 100644 --- a/src/remove_ai_watermarks/_internal/constants.py +++ b/src/remove_ai_watermarks/_internal/constants.py @@ -171,6 +171,11 @@ AI_GENERATOR_TOKENS = frozenset( "dreamstudio", "novelai", "reve.com", + # Luma AI stamps PNG tEXt Source="Luma AI" / Comment="Generated by + # Luma AI's Uni-1 model (https://lumalabs.ai)"; the space-bearing token + # avoids matching incidental "luma" runs (luma/chroma key names etc.). + "luma ai", + "lumalabs", "aphrodite ai", "apple photos clean up", "fal-ai", diff --git a/src/remove_ai_watermarks/_internal/isobmff.py b/src/remove_ai_watermarks/_internal/isobmff.py index e192c55..86f440f 100644 --- a/src/remove_ai_watermarks/_internal/isobmff.py +++ b/src/remove_ai_watermarks/_internal/isobmff.py @@ -12,7 +12,10 @@ so pixel, video, and audio data is preserved bit-for-bit. TC260-PG-20257A video metadata is nested instead: ``moov.udta.meta.keys/ilst``. Its detector seeks through those boxes without reading media payloads, and its stripper blanks the validated key/value in -place so fast-start media offsets remain valid. +place so fast-start media offsets remain valid. Two serialization variants are +covered: the ISO form (``meta`` as a FullBox under ``udta``) and the QuickTime +form Doubao's iOS export writes (a bare ``meta`` box as a direct ``moov`` child, +no FullBox header). This file intentionally avoids dependencies on format-specific libraries (pillow-heif, pillow-jxl, pymp4) so it works on systems where they aren't @@ -184,68 +187,116 @@ def _tc260_key_indices( return found +# The box types a TC260-bearing ``meta`` box opens with or contains: ISO files +# have ``hdlr`` then ``keys``/``ilst``; QuickTime metadata lists have ``hdlr`` +# and ``ilst`` only. Which payload offset (0 vs 4) yields such children is what +# disambiguates the QuickTime form (no FullBox header) from the ISO one. +_META_CHILD_TYPES = frozenset({b"hdlr", b"keys", b"ilst"}) + + +def _meta_child_boxes( + stream: BinaryIO, + meta_payload: int, + meta_end: int, +) -> Iterator[tuple[int, int, bytes, int]]: + """Yield the child boxes of one ``meta`` box in either serialized form. + + ISO serializes ``meta`` as a FullBox, so its children start 4 bytes into + the payload; QuickTime writes a bare box, so they start at 0. Both real + forms open with a recognized child (``hdlr``), so the form is picked by + which offset's first box type is one of ``_META_CHILD_TYPES``; a wrong + probe reads garbage header bytes that match no known type. + """ + for offset in (0, 4): + boxes = iter_file_boxes(stream, meta_payload + offset, meta_end) + first = next(boxes, None) + if first is not None and first[2] in _META_CHILD_TYPES: + yield first + yield from boxes + return + + +def _iter_tc260_meta_boxes( + stream: BinaryIO, + moov_payload: int, + moov_end: int, +) -> Iterator[tuple[int, int]]: + """Yield ``(payload, end)`` of every ``meta`` box that may hold a TC260 label. + + The normative ISO placement is ``moov.udta.meta``; Doubao's iOS MOV export + instead stores the label in a QuickTime-form ``meta`` box that hangs + directly off ``moov``. Both are yielded so one consumer covers them. + """ + for _start, end, box_type, payload in iter_file_boxes(stream, moov_payload, moov_end): + if box_type == b"udta": + for _udta_start, udta_end, udta_type, udta_payload in iter_file_boxes(stream, payload, end): + if udta_type == b"meta": + yield udta_payload, udta_end + elif box_type == b"meta": + yield payload, end + + def _tc260_aigc_regions( stream: BinaryIO, file_size: int, -) -> list[tuple[int, int, int, int, bytes]]: +) -> list[tuple[tuple[int, int] | None, int, int, bytes]]: """Locate validated native TC260 entries without reading media payloads. - Each tuple is ``(key_start, key_end, value_start, value_end, value)``. + Each tuple is ``(key_span, value_start, value_end, value)``; ``key_span`` is + the byte span of the ``AIGC`` key when the normative ``keys`` box maps the + item, or None for the QuickTime metadata-list form (``hdlr=mdir``) Doubao's + iOS export writes, where the JSON sits in a bare ``ilst`` data item with no + key name to blank. """ - regions: list[tuple[int, int, int, int, bytes]] = [] + regions: list[tuple[tuple[int, int] | None, int, int, bytes]] = [] for _moov_start, moov_end, moov_type, moov_payload in iter_file_boxes(stream, 0, file_size): if moov_type != b"moov": continue - for _udta_start, udta_end, udta_type, udta_payload in iter_file_boxes( - stream, - moov_payload, - moov_end, - ): - if udta_type != b"udta": - continue - for _meta_start, meta_end, meta_type, meta_payload in iter_file_boxes( + for meta_payload, meta_end in _iter_tc260_meta_boxes(stream, moov_payload, moov_end): + keys: dict[int, tuple[int, int]] = {} + ilst_boxes: list[tuple[int, int]] = [] + keyed = False + for _child_start, child_end, child_type, child_payload in _meta_child_boxes( stream, - udta_payload, - udta_end, + meta_payload, + meta_end, ): - if meta_type != b"meta" or meta_payload + 4 > meta_end: - continue - keys: dict[int, tuple[int, int]] = {} - ilst_boxes: list[tuple[int, int]] = [] - for _child_start, child_end, child_type, child_payload in iter_file_boxes( + if child_type == b"keys": + keyed = True + keys.update(_tc260_key_indices(stream, child_payload, child_end)) + elif child_type == b"ilst": + ilst_boxes.append((child_payload, child_end)) + if not ilst_boxes: + continue + for ilst_payload, ilst_end in ilst_boxes: + for _item_start, item_end, item_type, item_payload in iter_file_boxes( stream, - meta_payload + 4, - meta_end, + ilst_payload, + ilst_end, ): - if child_type == b"keys": - keys.update(_tc260_key_indices(stream, child_payload, child_end)) - elif child_type == b"ilst": - ilst_boxes.append((child_payload, child_end)) - if not keys: - continue - for ilst_payload, ilst_end in ilst_boxes: - for _item_start, item_end, item_type, item_payload in iter_file_boxes( + index = int.from_bytes(item_type, "big") + key_span = keys.get(index) + if keyed and key_span is None: + # A keyed (ISO) meta box maps items through ``keys``; + # an unmapped index is not an AIGC entry, and reading + # its value would pull arbitrary metadata (e.g. cover + # art) through the JSON parser on every scan. Only the + # keyless QuickTime list falls through to content + # validation below. + continue + for _data_start, data_end, data_type, data_payload in iter_file_boxes( stream, - ilst_payload, - ilst_end, + item_payload, + item_end, ): - index = int.from_bytes(item_type, "big") - key_span = keys.get(index) - if key_span is None: + value_start = data_payload + 8 + value_size = data_end - value_start + if data_type != b"data" or value_size < 0 or value_size > MAX_TC260_VALUE_BYTES: continue - for _data_start, data_end, data_type, data_payload in iter_file_boxes( - stream, - item_payload, - item_end, - ): - value_start = data_payload + 8 - value_size = data_end - value_start - if data_type != b"data" or value_size < 0 or value_size > MAX_TC260_VALUE_BYTES: - continue - stream.seek(value_start) - value = stream.read(value_size) - if len(value) == value_size and parse_tc260_aigc_json(value) is not None: - regions.append((*key_span, value_start, data_end, value)) + stream.seek(value_start) + value = stream.read(value_size) + if len(value) == value_size and parse_tc260_aigc_json(value) is not None: + regions.append((key_span, value_start, data_end, value)) return regions @@ -257,7 +308,7 @@ def tc260_aigc_payloads(path: str | Path) -> tuple[bytes, ...]: return () stream.seek(0, 2) file_size = stream.tell() - return tuple(region[4] for region in _tc260_aigc_regions(stream, file_size)) + return tuple(region[3] for region in _tc260_aigc_regions(stream, file_size)) except OSError: return () @@ -268,6 +319,8 @@ def blank_tc260_aigc_tags(data: bytes) -> tuple[bytes, int]: Removing a nested ``ilst`` item would shift ``mdat`` in a fast-start MP4 and invalidate its chunk offsets. Replacing the four-byte key with ``free`` and the JSON value with spaces keeps every box size and media offset unchanged. + A keyless QuickTime metadata-list entry has no key name, so only its value + is blanked. """ if not is_isobmff(data): return data, 0 @@ -276,9 +329,10 @@ def blank_tc260_aigc_tags(data: bytes) -> tuple[bytes, int]: return data, 0 out = bytearray(data) key_spans: set[tuple[int, int]] = set() - for key_start, key_end, value_start, value_end, _value in regions: - key_spans.add((key_start, key_end)) - out[key_start:key_end] = b"free" + for key_span, value_start, value_end, _value in regions: + if key_span is not None: + key_spans.add(key_span) + out[key_span[0] : key_span[1]] = b"free" out[value_start:value_end] = b" " * (value_end - value_start) return bytes(out), len(key_spans) @@ -456,7 +510,7 @@ def strip_isobmff_media_file( max_scan=max_box_scan, ) tc260_regions = _tc260_aigc_regions(stream, file_size) if targets is not None else [] - tc260_key_spans = {(region[0], region[1]) for region in tc260_regions} + tc260_key_spans = {region[0] for region in tc260_regions if region[0] is not None} with atomic_video_output(output_path) as temporary_path: with source_path.open("rb") as source_stream, temporary_path.open("r+b") as temporary: @@ -466,9 +520,10 @@ def strip_isobmff_media_file( temporary.seek(box_start + 4) temporary.write(b"free") _overwrite_range(temporary, payload_start, box_end, byte=b"\x00") - for key_start, _key_end, value_start, value_end, _value in tc260_regions: - temporary.seek(key_start) - temporary.write(b"free") + for key_span, value_start, value_end, _value in tc260_regions: + if key_span is not None: + temporary.seek(key_span[0]) + temporary.write(b"free") _overwrite_range(temporary, value_start, value_end, byte=b" ") temporary.flush() os.fsync(temporary.fileno()) diff --git a/src/remove_ai_watermarks/identify.py b/src/remove_ai_watermarks/identify.py index ea54f0a..c30e9e8 100644 --- a/src/remove_ai_watermarks/identify.py +++ b/src/remove_ai_watermarks/identify.py @@ -796,6 +796,8 @@ _AI_VENDOR_TOKENS: tuple[tuple[str, str], ...] = ( ("fal-ai", "fal.ai"), ("bria", "Bria"), ("apple photos clean up", "Apple"), + ("luma ai", "Luma AI"), + ("lumalabs", "Luma AI"), ) diff --git a/src/remove_ai_watermarks/metadata.py b/src/remove_ai_watermarks/metadata.py index 8de83a0..84286eb 100644 --- a/src/remove_ai_watermarks/metadata.py +++ b/src/remove_ai_watermarks/metadata.py @@ -1184,6 +1184,11 @@ def get_ai_metadata(image_path: Path) -> dict[str, str]: if (aigc := aigc_label(image_path)) is not None: producer = aigc.get("ContentProducer", "") result["aigc_label"] = f"China AIGC label (TC260){f'; producer {producer}' if producer else ''}" + # The structural producer beside its display rendering: a machine + # consumer (video-visible provenance) must not parse the formatted + # sentence above, whose wording can change without notice. + if producer: + result["aigc_producer"] = producer app_scan = scan_head(image_path) app_provenance, app_generator = _app_metadata_evidence(app_scan) diff --git a/src/remove_ai_watermarks/video.py b/src/remove_ai_watermarks/video.py index 37a9dae..1b73018 100644 --- a/src/remove_ai_watermarks/video.py +++ b/src/remove_ai_watermarks/video.py @@ -239,6 +239,7 @@ def _visible_removal_plan( from remove_ai_watermarks.video_visible import ( VISIBLE_MARK_POLICIES, has_bytedance_video_provenance, + has_hailuo_video_provenance, has_sora_provenance, has_veo_provenance, stabilize_localizations, @@ -249,6 +250,7 @@ def _visible_removal_plan( "veo": has_veo_provenance, "seedance": has_bytedance_video_provenance, "dola": has_bytedance_video_provenance, + "hailuo": has_hailuo_video_provenance, }.get(selected_mark) policy = VISIBLE_MARK_POLICIES[selected_mark] regions = stabilize_localizations( diff --git a/src/remove_ai_watermarks/video_visible.py b/src/remove_ai_watermarks/video_visible.py index 29a5033..01947ed 100644 --- a/src/remove_ai_watermarks/video_visible.py +++ b/src/remove_ai_watermarks/video_visible.py @@ -890,11 +890,14 @@ class VisibleMarkPolicy: Every value here is MEASURED per provider; the arbiter itself (:func:`_stabilize_localizations`) is shared and knows nothing about providers. - ``accepts_provenance`` is load-bearing rather than cosmetic. Hailuo and Kling have - no metadata that could confirm them, so their rows force ``provenance=False``; that - used to be guaranteed structurally by wrappers that took no ``provenance`` - parameter at all, and this flag is what preserves the guarantee now that one entry - point serves every mark. + ``accepts_provenance`` is load-bearing rather than cosmetic. A vendor with no + metadata that could confirm it forces ``provenance=False``; that used to be + guaranteed structurally by wrappers that took no ``provenance`` parameter at + all, and this flag is what preserves the guarantee now that one entry point + serves every mark. Kling keeps the flag off (its TC260 producer code is image + evidence; no kling video corpus row ties a producer to the moving mark), while + Hailuo accepts it through the MiniMax TC260 label + (:func:`has_hailuo_video_provenance`). ``padding_fraction`` and ``mask_style`` belong to the removal plan rather than the arbiter, but they are per-provider constants like the rest, so they live on the same @@ -969,7 +972,9 @@ VISIBLE_MARK_POLICIES: dict[str, VisibleMarkPolicy] = { anchor_iou=0.80, padding_fraction=0.12, mask_style="box", - accepts_provenance=False, + # No provenance_weak_floor: the measured weak floor stays the entry bar; + # a MiniMax TC260 label only drops the strong-frame requirement for an + # already-stable run (see has_hailuo_video_provenance). ), "kling": VisibleMarkPolicy( weak_floor=_KLING_WEAK_CONFIDENCE, @@ -1458,3 +1463,15 @@ def has_bytedance_video_provenance(markers: dict[str, str]) -> bool: ).lower() source_type = markers.get("source_type", "").lower() return ("bytedance" in identity or "byteplus" in identity) and "trainedalgorithmicmedia" in source_type + + +def has_hailuo_video_provenance(markers: dict[str, str]) -> bool: + """Whether a TC260 label names MiniMax, Hailuo's maker, as the producer. + + Hailuo video exports carry no C2PA; their TC260 ``ContentProducer`` is the + bare name ``MiniMax`` (verified on retained MiniMax-hailuo clips). The + producer travels as its own structural marker (``aigc_producer``, set in + ``metadata.get_ai_metadata``), so this matches the field exactly rather + than parsing the human-readable ``aigc_label`` sentence. + """ + return markers.get("aigc_producer", "").strip().lower() == "minimax" diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 20349e9..733d59f 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -823,6 +823,42 @@ class TestExifGenerator: path = _img_with_software(tmp_path, "jpg", "Forever Editor 2.0") assert exif_generator(path) is None + def test_luma_ai_png_text_chunks_detected(self, tmp_path: Path): + # Luma AI stamps tEXt Software="Uni-1" (model name, not a token) plus + # Source/Comment values carrying "Luma AI"; the Source value must match. + from PIL.PngImagePlugin import PngInfo + + info = PngInfo() + info.add_text("Software", "Uni-1") + info.add_text("Source", "Luma AI") + info.add_text("Comment", "Generated by Luma AI's Uni-1 model (https://lumalabs.ai)") + path = tmp_path / "luma.png" + Image.new("RGB", (64, 64)).save(path, pnginfo=info) + assert exif_generator(path) == "Luma AI" + + def test_luma_token_not_overmatched(self, tmp_path: Path): + # The token is "luma ai" with the space: a bare "luma" (e.g. a luma + # chart tool) must not fire. + path = _img_with_software(tmp_path, "jpg", "Luma Chart Export 2.0") + assert exif_generator(path) is None + + def test_luma_removal_parity(self, tmp_path: Path): + from PIL.PngImagePlugin import PngInfo + + from remove_ai_watermarks.metadata import remove_ai_metadata + + info = PngInfo() + info.add_text("Software", "Uni-1") + info.add_text("Source", "Luma AI") + info.add_text("Comment", "Generated by Luma AI's Uni-1 model (https://lumalabs.ai)") + src = tmp_path / "luma.png" + Image.new("RGB", (64, 64)).save(src, pnginfo=info) + assert exif_generator(src) == "Luma AI" + + out = tmp_path / "clean.png" + remove_ai_metadata(src, out) + assert exif_generator(out) is None + def test_aphrodite_make_detected(self, tmp_path: Path): # Aphrodite AI writes EXIF Make="Aphrodite AI". exif = piexif.dump({"0th": {piexif.ImageIFD.Make: b"Aphrodite AI"}, "Exif": {}, "GPS": {}, "1st": {}}) diff --git a/tests/test_video.py b/tests/test_video.py index db7d0c2..2b2cbb9 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -65,6 +65,47 @@ def _video_with_tc260(path: Path, *, media_payload: bytes = _VIDEO_PAYLOAD) -> P return path +def _hdlr(handler: bytes) -> bytes: + # version/flags + pre_defined + handler_type + reserved[3]; the TC260 walker + # never parses it, but a real-shaped hdlr keeps the fixture honest. + return _box(b"hdlr", b"\x00\x00\x00\x00" + b"\x00\x00\x00\x00" + handler + b"\x00" * 12) + + +def _quicktime_meta(*children: bytes) -> bytes: + # QuickTime form: no 4-byte FullBox header before the children. + return _box(b"meta", b"".join(children)) + + +def _video_with_tc260_moov_meta(path: Path, *, media_payload: bytes = _VIDEO_PAYLOAD) -> Path: + """Doubao's iOS export form: a QuickTime ``meta`` box as a direct ``moov`` child.""" + keys = _box( + b"keys", + b"\x00\x00\x00\x00" + + (2).to_bytes(4, "big") + + _metadata_key(b"com.apple.quicktime.artwork") + + _metadata_key(b"AIGC"), + ) + ilst = _box( + b"ilst", + _metadata_value(1, b'{"source_type":"","data":{"product":"doubao"}}') + _metadata_value(2, _TC260_AIGC), + ) + meta = _quicktime_meta(_hdlr(b"mdta"), keys, ilst) + path.write_bytes(_MP4_FTYP + _box(b"mdat", media_payload) + _box(b"moov", meta)) + return path + + +def _video_with_tc260_mdir_list(path: Path, *, media_payload: bytes = _VIDEO_PAYLOAD) -> Path: + """Doubao's iOS QuickTime metadata list: ``udta.meta(hdlr=mdir)/ilst`` data + items with numeric indices and no ``keys`` box at all.""" + ilst = _box( + b"ilst", + _metadata_value(0, b"vid:standard-video-id") + _metadata_value(0, _TC260_AIGC), + ) + meta = _quicktime_meta(_hdlr(b"mdir"), ilst) + path.write_bytes(_MP4_FTYP + _box(b"mdat", media_payload) + _box(b"moov", _box(b"udta", meta))) + return path + + def _ebml_size(value: int) -> bytes: for length in range(1, 9): if value < (1 << (7 * length)) - 1: @@ -612,6 +653,67 @@ class TestVideoMetadataApi: assert b"AIGC" not in cleaned assert _TC260_AIGC not in cleaned + @pytest.mark.parametrize("suffix", [".mp4", ".mov"]) + def test_inspects_native_tc260_in_quicktime_moov_meta(self, tmp_path: Path, suffix: str): + # Doubao's iOS export stores the label in a QuickTime-form meta box + # (no FullBox header) hanging directly off moov, not under udta. + from remove_ai_watermarks.video import inspect_video_metadata + + source = _video_with_tc260_moov_meta(tmp_path / f"source{suffix}") + + report = inspect_video_metadata(source) + + assert report.has_ai_metadata is True + assert report.markers["aigc_label"].endswith("producer 00119144030008867405X210002") + + def test_removes_native_tc260_in_quicktime_moov_meta(self, tmp_path: Path): + from remove_ai_watermarks.video import remove_video_metadata + + source = _video_with_tc260_moov_meta(tmp_path / "source.mov") + output = tmp_path / "clean.mov" + + result = remove_video_metadata(source, output) + cleaned = output.read_bytes() + + assert result.detected["aigc_label"].startswith("China AIGC label") + # The artwork item's Doubao product JSON is app-export provenance, a + # separate signal the TC260 blanker does not touch -- as on the real + # Doubao iOS export this fixture mirrors. + assert result.remaining == {"app_provenance": "App export provenance (ByteDance Doubao)"} + assert len(cleaned) == source.stat().st_size + assert _VIDEO_PAYLOAD in cleaned + assert _TC260_AIGC not in cleaned + + def test_inspects_native_tc260_in_quicktime_mdir_metadata_list(self, tmp_path: Path): + # The second Doubao iOS variant: a QuickTime metadata list under + # udta.meta with hdlr=mdir and ilst data items but no keys box. + from remove_ai_watermarks.video import inspect_video_metadata + + source = _video_with_tc260_mdir_list(tmp_path / "source.mov") + + report = inspect_video_metadata(source) + + assert report.has_ai_metadata is True + assert "aigc_label" in report.markers + + def test_removes_native_tc260_in_quicktime_mdir_metadata_list(self, tmp_path: Path): + from remove_ai_watermarks.video import remove_video_metadata + + source = _video_with_tc260_mdir_list(tmp_path / "source.mov") + output = tmp_path / "clean.mov" + + result = remove_video_metadata(source, output) + cleaned = output.read_bytes() + + assert result.detected["aigc_label"].startswith("China AIGC label") + assert result.remaining == {} + assert len(cleaned) == source.stat().st_size + assert _VIDEO_PAYLOAD in cleaned + # The keyless entry has no key name to blank; only the JSON is spaced + # out, and the neighboring standard ilst item survives untouched. + assert _TC260_AIGC not in cleaned + assert b"vid:standard-video-id" in cleaned + def test_streams_large_isobmff_without_full_file_read( self, tmp_path: Path, @@ -1894,6 +1996,25 @@ class TestAdditionalProviderTemporalArbiter: assert stabilize(weak) == [None] * 12 assert stabilize(strong) == [box] * 12 + def test_hailuo_provenance_accepts_sub_strong_stable_run(self): + # A TC260 label naming MiniMax relaxes only the strong-frame requirement: + # the entry bar stays the measured weak floor (0.30), so a stable run + # between weak and strong (0.34) passes with provenance, fails without. + from remove_ai_watermarks.video_visible import FrameLocalization, stabilize_localizations + + detections = [FrameLocalization(index, 0.31, self._HAILUO_BOX) for index in range(12)] + + assert stabilize_localizations("hailuo", detections, provenance=False) == [None] * 12 + assert stabilize_localizations("hailuo", detections, provenance=True) == [self._HAILUO_BOX] * 12 + + def test_hailuo_provenance_requires_a_minimax_producer_label(self): + from remove_ai_watermarks.video_visible import has_hailuo_video_provenance + + assert has_hailuo_video_provenance({"aigc_producer": "MiniMax"}) + assert not has_hailuo_video_provenance({"aigc_producer": "001191110102MACQD9K64010000"}) + assert not has_hailuo_video_provenance({"aigc_label": "China AIGC label (TC260); producer MiniMax"}) + assert not has_hailuo_video_provenance({"issuer": "Some other vendor"}) + class TestVideoVisibleScan: def test_auto_prepares_each_frame_once_for_every_detector( From 2e1428406e74c03db980e319386bddf7a03e6964 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Tue, 18 Aug 2026 11:02:18 -0700 Subject: [PATCH 18/44] Release 0.27.0 --- .gitignore | 1 + pyproject.toml | 2 +- src/remove_ai_watermarks/__init__.py | 2 +- uv.lock | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 5690373..50375b9 100644 --- a/.gitignore +++ b/.gitignore @@ -54,6 +54,7 @@ gfpgan/ # deeper in the tree is not caught. /.agents/ /.codex/ +/sch_*.yaml /.opencode/ /opencode.json /opencode.jsonc diff --git a/pyproject.toml b/pyproject.toml index cb1d37c..d4ca275 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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.26.3" +version = "0.27.0" description = "AI watermark remover for visible, invisible, and provenance marks in images and video" readme = "README.md" requires-python = ">=3.11,<3.15" diff --git a/src/remove_ai_watermarks/__init__.py b/src/remove_ai_watermarks/__init__.py index 857937a..247fb97 100644 --- a/src/remove_ai_watermarks/__init__.py +++ b/src/remove_ai_watermarks/__init__.py @@ -32,7 +32,7 @@ _os.environ.setdefault("TRANSFORMERS_VERBOSITY", "error") _warnings.filterwarnings("ignore", message=r".*ImageProcessorFast.*") -__version__ = "0.26.3" +__version__ = "0.27.0" __all__ = [ "BatchSummary", diff --git a/uv.lock b/uv.lock index 4fa8b82..fa3a725 100644 --- a/uv.lock +++ b/uv.lock @@ -2508,7 +2508,7 @@ wheels = [ [[package]] name = "remove-ai-watermarks" -version = "0.26.3" +version = "0.27.0" source = { editable = "." } dependencies = [ { name = "c2pa-python" }, From 50d9f44829078647688d124e2be6524ff521235a Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Tue, 18 Aug 2026 11:03:53 -0700 Subject: [PATCH 19/44] Guide text-heavy content away from forced diffusion scrubs Issue #84: text, tables, and UI screenshots are the worst case for --force regeneration. Document that the default pipeline skips the invisible stage without a detectable signal, that visible/metadata stages never redraw glyphs, and why paste-back is not offered. --- docs/known-limitations.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/known-limitations.md b/docs/known-limitations.md index bf66a78..d986311 100644 --- a/docs/known-limitations.md +++ b/docs/known-limitations.md @@ -57,6 +57,19 @@ Invisible removal does not decode and delete a payload. It regenerates the image through a diffusion pipeline. Faces, text, colors, and fine detail can change even when the watermark is successfully disrupted. +Text, tables, and UI screenshots are the worst case for a forced scrub: the +model redraws glyphs as plausible-but-wrong shapes. Do not run the invisible +stage on such content unless a locally-detectable invisible signal exists. +The default `all`, `invisible`, and `batch` commands already skip it then, and +`visible` (which strips AI metadata by default) or `metadata` alone never +redraws a glyph outside the filled watermark box. `--force` is for content you +know carries a pixel watermark despite no local signal; on a clean screenshot +it only buys distortion. Preserving or pasting back the original text pixels +during a real scrub is deliberately not offered: an invisible watermark such +as SynthID lives in the pixels everywhere, including inside the glyphs, so +frozen text regions would keep the watermark +([text protection research](text-protection-research.md)). + `qwen-zimage` is the default profile and `sdxl-zimage` the only alternative. Both are CUDA only and differ only in the global regeneration model: each conditions that stage on a canny edge map, which preserves structure but not From 42d427731f993502800e7b9b074f385eaa37d3d5 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Tue, 18 Aug 2026 21:23:25 -0700 Subject: [PATCH 20/44] Turn the verified-text fidelity anchor off by default The whole-frame 15% Qwen-VAE blend returned detector-visible OpenAI SynthID on poster-scale manifests through the engine text-manifest path (official Content Provenance API, 2026-08-19: restored detected x6 with the anchor, clean x6 without it; base outputs clean x6; pixel-identical stripped controls detected, proving the pixel channel). Add fidelity_anchor=False to remove_watermark and InvisibleOptions and --fidelity-anchor on the CLI to reproduce the 0.27.0 research behavior. Text-box MAE cost of the new default is under one point on all three fixtures (11.60->11.72, 7.79->7.86, 7.57->8.13). --- docs/cli.md | 6 +++++ docs/python-api.md | 7 +++++ docs/text-protection-research.md | 26 +++++++++++++++++++ pyproject.toml | 2 +- src/remove_ai_watermarks/__init__.py | 2 +- .../_internal/qwen_zimage_pipeline.py | 14 ++++++++-- src/remove_ai_watermarks/api.py | 2 ++ src/remove_ai_watermarks/cli.py | 16 ++++++++++++ src/remove_ai_watermarks/invisible_engine.py | 11 ++++++++ tests/test_invisible_engine.py | 17 ++++++++++++ tests/test_qwen_zimage_pipeline.py | 10 ++++++- uv.lock | 2 +- 12 files changed, 109 insertions(+), 6 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index a3386a5..6963d0c 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -416,6 +416,12 @@ remove-ai-watermarks invisible image.png -o clean.png \ --pipeline qwen-zimage --text-manifest verified-lines.json --force ``` +Since 0.27.1 the global 15% Qwen-VAE fidelity-anchor blend is off by default: it +was measured to return detector-visible OpenAI SynthID on poster-scale manifests +(official Content Provenance API, 2026-08-19). `--fidelity-anchor` restores the +0.27.0 research behavior; text-box fidelity lost by the default is well under one +MAE point on the measured fixtures. + The manifest is a JSON object with `schema_version: 1`, `verified: true`, decoded RGB dimensions, `source_pixel_sha256`, and a non-empty `lines` array. Each line has an integer `[x1, y1, x2, y2]` box, exact `text`, a non-empty `script`, and an optional diff --git a/docs/python-api.md b/docs/python-api.md index 385ea0e..55b4662 100644 --- a/docs/python-api.md +++ b/docs/python-api.md @@ -614,6 +614,13 @@ postprocessing combinations that were not evaluated. `InvisibleOptions` exposes same field for `remove_all`; after a visible-stage edit, the manifest must be built against the staged pixels rather than the pristine source. +Since 0.27.1 the mode's global 15% Qwen-VAE fidelity-anchor blend is **off by +default** (`fidelity_anchor=False`): that whole-frame blend was measured to +return detector-visible OpenAI SynthID on poster-scale manifests (official +Content Provenance API, 2026-08-19 - detected x6 with the anchor, clean x6 +without it, controls and base outputs validated in the same sessions). Pass +`fidelity_anchor=True` to reproduce the 0.27.0 research behavior. + `remove_watermark` takes strength, seed, tiling, resolution, and postprocessing controls. It takes no model id, step count or guidance scale, and neither does the constructor: each profile pins its model stack, its per-stage schedule and CFG diff --git a/docs/text-protection-research.md b/docs/text-protection-research.md index 01bc1b4..84775de 100644 --- a/docs/text-protection-research.md +++ b/docs/text-protection-research.md @@ -343,3 +343,29 @@ The stable errors were punctuation: one lost English comma and an ideographic comma consistently replaced by ASCII. Thus crop agreement and confidence do not verify literal text. The helper is retained only to seed annotations; every proposal needs manual review before it can enter the restoration compositor. + +## Production-path anchor leak and fix (2026-08-19) + +The engine `text_manifest=` path (unlike the research compositor above) blends a +15% Qwen-VAE fidelity anchor across the WHOLE frame before glyph restoration. +Certified through the official OpenAI Content Provenance API on the exact +production path (qwen-zimage, seed 0, no strength override, untiled, LANCZOS +resize-back, metadata strip; library 0.27.0 @ 50d9f448): + +- controls (pixel-identical metadata-stripped sources): `synthid: detected` - + the pixel channel was proven live before every session; +- base outputs: `clean` x6 (two 20-line posters, 3 attempts each); +- text-restored WITH the anchor: `synthid: detected` x6, deterministic; +- text-restored with `FIDELITY_BLEND_ALPHA = 0` (runtime ablation, base bytes + identical between runs): `clean` x6. + +The anchor was therefore the leak channel: a 15% global donor admixture over a +scrubbed frame returns enough OpenAI SynthID for the provider detector. Fidelity +cost of dropping it (source-referenced text-box MAE): 11.60->11.72, 7.79->7.86, +and 7.57->8.13 across the two posters and the CJK sign - well under one point +against base MAEs of 24.7/16.8/10.2. Since 0.27.1 the anchor is off by default +(`fidelity_anchor` parameter on `remove_watermark` / `InvisibleOptions` / +`--fidelity-anchor`); Google rows were not re-queried because the default +strength leaves that fixture's base detected (the vendor-strength lane tracks +that separately). Certification artifacts: raiw-app +`data/certification/text-restoration-2026-08-19-A1/` (sha256-stamped rows). diff --git a/pyproject.toml b/pyproject.toml index d4ca275..0484fcc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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.27.0" +version = "0.27.1" description = "AI watermark remover for visible, invisible, and provenance marks in images and video" readme = "README.md" requires-python = ">=3.11,<3.15" diff --git a/src/remove_ai_watermarks/__init__.py b/src/remove_ai_watermarks/__init__.py index 247fb97..7252578 100644 --- a/src/remove_ai_watermarks/__init__.py +++ b/src/remove_ai_watermarks/__init__.py @@ -32,7 +32,7 @@ _os.environ.setdefault("TRANSFORMERS_VERBOSITY", "error") _warnings.filterwarnings("ignore", message=r".*ImageProcessorFast.*") -__version__ = "0.27.0" +__version__ = "0.27.1" __all__ = [ "BatchSummary", diff --git a/src/remove_ai_watermarks/_internal/qwen_zimage_pipeline.py b/src/remove_ai_watermarks/_internal/qwen_zimage_pipeline.py index 64671a9..b441552 100644 --- a/src/remove_ai_watermarks/_internal/qwen_zimage_pipeline.py +++ b/src/remove_ai_watermarks/_internal/qwen_zimage_pipeline.py @@ -1071,6 +1071,7 @@ class QwenZImagePipeline: tile_size: int = 1024, tile_overlap: int = 128, text_manifest: VerifiedTextManifest | None = None, + fidelity_anchor: bool = False, ) -> Image.Image: """Execute global regeneration and masked face repair.""" self._require_cuda() @@ -1120,7 +1121,16 @@ class QwenZImagePipeline: restore_verified_text, ) - self._progress("Blending the Qwen-VAE fidelity anchor...") - anchor = blend_fidelity_anchor(result, donor) + # The anchor is OFF by default since 0.27.1: blending 15% of the Qwen-VAE + # donor ACROSS THE WHOLE FRAME returned detector-visible OpenAI SynthID on + # poster-scale manifests (official Content Provenance API, 2026-08-19: + # detected x6 with the anchor, clean x6 without it, base clean throughout; + # see docs/text-protection-research.md). ``fidelity_anchor=True`` keeps the + # 0.27.0 research behavior for reproduction. + if fidelity_anchor: + self._progress("Blending the Qwen-VAE fidelity anchor...") + anchor = blend_fidelity_anchor(result, donor) + else: + anchor = result self._progress(f"Restoring {len(text_manifest.lines)} verified text lines...") return restore_verified_text(image, anchor, donor, text_manifest.lines) diff --git a/src/remove_ai_watermarks/api.py b/src/remove_ai_watermarks/api.py index 117623f..ae80ea9 100644 --- a/src/remove_ai_watermarks/api.py +++ b/src/remove_ai_watermarks/api.py @@ -244,6 +244,7 @@ class InvisibleOptions: tile_size: int = 1024 tile_overlap: int = 128 text_manifest: Path | None = None + fidelity_anchor: bool = False # What the invisible stage did. "unavailable" is the one outcome the caller must @@ -525,6 +526,7 @@ def _run_invisible( tile_size=opts.tile_size, tile_overlap=opts.tile_overlap, text_manifest=opts.text_manifest, + fidelity_anchor=opts.fidelity_anchor, ) say("invisible", "removed") return "removed" diff --git a/src/remove_ai_watermarks/cli.py b/src/remove_ai_watermarks/cli.py index 4849edb..1f15a3d 100644 --- a/src/remove_ai_watermarks/cli.py +++ b/src/remove_ai_watermarks/cli.py @@ -321,6 +321,16 @@ _text_manifest_option = click.option( ), ) +_fidelity_anchor_option = click.option( + "--fidelity-anchor/--no-fidelity-anchor", + default=False, + help=( + "With --text-manifest: blend 15% of the Qwen-VAE donor across the whole " + "frame. Off by default - the global blend was measured to return " + "detector-visible OpenAI SynthID on poster-scale manifests." + ), +) + _visible_backend_option = click.option( "--backend", @@ -798,6 +808,7 @@ def cmd_erase( @_force_option @_cpu_offload_option @_text_manifest_option +@_fidelity_anchor_option @click.pass_context def cmd_invisible( ctx: click.Context, @@ -818,6 +829,7 @@ def cmd_invisible( force: bool, cpu_offload: bool, text_manifest: Path | None, + fidelity_anchor: bool, ) -> None: """Remove invisible AI watermarks (SynthID, StableSignature, TreeRing). @@ -880,6 +892,7 @@ def cmd_invisible( tile_size=tile_size, tile_overlap=tile_overlap, text_manifest=text_manifest, + fidelity_anchor=fidelity_anchor, ) except (OSError, RuntimeError, ValueError) as exc: console.print(f" Error: {exc}") @@ -1428,6 +1441,7 @@ def cmd_identify(ctx: click.Context, source: Path, no_visible: bool, as_json: bo @_force_option @_cpu_offload_option @_text_manifest_option +@_fidelity_anchor_option @click.pass_context def cmd_all( ctx: click.Context, @@ -1450,6 +1464,7 @@ def cmd_all( force: bool, cpu_offload: bool, text_manifest: Path | None, + fidelity_anchor: bool, ) -> None: """Remove ALL watermarks: visible + invisible + metadata. @@ -1528,6 +1543,7 @@ def cmd_all( tile_size=tile_size, tile_overlap=tile_overlap, text_manifest=text_manifest, + fidelity_anchor=fidelity_anchor, ), force=force, progress=progress, diff --git a/src/remove_ai_watermarks/invisible_engine.py b/src/remove_ai_watermarks/invisible_engine.py index 4ca8a59..93c75bc 100644 --- a/src/remove_ai_watermarks/invisible_engine.py +++ b/src/remove_ai_watermarks/invisible_engine.py @@ -150,6 +150,7 @@ class InvisibleEngine: tile_size: int = 1024, tile_overlap: int = 128, text_manifest: Path | None = None, + fidelity_anchor: bool = False, ) -> Path: """Remove invisible watermark from an image. @@ -187,6 +188,13 @@ class InvisibleEngine: Requires the ``text-restoration`` extra and the ``qwen-zimage`` profile. Incompatible with tiling, downscaling, humanize, unsharp, and adaptive polish because those combinations are not calibrated. + fidelity_anchor: Blend 15% of the Qwen-VAE donor across the whole frame + before glyph restoration. OFF by default since 0.27.1: that global + blend was measured to return detector-visible OpenAI SynthID on + poster-scale manifests (detected x6 with the anchor vs clean x6 + without it, base clean; official Content Provenance API, + 2026-08-19 - docs/text-protection-research.md). ``True`` reproduces + the 0.27.0 research behavior. Requires ``text_manifest``. Returns: Path to the cleaned image. @@ -196,6 +204,8 @@ class InvisibleEngine: seed = resolve_seed(seed) adaptive_polish = resolve_adaptive_polish(adaptive_polish, self._remover.model_profile) + if fidelity_anchor and text_manifest is None: + raise ValueError("fidelity_anchor requires a text manifest") if text_manifest is not None: if self._remover.model_profile != QWEN_ZIMAGE_PROFILE: raise ValueError("--text-manifest is supported only by the qwen-zimage profile") @@ -270,6 +280,7 @@ class InvisibleEngine: tile_size=tile_size, tile_overlap=tile_overlap, text_manifest=verified_text, + fidelity_anchor=fidelity_anchor, ) # Post-processing chain: decode the diffusion output ONCE, apply the diff --git a/tests/test_invisible_engine.py b/tests/test_invisible_engine.py index f459aa0..320fa9e 100644 --- a/tests/test_invisible_engine.py +++ b/tests/test_invisible_engine.py @@ -73,6 +73,15 @@ class TestVerifiedTextMode: **kwargs, ) + def test_rejects_fidelity_anchor_without_manifest(self, tmp_path): + import pytest + + with pytest.raises(ValueError, match="fidelity_anchor requires a text manifest"): + self._engine().remove_watermark( + tmp_path / "unused.png", + fidelity_anchor=True, + ) + def test_loads_and_forwards_verified_manifest(self, tmp_path, monkeypatch): import json @@ -111,6 +120,14 @@ class TestVerifiedTextMode: engine.remove_watermark(source, output, text_manifest=manifest) assert seen["text_manifest"].lines[0].text == "Exact" + # Leak-safe default since 0.27.1: the global 15% donor blend is OFF unless + # explicitly requested (measured to return detector-visible OpenAI SynthID + # on poster-scale manifests; see docs/text-protection-research.md). + assert seen["fidelity_anchor"] is False + + engine.remove_watermark(source, output, text_manifest=manifest, fidelity_anchor=True) + + assert seen["fidelity_anchor"] is True class TestNativeOutputSize: diff --git a/tests/test_qwen_zimage_pipeline.py b/tests/test_qwen_zimage_pipeline.py index e1b4824..7e9eadc 100644 --- a/tests/test_qwen_zimage_pipeline.py +++ b/tests/test_qwen_zimage_pipeline.py @@ -646,9 +646,17 @@ def test_no_face_path_still_runs_verified_text_restoration(monkeypatch): result = pipeline.run(source, strength=0.1, seed=0, text_manifest=manifest) + assert result is restored + # Off by default since 0.27.1 (leak finding, docs/text-protection-research.md): + # no whole-frame donor blend; the raw global result feeds restoration. + blend.assert_not_called() + restore.assert_called_once_with(source, global_result, donor, manifest.lines) + + result = pipeline.run(source, strength=0.1, seed=0, text_manifest=manifest, fidelity_anchor=True) + assert result is restored blend.assert_called_once_with(global_result, donor) - restore.assert_called_once_with(source, anchor, donor, manifest.lines) + restore.assert_called_with(source, anchor, donor, manifest.lines) def test_watermark_remover_dispatches_to_full_pipeline(tmp_path, monkeypatch): diff --git a/uv.lock b/uv.lock index fa3a725..ee7fc4c 100644 --- a/uv.lock +++ b/uv.lock @@ -2508,7 +2508,7 @@ wheels = [ [[package]] name = "remove-ai-watermarks" -version = "0.27.0" +version = "0.27.1" source = { editable = "." } dependencies = [ { name = "c2pa-python" }, From 2298282b8b0366a4d67f33410a672030597463ed Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Tue, 18 Aug 2026 21:32:57 -0700 Subject: [PATCH 21/44] Thread fidelity_anchor through WatermarkRemover The engine forwards the new kwarg into WatermarkRemover.remove_watermark, which rejected it with TypeError on the real (non-fake) path - caught by the deployed Modal smoke test, not by the unit fakes. --- src/remove_ai_watermarks/_internal/watermark_remover.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/remove_ai_watermarks/_internal/watermark_remover.py b/src/remove_ai_watermarks/_internal/watermark_remover.py index b704e27..7d6ce94 100644 --- a/src/remove_ai_watermarks/_internal/watermark_remover.py +++ b/src/remove_ai_watermarks/_internal/watermark_remover.py @@ -190,6 +190,7 @@ class WatermarkRemover: tile_size: int = 1024, tile_overlap: int = 128, text_manifest: VerifiedTextManifest | None = None, + fidelity_anchor: bool = False, ) -> Path: """Regenerate image pixels and write the result without AI metadata. @@ -219,6 +220,7 @@ class WatermarkRemover: tile_size=tile_size, tile_overlap=tile_overlap, text_manifest=text_manifest, + fidelity_anchor=fidelity_anchor, ) self._write_output(result, destination) return destination From 3cd1e479352916f993a608a3de8898815abd2ab2 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Tue, 18 Aug 2026 22:34:41 -0700 Subject: [PATCH 22/44] Add the oracle-measured Google strength floor to qwen-zimage The resolution curve's 0.154 top left the 4.33 MP CJK-sign fixture SynthID-detected x3 in Gemini on the full production path (visible stage -> qwen-zimage seed 0 -> resize-back -> metadata strip), with a valid pixel-identical stripped control in the same session (2026-08-18). Google-provenance content now resolves to the flat QWEN_ZIMAGE_GOOGLE_STRENGTH 0.30 floor instead of the area curve - anchors at 0.30 measured clean in Gemini on two fixtures (CJK sign + 18-face) at 3/3 checks across two work accounts, and stayed clean under the vae-glyphs donor layer. openai/unknown content keeps the curve; an explicit strength still wins. --- docs/known-limitations.md | 8 +++++-- docs/synthid.md | 12 +++++++++++ pyproject.toml | 2 +- src/remove_ai_watermarks/__init__.py | 2 +- .../_internal/watermark_profiles.py | 21 ++++++++++++++++--- tests/test_platform.py | 14 +++++++++---- tests/test_qwen_zimage_pipeline.py | 10 ++++++--- uv.lock | 2 +- 8 files changed, 56 insertions(+), 15 deletions(-) diff --git a/docs/known-limitations.md b/docs/known-limitations.md index d986311..137df50 100644 --- a/docs/known-limitations.md +++ b/docs/known-limitations.md @@ -178,8 +178,12 @@ The two profiles resolve an unset strength differently, because different things were measured for each. `qwen-zimage` reads it from image area, through the resolution-adaptive denoise -curve. The vendor is deliberately ignored: the curve, not the issuer, is what was -calibrated. +curve. The vendor is deliberately ignored for openai/unknown content: the curve, +not the issuer, is what was calibrated. One measured exception (0.27.2): +Google-provenance content takes the flat `QWEN_ZIMAGE_GOOGLE_STRENGTH` 0.30 floor +- the curve's 0.154 top left a 4.33 MP fixture oracle-detected on the full +production path (2026-08-18), while 0.30 anchors measured clean on two fixtures +in two accounts. `sdxl-zimage` reads it from the C2PA issuer, on a flat ladder: diff --git a/docs/synthid.md b/docs/synthid.md index 35c9caf..2935af2 100644 --- a/docs/synthid.md +++ b/docs/synthid.md @@ -769,3 +769,15 @@ reproducible verification requires a fixed seed. 8. Jiang et al. (2025). **VideoMarkBench: Benchmarking Robustness of Video Watermarking.** arXiv:2505.21620. https://arxiv.org/abs/2505.21620 + +**Google floor on qwen-zimage (0.27.2, 2026-08-19).** The bracket above did not +survive the full production path: on the 4.33 MP CJK-sign fixture (visible-stage +sparkle removal -> qwen-zimage seed 0 at the curve's 0.154 top -> resize-back -> +metadata strip), the Gemini verifier detected SynthID x3 with a valid +pixel-identical stripped control in the same session. Google-provenance content +now takes a flat `QWEN_ZIMAGE_GOOGLE_STRENGTH = 0.30` floor instead of the area +curve - 0.30 anchors measured clean in Gemini on two fixtures (CJK sign + +18-face) at 3/3 checks across two work accounts, and stayed clean with the +vae-glyphs donor layer on top; openai/unknown content keeps the resolution +curve. An explicit strength still wins. Certification artifacts: raiw-app +`data/certification/text-restoration-2026-08-18/` (the failing re-baseline). diff --git a/pyproject.toml b/pyproject.toml index 0484fcc..aded025 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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.27.1" +version = "0.27.2" description = "AI watermark remover for visible, invisible, and provenance marks in images and video" readme = "README.md" requires-python = ">=3.11,<3.15" diff --git a/src/remove_ai_watermarks/__init__.py b/src/remove_ai_watermarks/__init__.py index 7252578..f9e8ccf 100644 --- a/src/remove_ai_watermarks/__init__.py +++ b/src/remove_ai_watermarks/__init__.py @@ -32,7 +32,7 @@ _os.environ.setdefault("TRANSFORMERS_VERBOSITY", "error") _warnings.filterwarnings("ignore", message=r".*ImageProcessorFast.*") -__version__ = "0.27.1" +__version__ = "0.27.2" __all__ = [ "BatchSummary", diff --git a/src/remove_ai_watermarks/_internal/watermark_profiles.py b/src/remove_ai_watermarks/_internal/watermark_profiles.py index d2b2cf4..1e7468e 100644 --- a/src/remove_ai_watermarks/_internal/watermark_profiles.py +++ b/src/remove_ai_watermarks/_internal/watermark_profiles.py @@ -69,6 +69,14 @@ SDXL_ZIMAGE_OPENAI_STRENGTH = 0.15 SDXL_ZIMAGE_GEMINI_STRENGTH = 0.25 SDXL_ZIMAGE_UNKNOWN_STRENGTH = SDXL_ZIMAGE_GEMINI_STRENGTH +# qwen-zimage keeps its resolution curve for openai/unknown content, but Google +# content takes this flat oracle-measured floor instead of the curve (whose top, +# 0.154, left a 4.33 MP CJK-sign fixture detected x3 on the full production path +# with a valid pixel control, 2026-08-18). 0.30 anchors measured clean in Gemini +# across two fixtures and two work accounts (CJK sign + 18-face, 3/3 checks each, +# 2026-08 research) and stayed clean under the vae-glyphs donor layer on top. +QWEN_ZIMAGE_GOOGLE_STRENGTH = 0.30 + # sdxl-zimage picks its strength from the VENDOR (unlike qwen-zimage, which derives it # from image area). An unlisted or unknown vendor falls back to the Gemini value. @@ -103,7 +111,8 @@ def resolve_adaptive_polish(adaptive_polish: bool | None, pipeline: str) -> bool def strength_default_help() -> str: """Describe the live default policy without duplicating its values.""" return ( - "profile-adaptive (qwen-zimage uses resolution-adaptive denoise; sdxl-zimage " + "profile-adaptive (qwen-zimage uses resolution-adaptive denoise, with a " + f"flat oracle-measured Google floor of {QWEN_ZIMAGE_GOOGLE_STRENGTH}; sdxl-zimage " f"uses OpenAI {SDXL_ZIMAGE_OPENAI_STRENGTH} / Google {SDXL_ZIMAGE_GEMINI_STRENGTH} / " f"unknown {SDXL_ZIMAGE_UNKNOWN_STRENGTH}, from the C2PA issuer)" ) @@ -118,10 +127,14 @@ def resolve_strength( ) -> float: """Resolve a user override or the calibrated policy for a profile and vendor. - Total by design. qwen-zimage picks its strength from image area rather than from - the vendor, so it needs ``size``; returning ``None`` for it instead would push that + Total by design. qwen-zimage picks its strength from image area rather than + from the vendor, so it needs ``size``; returning ``None`` for it instead would push that branch onto every caller and move one of the two strength policies outside this module. ``size`` is required for qwen-zimage without an explicit strength. + One measured exception since 0.27.2: Google-provenance content on qwen-zimage + takes the flat ``QWEN_ZIMAGE_GOOGLE_STRENGTH`` floor instead of the area curve + (the curve's 0.154 top left a 4.33 MP fixture oracle-detected on the full + production path; see the constant's comment). """ if strength is not None: return strength @@ -129,6 +142,8 @@ def resolve_strength( return _SDXL_ZIMAGE_STRENGTH_BY_VENDOR.get((vendor or "").casefold(), SDXL_ZIMAGE_UNKNOWN_STRENGTH) if size is None: raise ValueError("qwen-zimage resolves strength from image area, so size is required") + if (vendor or "").casefold() == "google": + return QWEN_ZIMAGE_GOOGLE_STRENGTH from remove_ai_watermarks._internal.qwen_zimage_pipeline import resolution_adaptive_denoise return resolution_adaptive_denoise(*size) diff --git a/tests/test_platform.py b/tests/test_platform.py index 337e68e..bf81d43 100644 --- a/tests/test_platform.py +++ b/tests/test_platform.py @@ -14,6 +14,7 @@ import pytest from remove_ai_watermarks._internal.utils import get_image_format, is_supported_format from remove_ai_watermarks._internal.watermark_profiles import ( PROFILE_CHOICES, + QWEN_ZIMAGE_GOOGLE_STRENGTH, REMOVAL_MODULES, SDXL_ZIMAGE_GEMINI_STRENGTH, SDXL_ZIMAGE_OPENAI_STRENGTH, @@ -204,16 +205,21 @@ class TestResolveStrength: qwen-zimage picks strength from image area, so it takes the size. Returning None for it would push that branch onto every caller and leave one of the two - strength policies living outside this module. The vendor is ignored here on - purpose - the curve, not the issuer, is what was calibrated. + strength policies living outside this module. Google content is the one + measured exception since 0.27.2: it takes the flat oracle-measured floor + instead of the curve (the curve's 0.154 top left a 4.33 MP fixture + oracle-detected on the full production path). """ - assert resolve_strength(None, "google", "qwen-zimage", size=(2000, 1850)) == pytest.approx(0.154) + assert resolve_strength(None, "openai", "qwen-zimage", size=(2000, 1850)) == pytest.approx(0.154) assert resolve_strength(None, None, "qwen-zimage", size=(600, 500)) == pytest.approx(0.084) + assert resolve_strength(None, "google", "qwen-zimage", size=(600, 500)) == QWEN_ZIMAGE_GOOGLE_STRENGTH + # The floor holds at every size, not only above the curve's top rung. + assert resolve_strength(None, "google", "qwen-zimage", size=(2000, 1850)) == QWEN_ZIMAGE_GOOGLE_STRENGTH def test_qwen_zimage_without_a_size_fails_loudly(self): """A missing size must not silently fall back to some vendor value.""" with pytest.raises(ValueError, match="size is required"): - resolve_strength(None, "google", "qwen-zimage") + resolve_strength(None, "openai", "qwen-zimage") def test_sdxl_zimage_uses_its_flat_vendor_ladder(self): diff --git a/tests/test_qwen_zimage_pipeline.py b/tests/test_qwen_zimage_pipeline.py index 7e9eadc..46a8cf4 100644 --- a/tests/test_qwen_zimage_pipeline.py +++ b/tests/test_qwen_zimage_pipeline.py @@ -866,6 +866,7 @@ def test_invisible_engine_passes_the_seed_but_never_a_step_count(tmp_image_path, def test_sdxl_zimage_strength_is_vendor_adaptive_and_leaves_other_profiles_alone(): """An SDXL global pass needs more strength than Qwen, so it gets its own policy.""" from remove_ai_watermarks._internal.watermark_profiles import ( + QWEN_ZIMAGE_GOOGLE_STRENGTH, SDXL_ZIMAGE_GEMINI_STRENGTH, SDXL_ZIMAGE_OPENAI_STRENGTH, resolve_strength, @@ -875,11 +876,14 @@ def test_sdxl_zimage_strength_is_vendor_adaptive_and_leaves_other_profiles_alone assert resolve_strength(None, "google", "sdxl-zimage") == pytest.approx(SDXL_ZIMAGE_GEMINI_STRENGTH) # Unknown provenance takes the stricter of the two. assert resolve_strength(None, None, "sdxl-zimage") == pytest.approx(SDXL_ZIMAGE_GEMINI_STRENGTH) - # An explicit value still wins, and qwen-zimage is untouched by this ladder: it - # defers to its resolution curve rather than to a vendor value. + # An explicit value still wins, and qwen-zimage's curve is untouched by this + # ladder - with the one measured exception: Google content takes the flat + # oracle floor (0.27.2), not the area curve. assert resolve_strength(0.4, "google", "sdxl-zimage") == pytest.approx(0.4) assert resolve_strength(None, "openai", "qwen-zimage", size=(2000, 1850)) == pytest.approx(0.154) - assert resolve_strength(None, "google", "qwen-zimage", size=(2000, 1850)) == pytest.approx(0.154) + assert resolve_strength(None, "google", "qwen-zimage", size=(2000, 1850)) == pytest.approx( + QWEN_ZIMAGE_GOOGLE_STRENGTH + ) def test_sdxl_zimage_shares_the_fixed_seed_contract(): diff --git a/uv.lock b/uv.lock index ee7fc4c..99d0440 100644 --- a/uv.lock +++ b/uv.lock @@ -2508,7 +2508,7 @@ wheels = [ [[package]] name = "remove-ai-watermarks" -version = "0.27.1" +version = "0.27.2" source = { editable = "." } dependencies = [ { name = "c2pa-python" }, From e938b57f8c5974b2d1ddb7b9e3e1f603964daa2e Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Wed, 19 Aug 2026 10:13:22 -0700 Subject: [PATCH 23/44] Package the verified-text draft annotator as text_draft Move the draft-annotation logic (PaddleOCR line detection, word-box grouping, three script-chosen recognition engines, crop-jitter stability gate) from the evaluation-only scripts into the installable package, with lazy paddle imports and a text-draft extra (CPU, no torch). draft_text_lines() returns accepted (crop-stable, NEVER ground-truth-correct - precision on the reference posters was 90.0% / 94.4%) and rejected proposals; source_pixel_sha256 is re-exported for manifest building. scripts/infer_text_lines.py now dogfoods the package module instead of loading the eval script by path. --- docs/python-api.md | 23 + pyproject.toml | 10 +- scripts/infer_text_lines.py | 51 +- src/remove_ai_watermarks/__init__.py | 2 +- src/remove_ai_watermarks/text_draft.py | 281 ++++++++ tests/test_text_draft.py | 141 ++++ uv.lock | 870 +++++++++++++++++++++++-- 7 files changed, 1285 insertions(+), 93 deletions(-) create mode 100644 src/remove_ai_watermarks/text_draft.py create mode 100644 tests/test_text_draft.py diff --git a/docs/python-api.md b/docs/python-api.md index 55b4662..01edd9b 100644 --- a/docs/python-api.md +++ b/docs/python-api.md @@ -621,6 +621,29 @@ Content Provenance API, 2026-08-19 - detected x6 with the anchor, clean x6 without it, controls and base outputs validated in the same sessions). Pass `fidelity_anchor=True` to reproduce the 0.27.0 research behavior. +### Drafting manifest lines + +`remove_ai_watermarks.text_draft` proposes lines for a manifest; it never +produces verified ones: + +```python +from remove_ai_watermarks.text_draft import draft_text_lines + +draft = draft_text_lines(Path("watermarked.png")) +for line in draft.accepted: + print(line.box, line.script, line.min_score, line.text) +``` + +Install `remove-ai-watermarks[text-draft]` (CPU, no torch: PaddleOCR detection +plus three script-chosen recognition engines). A line lands in `accepted` only +when three crop paddings normalize identically and every confidence clears +`min_score` (default 0.85); `accepted` means crop-stable, NOT ground-truth- +correct - on the reference posters the draft's exact-text precision was 90.0% +and 94.4% because high-confidence OCR still lost punctuation. Every accepted +line needs a human yes/no before a manifest may claim `verified: true`. +`source_pixel_sha256` is re-exported here for building the manifest's +pixel-binding hash against the exact source the engine will decode. + `remove_watermark` takes strength, seed, tiling, resolution, and postprocessing controls. It takes no model id, step count or guidance scale, and neither does the constructor: each profile pins its model stack, its per-stage schedule and CFG diff --git a/pyproject.toml b/pyproject.toml index aded025..6bedc66 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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.27.2" +version = "0.28.0" description = "AI watermark remover for visible, invisible, and provenance marks in images and video" readme = "README.md" requires-python = ">=3.11,<3.15" @@ -125,6 +125,14 @@ qwen-zimage = [ text-restoration = [ "remove-ai-watermarks[qwen-zimage,lama]", ] +# Draft-only OCR proposals for verified-text manifests. CPU, no torch: +# PaddleOCR detection plus three script-chosen recognition engines. The models +# download on first use and are cached by huggingface_hub; never bundled. +text-draft = [ + "paddleocr>=3.3.3", + "paddlepaddle>=3.0.0", + "huggingface-hub>=0.20.0", +] # Adobe TrustMark decoder -- the open, keyless watermark behind Adobe Durable # Content Credentials (soft-binding alg ``com.adobe.trustmark.P``). Optional # because it pulls torch and downloads model weights on first use. identify() diff --git a/scripts/infer_text_lines.py b/scripts/infer_text_lines.py index 2c6c6c9..a6e5526 100644 --- a/scripts/infer_text_lines.py +++ b/scripts/infer_text_lines.py @@ -21,14 +21,11 @@ changes under crop jitter or whose minimum confidence is below the threshold. from __future__ import annotations -import importlib.util import json import logging import os import sys -import unicodedata from pathlib import Path -from typing import Any import click import numpy as np @@ -37,38 +34,15 @@ from PIL import Image log = logging.getLogger(__name__) ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) -RESTORATION_SCRIPT = ROOT / "scripts/selective_text_restoration.py" -from scripts._text_eval import normalize_text # noqa: E402 - - -def _load_restoration_module() -> Any: - spec = importlib.util.spec_from_file_location("selective_text_restoration_for_inference", RESTORATION_SCRIPT) - if spec is None or spec.loader is None: - raise RuntimeError(f"unable to load {RESTORATION_SCRIPT}") - module = importlib.util.module_from_spec(spec) - sys.modules[spec.name] = module - spec.loader.exec_module(module) - return module - - -def _has_script(text: str, script: str) -> bool: - return any(script in unicodedata.name(character, "") for character in text) - - -def choose_language(probes: dict[str, tuple[str, float]]) -> str: - if _has_script(probes["ch"][0], "CJK"): - return "ch" - if _has_script(probes["ru"][0], "CYRILLIC"): - return "ru" - return "en" - - -def stable_recognition(reads: list[tuple[str, float]], min_score: float = 0.85) -> str | None: - normalized = {normalize_text(text) for text, _score in reads} - if len(normalized) != 1 or min(score for _text, score in reads) < min_score: - return None - return reads[0][0] +# Dogfoods the packaged draft API (remove_ai_watermarks.text_draft); this script +# keeps only the CLI wrapper so the package stays the one home for the logic. +from remove_ai_watermarks.text_draft import ( # noqa: E402 + _detect_line_boxes, + _recognize, + choose_language, + stable_recognition, +) @click.command() @@ -81,7 +55,6 @@ def main(source: Path, out: Path, min_score: float) -> None: os.environ["PADDLE_PDX_DISABLE_MODEL_SOURCE_CHECK"] = "True" from paddleocr import PaddleOCR, TextRecognition - restoration = _load_restoration_module() source_rgb = np.asarray(Image.open(source).convert("RGB")) detector = PaddleOCR( lang="ch", @@ -94,19 +67,17 @@ def main(source: Path, out: Path, min_score: float) -> None: "ru": TextRecognition(model_name="eslav_PP-OCRv5_mobile_rec"), "ch": TextRecognition(model_name="PP-OCRv5_server_rec"), } - boxes = restoration.detect_line_boxes(detector, source_rgb) + boxes = _detect_line_boxes(detector, source_rgb) accepted = [] rejected = [] for box in boxes: probes = {} for language, engine in engines.items(): script = "cjk" if language == "ch" else "alphabetic" - line = restoration.TextLine(box, "", script) - probes[language] = restoration._recognize(engine, source_rgb, line, 0.1) + probes[language] = _recognize(engine, source_rgb, box, script, 0.1) language = choose_language(probes) script = "cjk" if language == "ch" else "alphabetic" - line = restoration.TextLine(box, "", script) - reads = [restoration._recognize(engines[language], source_rgb, line, ratio) for ratio in (0.08, 0.12, 0.2)] + reads = [_recognize(engines[language], source_rgb, box, script, ratio) for ratio in (0.08, 0.12, 0.2)] text = stable_recognition(reads, min_score) result = { "box": box, diff --git a/src/remove_ai_watermarks/__init__.py b/src/remove_ai_watermarks/__init__.py index f9e8ccf..01be6ac 100644 --- a/src/remove_ai_watermarks/__init__.py +++ b/src/remove_ai_watermarks/__init__.py @@ -32,7 +32,7 @@ _os.environ.setdefault("TRANSFORMERS_VERBOSITY", "error") _warnings.filterwarnings("ignore", message=r".*ImageProcessorFast.*") -__version__ = "0.27.2" +__version__ = "0.28.0" __all__ = [ "BatchSummary", diff --git a/src/remove_ai_watermarks/text_draft.py b/src/remove_ai_watermarks/text_draft.py new file mode 100644 index 0000000..3e4920e --- /dev/null +++ b/src/remove_ai_watermarks/text_draft.py @@ -0,0 +1,281 @@ +"""Draft operator-verifiable text lines for verified-text manifests. + +Proposal-only OCR: PaddleOCR detection plus three script-chosen recognition +engines (Latin / Cyrillic / CJK), accepted only when three crop paddings +normalize identically and every confidence clears the floor. ``accepted`` +means crop-stable, NEVER ground-truth-correct: on the reference posters the +draft's exact-text precision was 90.0% and 94.4% because high-confidence OCR +still lost punctuation (one English comma dropped, one ideographic comma +replaced with ASCII). Every accepted line needs a human yes/no before it may +enter a manifest with ``verified: true``. + +Heavy imports (paddle, and the numpy/cv2/PIL pixel stack) stay inside the +call so importing this module costs nothing without the ``text-draft`` extra; +``draft_available()`` reports whether the extra is installed. +""" + +# pyright: reportUnknownMemberType=false, reportUnknownArgumentType=false, reportUnknownVariableType=false, reportUnknownParameterType=false, reportMissingTypeArgument=false, reportMissingTypeStubs=false, reportMissingImports=false, reportArgumentType=false, reportAssignmentType=false, reportReturnType=false, reportCallIssue=false, reportIndexIssue=false, reportOperatorIssue=false + +from __future__ import annotations + +import unicodedata +from dataclasses import dataclass +from typing import TYPE_CHECKING, Any + +from remove_ai_watermarks._internal.text_restoration import source_pixel_sha256 + +if TYPE_CHECKING: + from pathlib import Path + +__all__ = [ + "DraftLine", + "RejectedLine", + "TextDraft", + "choose_language", + "draft_available", + "draft_text_lines", + "group_word_boxes", + "source_pixel_sha256", + "stable_recognition", +] + +# Crop paddings probed per line: recognition must be invariant across them. +JITTER_RATIOS: tuple[float, ...] = (0.08, 0.12, 0.2) +DETECT_SCORE_FLOOR = 0.5 + + +@dataclass(frozen=True) +class DraftLine: + """One crop-stable proposal. ``accepted`` means stable, not correct.""" + + box: tuple[int, int, int, int] + text: str + script: str + language: str + min_score: float + + +@dataclass(frozen=True) +class RejectedLine: + """A detected line whose recognition was unstable or low-confidence.""" + + box: tuple[int, int, int, int] + script: str + language: str + reads: tuple[tuple[str, float], ...] + + +@dataclass(frozen=True) +class TextDraft: + """The full proposal set for one image.""" + + accepted: tuple[DraftLine, ...] = () + rejected: tuple[RejectedLine, ...] = () + + +def normalize_text(text: str) -> str: + """Normalize text for layout-independent comparison (casefold + no spaces).""" + return "".join(unicodedata.normalize("NFC", text).casefold().split()) + + +def _has_script(text: str, script: str) -> bool: + return any(script in unicodedata.name(character, "") for character in text) + + +def choose_language(probes: dict[str, tuple[str, float]]) -> str: + """Pick ``ch``/``ru``/``en`` from what each probe engine actually read.""" + if _has_script(probes["ch"][0], "CJK"): + return "ch" + if _has_script(probes["ru"][0], "CYRILLIC"): + return "ru" + return "en" + + +def stable_recognition(reads: list[tuple[str, float]], min_score: float = 0.85) -> str | None: + """The proposal text when every read normalizes identically and clears the floor.""" + normalized = {normalize_text(text) for text, _score in reads} + if len(normalized) != 1 or min(score for _text, score in reads) < min_score: + return None + return reads[0][0] + + +def _vertical_overlap_ratio(left: tuple[int, int, int, int], right: tuple[int, int, int, int]) -> float: + overlap = max(0, min(left[3], right[3]) - max(left[1], right[1])) + return overlap / max(1, min(left[3] - left[1], right[3] - right[1])) + + +def _row_center(box: tuple[int, int, int, int]) -> float: + return (box[1] + box[3]) / 2 + + +def group_word_boxes(boxes: list[tuple[int, int, int, int]]) -> list[tuple[int, int, int, int]]: + """Merge word detections into line boxes by vertical overlap and gap.""" + groups: list[tuple[int, int, int, int]] = [] + for box in sorted(boxes, key=lambda item: (_row_center(item), item[0])): + matches: list[int] = [] + for index, group in enumerate(groups): + if _vertical_overlap_ratio(box, group) < 0.45: + continue + horizontal_gap = max(0, max(box[0], group[0]) - min(box[2], group[2])) + line_height = min(box[3] - box[1], group[3] - group[1]) + if horizontal_gap <= max(24, line_height * 3): + matches.append(index) + if not matches: + groups.append(box) + continue + index = max(matches, key=lambda item: _vertical_overlap_ratio(box, groups[item])) + x1, y1, x2, y2 = groups[index] + groups[index] = min(x1, box[0]), min(y1, box[1]), max(x2, box[2]), max(y2, box[3]) + return sorted(groups, key=_row_center) + + +def _recognition_box( + box: tuple[int, int, int, int], + script: str, + width: int, + height: int, + vertical_pad_ratio: float | None = None, +) -> tuple[int, int, int, int]: + x1, y1, x2, y2 = box + line_height = y2 - y1 + if script == "cjk": + left_pad = max(16, round(line_height * 0.2)) + right_pad = max(16, round(line_height * 0.6)) + return max(0, x1 - left_pad), y1, min(width, x2 + right_pad), y2 + pad_x = max(16, line_height) + pad_y = max(8, line_height // 3) if vertical_pad_ratio is None else max(8, round(line_height * vertical_pad_ratio)) + return max(0, x1 - pad_x), max(0, y1 - pad_y), min(width, x2 + pad_x), min(height, y2 + pad_y) + + +def _recognize( + engine: Any, + image: Any, + box: tuple[int, int, int, int], + script: str, + vertical_pad_ratio: float | None = None, +) -> tuple[str, float]: + import cv2 + + height, width = image.shape[:2] + x1, y1, x2, y2 = _recognition_box(box, script, width, height, vertical_pad_ratio) + crop = image[y1:y2, x1:x2] + if crop.shape[0] < 64: + scale = 64 / crop.shape[0] + crop = cv2.resize(crop, None, fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC) + result = next(iter(engine.predict(crop))) + return str(result.get("rec_text", "")), float(result.get("rec_score", 0.0)) + + +def _detect_line_boxes(engine: Any, source_rgb: Any) -> list[tuple[int, int, int, int]]: + import numpy as np + + boxes: list[tuple[int, int, int, int]] = [] + for page in engine.predict(source_rgb): + detected = page.get("rec_boxes", None) + if detected is None or len(detected) == 0: + detected = page.get("rec_polys", []) + for score, raw_box in zip(page.get("rec_scores", []), detected, strict=False): + if float(score) < DETECT_SCORE_FLOOR: + continue + points = np.asarray(raw_box, dtype=np.float32).reshape(-1) + if points.size == 4: + x1, y1, x2, y2 = points + else: + points = points.reshape(-1, 2) + x1, y1 = points.min(axis=0) + x2, y2 = points.max(axis=0) + boxes.append((round(float(x1)), round(float(y1)), round(float(x2)), round(float(y2)))) + return group_word_boxes(boxes) + + +def draft_available() -> bool: + """True when the ``text-draft`` extra (paddleocr + paddle) can run.""" + from remove_ai_watermarks.optional_deps import module_available + + return module_available("paddleocr") and module_available("paddle") + + +def _build_engines() -> tuple[Any, dict[str, Any]]: + from paddleocr import PaddleOCR, TextRecognition + + detector = PaddleOCR( + lang="ch", + use_doc_orientation_classify=False, + use_doc_unwarping=False, + use_textline_orientation=False, + ) + engines = { + "en": TextRecognition(model_name="en_PP-OCRv5_mobile_rec"), + "ru": TextRecognition(model_name="eslav_PP-OCRv5_mobile_rec"), + "ch": TextRecognition(model_name="PP-OCRv5_server_rec"), + } + return detector, engines + + +def draft_text_lines( + image: Path, + *, + min_score: float = 0.85, + detector: Any | None = None, + engines: dict[str, Any] | None = None, +) -> TextDraft: + """Propose verified-text manifest lines for ``image``; never verified ones. + + Args: + image: path of the source image (draft boxes are in ITS pixel space). + min_score: recognition confidence floor for every jittered read. + detector/engines: injectable Paddle objects (tests use fakes); when + None they are built from the ``text-draft`` extra, which must be + installed (``draft_available()`` reports it). + + Returns: + ``TextDraft`` with crop-stable ``accepted`` proposals and unstable + ``rejected`` lines. Both lists need human review before any manifest + may claim ``verified: true``; accepted means crop-stable, NOT + ground-truth-correct. + """ + import os + + import numpy as np + from PIL import Image + + if detector is None or engines is None: + if not draft_available(): + raise RuntimeError( + "Text drafting requires PaddleOCR. Install: pip install 'remove-ai-watermarks[text-draft]'" + ) + os.environ.setdefault("PADDLE_PDX_DISABLE_MODEL_SOURCE_CHECK", "True") + detector, engines = _build_engines() + + source_rgb = np.asarray(Image.open(image).convert("RGB")) + accepted: list[DraftLine] = [] + rejected: list[RejectedLine] = [] + for box in _detect_line_boxes(detector, source_rgb): + probes: dict[str, tuple[str, float]] = {} + for language, engine in engines.items(): + script = "cjk" if language == "ch" else "alphabetic" + probes[language] = _recognize(engine, source_rgb, box, script, 0.1) + language = choose_language(probes) + script = "cjk" if language == "ch" else "alphabetic" + reads = [_recognize(engines[language], source_rgb, box, script, ratio) for ratio in JITTER_RATIOS] + text = stable_recognition(reads, min_score) + if text is None: + rejected.append( + RejectedLine( + box=box, + script=script, + language=language, + reads=tuple((value, score) for value, score in reads), + ) + ) + else: + accepted.append( + DraftLine( + box=box, + text=text, + script=script, + language=language, + min_score=min(score for _value, score in reads), + ) + ) + return TextDraft(accepted=tuple(accepted), rejected=tuple(rejected)) diff --git a/tests/test_text_draft.py b/tests/test_text_draft.py new file mode 100644 index 0000000..6a1abc0 --- /dev/null +++ b/tests/test_text_draft.py @@ -0,0 +1,141 @@ +"""text_draft: proposal-only OCR for verified-text manifests (no paddle needed).""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from PIL import Image + +from remove_ai_watermarks.text_draft import ( + TextDraft, + choose_language, + draft_available, + draft_text_lines, + group_word_boxes, + stable_recognition, +) + +if TYPE_CHECKING: + from pathlib import Path + + +class TestPureHelpers: + def test_choose_language_follows_unicode_script(self): + assert choose_language({"ch": ("你好", 0.9), "ru": ("xxx", 0.1), "en": ("yyy", 0.1)}) == "ch" + assert choose_language({"ch": ("??", 0.1), "ru": ("привет", 0.9), "en": ("yyy", 0.1)}) == "ru" + assert choose_language({"ch": ("??", 0.1), "ru": ("xxx", 0.1), "en": ("hello", 0.9)}) == "en" + + def test_stable_recognition_requires_identical_normalizations(self): + # Same text modulo spacing/case (comma kept): stable. + assert ( + stable_recognition([("Hello, World", 0.97), ("hello,world", 0.96), ("HELLO, WORLD", 0.95)]) + == "Hello, World" + ) + # Different texts: rejected (None). + assert stable_recognition([("Hello", 0.97), ("Hallo", 0.96), ("Hello", 0.95)]) is None + # Identical text but one read under the floor: rejected. + assert stable_recognition([("Hello", 0.97), ("Hello", 0.60), ("Hello", 0.95)]) is None + assert stable_recognition([("Hello", 0.97), ("Hello", 0.96), ("Hello", 0.95)], min_score=0.99) is None + + def test_group_word_boxes_merges_a_line_and_keeps_rows_apart(self): + # Two words on one baseline merge; a distant lower line stays separate. + merged = group_word_boxes([(10, 100, 60, 140), (70, 100, 120, 140)]) + assert len(merged) == 1 + assert merged[0] == (10, 100, 120, 140) + apart = group_word_boxes([(10, 100, 60, 140), (10, 300, 60, 340)]) + assert len(apart) == 2 + + +class TestDraftAvailable: + def test_returns_a_bool_without_importing_paddle(self): + assert isinstance(draft_available(), bool) + + +class _FakeDetector: + """Emits Paddle-style pages for one synthetic poster.""" + + def __init__(self, boxes: list[tuple[int, int, int, int]]) -> None: + self._boxes = boxes + + def predict(self, _image: Any) -> Any: + import numpy as np + + yield { + "rec_boxes": np.asarray(self._boxes, dtype=np.float32), + "rec_scores": [0.99] * len(self._boxes), + } + + +class _FakeEngine: + """Returns a fixed text at high score regardless of the crop.""" + + def __init__(self, text: str) -> None: + self._text = text + + def predict(self, _crop: Any) -> Any: + yield {"rec_text": self._text, "rec_score": 0.97} + + +class _JitterEngine: + """Changes its answer with the crop height - exactly what the gate rejects.""" + + def __init__(self, texts: list[str]) -> None: + self._texts = texts + self._calls = 0 + + def predict(self, crop: Any) -> Any: + text = self._texts[self._calls % len(self._texts)] + self._calls += 1 + yield {"rec_text": text, "rec_score": 0.97} + + +class TestDraftTextLines: + @staticmethod + def _poster(tmp_path: Path) -> Path: + path = tmp_path / "poster.png" + Image.new("RGB", (400, 300), (255, 255, 255)).save(path) + return path + + def test_accepts_crop_stable_and_rejects_jittered(self, tmp_path: Path): + path = self._poster(tmp_path) + stable = _FakeEngine("Invoice 42") + jitter = _JitterEngine(["Hello", "Hallo", "Hullo"]) + # All three probe engines read the same string, so language = en; the + # jitter engine then serves as the en engine and flips across crops. + draft = draft_text_lines( + path, + detector=_FakeDetector([(20, 20, 200, 60), (20, 80, 200, 120)]), + engines={"en": jitter, "ru": stable, "ch": stable}, + ) + assert isinstance(draft, TextDraft) + assert len(draft.rejected) == 2 + assert all(line.language == "en" for line in draft.rejected) + assert draft.accepted == () + + def test_accepted_line_carries_box_text_script_and_floor(self, tmp_path: Path): + path = self._poster(tmp_path) + engine = _FakeEngine("Total: 1,234.56") + draft = draft_text_lines( + path, + detector=_FakeDetector([(20, 20, 260, 60)]), + engines={"en": engine, "ru": engine, "ch": engine}, + ) + (line,) = draft.accepted + assert line.box == (20, 20, 260, 60) + assert line.text == "Total: 1,234.56" + assert line.script == "alphabetic" + assert line.language == "en" + assert line.min_score >= 0.85 + + def test_cjk_probe_switches_script_and_language(self, tmp_path: Path): + path = self._poster(tmp_path) + cjk = _FakeEngine("每天都是一个新的机会。") + latin = _FakeEngine("hello") + draft = draft_text_lines( + path, + detector=_FakeDetector([(30, 30, 300, 90)]), + engines={"en": latin, "ru": latin, "ch": cjk}, + ) + (line,) = draft.accepted + assert line.language == "ch" + assert line.script == "cjk" diff --git a/uv.lock b/uv.lock index 99d0440..f8eb3ac 100644 --- a/uv.lock +++ b/uv.lock @@ -183,6 +183,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, ] +[[package]] +name = "aistudio-sdk" +version = "0.3.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bce-python-sdk" }, + { name = "click" }, + { name = "prettytable" }, + { name = "psutil" }, + { name = "requests" }, + { name = "tqdm" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/56/ce14f76c1892f335fbcd82568d347fd1652e1a4b0b76ac4a4cd90e1ae202/aistudio_sdk-0.3.9-py3-none-any.whl", hash = "sha256:41621d052ddb7a58643438a7ca541f58ef0ab94a2acdfff0a71db81bf68dc027", size = 67186, upload-time = "2026-07-27T06:44:45.294Z" }, +] + [[package]] name = "annotated-doc" version = "0.0.5" @@ -192,6 +208,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302, upload-time = "2026-07-28T13:50:57.239Z" }, ] +[[package]] +name = "annotated-types" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" }, +] + [[package]] name = "antlr4-python3-runtime" version = "4.9.3" @@ -246,6 +271,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/01/20/1e24450ea981c44ed328691496fd2774dfa9fa3c3b00fd07f72fd5614abe/av-18.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:96f594ff506a09475e5549359352332049a25d37a08f00b4623f7f6e92e45b9c", size = 21377289, upload-time = "2026-07-02T06:37:55.935Z" }, ] +[[package]] +name = "bce-python-sdk" +version = "0.9.76" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "crc32c" }, + { name = "future" }, + { name = "pycryptodome" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/ed/b41906366c0e1e3a1a883e0f1bcfc927403d9697d74b1eb7e89502870218/bce_python_sdk-0.9.76.tar.gz", hash = "sha256:01c630ce8dcbf8be0563d65f18b5201eaac6bd954b3dc776040aec268f81b6ff", size = 317399, upload-time = "2026-07-24T05:12:57.005Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/06/4d4a26c3dfcdb29599f563329b87eefe55b02b3c2b9d8f8c3aeaa38a33a6/bce_python_sdk-0.9.76-py3-none-any.whl", hash = "sha256:e629181d060f4ed8f29749f47139bf08f1f9b0d8b15daedc956900785587e8d8", size = 435179, upload-time = "2026-07-24T05:12:55.064Z" }, +] + [[package]] name = "c2pa-python" version = "0.37.5" @@ -352,6 +392,43 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/1b/58496f2ed0a35de575250c02a43ab3cc2c04d494a88fed31c1cabc0fd176/cffi-2.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ad2c86c495b899d862ea0f4b42891b8713a3bd45dd4105c7fd51c2a72f39f3a5", size = 186402, upload-time = "2026-08-03T21:20:43.042Z" }, ] +[[package]] +name = "chardet" +version = "7.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/cd61c567092a6cec796144510a68aff158ebfc1df82950a45bae65f28413/chardet-7.6.0.tar.gz", hash = "sha256:93d9df6089ded42ed1fe9f57e272c0b74bd0464d45c0c7d50f09f26f31105c3c", size = 914462, upload-time = "2026-08-14T20:36:59.305Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/2e/d8634bee23a07bf512512ddf6218a68e47f045ae061c0cc80657ed79dcc0/chardet-7.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6424512f576fa7e88b7431d38a42d57552c8f717465a975fc42e497cd280d833", size = 1088243, upload-time = "2026-08-14T20:36:17.135Z" }, + { url = "https://files.pythonhosted.org/packages/55/95/bd6d59026638cec47dace85858171fbecadd2f9e58cb2b973dc515aa790c/chardet-7.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:284136186ff90735f901ed0a1c6d41e7af67c666841cc0eceb58482a21b7056c", size = 1068688, upload-time = "2026-08-14T20:36:19.183Z" }, + { url = "https://files.pythonhosted.org/packages/7f/4a/60ed03656b28c1f4d378bc3cfe8a6cdda62c7c289398f925610fbbabfd00/chardet-7.6.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e9b31b9ae93872d66439b046a1e08c2ea99791f3c254dce1e2633e395c5587c", size = 1487945, upload-time = "2026-08-14T20:36:20.64Z" }, + { url = "https://files.pythonhosted.org/packages/03/25/9c8db4f951e974a4db5558d9eea62e1fd5b5889c9d0ad0315eed67c5cdb3/chardet-7.6.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa03322e07ac08d520ec50bb50c73143d0892d1adc067d4c5e58f4ef4b2363a8", size = 1510112, upload-time = "2026-08-14T20:36:22.248Z" }, + { url = "https://files.pythonhosted.org/packages/76/1b/59eb88a78d8f5855c27c25788088df82834ad067f3dddaf4d86ef03cf2d3/chardet-7.6.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0ad9bc6dab4f338673353fa3f0dc96122f559aaf746087408106e2fcbf132fe8", size = 1462470, upload-time = "2026-08-14T20:36:23.557Z" }, + { url = "https://files.pythonhosted.org/packages/02/af/46c80c317f9b4dd61f15c33b1e073fbdd64d13c70fe19e34943071dc9e50/chardet-7.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:360260d074d8712ac1e9048fcafb0fdde246f9d0b12555748ad0017c5ecee43d", size = 1157092, upload-time = "2026-08-14T20:36:25.082Z" }, + { url = "https://files.pythonhosted.org/packages/6f/62/64da80dad0c804e743b4156f379183578f1e33918856ae928dc9248a6002/chardet-7.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:19fea52164e6e00f2a21ed418f42e4b0162a09199274c86d07ad3efd661317c4", size = 1094073, upload-time = "2026-08-14T20:36:26.53Z" }, + { url = "https://files.pythonhosted.org/packages/44/99/934fb862d102c8756008597f4398323f32cef329f16e87fbb3bf76d4f4be/chardet-7.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a12023d48d0e207791c01161d03cb3c0d85c6a15f345eb9d3d56063a63d1e40f", size = 1071612, upload-time = "2026-08-14T20:36:28.067Z" }, + { url = "https://files.pythonhosted.org/packages/71/e9/b04e0ec576a77e79fe37279a9a5d5b1ae752d365e43df2eca0d0eee4cea5/chardet-7.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:249993b88ac7a58cad2781acea8f379152a28a719c9b401d614898c63a8c83da", size = 1489219, upload-time = "2026-08-14T20:36:29.357Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a2/c4d99299e9ce7fad561f8bb56babbbbdd3bb6b4fbd7c0ec674c1dbdd2cc5/chardet-7.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf0adaca8b1c4bacfade9d0a1e4f8f70b1bb122833d6f07ab90e3adc84eb13a", size = 1518293, upload-time = "2026-08-14T20:36:30.879Z" }, + { url = "https://files.pythonhosted.org/packages/56/1d/49f13052b74303bab2789d098063cbd19758217949ea54ffa216b6098cb3/chardet-7.6.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cf6d08c2373b7772a558d141f9e8cee53fe1d222341bac612e4d558b04995f73", size = 1459077, upload-time = "2026-08-14T20:36:32.149Z" }, + { url = "https://files.pythonhosted.org/packages/0d/53/8da1f4758286efd8faf71356facddb382788ecf1bbd7c70d63e2e18a4898/chardet-7.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:406936df1328a3284fef366eaa2bfd1cccd0ef1b10cb99781dd5b022ea644b84", size = 1160778, upload-time = "2026-08-14T20:36:33.436Z" }, + { url = "https://files.pythonhosted.org/packages/a3/29/16a7419edfbd60e901e6a797cbc3e038cb2a81903bc16c029db755f0156f/chardet-7.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:57e6846cc13ce1ff59979f4ec9da770c57e12aa99046073f632de5a51d9a6f20", size = 1088449, upload-time = "2026-08-14T20:36:34.723Z" }, + { url = "https://files.pythonhosted.org/packages/1d/36/3a14b0f8ddeb302f157281ca656a3ce6874b78e2d6af03682f520b487245/chardet-7.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:089e3bb81a0a07e94f15461ded9f9ee66d349615b1a9fd557d4de1003e2fc12e", size = 1065126, upload-time = "2026-08-14T20:36:36.21Z" }, + { url = "https://files.pythonhosted.org/packages/d2/4c/f59a39c2bfe4ac99baba8da842e8d2ea0b84dff7ba53a96e7ad8c71602d7/chardet-7.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:43ea433e43a23c55e8e17f3fad1e07f5cfe5450c73124b95b0d849c21ad379ee", size = 1482492, upload-time = "2026-08-14T20:36:37.649Z" }, + { url = "https://files.pythonhosted.org/packages/bd/eb/93e8036681157f2217a18769927a035984526e6dbd5e91f28a375ca41c14/chardet-7.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b5d31f9b7f793e15e81cca877e7ccd72bffffa2a3443a9d47be9dfee84fad69", size = 1514185, upload-time = "2026-08-14T20:36:39.119Z" }, + { url = "https://files.pythonhosted.org/packages/10/04/0066d7ab2c135e404a6fa166bb7fa49d1c7bf7af07b86ed95b1c48a348c7/chardet-7.6.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c54b6a8d3b219560fa5cf4c28df932c37471afe047afdc152067104e741f38c1", size = 1454420, upload-time = "2026-08-14T20:36:40.549Z" }, + { url = "https://files.pythonhosted.org/packages/6c/9f/e965f1d9eddfb86cf118f980d329cbee43c4ac4b562448b369a6b6ef36af/chardet-7.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:b3b4c96c4df93899b3c8b9e8159e06b1f55c66d7ca384d91481108e251a06eb0", size = 1159067, upload-time = "2026-08-14T20:36:41.816Z" }, + { url = "https://files.pythonhosted.org/packages/cf/78/e14991c9487277ed7d006fff58f3084ac792ed94cced081788abb95df70c/chardet-7.6.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c6061adf247ab5dda173b67010e13904c6071717660c7c8077fb50aca362b264", size = 1087678, upload-time = "2026-08-14T20:36:43.2Z" }, + { url = "https://files.pythonhosted.org/packages/7a/40/0f95e04cb1820e0a582cd6d86bbf26be8302a94ccf330f8ba5f69735389d/chardet-7.6.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fc1e1571321baf8927582fe34363ad7f02279f11c8c2839c14b4c76894148db6", size = 1065310, upload-time = "2026-08-14T20:36:44.489Z" }, + { url = "https://files.pythonhosted.org/packages/3b/a1/04404dcc9d6e1253b02583e562d2c7ddca50a7e91f460d62eff7e1d07c92/chardet-7.6.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8900f6c7cf6b015b17a51767cc6144689059ba1cdceaa383d29eb037ac28579e", size = 1485028, upload-time = "2026-08-14T20:36:45.797Z" }, + { url = "https://files.pythonhosted.org/packages/f4/88/360064c4c7d9d0664561dae03b74c871d2f5332b329f5c99f1c997fb869a/chardet-7.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cedbc584789eb2edfde20fd03669972a833ce6019e60014ae613f9bfc440e8e3", size = 1510242, upload-time = "2026-08-14T20:36:47.206Z" }, + { url = "https://files.pythonhosted.org/packages/43/4c/302869fa1c69a5a41e4e78782680b12552ffd4286c3ae3c839b7b8df53d4/chardet-7.6.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d5dc835e40e0e09c2c3eab43731a8b5127834f42786dda09ba2f4b699ccd527a", size = 1456456, upload-time = "2026-08-14T20:36:48.668Z" }, + { url = "https://files.pythonhosted.org/packages/7c/a9/ff4fef15ed25fc3f945a3b981ae0f43c8559b3fbedb40267e59e583d105b/chardet-7.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:0f304de7041afaec0195ad6464937cd112392002e9d72ed15d55f20a9abd3a13", size = 1159103, upload-time = "2026-08-14T20:36:50.107Z" }, + { url = "https://files.pythonhosted.org/packages/31/44/94e6f89485ce6c630e8fec6d388e3fa747e58b7246b0cc8c5c532ba98650/chardet-7.6.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a4f0a368ad04d5def08bdfaa17c7e15e71552f93923dc2aa9b2f7d9dee02fbb6", size = 1120302, upload-time = "2026-08-14T20:36:51.484Z" }, + { url = "https://files.pythonhosted.org/packages/e0/cd/8e68ba12f13aaf4ee82d54ef8a1f83d62942e7a6bc1e579dd2d530a3e26e/chardet-7.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:75d6c3a4d2046d49e83d2d2206eb073a1f390743e856d90c1bbc19949b26acf4", size = 1093157, upload-time = "2026-08-14T20:36:52.987Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f6/6a35342b9efa69dcceab0ea8966571c6442a59c336bf460f0a2af95ed234/chardet-7.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:459e2b1c98f9a86a4698112aa42dffa802bbbff883c1ff144071f87224125862", size = 1521213, upload-time = "2026-08-14T20:36:54.812Z" }, + { url = "https://files.pythonhosted.org/packages/df/8c/8f09bfabbaedae45caa72b996e96d5e3f6436dfddc55c1311ffa2f6bd7d2/chardet-7.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bdb6f03107b7ace3f44e0edd91aa24456ee558787df265cc19daf45785b31c7", size = 1528427, upload-time = "2026-08-14T20:36:56.224Z" }, + { url = "https://files.pythonhosted.org/packages/cf/6e/5a0b348fa4cd7847567a28c6e697ccf58391960bfd13a6e7473ee23ca2f2/chardet-7.6.0-py3-none-any.whl", hash = "sha256:4076d795897ce45239825956a1334e134322ecc4bfe84dbb12acd5390de0fbc1", size = 680279, upload-time = "2026-08-14T20:36:57.763Z" }, +] + [[package]] name = "charset-normalizer" version = "3.4.9" @@ -447,6 +524,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "colorlog" +version = "6.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8c/55/ba79756cb90c8d69d599d57785398ac87bba7b19c80e87f4e8a562197c93/colorlog-6.12.0.tar.gz", hash = "sha256:2a7924c1dadf18b22a0eb8b06d1c7b01d5341707ec1641eb6fcc4fde0c3e8e5f", size = 18151, upload-time = "2026-07-23T13:40:40.71Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/19/0b6647bf5e331521e55d2b63bfbdc210bd9cd605189273f03614a05f702d/colorlog-6.12.0-py3-none-any.whl", hash = "sha256:30d392604e9110045a2c2aeefc27d7a017abbab63f3a8aee594eac0801df784e", size = 12239, upload-time = "2026-07-23T13:40:39.562Z" }, +] + [[package]] name = "coverage" version = "7.15.3" @@ -536,6 +625,73 @@ toml = [ { name = "tomli", marker = "python_full_version <= '3.11'" }, ] +[[package]] +name = "crc32c" +version = "2.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/66/7e97aa77af7cf6afbff26e3651b564fe41932599bc2d3dce0b2f73d4829a/crc32c-2.8.tar.gz", hash = "sha256:578728964e59c47c356aeeedee6220e021e124b9d3e8631d95d9a5e5f06e261c", size = 48179, upload-time = "2025-10-17T06:20:13.61Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/0b/5e03b22d913698e9cc563f39b9f6bbd508606bf6b8e9122cd6bf196b87ea/crc32c-2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e560a97fbb96c9897cb1d9b5076ef12fc12e2e25622530a1afd0de4240f17e1f", size = 66329, upload-time = "2025-10-17T06:19:01.771Z" }, + { url = "https://files.pythonhosted.org/packages/6b/38/2fe0051ffe8c6a650c8b1ac0da31b8802d1dbe5fa40a84e4b6b6f5583db5/crc32c-2.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6762d276d90331a490ef7e71ffee53b9c0eb053bd75a272d786f3b08d3fe3671", size = 62988, upload-time = "2025-10-17T06:19:02.953Z" }, + { url = "https://files.pythonhosted.org/packages/3e/30/5837a71c014be83aba1469c58820d287fc836512a0cad6b8fdd43868accd/crc32c-2.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:60670569f5ede91e39f48fb0cb4060e05b8d8704dd9e17ede930bf441b2f73ef", size = 61522, upload-time = "2025-10-17T06:19:03.796Z" }, + { url = "https://files.pythonhosted.org/packages/ca/29/63972fc1452778e2092ae998c50cbfc2fc93e3fa9798a0278650cd6169c5/crc32c-2.8-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:711743da6ccc70b3c6718c328947b0b6f34a1fe6a6c27cc6c1d69cc226bf70e9", size = 80200, upload-time = "2025-10-17T06:19:04.617Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3a/60eb49d7bdada4122b3ffd45b0df54bdc1b8dd092cda4b069a287bdfcff4/crc32c-2.8-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5eb4094a2054774f13b26f21bf56792bb44fa1fcee6c6ad099387a43ffbfb4fa", size = 81757, upload-time = "2025-10-17T06:19:05.496Z" }, + { url = "https://files.pythonhosted.org/packages/f5/63/6efc1b64429ef7d23bd58b75b7ac24d15df327e3ebbe9c247a0f7b1c2ed1/crc32c-2.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fff15bf2bd3e95780516baae935ed12be88deaa5ebe6143c53eb0d26a7bdc7b7", size = 80830, upload-time = "2025-10-17T06:19:06.621Z" }, + { url = "https://files.pythonhosted.org/packages/e1/eb/0ae9f436f8004f1c88f7429e659a7218a3879bd11a6b18ed1257aad7e98b/crc32c-2.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c0e11e3826668121fa53e0745635baf5e4f0ded437e8ff63ea56f38fc4f970a", size = 80095, upload-time = "2025-10-17T06:19:07.381Z" }, + { url = "https://files.pythonhosted.org/packages/9e/81/4afc9d468977a4cd94a2eb62908553345009a7c0d30e74463a15d4b48ec3/crc32c-2.8-cp311-cp311-win32.whl", hash = "sha256:38f915336715d1f1353ab07d7d786f8a789b119e273aea106ba55355dfc9101d", size = 64886, upload-time = "2025-10-17T06:19:08.497Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e8/94e839c9f7e767bf8479046a207afd440a08f5c59b52586e1af5e64fa4a0/crc32c-2.8-cp311-cp311-win_amd64.whl", hash = "sha256:60e0a765b1caab8d31b2ea80840639253906a9351d4b861551c8c8625ea20f86", size = 66639, upload-time = "2025-10-17T06:19:09.338Z" }, + { url = "https://files.pythonhosted.org/packages/b6/36/fd18ef23c42926b79c7003e16cb0f79043b5b179c633521343d3b499e996/crc32c-2.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:572ffb1b78cce3d88e8d4143e154d31044a44be42cb3f6fbbf77f1e7a941c5ab", size = 66379, upload-time = "2025-10-17T06:19:10.115Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b8/c584958e53f7798dd358f5bdb1bbfc97483134f053ee399d3eeb26cca075/crc32c-2.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cf827b3758ee0c4aacd21ceca0e2da83681f10295c38a10bfeb105f7d98f7a68", size = 63042, upload-time = "2025-10-17T06:19:10.946Z" }, + { url = "https://files.pythonhosted.org/packages/62/e6/6f2af0ec64a668a46c861e5bc778ea3ee42171fedfc5440f791f470fd783/crc32c-2.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:106fbd79013e06fa92bc3b51031694fcc1249811ed4364ef1554ee3dd2c7f5a2", size = 61528, upload-time = "2025-10-17T06:19:11.768Z" }, + { url = "https://files.pythonhosted.org/packages/17/8b/4a04bd80a024f1a23978f19ae99407783e06549e361ab56e9c08bba3c1d3/crc32c-2.8-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6dde035f91ffbfe23163e68605ee5a4bb8ceebd71ed54bb1fb1d0526cdd125a2", size = 80028, upload-time = "2025-10-17T06:19:12.554Z" }, + { url = "https://files.pythonhosted.org/packages/21/8f/01c7afdc76ac2007d0e6a98e7300b4470b170480f8188475b597d1f4b4c6/crc32c-2.8-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e41ebe7c2f0fdcd9f3a3fd206989a36b460b4d3f24816d53e5be6c7dba72c5e1", size = 81531, upload-time = "2025-10-17T06:19:13.406Z" }, + { url = "https://files.pythonhosted.org/packages/32/2b/8f78c5a8cc66486be5f51b6f038fc347c3ba748d3ea68be17a014283c331/crc32c-2.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ecf66cf90266d9c15cea597d5cc86c01917cd1a238dc3c51420c7886fa750d7e", size = 80608, upload-time = "2025-10-17T06:19:14.223Z" }, + { url = "https://files.pythonhosted.org/packages/db/86/fad1a94cdeeeb6b6e2323c87f970186e74bfd6fbfbc247bf5c88ad0873d5/crc32c-2.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:59eee5f3a69ad0793d5fa9cdc9b9d743b0cd50edf7fccc0a3988a821fef0208c", size = 79886, upload-time = "2025-10-17T06:19:15.345Z" }, + { url = "https://files.pythonhosted.org/packages/d5/db/1a7cb6757a1e32376fa2dfce00c815ea4ee614a94f9bff8228e37420c183/crc32c-2.8-cp312-cp312-win32.whl", hash = "sha256:a73d03ce3604aa5d7a2698e9057a0eef69f529c46497b27ee1c38158e90ceb76", size = 64896, upload-time = "2025-10-17T06:19:16.457Z" }, + { url = "https://files.pythonhosted.org/packages/bf/8e/2024de34399b2e401a37dcb54b224b56c747b0dc46de4966886827b4d370/crc32c-2.8-cp312-cp312-win_amd64.whl", hash = "sha256:56b3b7d015247962cf58186e06d18c3d75a1a63d709d3233509e1c50a2d36aa2", size = 66645, upload-time = "2025-10-17T06:19:17.235Z" }, + { url = "https://files.pythonhosted.org/packages/e8/d8/3ae227890b3be40955a7144106ef4dd97d6123a82c2a5310cdab58ca49d8/crc32c-2.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:36f1e03ee9e9c6938e67d3bcb60e36f260170aa5f37da1185e04ef37b56af395", size = 66380, upload-time = "2025-10-17T06:19:18.009Z" }, + { url = "https://files.pythonhosted.org/packages/bd/8b/178d3f987cd0e049b484615512d3f91f3d2caeeb8ff336bb5896ae317438/crc32c-2.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b2f3226b94b85a8dd9b3533601d7a63e9e3e8edf03a8a169830ee8303a199aeb", size = 63048, upload-time = "2025-10-17T06:19:18.853Z" }, + { url = "https://files.pythonhosted.org/packages/f2/a1/48145ae2545ebc0169d3283ebe882da580ea4606bfb67cf4ca922ac3cfc3/crc32c-2.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6e08628bc72d5b6bc8e0730e8f142194b610e780a98c58cb6698e665cb885a5b", size = 61530, upload-time = "2025-10-17T06:19:19.974Z" }, + { url = "https://files.pythonhosted.org/packages/06/4b/cf05ed9d934cc30e5ae22f97c8272face420a476090e736615d9a6b53de0/crc32c-2.8-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:086f64793c5ec856d1ab31a026d52ad2b895ac83d7a38fce557d74eb857f0a82", size = 80001, upload-time = "2025-10-17T06:19:20.784Z" }, + { url = "https://files.pythonhosted.org/packages/15/ab/4b04801739faf36345f6ba1920be5b1c70282fec52f8280afd3613fb13e2/crc32c-2.8-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcf72ee7e0135b3d941c34bb2c26c3fc6bc207106b49fd89aaafaeae223ae209", size = 81543, upload-time = "2025-10-17T06:19:21.557Z" }, + { url = "https://files.pythonhosted.org/packages/a9/1b/6e38dde5bfd2ea69b7f2ab6ec229fcd972a53d39e2db4efe75c0ac0382ce/crc32c-2.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8a717dd9c3fd777d9bc6603717eae172887d402c4ab589d124ebd0184a83f89e", size = 80644, upload-time = "2025-10-17T06:19:22.325Z" }, + { url = "https://files.pythonhosted.org/packages/ce/45/012176ffee90059ae8ec7131019c71724ea472aa63e72c0c8edbd1fad1d7/crc32c-2.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0450bb845b3c3c7b9bdc0b4e95620ec9a40824abdc8c86d6285c919a90743c1a", size = 79919, upload-time = "2025-10-17T06:19:23.101Z" }, + { url = "https://files.pythonhosted.org/packages/f0/2b/f557629842f9dec2b3461cb3a0d854bb586ec45b814cea58b082c32f0dde/crc32c-2.8-cp313-cp313-win32.whl", hash = "sha256:765d220bfcbcffa6598ac11eb1e10af0ee4802b49fe126aa6bf79f8ddb9931d1", size = 64896, upload-time = "2025-10-17T06:19:23.88Z" }, + { url = "https://files.pythonhosted.org/packages/d0/db/fd0f698c15d1e21d47c64181a98290665a08fcbb3940cd559e9c15bda57e/crc32c-2.8-cp313-cp313-win_amd64.whl", hash = "sha256:171ff0260d112c62abcce29332986950a57bddee514e0a2418bfde493ea06bb3", size = 66646, upload-time = "2025-10-17T06:19:24.702Z" }, + { url = "https://files.pythonhosted.org/packages/db/b9/8e5d7054fe8e7eecab10fd0c8e7ffb01439417bdb6de1d66a81c38fc4a20/crc32c-2.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b977a32a3708d6f51703c8557008f190aaa434d7347431efb0e86fcbe78c2a50", size = 66203, upload-time = "2025-10-17T06:19:25.872Z" }, + { url = "https://files.pythonhosted.org/packages/55/5f/cc926c70057a63cc0c98a3c8a896eb15fc7e74d3034eadd53c94917c6cc3/crc32c-2.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7399b01db4adaf41da2fb36fe2408e75a8d82a179a9564ed7619412e427b26d6", size = 62956, upload-time = "2025-10-17T06:19:26.652Z" }, + { url = "https://files.pythonhosted.org/packages/a1/8a/0660c44a2dd2cb6ccbb529eb363b9280f5c766f1017bc8355ed8d695bd94/crc32c-2.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4379f73f9cdad31958a673d11a332ec725ca71572401ca865867229f5f15e853", size = 61442, upload-time = "2025-10-17T06:19:27.74Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5a/6108d2dfc0fe33522ce83ba07aed4b22014911b387afa228808a278e27cd/crc32c-2.8-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2e68264555fab19bab08331550dab58573e351a63ed79c869d455edd3b0aa417", size = 79109, upload-time = "2025-10-17T06:19:28.535Z" }, + { url = "https://files.pythonhosted.org/packages/84/1e/c054f9e390090c197abf3d2936f4f9effaf0c6ee14569ae03d6ddf86958a/crc32c-2.8-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b48f2486727b8d0e7ccbae4a34cb0300498433d2a9d6b49cb13cb57c2e3f19cb", size = 80987, upload-time = "2025-10-17T06:19:29.305Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ad/1650e5c3341e4a485f800ea83116d72965030c5d48ccc168fcc685756e4d/crc32c-2.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ecf123348934a086df8c8fde7f9f2d716d523ca0707c5a1367b8bb00d8134823", size = 79994, upload-time = "2025-10-17T06:19:30.109Z" }, + { url = "https://files.pythonhosted.org/packages/d7/3b/f2ed924b177729cbb2ab30ca2902abff653c31d48c95e7b66717a9ca9fcc/crc32c-2.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e636ac60f76de538f7a2c0d0f3abf43104ee83a8f5e516f6345dc283ed1a4df7", size = 79046, upload-time = "2025-10-17T06:19:30.894Z" }, + { url = "https://files.pythonhosted.org/packages/4b/80/413b05ee6ace613208b31b3670c3135ee1cf451f0e72a9c839b4946acc04/crc32c-2.8-cp313-cp313t-win32.whl", hash = "sha256:8dd4a19505e0253892e1b2f1425cc3bd47f79ae5a04cb8800315d00aad7197f2", size = 64837, upload-time = "2025-10-17T06:19:32.03Z" }, + { url = "https://files.pythonhosted.org/packages/3b/1b/85eddb6ac5b38496c4e35c20298aae627970c88c3c624a22ab33e84f16c7/crc32c-2.8-cp313-cp313t-win_amd64.whl", hash = "sha256:4bb18e4bd98fb266596523ffc6be9c5b2387b2fa4e505ec56ca36336f49cb639", size = 66574, upload-time = "2025-10-17T06:19:33.143Z" }, + { url = "https://files.pythonhosted.org/packages/aa/df/50e9079b532ff53dbfc0e66eed781374bd455af02ed5df8b56ad538de4ff/crc32c-2.8-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3a3b2e4bcf7b3ee333050e7d3ff38e2ba46ea205f1d73d8949b248aaffe937ac", size = 66399, upload-time = "2025-10-17T06:19:34.279Z" }, + { url = "https://files.pythonhosted.org/packages/5a/2e/67e3b0bc3d30e46ea5d16365cc81203286387671e22f2307eb41f19abb9c/crc32c-2.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:445e559e66dff16be54f8a4ef95aa6b01db799a639956d995c5498ba513fccc2", size = 63044, upload-time = "2025-10-17T06:19:35.062Z" }, + { url = "https://files.pythonhosted.org/packages/36/ea/1723b17437e4344ed8d067456382ecb1f5b535d83fdc5aaebab676c6d273/crc32c-2.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bf3040919e17afa5782e01b1875d6a05f44b8f19c05f211d8b9f8a1deb8bbd9c", size = 61541, upload-time = "2025-10-17T06:19:36.204Z" }, + { url = "https://files.pythonhosted.org/packages/4c/6a/cbec8a235c5b46a01f319939b538958662159aec0ed3a74944e3a6de21f1/crc32c-2.8-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5607ab8221e1ffd411f64aa40dbb6850cf06dd2908c9debd05d371e1acf62ff3", size = 80139, upload-time = "2025-10-17T06:19:37.351Z" }, + { url = "https://files.pythonhosted.org/packages/21/31/d096722fe74b692d6e8206c27da1ea5f6b2a12ff92c54a62a6ba2f376254/crc32c-2.8-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f5db4f16816926986d3c94253314920689706ae13a9bf4888b47336c6735ce", size = 81736, upload-time = "2025-10-17T06:19:38.16Z" }, + { url = "https://files.pythonhosted.org/packages/f6/a2/f75ef716ff7e3c22f385ba6ef30c5de80c19a21ebe699dc90824a1903275/crc32c-2.8-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:70b0153c4d418b673309d3529334d117e1074c4a3b2d7f676e430d72c14de67b", size = 80795, upload-time = "2025-10-17T06:19:38.948Z" }, + { url = "https://files.pythonhosted.org/packages/d8/94/6d647a12d96ab087d9b8eacee3da073f981987827d57c7072f89ffc7b6cd/crc32c-2.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5c8933531442042438753755a5c8a9034e4d88b01da9eb796f7e151b31a7256c", size = 80042, upload-time = "2025-10-17T06:19:39.725Z" }, + { url = "https://files.pythonhosted.org/packages/cd/dc/32b8896b40a0afee7a3c040536d0da5a73e68df2be9fadd21770fd158e16/crc32c-2.8-cp314-cp314-win32.whl", hash = "sha256:cdc83a3fe6c4e5df9457294cfd643de7d95bd4e9382c1dd6ed1e0f0f9169172c", size = 64914, upload-time = "2025-10-17T06:19:40.527Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b4/4308b27d307e8ecaf8dd1dcc63bbb0e47ae1826d93faa3e62d1ee00ee2d5/crc32c-2.8-cp314-cp314-win_amd64.whl", hash = "sha256:509e10035106df66770fe24b9eb8d9e32b6fb967df17744402fb67772d8b2bc7", size = 66723, upload-time = "2025-10-17T06:19:42.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/d5/a19d2489fa997a143bfbbf971a5c9a43f8b1ba9e775b1fb362d8fb15260c/crc32c-2.8-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:864359a39777a07b09b28eb31337c0cc603d5c1bf0fc328c3af736a8da624ec0", size = 66201, upload-time = "2025-10-17T06:19:43.273Z" }, + { url = "https://files.pythonhosted.org/packages/98/c2/5f82f22d2c1242cb6f6fe92aa9a42991ebea86de994b8f9974d9c1d128e2/crc32c-2.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:14511d7cfc5d9f5e1a6c6b64caa6225c2bdc1ed00d725e9a374a3e84073ce180", size = 62956, upload-time = "2025-10-17T06:19:44.099Z" }, + { url = "https://files.pythonhosted.org/packages/9b/61/3d43d33489cf974fb78bfb3500845770e139ae6d1d83473b660bd8f79a6c/crc32c-2.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:918b7999b52b5dcbcea34081e9a02d46917d571921a3f209956a9a429b2e06e5", size = 61443, upload-time = "2025-10-17T06:19:44.89Z" }, + { url = "https://files.pythonhosted.org/packages/52/6d/f306ce64a352a3002f76b0fc88a1373f4541f9d34fad3668688610bab14b/crc32c-2.8-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cc445da03fc012a5a03b71da1df1b40139729e6a5571fd4215ab40bfb39689c7", size = 79106, upload-time = "2025-10-17T06:19:45.688Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b7/1f74965dd7ea762954a69d172dfb3a706049c84ffa45d31401d010a4a126/crc32c-2.8-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e3dde2ec59a8a830511d72a086ead95c0b0b7f0d418f93ea106244c5e77e350", size = 80983, upload-time = "2025-10-17T06:19:46.792Z" }, + { url = "https://files.pythonhosted.org/packages/1b/50/af93f0d91ccd61833ce77374ebfbd16f5805f5c17d18c6470976d9866d76/crc32c-2.8-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:61d51681a08b6a2a2e771b7f0cd1947fb87cb28f38ed55a01cb7c40b2ac4cdd8", size = 80009, upload-time = "2025-10-17T06:19:47.619Z" }, + { url = "https://files.pythonhosted.org/packages/ee/fa/94f394beb68a88258af694dab2f1284f55a406b615d7900bdd6235283bc4/crc32c-2.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:67c0716c3b1a02d5235be649487b637eed21f2d070f2b3f63f709dcd2fefb4c7", size = 79066, upload-time = "2025-10-17T06:19:48.409Z" }, + { url = "https://files.pythonhosted.org/packages/91/c6/a6050e0c64fd73c67a97da96cb59f08b05111e00b958fb87ecdce99f17ac/crc32c-2.8-cp314-cp314t-win32.whl", hash = "sha256:2e8fe863fbbd8bdb6b414a2090f1b0f52106e76e9a9c96a413495dbe5ebe492a", size = 64869, upload-time = "2025-10-17T06:19:49.197Z" }, + { url = "https://files.pythonhosted.org/packages/08/1f/c7735034e401cb1ea14f996a224518e3a3fa9987cb13680e707328a7d779/crc32c-2.8-cp314-cp314t-win_amd64.whl", hash = "sha256:20a9cfb897693eb6da19e52e2a7be2026fd4d9fc8ae318f086c0d71d5dd2d8e0", size = 66633, upload-time = "2025-10-17T06:19:50.003Z" }, + { url = "https://files.pythonhosted.org/packages/a7/1d/dd926c68eb8aac8b142a1a10b8eb62d95212c1cf81775644373fe7cceac2/crc32c-2.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5833f4071da7ea182c514ba17d1eee8aec3c5be927d798222fbfbbd0f5eea02c", size = 62345, upload-time = "2025-10-17T06:20:09.39Z" }, + { url = "https://files.pythonhosted.org/packages/51/be/803404e5abea2ef2c15042edca04bbb7f625044cca879e47f186b43887c2/crc32c-2.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:1dc4da036126ac07b39dd9d03e93e585ec615a2ad28ff12757aef7de175295a8", size = 61229, upload-time = "2025-10-17T06:20:10.236Z" }, + { url = "https://files.pythonhosted.org/packages/fc/3a/00cc578cd27ed0b22c9be25cef2c24539d92df9fa80ebd67a3fc5419724c/crc32c-2.8-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:15905fa78344654e241371c47e6ed2411f9eeb2b8095311c68c88eccf541e8b4", size = 64108, upload-time = "2025-10-17T06:20:11.072Z" }, + { url = "https://files.pythonhosted.org/packages/6b/bc/0587ef99a1c7629f95dd0c9d4f3d894de383a0df85831eb16c48a6afdae4/crc32c-2.8-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c596f918688821f796434e89b431b1698396c38bf0b56de873621528fe3ecb1e", size = 64815, upload-time = "2025-10-17T06:20:11.919Z" }, + { url = "https://files.pythonhosted.org/packages/73/42/94f2b8b92eae9064fcfb8deef2b971514065bd606231f8857ff8ae02bebd/crc32c-2.8-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8d23c4fe01b3844cb6e091044bc1cebdef7d16472e058ce12d9fadf10d2614af", size = 66659, upload-time = "2025-10-17T06:20:12.766Z" }, +] + [[package]] name = "cryptography" version = "50.0.0" @@ -916,6 +1072,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/6e/81d47999aebc1b155f81eca4477a616a70f238a2549848c38983f3c22a82/ftfy-6.3.1-py3-none-any.whl", hash = "sha256:7c70eb532015cd2f9adb53f101fb6c7945988d023a085d127d1573dc49dd0083", size = 44821, upload-time = "2024-10-26T00:50:33.425Z" }, ] +[[package]] +name = "future" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/b2/4140c69c6a66432916b26158687e821ba631a4c9273c474343badf84d3ba/future-1.0.0.tar.gz", hash = "sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05", size = 1228490, upload-time = "2024-02-21T11:52:38.461Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/71/ae30dadffc90b9006d77af76b393cb9dfbfc9629f339fc1574a1c52e6806/future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216", size = 491326, upload-time = "2024-02-21T11:52:35.956Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -1040,6 +1205,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/c6/fa760e12a2483469e2bf5058c5faff664acf66cadb4df2ad6205b016a73d/imageio_ffmpeg-0.6.0-py3-none-win_amd64.whl", hash = "sha256:02fa47c83703c37df6bfe4896aab339013f62bf02c5ebf2dce6da56af04ffc0a", size = 31246824, upload-time = "2025-01-16T21:34:28.6Z" }, ] +[[package]] +name = "imagesize" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z" }, +] + [[package]] name = "importlib-metadata" version = "9.0.0" @@ -1716,6 +1890,24 @@ wheels = [ { 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" }, ] +[[package]] +name = "opencv-contrib-python" +version = "4.10.0.84" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/33/7b8ec6c4d45e678b26297e4a5e76464a93033a9adcc8c17eac01097065f6/opencv-contrib-python-4.10.0.84.tar.gz", hash = "sha256:4a3eae0ed9cadf1abe9293a6938a25a540e2fd6d7fc308595caa5896c8b36a0c", size = 150433857, upload-time = "2024-06-17T18:30:50.217Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/64/c1194510eaed272d86b53a08c790ca6ed1c450f06d401c49c8145fc46d40/opencv_contrib_python-4.10.0.84-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:ee4b0919026d8c533aeb69b16c6ec4a891a2f6844efaa14121bf68838753209c", size = 63667391, upload-time = "2024-06-18T04:57:54.718Z" }, + { url = "https://files.pythonhosted.org/packages/09/94/d077c4c976c2d7a88812fd55396e92edae0e0c708689dbd8c8f508920e47/opencv_contrib_python-4.10.0.84-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:dea80d4db73b8acccf9e16b5744bf3654f47b22745074263f0a6c10de26c5ef5", size = 66278032, upload-time = "2024-06-17T19:34:23.718Z" }, + { url = "https://files.pythonhosted.org/packages/f8/76/f76fe74b864f3cfa737173ca12e8890aad8369e980006fb8a0b6cd14c6c7/opencv_contrib_python-4.10.0.84-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:040575b69e4f3aa761676bace4e3d1b8485fbfaf77ef77b266ab6bda5a3b5e9b", size = 47384495, upload-time = "2024-06-17T20:00:39.027Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e0/8f5d065ebb2e5941d289c5f653f944318f9e418bc5167bc6a346ab5e0f6a/opencv_contrib_python-4.10.0.84-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a261223db41f6e512d76deaf21c8fcfb4fbbcbc2de62ca7f74a05f2c9ee489ef", size = 68681489, upload-time = "2024-06-17T18:30:32.918Z" }, + { url = "https://files.pythonhosted.org/packages/36/30/7041bd7350cb1a26fa80415a7664b6f04f7ccbf0c12b9318d564cdf35932/opencv_contrib_python-4.10.0.84-cp37-abi3-win32.whl", hash = "sha256:2a36257ec1375d1bec2a62177ea39828ff9804de6831ee39646bdc875c343cec", size = 34506122, upload-time = "2024-06-17T18:28:29.922Z" }, + { url = "https://files.pythonhosted.org/packages/a7/9e/7110d2c5d543ab03b9581dbb1f8e2429863e44e0c9b4960b766f230c1279/opencv_contrib_python-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:47ec3160dae75f70e099b286d1a2e086d20dac8b06e759f60eaf867e6bdecba7", size = 45541421, upload-time = "2024-06-17T18:28:46.012Z" }, +] + [[package]] name = "opencv-python" version = "4.11.0.86" @@ -1752,6 +1944,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/86/8a/69176a64335aed183529207ba8bc3d329c2999d852b4f3818027203f50e6/opencv_python_headless-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:6c304df9caa7a6a5710b91709dd4786bf20a74d57672b3c31f7033cc638174ca", size = 39402386, upload-time = "2025-01-16T13:52:56.418Z" }, ] +[[package]] +name = "opt-einsum" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/bf/9257e53a0e7715bc1127e15063e831f076723c6cd60985333a1c18878fb8/opt_einsum-3.3.0.tar.gz", hash = "sha256:59f6475f77bbc37dcf7cd748519c0ec60722e91e63ca114e68821c0c54a46549", size = 73951, upload-time = "2020-07-19T22:40:32.141Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/19/404708a7e54ad2798907210462fd950c3442ea51acc8790f3da48d2bee8b/opt_einsum-3.3.0-py3-none-any.whl", hash = "sha256:2455e59e3947d3c275477df7f5205b30635e266fe6dc300e3d9f9646bfcea147", size = 65486, upload-time = "2020-07-19T22:40:30.301Z" }, +] + [[package]] name = "packaging" version = "26.3" @@ -1761,6 +1966,89 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, ] +[[package]] +name = "paddleocr" +version = "3.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "paddlex", extra = ["ocr-core"] }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6b/a1/408871ae4316c4c1a0eba3a627f6524008feda6b79f22a2dae4c66553745/paddleocr-3.4.1.tar.gz", hash = "sha256:d8b3e5d0de0edca7141ac826eeb48063bb2a9d4a34d20ffffba22d84cd405800", size = 2504875, upload-time = "2026-04-14T05:48:07.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/93/643d26b16c9709e0eeddf750118e79d92ceddab3b7ed0f5ed97e662dd6b6/paddleocr-3.4.1-py3-none-any.whl", hash = "sha256:4538431f10cb9d7741caab5bf4e456f4da1ec0aa74eee93d11d5eba65fb66c1e", size = 88113, upload-time = "2026-04-14T05:48:05.492Z" }, +] + +[[package]] +name = "paddlepaddle" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "networkx" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "opt-einsum" }, + { name = "pillow" }, + { name = "protobuf" }, + { name = "safetensors" }, + { name = "setuptools", marker = "python_full_version >= '3.12'" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/02/854d122760406d8024837112d91ff509e7820d763ac0a5002c4c2812f832/paddlepaddle-3.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55df22b0bc6bfc0b97093a538eda3c774ef27050ad19e0ee5aa17e468b0de714", size = 104491591, upload-time = "2026-03-26T11:18:09.265Z" }, + { url = "https://files.pythonhosted.org/packages/c6/df/50cff6a0795ff0ff6f210b5355fe77344a4be455d9d13e5dbaf5e3b9883d/paddlepaddle-3.3.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:bb22396233d807f6957d9c4a6a1edc76693fb589c4ee3d17caf7e39113ca4047", size = 194813574, upload-time = "2026-03-24T08:01:59.868Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b4/587190301767a5a7e2b43e86e1f3185556bbf59d3eb2aaaee68c9855a991/paddlepaddle-3.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:9651888c3586e2da4e443a0ee7630b95afbf58e6fbfee6203e6975cb642e615b", size = 104785835, upload-time = "2026-03-26T11:11:23.176Z" }, + { url = "https://files.pythonhosted.org/packages/3b/97/62d061b4700a7020506f4db80c4721100b5b2fa0709f4e999c38dc7e2293/paddlepaddle-3.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d7827d867503b502a881c718e3743d9b91648a62d01f93bcd5ba3d7846de5ab5", size = 104546703, upload-time = "2026-03-26T11:18:20.499Z" }, + { url = "https://files.pythonhosted.org/packages/a0/03/f59f706902aad55a1f216c98fc55187945d6ccda6fdeccd226928bcc06c6/paddlepaddle-3.3.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:9016fc497213e1101261684321fbb31ef5960019ef39cb07ded27bc70e2a9858", size = 194839332, upload-time = "2026-03-24T08:02:17.755Z" }, + { url = "https://files.pythonhosted.org/packages/47/1b/66e269a6fffecc887a87c1ea2c72fdb31f9cafb2f40dc24c639f35010527/paddlepaddle-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:324b5122cf3887dfbd15db17f36e2421ef923fd4569d26111bf1a21fe84d442b", size = 104794793, upload-time = "2026-03-26T11:11:37.18Z" }, + { url = "https://files.pythonhosted.org/packages/65/f8/bebb829227ac5574454b4342bcf512dbb02268118b62222fc7c1b420a3ef/paddlepaddle-3.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2ce30bfb0023d8c9314b1413ca7a775a4ddd076e4551e74fbed2366cf1395c98", size = 104538799, upload-time = "2026-03-26T11:18:41.158Z" }, + { url = "https://files.pythonhosted.org/packages/56/8d/5a700dc20c0d3d581f1d429f95f5064eb766c2818c5a05992dfef66b0765/paddlepaddle-3.3.1-cp313-cp313-manylinux1_x86_64.whl", hash = "sha256:4f28038427649bb2fcfd4d52efa85ed8311df19122b4ebedc942484a0b57b032", size = 194837642, upload-time = "2026-03-24T08:02:36.024Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c8/408b9e94ac2e10a55c1122b787359f7a6f79ef894ee1d57d78c5cca838d0/paddlepaddle-3.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:1203e2e1114b49e73a8440b68837ce5c93fdab51fe4838c5e5874ab40f58f747", size = 104794530, upload-time = "2026-03-26T11:11:52.214Z" }, +] + +[[package]] +name = "paddlex" +version = "3.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aistudio-sdk" }, + { name = "chardet" }, + { name = "colorlog" }, + { name = "filelock" }, + { name = "huggingface-hub" }, + { name = "modelscope" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "prettytable" }, + { name = "py-cpuinfo" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "ruamel-yaml" }, + { name = "typing-extensions" }, + { name = "ujson" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/f6/a1ab1c812ceec3c505806015483b064e1a733c98edcace2aa85aa8186a75/paddlex-3.4.3.tar.gz", hash = "sha256:acecc48f589169607a49b846bfb108817aee47c5e1610aed59cfc3e451b1a825", size = 4173136, upload-time = "2026-03-27T02:31:18.457Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/39/8fd9fd72ae51e1a0b21cb5596f1125f42286d688cf28797f883b42f2c8ce/paddlex-3.4.3-py3-none-any.whl", hash = "sha256:78aa8313e672ecbf27473b7afe7188b68716517b1b522060985d6d7dab1ed17c", size = 2022071, upload-time = "2026-03-27T02:31:15.751Z" }, +] + +[package.optional-dependencies] +ocr-core = [ + { name = "imagesize" }, + { name = "opencv-contrib-python" }, + { name = "pyclipper" }, + { name = "pypdfium2" }, + { name = "python-bidi" }, + { name = "shapely" }, +] + [[package]] name = "pandas" version = "3.0.5" @@ -1976,6 +2264,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "prettytable" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/74/ba08d81e668ccfe8658d7520a307e63c19862c08eb4ccb26f356c5239a7a/prettytable-3.18.0.tar.gz", hash = "sha256:439217116152244369caf3d9f1caf2f9fe29b03bd79e88d2928c8e718c95d680", size = 76373, upload-time = "2026-06-22T16:07:50.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/be/2e6798ace5cc036f5d05d36b7b2fd85346f1a708c87060890b070d0ec607/prettytable-3.18.0-py3-none-any.whl", hash = "sha256:b3346e0e6f79180833aebaac088ae926340586cf6d7d991b9eb125b65f72313a", size = 37357, upload-time = "2026-06-22T16:07:48.595Z" }, +] + [[package]] name = "propcache" version = "0.5.2" @@ -2130,6 +2430,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, ] +[[package]] +name = "py-cpuinfo" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/a8/d832f7293ebb21690860d2e01d8115e5ff6f2ae8bbdc953f0eb0fa4bd2c7/py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690", size = 104716, upload-time = "2022-10-25T20:38:06.303Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5", size = 22335, upload-time = "2022-10-25T20:38:27.636Z" }, +] + [[package]] name = "pyarrow" version = "25.0.0" @@ -2173,6 +2482,43 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/8d/d236e9c82fe315f9128885c8be3ec719f41965a1eb6b6f4b42470904cd41/pyarrow-25.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:13240f0d3dc5932ccd0bfa90cd76d835680b9d94a7661c635df4b703d40ce849", size = 28743657, upload-time = "2026-07-10T08:29:42.742Z" }, ] +[[package]] +name = "pyclipper" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/21/3c06205bb407e1f79b73b7b4dfb3950bd9537c4f625a68ab5cc41177f5bc/pyclipper-1.4.0.tar.gz", hash = "sha256:9882bd889f27da78add4dd6f881d25697efc740bf840274e749988d25496c8e1", size = 54489, upload-time = "2025-12-01T13:15:35.015Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/e3/64cf7794319b088c288706087141e53ac259c7959728303276d18adc665d/pyclipper-1.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:adcb7ca33c5bdc33cd775e8b3eadad54873c802a6d909067a57348bcb96e7a2d", size = 264281, upload-time = "2025-12-01T13:14:55.47Z" }, + { url = "https://files.pythonhosted.org/packages/34/cd/44ec0da0306fa4231e76f1c2cb1fa394d7bde8db490a2b24d55b39865f69/pyclipper-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fd24849d2b94ec749ceac7c34c9f01010d23b6e9d9216cf2238b8481160e703d", size = 139426, upload-time = "2025-12-01T13:14:56.683Z" }, + { url = "https://files.pythonhosted.org/packages/ad/88/d8f6c6763ea622fe35e19c75d8b39ed6c55191ddc82d65e06bc46b26cb8e/pyclipper-1.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1b6c8d75ba20c6433c9ea8f1a0feb7e4d3ac06a09ad1fd6d571afc1ddf89b869", size = 989649, upload-time = "2025-12-01T13:14:58.28Z" }, + { url = "https://files.pythonhosted.org/packages/ff/e9/ea7d68c8c4af3842d6515bedcf06418610ad75f111e64c92c1d4785a1513/pyclipper-1.4.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:58e29d7443d7cc0e83ee9daf43927730386629786d00c63b04fe3b53ac01462c", size = 962842, upload-time = "2025-12-01T13:15:00.044Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b7/0b4a272d8726e51ab05e2b933d8cc47f29757fb8212e38b619e170e6015c/pyclipper-1.4.0-cp311-cp311-win32.whl", hash = "sha256:a8d2b5fb75ebe57e21ce61e79a9131edec2622ff23cc665e4d1d1f201bc1a801", size = 95098, upload-time = "2025-12-01T13:15:01.359Z" }, + { url = "https://files.pythonhosted.org/packages/3a/76/4901de2919198bb2bd3d989f86d4a1dff363962425bb2d63e24e6c990042/pyclipper-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:e9b973467d9c5fa9bc30bb6ac95f9f4d7c3d9fc25f6cf2d1cc972088e5955c01", size = 104362, upload-time = "2025-12-01T13:15:02.439Z" }, + { url = "https://files.pythonhosted.org/packages/90/1b/7a07b68e0842324d46c03e512d8eefa9cb92ba2a792b3b4ebf939dafcac3/pyclipper-1.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:222ac96c8b8281b53d695b9c4fedc674f56d6d4320ad23f1bdbd168f4e316140", size = 265676, upload-time = "2025-12-01T13:15:04.15Z" }, + { url = "https://files.pythonhosted.org/packages/6b/dd/8bd622521c05d04963420ae6664093f154343ed044c53ea260a310c8bb4d/pyclipper-1.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f3672dbafbb458f1b96e1ee3e610d174acb5ace5bd2ed5d1252603bb797f2fc6", size = 140458, upload-time = "2025-12-01T13:15:05.76Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/6e3e241882bf7d6ab23d9c69ba4e85f1ec47397cbbeee948a16cf75e21ed/pyclipper-1.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d1f807e2b4760a8e5c6d6b4e8c1d71ef52b7fe1946ff088f4fa41e16a881a5ca", size = 978235, upload-time = "2025-12-01T13:15:06.993Z" }, + { url = "https://files.pythonhosted.org/packages/cf/f4/3418c1cd5eea640a9fa2501d4bc0b3655fa8d40145d1a4f484b987990a75/pyclipper-1.4.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce1f83c9a4e10ea3de1959f0ae79e9a5bd41346dff648fee6228ba9eaf8b3872", size = 961388, upload-time = "2025-12-01T13:15:08.467Z" }, + { url = "https://files.pythonhosted.org/packages/ac/94/c85401d24be634af529c962dd5d781f3cb62a67cd769534df2cb3feee97a/pyclipper-1.4.0-cp312-cp312-win32.whl", hash = "sha256:3ef44b64666ebf1cb521a08a60c3e639d21b8c50bfbe846ba7c52a0415e936f4", size = 95169, upload-time = "2025-12-01T13:15:10.098Z" }, + { url = "https://files.pythonhosted.org/packages/97/77/dfea08e3b230b82ee22543c30c35d33d42f846a77f96caf7c504dd54fab1/pyclipper-1.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:d1e5498d883b706a4ce636247f0d830c6eb34a25b843a1b78e2c969754ca9037", size = 104619, upload-time = "2025-12-01T13:15:11.592Z" }, + { url = "https://files.pythonhosted.org/packages/67/d0/cbce7d47de1e6458f66a4d999b091640134deb8f2c7351eab993b70d2e10/pyclipper-1.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d49df13cbb2627ccb13a1046f3ea6ebf7177b5504ec61bdef87d6a704046fd6e", size = 264342, upload-time = "2025-12-01T13:15:12.697Z" }, + { url = "https://files.pythonhosted.org/packages/ce/cc/742b9d69d96c58ac156947e1b56d0f81cbacbccf869e2ac7229f2f86dc4e/pyclipper-1.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37bfec361e174110cdddffd5ecd070a8064015c99383d95eb692c253951eee8a", size = 139839, upload-time = "2025-12-01T13:15:13.911Z" }, + { url = "https://files.pythonhosted.org/packages/db/48/dd301d62c1529efdd721b47b9e5fb52120fcdac5f4d3405cfc0d2f391414/pyclipper-1.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:14c8bdb5a72004b721c4e6f448d2c2262d74a7f0c9e3076aeff41e564a92389f", size = 972142, upload-time = "2025-12-01T13:15:15.477Z" }, + { url = "https://files.pythonhosted.org/packages/07/bf/d493fd1b33bb090fa64e28c1009374d5d72fa705f9331cd56517c35e381e/pyclipper-1.4.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f2a50c22c3a78cb4e48347ecf06930f61ce98cf9252f2e292aa025471e9d75b1", size = 952789, upload-time = "2025-12-01T13:15:17.042Z" }, + { url = "https://files.pythonhosted.org/packages/cf/88/b95ea8ea21ddca34aa14b123226a81526dd2faaa993f9aabd3ed21231604/pyclipper-1.4.0-cp313-cp313-win32.whl", hash = "sha256:c9a3faa416ff536cee93417a72bfb690d9dea136dc39a39dbbe1e5dadf108c9c", size = 94817, upload-time = "2025-12-01T13:15:18.724Z" }, + { url = "https://files.pythonhosted.org/packages/ba/42/0a1920d276a0e1ca21dc0d13ee9e3ba10a9a8aa3abac76cd5e5a9f503306/pyclipper-1.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:d4b2d7c41086f1927d14947c563dfc7beed2f6c0d9af13c42fe3dcdc20d35832", size = 104007, upload-time = "2025-12-01T13:15:19.763Z" }, + { url = "https://files.pythonhosted.org/packages/1a/20/04d58c70f3ccd404f179f8dd81d16722a05a3bf1ab61445ee64e8218c1f8/pyclipper-1.4.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:7c87480fc91a5af4c1ba310bdb7de2f089a3eeef5fe351a3cedc37da1fcced1c", size = 265167, upload-time = "2025-12-01T13:15:20.844Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2e/a570c1abe69b7260ca0caab4236ce6ea3661193ebf8d1bd7f78ccce537a5/pyclipper-1.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:81d8bb2d1fb9d66dc7ea4373b176bb4b02443a7e328b3b603a73faec088b952e", size = 139966, upload-time = "2025-12-01T13:15:22.036Z" }, + { url = "https://files.pythonhosted.org/packages/e8/3b/e0859e54adabdde8a24a29d3f525ebb31c71ddf2e8d93edce83a3c212ffc/pyclipper-1.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:773c0e06b683214dcfc6711be230c83b03cddebe8a57eae053d4603dd63582f9", size = 968216, upload-time = "2025-12-01T13:15:23.18Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6b/e3c4febf0a35ae643ee579b09988dd931602b5bf311020535fd9e5b7e715/pyclipper-1.4.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bc45f2463d997848450dbed91c950ca37c6cf27f84a49a5cad4affc0b469e39", size = 954198, upload-time = "2025-12-01T13:15:24.522Z" }, + { url = "https://files.pythonhosted.org/packages/fc/74/728efcee02e12acb486ce9d56fa037120c9bf5b77c54bbdbaa441c14a9d9/pyclipper-1.4.0-cp314-cp314-win32.whl", hash = "sha256:0b8c2105b3b3c44dbe1a266f64309407fe30bf372cf39a94dc8aaa97df00da5b", size = 96951, upload-time = "2025-12-01T13:15:25.79Z" }, + { url = "https://files.pythonhosted.org/packages/e3/d7/7f4354e69f10a917e5c7d5d72a499ef2e10945312f5e72c414a0a08d2ae4/pyclipper-1.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:6c317e182590c88ec0194149995e3d71a979cfef3b246383f4e035f9d4a11826", size = 106782, upload-time = "2025-12-01T13:15:26.945Z" }, + { url = "https://files.pythonhosted.org/packages/63/60/fc32c7a3d7f61a970511ec2857ecd09693d8ac80d560ee7b8e67a6d268c9/pyclipper-1.4.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:f160a2c6ba036f7eaf09f1f10f4fbfa734234af9112fb5187877efed78df9303", size = 269880, upload-time = "2025-12-01T13:15:28.117Z" }, + { url = "https://files.pythonhosted.org/packages/49/df/c4a72d3f62f0ba03ec440c4fff56cd2d674a4334d23c5064cbf41c9583f6/pyclipper-1.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a9f11ad133257c52c40d50de7a0ca3370a0cdd8e3d11eec0604ad3c34ba549e9", size = 141706, upload-time = "2025-12-01T13:15:30.134Z" }, + { url = "https://files.pythonhosted.org/packages/c5/0b/cf55df03e2175e1e2da9db585241401e0bc98f76bee3791bed39d0313449/pyclipper-1.4.0-cp314-cp314t-win32.whl", hash = "sha256:bbc827b77442c99deaeee26e0e7f172355ddb097a5e126aea206d447d3b26286", size = 105308, upload-time = "2025-12-01T13:15:31.225Z" }, + { url = "https://files.pythonhosted.org/packages/8f/dc/53df8b6931d47080b4fe4ee8450d42e660ee1c5c1556c7ab73359182b769/pyclipper-1.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:29dae3e0296dff8502eeb7639fcfee794b0eec8590ba3563aee28db269da6b04", size = 117608, upload-time = "2025-12-01T13:15:32.69Z" }, + { url = "https://files.pythonhosted.org/packages/18/59/81050abdc9e5b90ffc2c765738c5e40e9abd8e44864aaa737b600f16c562/pyclipper-1.4.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98b2a40f98e1fc1b29e8a6094072e7e0c7dfe901e573bf6cfc6eb7ce84a7ae87", size = 126495, upload-time = "2025-12-01T13:15:33.743Z" }, +] + [[package]] name = "pycparser" version = "3.0" @@ -2182,6 +2528,153 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] +[[package]] +name = "pycryptodome" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/a6/8452177684d5e906854776276ddd34eca30d1b1e15aa1ee9cefc289a33f5/pycryptodome-3.23.0.tar.gz", hash = "sha256:447700a657182d60338bab09fdb27518f8856aecd80ae4c6bdddb67ff5da44ef", size = 4921276, upload-time = "2025-05-17T17:21:45.242Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/5d/bdb09489b63cd34a976cc9e2a8d938114f7a53a74d3dd4f125ffa49dce82/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0011f7f00cdb74879142011f95133274741778abba114ceca229adbf8e62c3e4", size = 2495152, upload-time = "2025-05-17T17:20:20.833Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ce/7840250ed4cc0039c433cd41715536f926d6e86ce84e904068eb3244b6a6/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:90460fc9e088ce095f9ee8356722d4f10f86e5be06e2354230a9880b9c549aae", size = 1639348, upload-time = "2025-05-17T17:20:23.171Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f0/991da24c55c1f688d6a3b5a11940567353f74590734ee4a64294834ae472/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4764e64b269fc83b00f682c47443c2e6e85b18273712b98aa43bcb77f8570477", size = 2184033, upload-time = "2025-05-17T17:20:25.424Z" }, + { url = "https://files.pythonhosted.org/packages/54/16/0e11882deddf00f68b68dd4e8e442ddc30641f31afeb2bc25588124ac8de/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb8f24adb74984aa0e5d07a2368ad95276cf38051fe2dc6605cbcf482e04f2a7", size = 2270142, upload-time = "2025-05-17T17:20:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/d5/fc/4347fea23a3f95ffb931f383ff28b3f7b1fe868739182cb76718c0da86a1/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d97618c9c6684a97ef7637ba43bdf6663a2e2e77efe0f863cce97a76af396446", size = 2309384, upload-time = "2025-05-17T17:20:30.765Z" }, + { url = "https://files.pythonhosted.org/packages/6e/d9/c5261780b69ce66d8cfab25d2797bd6e82ba0241804694cd48be41add5eb/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a53a4fe5cb075075d515797d6ce2f56772ea7e6a1e5e4b96cf78a14bac3d265", size = 2183237, upload-time = "2025-05-17T17:20:33.736Z" }, + { url = "https://files.pythonhosted.org/packages/5a/6f/3af2ffedd5cfa08c631f89452c6648c4d779e7772dfc388c77c920ca6bbf/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:763d1d74f56f031788e5d307029caef067febf890cd1f8bf61183ae142f1a77b", size = 2343898, upload-time = "2025-05-17T17:20:36.086Z" }, + { url = "https://files.pythonhosted.org/packages/9a/dc/9060d807039ee5de6e2f260f72f3d70ac213993a804f5e67e0a73a56dd2f/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:954af0e2bd7cea83ce72243b14e4fb518b18f0c1649b576d114973e2073b273d", size = 2269197, upload-time = "2025-05-17T17:20:38.414Z" }, + { url = "https://files.pythonhosted.org/packages/f9/34/e6c8ca177cb29dcc4967fef73f5de445912f93bd0343c9c33c8e5bf8cde8/pycryptodome-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:257bb3572c63ad8ba40b89f6fc9d63a2a628e9f9708d31ee26560925ebe0210a", size = 1768600, upload-time = "2025-05-17T17:20:40.688Z" }, + { url = "https://files.pythonhosted.org/packages/e4/1d/89756b8d7ff623ad0160f4539da571d1f594d21ee6d68be130a6eccb39a4/pycryptodome-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6501790c5b62a29fcb227bd6b62012181d886a767ce9ed03b303d1f22eb5c625", size = 1799740, upload-time = "2025-05-17T17:20:42.413Z" }, + { url = "https://files.pythonhosted.org/packages/5d/61/35a64f0feaea9fd07f0d91209e7be91726eb48c0f1bfc6720647194071e4/pycryptodome-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9a77627a330ab23ca43b48b130e202582e91cc69619947840ea4d2d1be21eb39", size = 1703685, upload-time = "2025-05-17T17:20:44.388Z" }, + { url = "https://files.pythonhosted.org/packages/db/6c/a1f71542c969912bb0e106f64f60a56cc1f0fabecf9396f45accbe63fa68/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27", size = 2495627, upload-time = "2025-05-17T17:20:47.139Z" }, + { url = "https://files.pythonhosted.org/packages/6e/4e/a066527e079fc5002390c8acdd3aca431e6ea0a50ffd7201551175b47323/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cfb5cd445280c5b0a4e6187a7ce8de5a07b5f3f897f235caa11f1f435f182843", size = 1640362, upload-time = "2025-05-17T17:20:50.392Z" }, + { url = "https://files.pythonhosted.org/packages/50/52/adaf4c8c100a8c49d2bd058e5b551f73dfd8cb89eb4911e25a0c469b6b4e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490", size = 2182625, upload-time = "2025-05-17T17:20:52.866Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e9/a09476d436d0ff1402ac3867d933c61805ec2326c6ea557aeeac3825604e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575", size = 2268954, upload-time = "2025-05-17T17:20:55.027Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c5/ffe6474e0c551d54cab931918127c46d70cab8f114e0c2b5a3c071c2f484/pycryptodome-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0698f65e5b570426fc31b8162ed4603b0c2841cbb9088e2b01641e3065915b", size = 2308534, upload-time = "2025-05-17T17:20:57.279Z" }, + { url = "https://files.pythonhosted.org/packages/18/28/e199677fc15ecf43010f2463fde4c1a53015d1fe95fb03bca2890836603a/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53ecbafc2b55353edcebd64bf5da94a2a2cdf5090a6915bcca6eca6cc452585a", size = 2181853, upload-time = "2025-05-17T17:20:59.322Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ea/4fdb09f2165ce1365c9eaefef36625583371ee514db58dc9b65d3a255c4c/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:156df9667ad9f2ad26255926524e1c136d6664b741547deb0a86a9acf5ea631f", size = 2342465, upload-time = "2025-05-17T17:21:03.83Z" }, + { url = "https://files.pythonhosted.org/packages/22/82/6edc3fc42fe9284aead511394bac167693fb2b0e0395b28b8bedaa07ef04/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dea827b4d55ee390dc89b2afe5927d4308a8b538ae91d9c6f7a5090f397af1aa", size = 2267414, upload-time = "2025-05-17T17:21:06.72Z" }, + { url = "https://files.pythonhosted.org/packages/59/fe/aae679b64363eb78326c7fdc9d06ec3de18bac68be4b612fc1fe8902693c/pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886", size = 1768484, upload-time = "2025-05-17T17:21:08.535Z" }, + { url = "https://files.pythonhosted.org/packages/54/2f/e97a1b8294db0daaa87012c24a7bb714147c7ade7656973fd6c736b484ff/pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2", size = 1799636, upload-time = "2025-05-17T17:21:10.393Z" }, + { url = "https://files.pythonhosted.org/packages/18/3d/f9441a0d798bf2b1e645adc3265e55706aead1255ccdad3856dbdcffec14/pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c", size = 1703675, upload-time = "2025-05-17T17:21:13.146Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" }, + { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" }, + { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" }, + { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" }, + { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" }, + { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" }, + { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" }, + { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" }, + { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" }, + { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" }, + { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" }, + { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" }, + { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" }, + { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, + { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" }, + { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" }, + { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" }, + { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" }, + { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" }, +] + [[package]] name = "pygments" version = "2.20.0" @@ -2191,6 +2684,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] +[[package]] +name = "pypdfium2" +version = "5.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/78/a52cb80611339ec95f35c7a10d7bfe7a6f97f3b50a35a9f94283d062512e/pypdfium2-5.13.0.tar.gz", hash = "sha256:7ca2d8e31bd8d0d40c496416b7d8bea423388669ffd494929f50e8c3a82326b8", size = 273639, upload-time = "2026-08-13T10:58:15.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/9c/a49050af85055054299c7fab658ac63f8fddde575774aecbf8f71c7a9e5f/pypdfium2-5.13.0-py3-none-android_23_arm64_v8a.whl", hash = "sha256:882f4bbd4b17a335b43603169a14cde9341de12b238acd5c39e690cbca7c4293", size = 3417299, upload-time = "2026-08-13T10:57:40.522Z" }, + { url = "https://files.pythonhosted.org/packages/50/ad/f23027328843ee2bdd05afe16bb101f5906befd0c70de35fa8c53f60a5ff/pypdfium2-5.13.0-py3-none-android_23_armeabi_v7a.whl", hash = "sha256:d96929bde3bd64c771ab3558ca1ffd7704cc4d872ab92cd9f8f8b8a20f7f36b8", size = 2864708, upload-time = "2026-08-13T10:57:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/08/99/1fe58428b69d2722dcbcfaa08ce71834a332c5b518fd58874bcef936b823/pypdfium2-5.13.0-py3-none-macosx_13_0_arm64.whl", hash = "sha256:da5c7b74eebf40b5c1fbe1de01aa1edc8827a79fb1efd999616bc20dcaf77ba4", size = 3507415, upload-time = "2026-08-13T10:57:43.978Z" }, + { url = "https://files.pythonhosted.org/packages/9f/41/06e26da88a4f5b4ed289325868717a186020661b7b221aa6df622711d31b/pypdfium2-5.13.0-py3-none-macosx_13_0_x86_64.whl", hash = "sha256:2abedfb5c70992b19c780ed58d7f7b929e8ce8ee52c9140158f44317c90ec6c7", size = 3670979, upload-time = "2026-08-13T10:57:45.607Z" }, + { url = "https://files.pythonhosted.org/packages/fe/31/f8210d53775f142be934336665b1d60e800c3f176f28c29b4908d945c518/pypdfium2-5.13.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ee8c2bb2e68b396ab4a763215ac100dacb6b96d0da5bebeb239a021aecc3a7e", size = 3676486, upload-time = "2026-08-13T10:57:47.267Z" }, + { url = "https://files.pythonhosted.org/packages/94/50/d339fa09fbe592564b100bfc76833170a1104a764a458ac2abfffcb632f2/pypdfium2-5.13.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07f58e91b8c45ca144a1ff3008faf3c73ef8a5e9fb32988831788363288228cd", size = 3400883, upload-time = "2026-08-13T10:57:49.189Z" }, + { url = "https://files.pythonhosted.org/packages/c3/e0/b10cf41b5e9f0212d014c40635659c6ab95bb4fcc6fc47f5d3c571f8d57f/pypdfium2-5.13.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46b2f5be9e7ae941ee4216e3d20b66f9dc3d81944a3d57756272de5275204709", size = 3803912, upload-time = "2026-08-13T10:57:50.865Z" }, + { url = "https://files.pythonhosted.org/packages/a7/d8/25ba4ce9a9059ece82f4514df0658fde0aa9bbeafe135e76017c052bf56f/pypdfium2-5.13.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d96beb7f379e6c76d874ca93fcd182ac3168dd499056407070f9927fb1061b8e", size = 4218231, upload-time = "2026-08-13T10:57:52.525Z" }, + { url = "https://files.pythonhosted.org/packages/d3/7c/74a2fb48e5b0d2402d9ca64b39074c722d67e9a8a2c58449a843a8c2329a/pypdfium2-5.13.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81df25c1ab4c13ff773102d3cbea1967511d079123b067fc077bd0c4d57d91d8", size = 3730077, upload-time = "2026-08-13T10:57:54.021Z" }, + { url = "https://files.pythonhosted.org/packages/59/12/8c922f00518c26dc47d3676cc09c1d3c95e991c1977e31067d23cc2215cb/pypdfium2-5.13.0-py3-none-manylinux_2_27_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d66a32d89fa5b4a2715810171239eb194df4aba604727483ab760512f3c6a851", size = 4031512, upload-time = "2026-08-13T10:57:55.736Z" }, + { url = "https://files.pythonhosted.org/packages/c6/48/a171d034c2dac01adcc57d3dad3c97ba11f19d916f421176002c9e02c904/pypdfium2-5.13.0-py3-none-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b90b0a5ac310bb34db8eb848e58fcab4e201e124e3cf3cb1ccb7b85293e034af", size = 3995485, upload-time = "2026-08-13T10:57:57.39Z" }, + { url = "https://files.pythonhosted.org/packages/36/2e/dcb24776d409bb9e5b7fb26a0c62a87b98ab0e30dfcca645eaf31e35123b/pypdfium2-5.13.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ada81c36483cd61d07e32bc7814620ee96256b4f421b913f566861bf91800248", size = 5016636, upload-time = "2026-08-13T10:57:59.181Z" }, + { url = "https://files.pythonhosted.org/packages/93/24/1fab8470fc6de6f4481f009c90757b1a1ee0a61d8e864ed273f72ffca855/pypdfium2-5.13.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:3826e521e895648983cb9ee6b934d4bf51552600043984f84e9c2b3b14b696f3", size = 4555251, upload-time = "2026-08-13T10:58:00.753Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ef/6e8dbea1eddcb55cf34172753ffccd39566333c803cc94d43c653f369f2f/pypdfium2-5.13.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5c029d7163a91f264eafab51fb442a84a33efd9fd83d5a06c0136a7857a3cc8d", size = 5263483, upload-time = "2026-08-13T10:58:02.48Z" }, + { url = "https://files.pythonhosted.org/packages/53/fe/2ff673730189a621c01f9193c74b0f6aa70d8740889fdf11949e1c541869/pypdfium2-5.13.0-py3-none-musllinux_1_2_ppc64le.whl", hash = "sha256:be2dccbde0ce7efe334ecd8f348df4308db360756ede4f0821d82dfc9a58caa8", size = 5144135, upload-time = "2026-08-13T10:58:04.351Z" }, + { url = "https://files.pythonhosted.org/packages/19/0b/759b9037c007317fa5c990dd3f6eff2b99d3fbced251d1e2512be92f2e2e/pypdfium2-5.13.0-py3-none-musllinux_1_2_riscv64.whl", hash = "sha256:bcd81394fe101405e026eedb3e40bef84635c1e5d974dd6036420eb6937753c6", size = 4648156, upload-time = "2026-08-13T10:58:06.036Z" }, + { url = "https://files.pythonhosted.org/packages/db/3b/ffe29679c52efe8eb02d77aa6656e6d6201395423329af018ebd5923a3d0/pypdfium2-5.13.0-py3-none-musllinux_1_2_s390x.whl", hash = "sha256:2ed32ff685f8e05e637c990bedbf5fca66727bf27718d8bc33eeab21ce0630d1", size = 5089852, upload-time = "2026-08-13T10:58:07.791Z" }, + { url = "https://files.pythonhosted.org/packages/7b/b6/cebacc1601ddfdcd1e6a1dc321533d215ceccf9b825fa9b91b11c6dc39fb/pypdfium2-5.13.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9c777edba28d1d5fd15435ed3a78ee2fdb93dd069be37cb53b559bc122793770", size = 5074153, upload-time = "2026-08-13T10:58:09.396Z" }, + { url = "https://files.pythonhosted.org/packages/54/40/cf14c4f534f817788966857afdedb90002198dca5ce4fe2c6ecb031955ae/pypdfium2-5.13.0-py3-none-win32.whl", hash = "sha256:d33ee7077db67478b75efe4b5ea9610fb96c5416a0bc4949227f0f59c34dfcd9", size = 3753164, upload-time = "2026-08-13T10:58:10.97Z" }, + { url = "https://files.pythonhosted.org/packages/5d/99/a37b6b902457569468ed5908c94e56cb6c4032541f02cf89f723d42a9148/pypdfium2-5.13.0-py3-none-win_amd64.whl", hash = "sha256:47dcca2a8d507b5fd24f94c3c9d48fb379430f097bc20f01beff6c963ffbcedb", size = 3885553, upload-time = "2026-08-13T10:58:12.709Z" }, + { url = "https://files.pythonhosted.org/packages/50/7f/d39f6e64375c2ffd50ea100e3c73af79085c880c2791eb7203bc61d8913f/pypdfium2-5.13.0-py3-none-win_arm64.whl", hash = "sha256:554a0b23376460af1410e3c915906895e2dac67a086b9e6ccde0643a795d3b0d", size = 3700026, upload-time = "2026-08-13T10:58:14.206Z" }, +] + [[package]] name = "pyright" version = "1.1.411" @@ -2247,6 +2769,96 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, ] +[[package]] +name = "python-bidi" +version = "0.6.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/e7/f168f2c3151aa05b9f9c9b2f7767bc8e06a133ea822c231ab497d4f36833/python_bidi-0.6.11.tar.gz", hash = "sha256:034090c597af250d699299d7e7f1e83eb016f9e47b3b707bd89ab2bdec77bce0", size = 57647, upload-time = "2026-06-30T14:23:42.626Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/4b/7f942cdb3cc948a369bfe2530343f2d650aa17bb04b0b959834919f699a4/python_bidi-0.6.11-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:56f27c1edfd15c12c9c348378ccd79166930d720cf316b1181a0a0ade2146253", size = 269449, upload-time = "2026-06-30T14:22:42.207Z" }, + { url = "https://files.pythonhosted.org/packages/ee/6e/af3e17cb48b87176c209ac4271c8a9aaad8c33f5535739b58336222e69af/python_bidi-0.6.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a52f7ad9ef9091e81869e5d255e796755ccf542ade14dda17647cb7d7ffe1b9c", size = 269840, upload-time = "2026-06-30T14:22:31.833Z" }, + { url = "https://files.pythonhosted.org/packages/91/62/f7303a11e8286b2219088bb863974398a1a9f117444e78bbfcefaef7bc14/python_bidi-0.6.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4ebc24ac38e50676f65daf7ba6c568789660cb60d6dcf2606d4310dba826721", size = 295322, upload-time = "2026-06-30T14:21:29.37Z" }, + { url = "https://files.pythonhosted.org/packages/29/38/930e63c374133760f69159da36a7aba98368ade44ae708215addc4079d91/python_bidi-0.6.11-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:969ee7db3e169fcc0b2d2d094826e03cc5798dfd6b3571a340ea883672396cb1", size = 300777, upload-time = "2026-06-30T14:21:40.302Z" }, + { url = "https://files.pythonhosted.org/packages/8d/af/f92408a2882ed7c94c3df9038df48400a5adc345e963d488bf716ad25358/python_bidi-0.6.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0d496f9fc21b7457e12395e54088ab99776966c663b4cd4a74770c7a6418ab59", size = 419532, upload-time = "2026-06-30T14:21:50.531Z" }, + { url = "https://files.pythonhosted.org/packages/24/0c/5fb11159f50e9a898862fa40fe98b8994eccd224189bb1c805ecfae66977/python_bidi-0.6.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad5d9b8e8a6c330208eba413db506de58f21dbf88a1f1d5d75ef5f9e0e714adf", size = 320985, upload-time = "2026-06-30T14:22:01.157Z" }, + { url = "https://files.pythonhosted.org/packages/16/d3/6bd8b189219ec128263b7277b1cdaed0cf014199c61e05793be2d9cb0456/python_bidi-0.6.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acccb6d90e694684db2d314db2e2b0d3b8949bf1cfaec6d9808a8889f548add7", size = 299186, upload-time = "2026-06-30T14:22:21.702Z" }, + { url = "https://files.pythonhosted.org/packages/36/9e/b7da9c128f5ed867a62cb8112602bc7a6b6af783bbac18ca303a8a09b868/python_bidi-0.6.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a1b5ee069001bf7f4fff109598a9396caa96bb35e1b906b2c6d1bab9f9b2c4bd", size = 316856, upload-time = "2026-06-30T14:22:11.328Z" }, + { url = "https://files.pythonhosted.org/packages/72/a9/f5e4c286ae22fff569eb7aeeec7a5342e26e0ed1ac8202c48cf273932b28/python_bidi-0.6.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:619ee3fe03daec8d3ce12239f0c22455676a063b2bcde361caecd788fae5b8d5", size = 471488, upload-time = "2026-06-30T14:22:53.429Z" }, + { url = "https://files.pythonhosted.org/packages/22/29/47d84a333ee00db0cff74fde2aa39e01953abb360d14ffed70ad9195af26/python_bidi-0.6.11-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:af7711cc6eeeadcb1aae877e0736a68e111c73a29562da6106c5e2fbb4dd83b2", size = 576451, upload-time = "2026-06-30T14:23:05.961Z" }, + { url = "https://files.pythonhosted.org/packages/50/b1/1c158a64d745e4916fbc39c6ad700c5b2cf3b4456f710a62f625fba0694f/python_bidi-0.6.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b2c759e13ebb81edaac3041697328bb1ca8433b55281723879f6b54e74881240", size = 537476, upload-time = "2026-06-30T14:23:18.118Z" }, + { url = "https://files.pythonhosted.org/packages/a9/7c/eeaad2247b29f736cda2632d14c5959940a4b4e8e098559cd2a4065356eb/python_bidi-0.6.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b433840a924c8788f0abbda15d22c71dc636e2078d7d3ba39369cebe4bef74b8", size = 503885, upload-time = "2026-06-30T14:23:31.134Z" }, + { url = "https://files.pythonhosted.org/packages/ad/d0/ec71ea3e29cc745580ac5477bbf3c9235782ab0c7bc08ac065e4c7cb12ec/python_bidi-0.6.11-cp311-cp311-win32.whl", hash = "sha256:8b6b7fce8f47578be9aebf5a0b6b2d6c157b4e97af7586ecf13bfca5d128deea", size = 158704, upload-time = "2026-06-30T14:23:53.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/98/0458bf0adcf09f1766e069f6d1265d83b8354146e689927f0d14451bf4e2/python_bidi-0.6.11-cp311-cp311-win_amd64.whl", hash = "sha256:555cdf9303c40bae1ab512ca427f1f0316a574bc0a48db22eec76ec0fd1213cf", size = 163236, upload-time = "2026-06-30T14:23:44.741Z" }, + { url = "https://files.pythonhosted.org/packages/bd/ad/e2ff0e5077de577211d7d4fd6a436a97d903d6c65e8deb4c958de901b0eb/python_bidi-0.6.11-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:83ee87feb5eafc0442e1db0014dad20d52a2a7a140b6cddc8f7bc65918f0a7b4", size = 267279, upload-time = "2026-06-30T14:22:43.34Z" }, + { url = "https://files.pythonhosted.org/packages/08/19/776b39e47e0bde27000fc2e68c2dd0bad023d4c470dcd5ec9c98779a62c8/python_bidi-0.6.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d6970b09f5a3102c0aa192f5258c585742ab4ebd94f637a635ad3448ccba567", size = 265032, upload-time = "2026-06-30T14:22:32.901Z" }, + { url = "https://files.pythonhosted.org/packages/cb/fc/87f3b820bbee3620bdd89047ee617db49700719a632d149e1c8a8c6ec59b/python_bidi-0.6.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:495a76c881d78ab87b57c5270679c9bc3c1de36d8c6596d5e3a5a1b5f9c57471", size = 292228, upload-time = "2026-06-30T14:21:30.411Z" }, + { url = "https://files.pythonhosted.org/packages/d2/2b/e48c592fcd01409bd09eb1a181c31702f7c048d06bca004b8840d101f69a/python_bidi-0.6.11-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9af2b5c26a3eb960699dff040535a86dc2c0f708087b2d63bcfd6452fe9d0664", size = 297708, upload-time = "2026-06-30T14:21:41.536Z" }, + { url = "https://files.pythonhosted.org/packages/77/68/9da530ac64b961f5dcdcaad03d90b35becb43dd7d244241809da11acca34/python_bidi-0.6.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f99162d6c6c9522c46eb213f1bc932829c2602131676c92f081cb865b8ef6784", size = 415289, upload-time = "2026-06-30T14:21:51.919Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8e/ce62bfec64769c28d537ef0481a36dd9ee42795b2975b41846f1aa82f7cc/python_bidi-0.6.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:caee7ee3662eab1411b44fef8571b87273cb5235061d7463ebd10e412ac07986", size = 318361, upload-time = "2026-06-30T14:22:02.282Z" }, + { url = "https://files.pythonhosted.org/packages/09/fb/57a496606a4faf051a3e851cc05a5bab2a1cd37b14fc738a707a5a51bba8/python_bidi-0.6.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3611d13b53d4c899c4f2a7cd8eb897064e8b5546c3c5d1037dd6209c82858a27", size = 296198, upload-time = "2026-06-30T14:22:22.914Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e9/31fbe166932d34860271ec5e0e0ecf0bc4166bddb889a260962a448d8617/python_bidi-0.6.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ca92a4e460f7e25e434a6d7982d94a4765ca242f527995da70abb5a32003b8a", size = 313188, upload-time = "2026-06-30T14:22:12.589Z" }, + { url = "https://files.pythonhosted.org/packages/c5/ea/0c2ae4a316ea919698ede4201da1c07dc1c2f9a86bd3bce3f9b94b0ce7e6/python_bidi-0.6.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:539d99efe02f4981171ed57bbc085094ef780405ca14663550a56c6c3e265c34", size = 468437, upload-time = "2026-06-30T14:22:54.946Z" }, + { url = "https://files.pythonhosted.org/packages/17/6c/4795fb7f3ddca33a981158437ff6bd4c532d1011b9d887f47cff45d035e8/python_bidi-0.6.11-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9435acc52438c3c8f5142b9a17a622927618e80a32eed707343bc375cb51cebe", size = 573566, upload-time = "2026-06-30T14:23:07.133Z" }, + { url = "https://files.pythonhosted.org/packages/6e/79/8e707fb95cec1afed3fbffbf5eee93f2a73cad6f1445e17e32c6256cf397/python_bidi-0.6.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:239a00b2adb5f897d11d7b7a491d759fb9883e71a71cfd90c4147a733b4df4d3", size = 533591, upload-time = "2026-06-30T14:23:19.389Z" }, + { url = "https://files.pythonhosted.org/packages/fb/4c/5e8d01ed3d2f8ca1116e1e21085a5ca97450b4035138988e5a82ea2db916/python_bidi-0.6.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:51915502898c45e9cb36636e974aca068fc5cdb9f06b794f49abea5b12f02016", size = 500360, upload-time = "2026-06-30T14:23:32.445Z" }, + { url = "https://files.pythonhosted.org/packages/b0/77/86bf9c4a95f363451e7c322ef76afbe56034da23a2b5532bd774751022ff/python_bidi-0.6.11-cp312-cp312-win32.whl", hash = "sha256:6c92d1cad16f9ec2f2a3ae439a0bc3a8e4189ec227987bed03d5b4056d5eb9c5", size = 157049, upload-time = "2026-06-30T14:23:54.895Z" }, + { url = "https://files.pythonhosted.org/packages/04/e3/8912d05e04575a60a0481cc222805331b74154940528a6f419fc5bbba744/python_bidi-0.6.11-cp312-cp312-win_amd64.whl", hash = "sha256:0608bddcc1c53dfa5293499de13ca9935b31aa46d1c722c404a88c703d1a4e47", size = 161243, upload-time = "2026-06-30T14:23:45.819Z" }, + { url = "https://files.pythonhosted.org/packages/c4/13/38f195e9d9a144747a2ca5ed6ec922df50534e6bacb2188a27154c9f9400/python_bidi-0.6.11-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1b41cc6bc9ad78a12da5f987da15e931c771f18ceca58f2fe8ed50f253490a97", size = 266692, upload-time = "2026-06-30T14:22:44.497Z" }, + { url = "https://files.pythonhosted.org/packages/a6/a0/4a29e0bfa45038edeeac9397c0c91aee674efbbaa962f0c32c17aaf1a2c4/python_bidi-0.6.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4d00757ef7bbbf14d8628f9bdb6b0e168d5e7b03fec20da3226624f11bffce89", size = 264510, upload-time = "2026-06-30T14:22:34.029Z" }, + { url = "https://files.pythonhosted.org/packages/6c/12/0c599f95cfd3433bb773dba3624fb0f00e74ee4d9f7c0c0455d935cf938d/python_bidi-0.6.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:302f4ca7dabfe447e707d40e98520551181036c15750bdd1e73292ad8b3d8e75", size = 291878, upload-time = "2026-06-30T14:21:31.721Z" }, + { url = "https://files.pythonhosted.org/packages/78/7d/ca9f710b5bc279decae719041795a0ab2fbc02f85a5231cf2e3f4195ed1f/python_bidi-0.6.11-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:721697187f4da67dafc26f63488f463fa35ff2de8668d959fda773cccdbef0eb", size = 297428, upload-time = "2026-06-30T14:21:42.745Z" }, + { url = "https://files.pythonhosted.org/packages/0c/0c/626a2fde3ba831ace3e092544617974dfb99d3a225cf7445987b1fd68b3e/python_bidi-0.6.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da9e536546f7c62c0da595c8f71a096e0b9a80e94cfd0f329b7b200ef81e7d5", size = 414583, upload-time = "2026-06-30T14:21:53.006Z" }, + { url = "https://files.pythonhosted.org/packages/0d/93/23daba3a074f3b181fbeeef559735cd21ab55a8bad46934ed311696812b5/python_bidi-0.6.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d52ec8ccdc2fd5c61749d876a9d1eb0ea6543c1676722e1e3fb9d7800852131c", size = 318441, upload-time = "2026-06-30T14:22:03.41Z" }, + { url = "https://files.pythonhosted.org/packages/66/60/569132a43fff4e52abbdd640b76b761a773c1c1b07f5bf3576be5049e8da/python_bidi-0.6.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74457b43db34f984252e915828b5d1a4042a771f44e853a5643506d01562eccb", size = 295716, upload-time = "2026-06-30T14:22:24.117Z" }, + { url = "https://files.pythonhosted.org/packages/7c/cd/a7ffa9ae8dd1903f3c17a9a2af6c531c92c3f7f7789f5c304ef77f94bd06/python_bidi-0.6.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:397f7f289eba6ce25d99dbea99f873d699bf9aa030074e7fb746d8f93c2fb6f9", size = 313112, upload-time = "2026-06-30T14:22:13.691Z" }, + { url = "https://files.pythonhosted.org/packages/dd/e0/f252c15167d7b175e37514cf7d52cab3a244bfb0b64a5973c9ce6b33e191/python_bidi-0.6.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:946b7dbec4e64017680f1a66b3a8534659d889018ac83bd2abf958278e6f62b0", size = 468245, upload-time = "2026-06-30T14:22:56.534Z" }, + { url = "https://files.pythonhosted.org/packages/f9/06/7404eac40f2be2148cd0497251438e7690f553c5a09916d13d9803f8d32b/python_bidi-0.6.11-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:380b70d615647646dbe06f7d95dc30b8fdc9b596dbfa1cd3814feb9805c0c8f2", size = 573246, upload-time = "2026-06-30T14:23:08.653Z" }, + { url = "https://files.pythonhosted.org/packages/4c/3e/49ca8310bdad3adedd6265da5c4877368ed528a0095348aa32edd8a0299e/python_bidi-0.6.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1ffd728f1f7866ff7399906bbc17ef5cf010b90ce56a1e94937b28a1a4cf5a7d", size = 533495, upload-time = "2026-06-30T14:23:20.695Z" }, + { url = "https://files.pythonhosted.org/packages/53/60/47cea7de2ebad67e30e5154d846f4d6a49347ca0dffcb76136be57a08226/python_bidi-0.6.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:36f23f12d9b1c56ed8d82e11f56c6cba7bc3f614ee73374bc7772bb2270e0966", size = 499825, upload-time = "2026-06-30T14:23:33.846Z" }, + { url = "https://files.pythonhosted.org/packages/58/bb/94c89a185d9c5c6154a72583eff91448f48f5f776567af217326d839d8b3/python_bidi-0.6.11-cp313-cp313-win32.whl", hash = "sha256:79df1099a08e53edb678236d4d76d8de4e3901bafc84ce1788b71f9b96547325", size = 156754, upload-time = "2026-06-30T14:23:56.033Z" }, + { url = "https://files.pythonhosted.org/packages/85/0a/7ac8da3629ca8d93a419f5250c240fc13f7d34b7c04f279c8f9a474a2be9/python_bidi-0.6.11-cp313-cp313-win_amd64.whl", hash = "sha256:f563d20481f7d316adf605bb94d5b7182acecdbc4d431d60473e9b1d526d0210", size = 160888, upload-time = "2026-06-30T14:23:46.872Z" }, + { url = "https://files.pythonhosted.org/packages/1a/dc/8d088a648845e60ee8d3d758251909320d6a51774181d2a3e72e985af0b8/python_bidi-0.6.11-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5f3e1743b2d43377c4da5d687a03430a27f5769e158a9f75a50b05e7e82f4d21", size = 267877, upload-time = "2026-06-30T14:22:45.785Z" }, + { url = "https://files.pythonhosted.org/packages/ce/53/9c3e47a0579e5a3f168bb18ecfa197d494577ac19a1dd4557ecff99e2870/python_bidi-0.6.11-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6d9ef69c108b31f38e1f281a55fdedad7774bc1e952a45c8c14a18e891eee397", size = 265786, upload-time = "2026-06-30T14:22:35.254Z" }, + { url = "https://files.pythonhosted.org/packages/f6/51/3fc218678ac34a99065e45fdcc0209d2dd5e1f7766132fc4c58989a96d7d/python_bidi-0.6.11-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30d543b5baf9fca5ef5ec95647aa07c5e38fc7fa0f18be0c61d1d6c0a1032c6c", size = 293177, upload-time = "2026-06-30T14:21:32.962Z" }, + { url = "https://files.pythonhosted.org/packages/47/71/f359cfa65a6716f23c069d17b6e2b87a8c673f499753c7f9712979096f6a/python_bidi-0.6.11-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d14f2d400c75e07d1154299463a3d4d14aa5565b05088b2d9b314ccd9fd6dc3a", size = 297983, upload-time = "2026-06-30T14:21:43.731Z" }, + { url = "https://files.pythonhosted.org/packages/63/76/624bf0155d2b4fc2aa73e1276c22b66545a8d1f7280286c7e4dc443202db/python_bidi-0.6.11-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a4f4a0cd24c09df748bfdc207b089c00e7af19f3151063f4cd74ac658290186b", size = 418424, upload-time = "2026-06-30T14:21:54.25Z" }, + { url = "https://files.pythonhosted.org/packages/28/cf/4a919e5be87a352ccb48b0ad2c01e2bca5d1f804d927e17df67904af62dd/python_bidi-0.6.11-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ff75d1befc335cbe85f834e81554a024f94d9b5d1dd75a5bd99af81a1cb783c", size = 319005, upload-time = "2026-06-30T14:22:04.514Z" }, + { url = "https://files.pythonhosted.org/packages/fa/25/2d9a3b0c4982ac60b8bc94b273eb7768cca18e92c5041807058fe81f4485/python_bidi-0.6.11-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:959335cd3814cb767fb832c5c71cbc838ccd9231a812ef2cb43092a216a91d5b", size = 296830, upload-time = "2026-06-30T14:22:25.187Z" }, + { url = "https://files.pythonhosted.org/packages/61/d9/1798bf13b0e8167fee6a1bd0c2ec374c8d49a6de34a205a1a67bb8d45fb2/python_bidi-0.6.11-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:20ab47a4098577fc9a82816c330c89ff597c31ba69c98bc6a1b6a5737b03de05", size = 313852, upload-time = "2026-06-30T14:22:14.793Z" }, + { url = "https://files.pythonhosted.org/packages/25/aa/56a51fed9718e751a93ea3a4b894217a04cfaef0704159b400dee5fa5b4a/python_bidi-0.6.11-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:60d9bf6c60c022657637f64897e63dc3f5b1c07cb7f0e1ead6150aff5150c5ab", size = 469382, upload-time = "2026-06-30T14:22:57.838Z" }, + { url = "https://files.pythonhosted.org/packages/c5/8b/82979c858cba237355aee8a2a35c317ba412c4bce494e8a51ccb1c9e5321/python_bidi-0.6.11-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:cd564b8c583eba2d230d02d0467fd840045f08856b6524555cfff7f32af63c72", size = 573783, upload-time = "2026-06-30T14:23:10.012Z" }, + { url = "https://files.pythonhosted.org/packages/0f/55/a88d71d784144283f4e53a9849c4788c8a5fcce56a1690e073c014fa34fe/python_bidi-0.6.11-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fc3b0a6e2460f68de9ab98f71e3098bb21bb984563417ad104dec7ab08ebcadc", size = 534229, upload-time = "2026-06-30T14:23:22.108Z" }, + { url = "https://files.pythonhosted.org/packages/1c/f1/c50692fd2cfccb563fecac7c53af00c141bd9bed09fb9cd626e0d765c53d/python_bidi-0.6.11-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3bdb64ee0a74951465cd4a761e1029e73806ff43b2fe5be98643e52da5cabb66", size = 501275, upload-time = "2026-06-30T14:23:35.379Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0f/4a83866615b86572d0854b9623e7c44de51a7d3e21b16880cfeb0fdc1b49/python_bidi-0.6.11-cp314-cp314-win32.whl", hash = "sha256:f237ebb570fd8bbe479b6967374d82b7f0b26f9452c276bdd5f793d83e7062bd", size = 157398, upload-time = "2026-06-30T14:23:57.202Z" }, + { url = "https://files.pythonhosted.org/packages/90/78/bf20f1ab2cafaf744df006691e0f7d292f95f7c01fbaced92f5970ab3f8a/python_bidi-0.6.11-cp314-cp314-win_amd64.whl", hash = "sha256:8fbb6d222b50324fb9d49b6ff0f8566fa97b907a68c00e6622fcf34463104f4a", size = 161347, upload-time = "2026-06-30T14:23:47.921Z" }, + { url = "https://files.pythonhosted.org/packages/4d/da/cabfc8c055b53d845de46a78b641996a8ca2e4b9f7c9667fc8a54e7c9030/python_bidi-0.6.11-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:43f3e81bdd36f49171b7de6cf471086df503c29555f6f7f035ebe8f8ec1da779", size = 267716, upload-time = "2026-06-30T14:22:46.973Z" }, + { url = "https://files.pythonhosted.org/packages/b7/0e/2839f8671a2201c5e1776e04ac48170e9b8a6c989147fe1b656d19603c7a/python_bidi-0.6.11-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d7291e13496cb74fc1b71f7f1e3628586afefa531102bb4fa7af9c2d543efc3c", size = 265337, upload-time = "2026-06-30T14:22:36.426Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b8/561bfe22ac7ad3de4017542c4c6c192d0fa83a416ab2e0a66728c898e937/python_bidi-0.6.11-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ccf1fe9ecb3b02a1a11b103cd2557e2653d82c141b6e2dccec8177e6af5c4bb", size = 292362, upload-time = "2026-06-30T14:21:34.19Z" }, + { url = "https://files.pythonhosted.org/packages/45/ae/2b1159ac11e4516f566d83572481a0ca453abe132a43db4c048931676bfd/python_bidi-0.6.11-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f400a30573774a1e90c0d50d43c35d9c004afcf53de801bbfd259f84ea80f31c", size = 297068, upload-time = "2026-06-30T14:21:44.769Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d1/cf4c90a99d54ac01aeb186335dbf5a9cb89b3afbd5adc0b77b5c6f826134/python_bidi-0.6.11-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:788c11c84b520ea973992cc95751d56b783ff5857df504c1347d99cacd2fcfe7", size = 416236, upload-time = "2026-06-30T14:21:55.374Z" }, + { url = "https://files.pythonhosted.org/packages/ed/89/526f2b7be7c2e0d72dee52955538f816b7855e20d4a8e092516c13cbef78/python_bidi-0.6.11-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:879c3fba3e7511c7d01020449d970ca7d6a593f20cfc47c014d3d229a38930b2", size = 318604, upload-time = "2026-06-30T14:22:05.574Z" }, + { url = "https://files.pythonhosted.org/packages/67/a7/779464d0a6a96566f77bf8b76ec3d83cefadd8210ac758f4492942192357/python_bidi-0.6.11-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dc112e2239f69913273cbb0c050ca816b145b32037d4266c430159c5ddcdab2", size = 296857, upload-time = "2026-06-30T14:22:26.36Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bf/cba41041e3a4369be495011bd773aaedefbfef59d6c21e121e61f8ac1e2b/python_bidi-0.6.11-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19bae96ff1ee76b3a7dc962598b69426538e3021460cf85a4017437548ab6947", size = 313478, upload-time = "2026-06-30T14:22:15.968Z" }, + { url = "https://files.pythonhosted.org/packages/be/88/a4cd8dea27cab1eee8ee33be1ee2a948a7a53d37beb071811d2a0aa328d1/python_bidi-0.6.11-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:565d819fddb2bbc58c42ca5c97d73da7567f181115011e8f04c76b9d08378dcd", size = 468573, upload-time = "2026-06-30T14:22:59.11Z" }, + { url = "https://files.pythonhosted.org/packages/a5/f3/9d8954d038e876386bb36fce4f0df61c6cf5b0664e69ab4c1914420d397e/python_bidi-0.6.11-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:e6c474618a7b6a50c10007f8a9edb50def6d98d297a07a34dc2fb82c344f2b8b", size = 572916, upload-time = "2026-06-30T14:23:11.277Z" }, + { url = "https://files.pythonhosted.org/packages/ae/02/a5f8763031912748e17e0f0e06e7056b377754c3a084432f6c7f32847acd/python_bidi-0.6.11-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:73c38c604bfc01647c69ce38e5cf8b4206e2ede91ca3eb8e5d79b7409f17e0b3", size = 533968, upload-time = "2026-06-30T14:23:24.072Z" }, + { url = "https://files.pythonhosted.org/packages/fe/5c/823d93e8ea9e05e77eb76eace84ea9879fe876890da616a41077b8c7ffd5/python_bidi-0.6.11-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c26cd9d81f820159026b1c99905ca12bf13892e3f6a9303359fef42fe6f39e50", size = 500671, upload-time = "2026-06-30T14:23:36.907Z" }, + { url = "https://files.pythonhosted.org/packages/a1/00/9d16556d2e0bb4a4b2132fa37fc6235e333c6bf5bccd1375ce0a15ea1db1/python_bidi-0.6.11-cp314-cp314t-win32.whl", hash = "sha256:dfbb9ba8343a60daf4ced67c11d551dafe9a3c94892c326e4c216fa2e6eca802", size = 157355, upload-time = "2026-06-30T14:23:58.321Z" }, + { url = "https://files.pythonhosted.org/packages/9a/b6/13ea093c161da232a6eb534d420fe575ad802b0c8184860fe4f3881fbc08/python_bidi-0.6.11-cp314-cp314t-win_amd64.whl", hash = "sha256:6623683fe39b9fbf508e3069f17e8e9cab26143f9d9f89c8a8f45424c052df4f", size = 161439, upload-time = "2026-06-30T14:23:49.011Z" }, + { url = "https://files.pythonhosted.org/packages/29/27/b4878ebed0c75833629aefe0b9cbff588df86efe6068d4c100ee8d0df27b/python_bidi-0.6.11-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:8eb09af209cd660fa9689f6ce9e61e73c8afa4829ae61801deea7f6e32263800", size = 271411, upload-time = "2026-06-30T14:22:50.789Z" }, + { url = "https://files.pythonhosted.org/packages/f9/d5/080a6acda54809d12736eaf58cd71b930eba431eab78b2920727204f8a94/python_bidi-0.6.11-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7a8429d0d65232e314b4f494825c1205abcc3039f42bf7da80424a15b731709", size = 271915, upload-time = "2026-06-30T14:22:39.907Z" }, + { url = "https://files.pythonhosted.org/packages/2b/33/a076031a95627bef4e051d94ac90ff44dac687e988da4417a387e226555d/python_bidi-0.6.11-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6b3c853f99172ef22e5a16c8114cf243e351c8e70f72a894164088c2c99d9cb", size = 296494, upload-time = "2026-06-30T14:21:38.138Z" }, + { url = "https://files.pythonhosted.org/packages/3d/dd/dcb312034d421f99b00d2d2b37b1a4b6ce919a3f0c70ed3aa5570b4d1bef/python_bidi-0.6.11-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:69d1f4ee17644e8aeac93a7238ee2f28d79b0180815441eb511b77e6585aa971", size = 302105, upload-time = "2026-06-30T14:21:48.328Z" }, + { url = "https://files.pythonhosted.org/packages/8a/e2/25192b48e4bedf7491daa7aa50e196d06b46ffa495b039d492845173bffb/python_bidi-0.6.11-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80cfe97e2d65981be877ae5bd2338c6919a7cc1171fc308c8b8c7c306ba6ffd8", size = 419597, upload-time = "2026-06-30T14:21:58.598Z" }, + { url = "https://files.pythonhosted.org/packages/45/c0/1fb868cf41aab7e22cf01e8a4717c8447a43eadda8ccf367f3660c9005d8/python_bidi-0.6.11-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:83c780f7e4c3dd3f020db75dc425567981e65c6d5571b3c0372203d0df92c834", size = 322465, upload-time = "2026-06-30T14:22:08.965Z" }, + { url = "https://files.pythonhosted.org/packages/27/41/a7903ff2829d16eae2b11dff1cffeb9739373b8fe12c27ab04dda7c5c45d/python_bidi-0.6.11-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac819cac1abb15486c48af3399a5c726e89f0977a3aff205ac162533186e756", size = 300467, upload-time = "2026-06-30T14:22:29.729Z" }, + { url = "https://files.pythonhosted.org/packages/29/eb/16f4fb6acaf5d381da8faf26a306a255e54417450674d4c004a0ba0545ae/python_bidi-0.6.11-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63d9dcc714d549a5118ebc93b7a7c903a0c1feec8e57f5fcacf98d984b76325b", size = 318422, upload-time = "2026-06-30T14:22:19.416Z" }, + { url = "https://files.pythonhosted.org/packages/86/04/ef0ba9e878bb5169e32bbd96e3e39ab60e947ae1757b7dd645eb4fc2e7a4/python_bidi-0.6.11-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:d73a821873c52635321196cfb8d3a231d7917ca284cf2bcd9422f6deb19db7ca", size = 473278, upload-time = "2026-06-30T14:23:03.142Z" }, + { url = "https://files.pythonhosted.org/packages/be/93/b3aa2631c4801ddd5c22f93e430ed1d570bcb634c4a217e864195cbe7574/python_bidi-0.6.11-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:bde89739a979d9eb3ac48c4882c8a42dd528a7708b0faa99b90b551588bd5f8d", size = 577704, upload-time = "2026-06-30T14:23:15.336Z" }, + { url = "https://files.pythonhosted.org/packages/87/b0/f5b755e1e4807bb403ccad09b1ca4faca52e260f384595e9362a68e378a6/python_bidi-0.6.11-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:7738cfc7ee9fcdff3fef76b40007982ce7704010a51df307c4a51d378f5ff1ba", size = 538805, upload-time = "2026-06-30T14:23:28.158Z" }, + { url = "https://files.pythonhosted.org/packages/c7/f0/f6b9d17e3426e7b54c18d05d917982647a976233ed9348a2ef40f1a17f85/python_bidi-0.6.11-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:cf4e88a6fec81b7155a487cbbea7753a3d9a76dc4d391b4f8958b37227ef2c12", size = 505204, upload-time = "2026-06-30T14:23:41.222Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -2349,57 +2961,37 @@ wheels = [ [[package]] name = "pyyaml" -version = "6.0.3" +version = "6.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, - { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, - { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, - { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, - { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, - { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, - { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, - { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, - { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, - { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, - { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, - { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, - { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, - { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, - { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, - { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, - { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, - { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, - { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, - { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, - { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, - { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, - { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, - { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, - { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, - { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, - { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, - { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, - { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, - { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, - { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, - { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, - { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, - { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, - { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, - { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, - { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, ] [[package]] @@ -2508,7 +3100,7 @@ wheels = [ [[package]] name = "remove-ai-watermarks" -version = "0.27.2" +version = "0.28.0" source = { editable = "." } dependencies = [ { name = "c2pa-python" }, @@ -2604,6 +3196,11 @@ qwen-zimage = [ { name = "torchvision" }, { name = "transformers" }, ] +text-draft = [ + { name = "huggingface-hub" }, + { name = "paddleocr" }, + { name = "paddlepaddle" }, +] text-restoration = [ { name = "accelerate" }, { name = "diffsynth" }, @@ -2644,6 +3241,7 @@ requires-dist = [ { name = "diffusers", marker = "extra == 'diffusion'", specifier = ">=0.38.0" }, { name = "huggingface-hub", marker = "extra == 'lama'", specifier = ">=0.20.0" }, { name = "huggingface-hub", marker = "extra == 'migan'", specifier = ">=0.20.0" }, + { name = "huggingface-hub", marker = "extra == 'text-draft'", specifier = ">=0.20.0" }, { name = "invisible-watermark", marker = "extra == 'dev'", specifier = ">=0.2.0" }, { name = "numpy", marker = "python_full_version >= '3.13' and extra == 'pixels'", specifier = ">=2" }, { name = "numpy", marker = "python_full_version < '3.13' and extra == 'pixels'", specifier = ">=1.24.0" }, @@ -2651,6 +3249,8 @@ requires-dist = [ { name = "onnxruntime", marker = "extra == 'migan'", specifier = ">=1.24.0" }, { name = "opencv-python-headless", marker = "extra == 'pixels'", specifier = ">=4.8.0" }, { name = "packaging", marker = "extra == 'dev'", specifier = ">=24.0" }, + { name = "paddleocr", marker = "extra == 'text-draft'", specifier = ">=3.3.3" }, + { name = "paddlepaddle", marker = "extra == 'text-draft'", specifier = ">=3.0.0" }, { name = "piexif", specifier = ">=1.1.3" }, { name = "pillow", specifier = ">=10.0.0" }, { name = "pillow-heif", marker = "extra == 'heif'", specifier = ">=0.13.0" }, @@ -2679,7 +3279,7 @@ requires-dist = [ { name = "transformers", marker = "extra == 'diffusion'", specifier = ">=5,<6" }, { name = "trustmark", marker = "python_full_version < '3.13' and extra == 'trustmark'", specifier = ">=0.8.0" }, ] -provides-extras = ["pixels", "heif", "visible", "video", "detect", "diffusion", "qwen-zimage", "text-restoration", "trustmark", "lama", "migan", "dev", "all"] +provides-extras = ["pixels", "heif", "visible", "video", "detect", "diffusion", "qwen-zimage", "text-restoration", "text-draft", "trustmark", "lama", "migan", "dev", "all"] [[package]] name = "requests" @@ -2709,6 +3309,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, ] +[[package]] +name = "ruamel-yaml" +version = "0.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/3b/ebda527b56beb90cb7652cb1c7e4f91f48649fbcd8d2eb2fb6e77cd3329b/ruamel_yaml-0.19.1.tar.gz", hash = "sha256:53eb66cd27849eff968ebf8f0bf61f46cdac2da1d1f3576dd4ccee9b25c31993", size = 142709, upload-time = "2026-01-02T16:50:31.84Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/0c/51f6841f1d84f404f92463fc2b1ba0da357ca1e3db6b7fbda26956c3b82a/ruamel_yaml-0.19.1-py3-none-any.whl", hash = "sha256:27592957fedf6e0b62f281e96effd28043345e0e66001f97683aa9a40c667c93", size = 118102, upload-time = "2026-01-02T16:50:29.201Z" }, +] + [[package]] name = "ruff" version = "0.16.2" @@ -2817,6 +3426,66 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5d/40/e1e72872c6354b306daef1703549e8e83b4d43cfea356311bf722a043752/setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3", size = 1008090, upload-time = "2026-07-04T15:31:20.885Z" }, ] +[[package]] +name = "shapely" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/bc/0989043118a27cccb4e906a46b7565ce36ca7b57f5a18b78f4f1b0f72d9d/shapely-2.1.2.tar.gz", hash = "sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9", size = 315489, upload-time = "2025-09-24T13:51:41.432Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/8d/1ff672dea9ec6a7b5d422eb6d095ed886e2e523733329f75fdcb14ee1149/shapely-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:91121757b0a36c9aac3427a651a7e6567110a4a67c97edf04f8d55d4765f6618", size = 1820038, upload-time = "2025-09-24T13:50:15.628Z" }, + { url = "https://files.pythonhosted.org/packages/4f/ce/28fab8c772ce5db23a0d86bf0adaee0c4c79d5ad1db766055fa3dab442e2/shapely-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:16a9c722ba774cf50b5d4541242b4cce05aafd44a015290c82ba8a16931ff63d", size = 1626039, upload-time = "2025-09-24T13:50:16.881Z" }, + { url = "https://files.pythonhosted.org/packages/70/8b/868b7e3f4982f5006e9395c1e12343c66a8155c0374fdc07c0e6a1ab547d/shapely-2.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cc4f7397459b12c0b196c9efe1f9d7e92463cbba142632b4cc6d8bbbbd3e2b09", size = 3001519, upload-time = "2025-09-24T13:50:18.606Z" }, + { url = "https://files.pythonhosted.org/packages/13/02/58b0b8d9c17c93ab6340edd8b7308c0c5a5b81f94ce65705819b7416dba5/shapely-2.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:136ab87b17e733e22f0961504d05e77e7be8c9b5a8184f685b4a91a84efe3c26", size = 3110842, upload-time = "2025-09-24T13:50:21.77Z" }, + { url = "https://files.pythonhosted.org/packages/af/61/8e389c97994d5f331dcffb25e2fa761aeedfb52b3ad9bcdd7b8671f4810a/shapely-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:16c5d0fc45d3aa0a69074979f4f1928ca2734fb2e0dde8af9611e134e46774e7", size = 4021316, upload-time = "2025-09-24T13:50:23.626Z" }, + { url = "https://files.pythonhosted.org/packages/d3/d4/9b2a9fe6039f9e42ccf2cb3e84f219fd8364b0c3b8e7bbc857b5fbe9c14c/shapely-2.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ddc759f72b5b2b0f54a7e7cde44acef680a55019eb52ac63a7af2cf17cb9cd2", size = 4178586, upload-time = "2025-09-24T13:50:25.443Z" }, + { url = "https://files.pythonhosted.org/packages/16/f6/9840f6963ed4decf76b08fd6d7fed14f8779fb7a62cb45c5617fa8ac6eab/shapely-2.1.2-cp311-cp311-win32.whl", hash = "sha256:2fa78b49485391224755a856ed3b3bd91c8455f6121fee0db0e71cefb07d0ef6", size = 1543961, upload-time = "2025-09-24T13:50:26.968Z" }, + { url = "https://files.pythonhosted.org/packages/38/1e/3f8ea46353c2a33c1669eb7327f9665103aa3a8dfe7f2e4ef714c210b2c2/shapely-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:c64d5c97b2f47e3cd9b712eaced3b061f2b71234b3fc263e0fcf7d889c6559dc", size = 1722856, upload-time = "2025-09-24T13:50:28.497Z" }, + { url = "https://files.pythonhosted.org/packages/24/c0/f3b6453cf2dfa99adc0ba6675f9aaff9e526d2224cbd7ff9c1a879238693/shapely-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe2533caae6a91a543dec62e8360fe86ffcdc42a7c55f9dfd0128a977a896b94", size = 1833550, upload-time = "2025-09-24T13:50:30.019Z" }, + { url = "https://files.pythonhosted.org/packages/86/07/59dee0bc4b913b7ab59ab1086225baca5b8f19865e6101db9ebb7243e132/shapely-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ba4d1333cc0bc94381d6d4308d2e4e008e0bd128bdcff5573199742ee3634359", size = 1643556, upload-time = "2025-09-24T13:50:32.291Z" }, + { url = "https://files.pythonhosted.org/packages/26/29/a5397e75b435b9895cd53e165083faed5d12fd9626eadec15a83a2411f0f/shapely-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bd308103340030feef6c111d3eb98d50dc13feea33affc8a6f9fa549e9458a3", size = 2988308, upload-time = "2025-09-24T13:50:33.862Z" }, + { url = "https://files.pythonhosted.org/packages/b9/37/e781683abac55dde9771e086b790e554811a71ed0b2b8a1e789b7430dd44/shapely-2.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e7d4d7ad262a48bb44277ca12c7c78cb1b0f56b32c10734ec9a1d30c0b0c54b", size = 3099844, upload-time = "2025-09-24T13:50:35.459Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f3/9876b64d4a5a321b9dc482c92bb6f061f2fa42131cba643c699f39317cb9/shapely-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9eddfe513096a71896441a7c37db72da0687b34752c4e193577a145c71736fc", size = 3988842, upload-time = "2025-09-24T13:50:37.478Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a0/704c7292f7014c7e74ec84eddb7b109e1fbae74a16deae9c1504b1d15565/shapely-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:980c777c612514c0cf99bc8a9de6d286f5e186dcaf9091252fcd444e5638193d", size = 4152714, upload-time = "2025-09-24T13:50:39.9Z" }, + { url = "https://files.pythonhosted.org/packages/53/46/319c9dc788884ad0785242543cdffac0e6530e4d0deb6c4862bc4143dcf3/shapely-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9111274b88e4d7b54a95218e243282709b330ef52b7b86bc6aaf4f805306f454", size = 1542745, upload-time = "2025-09-24T13:50:41.414Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bf/cb6c1c505cb31e818e900b9312d514f381fbfa5c4363edfce0fcc4f8c1a4/shapely-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:743044b4cfb34f9a67205cee9279feaf60ba7d02e69febc2afc609047cb49179", size = 1722861, upload-time = "2025-09-24T13:50:43.35Z" }, + { url = "https://files.pythonhosted.org/packages/c3/90/98ef257c23c46425dc4d1d31005ad7c8d649fe423a38b917db02c30f1f5a/shapely-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b510dda1a3672d6879beb319bc7c5fd302c6c354584690973c838f46ec3e0fa8", size = 1832644, upload-time = "2025-09-24T13:50:44.886Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ab/0bee5a830d209adcd3a01f2d4b70e587cdd9fd7380d5198c064091005af8/shapely-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8cff473e81017594d20ec55d86b54bc635544897e13a7cfc12e36909c5309a2a", size = 1642887, upload-time = "2025-09-24T13:50:46.735Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5e/7d7f54ba960c13302584c73704d8c4d15404a51024631adb60b126a4ae88/shapely-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe7b77dc63d707c09726b7908f575fc04ff1d1ad0f3fb92aec212396bc6cfe5e", size = 2970931, upload-time = "2025-09-24T13:50:48.374Z" }, + { url = "https://files.pythonhosted.org/packages/f2/a2/83fc37e2a58090e3d2ff79175a95493c664bcd0b653dd75cb9134645a4e5/shapely-2.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ed1a5bbfb386ee8332713bf7508bc24e32d24b74fc9a7b9f8529a55db9f4ee6", size = 3082855, upload-time = "2025-09-24T13:50:50.037Z" }, + { url = "https://files.pythonhosted.org/packages/44/2b/578faf235a5b09f16b5f02833c53822294d7f21b242f8e2d0cf03fb64321/shapely-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a84e0582858d841d54355246ddfcbd1fce3179f185da7470f41ce39d001ee1af", size = 3979960, upload-time = "2025-09-24T13:50:51.74Z" }, + { url = "https://files.pythonhosted.org/packages/4d/04/167f096386120f692cc4ca02f75a17b961858997a95e67a3cb6a7bbd6b53/shapely-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc3487447a43d42adcdf52d7ac73804f2312cbfa5d433a7d2c506dcab0033dfd", size = 4142851, upload-time = "2025-09-24T13:50:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/48/74/fb402c5a6235d1c65a97348b48cdedb75fb19eca2b1d66d04969fc1c6091/shapely-2.1.2-cp313-cp313-win32.whl", hash = "sha256:9c3a3c648aedc9f99c09263b39f2d8252f199cb3ac154fadc173283d7d111350", size = 1541890, upload-time = "2025-09-24T13:50:55.337Z" }, + { url = "https://files.pythonhosted.org/packages/41/47/3647fe7ad990af60ad98b889657a976042c9988c2807cf322a9d6685f462/shapely-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:ca2591bff6645c216695bdf1614fca9c82ea1144d4a7591a466fef64f28f0715", size = 1722151, upload-time = "2025-09-24T13:50:57.153Z" }, + { url = "https://files.pythonhosted.org/packages/3c/49/63953754faa51ffe7d8189bfbe9ca34def29f8c0e34c67cbe2a2795f269d/shapely-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2d93d23bdd2ed9dc157b46bc2f19b7da143ca8714464249bef6771c679d5ff40", size = 1834130, upload-time = "2025-09-24T13:50:58.49Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ee/dce001c1984052970ff60eb4727164892fb2d08052c575042a47f5a9e88f/shapely-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01d0d304b25634d60bd7cf291828119ab55a3bab87dc4af1e44b07fb225f188b", size = 1642802, upload-time = "2025-09-24T13:50:59.871Z" }, + { url = "https://files.pythonhosted.org/packages/da/e7/fc4e9a19929522877fa602f705706b96e78376afb7fad09cad5b9af1553c/shapely-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8d8382dd120d64b03698b7298b89611a6ea6f55ada9d39942838b79c9bc89801", size = 3018460, upload-time = "2025-09-24T13:51:02.08Z" }, + { url = "https://files.pythonhosted.org/packages/a1/18/7519a25db21847b525696883ddc8e6a0ecaa36159ea88e0fef11466384d0/shapely-2.1.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:19efa3611eef966e776183e338b2d7ea43569ae99ab34f8d17c2c054d3205cc0", size = 3095223, upload-time = "2025-09-24T13:51:04.472Z" }, + { url = "https://files.pythonhosted.org/packages/48/de/b59a620b1f3a129c3fecc2737104a0a7e04e79335bd3b0a1f1609744cf17/shapely-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:346ec0c1a0fcd32f57f00e4134d1200e14bf3f5ae12af87ba83ca275c502498c", size = 4030760, upload-time = "2025-09-24T13:51:06.455Z" }, + { url = "https://files.pythonhosted.org/packages/96/b3/c6655ee7232b417562bae192ae0d3ceaadb1cc0ffc2088a2ddf415456cc2/shapely-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6305993a35989391bd3476ee538a5c9a845861462327efe00dd11a5c8c709a99", size = 4170078, upload-time = "2025-09-24T13:51:08.584Z" }, + { url = "https://files.pythonhosted.org/packages/a0/8e/605c76808d73503c9333af8f6cbe7e1354d2d238bda5f88eea36bfe0f42a/shapely-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:c8876673449f3401f278c86eb33224c5764582f72b653a415d0e6672fde887bf", size = 1559178, upload-time = "2025-09-24T13:51:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/36/f7/d317eb232352a1f1444d11002d477e54514a4a6045536d49d0c59783c0da/shapely-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:4a44bc62a10d84c11a7a3d7c1c4fe857f7477c3506e24c9062da0db0ae0c449c", size = 1739756, upload-time = "2025-09-24T13:51:12.105Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c4/3ce4c2d9b6aabd27d26ec988f08cb877ba9e6e96086eff81bfea93e688c7/shapely-2.1.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:9a522f460d28e2bf4e12396240a5fc1518788b2fcd73535166d748399ef0c223", size = 1831290, upload-time = "2025-09-24T13:51:13.56Z" }, + { url = "https://files.pythonhosted.org/packages/17/b9/f6ab8918fc15429f79cb04afa9f9913546212d7fb5e5196132a2af46676b/shapely-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ff629e00818033b8d71139565527ced7d776c269a49bd78c9df84e8f852190c", size = 1641463, upload-time = "2025-09-24T13:51:14.972Z" }, + { url = "https://files.pythonhosted.org/packages/a5/57/91d59ae525ca641e7ac5551c04c9503aee6f29b92b392f31790fcb1a4358/shapely-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f67b34271dedc3c653eba4e3d7111aa421d5be9b4c4c7d38d30907f796cb30df", size = 2970145, upload-time = "2025-09-24T13:51:16.961Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cb/4948be52ee1da6927831ab59e10d4c29baa2a714f599f1f0d1bc747f5777/shapely-2.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:21952dc00df38a2c28375659b07a3979d22641aeb104751e769c3ee825aadecf", size = 3073806, upload-time = "2025-09-24T13:51:18.712Z" }, + { url = "https://files.pythonhosted.org/packages/03/83/f768a54af775eb41ef2e7bec8a0a0dbe7d2431c3e78c0a8bdba7ab17e446/shapely-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1f2f33f486777456586948e333a56ae21f35ae273be99255a191f5c1fa302eb4", size = 3980803, upload-time = "2025-09-24T13:51:20.37Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/559c7c195807c91c79d38a1f6901384a2878a76fbdf3f1048893a9b7534d/shapely-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cf831a13e0d5a7eb519e96f58ec26e049b1fad411fc6fc23b162a7ce04d9cffc", size = 4133301, upload-time = "2025-09-24T13:51:21.887Z" }, + { url = "https://files.pythonhosted.org/packages/80/cd/60d5ae203241c53ef3abd2ef27c6800e21afd6c94e39db5315ea0cbafb4a/shapely-2.1.2-cp314-cp314-win32.whl", hash = "sha256:61edcd8d0d17dd99075d320a1dd39c0cb9616f7572f10ef91b4b5b00c4aeb566", size = 1583247, upload-time = "2025-09-24T13:51:23.401Z" }, + { url = "https://files.pythonhosted.org/packages/74/d4/135684f342e909330e50d31d441ace06bf83c7dc0777e11043f99167b123/shapely-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:a444e7afccdb0999e203b976adb37ea633725333e5b119ad40b1ca291ecf311c", size = 1773019, upload-time = "2025-09-24T13:51:24.873Z" }, + { url = "https://files.pythonhosted.org/packages/a3/05/a44f3f9f695fa3ada22786dc9da33c933da1cbc4bfe876fe3a100bafe263/shapely-2.1.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:5ebe3f84c6112ad3d4632b1fd2290665aa75d4cef5f6c5d77c4c95b324527c6a", size = 1834137, upload-time = "2025-09-24T13:51:26.665Z" }, + { url = "https://files.pythonhosted.org/packages/52/7e/4d57db45bf314573427b0a70dfca15d912d108e6023f623947fa69f39b72/shapely-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5860eb9f00a1d49ebb14e881f5caf6c2cf472c7fd38bd7f253bbd34f934eb076", size = 1642884, upload-time = "2025-09-24T13:51:28.029Z" }, + { url = "https://files.pythonhosted.org/packages/5a/27/4e29c0a55d6d14ad7422bf86995d7ff3f54af0eba59617eb95caf84b9680/shapely-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b705c99c76695702656327b819c9660768ec33f5ce01fa32b2af62b56ba400a1", size = 3018320, upload-time = "2025-09-24T13:51:29.903Z" }, + { url = "https://files.pythonhosted.org/packages/9f/bb/992e6a3c463f4d29d4cd6ab8963b75b1b1040199edbd72beada4af46bde5/shapely-2.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a1fd0ea855b2cf7c9cddaf25543e914dd75af9de08785f20ca3085f2c9ca60b0", size = 3094931, upload-time = "2025-09-24T13:51:32.699Z" }, + { url = "https://files.pythonhosted.org/packages/9c/16/82e65e21070e473f0ed6451224ed9fa0be85033d17e0c6e7213a12f59d12/shapely-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:df90e2db118c3671a0754f38e36802db75fe0920d211a27481daf50a711fdf26", size = 4030406, upload-time = "2025-09-24T13:51:34.189Z" }, + { url = "https://files.pythonhosted.org/packages/7c/75/c24ed871c576d7e2b64b04b1fe3d075157f6eb54e59670d3f5ffb36e25c7/shapely-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:361b6d45030b4ac64ddd0a26046906c8202eb60d0f9f53085f5179f1d23021a0", size = 4169511, upload-time = "2025-09-24T13:51:36.297Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f7/b3d1d6d18ebf55236eec1c681ce5e665742aab3c0b7b232720a7d43df7b6/shapely-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:b54df60f1fbdecc8ebc2c5b11870461a6417b3d617f555e5033f1505d36e5735", size = 1602607, upload-time = "2025-09-24T13:51:37.757Z" }, + { url = "https://files.pythonhosted.org/packages/9a/f6/f09272a71976dfc138129b8faf435d064a811ae2f708cb147dccdf7aacdb/shapely-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:0036ac886e0923417932c2e6369b6c52e38e0ff5d9120b90eef5cd9a5fc5cae9", size = 1796682, upload-time = "2025-09-24T13:51:39.233Z" }, +] + [[package]] name = "shellingham" version = "1.5.4" @@ -3116,6 +3785,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, ] +[[package]] +name = "typing-inspection" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" }, +] + [[package]] name = "tzdata" version = "2026.3" @@ -3125,6 +3806,93 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931", size = 348168, upload-time = "2026-07-10T08:50:36.46Z" }, ] +[[package]] +name = "ujson" +version = "5.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/89/7a/c8bb37c8f6f3623d60c33d15d18cd6d6655d0f9c3eb31a9969f76361b199/ujson-5.13.0.tar.gz", hash = "sha256:d62e3d7625384c08082abad81a077af587fdef2761bb14c3822f4234b8d07d75", size = 7166784, upload-time = "2026-06-14T22:36:50.209Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/dc/2fcf821896803248122835c800e74f4582de9d6092efb37152acd7f79bdc/ujson-5.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b7badefa73f96bad9e295ea22bd06967b851c8aad68c74196437e3584f25de5", size = 56496, upload-time = "2026-06-14T22:35:14.362Z" }, + { url = "https://files.pythonhosted.org/packages/b5/c6/83db69f96dc12509c9510084c0389c4aff4dcf427f9b613d1a23abd446ce/ujson-5.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:09effd42924a80df20a63b31a1ede905e66b0ce24aafe7a4cbedb05c783f8bb3", size = 54300, upload-time = "2026-06-14T22:35:15.522Z" }, + { url = "https://files.pythonhosted.org/packages/f0/6c/6d87206988172015781b9ff842c0a7eca897c026b2e7a95e11d86d46ddf5/ujson-5.13.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:89d0bfc986d02b4ce76b00e0f560bc8d30dfe8c05a1bfd8529e085eb6c1a77d9", size = 59976, upload-time = "2026-06-14T22:35:16.636Z" }, + { url = "https://files.pythonhosted.org/packages/ee/43/d28ca5e6c3d8c467a4c6296404067f4eee9d67dfeeebeb3c5fb0bd6cb958/ujson-5.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cdd9618e07b3b142a02f0ab8227fd52453688b8e8e60ac0511f13a25fa8009db", size = 53476, upload-time = "2026-06-14T22:35:17.664Z" }, + { url = "https://files.pythonhosted.org/packages/a9/99/d6bb0e18188954326b38499ae61b04ce8ead6425b8844384e537a491267b/ujson-5.13.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0806683e8171ec06817e6af22f14ba0cd2f16def8a2ffb22a28a10615249355d", size = 54962, upload-time = "2026-06-14T22:35:18.693Z" }, + { url = "https://files.pythonhosted.org/packages/c6/5f/8f1ce659a59ef9510fa47b95773442049a383c533a645b2da8beeeec90f1/ujson-5.13.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1cf46c79498c81f088cad4165b1669a78bba7bfbeb778c7cc1aff316e062b0d", size = 58265, upload-time = "2026-06-14T22:35:19.775Z" }, + { url = "https://files.pythonhosted.org/packages/ad/8e/9d11af9d1d19ea239b0d289cf62a611cb4f2da9ec0d46b826767f4ab1768/ujson-5.13.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2282039eb26f08ed1a1381360395f8a310f39a45ef7314cb0f258a7d2917ea3", size = 57874, upload-time = "2026-06-14T22:35:20.896Z" }, + { url = "https://files.pythonhosted.org/packages/25/e2/7891c4a1c954307ab6a575686897a4963aad36c284742216f52dc40cba4a/ujson-5.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c75eb7fac0fe92925b959cf2fa18f88d9fb76b10781fd2a6ccb895d5fb89171", size = 1037746, upload-time = "2026-06-14T22:35:21.964Z" }, + { url = "https://files.pythonhosted.org/packages/89/4e/8a4ce87d3eaaee398f4238f74f128c6eb34269f041659995b2c09710ae7c/ujson-5.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:12078e81def2790140583abefc1b979f3c77be15d53c524bf0e232f669822052", size = 1197022, upload-time = "2026-06-14T22:35:23.183Z" }, + { url = "https://files.pythonhosted.org/packages/ac/fb/a1d0a6a83a13adee3a13cad308dcd3251ac53c4bd781f737242ad2f10d19/ujson-5.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e12192a37c6c0e476554b62647acdf6139a47b6f13d8bad8dd2316763c4cee12", size = 1090116, upload-time = "2026-06-14T22:35:24.421Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6c/4aed5dce0161d6b8c95c5da760477602a05e2a540a762424395acad9aec0/ujson-5.13.0-cp311-cp311-win32.whl", hash = "sha256:ae53b3f046529c193d533ca8111492330b204d6007611cdfa20e8b764c7c1389", size = 39974, upload-time = "2026-06-14T22:35:25.779Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c5/f9f3cf19f6ac29e07782eafed562fe0a7cb451b44bd1664c58fe8974235b/ujson-5.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:2275bbaaea3eddd2e8ec0863e28a420f5f520b14a760fc3f1e49fd07a974448c", size = 40941, upload-time = "2026-06-14T22:35:26.774Z" }, + { url = "https://files.pythonhosted.org/packages/15/ec/46058bbbbe45e054cdb9f1dc0bf416fbbd5fec06bf3b4987c2437e496350/ujson-5.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:32a59e7151fe2fec8fdf9a565ee66fcf87918d48827e21cdab5e15ff9f274b78", size = 38974, upload-time = "2026-06-14T22:35:27.871Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ae/b66deca15da1f7faf6952d8eddf55978482bcbfd294ed2afe2c526ea325f/ujson-5.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bf81570ac056cb058f9117b52ca5dd800bfe9381d0076d0bb30a08a54591d654", size = 56743, upload-time = "2026-06-14T22:35:28.863Z" }, + { url = "https://files.pythonhosted.org/packages/88/4f/b03bcc9eaf4621ac9008dec90918d8fb4839d611666cb99eb255696c67fe/ujson-5.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7edf16359c52ed53406e216565d83e6b98c23c3cb9a0a01673f2493f8fb15edf", size = 54390, upload-time = "2026-06-14T22:35:29.857Z" }, + { url = "https://files.pythonhosted.org/packages/77/79/f98c6c1a4ed9d92d39d5d2d133f2b6fce5da11ea50c341117aedde8011c4/ujson-5.13.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:24539618fb3243cfdf27dab9a850acab80798a01501e9586b61fb9ecd016a891", size = 60047, upload-time = "2026-06-14T22:35:30.857Z" }, + { url = "https://files.pythonhosted.org/packages/bc/1d/f68e14cf476d149945211142f4c20782c1f232c489e8edcc4f4b58ce4997/ujson-5.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fdde6341d213b29f413b5fa9fad1392d5408074c75f0900ed949e97e546fa5df", size = 53437, upload-time = "2026-06-14T22:35:31.835Z" }, + { url = "https://files.pythonhosted.org/packages/7d/1a/5718237cf4141e5be46ff371387e90b01f27774cb6f0f79ff4803a2430ca/ujson-5.13.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:229faf041ef249ee3fd57bac1cedb123d2718ab63f6ccd50eca95ea902eb0dca", size = 55057, upload-time = "2026-06-14T22:35:32.897Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6f/7f55c1e9e0be87beebaed553fa186ad5f6d5d639cbaa9d49f78f2f91c3a9/ujson-5.13.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d02f31c2f59cc6a1c2c3633b377701fc2d8e876cc01950735d7a01132ccc233", size = 58186, upload-time = "2026-06-14T22:35:34.055Z" }, + { url = "https://files.pythonhosted.org/packages/bc/c4/9a34ade542426f56a0bc042f774073d1c247ae7575363c27587788cb2b2f/ujson-5.13.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea7204e9fa7538bfbb1396e1ee8c2bbcd3818b3633ef5bb14d4fdea52994d14d", size = 57935, upload-time = "2026-06-14T22:35:35.05Z" }, + { url = "https://files.pythonhosted.org/packages/36/06/407633f0709e168107f56368bd5a0fa8fe07acd7f1d3000710bc0bb07470/ujson-5.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c5a2478a3a1fa4421f7e035b87194eea0cf44c7971a3f32ad1b42a0dfd63c03", size = 1037685, upload-time = "2026-06-14T22:35:36.022Z" }, + { url = "https://files.pythonhosted.org/packages/c3/df/eb5bd92dc1b23254fea5b2022007baff5491a7478bfcf7e9260d3a10f1ac/ujson-5.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b535e0970c96957e999cfe5ec89361f0e8d0bb987fb5d5144f6f495cb3ed9e19", size = 1197141, upload-time = "2026-06-14T22:35:37.38Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1c/65f2ce1a0411ec9a87339db01f0d5d554a49c4248ec68ab52a1b7e14e9c4/ujson-5.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d0ad1207694988498fca7e0bb28eba7564fa33261d2f9fdf66a3aaab376b803", size = 1090225, upload-time = "2026-06-14T22:35:38.95Z" }, + { url = "https://files.pythonhosted.org/packages/73/53/310aabff0704f9c7ef0d3f431ce8b8e3147c3cca25334a205615c511f65e/ujson-5.13.0-cp312-cp312-win32.whl", hash = "sha256:d6bc9fa43a49e403c68c7eb164eef0feee9dd29474a7c6e0d3b6267025371990", size = 40075, upload-time = "2026-06-14T22:35:40.44Z" }, + { url = "https://files.pythonhosted.org/packages/b5/23/d3536d8945d1bb00248d998c8dcbe678a884681ad181072daecfafe4eea6/ujson-5.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:6692d49ff970aaa5008f4a6fe06974bc91fd957bf13173f765e46d8ba44906ea", size = 41097, upload-time = "2026-06-14T22:35:41.39Z" }, + { url = "https://files.pythonhosted.org/packages/72/a1/4b147c06ee5bb14bec6e26786358c8510c4d75e28b88146a6ac7620f1f71/ujson-5.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:5737ffe0740a788b0e6255f0ffb281db49305fd6e6a587be44c73d9e92b554c4", size = 38875, upload-time = "2026-06-14T22:35:42.357Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f1/fe8a467d8ff5821e076b96f398d3acfe3cd568d900e6ccb41b215592b152/ujson-5.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:46998fc8d11aec34a20e2010905e7059732a3d192d9a3c3fe4f9ffd146c87ec8", size = 56746, upload-time = "2026-06-14T22:35:43.398Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c0/c7ab82d6471dfa7e4fd68ae6ff2c6a50d077c05d6ecdea0cec8af635b2c4/ujson-5.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ee03ce288ba25b05cf0de87203165642277a25caa4f00a437e13152e5214e310", size = 54388, upload-time = "2026-06-14T22:35:44.586Z" }, + { url = "https://files.pythonhosted.org/packages/10/e6/4e9e998d991ff88bbc93b21daa63bba2baa61c6f952dbcec937cf7304ebe/ujson-5.13.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:cdf33b588a81b05d0b585c66f83050c49cb670623424d10e4d1ad37ba2f7eed9", size = 60051, upload-time = "2026-06-14T22:35:45.567Z" }, + { url = "https://files.pythonhosted.org/packages/9c/11/876dff43f05417a01c6119f0fa10e01f1226631c5927ef08f56876b2bb67/ujson-5.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4cabd73c114ce93c21d7db2e2d8e16217fd8a5b2b3ec754629eebef5c262d47f", size = 53438, upload-time = "2026-06-14T22:35:46.623Z" }, + { url = "https://files.pythonhosted.org/packages/09/02/f9dbf6c3e46d700eb1d9ed637567221a06eeb1ec289633be992ef54d7a34/ujson-5.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ffc61fc756a64f4d169a78cc638d769e3c324f45fc51997626abf4e5e5dd6460", size = 55060, upload-time = "2026-06-14T22:35:47.647Z" }, + { url = "https://files.pythonhosted.org/packages/bc/3d/7e49a70265a1e5ed1b5e8edd5f54d57ae41e2134faeae9b16f6f5a0eae20/ujson-5.13.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c00323c13a35822c9a67a26c0b2a0787510bf1ef490922b58009b362d1a3e21", size = 58189, upload-time = "2026-06-14T22:35:48.617Z" }, + { url = "https://files.pythonhosted.org/packages/66/34/b64278f67e19052f09810576c7e50b3da8d4f5218b226046324d4d5c24b4/ujson-5.13.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:496e662a6b46d5f936d77fb68259cece19213bb2301ddd520dbd75ac7c90c5f4", size = 57941, upload-time = "2026-06-14T22:35:49.674Z" }, + { url = "https://files.pythonhosted.org/packages/a9/8c/51513357a5c75bf3e5bae46accfdb3e6e6f5caeb72ca8b253ec45ba853fb/ujson-5.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7fd41b86444df14f8b4b7afaaa9f27bacfbf8c18380872317aeab6cd125dcede", size = 1037688, upload-time = "2026-06-14T22:35:50.699Z" }, + { url = "https://files.pythonhosted.org/packages/54/5a/dc6afe071d6b977390d2dc41e15800a2716f317988dd03187cffe7b4d624/ujson-5.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bf3c2c4ea55d4187903fcdc689a9bf5b0fc72d8c0eaff39db18c1f337c8832c1", size = 1197141, upload-time = "2026-06-14T22:35:52.052Z" }, + { url = "https://files.pythonhosted.org/packages/50/23/b473d101412c68527cb502a8728f96ab307aa7bfa75d6ea2037e2c7f74e8/ujson-5.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b6eca7751d61045a9b1e7f9a8c97ac24b164f085b60bef1c4668654bb2338011", size = 1090235, upload-time = "2026-06-14T22:35:53.589Z" }, + { url = "https://files.pythonhosted.org/packages/fa/0a/8e583cce90f9f91ca1bedb3e628b6f5642aed91feb29b197431268d4c4be/ujson-5.13.0-cp313-cp313-win32.whl", hash = "sha256:b63d3820f978bc8e98cc3f1fe26a33b0d2ea237733a23fe5e9cb5d51f466bd97", size = 40069, upload-time = "2026-06-14T22:35:55.019Z" }, + { url = "https://files.pythonhosted.org/packages/b4/b5/1fe203bc294e98fdd65606883692ad8dc0aaac73838b89c99c3513404424/ujson-5.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:17a59d5cf23ef98f7c9314524976b4b288374d83200add01d953024fb06404f9", size = 41098, upload-time = "2026-06-14T22:35:55.966Z" }, + { url = "https://files.pythonhosted.org/packages/50/5e/aceadce24fdb7cbc67f02286b1d4e91a575aaef5afb876c9908d6e6e5769/ujson-5.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:b49516fbe803ff30d6caa9ccc3799ec7f968992747ce3099eae4758928577b53", size = 38877, upload-time = "2026-06-14T22:35:56.936Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/b5139d696f5328f3cab70b9ec046f15e3f49497a4de6280974640602f539/ujson-5.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:cc9dfd41fed397ab03bb9d9fe1cbd83301211c772a17536033ce7d68877ac82b", size = 56897, upload-time = "2026-06-14T22:35:57.974Z" }, + { url = "https://files.pythonhosted.org/packages/53/55/477183aeddfdf0f88ae039ffee0ed866cfb993da0c0c9aa915807554aef8/ujson-5.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ca7ef2fa6c408a7c0f558e4d33d93b32ddc35ed6d3cfc505747931a64b7465d5", size = 54451, upload-time = "2026-06-14T22:35:58.932Z" }, + { url = "https://files.pythonhosted.org/packages/ea/63/55e5f23e156b4c8bca095d828b4cd3180c0b42aa3501ef88836d79606fea/ujson-5.13.0-cp314-cp314-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:a554b2e5bee85030369514cef8b0b913cebe1a4c2c0c13541966d50bcba22b1a", size = 60053, upload-time = "2026-06-14T22:35:59.969Z" }, + { url = "https://files.pythonhosted.org/packages/26/b6/08c6cf5548bd6f4bb557c9fa7e8edf87324bb04c17249d1966028d61dde0/ujson-5.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ea939ff629ab03ae970d03eca6d1febd8ed55ba38ca44aec64ce997537cd3fa0", size = 53481, upload-time = "2026-06-14T22:36:01.007Z" }, + { url = "https://files.pythonhosted.org/packages/6e/b3/0ac9a03551467784067f505df1bb875c639ba32f1da79ce467ab15911ada/ujson-5.13.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b98bf2faa5e37ecfe752226ea08290031e375a0c43d425a0b955fb3e702a2a71", size = 55058, upload-time = "2026-06-14T22:36:02.297Z" }, + { url = "https://files.pythonhosted.org/packages/ba/be/ec91029aec067174473d022fa0f6c3c1431a173f888d7599739f05c668eb/ujson-5.13.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a4b92344b16e414aeb609e57f62c466500e53c94f1698f5b149dc0b7223ec3e", size = 58225, upload-time = "2026-06-14T22:36:03.321Z" }, + { url = "https://files.pythonhosted.org/packages/29/33/a948f329252ece3f9c93d177243de6e677927ebc6ac44256742dbbef3c39/ujson-5.13.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7df805aad707507a1fa165fb716218ca3a89f142125dc4b23c9fcc08fa402d97", size = 57930, upload-time = "2026-06-14T22:36:04.385Z" }, + { url = "https://files.pythonhosted.org/packages/f1/0c/c33655218b8e0a8adbf066de0b999cae5c324061f3eaa4dda17423145d9e/ujson-5.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7576bdbef327c3528f011002a2d74486f6fe4e33289bdb7a042b7f1a6e9d8285", size = 1037728, upload-time = "2026-06-14T22:36:05.467Z" }, + { url = "https://files.pythonhosted.org/packages/2c/bd/d286947525ea7ce3f2d8dc55c15b9ffbe425bc455c96af7b8f8a402599a9/ujson-5.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6eee5d7cce3f32a468905f9ff61807a60287a90258d849460f6fa826e810870d", size = 1197146, upload-time = "2026-06-14T22:36:06.839Z" }, + { url = "https://files.pythonhosted.org/packages/3c/3c/9eb916377050b0785f048a34588c1c390ddd41ae00b78db68ee1ad022356/ujson-5.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:144e9d8a454cfa727e0f755e1863738ed68068583bda5463052cb446835bd56c", size = 1090223, upload-time = "2026-06-14T22:36:08.329Z" }, + { url = "https://files.pythonhosted.org/packages/dd/5f/242fd97a2628b842d4bfaa9b18e1f68187f934d67503291ebbaab1254637/ujson-5.13.0-cp314-cp314-win32.whl", hash = "sha256:576f35c35b918d67d41b933878062ec0a5c9f4d1e9e14e04aeef35384963feae", size = 41223, upload-time = "2026-06-14T22:36:09.644Z" }, + { url = "https://files.pythonhosted.org/packages/23/f3/7f2bd9ca0c507142d0c22347b3d6f8803be1d8851c31707e57f5923fdbea/ujson-5.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:d5e206e9f849ead27e51ef8da44e52b38da7c6dbd929a7340ab44533edcda8d7", size = 42265, upload-time = "2026-06-14T22:36:11.043Z" }, + { url = "https://files.pythonhosted.org/packages/b0/29/3e9a8fba321c031315f6d263510969a5d01f41fc471b5be107e413c1b2f8/ujson-5.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:dc470179775468f9a007d3a6a2734624248c94bf47c6645e808c7e50a5070d1a", size = 40205, upload-time = "2026-06-14T22:36:12.286Z" }, + { url = "https://files.pythonhosted.org/packages/12/e9/1c543837c6a3c6672361882a0fa269bd02daf9cc4c0ca88a9dccd9df98d9/ujson-5.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:69b4e36bb7d5f413ba8c00c8006b2ec627cc5ace97301462f6aadb66ec9d2979", size = 57402, upload-time = "2026-06-14T22:36:13.238Z" }, + { url = "https://files.pythonhosted.org/packages/99/e4/39862f0f7174ff07cfd1e2d0c9065ded34aeebdb7db8daf2f0e5bf89b46f/ujson-5.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8b644d50f66de5490c1823c7176618cead5e8e8a88cba9f40a6308ca52e79267", size = 54973, upload-time = "2026-06-14T22:36:14.432Z" }, + { url = "https://files.pythonhosted.org/packages/02/66/f53d3b32c3f177f846ca6b624e832f29000d8a213a2d8768e254bd470ced/ujson-5.13.0-cp314-cp314t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:15107aaa4f559d55201165ec32abb35c283a861be1fa67229578cb7d93fcd93a", size = 60683, upload-time = "2026-06-14T22:36:15.806Z" }, + { url = "https://files.pythonhosted.org/packages/e6/d4/dddc4646d2633c85c938c2ded7d5a9711cdad5be1e13b31b7dad76f61c83/ujson-5.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6e343c5f0c058523f1edbf6ae4eceb4e0d934205a53bbdd8d9a945c83324662a", size = 54167, upload-time = "2026-06-14T22:36:16.952Z" }, + { url = "https://files.pythonhosted.org/packages/5f/c0/d8608c3f4d3f05e6441364b63fde1d279700135c1a6577a773662c07fbcc/ujson-5.13.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:02200035bc80e830f076ffc1b329a94c295aee6d9de8c9043647cb9a7bd4f76f", size = 55568, upload-time = "2026-06-14T22:36:17.975Z" }, + { url = "https://files.pythonhosted.org/packages/22/8e/dd12b735aaba0806c3d70c18184d50e1f9712e0757c7c0a4f376450cfe28/ujson-5.13.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7f19b81b73ff28f5c5022ee794f94122bfcda07a76423078e349465d71223a1", size = 59086, upload-time = "2026-06-14T22:36:19.071Z" }, + { url = "https://files.pythonhosted.org/packages/48/43/ad41e8752d5ec3a590a5e7b426a54e36b7aab911d9b5a4f7384dc62507ab/ujson-5.13.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:82e1393e6dbe3c95fdfc95c6c528890e191351a1f024ef51126cf1f22543af52", size = 58667, upload-time = "2026-06-14T22:36:20.171Z" }, + { url = "https://files.pythonhosted.org/packages/e1/8e/b44a6afb77b94118655c029081b7932d64bb4c5b1c8ba2b7f5808b5d0bc2/ujson-5.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:38afcf994b28ed85ea2420e2a8d79a37d0a77348b3daf53850c16edda66f942d", size = 1038553, upload-time = "2026-06-14T22:36:21.245Z" }, + { url = "https://files.pythonhosted.org/packages/7e/93/fab1d786174c8780eb3e386c73f1925a435e97fbf77c957fea4fca83994d/ujson-5.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:1bdf2518971586f2b413156c49d9dd8b56cc990a8647081e1bd00af60564d469", size = 1197938, upload-time = "2026-06-14T22:36:22.585Z" }, + { url = "https://files.pythonhosted.org/packages/f3/bc/2f073bb708f9d128f5d1cb39063a5f6421b1ce94c61be8661c55a189f407/ujson-5.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:751ad01042472f1c7c02f5c597c7aee79834e82a6cc384ca302173bbc8e8deb8", size = 1090938, upload-time = "2026-06-14T22:36:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/c4/67/cdaa50bba29d7dc9eb19212755b09bb96f56596e75957c3717c6b85454de/ujson-5.13.0-cp314-cp314t-win32.whl", hash = "sha256:74f3dd61aeb01b7b2a6754e400224e819279041b3867935a55ccf57fb86a43b2", size = 41802, upload-time = "2026-06-14T22:36:25.418Z" }, + { url = "https://files.pythonhosted.org/packages/bd/66/a6e669e90083febdf6c0600d3807f6017fd4d3962d5bd6ddc605c73a06e5/ujson-5.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5c31317d5e4504dae8f98795358b6082fc0ef96e7394806db0a76a4a8717f446", size = 42790, upload-time = "2026-06-14T22:36:26.614Z" }, + { url = "https://files.pythonhosted.org/packages/3d/5f/fcc6c6a9d711fd8b020ca8ff65148212f0a712c809d173cd949e58de68c6/ujson-5.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aefd3c9c95f9b62348956396ff7b31818476f8f54dc4a4e64cbd4f0491db6fca", size = 40708, upload-time = "2026-06-14T22:36:27.721Z" }, + { url = "https://files.pythonhosted.org/packages/30/70/dbdd277d64bd3a149532567ceb082fe26f4ead58c39e0a97566ccbdf14a3/ujson-5.13.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3e074a1f7778d58aa3b3056bab7b6251aabb3f381808018ca2b7fb8dbdeef7ab", size = 58393, upload-time = "2026-06-14T22:36:28.702Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/592c70af94a67cafacd9c840ae2980f27d511dde2732a4c0dfac8f176ae8/ujson-5.13.0-graalpy312-graalpy250_312_native-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8bb53ef95d35875262b8d0aa28506ca612ddd07058bee2a90f609938e69dc801", size = 54447, upload-time = "2026-06-14T22:36:29.802Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9d/2bb91e1e25a8584cb3b63544b9bd26f621173535c77ac6cae13bad8e7904/ujson-5.13.0-graalpy312-graalpy250_312_native-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fb296a0aa480ab88d895ddaa90372604c08ccc72323f02590612c775426ab413", size = 56066, upload-time = "2026-06-14T22:36:30.806Z" }, + { url = "https://files.pythonhosted.org/packages/76/ec/8e3802fc4a4e31e817b972bbb0e704a484d8c75ec349b3feb45fa9fb54c4/ujson-5.13.0-graalpy312-graalpy250_312_native-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2862f81af44b3a7e74c5d80caa118d736be1991ce6f1d5c723716fa403060cc6", size = 54938, upload-time = "2026-06-14T22:36:32.051Z" }, + { url = "https://files.pythonhosted.org/packages/4a/48/d0e3e511039b86fd1ecfe2bf761c800552d273ef8f19e71de93bf38a909e/ujson-5.13.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c16e07581172f08585b409246f4535dab13ee85af0e3d3cfa8684b653ca44fa8", size = 56115, upload-time = "2026-06-14T22:36:33.349Z" }, + { url = "https://files.pythonhosted.org/packages/81/b5/689613037fe691d18eae075cd141089f3a3156146be14512df92d8a9ae8f/ujson-5.13.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:9bd0f2dd05937c3b089af316884de18c6f6182ddb8ffce597d2e7c7a9ba9f447", size = 41802, upload-time = "2026-06-14T22:36:34.523Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7f/276f830bef4d530d50ff4d8f8c568002e7ebed9f64c06747b1d1c4325f02/ujson-5.13.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:96e7e019b097b4b25fccddadb369d13f412c13695fcf0680b6bf906376156151", size = 52479, upload-time = "2026-06-14T22:36:35.627Z" }, + { url = "https://files.pythonhosted.org/packages/fc/50/99b05555ef42a2ab2e26aa369c46bde6a64f8aeeb687628102e98cc78bed/ujson-5.13.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:7892ea6dd85ede6d30fbbd22af1239b9d81dabdf9b7a8f10ca6d4464d4d9b8ab", size = 49953, upload-time = "2026-06-14T22:36:36.72Z" }, + { url = "https://files.pythonhosted.org/packages/78/15/399766e8ba002bd8e5e2e45828e0e12a5dbeb4145d6a604ba3787db5eec2/ujson-5.13.0-pp311-pypy311_pp73-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:e1842e10adc8f0db0d3e8aa3a1f8b05ce0456b39e180c8553d7f36dd0bf24b6b", size = 55716, upload-time = "2026-06-14T22:36:37.833Z" }, + { url = "https://files.pythonhosted.org/packages/61/a6/825d92bce42cd442b57d89b4c20afc1737246321d3433c6194e5c35c6a11/ujson-5.13.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8bbb1f5ab810954fb307b6c5e68af58210c31da8569f9e6498a3958c1859e72", size = 48648, upload-time = "2026-06-14T22:36:38.878Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d6/7ea7a32f35457560806375193dbc4f0d8ffd8c8adae42f86686392a76c41/ujson-5.13.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3f6d55c68985654a84a9b47ac51f2655adac4fd264e4189f832960c2270dcf70", size = 49947, upload-time = "2026-06-14T22:36:40.022Z" }, + { url = "https://files.pythonhosted.org/packages/af/b4/0bd6449ae35b6026d94f4d1a32dbc9f40c969ed1d6e36a7e26ccf56371be/ujson-5.13.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b266f182d4bce74a6a9e1f86988485e8cd422efdcc7c3f537e00cc956f52678", size = 51149, upload-time = "2026-06-14T22:36:41.129Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2d/39da479a8461d1d78d533940a8426a84e23708a6a7a426f2178e04443252/ujson-5.13.0-pp311-pypy311_pp73-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfaa8302eb9bb7f5e231f256caf83040760585e32751d19155c0f0c0225f8de1", size = 52456, upload-time = "2026-06-14T22:36:42.266Z" }, + { url = "https://files.pythonhosted.org/packages/12/cc/91ff81c50b85a158878f06d6e9153227bbc04209db291a152c286a0ee4a8/ujson-5.13.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:43dee3081b00fe447c5b9e2fe7ebfd57f7bcc5dd25b8a439c26c8c174dd581be", size = 41155, upload-time = "2026-06-14T22:36:43.455Z" }, +] + [[package]] name = "urllib3" version = "2.7.0" From 13888704f84fdc9a13195d55693f17a053dd4897 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 17:19:11 +0000 Subject: [PATCH 24/44] Bump the minor-and-patch group across 1 directory with 7 updates Bumps the minor-and-patch group with 7 updates in the / directory: | Package | From | To | | --- | --- | --- | | [python-dotenv](https://github.com/theskumar/python-dotenv) | `1.2.2` | `1.2.3` | | [c2pa-python](https://github.com/contentauth/c2pa-python) | `0.37.5` | `0.37.7` | | [av](https://github.com/PyAV-Org/PyAV) | `18.0.0` | `18.1.0` | | [transformers](https://github.com/huggingface/transformers) | `5.14.1` | `5.15.0` | | diffsynth | `2.1.0` | `2.1.2` | | [paddleocr](https://github.com/PaddlePaddle/PaddleOCR) | `3.4.1` | `3.7.0` | | [ruff](https://github.com/astral-sh/ruff) | `0.16.2` | `0.16.3` | Updates `python-dotenv` from 1.2.2 to 1.2.3 - [Release notes](https://github.com/theskumar/python-dotenv/releases) - [Changelog](https://github.com/theskumar/python-dotenv/blob/main/CHANGELOG.md) - [Commits](https://github.com/theskumar/python-dotenv/compare/v1.2.2...v1.2.3) Updates `c2pa-python` from 0.37.5 to 0.37.7 - [Release notes](https://github.com/contentauth/c2pa-python/releases) - [Changelog](https://github.com/contentauth/c2pa-python/blob/main/docs/release-notes.md) - [Commits](https://github.com/contentauth/c2pa-python/compare/v0.37.5...v0.37.7) Updates `av` from 18.0.0 to 18.1.0 - [Release notes](https://github.com/PyAV-Org/PyAV/releases) - [Changelog](https://github.com/PyAV-Org/PyAV/blob/v18.1.0/CHANGELOG.rst) - [Commits](https://github.com/PyAV-Org/PyAV/compare/v18.0.0...v18.1.0) Updates `transformers` from 5.14.1 to 5.15.0 - [Release notes](https://github.com/huggingface/transformers/releases) - [Commits](https://github.com/huggingface/transformers/compare/v5.14.1...v5.15.0) Updates `diffsynth` from 2.1.0 to 2.1.2 Updates `paddleocr` from 3.4.1 to 3.7.0 - [Release notes](https://github.com/PaddlePaddle/PaddleOCR/releases) - [Commits](https://github.com/PaddlePaddle/PaddleOCR/compare/v3.4.1...v3.7.0) Updates `ruff` from 0.16.2 to 0.16.3 - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.16.2...0.16.3) --- updated-dependencies: - dependency-name: python-dotenv dependency-version: 1.2.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-and-patch - dependency-name: c2pa-python dependency-version: 0.37.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-and-patch - dependency-name: av dependency-version: 18.1.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: transformers dependency-version: 5.15.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: diffsynth dependency-version: 2.1.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-and-patch - dependency-name: paddleocr dependency-version: 3.7.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: ruff dependency-version: 0.16.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-and-patch ... Signed-off-by: dependabot[bot] --- uv.lock | 419 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 225 insertions(+), 194 deletions(-) diff --git a/uv.lock b/uv.lock index f8eb3ac..a895a7f 100644 --- a/uv.lock +++ b/uv.lock @@ -31,7 +31,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "huggingface-hub" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "packaging" }, { name = "psutil" }, { name = "pyyaml" }, @@ -247,28 +247,28 @@ wheels = [ [[package]] name = "av" -version = "18.0.0" +version = "18.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/a4/570a5a35c8638aba01e739925846c35fdd6b0756a15526766d0a4dd3b7df/av-18.0.0.tar.gz", hash = "sha256:4ef7e72c3d3a872584a1215173b16e0226811037f40dcdbf75992631098df1ba", size = 4340222, upload-time = "2026-07-02T06:37:58.907Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/f4/f22114d30d3435e38c6af2b4870f37b864403dca6ae7af747a289ce0a18e/av-18.1.0.tar.gz", hash = "sha256:47bfc286e1bc9de7ab4681fc2b575cd2460a66919d31ffe1bd5aa54fae531a28", size = 4451061, upload-time = "2026-08-12T22:28:18.761Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/4a/9e3463df030e063d757fa12f0f39be6541b45b06b5bad48c2ce361b924bf/av-18.0.0-cp311-abi3-macosx_11_0_x86_64.whl", hash = "sha256:149289d40e732a6e49c9530bc245b49d9964cfd1c8c9e06778703b7d5bba6b25", size = 22499354, upload-time = "2026-07-02T06:36:58.751Z" }, - { url = "https://files.pythonhosted.org/packages/77/b3/2576a44b4f39c7462ced4c17fec04c756f7b0f3c5cb940d124173e417d6a/av-18.0.0-cp311-abi3-macosx_14_0_arm64.whl", hash = "sha256:35274c20d2ad3b4774fe632bcef2e34af79858ddf899352339cc3babbc13a484", size = 18175248, upload-time = "2026-07-02T06:37:01.741Z" }, - { url = "https://files.pythonhosted.org/packages/84/74/6732f17b96dc23fd23b876b2805435855abdc8a3b397142be4e581165de8/av-18.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4d683b7747a0ba9222b8a5f81e41db5f796e7f64473454ec4fe2548e083c2fa0", size = 33387843, upload-time = "2026-07-02T06:37:05.097Z" }, - { url = "https://files.pythonhosted.org/packages/6d/b9/7708c43fed7ae28b4a1bad060b4221e3334cd827cec24f7165902a6ac1f4/av-18.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ae56b40b6f8b067a8ad2dac664fbfbabac7f7a55b9a7bb031eb99289252bc017", size = 35536910, upload-time = "2026-07-02T06:37:08.806Z" }, - { url = "https://files.pythonhosted.org/packages/5a/94/eba99691d184f6a395a242d54dc370e2fd2265e95bbc98e2963a0fdbdd6c/av-18.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:ea2e8ebbce521f21b55df9400e00d721623c9020ef158f5a188a96130be0743f", size = 38984619, upload-time = "2026-07-02T06:37:11.861Z" }, - { url = "https://files.pythonhosted.org/packages/c9/cf/0d7aee07fe16aa9ffdf96043c14bed5485a52c0dea4259de87aa306ecab4/av-18.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef96dabb3e50dac249913145dff5424b302b257fd95dcb64be3c7b7a8aef16d1", size = 34451176, upload-time = "2026-07-02T06:37:15.154Z" }, - { url = "https://files.pythonhosted.org/packages/76/92/810da80b12680d4c4fe235bd1b4003289be9213ac7f114b77b8ecf0e3b3e/av-18.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:0f65518a184613e41536f29e8758c8e3d8293e46bf5bef108f04f925bbfa3f44", size = 36619869, upload-time = "2026-07-02T06:37:18.495Z" }, - { url = "https://files.pythonhosted.org/packages/11/85/0f121ff43dc5a70696676c98a8f1674e2fa787614c2abaacb15fa1a9bc99/av-18.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:aaf4d354d2beaa6651e4f92e54409a578bde64f79c0beef9a30b388d06f7c629", size = 27556236, upload-time = "2026-07-02T06:37:21.388Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f6/2509754d4d2356abc6fc0ea3d57c12ade29bac23a1fb7fc215a53ca518fb/av-18.0.0-cp311-abi3-win_arm64.whl", hash = "sha256:adac2b3833b6cb9bd6cb52664a522b94db453615b3675b1dbb26e13fe1c80da6", size = 20221133, upload-time = "2026-07-02T06:37:23.88Z" }, - { url = "https://files.pythonhosted.org/packages/e2/25/4ee23a7f1609adf9b2f140c7a8ffade64a1449d89ab431d922a809eebf19/av-18.0.0-cp314-cp314t-macosx_11_0_x86_64.whl", hash = "sha256:88dd8e35e9242662b409a6a05fd24a6775d949eb05da0ba31cab4f250eacbab5", size = 22740741, upload-time = "2026-07-02T06:37:26.659Z" }, - { url = "https://files.pythonhosted.org/packages/f1/f0/b9f8363d07aa4521913e483f6a30c7c164973ef01de62769bf9b97049cd8/av-18.0.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:f8f454349c402e2c8d6fa80b54eb2a3f86c00f414d2b399f01ae6dab075c6fd8", size = 18384189, upload-time = "2026-07-02T06:37:29.518Z" }, - { url = "https://files.pythonhosted.org/packages/c3/e5/69397019aed280a72a43e97a252dee4295df1a9e608848452e5300ec4dab/av-18.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:88ce194c2201c6a6d40336adee8a5ddde46ed743eacb500e3ae9368d1c6d889e", size = 36749881, upload-time = "2026-07-02T06:37:33.096Z" }, - { url = "https://files.pythonhosted.org/packages/37/3a/1614d74f0d676ea6745eb59553c9ad01ca25db523cba808d522e838f4f5b/av-18.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:aa15e567a018cc94a26b0ab45da676dee70c4146ace6e92e47d30cc9689cbfbe", size = 38645927, upload-time = "2026-07-02T06:37:37.086Z" }, - { url = "https://files.pythonhosted.org/packages/6b/3c/5f54710d69b0ea93634134f92b49c7a2a7fd27da5486a8a7e6251ac1cfb4/av-18.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:613153e48cefc91700746dde0ad0282d4677b194cba22cc771de14c78411cf8b", size = 40454783, upload-time = "2026-07-02T06:37:40.904Z" }, - { url = "https://files.pythonhosted.org/packages/26/92/8293e6a267e0591b543abd96ae01e7e8ed228509bdb4e4644a8a8395d90f/av-18.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:30404f53ca1ea7f350ac86ff22a2c04f903014758e9b33f398c5a62de34bd84f", size = 37573117, upload-time = "2026-07-02T06:37:44.856Z" }, - { url = "https://files.pythonhosted.org/packages/10/0c/38ed7601277ae57dfe857d040be4762530fd728efff45c2fb8f035fef96a/av-18.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6882a48f7aec2863c96cddee3256ff2da98f7fb6cbed83cee9d7e70a8f186a6b", size = 39669026, upload-time = "2026-07-02T06:37:48.761Z" }, - { url = "https://files.pythonhosted.org/packages/c8/95/0636ca04d5d89d01c49bd366d2b660cc85d1f8117c476b2be62eb0c70855/av-18.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:55a646e9afce9fdc5de5224205a8a12c7ed1ba9803145dcc876c40bfc03a109b", size = 28448336, upload-time = "2026-07-02T06:37:52.477Z" }, - { url = "https://files.pythonhosted.org/packages/01/20/1e24450ea981c44ed328691496fd2774dfa9fa3c3b00fd07f72fd5614abe/av-18.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:96f594ff506a09475e5549359352332049a25d37a08f00b4623f7f6e92e45b9c", size = 21377289, upload-time = "2026-07-02T06:37:55.935Z" }, + { url = "https://files.pythonhosted.org/packages/05/d4/d7cdc8bff143c17a6d35924375ae28dd692cacde38700a7d419fde54f44a/av-18.1.0-cp311-abi3-macosx_11_0_x86_64.whl", hash = "sha256:ae75d8bb6467895ed1f8572ededf7ffa49eac07f6e483222f5d7d62a41d12f04", size = 22546147, upload-time = "2026-08-12T22:27:11.851Z" }, + { url = "https://files.pythonhosted.org/packages/3f/c9/37a619297492256b77d5ed906e7d8166c10a26ed251dccf1ae03ab19bff6/av-18.1.0-cp311-abi3-macosx_14_0_arm64.whl", hash = "sha256:b30a4e8d934558e19602b68998a4d9ac9f250fa0dacef216f7e8e40153b13316", size = 18217603, upload-time = "2026-08-12T22:27:14.713Z" }, + { url = "https://files.pythonhosted.org/packages/d9/84/2464ffb64c08c5ce8b522c8e74594714414e3b0575267652c5c51c0574b9/av-18.1.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6fc837cc51adf80331ac850779cd53b5d4c4460b0ebe9057a02a921c6736f19d", size = 33640142, upload-time = "2026-08-12T22:27:17.835Z" }, + { url = "https://files.pythonhosted.org/packages/27/3a/204dbfc3e08eb4cdc6e6ff57be02150bc44523ebdb50182d10025792ebd9/av-18.1.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:8a032e8d8ebc73dec079364b9b4a6837638a2d106e8472314e685ffbf163e700", size = 35786210, upload-time = "2026-08-12T22:27:20.984Z" }, + { url = "https://files.pythonhosted.org/packages/e1/99/b0d04ec553ff9a7e00455458dfa3a39c8a8f627b273056b4e5fe57d590de/av-18.1.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:3c8b1f8b46f99d52e2d8b0ed5d0cdadf172d24794d46e2077b16e44ed08e26ff", size = 39379798, upload-time = "2026-08-12T22:27:24.432Z" }, + { url = "https://files.pythonhosted.org/packages/56/b1/e00d4feae59160149df6126585e726fdc6300798fd40c5dd324879e81f68/av-18.1.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ab5ac081bc9eaf54109120d4e56284674fecfbe520d9aa1707c7fa911ec5f4d2", size = 34690321, upload-time = "2026-08-12T22:27:27.769Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/836fa987e3084d11a21489f11357fb24843ef3aa8faf74ddddfc603d5062/av-18.1.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:191224788d87af06c31784a395bb73f14b72f33d7f4871ace0157de2abdc6276", size = 36859932, upload-time = "2026-08-12T22:27:31.403Z" }, + { url = "https://files.pythonhosted.org/packages/33/b4/76ba21e46704f632004276b85289a1582e95f5eff760436d6149875a1881/av-18.1.0-cp311-abi3-win_amd64.whl", hash = "sha256:ea1480b7a8d5405cb5f382b344731bf125fd2c1c6fae3964f6c48595628387ff", size = 27595679, upload-time = "2026-08-12T22:27:35.177Z" }, + { url = "https://files.pythonhosted.org/packages/4f/ad/a3135884c5753b09773176b97201ae602f67ad14206c395ff838d66bf9b0/av-18.1.0-cp311-abi3-win_arm64.whl", hash = "sha256:5509ec12aaa19fd6601de13cfa6f4cdad450da07982118510592875d970454d6", size = 20257584, upload-time = "2026-08-12T22:27:38.472Z" }, + { url = "https://files.pythonhosted.org/packages/4f/5b/4a756265d7fb164336c8d377bca21c39cfa2c178be23cedee840a69b59c5/av-18.1.0-cp314-cp314t-macosx_11_0_x86_64.whl", hash = "sha256:b36b0bae9e4c62f9487c99481ec15e4e3870fcc868522cd6d18fc2d6bfa04f01", size = 22795654, upload-time = "2026-08-12T22:27:42.016Z" }, + { url = "https://files.pythonhosted.org/packages/d5/cc/1bc841462114a1adf4f7d87456ab78a6972e23271e71865fcd2bbd0e7360/av-18.1.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:025f84494cb23278498f03b0d8117d3e47a1cbc9c44b97eb31875cf02251e46b", size = 18435735, upload-time = "2026-08-12T22:27:45.787Z" }, + { url = "https://files.pythonhosted.org/packages/b8/20/005500ed17a2e62a5e4bb94aa3786942560ec2f55ec1895ebf174c87abef/av-18.1.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:08a9ae288299cfcbf739dba4ad0c53b9b71f45184303dd45947920d022fed695", size = 37090807, upload-time = "2026-08-12T22:27:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/5c/f7/11e7f6d848d3690c31ca4f8578167393e619177f1493ccc93b9400852d4e/av-18.1.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:cf8a17466bef07765dbdecc9e66ed9b25d20b4e14f654fbf35345a58ac45fa0c", size = 38976836, upload-time = "2026-08-12T22:27:54.565Z" }, + { url = "https://files.pythonhosted.org/packages/c3/63/b271473b24e806062d31191e40c6d65545e9cf59f80f044eba56dcbba0f4/av-18.1.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d49a5c542dfdc00f43c6cdb6cc41dac1781ee206fe180b56aa7433dfa816dfae", size = 40896630, upload-time = "2026-08-12T22:27:59.118Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9f/2ab7fa292a947ad3466ed8e655eefa3b82f535d7ea598c297b4471a937c4/av-18.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5548b79e2bf1f59b3e9aedc918a72d9dc45b9adaac10ff9470d5dbdda0002e47", size = 37895673, upload-time = "2026-08-12T22:28:03.98Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d8/04507c57249b399c3e4f23f01d221532f357338b5316fd2858fbd343127d/av-18.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7ea063f6690193ea335a1d592d6e0274350d45e2ed6af83ee107cb90cbfd84f", size = 39992431, upload-time = "2026-08-12T22:28:08.736Z" }, + { url = "https://files.pythonhosted.org/packages/d6/d6/bc4b95bea9c2353a7e4d62a3fcfad9adcf0f881741c6ce01ee179d539ce3/av-18.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e4d48b9f12cad009cc72fe4f4099107de5e819c95f82767f4fd01a01481c0661", size = 28497798, upload-time = "2026-08-12T22:28:13.003Z" }, + { url = "https://files.pythonhosted.org/packages/c1/d2/0c277a46f12647c1833f40496e132fb6001e0d19e6144b5ea30896461feb/av-18.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:5cd9085028902c9880622bd37a12fd4b33060f06a52311f6f4867ca9f29a2c3b", size = 21421979, upload-time = "2026-08-12T22:28:16.48Z" }, ] [[package]] @@ -288,7 +288,7 @@ wheels = [ [[package]] name = "c2pa-python" -version = "0.37.5" +version = "0.37.7" 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/f7/ec/416d05dc0cffc362f24cb33121aeddb621cfe5f06b239b0051aa25b28079/c2pa_python-0.37.5.tar.gz", hash = "sha256:185cd00c18d8386a0c6c8c5af19434005406626545e6250e1273f4cc14389739", size = 112944, upload-time = "2026-08-05T23:09:56.409Z" } +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" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/f2/344074f26373f3c596a98549b0a7755b1cf6036ed4a831ad74526ac33d80/c2pa_python-0.37.5-py3-none-macosx_10_9_universal2.whl", hash = "sha256:8f719bd53a08bb54a4e8b761825db426d153430199821a4fcac1c13efe26d309", size = 16150883, upload-time = "2026-08-05T23:09:21.934Z" }, - { url = "https://files.pythonhosted.org/packages/f3/38/bd924e7f829b156fcf967923d26918fd4cc3435860055ae8b1b46d97d982/c2pa_python-0.37.5-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:72a9b074f3fecf67fb1740003869fe17c2d664ab79e6fc0669d3a77dc01e6699", size = 14116139, upload-time = "2026-08-05T23:09:25.589Z" }, - { url = "https://files.pythonhosted.org/packages/74/50/1128ae57644c079c75dcfeeaf5b51e199246b17f745d42e9a929d224b199/c2pa_python-0.37.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:95e446663cd53d9292fd04afb84996c3be9a4aeea1ac076440d9a6c6477da921", size = 13785203, upload-time = "2026-08-05T23:09:28.895Z" }, - { url = "https://files.pythonhosted.org/packages/a8/f0/f5291ba6f3bc778575af06d9a6630d841432b7cf8136ae57e8889b212d8d/c2pa_python-0.37.5-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:8fc4ce81ae5449a65981ce10e33c91c33a85a10b3aed1124d580ad5ce15b1ea7", size = 14006289, upload-time = "2026-08-05T23:09:32.566Z" }, - { url = "https://files.pythonhosted.org/packages/57/33/e258809ad6ae1ee7c9af29117bd8addf87db9e0a0b53354ac668b2f2e4a4/c2pa_python-0.37.5-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:82bf2dcc5f184a40d7b65a5804b3e6251d52e6e65958cfbc0dc80321eac4bb33", size = 14652564, upload-time = "2026-08-05T23:09:36.231Z" }, - { url = "https://files.pythonhosted.org/packages/95/2f/5c2ebd8baa36c4422f4827323a6a67c07ed57898f3500f689ab0d7b31435/c2pa_python-0.37.5-py3-none-win_amd64.whl", hash = "sha256:c122680aa05ea12bbe061c69333c0137f4f2f8c7906ae06fec255db346d788b8", size = 86368147, upload-time = "2026-08-05T23:09:45.232Z" }, - { url = "https://files.pythonhosted.org/packages/39/6f/f54775038b6a469951fe9899249ff3f8545421f64cc5e6a718af092f383d/c2pa_python-0.37.5-py3-none-win_arm64.whl", hash = "sha256:1ed2f1bd1d69e1ac243a9d769e5fa1047d5f9be3fed3206bb3c0a68c3c9e0bce", size = 83916232, upload-time = "2026-08-05T23:09:53.31Z" }, + { 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" }, ] [[package]] @@ -753,7 +753,7 @@ name = "cuda-bindings" version = "13.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cuda-pathfinder", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "cuda-pathfinder" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/51/6b/457ca12dad3ee9bfcc9a545cfd6b64b359ba49de40f776f6e028e678f262/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5879712accf6e14bb01aa5e67440eb84998b8d104b509cc7a6dc0b8f656a474", size = 6053539, upload-time = "2026-05-29T23:11:43.19Z" }, @@ -786,43 +786,43 @@ wheels = [ [package.optional-dependencies] cublas = [ - { name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] cudart = [ - { name = "nvidia-cuda-runtime", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cuda-runtime", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] cufft = [ - { name = "nvidia-cufft", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cufft", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] cufile = [ - { name = "nvidia-cufile", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cufile", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] cupti = [ - { name = "nvidia-cuda-cupti", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cuda-cupti", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] curand = [ - { name = "nvidia-curand", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-curand", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] cusolver = [ - { name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nvidia-cusolver", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cusolver", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] cusparse = [ - { name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] nvjitlink = [ - { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] nvrtc = [ - { name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] nvtx = [ - { name = "nvidia-nvtx", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-nvtx", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] [[package]] @@ -837,7 +837,7 @@ dependencies = [ { name = "huggingface-hub" }, { name = "multiprocess" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "packaging" }, { name = "pandas" }, { name = "pyarrow" }, @@ -853,7 +853,7 @@ wheels = [ [[package]] name = "diffsynth" -version = "2.1.0" +version = "2.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "accelerate" }, @@ -871,9 +871,9 @@ dependencies = [ { name = "torchvision" }, { name = "transformers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b4/86/3665041e0ef267fae10b6191afdf0cb51f08177cffa4c27e02088bcb9e7b/diffsynth-2.1.0.tar.gz", hash = "sha256:af568befac3276beca2c102b398c34ba77e08595c0e9ec00c79cc1aa33f59d76", size = 640801, upload-time = "2026-08-04T08:54:34.624Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/48/03c981029bceca79fa1ee1460d6294601abf4302962407d18239ea41c985/diffsynth-2.1.2.tar.gz", hash = "sha256:ccd23410c8042ea3e9144dab0479eceb5496c480a646d097aa399773209f1ee7", size = 657409, upload-time = "2026-08-14T03:34:26.088Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/35/1a/5d8907b2e1090756f3454fbd0af9a6bbfdde0c28c4ad59d7d559d1c5bfd5/diffsynth-2.1.0-py3-none-any.whl", hash = "sha256:40960d5c9089464a35f3c437564075127631957f9ea42143c5ef5400fc55dfdc", size = 728228, upload-time = "2026-08-04T08:54:32.846Z" }, + { url = "https://files.pythonhosted.org/packages/64/f5/4705f2ea899d23172c5ffb9a30ac3d97fb767630f932a32c288b9137d5a1/diffsynth-2.1.2-py3-none-any.whl", hash = "sha256:a331ad057931e08c2e0a146f5f246f2b4f75e6c04a27b57f5c034d9235b6c66d", size = 744199, upload-time = "2026-08-14T03:34:24.303Z" }, ] [[package]] @@ -886,7 +886,7 @@ dependencies = [ { name = "huggingface-hub" }, { name = "importlib-metadata" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "pillow" }, { name = "regex" }, { name = "requests" }, @@ -1177,7 +1177,7 @@ version = "2.37.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "pillow" }, ] sdist = { url = "https://files.pythonhosted.org/packages/48/62/aa770a9307508d2a2a2c62d536a49347bffe9e55322db27838d3c93d0b07/imageio-2.37.4.tar.gz", hash = "sha256:e45cbc5e83502047fb138f7f585f7f105a136a57eea5f4b3cfc6ce1b52720bd3", size = 390173, upload-time = "2026-07-20T05:26:11.369Z" } @@ -1241,7 +1241,7 @@ version = "0.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "opencv-python" }, { name = "pillow" }, { name = "pywavelets" }, @@ -1268,15 +1268,15 @@ name = "lightning" version = "2.6.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "fsspec", extra = ["http"], marker = "python_full_version < '3.13'" }, - { name = "lightning-utilities", marker = "python_full_version < '3.13'" }, - { name = "packaging", marker = "python_full_version < '3.13'" }, - { name = "pytorch-lightning", marker = "python_full_version < '3.13'" }, - { name = "pyyaml", marker = "python_full_version < '3.13'" }, - { name = "torch", marker = "python_full_version < '3.13'" }, - { name = "torchmetrics", marker = "python_full_version < '3.13'" }, - { name = "tqdm", marker = "python_full_version < '3.13'" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "fsspec", extra = ["http"] }, + { name = "lightning-utilities" }, + { name = "packaging" }, + { name = "pytorch-lightning" }, + { name = "pyyaml" }, + { name = "torch" }, + { name = "torchmetrics" }, + { name = "tqdm" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c9/1d/83be8536bec71a0173e762a9a1fd92a24a5ad0d0f74c59550c3c4e6c103b/lightning-2.6.5.tar.gz", hash = "sha256:16a30310ed69afde3748491feb5d13508908effd70390d2bfc203dc0812a4b4a", size = 659201, upload-time = "2026-05-27T14:33:41.806Z" } wheels = [ @@ -1288,8 +1288,8 @@ name = "lightning-utilities" version = "0.15.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "packaging", marker = "python_full_version < '3.13'" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "packaging" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f1/45/7fa8f56b17dc0f0a41ec70dd307ecd6787254483549843bef4c30ab5adce/lightning_utilities-0.15.3.tar.gz", hash = "sha256:792ae0204c79f6859721ac7f386c237a33b0ed06ba775009cb894e010a842033", size = 33553, upload-time = "2026-02-22T14:48:53.348Z" } wheels = [ @@ -1626,7 +1626,7 @@ wheels = [ [[package]] name = "numpy" -version = "2.5.2" +version = "2.3.5" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'darwin'", @@ -1640,51 +1640,81 @@ resolution-markers = [ "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "(python_full_version == '3.13.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", ] -sdist = { url = "https://files.pythonhosted.org/packages/9a/80/db0b4559e57ec36362bedbb05530a87fafbcb6067708c946967a41d449e7/numpy-2.5.2.tar.gz", hash = "sha256:d482d171c406ae88c5b19cad3b6a1c4c5209f886ab74bc44c2c865c23f52d860", size = 20773161, upload-time = "2026-08-09T13:48:27.962Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950, upload-time = "2025-11-16T22:52:42.067Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/72/dccb0aaf40972777283303919f613964227266d0c13adebb79ac124f1c3e/numpy-2.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:14e373cfc6387177e8409dac3c7159be8eb05cd77096cd7c950268b86f62831c", size = 16891693, upload-time = "2026-08-09T13:44:51.702Z" }, - { url = "https://files.pythonhosted.org/packages/60/2e/b5aee50a1f74ac815cf8331812cb8251e29024025de462e0c047641c614c/numpy-2.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bbd96c833ecc8cc069ce518078fc8c60cb9cbfb0fea5b7a803ad65035596d03", size = 11903109, upload-time = "2026-08-09T13:44:55.501Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f4/29e78102a80601cf034d4e9767022cffeca2c3b4c926e1754572ca95593d/numpy-2.5.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:6e8172ddfcf5cf74b811d372b570b83c60bd2de87a6fbfbebdadb4a9bd9c6cbb", size = 5350202, upload-time = "2026-08-09T13:44:58.401Z" }, - { url = "https://files.pythonhosted.org/packages/11/4b/dcd3b7eadaf4035d2c7a4289d232523a6964f602598ef7674e4bd7291f93/numpy-2.5.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f188481f1669e26f62b701e8205d19e460fa4a9b52a1414ba382330e4a3414", size = 6687736, upload-time = "2026-08-09T13:45:00.813Z" }, - { url = "https://files.pythonhosted.org/packages/e5/21/4947e0e9d6c9fc2e2ff15b8949049ee44f63adb9cacc729ab8793f97e712/numpy-2.5.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ee9c4eeb8454b3660a8b53493563c3e121c2fc94fbd72b848ef814ed7b676a9", size = 15612696, upload-time = "2026-08-09T13:45:04.151Z" }, - { url = "https://files.pythonhosted.org/packages/3a/5f/62d28cf019460c7f1394105b4d49d9911a9c444cb77ab0bd95a204c5a6de/numpy-2.5.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3cdec01fa790a186d430433fdd4d4ffb70eed6f0eeb4bf05c8dbe2dce0a9bcb8", size = 16722264, upload-time = "2026-08-09T13:45:07.714Z" }, - { url = "https://files.pythonhosted.org/packages/14/25/3f0be4c1b9fdf5dd5e708a6806978564d7c46a055c000496309ff2a2f8af/numpy-2.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7999d4ddb0c4025018373fd787510d46e04c769467af22869707b3c1cfd459ab", size = 16974396, upload-time = "2026-08-09T13:45:11.316Z" }, - { url = "https://files.pythonhosted.org/packages/22/72/6262cbdeeb45da9d971e40715f579d791603ba8ec0b5e2db1ac55454421d/numpy-2.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c1f017dc0875c9209d219f97feceb7d54c2661bb243deb4114478e1295808af7", size = 18476044, upload-time = "2026-08-09T13:45:14.869Z" }, - { url = "https://files.pythonhosted.org/packages/36/33/29208b8b075bde62d26a81d14b358c42b0f69b6cabd98d4ff97f37f22b05/numpy-2.5.2-cp312-cp312-win32.whl", hash = "sha256:d6a48072864e3324e194a8fbb3c657bcc5b5c869dbc64c9537b1d5c862572c0a", size = 6072817, upload-time = "2026-08-09T13:45:17.867Z" }, - { url = "https://files.pythonhosted.org/packages/7f/b9/87fea2769fe1c47c1b5b01d8310772c9d1a85d485de7cf386ef7a3332b02/numpy-2.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:28ac63476ec7651484215ee7fa15a1f78b57c14621f01e392afe17b9a1390ce4", size = 12464674, upload-time = "2026-08-09T13:45:20.734Z" }, - { url = "https://files.pythonhosted.org/packages/14/52/032b97e00461ab0809bbe4c588b035620e5a14b8cdee47ecddefc7b17d33/numpy-2.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:27650bb0e7140fa3d37b9923b4803645e0b125d190f326eecfd3f4dad8e8ade1", size = 10397131, upload-time = "2026-08-09T13:45:23.73Z" }, - { url = "https://files.pythonhosted.org/packages/f5/d2/6b24738a0ef4557d189b150046cd07823c50e4273e8aebd651222e24306f/numpy-2.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8e4cb9a754c8a0c62eaa88273a5fba3391f4a610d1dee893c0755da31c083f15", size = 16886595, upload-time = "2026-08-09T13:45:27.323Z" }, - { url = "https://files.pythonhosted.org/packages/65/60/f2d208d366f263f39c6e69ed309290717aab41078b6d04c9be2a84fa2a07/numpy-2.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:52c808f96484f5571a5cc863775ce50247c17dfb3b0361f8ed6b4b0456f80080", size = 11896845, upload-time = "2026-08-09T13:45:31.638Z" }, - { url = "https://files.pythonhosted.org/packages/3c/79/81e0bf24f4d020a2b1d5cd297a9f60c3f24eeb116f9bba5870443f7b6a4a/numpy-2.5.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:29d81e97f668489cba8ebfd796b9bdd453525d35dd9e162e2daec94bf3fc7740", size = 5343880, upload-time = "2026-08-09T13:45:34.373Z" }, - { url = "https://files.pythonhosted.org/packages/ba/cc/e3141cf06d1a8a2c7e107543fe1269c1d1af760d4d683c0794a4ee1127c2/numpy-2.5.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afb3f0632d6b2e3ba04dbce8d1e48d321b369138b73830b5ca371a0e8d479d56", size = 6682264, upload-time = "2026-08-09T13:45:36.7Z" }, - { url = "https://files.pythonhosted.org/packages/29/f1/2a64a307d92c5d98f5255a4014eb43bb6103ee477087b61ecae44a3aa9b9/numpy-2.5.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0aadf13b60048d501e05fa699efaf7734e2494f3498a4c2a5521d822640324f3", size = 15609566, upload-time = "2026-08-09T13:45:39.518Z" }, - { url = "https://files.pythonhosted.org/packages/7b/44/59a1eb68e773c4098d107ef34a0dbdeca501d72ffcfbff9a7707343921ce/numpy-2.5.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29b86ff8a6cc556b47ec6b64b194815cc80e6bf5eedcc6cddfd65318cb0b4eee", size = 16709995, upload-time = "2026-08-09T13:45:43.661Z" }, - { url = "https://files.pythonhosted.org/packages/8a/4c/3e54d4ddbc359a1295f8b633e8106bcd4d7d4a206e82df051bdfb3058755/numpy-2.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6950c4b7dd562453090548ba7f5da7e59f57f85663f15d5dcc60e249192f7e59", size = 16972511, upload-time = "2026-08-09T13:45:47.094Z" }, - { url = "https://files.pythonhosted.org/packages/f2/9f/02e371638ebf19b66d46231e4be52999e87f32d1961b113bc45656608b22/numpy-2.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9727f472d2f3888053b8a75ab0cb94745a9de224bb5846dbadc0092101bc71d", size = 18465609, upload-time = "2026-08-09T13:45:50.808Z" }, - { url = "https://files.pythonhosted.org/packages/eb/ae/ad6645abc7a3510fe48e8ea1ab4598166f500057ef4ebf38bfad4f1577de/numpy-2.5.2-cp313-cp313-win32.whl", hash = "sha256:4f9744f9fbdcea0bc552e8f19e1f141f811a3f9bc2be2cc6e86d982cab23e3f4", size = 6070204, upload-time = "2026-08-09T13:45:54.111Z" }, - { url = "https://files.pythonhosted.org/packages/15/20/f3489f86d81ea460b2bcdceaed094142ca6579f6be0ec527b781d39afe68/numpy-2.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:85aaccb24182c25df891ad0ec333585967e115269d5f1b17f2c9ae005bc96657", size = 12460532, upload-time = "2026-08-09T13:45:57.167Z" }, - { url = "https://files.pythonhosted.org/packages/d5/21/35b31dde1b283b79de828b80f876afd8c94e28fe1e9c375f89e261cc4c0d/numpy-2.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:bd68ece1553d2023c09a4226d9e41c586ad2d20594d1a456186c33513d2cb3f2", size = 10396725, upload-time = "2026-08-09T13:46:00.478Z" }, - { url = "https://files.pythonhosted.org/packages/ac/f8/c3b222bf075b50afd8e949a07a15c4b312a4a84bd8102a332bcd953cbbb4/numpy-2.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d787cf769c3baeb5f6235e778edb52c08dfa923789b5958f28e6450f96107cb1", size = 16885180, upload-time = "2026-08-09T13:46:03.939Z" }, - { url = "https://files.pythonhosted.org/packages/17/e1/2c1d4b1987795a92b5bbf7c24fe249ab96aa2573ab0d7604802c189d7b86/numpy-2.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:24b9dc2e3d84aa58523798805194e23e736f3f6ce2d1a5b92583ae734e6dbda8", size = 11907878, upload-time = "2026-08-09T13:46:07.045Z" }, - { url = "https://files.pythonhosted.org/packages/b9/ee/d08226fc858044355983a6e5b94f08ff6f3969e0a2b160a4a89f0ddb3445/numpy-2.5.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:9e9413326d726c2545bfa65d2c0876871e8d8386e77f992c1d426e180bbd4323", size = 5354922, upload-time = "2026-08-09T13:46:10.04Z" }, - { url = "https://files.pythonhosted.org/packages/94/f0/6d3d933056440ebbc5e6bad92065fc6c26a48a84a36b1208580e94eea76c/numpy-2.5.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:60e902ac295855348a5ca2ea4c89108989a9f5fddfad3dfc0a8f36b10358567e", size = 6679168, upload-time = "2026-08-09T13:46:12.275Z" }, - { url = "https://files.pythonhosted.org/packages/c4/3b/ecd49dd90033cceb2704d88ca905d4d7d89b0e8c739608754ffd325fa820/numpy-2.5.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50e500dc868e9313530ce12ba470fe50ff3afe3d62993ed6eff652dacd555b65", size = 15624501, upload-time = "2026-08-09T13:46:15.322Z" }, - { url = "https://files.pythonhosted.org/packages/c7/99/461bd36dbdfac6c1c53efa370bd55a83227542d0d118f1677dbf1a3dacd5/numpy-2.5.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318b9a4c845dbea06708a29c84ee429cc3065048db34cdb799047643492050ee", size = 16713701, upload-time = "2026-08-09T13:46:18.949Z" }, - { url = "https://files.pythonhosted.org/packages/f9/9c/2b251df9e8a5d647b62b0cbc1b90a91850c1cf4859ecb532fd0b4eacff6c/numpy-2.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:34c319e2963be042673fb46570501b2f06c41924e17e3563d58646b4380dfb68", size = 16986065, upload-time = "2026-08-09T13:46:23.006Z" }, - { url = "https://files.pythonhosted.org/packages/8f/25/20de43f53ff1390534a124475055a19f01fe10c920a0fd11b8e18d6d6052/numpy-2.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f06571a052127dc1b4e8b83029b4d1b20daa2b64a31cdd181fc6bc774e9000eb", size = 18470031, upload-time = "2026-08-09T13:46:27.102Z" }, - { url = "https://files.pythonhosted.org/packages/56/5e/0c577ca308d6da5eb79b546ba10bbe5b60148192194e2da060913b1de4f1/numpy-2.5.2-cp314-cp314-win32.whl", hash = "sha256:2cc779226e476d1e1f08c74068c419e60f41a9e0e069c92f6671d31d5c985e98", size = 6121028, upload-time = "2026-08-09T13:46:30.046Z" }, - { url = "https://files.pythonhosted.org/packages/15/5c/7bcbd5b11f94199073320410cddcbb80cee62415bfeb540874b265c2d922/numpy-2.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:7587f53dfbd5edc0f7b87c6217b4c6d2d1f2ef9c3da70bc1315e7db5f8d7ec9d", size = 12597627, upload-time = "2026-08-09T13:46:32.886Z" }, - { url = "https://files.pythonhosted.org/packages/87/bc/4d0b06fba0da90ccc75af62823cb9dcedb6c9ea0cffa058cb2c9ee773a77/numpy-2.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:3e4c367352d3747784248a227fbec218e193b56f7e6692e3b64fc805478ecfdf", size = 10680414, upload-time = "2026-08-09T13:46:36.036Z" }, - { url = "https://files.pythonhosted.org/packages/cd/17/f429aac9dc08833a0d0f188eba38c532a751b1a1f2ca6018a37b455cb321/numpy-2.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b879fb674276e331513fb136b78dbc6bd3c848309e0d841cfd63be3896c4cfc1", size = 12026967, upload-time = "2026-08-09T13:46:39.084Z" }, - { url = "https://files.pythonhosted.org/packages/ca/9f/d0849de96a2a4ceaa16662f18ee13eaa9c0aa418269fdc8c4857c56b11da/numpy-2.5.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:fd0d703772bba096843785bd38371e31bb4a0c1151497ad5739d182114a73f7f", size = 5473874, upload-time = "2026-08-09T13:46:42.075Z" }, - { url = "https://files.pythonhosted.org/packages/89/3c/8df216d4a4a5422a3de045301cf7df8ea47286d76f5cb7160b0128ac26b7/numpy-2.5.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:3a2f061cebd9e3d23bdcfaaded5e2293a4c6a5b60fa42df85d410a725ce621bf", size = 6789276, upload-time = "2026-08-09T13:46:44.387Z" }, - { url = "https://files.pythonhosted.org/packages/e6/3a/20d7e9891c4ddfadd6ff8d95bf4b29f353d8e1770553de2099880551dfb9/numpy-2.5.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6df895598c0edcb41030126c89e0f353b07d93238116143b7405e937359736c4", size = 15659154, upload-time = "2026-08-09T13:46:47.538Z" }, - { url = "https://files.pythonhosted.org/packages/aa/d6/f3aa3d2688bf501b858835c6bd087ae9b51a56ae6fca8e2b0990abd177af/numpy-2.5.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ab3d4a901f844ea836c3e80bf463c6a27d7f3c14e8e292fcf28d348b25b9bce", size = 16748909, upload-time = "2026-08-09T13:46:51.442Z" }, - { url = "https://files.pythonhosted.org/packages/7d/8f/1c5cae8d2baf86ab802ae97a00be55bc7e21ebc11b12bbc33376c5f05342/numpy-2.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:cebc2d6dbb605a7703d59751dea4bd6b0ab127a5a4338a6f432df1936fef8b26", size = 17027685, upload-time = "2026-08-09T13:46:55.095Z" }, - { url = "https://files.pythonhosted.org/packages/5c/27/71d3467404aedc1c24ce79610f91b52b0b0f466c43a701aa56fc75c145ab/numpy-2.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eaca7ff36f0f52e2111ec71f169d8fd3e889e7ddc0d2592e0d703fd8d3ce8fac", size = 18501181, upload-time = "2026-08-09T13:46:59.09Z" }, - { url = "https://files.pythonhosted.org/packages/14/2f/42921d27c40aea7e077f4a423ae509fd9220b028cd787bafefd8ab2b3a5f/numpy-2.5.2-cp314-cp314t-win32.whl", hash = "sha256:ddf47472af2e4280d79bac82304f5e80150211f1b9e614b760061d5fdfbb6eba", size = 6271085, upload-time = "2026-08-09T13:47:01.903Z" }, - { url = "https://files.pythonhosted.org/packages/75/e6/bad5f5d56de9b1971bac959963dda276d35c40f1854475005434bbe08692/numpy-2.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:44ef9675d908e65f9953063837c3277730f3f4437615a4cdab67b366cabaf884", size = 12787971, upload-time = "2026-08-09T13:47:04.963Z" }, - { url = "https://files.pythonhosted.org/packages/df/05/f608795cb34391acd67e38d94a3c36abd8d8576293a3a80727d7595c372c/numpy-2.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:eaa088384c46f519dacb93b7ec483a6d6b19a4a2085ae4f25ab9b1c43d387d1e", size = 10750306, upload-time = "2026-08-09T13:47:07.976Z" }, + { url = "https://files.pythonhosted.org/packages/43/77/84dd1d2e34d7e2792a236ba180b5e8fcc1e3e414e761ce0253f63d7f572e/numpy-2.3.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de5672f4a7b200c15a4127042170a694d4df43c992948f5e1af57f0174beed10", size = 17034641, upload-time = "2025-11-16T22:49:19.336Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ea/25e26fa5837106cde46ae7d0b667e20f69cbbc0efd64cba8221411ab26ae/numpy-2.3.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:acfd89508504a19ed06ef963ad544ec6664518c863436306153e13e94605c218", size = 12528324, upload-time = "2025-11-16T22:49:22.582Z" }, + { url = "https://files.pythonhosted.org/packages/4d/1a/e85f0eea4cf03d6a0228f5c0256b53f2df4bc794706e7df019fc622e47f1/numpy-2.3.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ffe22d2b05504f786c867c8395de703937f934272eb67586817b46188b4ded6d", size = 5356872, upload-time = "2025-11-16T22:49:25.408Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bb/35ef04afd567f4c989c2060cde39211e4ac5357155c1833bcd1166055c61/numpy-2.3.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:872a5cf366aec6bb1147336480fef14c9164b154aeb6542327de4970282cd2f5", size = 6893148, upload-time = "2025-11-16T22:49:27.549Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2b/05bbeb06e2dff5eab512dfc678b1cc5ee94d8ac5956a0885c64b6b26252b/numpy-2.3.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3095bdb8dd297e5920b010e96134ed91d852d81d490e787beca7e35ae1d89cf7", size = 14557282, upload-time = "2025-11-16T22:49:30.964Z" }, + { url = "https://files.pythonhosted.org/packages/65/fb/2b23769462b34398d9326081fad5655198fcf18966fcb1f1e49db44fbf31/numpy-2.3.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cba086a43d54ca804ce711b2a940b16e452807acebe7852ff327f1ecd49b0d4", size = 16897903, upload-time = "2025-11-16T22:49:34.191Z" }, + { url = "https://files.pythonhosted.org/packages/ac/14/085f4cf05fc3f1e8aa95e85404e984ffca9b2275a5dc2b1aae18a67538b8/numpy-2.3.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6cf9b429b21df6b99f4dee7a1218b8b7ffbbe7df8764dc0bd60ce8a0708fed1e", size = 16341672, upload-time = "2025-11-16T22:49:37.2Z" }, + { url = "https://files.pythonhosted.org/packages/6f/3b/1f73994904142b2aa290449b3bb99772477b5fd94d787093e4f24f5af763/numpy-2.3.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:396084a36abdb603546b119d96528c2f6263921c50df3c8fd7cb28873a237748", size = 18838896, upload-time = "2025-11-16T22:49:39.727Z" }, + { url = "https://files.pythonhosted.org/packages/cd/b9/cf6649b2124f288309ffc353070792caf42ad69047dcc60da85ee85fea58/numpy-2.3.5-cp311-cp311-win32.whl", hash = "sha256:b0c7088a73aef3d687c4deef8452a3ac7c1be4e29ed8bf3b366c8111128ac60c", size = 6563608, upload-time = "2025-11-16T22:49:42.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/44/9fe81ae1dcc29c531843852e2874080dc441338574ccc4306b39e2ff6e59/numpy-2.3.5-cp311-cp311-win_amd64.whl", hash = "sha256:a414504bef8945eae5f2d7cb7be2d4af77c5d1cb5e20b296c2c25b61dff2900c", size = 13078442, upload-time = "2025-11-16T22:49:43.99Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a7/f99a41553d2da82a20a2f22e93c94f928e4490bb447c9ff3c4ff230581d3/numpy-2.3.5-cp311-cp311-win_arm64.whl", hash = "sha256:0cd00b7b36e35398fa2d16af7b907b65304ef8bb4817a550e06e5012929830fa", size = 10458555, upload-time = "2025-11-16T22:49:47.092Z" }, + { url = "https://files.pythonhosted.org/packages/44/37/e669fe6cbb2b96c62f6bbedc6a81c0f3b7362f6a59230b23caa673a85721/numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e", size = 16733873, upload-time = "2025-11-16T22:49:49.84Z" }, + { url = "https://files.pythonhosted.org/packages/c5/65/df0db6c097892c9380851ab9e44b52d4f7ba576b833996e0080181c0c439/numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769", size = 12259838, upload-time = "2025-11-16T22:49:52.863Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e1/1ee06e70eb2136797abe847d386e7c0e830b67ad1d43f364dd04fa50d338/numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5", size = 5088378, upload-time = "2025-11-16T22:49:55.055Z" }, + { url = "https://files.pythonhosted.org/packages/6d/9c/1ca85fb86708724275103b81ec4cf1ac1d08f465368acfc8da7ab545bdae/numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4", size = 6628559, upload-time = "2025-11-16T22:49:57.371Z" }, + { url = "https://files.pythonhosted.org/packages/74/78/fcd41e5a0ce4f3f7b003da85825acddae6d7ecb60cf25194741b036ca7d6/numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d", size = 14250702, upload-time = "2025-11-16T22:49:59.632Z" }, + { url = "https://files.pythonhosted.org/packages/b6/23/2a1b231b8ff672b4c450dac27164a8b2ca7d9b7144f9c02d2396518352eb/numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28", size = 16606086, upload-time = "2025-11-16T22:50:02.127Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c5/5ad26fbfbe2012e190cc7d5003e4d874b88bb18861d0829edc140a713021/numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b", size = 16025985, upload-time = "2025-11-16T22:50:04.536Z" }, + { url = "https://files.pythonhosted.org/packages/d2/fa/dd48e225c46c819288148d9d060b047fd2a6fb1eb37eae25112ee4cb4453/numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c", size = 18542976, upload-time = "2025-11-16T22:50:07.557Z" }, + { url = "https://files.pythonhosted.org/packages/05/79/ccbd23a75862d95af03d28b5c6901a1b7da4803181513d52f3b86ed9446e/numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952", size = 6285274, upload-time = "2025-11-16T22:50:10.746Z" }, + { url = "https://files.pythonhosted.org/packages/2d/57/8aeaf160312f7f489dea47ab61e430b5cb051f59a98ae68b7133ce8fa06a/numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa", size = 12782922, upload-time = "2025-11-16T22:50:12.811Z" }, + { url = "https://files.pythonhosted.org/packages/78/a6/aae5cc2ca78c45e64b9ef22f089141d661516856cf7c8a54ba434576900d/numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013", size = 10194667, upload-time = "2025-11-16T22:50:16.16Z" }, + { url = "https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff", size = 16728251, upload-time = "2025-11-16T22:50:19.013Z" }, + { url = "https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188", size = 12254652, upload-time = "2025-11-16T22:50:21.487Z" }, + { url = "https://files.pythonhosted.org/packages/78/da/8c7738060ca9c31b30e9301ee0cf6c5ffdbf889d9593285a1cead337f9a5/numpy-2.3.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0", size = 5083172, upload-time = "2025-11-16T22:50:24.562Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b4/ee5bb2537fb9430fd2ef30a616c3672b991a4129bb1c7dcc42aa0abbe5d7/numpy-2.3.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903", size = 6622990, upload-time = "2025-11-16T22:50:26.47Z" }, + { url = "https://files.pythonhosted.org/packages/95/03/dc0723a013c7d7c19de5ef29e932c3081df1c14ba582b8b86b5de9db7f0f/numpy-2.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d", size = 14248902, upload-time = "2025-11-16T22:50:28.861Z" }, + { url = "https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017", size = 16597430, upload-time = "2025-11-16T22:50:31.56Z" }, + { url = "https://files.pythonhosted.org/packages/2a/51/c1e29be863588db58175175f057286900b4b3327a1351e706d5e0f8dd679/numpy-2.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf", size = 16024551, upload-time = "2025-11-16T22:50:34.242Z" }, + { url = "https://files.pythonhosted.org/packages/83/68/8236589d4dbb87253d28259d04d9b814ec0ecce7cb1c7fed29729f4c3a78/numpy-2.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce", size = 18533275, upload-time = "2025-11-16T22:50:37.651Z" }, + { url = "https://files.pythonhosted.org/packages/40/56/2932d75b6f13465239e3b7b7e511be27f1b8161ca2510854f0b6e521c395/numpy-2.3.5-cp313-cp313-win32.whl", hash = "sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e", size = 6277637, upload-time = "2025-11-16T22:50:40.11Z" }, + { url = "https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b", size = 12779090, upload-time = "2025-11-16T22:50:42.503Z" }, + { url = "https://files.pythonhosted.org/packages/8f/88/3f41e13a44ebd4034ee17baa384acac29ba6a4fcc2aca95f6f08ca0447d1/numpy-2.3.5-cp313-cp313-win_arm64.whl", hash = "sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae", size = 10194710, upload-time = "2025-11-16T22:50:44.971Z" }, + { url = "https://files.pythonhosted.org/packages/13/cb/71744144e13389d577f867f745b7df2d8489463654a918eea2eeb166dfc9/numpy-2.3.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd", size = 16827292, upload-time = "2025-11-16T22:50:47.715Z" }, + { url = "https://files.pythonhosted.org/packages/71/80/ba9dc6f2a4398e7f42b708a7fdc841bb638d353be255655498edbf9a15a8/numpy-2.3.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f", size = 12378897, upload-time = "2025-11-16T22:50:51.327Z" }, + { url = "https://files.pythonhosted.org/packages/2e/6d/db2151b9f64264bcceccd51741aa39b50150de9b602d98ecfe7e0c4bff39/numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a", size = 5207391, upload-time = "2025-11-16T22:50:54.542Z" }, + { url = "https://files.pythonhosted.org/packages/80/ae/429bacace5ccad48a14c4ae5332f6aa8ab9f69524193511d60ccdfdc65fa/numpy-2.3.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139", size = 6721275, upload-time = "2025-11-16T22:50:56.794Z" }, + { url = "https://files.pythonhosted.org/packages/74/5b/1919abf32d8722646a38cd527bc3771eb229a32724ee6ba340ead9b92249/numpy-2.3.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e", size = 14306855, upload-time = "2025-11-16T22:50:59.208Z" }, + { url = "https://files.pythonhosted.org/packages/a5/87/6831980559434973bebc30cd9c1f21e541a0f2b0c280d43d3afd909b66d0/numpy-2.3.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9", size = 16657359, upload-time = "2025-11-16T22:51:01.991Z" }, + { url = "https://files.pythonhosted.org/packages/dd/91/c797f544491ee99fd00495f12ebb7802c440c1915811d72ac5b4479a3356/numpy-2.3.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946", size = 16093374, upload-time = "2025-11-16T22:51:05.291Z" }, + { url = "https://files.pythonhosted.org/packages/74/a6/54da03253afcbe7a72785ec4da9c69fb7a17710141ff9ac5fcb2e32dbe64/numpy-2.3.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1", size = 18594587, upload-time = "2025-11-16T22:51:08.585Z" }, + { url = "https://files.pythonhosted.org/packages/80/e9/aff53abbdd41b0ecca94285f325aff42357c6b5abc482a3fcb4994290b18/numpy-2.3.5-cp313-cp313t-win32.whl", hash = "sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3", size = 6405940, upload-time = "2025-11-16T22:51:11.541Z" }, + { url = "https://files.pythonhosted.org/packages/d5/81/50613fec9d4de5480de18d4f8ef59ad7e344d497edbef3cfd80f24f98461/numpy-2.3.5-cp313-cp313t-win_amd64.whl", hash = "sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234", size = 12920341, upload-time = "2025-11-16T22:51:14.312Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ab/08fd63b9a74303947f34f0bd7c5903b9c5532c2d287bead5bdf4c556c486/numpy-2.3.5-cp313-cp313t-win_arm64.whl", hash = "sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7", size = 10262507, upload-time = "2025-11-16T22:51:16.846Z" }, + { url = "https://files.pythonhosted.org/packages/ba/97/1a914559c19e32d6b2e233cf9a6a114e67c856d35b1d6babca571a3e880f/numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82", size = 16735706, upload-time = "2025-11-16T22:51:19.558Z" }, + { url = "https://files.pythonhosted.org/packages/57/d4/51233b1c1b13ecd796311216ae417796b88b0616cfd8a33ae4536330748a/numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0", size = 12264507, upload-time = "2025-11-16T22:51:22.492Z" }, + { url = "https://files.pythonhosted.org/packages/45/98/2fe46c5c2675b8306d0b4a3ec3494273e93e1226a490f766e84298576956/numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63", size = 5093049, upload-time = "2025-11-16T22:51:25.171Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0e/0698378989bb0ac5f1660c81c78ab1fe5476c1a521ca9ee9d0710ce54099/numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9", size = 6626603, upload-time = "2025-11-16T22:51:27Z" }, + { url = "https://files.pythonhosted.org/packages/5e/a6/9ca0eecc489640615642a6cbc0ca9e10df70df38c4d43f5a928ff18d8827/numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b", size = 14262696, upload-time = "2025-11-16T22:51:29.402Z" }, + { url = "https://files.pythonhosted.org/packages/c8/f6/07ec185b90ec9d7217a00eeeed7383b73d7e709dae2a9a021b051542a708/numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520", size = 16597350, upload-time = "2025-11-16T22:51:32.167Z" }, + { url = "https://files.pythonhosted.org/packages/75/37/164071d1dde6a1a84c9b8e5b414fa127981bad47adf3a6b7e23917e52190/numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c", size = 16040190, upload-time = "2025-11-16T22:51:35.403Z" }, + { url = "https://files.pythonhosted.org/packages/08/3c/f18b82a406b04859eb026d204e4e1773eb41c5be58410f41ffa511d114ae/numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8", size = 18536749, upload-time = "2025-11-16T22:51:39.698Z" }, + { url = "https://files.pythonhosted.org/packages/40/79/f82f572bf44cf0023a2fe8588768e23e1592585020d638999f15158609e1/numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248", size = 6335432, upload-time = "2025-11-16T22:51:42.476Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2e/235b4d96619931192c91660805e5e49242389742a7a82c27665021db690c/numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e", size = 12919388, upload-time = "2025-11-16T22:51:45.275Z" }, + { url = "https://files.pythonhosted.org/packages/07/2b/29fd75ce45d22a39c61aad74f3d718e7ab67ccf839ca8b60866054eb15f8/numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2", size = 10476651, upload-time = "2025-11-16T22:51:47.749Z" }, + { url = "https://files.pythonhosted.org/packages/17/e1/f6a721234ebd4d87084cfa68d081bcba2f5cfe1974f7de4e0e8b9b2a2ba1/numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41", size = 16834503, upload-time = "2025-11-16T22:51:50.443Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1c/baf7ffdc3af9c356e1c135e57ab7cf8d247931b9554f55c467efe2c69eff/numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad", size = 12381612, upload-time = "2025-11-16T22:51:53.609Z" }, + { url = "https://files.pythonhosted.org/packages/74/91/f7f0295151407ddc9ba34e699013c32c3c91944f9b35fcf9281163dc1468/numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39", size = 5210042, upload-time = "2025-11-16T22:51:56.213Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3b/78aebf345104ec50dd50a4d06ddeb46a9ff5261c33bcc58b1c4f12f85ec2/numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20", size = 6724502, upload-time = "2025-11-16T22:51:58.584Z" }, + { url = "https://files.pythonhosted.org/packages/02/c6/7c34b528740512e57ef1b7c8337ab0b4f0bddf34c723b8996c675bc2bc91/numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52", size = 14308962, upload-time = "2025-11-16T22:52:01.698Z" }, + { url = "https://files.pythonhosted.org/packages/80/35/09d433c5262bc32d725bafc619e095b6a6651caf94027a03da624146f655/numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b", size = 16655054, upload-time = "2025-11-16T22:52:04.267Z" }, + { url = "https://files.pythonhosted.org/packages/7a/ab/6a7b259703c09a88804fa2430b43d6457b692378f6b74b356155283566ac/numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3", size = 16091613, upload-time = "2025-11-16T22:52:08.651Z" }, + { url = "https://files.pythonhosted.org/packages/c2/88/330da2071e8771e60d1038166ff9d73f29da37b01ec3eb43cb1427464e10/numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227", size = 18591147, upload-time = "2025-11-16T22:52:11.453Z" }, + { url = "https://files.pythonhosted.org/packages/51/41/851c4b4082402d9ea860c3626db5d5df47164a712cb23b54be028b184c1c/numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5", size = 6479806, upload-time = "2025-11-16T22:52:14.641Z" }, + { url = "https://files.pythonhosted.org/packages/90/30/d48bde1dfd93332fa557cff1972fbc039e055a52021fbef4c2c4b1eefd17/numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf", size = 13105760, upload-time = "2025-11-16T22:52:17.975Z" }, + { url = "https://files.pythonhosted.org/packages/2d/fd/4b5eb0b3e888d86aee4d198c23acec7d214baaf17ea93c1adec94c9518b9/numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42", size = 10545459, upload-time = "2025-11-16T22:52:20.55Z" }, + { url = "https://files.pythonhosted.org/packages/c6/65/f9dea8e109371ade9c782b4e4756a82edf9d3366bca495d84d79859a0b79/numpy-2.3.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f0963b55cdd70fad460fa4c1341f12f976bb26cb66021a5580329bd498988310", size = 16910689, upload-time = "2025-11-16T22:52:23.247Z" }, + { url = "https://files.pythonhosted.org/packages/00/4f/edb00032a8fb92ec0a679d3830368355da91a69cab6f3e9c21b64d0bb986/numpy-2.3.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f4255143f5160d0de972d28c8f9665d882b5f61309d8362fdd3e103cf7bf010c", size = 12457053, upload-time = "2025-11-16T22:52:26.367Z" }, + { url = "https://files.pythonhosted.org/packages/16/a4/e8a53b5abd500a63836a29ebe145fc1ab1f2eefe1cfe59276020373ae0aa/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:a4b9159734b326535f4dd01d947f919c6eefd2d9827466a696c44ced82dfbc18", size = 5285635, upload-time = "2025-11-16T22:52:29.266Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2f/37eeb9014d9c8b3e9c55bc599c68263ca44fdbc12a93e45a21d1d56df737/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2feae0d2c91d46e59fcd62784a3a83b3fb677fead592ce51b5a6fbb4f95965ff", size = 6801770, upload-time = "2025-11-16T22:52:31.421Z" }, + { url = "https://files.pythonhosted.org/packages/7d/e4/68d2f474df2cb671b2b6c2986a02e520671295647dad82484cde80ca427b/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffac52f28a7849ad7576293c0cb7b9f08304e8f7d738a8cb8a90ec4c55a998eb", size = 14391768, upload-time = "2025-11-16T22:52:33.593Z" }, + { url = "https://files.pythonhosted.org/packages/b8/50/94ccd8a2b141cb50651fddd4f6a48874acb3c91c8f0842b08a6afc4b0b21/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63c0e9e7eea69588479ebf4a8a270d5ac22763cc5854e9a7eae952a3908103f7", size = 16729263, upload-time = "2025-11-16T22:52:36.369Z" }, + { url = "https://files.pythonhosted.org/packages/2d/ee/346fa473e666fe14c52fcdd19ec2424157290a032d4c41f98127bfb31ac7/numpy-2.3.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f16417ec91f12f814b10bafe79ef77e70113a2f5f7018640e7425ff979253425", size = 12967213, upload-time = "2025-11-16T22:52:39.38Z" }, ] [[package]] @@ -1692,7 +1722,7 @@ name = "nvidia-cublas" version = "13.1.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cuda-nvrtc", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "nvidia-cuda-nvrtc" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" }, @@ -1731,7 +1761,7 @@ name = "nvidia-cudnn-cu13" version = "9.20.0.48" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "nvidia-cublas" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/56/c5/83384d846b2fd17c44bd499b36c75a45ed4f095fbbb2252294e89cea5c5c/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:e31454ae00094b0c55319d9d15b6fa2fc50a9e1c0f5c8c80fb75258234e731e1", size = 444574296, upload-time = "2026-03-09T19:28:27.751Z" }, @@ -1743,7 +1773,7 @@ name = "nvidia-cufft" version = "12.0.0.61" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "nvidia-nvjitlink" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, @@ -1773,9 +1803,9 @@ name = "nvidia-cusolver" version = "12.0.4.66" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "nvidia-cusparse", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "nvidia-cublas" }, + { name = "nvidia-cusparse" }, + { name = "nvidia-nvjitlink" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, @@ -1787,7 +1817,7 @@ name = "nvidia-cusparse" version = "12.6.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "nvidia-nvjitlink" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, @@ -1844,8 +1874,8 @@ name = "omegaconf" version = "2.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "antlr4-python3-runtime", marker = "python_full_version < '3.13'" }, - { name = "pyyaml", marker = "python_full_version < '3.13'" }, + { name = "antlr4-python3-runtime" }, + { name = "pyyaml" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ce/3d/e4b57b8d9008c6ebe0d5eff901f91d5700cf7bdb8c8863df817463a7fd5e/omegaconf-2.3.1.tar.gz", hash = "sha256:e5e7de64aeebeddaf8e6d3f7a783b32ac2a01c0fbd9c878012caecb891a1f42a", size = 3298472, upload-time = "2026-06-11T05:05:12.885Z" } wheels = [ @@ -1859,7 +1889,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "flatbuffers" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "packaging" }, { name = "protobuf" }, ] @@ -1896,7 +1926,7 @@ version = "4.10.0.84" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1d/33/7b8ec6c4d45e678b26297e4a5e76464a93033a9adcc8c17eac01097065f6/opencv-contrib-python-4.10.0.84.tar.gz", hash = "sha256:4a3eae0ed9cadf1abe9293a6938a25a540e2fd6d7fc308595caa5896c8b36a0c", size = 150433857, upload-time = "2024-06-17T18:30:50.217Z" } wheels = [ @@ -1914,7 +1944,7 @@ version = "4.11.0.86" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/17/06/68c27a523103dad5837dc5b87e71285280c4f098c60e4fe8a8db6486ab09/opencv-python-4.11.0.86.tar.gz", hash = "sha256:03d60ccae62304860d232272e4a4fda93c39d595780cb40b161b310244b736a4", size = 95171956, upload-time = "2025-01-16T13:52:24.737Z" } wheels = [ @@ -1932,7 +1962,7 @@ version = "4.11.0.86" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/36/2f/5b2b3ba52c864848885ba988f24b7f105052f68da9ab0e693cc7c25b0b30/opencv-python-headless-4.11.0.86.tar.gz", hash = "sha256:996eb282ca4b43ec6a3972414de0e2331f5d9cda2b41091a49739c19fb843798", size = 95177929, upload-time = "2025-01-16T13:53:40.22Z" } wheels = [ @@ -1950,7 +1980,7 @@ version = "3.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7d/bf/9257e53a0e7715bc1127e15063e831f076723c6cd60985333a1c18878fb8/opt_einsum-3.3.0.tar.gz", hash = "sha256:59f6475f77bbc37dcf7cd748519c0ec60722e91e63ca114e68821c0c54a46549", size = 73951, upload-time = "2020-07-19T22:40:32.141Z" } wheels = [ @@ -1968,17 +1998,18 @@ wheels = [ [[package]] name = "paddleocr" -version = "3.4.1" +version = "3.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "aiohttp" }, { name = "paddlex", extra = ["ocr-core"] }, { name = "pyyaml" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6b/a1/408871ae4316c4c1a0eba3a627f6524008feda6b79f22a2dae4c66553745/paddleocr-3.4.1.tar.gz", hash = "sha256:d8b3e5d0de0edca7141ac826eeb48063bb2a9d4a34d20ffffba22d84cd405800", size = 2504875, upload-time = "2026-04-14T05:48:07.037Z" } +sdist = { url = "https://files.pythonhosted.org/packages/97/b1/9454e886afa925067ce51de2e513d146524645888de299d51ddc1caa3100/paddleocr-3.7.0.tar.gz", hash = "sha256:9b81eb62cf37e590d4873e60262ff56f968ff9de6ce1f565749760ceb1c422e2", size = 3082862, upload-time = "2026-06-11T12:09:52.545Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/93/643d26b16c9709e0eeddf750118e79d92ceddab3b7ed0f5ed97e662dd6b6/paddleocr-3.4.1-py3-none-any.whl", hash = "sha256:4538431f10cb9d7741caab5bf4e456f4da1ec0aa74eee93d11d5eba65fb66c1e", size = 88113, upload-time = "2026-04-14T05:48:05.492Z" }, + { url = "https://files.pythonhosted.org/packages/d2/79/2ade66cf72074a3d2a013e2ba1f256d6116fec797b908fc1b4f9c320d8c4/paddleocr-3.7.0-py3-none-any.whl", hash = "sha256:c0f0a81ad4112727f30c6fcf986ac0ef6a120d31ee0991a01fae0357ee32d338", size = 146750, upload-time = "2026-06-11T12:09:51.204Z" }, ] [[package]] @@ -1989,7 +2020,7 @@ dependencies = [ { name = "httpx" }, { name = "networkx" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "opt-einsum" }, { name = "pillow" }, { name = "protobuf" }, @@ -2011,7 +2042,7 @@ wheels = [ [[package]] name = "paddlex" -version = "3.4.3" +version = "3.7.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aistudio-sdk" }, @@ -2021,7 +2052,7 @@ dependencies = [ { name = "huggingface-hub" }, { name = "modelscope" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "packaging" }, { name = "pandas" }, { name = "pillow" }, @@ -2034,9 +2065,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "ujson" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/73/f6/a1ab1c812ceec3c505806015483b064e1a733c98edcace2aa85aa8186a75/paddlex-3.4.3.tar.gz", hash = "sha256:acecc48f589169607a49b846bfb108817aee47c5e1610aed59cfc3e451b1a825", size = 4173136, upload-time = "2026-03-27T02:31:18.457Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/96/1e86d4056ffc1e494c53aebab7e2faf08dcf8b929180f9872ae46632e9bd/paddlex-3.7.2.tar.gz", hash = "sha256:6a60b4595e2d51460f7f51326672adca86b89ff9ecfd02ac348fb69739e0093c", size = 4415130, upload-time = "2026-06-25T05:50:53.34Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/39/8fd9fd72ae51e1a0b21cb5596f1125f42286d688cf28797f883b42f2c8ce/paddlex-3.4.3-py3-none-any.whl", hash = "sha256:78aa8313e672ecbf27473b7afe7188b68716517b1b522060985d6d7dab1ed17c", size = 2022071, upload-time = "2026-03-27T02:31:15.751Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/9bbb9cd5f4fbbbefc7391c7d1ee61df8d511017a31e6933f6744b063fdab/paddlex-3.7.2-py3-none-any.whl", hash = "sha256:f1678bf650bbaccfd8f0d4e49d0ae631b4685c829fdae6e802ccd90d4fcb9a7f", size = 2239708, upload-time = "2026-06-25T05:50:51.196Z" }, ] [package.optional-dependencies] @@ -2055,7 +2086,7 @@ version = "3.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "python-dateutil" }, { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, ] @@ -2112,7 +2143,7 @@ dependencies = [ { name = "accelerate" }, { name = "huggingface-hub" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "packaging" }, { name = "psutil" }, { name = "pyyaml" }, @@ -2873,11 +2904,11 @@ wheels = [ [[package]] name = "python-dotenv" -version = "1.2.2" +version = "1.2.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/53/ed9d74092561d4b01a2ef1349d52cdbc135e526c245f366b089cfca6de49/python_dotenv-1.2.3.tar.gz", hash = "sha256:a20a594dabeaa385725aa239d5244871c143ecb356add8a20fcf23773a6c3a35", size = 58945, upload-time = "2026-08-16T16:54:54.067Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, + { url = "https://files.pythonhosted.org/packages/0d/17/c5c6b53ddc18f297992099b3d9ec16c855c0ccc83263a21fe4d1c625ec6c/python_dotenv-1.2.3-py3-none-any.whl", hash = "sha256:904552145e8bfed22162c09dab1c2b9b54fefa7b23ba780f4f26ca0316b0f0d9", size = 22780, upload-time = "2026-08-16T16:54:52.473Z" }, ] [[package]] @@ -2885,14 +2916,14 @@ name = "pytorch-lightning" version = "2.6.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "fsspec", extra = ["http"], marker = "python_full_version < '3.13'" }, - { name = "lightning-utilities", marker = "python_full_version < '3.13'" }, - { name = "packaging", marker = "python_full_version < '3.13'" }, - { name = "pyyaml", marker = "python_full_version < '3.13'" }, - { name = "torch", marker = "python_full_version < '3.13'" }, - { name = "torchmetrics", marker = "python_full_version < '3.13'" }, - { name = "tqdm", marker = "python_full_version < '3.13'" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "fsspec", extra = ["http"] }, + { name = "lightning-utilities" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "torch" }, + { name = "torchmetrics" }, + { name = "tqdm" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/52/2c/8e73a3929b4c4bd600cafd38a97aaf7242a8cf518fb9f33d27c274ec898f/pytorch_lightning-2.6.5.tar.gz", hash = "sha256:1c32cefa76a1a9c4c5250338272d961d1e48b180e68396849efe128538ddb28e", size = 661673, upload-time = "2026-05-27T14:33:41.961Z" } wheels = [ @@ -2905,7 +2936,7 @@ version = "1.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5a/75/50581633d199812205ea8cdd0f6d52f12a624886b74bf1486335b67f01ff/pywavelets-1.9.0.tar.gz", hash = "sha256:148d12203377772bea452a59211d98649c8ee4a05eff019a9021853a36babdc8", size = 3938340, upload-time = "2025-08-04T16:20:04.978Z" } wheels = [ @@ -3118,7 +3149,7 @@ all = [ { name = "diffusers" }, { name = "huggingface-hub" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "onnxruntime" }, { name = "opencv-python-headless" }, { name = "pillow-heif" }, @@ -3132,7 +3163,7 @@ all = [ ] detect = [ { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "opencv-python-headless" }, { name = "pywavelets" }, ] @@ -3140,7 +3171,7 @@ dev = [ { name = "av" }, { name = "invisible-watermark" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "opencv-python-headless" }, { name = "packaging" }, { name = "pyright" }, @@ -3154,7 +3185,7 @@ diffusion = [ { name = "accelerate" }, { name = "diffusers" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "opencv-python-headless" }, { name = "safetensors" }, { name = "tokenizers" }, @@ -3167,20 +3198,20 @@ heif = [ lama = [ { name = "huggingface-hub" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "onnxruntime" }, { name = "opencv-python-headless" }, ] migan = [ { name = "huggingface-hub" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "onnxruntime" }, { name = "opencv-python-headless" }, ] pixels = [ { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "opencv-python-headless" }, ] qwen-zimage = [ @@ -3188,7 +3219,7 @@ qwen-zimage = [ { name = "diffsynth" }, { name = "diffusers" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "opencv-python-headless" }, { name = "safetensors" }, { name = "tokenizers" }, @@ -3207,7 +3238,7 @@ text-restoration = [ { name = "diffusers" }, { name = "huggingface-hub" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "onnxruntime" }, { name = "opencv-python-headless" }, { name = "safetensors" }, @@ -3222,12 +3253,12 @@ trustmark = [ video = [ { name = "av" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "opencv-python-headless" }, ] visible = [ { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "opencv-python-headless" }, ] @@ -3320,27 +3351,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.16.2" +version = "0.16.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/73/e1/4508a569211b35599016e84ba65c1a992b7a4004b4b6c4bea02a851cba1b/ruff-0.16.2.tar.gz", hash = "sha256:c3d7828d12e8927a6fc65fe38e2c2541b9e762d360a1786d752cb1b8883b3c9c", size = 4885811, upload-time = "2026-08-07T13:31:01.432Z" } +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" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/57/db19951540f98859c956b50bdb4d31089b4d91e9f15e2968e7d5193806d5/ruff-0.16.2-py3-none-linux_armv6l.whl", hash = "sha256:3c8de4cf2181f01d57946d87d777aa52916976fc09942aed89938fab5e013318", size = 10847925, upload-time = "2026-08-07T13:30:14.468Z" }, - { url = "https://files.pythonhosted.org/packages/13/5a/995fe85a8470d3e391ac0f7fa8054bb454eaf33ee138196d6172ed1079c0/ruff-0.16.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a48cc05c6fbc811ca81b5d7ba95375affea6582d1b8024e455e41afbbf55344", size = 11072662, upload-time = "2026-08-07T13:30:18.143Z" }, - { url = "https://files.pythonhosted.org/packages/32/53/370d767c61c71a971a4ace36703a7ecd8c393956349a7325d7fab2b56827/ruff-0.16.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a2c0d14fcbb26c91f0f867a6dc9bd71bbc30b1b6151829c884f23faeab2e5700", size = 10566771, upload-time = "2026-08-07T13:30:20.899Z" }, - { url = "https://files.pythonhosted.org/packages/85/d6/9d96948caf5a632be62d62202d5ec914d6856f204fd79eb036e5915e79ea/ruff-0.16.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:335c621622c4650330be50842561c6586ac6971bb8ab5407fe34dcc9efb16bbe", size = 10975825, upload-time = "2026-08-07T13:30:23.517Z" }, - { url = "https://files.pythonhosted.org/packages/3b/92/ea87129b3414acb0b5770563779c51804d37ac67675c7ba35447ddb14773/ruff-0.16.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20e66910f2c37cc753f9ef6580c914a621b80c4fa3549d3e3521e29d0f5bfc3f", size = 10649437, upload-time = "2026-08-07T13:30:26.097Z" }, - { url = "https://files.pythonhosted.org/packages/ac/43/f8f291dcd4af5bb7872b74fdfa41a7cd7c856ca1d4069670971cf1b9f5cb/ruff-0.16.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7e36fbfba65510548156902bcf1350a979a958ce0347ce0f90d73894036b39f", size = 11446761, upload-time = "2026-08-07T13:30:28.752Z" }, - { url = "https://files.pythonhosted.org/packages/71/4a/ef991fb2fcf516ab71f0808adcdd8da5e18c8cde447f4ceaf5f47a5132a5/ruff-0.16.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f0eab35f80df8f134aae5d1630e751901321d317cc8e50dc39e36fa3ed34cd12", size = 12336364, upload-time = "2026-08-07T13:30:31.468Z" }, - { url = "https://files.pythonhosted.org/packages/f3/24/f615e74f307e6ca0e56a482872477b856c70d530aa356abfb6dfe5ca8a80/ruff-0.16.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ea8c0594feb894e89c8c61ab9c103d38b0ea72dfde6c594107147ca31b1140", size = 11630720, upload-time = "2026-08-07T13:30:34.426Z" }, - { url = "https://files.pythonhosted.org/packages/c5/d3/8ef50149e8412a77f7ab409efdef0e2b23803707a3863da4fc64cb23d459/ruff-0.16.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab3d62dde0b19facdd632008cc4827fc28ada7736c6bd35ab6f1050f0bfed53f", size = 11466130, upload-time = "2026-08-07T13:30:36.958Z" }, - { url = "https://files.pythonhosted.org/packages/dd/a7/a19334985c4dea8c381981fa252cd854c7ee52dc4b1686dc16f4a911c702/ruff-0.16.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e43e1f5b8388da9eca1b9e88328d47a5cec794633ccf6f7484ac2dd15eee92c0", size = 11523634, upload-time = "2026-08-07T13:30:39.822Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6c/96d192b0e742412ceda08c0a50f9669b253dde9fd6a60ea1a10c9fa79a63/ruff-0.16.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c24788a980581e1d7ea3a0cbe4344c4fbeb0a6a9b1f4713aa46bb104f8294690", size = 10949807, upload-time = "2026-08-07T13:30:42.745Z" }, - { url = "https://files.pythonhosted.org/packages/fa/51/e26599ceca11e79ee255c7df515995561edf87e9ca1893284e44d98f5a86/ruff-0.16.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:81806b08329130005dd4a8a8394a0c9da8c6f4cafb16ba438d2a2ee6a18bedf1", size = 10646891, upload-time = "2026-08-07T13:30:45.522Z" }, - { url = "https://files.pythonhosted.org/packages/68/01/800c4b1f97bc8d7c6029e06b1f20473a3cf1e13c4933d8f3342add83fc55/ruff-0.16.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4ce4e02bad779bef557f541a1b31f20d6abeae1cc05ed1b1ac019d4ffd1044c8", size = 11162063, upload-time = "2026-08-07T13:30:48.131Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d0/1477ea50fc5a0d4b0b71d1d63d50770bdd794d90b43e37a7618e63ec9894/ruff-0.16.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e0422abdf70070255fc4073ce9dfc814cc03db577013761ddd09bc1e4a9a4fbd", size = 11556038, upload-time = "2026-08-07T13:30:50.686Z" }, - { url = "https://files.pythonhosted.org/packages/b8/76/a7776f32048d991e16d4fa8ff91790b877342d3596cc3ed04acdbf1aaedc/ruff-0.16.2-py3-none-win32.whl", hash = "sha256:bf3a63d78fb39f4bf5ac8ae52051c5520505301abe19ba4e204c453b3f09bb0b", size = 10872850, upload-time = "2026-08-07T13:30:53.471Z" }, - { url = "https://files.pythonhosted.org/packages/00/0d/929c800d920e61397d82a01b60bffc68da3052c17d31de59efaad2e4ed75/ruff-0.16.2-py3-none-win_amd64.whl", hash = "sha256:bcabe2f6d0fc7819f1431793005af4e4de7371927d037345bf941252b195b9fa", size = 12023338, upload-time = "2026-08-07T13:30:56.193Z" }, - { url = "https://files.pythonhosted.org/packages/5b/6c/93e26c22c5f78ff87363e07da49c84955affbeb1098bd1936bf3b3f293bf/ruff-0.16.2-py3-none-win_arm64.whl", hash = "sha256:d614e95cedf38a2053fd351c55b103ba30d017d61688fdbfd40ee0412852a99f", size = 11374065, upload-time = "2026-08-07T13:30:58.775Z" }, + { 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" }, ] [[package]] @@ -3432,7 +3463,7 @@ version = "2.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4d/bc/0989043118a27cccb4e906a46b7565ce36ca7b57f5a18b78f4f1b0f72d9d/shapely-2.1.2.tar.gz", hash = "sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9", size = 315489, upload-time = "2025-09-24T13:51:41.432Z" } wheels = [ @@ -3653,10 +3684,10 @@ name = "torchmetrics" version = "1.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "lightning-utilities", marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "packaging", marker = "python_full_version < '3.13'" }, - { name = "torch", marker = "python_full_version < '3.13'" }, + { name = "lightning-utilities" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "torch" }, ] sdist = { url = "https://files.pythonhosted.org/packages/81/34/39b8b749333db56c0585d7a11fa62a283c087bb1dfc897d69fb8cedbefb1/torchmetrics-1.9.0.tar.gz", hash = "sha256:a488609948600df52d3db4fcdab02e62aab2a85ef34da67037dc3e65b8512faa", size = 581765, upload-time = "2026-03-09T17:41:22.443Z" } wheels = [ @@ -3669,7 +3700,7 @@ version = "0.28.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "pillow" }, { name = "torch" }, ] @@ -3710,12 +3741,12 @@ wheels = [ [[package]] name = "transformers" -version = "5.14.1" +version = "5.15.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "huggingface-hub" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "packaging" }, { name = "pyyaml" }, { name = "regex" }, @@ -3724,9 +3755,9 @@ dependencies = [ { name = "tqdm" }, { name = "typer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5a/fb/2a2ba88f325e68a921d8b69ff63b477830b2e73ade9a3c8c8cab2f06d741/transformers-5.14.1.tar.gz", hash = "sha256:60d196c27781eacf8637e2b533f517582907ad6f9ae142046d6b69431a5b2173", size = 9295927, upload-time = "2026-07-16T09:41:57.773Z" } +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" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/67/8d85ca2323233ae3c0365a659c4e52ee1f587b440e4bc577e7d8e4416d0f/transformers-5.14.1-py3-none-any.whl", hash = "sha256:9db974c4079ede2d1a3ea7ca5a240df33f2cc26fc2b36ba64c5f2a4f43b6e725", size = 11625234, upload-time = "2026-07-16T09:41:54.143Z" }, + { 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" }, ] [[package]] @@ -3751,13 +3782,13 @@ name = "trustmark" version = "0.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "einops", marker = "python_full_version < '3.13'" }, - { name = "lightning", marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "omegaconf", marker = "python_full_version < '3.13'" }, - { name = "six", marker = "python_full_version < '3.13'" }, - { name = "torch", marker = "python_full_version < '3.13'" }, - { name = "torchvision", marker = "python_full_version < '3.13'" }, + { name = "einops" }, + { name = "lightning" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, + { name = "omegaconf" }, + { name = "six" }, + { name = "torch" }, + { name = "torchvision" }, ] sdist = { url = "https://files.pythonhosted.org/packages/87/0a/0a4232030c6a62d12b6a02ae73bdce6e99c8532bc8f05a5a2e6ce103da82/trustmark-0.9.1.tar.gz", hash = "sha256:dc79e3fb070f5d94765acf8868a51f50a612cc05b53223cf1e6b605d4ff1e0ae", size = 63949, upload-time = "2026-04-09T08:59:52.472Z" } From 3ba4ace7e07b818302bc2955e9a96ccc5ce0c3ae Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Wed, 19 Aug 2026 10:22:58 -0700 Subject: [PATCH 25/44] Disable mkldnn in the draft engines (linux PIR crash) --- src/remove_ai_watermarks/text_draft.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/remove_ai_watermarks/text_draft.py b/src/remove_ai_watermarks/text_draft.py index 3e4920e..96256a6 100644 --- a/src/remove_ai_watermarks/text_draft.py +++ b/src/remove_ai_watermarks/text_draft.py @@ -198,16 +198,21 @@ def draft_available() -> bool: def _build_engines() -> tuple[Any, dict[str, Any]]: from paddleocr import PaddleOCR, TextRecognition + # enable_mkldnn=False is load-bearing on linux: paddle's oneDNN PIR executor + # raises NotImplementedError on the text-detection graph there (seen on the + # Modal draft container, 2026-08-19), while the reference CPU path runs. The + # eval scripts never hit it because macOS pads take a different path. detector = PaddleOCR( lang="ch", use_doc_orientation_classify=False, use_doc_unwarping=False, use_textline_orientation=False, + enable_mkldnn=False, ) engines = { - "en": TextRecognition(model_name="en_PP-OCRv5_mobile_rec"), - "ru": TextRecognition(model_name="eslav_PP-OCRv5_mobile_rec"), - "ch": TextRecognition(model_name="PP-OCRv5_server_rec"), + "en": TextRecognition(model_name="en_PP-OCRv5_mobile_rec", enable_mkldnn=False), + "ru": TextRecognition(model_name="eslav_PP-OCRv5_mobile_rec", enable_mkldnn=False), + "ch": TextRecognition(model_name="PP-OCRv5_server_rec", enable_mkldnn=False), } return detector, engines From e315eacb1402cbe092acf214aa464cb1f5028327 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Wed, 19 Aug 2026 12:21:32 -0700 Subject: [PATCH 26/44] Document automated-operator verified manifests verified:true may attest machine-verified geometry (stability-gated boxes inside caps) set by a service; the pipeline consumes box/script only, text is advisory, so the verification burden is on the geometry. --- docs/cli.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/cli.md b/docs/cli.md index 6963d0c..947688b 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -416,6 +416,12 @@ remove-ai-watermarks invisible image.png -o clean.png \ --pipeline qwen-zimage --text-manifest verified-lines.json --force ``` +``verified: true`` may also be set by an automated operator (a service) that +attests machine-verified geometry: stability-gated detector boxes inside sane +caps. The restoration pipeline consumes box/script geometry only - the ``text`` +field is advisory metadata and never reaches the pixels - so what verification +must guarantee is the geometry, and a machine gate can. + Since 0.27.1 the global 15% Qwen-VAE fidelity-anchor blend is off by default: it was measured to return detector-visible OpenAI SynthID on poster-scale manifests (official Content Provenance API, 2026-08-19). `--fidelity-anchor` restores the From ee865e95e4705451e9b840f64dfcefb4c069d867 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Wed, 19 Aug 2026 19:53:38 -0700 Subject: [PATCH 27/44] Add geometry draft mode and pad silhouette descenders pre-commit: 1 maintain.sh - not rerun full, ruff + 34 targeted tests passed; 2) /simplify - pad lives in silhouette crop, stable=False reuses probes; 3) docs sync - no README/cli refs to update; 4) CLAUDE.md - no change EOF ) --- .../_internal/text_restoration.py | 16 +++++++++++++++- src/remove_ai_watermarks/text_draft.py | 19 ++++++++++++++++--- tests/test_text_draft.py | 17 +++++++++++++++++ tests/test_text_restoration.py | 12 ++++++++++++ 4 files changed, 60 insertions(+), 4 deletions(-) diff --git a/src/remove_ai_watermarks/_internal/text_restoration.py b/src/remove_ai_watermarks/_internal/text_restoration.py index b956add..58431d2 100644 --- a/src/remove_ai_watermarks/_internal/text_restoration.py +++ b/src/remove_ai_watermarks/_internal/text_restoration.py @@ -185,6 +185,20 @@ def restore_verified_text( return Image.fromarray(restored) +def _glyph_crop_box(box: tuple[int, int, int, int], width: int, height: int) -> tuple[int, int, int, int]: + """Widen the detector box so descenders stay inside the silhouette crop. + + Paddle line boxes sit 2-5 px above the true ink bottom on the poster + fixtures. The crop is the box itself, so those pixels never reached the + donor composite. Expand only on Y: 8% up, 25% down, clamped to the frame. + """ + x1, y1, x2, y2 = box + line_h = max(1, y2 - y1) + pad_top = max(1, round(line_h * 0.08)) + pad_bot = max(2, round(line_h * 0.25)) + return max(0, x1), max(0, y1 - pad_top), min(width, x2), min(height, y2 + pad_bot) + + def source_silhouette_mask( source_rgb: NDArray[Any], box: tuple[int, int, int, int], @@ -192,7 +206,7 @@ def source_silhouette_mask( ) -> NDArray[Any]: """Recover a thresholded glyph shape without retaining source amplitudes.""" height, width = source_rgb.shape[:2] - x1, y1, x2, y2 = _clip_box(box, width, height) + x1, y1, x2, y2 = _glyph_crop_box(box, width, height) gray = cv2.cvtColor(source_rgb[y1:y2, x1:x2], cv2.COLOR_RGB2GRAY) support = np.ones(gray.shape, dtype=np.uint8) if angle: diff --git a/src/remove_ai_watermarks/text_draft.py b/src/remove_ai_watermarks/text_draft.py index 96256a6..742c009 100644 --- a/src/remove_ai_watermarks/text_draft.py +++ b/src/remove_ai_watermarks/text_draft.py @@ -223,15 +223,22 @@ def draft_text_lines( min_score: float = 0.85, detector: Any | None = None, engines: dict[str, Any] | None = None, + stable: bool = True, ) -> TextDraft: """Propose verified-text manifest lines for ``image``; never verified ones. Args: image: path of the source image (draft boxes are in ITS pixel space). - min_score: recognition confidence floor for every jittered read. + min_score: recognition confidence floor. With ``stable=True`` every + jittered read must clear it; with ``stable=False`` the single + probe for the chosen language must. detector/engines: injectable Paddle objects (tests use fakes); when None they are built from the ``text-draft`` extra, which must be installed (``draft_available()`` reports it). + stable: when True (default), accept a line only if three crop paddings + normalize identically. When False, take one recognition trio and + accept on score alone. Automatic restoration uses box/script only, + so the jitter gate is optional there. Returns: ``TextDraft`` with crop-stable ``accepted`` proposals and unstable @@ -262,8 +269,14 @@ def draft_text_lines( probes[language] = _recognize(engine, source_rgb, box, script, 0.1) language = choose_language(probes) script = "cjk" if language == "ch" else "alphabetic" - reads = [_recognize(engines[language], source_rgb, box, script, ratio) for ratio in JITTER_RATIOS] - text = stable_recognition(reads, min_score) + if stable: + reads = [_recognize(engines[language], source_rgb, box, script, ratio) for ratio in JITTER_RATIOS] + text = stable_recognition(reads, min_score) + else: + text, score = probes[language] + reads = [(text, score)] + if not text.strip() or score < min_score: + text = None if text is None: rejected.append( RejectedLine( diff --git a/tests/test_text_draft.py b/tests/test_text_draft.py index 6a1abc0..4a8f3aa 100644 --- a/tests/test_text_draft.py +++ b/tests/test_text_draft.py @@ -139,3 +139,20 @@ class TestDraftTextLines: (line,) = draft.accepted assert line.language == "ch" assert line.script == "cjk" + + def test_unstable_geometry_draft_accepts_a_single_high_score_read(self, tmp_path: Path): + path = self._poster(tmp_path) + jitter = _JitterEngine(["Hello", "Hallo", "Hullo"]) + stable = _FakeEngine("Hello") + # Probes disagree across engines so language is en; jitter would fail + # the stable gate. Geometry mode uses the one en probe and accepts. + draft = draft_text_lines( + path, + min_score=0.75, + stable=False, + detector=_FakeDetector([(20, 20, 200, 60)]), + engines={"en": jitter, "ru": stable, "ch": stable}, + ) + (line,) = draft.accepted + assert line.text == "Hello" + assert line.min_score >= 0.75 diff --git a/tests/test_text_restoration.py b/tests/test_text_restoration.py index 9ea4887..bdc0e2e 100644 --- a/tests/test_text_restoration.py +++ b/tests/test_text_restoration.py @@ -15,6 +15,7 @@ from remove_ai_watermarks._internal.text_restoration import ( load_verified_text_manifest, restore_verified_text, source_pixel_sha256, + source_silhouette_mask, ) @@ -126,3 +127,14 @@ def test_restoration_uses_lama_and_qwen_vae_core(monkeypatch) -> None: assert calls assert np.all(restored[16, 20] == donor[16, 20]) assert np.all(restored[0, 0] == candidate[0, 0]) + + +def test_silhouette_includes_descender_below_the_detector_box() -> None: + source = np.full((40, 50, 3), 240, dtype=np.uint8) + source[10:22, 18:24] = 20 + source[22:27, 18:22] = 20 # tail of a y / Cyrillic u, under the box + + mask = source_silhouette_mask(source, (10, 10, 40, 22)) + + assert mask[24, 20] == 255 + assert mask[16, 20] == 255 From 91aa49b03a170396b6130b555f7cd4210fe784cc Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Wed, 19 Aug 2026 21:33:22 -0700 Subject: [PATCH 28/44] Allow verified-text restoration on tiled images Tile the Qwen VAE donor the same way as the global pass. Glyph restore already runs on the blended full frame, so the old tile ban was an artificial gate. --- docs/python-api.md | 5 ++- .../_internal/qwen_zimage_pipeline.py | 19 ++++++++- src/remove_ai_watermarks/invisible_engine.py | 8 ++-- tests/test_invisible_engine.py | 5 ++- tests/test_qwen_zimage_pipeline.py | 41 +++++++++++++++++++ 5 files changed, 70 insertions(+), 8 deletions(-) diff --git a/docs/python-api.md b/docs/python-api.md index 01edd9b..81351a5 100644 --- a/docs/python-api.md +++ b/docs/python-api.md @@ -609,8 +609,9 @@ engine.remove_watermark( Install `remove-ai-watermarks[text-restoration]`. The manifest schema and safety constraints are documented in the CLI guide. The engine verifies its decoded RGB -hash before loading the diffusion models and rejects SDXL, tiling, downscaling, and -postprocessing combinations that were not evaluated. `InvisibleOptions` exposes the +hash before loading the diffusion models and rejects SDXL, downscaling, and +postprocessing combinations that were not evaluated. Tiling is allowed: the VAE +donor uses the same overlapping tiles as the global pass. `InvisibleOptions` exposes the same field for `remove_all`; after a visible-stage edit, the manifest must be built against the staged pixels rather than the pristine source. diff --git a/src/remove_ai_watermarks/_internal/qwen_zimage_pipeline.py b/src/remove_ai_watermarks/_internal/qwen_zimage_pipeline.py index b441552..9cda34e 100644 --- a/src/remove_ai_watermarks/_internal/qwen_zimage_pipeline.py +++ b/src/remove_ai_watermarks/_internal/qwen_zimage_pipeline.py @@ -1079,7 +1079,24 @@ class QwenZImagePipeline: donor = None if text_manifest is not None: self._progress("Reconstructing the verified text donor with the Qwen VAE...") - donor = self._qwen_vae_roundtrip(image) + if tile and max(image.size) > tile_size: + from remove_ai_watermarks._internal.tiling import run_tiled + + donor = run_tiled( + self._qwen_vae_roundtrip, + image, + tile_size, + tile_overlap, + lambda message: self._progress( + message.replace( + "Tiled diffusion", + "Reconstructing the verified text donor", + 1, + ) + ), + ) + else: + donor = self._qwen_vae_roundtrip(image) global_strength = ( resolution_adaptive_denoise(image.width, image.height) if strength is None else float(strength) ) diff --git a/src/remove_ai_watermarks/invisible_engine.py b/src/remove_ai_watermarks/invisible_engine.py index 93c75bc..cdf9c1e 100644 --- a/src/remove_ai_watermarks/invisible_engine.py +++ b/src/remove_ai_watermarks/invisible_engine.py @@ -186,8 +186,10 @@ class InvisibleEngine: text_manifest: Operator-verified text lines bound to the decoded source pixels. Enables the experimental Qwen-VAE ``vae-glyphs`` post-pass. Requires the ``text-restoration`` extra and the ``qwen-zimage`` - profile. Incompatible with tiling, downscaling, humanize, unsharp, - and adaptive polish because those combinations are not calibrated. + profile. Incompatible with downscaling, humanize, unsharp, and + adaptive polish. Tiling is supported: the VAE donor uses the same + overlapping tiles as the global pass, then glyph restore runs on + the blended full frame. fidelity_anchor: Blend 15% of the Qwen-VAE donor across the whole frame before glyph restoration. OFF by default since 0.27.1: that global blend was measured to return detector-visible OpenAI SynthID on @@ -211,8 +213,6 @@ class InvisibleEngine: raise ValueError("--text-manifest is supported only by the qwen-zimage profile") if max_resolution != 0: raise ValueError("--text-manifest requires --max-resolution 0") - if tile: - raise ValueError("--text-manifest is not calibrated with --tile") if humanize > 0.0 or unsharp > 0.0 or adaptive_polish: raise ValueError("--text-manifest requires humanize=0, unsharp=0, and adaptive polish disabled") from remove_ai_watermarks import region_eraser diff --git a/tests/test_invisible_engine.py b/tests/test_invisible_engine.py index 320fa9e..b7fc46b 100644 --- a/tests/test_invisible_engine.py +++ b/tests/test_invisible_engine.py @@ -61,7 +61,6 @@ class TestVerifiedTextMode: cases = ( ("sdxl-zimage", {}, "qwen-zimage"), ("qwen-zimage", {"max_resolution": 1024}, "max-resolution 0"), - ("qwen-zimage", {"tile": True}, "not calibrated"), ("qwen-zimage", {"humanize": 1.0}, "humanize=0"), ("qwen-zimage", {"adaptive_polish": True}, "polish disabled"), ) @@ -129,6 +128,10 @@ class TestVerifiedTextMode: assert seen["fidelity_anchor"] is True + engine.remove_watermark(source, output, text_manifest=manifest, tile=True) + + assert seen["tile"] is True + class TestNativeOutputSize: """Model-side latent-grid rounding must not change the public output size.""" diff --git a/tests/test_qwen_zimage_pipeline.py b/tests/test_qwen_zimage_pipeline.py index 46a8cf4..3e4775c 100644 --- a/tests/test_qwen_zimage_pipeline.py +++ b/tests/test_qwen_zimage_pipeline.py @@ -659,6 +659,47 @@ def test_no_face_path_still_runs_verified_text_restoration(monkeypatch): restore.assert_called_with(source, anchor, donor, manifest.lines) +def test_tiled_verified_text_runs_vae_donor_per_tile(monkeypatch): + from remove_ai_watermarks._internal import qwen_zimage_pipeline, text_restoration + from remove_ai_watermarks._internal.qwen_zimage_pipeline import QwenZImagePipeline + from remove_ai_watermarks._internal.text_restoration import VerifiedTextLine, VerifiedTextManifest + + pipeline = object.__new__(QwenZImagePipeline) + pipeline.device = "cuda" + pipeline.progress_callback = None + source = Image.new("RGB", (96, 80), (10, 20, 30)) + restored = Image.new("RGB", (96, 80), (130, 140, 150)) + pipeline._qwen_vae_roundtrip = MagicMock(side_effect=lambda tile: tile) + pipeline._run_global = MagicMock(side_effect=lambda tile, _strength, _seed: tile) + monkeypatch.setattr(qwen_zimage_pipeline, "detect_faces", lambda _image: []) + restore = MagicMock(return_value=restored) + monkeypatch.setattr(text_restoration, "restore_verified_text", restore) + manifest = VerifiedTextManifest( + "0" * 64, + 96, + 80, + (VerifiedTextLine((4, 4, 20, 16), "Exact", "alphabetic"),), + ) + + result = pipeline.run( + source, + strength=0.1, + seed=0, + tile=True, + tile_size=64, + tile_overlap=16, + text_manifest=manifest, + ) + + assert result is restored + assert pipeline._qwen_vae_roundtrip.call_count > 1 + assert pipeline._run_global.call_count > 1 + restore.assert_called_once() + assert restore.call_args.args[0].size == (96, 80) + assert restore.call_args.args[1].size == (96, 80) + assert restore.call_args.args[2].size == (96, 80) + + def test_watermark_remover_dispatches_to_full_pipeline(tmp_path, monkeypatch): from remove_ai_watermarks._internal.watermark_remover import WatermarkRemover From 0aa03c4e89607ae47e7fbbdb67dea9aacdbadd98 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Wed, 19 Aug 2026 21:46:14 -0700 Subject: [PATCH 29/44] Release 0.28.1 pre-commit: 1 maintain.sh - uv-secure lightning PYSEC-2026-3624 known, no fix, same as 0.28.0; ruff/pyright/1378 tests passed; 2) /simplify - version bump only; 3) docs sync - no refs; 4) CLAUDE.md - no change EOF ) --- pyproject.toml | 2 +- src/remove_ai_watermarks/__init__.py | 2 +- uv.lock | 116 +++++++++++++-------------- 3 files changed, 60 insertions(+), 60 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6bedc66..8dd36ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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.28.0" +version = "0.28.1" description = "AI watermark remover for visible, invisible, and provenance marks in images and video" readme = "README.md" requires-python = ">=3.11,<3.15" diff --git a/src/remove_ai_watermarks/__init__.py b/src/remove_ai_watermarks/__init__.py index 01be6ac..66f3c0b 100644 --- a/src/remove_ai_watermarks/__init__.py +++ b/src/remove_ai_watermarks/__init__.py @@ -32,7 +32,7 @@ _os.environ.setdefault("TRANSFORMERS_VERBOSITY", "error") _warnings.filterwarnings("ignore", message=r".*ImageProcessorFast.*") -__version__ = "0.28.0" +__version__ = "0.28.1" __all__ = [ "BatchSummary", diff --git a/uv.lock b/uv.lock index a895a7f..2accb60 100644 --- a/uv.lock +++ b/uv.lock @@ -753,7 +753,7 @@ name = "cuda-bindings" version = "13.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cuda-pathfinder" }, + { name = "cuda-pathfinder", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/51/6b/457ca12dad3ee9bfcc9a545cfd6b64b359ba49de40f776f6e028e678f262/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5879712accf6e14bb01aa5e67440eb84998b8d104b509cc7a6dc0b8f656a474", size = 6053539, upload-time = "2026-05-29T23:11:43.19Z" }, @@ -786,43 +786,43 @@ wheels = [ [package.optional-dependencies] cublas = [ - { name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, - { name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] cudart = [ - { name = "nvidia-cuda-runtime", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cuda-runtime", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] cufft = [ - { name = "nvidia-cufft", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, - { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cufft", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] cufile = [ - { name = "nvidia-cufile", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cufile", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] cupti = [ - { name = "nvidia-cuda-cupti", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cuda-cupti", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] curand = [ - { name = "nvidia-curand", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-curand", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] cusolver = [ - { name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, - { name = "nvidia-cusolver", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, - { name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, - { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cusolver", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] cusparse = [ - { name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, - { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] nvjitlink = [ - { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] nvrtc = [ - { name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] nvtx = [ - { name = "nvidia-nvtx", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-nvtx", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] [[package]] @@ -1268,15 +1268,15 @@ name = "lightning" version = "2.6.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "fsspec", extra = ["http"] }, - { name = "lightning-utilities" }, - { name = "packaging" }, - { name = "pytorch-lightning" }, - { name = "pyyaml" }, - { name = "torch" }, - { name = "torchmetrics" }, - { name = "tqdm" }, - { name = "typing-extensions" }, + { name = "fsspec", extra = ["http"], marker = "python_full_version < '3.13'" }, + { name = "lightning-utilities", marker = "python_full_version < '3.13'" }, + { name = "packaging", marker = "python_full_version < '3.13'" }, + { name = "pytorch-lightning", marker = "python_full_version < '3.13'" }, + { name = "pyyaml", marker = "python_full_version < '3.13'" }, + { name = "torch", marker = "python_full_version < '3.13'" }, + { name = "torchmetrics", marker = "python_full_version < '3.13'" }, + { name = "tqdm", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c9/1d/83be8536bec71a0173e762a9a1fd92a24a5ad0d0f74c59550c3c4e6c103b/lightning-2.6.5.tar.gz", hash = "sha256:16a30310ed69afde3748491feb5d13508908effd70390d2bfc203dc0812a4b4a", size = 659201, upload-time = "2026-05-27T14:33:41.806Z" } wheels = [ @@ -1288,8 +1288,8 @@ name = "lightning-utilities" version = "0.15.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "packaging" }, - { name = "typing-extensions" }, + { name = "packaging", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f1/45/7fa8f56b17dc0f0a41ec70dd307ecd6787254483549843bef4c30ab5adce/lightning_utilities-0.15.3.tar.gz", hash = "sha256:792ae0204c79f6859721ac7f386c237a33b0ed06ba775009cb894e010a842033", size = 33553, upload-time = "2026-02-22T14:48:53.348Z" } wheels = [ @@ -1722,7 +1722,7 @@ name = "nvidia-cublas" version = "13.1.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cuda-nvrtc" }, + { name = "nvidia-cuda-nvrtc", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" }, @@ -1761,7 +1761,7 @@ name = "nvidia-cudnn-cu13" version = "9.20.0.48" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas" }, + { name = "nvidia-cublas", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/56/c5/83384d846b2fd17c44bd499b36c75a45ed4f095fbbb2252294e89cea5c5c/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:e31454ae00094b0c55319d9d15b6fa2fc50a9e1c0f5c8c80fb75258234e731e1", size = 444574296, upload-time = "2026-03-09T19:28:27.751Z" }, @@ -1773,7 +1773,7 @@ name = "nvidia-cufft" version = "12.0.0.61" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink" }, + { name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, @@ -1803,9 +1803,9 @@ name = "nvidia-cusolver" version = "12.0.4.66" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas" }, - { name = "nvidia-cusparse" }, - { name = "nvidia-nvjitlink" }, + { name = "nvidia-cublas", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "nvidia-cusparse", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, @@ -1817,7 +1817,7 @@ name = "nvidia-cusparse" version = "12.6.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink" }, + { name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, @@ -1874,8 +1874,8 @@ name = "omegaconf" version = "2.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "antlr4-python3-runtime" }, - { name = "pyyaml" }, + { name = "antlr4-python3-runtime", marker = "python_full_version < '3.13'" }, + { name = "pyyaml", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ce/3d/e4b57b8d9008c6ebe0d5eff901f91d5700cf7bdb8c8863df817463a7fd5e/omegaconf-2.3.1.tar.gz", hash = "sha256:e5e7de64aeebeddaf8e6d3f7a783b32ac2a01c0fbd9c878012caecb891a1f42a", size = 3298472, upload-time = "2026-06-11T05:05:12.885Z" } wheels = [ @@ -2916,14 +2916,14 @@ name = "pytorch-lightning" version = "2.6.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "fsspec", extra = ["http"] }, - { name = "lightning-utilities" }, - { name = "packaging" }, - { name = "pyyaml" }, - { name = "torch" }, - { name = "torchmetrics" }, - { name = "tqdm" }, - { name = "typing-extensions" }, + { name = "fsspec", extra = ["http"], marker = "python_full_version < '3.13'" }, + { name = "lightning-utilities", marker = "python_full_version < '3.13'" }, + { name = "packaging", marker = "python_full_version < '3.13'" }, + { name = "pyyaml", marker = "python_full_version < '3.13'" }, + { name = "torch", marker = "python_full_version < '3.13'" }, + { name = "torchmetrics", marker = "python_full_version < '3.13'" }, + { name = "tqdm", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/52/2c/8e73a3929b4c4bd600cafd38a97aaf7242a8cf518fb9f33d27c274ec898f/pytorch_lightning-2.6.5.tar.gz", hash = "sha256:1c32cefa76a1a9c4c5250338272d961d1e48b180e68396849efe128538ddb28e", size = 661673, upload-time = "2026-05-27T14:33:41.961Z" } wheels = [ @@ -3131,7 +3131,7 @@ wheels = [ [[package]] name = "remove-ai-watermarks" -version = "0.28.0" +version = "0.28.1" source = { editable = "." } dependencies = [ { name = "c2pa-python" }, @@ -3684,10 +3684,10 @@ name = "torchmetrics" version = "1.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "lightning-utilities" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, - { name = "packaging" }, - { name = "torch" }, + { name = "lightning-utilities", marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "packaging", marker = "python_full_version < '3.13'" }, + { name = "torch", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/81/34/39b8b749333db56c0585d7a11fa62a283c087bb1dfc897d69fb8cedbefb1/torchmetrics-1.9.0.tar.gz", hash = "sha256:a488609948600df52d3db4fcdab02e62aab2a85ef34da67037dc3e65b8512faa", size = 581765, upload-time = "2026-03-09T17:41:22.443Z" } wheels = [ @@ -3782,13 +3782,13 @@ name = "trustmark" version = "0.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "einops" }, - { name = "lightning" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, - { name = "omegaconf" }, - { name = "six" }, - { name = "torch" }, - { name = "torchvision" }, + { name = "einops", marker = "python_full_version < '3.13'" }, + { name = "lightning", marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "omegaconf", marker = "python_full_version < '3.13'" }, + { name = "six", marker = "python_full_version < '3.13'" }, + { name = "torch", marker = "python_full_version < '3.13'" }, + { name = "torchvision", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/87/0a/0a4232030c6a62d12b6a02ae73bdce6e99c8532bc8f05a5a2e6ce103da82/trustmark-0.9.1.tar.gz", hash = "sha256:dc79e3fb070f5d94765acf8868a51f50a612cc05b53223cf1e6b605d4ff1e0ae", size = 63949, upload-time = "2026-04-09T08:59:52.472Z" } From f52e40ae245db73600c2cbbf9214e6a597d36598 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Fri, 21 Aug 2026 14:43:39 -0700 Subject: [PATCH 30/44] Name Copilot, Dreamina, and Qwen in C2PA source labels --- docs/watermarking-landscape.md | 4 ++-- .../_internal/constants.py | 19 +++++++++++++------ src/remove_ai_watermarks/identify.py | 1 + src/remove_ai_watermarks/qwen_engine.py | 2 +- .../watermark_registry.py | 4 ++-- tests/test_identify.py | 11 ++++++++++- 6 files changed, 29 insertions(+), 12 deletions(-) diff --git a/docs/watermarking-landscape.md b/docs/watermarking-landscape.md index 90798fb..572a8a1 100644 --- a/docs/watermarking-landscape.md +++ b/docs/watermarking-landscape.md @@ -10,7 +10,7 @@ Who embeds what, and whether it is locally detectable (so we know which gaps are **Root cause and external confirmation (deep-research 2026-06-19, adversarially verified).** This is the SCHEME's ceiling, not our usage — there is no better decoder to adopt. The imwatermark maintainers state verbatim (both the ShieldMnt and Stability-AI READMEs) that the algorithm "cannot guarantee to decode the original watermarks 100% accurately even though we don't apply any attack." Independent measurement (WMAdapter, arXiv:2406.08337 Table 2) puts dwtDct at only **~0.79 bit accuracy on CLEAN images (~38/48 bits — already below our 44 gate)**, collapsing to ~0.50 (chance) under crop/JPEG. Two code-verified + locally-reproduced mechanisms drive the content-dependent failures: (1) the decoder reads each bit as the **highest-magnitude DCT coefficient per block**, so any content coefficient exceeding the encoded target flips the bit; (2) the default embed is in the **YUV chroma channel, which 8-bit-clamps on white/bright pixels** (a +36 chroma delta survives a white-fill round-trip as only +4, ~89% loss) — this is the mechanism behind the bright-flat / minimalist failures and the all-ones degenerate decode. No maintained fork or detector decodes this scheme reliably: the WAVES benchmark (arXiv:2401.08573) relegates DWT-DCT to supplementary appendix G.5 and targets Stable Signature / Tree-Ring / StegaStamp instead; learned encoder/decoder schemes reach ~0.98-0.99 clean but are a DIFFERENT watermark class (not what SDXL/FLUX stamp). `dwtDctSvd` does not help (SDXL embeds `dwtDct`; dwtDctSvd cannot decode it, and its clean accuracy ~0.72 is lower). **Authoritative conclusion: the open DWT-DCT mark cannot be turned from positive-only into a reliable real-world detector; keep it positive-only and rely on C2PA.** (Refuted along the way: that the library is unmaintained, and that it is robust to JPEG but only fails on geometric attacks — both did not survive verification.) Consequence for the FLUX hosted-output question (BFL Playground, FLUX.2 [pro] + FLUX.1 [dev], 2026-06-19): all samples carry the signed C2PA manifest (issuer "Black Forest Labs"); the open DWT-DCT decode returned `None`, but every available FLUX carrier (textured fox AND a minimalist-flat generation) failed the positive control (28/48), so the detector is blind on them and **whether BFL hosted output embeds the open pixel watermark is UNRESOLVED** (an earlier note here wrongly asserted it absent — overstated; a later note blamed "high texture" — also wrong, flat carriers fail too). What IS established: C2PA is the reliable FLUX identifier; the `_BITS_48` pattern is correct (round-trips on chatgpt/firefly/random). Resolving the hosted question needs a hosted FLUX carrier that first passes a >=44/48 positive control, which neither a textured nor a flat prompt produced — low priority (the open mark is only a stripped-metadata fallback). -- **C2PA / IPTC (covered by the issuer/marker scan):** OpenAI, Google, Adobe Firefly, Microsoft (Designer + **Bing Image Creator** — collected 2026-05-24; Bing now runs Microsoft's own **MAI-Image** model, signs C2PA as "Microsoft", NOT OpenAI/DALL-E), **Stability AI** (collected from Brand Studio / DreamStudio successor; signs C2PA as "Stability AI Ltd", no SynthID, no imwatermark on its current Stable Image model — issuer added to `C2PA_ISSUERS`), and **Canva** (Magic Media signs C2PA as "Canva" + `trainedAlgorithmicMedia` with a generic `c2pa-rs` claim generator, no SynthID — issuer `b"Canva"` → "Canva (Magic Media)"; verified samples disproved the earlier assumption that Canva downloads always strip C2PA). Still unsampled: Getty, Shutterstock. Midjourney embeds NO C2PA and no invisible watermark (our `mj-*` sample carried only the IPTC tag). +- **C2PA / IPTC (covered by the issuer/marker scan):** OpenAI, Google, Adobe Firefly, Microsoft (Copilot + Designer; Bing Image Creator collected 2026-05-24 still signs as "Microsoft" and now runs **MAI-Image**, NOT OpenAI/DALL-E), **Stability AI** (collected from Brand Studio / DreamStudio successor; signs C2PA as "Stability AI Ltd", no SynthID, no imwatermark on its current Stable Image model — issuer added to `C2PA_ISSUERS`), and **Canva** (Magic Media signs C2PA as "Canva" + `trainedAlgorithmicMedia` with a generic `c2pa-rs` claim generator, no SynthID — issuer `b"Canva"` → "Canva (Magic Media)"; verified samples disproved the earlier assumption that Canva downloads always strip C2PA). Still unsampled: Getty, Shutterstock. Midjourney embeds NO C2PA and no invisible watermark (our `mj-*` sample carried only the IPTC tag). **Samsung Galaxy AI** signs supported edits with C2PA and may carry the proprietary `genAIType` marker. The registered visible detector covers the @@ -28,7 +28,7 @@ take precedence over issuer attribution. Supported mappings include Higgsfield AI, Topaz Labs Image API, and TikTok Ad Creative Toolbox; an unknown claim generator still falls back to the certificate issuer. -**ByteDance Volcano Engine (Volcengine)** — the cloud behind Doubao / Jimeng — signs its AI image output with a cert from `certificate_center@volcengine.com` + `trainedAlgorithmicMedia` (issuer `b"volcengine"` → "ByteDance (Volcano Engine)", platform "ByteDance (Doubao / Jimeng / Volcano Engine)"); note this is the C2PA-signed surface, distinct from the XMP/PNG TC260 `AIGC` label Doubao also uses. ByteDance's **international brand (BytePlus / Seedream / Seededit)** signs the same content as **"Byteplus Pte. Ltd."**. The bare `volcengine` needle missed it, so BytePlus output was mis-attributed to "Adobe Firefly" through an incidental "Adobe XMP" toolkit string. Issuer `b"Byteplus"` now maps directly to "BytePlus (ByteDance)". ByteDance's consumer app **Dreamina** (the international Jimeng brand) signs as **"Bytedance Pte. Ltd."** with a `Dreamina/x.y` claim generator but, unlike the Volcano Engine surface, ships **no `trainedAlgorithmicMedia`**. Issuer `b"Dreamina"` maps to "ByteDance (Dreamina)" with **`asserts_ai=True`**. Registering the broader **issuer** `b"Bytedance Pte"` was deliberately avoided because that same entity also signs non-AI CapCut edits; keying on the `Dreamina` generator token is precise. +**ByteDance Volcano Engine (Volcengine)** — the cloud behind Doubao / Jimeng — signs its AI image output with a cert from `certificate_center@volcengine.com` + `trainedAlgorithmicMedia` (issuer `b"volcengine"` → "ByteDance (Volcano Engine)", platform "ByteDance (Doubao / Jimeng / Dreamina / Volcano Engine)"); note this is the C2PA-signed surface, distinct from the XMP/PNG TC260 `AIGC` label Doubao also uses. ByteDance's **international brand (BytePlus / Seedream / Seededit)** signs the same content as **"Byteplus Pte. Ltd."**. The bare `volcengine` needle missed it, so BytePlus output was mis-attributed to "Adobe Firefly" through an incidental "Adobe XMP" toolkit string. Issuer `b"Byteplus"` now maps directly to "BytePlus (ByteDance)". ByteDance's consumer app **Dreamina** (the international Jimeng brand) signs as **"Bytedance Pte. Ltd."** with a `Dreamina/x.y` claim generator but, unlike the Volcano Engine surface, ships **no `trainedAlgorithmicMedia`**. Issuer `b"Dreamina"` maps to "ByteDance (Dreamina)" with **`asserts_ai=True`**. Registering the broader **issuer** `b"Bytedance Pte"` was deliberately avoided because that same entity also signs non-AI CapCut edits; keying on the `Dreamina` generator token is precise. - **EXIF/XMP/PNG-text generator tag (caught by `exif_generator`):** **Ideogram** writes EXIF `Make="Ideogram AI"` (collected 2026-05-24 — no C2PA, no SynthID, no imwatermark; the Make tag is the only signal). Additional verified generator stamps include **NovelAI** (`Software`, `Source`, and `Title` PNG text chunks), **Reve** (`Software` or XMP `CreatorTool` = `reve.com`), and **Aphrodite AI** (`Make` or `Software` = `Aphrodite AI`). - **App-export provenance and AIGC JSON:** supported ByteDance-family exports can place a JSON object in EXIF `ImageDescription` or `UserComment`, independently of C2PA or TC260. Exact `product` values for Doubao, Xinghui, and Dreamina are removable product provenance, but do not alone prove that the pixels were generated. Dreamina additionally requires `exportType=generation` for that verdict. A nested Aweme `aigc_type=1` or private ByteDance `aigc_label_type=1` / `2` is an AIGC disclosure; `0` is inconclusive and can occur on a Dreamina generation export. Plain Aweme, retouch, and `lv` exports are preserved. The lower-case private field is deliberately not interpreted as the normative TC260 `Label`, whose values `1` / `2` / `3` mean generated / possibly generated / suspected generated under [GB 45438-2025](https://www.tc260.org.cn/upload/2025-03-15/1742009439794081593.pdf). - **xAI / Grok — its own EXIF signature scheme, NOT C2PA (DETECTED by `metadata.xai_signature`, built 2026-05-26).** diff --git a/src/remove_ai_watermarks/_internal/constants.py b/src/remove_ai_watermarks/_internal/constants.py index 0dcbea7..7f7f416 100644 --- a/src/remove_ai_watermarks/_internal/constants.py +++ b/src/remove_ai_watermarks/_internal/constants.py @@ -65,7 +65,7 @@ def _vendor( # Order is product priority when a manifest mentions more than one organization. C2PA_AI_VENDORS: tuple[C2paAiVendor, ...] = ( - _vendor(b"Microsoft", "Microsoft", "Microsoft (Bing Image Creator / Designer)", "Microsoft"), + _vendor(b"Microsoft", "Microsoft", "Microsoft (Copilot / Designer)", "Microsoft"), _vendor(b"Adobe", "Adobe", "Adobe Firefly", "Adobe"), _vendor( b"OpenAI", @@ -78,18 +78,25 @@ C2PA_AI_VENDORS: tuple[C2paAiVendor, ...] = ( _vendor(b"Google", "Google LLC", "Google (Gemini / Imagen)", "Google", synthid=True), _vendor(b"Stability AI", "Stability AI", "Stability AI (Stable Image / DreamStudio)", "Stability AI"), _vendor(b"Black Forest Labs", "Black Forest Labs", "Black Forest Labs (FLUX)", "Black Forest Labs"), - _vendor(b"volcengine", "ByteDance (Volcano Engine)", "ByteDance (Doubao / Jimeng / Volcano Engine)", "ByteDance"), + _vendor( + b"volcengine", + "ByteDance (Volcano Engine)", + "ByteDance (Doubao / Jimeng / Dreamina / Volcano Engine)", + "ByteDance", + ), _vendor( "北京火山引擎科技有限公司", "ByteDance (Volcano Engine)", - "ByteDance (Doubao / Jimeng / Volcano Engine)", + "ByteDance (Doubao / Jimeng / Dreamina / Volcano Engine)", "ByteDance", ), - _vendor(b"Byteplus", "BytePlus (ByteDance)", "ByteDance (Doubao / Jimeng / Volcano Engine)", "ByteDance"), + _vendor( + b"Byteplus", "BytePlus (ByteDance)", "ByteDance (Doubao / Jimeng / Dreamina / Volcano Engine)", "ByteDance" + ), _vendor( b"Dreamina", "ByteDance (Dreamina)", - "ByteDance (Doubao / Jimeng / Volcano Engine)", + "ByteDance (Doubao / Jimeng / Dreamina / Volcano Engine)", "ByteDance", asserts_ai=True, ), @@ -109,7 +116,7 @@ C2PA_IDENTITY_AI_ORGS = frozenset(vendor.org for vendor in C2PA_AI_VENDORS if ve C2PA_CLAIM_GENERATOR_PLATFORMS: tuple[tuple[str, str], ...] = ( ("adobe_firefly", "Adobe Firefly"), ("firefly", "Adobe Firefly"), - ("dreamina", "ByteDance (Doubao / Jimeng / Volcano Engine)"), + ("dreamina", "ByteDance (Doubao / Jimeng / Dreamina / Volcano Engine)"), ("higgsfield ai", "Higgsfield AI"), ("topaz labs image api", "Topaz Labs"), ("tiktok ad creative toolbox", "TikTok Ad Creative Toolbox"), diff --git a/src/remove_ai_watermarks/identify.py b/src/remove_ai_watermarks/identify.py index c30e9e8..ecdeafc 100644 --- a/src/remove_ai_watermarks/identify.py +++ b/src/remove_ai_watermarks/identify.py @@ -770,6 +770,7 @@ _AI_VENDOR_TOKENS: tuple[tuple[str, str], ...] = ( ("google", "Google"), ("firefly", "Adobe"), ("adobe", "Adobe"), + ("copilot", "Microsoft"), ("bing", "Microsoft"), ("designer", "Microsoft"), ("microsoft", "Microsoft"), diff --git a/src/remove_ai_watermarks/qwen_engine.py b/src/remove_ai_watermarks/qwen_engine.py index a1c1045..641f460 100644 --- a/src/remove_ai_watermarks/qwen_engine.py +++ b/src/remove_ai_watermarks/qwen_engine.py @@ -1,4 +1,4 @@ -"""Qwen (Tongyi Qianwen, Alibaba) visible watermark detector/localizer. +"""Qwen (Alibaba) visible watermark detector/localizer. Qwen stamps its generations with a visible "千问AI生成" text strip in the bottom-right corner -- the explicit AIGC label mandated by China's GB 45438-2025 diff --git a/src/remove_ai_watermarks/watermark_registry.py b/src/remove_ai_watermarks/watermark_registry.py index ed60233..d116d45 100644 --- a/src/remove_ai_watermarks/watermark_registry.py +++ b/src/remove_ai_watermarks/watermark_registry.py @@ -20,7 +20,7 @@ Entries: - ``gemini`` -- Google Gemini / Nano Banana sparkle, bottom-right. - ``doubao`` -- ByteDance Doubao "豆包AI生成" text strip, bottom-right. - ``jimeng`` -- ByteDance Jimeng / Dreamina "★ 即梦AI" wordmark, bottom-right. - - ``qwen`` -- Alibaba Tongyi Qianwen "千问AI生成" text strip, bottom-right. + - ``qwen`` -- Alibaba Qwen "千问AI生成" text strip, bottom-right. - ``kling`` -- Kuaishou Kling "可灵AI 3.0" text strip, bottom-right. - ``yuanbao`` -- Tencent Yuanbao "元宝 / AI生成" two-line mark, bottom-right. - ``samsung`` -- Samsung Galaxy AI "Contenuti generati dall'AI" strip, bottom-left. @@ -651,7 +651,7 @@ _REGISTRY: tuple[KnownMark, ...] = ( "qwen", "Qwen 千问AI生成 text", "bottom-right", - platform="Alibaba Tongyi Qianwen (visible 千问AI生成 mark detected)", + platform="Alibaba Qwen (visible 千问AI生成 mark detected)", tc260_producer_codes=("91440101MA9Y9T4H7A",), ), _text_mark( diff --git a/tests/test_identify.py b/tests/test_identify.py index 25f89cf..706037e 100644 --- a/tests/test_identify.py +++ b/tests/test_identify.py @@ -16,6 +16,7 @@ from unittest.mock import patch import pytest +from remove_ai_watermarks._internal.constants import C2PA_AI_VENDORS, C2PA_CLAIM_GENERATOR_PLATFORMS from remove_ai_watermarks.identify import ( ProvenanceEvidence, ProvenanceReport, @@ -1300,12 +1301,20 @@ class TestVendorOf: def test_registered_vendors_normalize(self): # Regression: these registered C2PA vendors returned None, so their claims never # entered clash detection (a coverage hole). They now normalize to one origin. - assert _vendor_of("ByteDance (Doubao / Jimeng / Volcano Engine)") == "ByteDance" + assert _vendor_of("Microsoft (Copilot / Designer)") == "Microsoft" + assert _vendor_of("Copilot") == "Microsoft" + assert _vendor_of("ByteDance (Doubao / Jimeng / Dreamina / Volcano Engine)") == "ByteDance" assert _vendor_of("Dreamina/1.2") == "ByteDance" assert _vendor_of("Canva (Magic Media)") == "Canva" assert _vendor_of("Black Forest Labs (FLUX)") == "Black Forest Labs" assert _vendor_of("Eleven Labs Inc.") == "ElevenLabs" + def test_bytedance_issuers_share_one_platform(self): + expected = "ByteDance (Doubao / Jimeng / Dreamina / Volcano Engine)" + platforms = {vendor.platform for vendor in C2PA_AI_VENDORS if vendor.needle == "ByteDance"} + assert platforms == {expected} + assert ("dreamina", expected) in C2PA_CLAIM_GENERATOR_PLATFORMS + class TestIntegrityClashesHelper: def test_two_ai_vendors_clash(self): From fa83c1444f87bd0c5be003218eb5b2434a244178 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Tue, 25 Aug 2026 11:33:21 -0700 Subject: [PATCH 31/44] Add geometry-only text manifest schema --- README.md | 5 +- docs/cli.md | 21 +++++---- docs/module-internals.md | 7 +-- docs/python-api.md | 4 ++ .../_internal/text_restoration.py | 43 ++++++++++------- tests/test_text_restoration.py | 47 ++++++++++++++++++- 6 files changed, 94 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index bfb677e..aa7cc6a 100644 --- a/README.md +++ b/README.md @@ -202,8 +202,9 @@ remove-ai-watermarks invisible image.png -o clean.png --force ``` Typography-heavy images can opt into the experimental verified-text post-pass. -It requires manually reviewed strings and line boxes; it never trusts OCR as ground -truth or runs automatically: +It accepts manually reviewed strings and line boxes or an operator-verified +geometry-only manifest; it never treats raw OCR output as ground truth or runs +automatically: ```bash uv tool install --force "remove-ai-watermarks[text-restoration]" diff --git a/docs/cli.md b/docs/cli.md index 947688b..6184790 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -408,7 +408,7 @@ the source with the Qwen VAE, blends 15% of that reconstruction into the normal only the reconstructed glyph cores through source-derived silhouettes. It does not run OCR or choose which strings are correct. -Install the combined extra and run only with a manually reviewed manifest: +Install the combined extra and run only with an operator-verified manifest: ```bash uv tool install --force "remove-ai-watermarks[text-restoration]" @@ -416,11 +416,10 @@ remove-ai-watermarks invisible image.png -o clean.png \ --pipeline qwen-zimage --text-manifest verified-lines.json --force ``` -``verified: true`` may also be set by an automated operator (a service) that -attests machine-verified geometry: stability-gated detector boxes inside sane -caps. The restoration pipeline consumes box/script geometry only - the ``text`` -field is advisory metadata and never reaches the pixels - so what verification -must guarantee is the geometry, and a machine gate can. +``verified: true`` may also be set by an automated operator that attests +machine-verified geometry: stability-gated detector boxes inside sane caps. Such +operators should use the geometry-only schema 2, which carries no transcription +or script metadata. Since 0.27.1 the global 15% Qwen-VAE fidelity-anchor blend is off by default: it was measured to return detector-visible OpenAI SynthID on poster-scale manifests @@ -428,10 +427,12 @@ was measured to return detector-visible OpenAI SynthID on poster-scale manifests 0.27.0 research behavior; text-box fidelity lost by the default is well under one MAE point on the measured fixtures. -The manifest is a JSON object with `schema_version: 1`, `verified: true`, decoded -RGB dimensions, `source_pixel_sha256`, and a non-empty `lines` array. Each line has -an integer `[x1, y1, x2, y2]` box, exact `text`, a non-empty `script`, and an optional -angle from -30 to 30 degrees. Lines must be in top-to-bottom, left-to-right order. +The manifest is a JSON object with `verified: true`, decoded RGB dimensions, +`source_pixel_sha256`, and a non-empty `lines` array. Schema 1 is retained for +manually reviewed annotations: each line has an integer `[x1, y1, x2, y2]` box, +exact `text`, a non-empty `script`, and an optional angle from -30 to 30 degrees. +Schema 2 is geometry-only: each line has the box and optional angle, with no +required `text` or `script`. Lines must be in top-to-bottom, left-to-right order. The hash binds the annotations to decoded RGB geometry and pixels, so metadata-only container changes remain valid while a resized or edited source fails closed. The experimental helper diff --git a/docs/module-internals.md b/docs/module-internals.md index dbde242..b6bfd64 100644 --- a/docs/module-internals.md +++ b/docs/module-internals.md @@ -960,9 +960,10 @@ requires the same provider-oracle and identity evaluation as a model change. [`_internal/text_restoration.py`](../src/remove_ai_watermarks/_internal/text_restoration.py) implements the opt-in `vae-glyphs` stage. A versioned manifest carries manually -reviewed strings and source-space line boxes, plus a SHA-256 over decoded RGB width, -height, and pixels. Validation happens before model loading. The product never treats -OCR confidence as verification. +reviewed strings and source-space line boxes in schema 1, or verified source-space +geometry alone in schema 2, plus a SHA-256 over decoded RGB width, height, and pixels. +Validation happens before model loading. The library never treats OCR confidence as +verification, and geometry-only operators do not need to invent text or script fields. When enabled, `QwenZImagePipeline` reconstructs the source once through its already loaded Qwen VAE, runs the ordinary global and face stages, blends 15% of the VAE diff --git a/docs/python-api.md b/docs/python-api.md index 81351a5..bff2d77 100644 --- a/docs/python-api.md +++ b/docs/python-api.md @@ -615,6 +615,10 @@ donor uses the same overlapping tiles as the global pass. `InvisibleOptions` exp same field for `remove_all`; after a visible-stage edit, the manifest must be built against the staged pixels rather than the pristine source. +Use manifest schema 1 for manually reviewed text plus script metadata. Automated +operators that verify only text-region geometry should emit schema 2 lines with a +`box` and optional `angle`; no placeholder transcription or script is required. + Since 0.27.1 the mode's global 15% Qwen-VAE fidelity-anchor blend is **off by default** (`fidelity_anchor=False`): that whole-frame blend was measured to return detector-visible OpenAI SynthID on poster-scale manifests (official diff --git a/src/remove_ai_watermarks/_internal/text_restoration.py b/src/remove_ai_watermarks/_internal/text_restoration.py index 58431d2..ab995c6 100644 --- a/src/remove_ai_watermarks/_internal/text_restoration.py +++ b/src/remove_ai_watermarks/_internal/text_restoration.py @@ -13,30 +13,33 @@ import cv2 import numpy as np from PIL import Image +from remove_ai_watermarks._internal.schema import require_schema_version + if TYPE_CHECKING: from collections.abc import Sequence from pathlib import Path from numpy.typing import NDArray -TEXT_MANIFEST_SCHEMA = 1 +TEXT_MANIFEST_SCHEMA = 2 +_SUPPORTED_TEXT_MANIFEST_SCHEMAS = frozenset({1, TEXT_MANIFEST_SCHEMA}) FIDELITY_BLEND_ALPHA = 0.15 GLYPH_FEATHER = 0.5 @dataclass(frozen=True) class VerifiedTextLine: - """One operator-verified source line in source-pixel coordinates.""" + """One operator-verified source region in source-pixel coordinates.""" box: tuple[int, int, int, int] - text: str - script: str + text: str | None = None + script: str | None = None angle: float = 0.0 @dataclass(frozen=True) class VerifiedTextManifest: - """Text annotations cryptographically bound to one decoded RGB source.""" + """Verified text regions cryptographically bound to one decoded RGB source.""" source_pixel_sha256: str width: int @@ -55,17 +58,20 @@ def source_pixel_sha256(image: Image.Image) -> str: def load_verified_text_manifest(path: Path, source: Image.Image) -> VerifiedTextManifest: - """Load and validate a manually verified manifest for exactly ``source``.""" + """Load and validate an operator-verified manifest for exactly ``source``.""" try: payload = json.loads(path.read_text(encoding="utf-8")) except (OSError, json.JSONDecodeError) as exc: raise ValueError(f"Cannot read text manifest {path}: {exc}") from exc if not isinstance(payload, dict): raise ValueError("Text manifest must be a JSON object") - if payload.get("schema_version") != TEXT_MANIFEST_SCHEMA: - raise ValueError(f"Text manifest schema_version must be {TEXT_MANIFEST_SCHEMA}") + schema_version = require_schema_version( + payload.get("schema_version"), + contract="text manifest", + supported=_SUPPORTED_TEXT_MANIFEST_SCHEMAS, + ) if payload.get("verified") is not True: - raise ValueError("Text manifest must contain verified=true after manual review") + raise ValueError("Text manifest must contain verified=true after operator verification") rgb = source.convert("RGB") width = _manifest_integer(payload, "width") @@ -82,7 +88,7 @@ def load_verified_text_manifest(path: Path, source: Image.Image) -> VerifiedText raw_lines = payload.get("lines") if not isinstance(raw_lines, list) or not raw_lines: raise ValueError("Text manifest lines must be a non-empty list") - lines = tuple(_load_line(item, width, height, index) for index, item in enumerate(raw_lines)) + lines = tuple(_load_line(item, width, height, index, schema_version) for index, item in enumerate(raw_lines)) if list(lines) != sorted(lines, key=lambda line: (line.box[1], line.box[0])): raise ValueError("Text manifest lines must be in top-to-bottom, left-to-right reading order") return VerifiedTextManifest(actual_hash, width, height, lines) @@ -95,7 +101,7 @@ def _manifest_integer(payload: dict[str, Any], key: str) -> int: return value -def _load_line(item: Any, width: int, height: int, index: int) -> VerifiedTextLine: +def _load_line(item: Any, width: int, height: int, index: int, schema_version: int) -> VerifiedTextLine: if not isinstance(item, dict): raise ValueError(f"Text manifest line {index} must be an object") raw_box = item.get("box") @@ -109,12 +115,15 @@ def _load_line(item: Any, width: int, height: int, index: int) -> VerifiedTextLi x1, y1, x2, y2 = box if not (0 <= x1 < x2 <= width and 0 <= y1 < y2 <= height): raise ValueError(f"Text manifest line {index} box is outside the source dimensions") - text = item.get("text") - script = item.get("script") - if not isinstance(text, str) or not text.strip(): - raise ValueError(f"Text manifest line {index} text must be non-empty") - if not isinstance(script, str) or not script.strip(): - raise ValueError(f"Text manifest line {index} script must be non-empty") + text: str | None = None + script: str | None = None + if schema_version == 1: + text = item.get("text") + script = item.get("script") + if not isinstance(text, str) or not text.strip(): + raise ValueError(f"Text manifest line {index} text must be non-empty") + if not isinstance(script, str) or not script.strip(): + raise ValueError(f"Text manifest line {index} script must be non-empty") angle_value = item.get("angle", 0.0) if isinstance(angle_value, bool) or not isinstance(angle_value, int | float): raise ValueError(f"Text manifest line {index} angle must be numeric") diff --git a/tests/test_text_restoration.py b/tests/test_text_restoration.py index bdc0e2e..6b79bd3 100644 --- a/tests/test_text_restoration.py +++ b/tests/test_text_restoration.py @@ -37,6 +37,17 @@ def _manifest(image: Image.Image) -> dict[str, object]: } +def _geometry_manifest(image: Image.Image) -> dict[str, object]: + return { + "schema_version": 2, + "verified": True, + "source_pixel_sha256": source_pixel_sha256(image), + "width": image.width, + "height": image.height, + "lines": [{"box": [8, 8, 40, 24], "angle": 0.0}], + } + + def test_pixel_hash_ignores_container_metadata(tmp_path) -> None: image = Image.new("RGB", (48, 32), (10, 20, 30)) plain = tmp_path / "plain.png" @@ -63,6 +74,40 @@ def test_verified_manifest_is_bound_to_source_pixels(tmp_path) -> None: assert loaded.lines == (VerifiedTextLine((8, 8, 40, 24), "Exact text", "alphabetic", 0.0),) +def test_geometry_manifest_needs_no_transcription_or_script(tmp_path) -> None: + source = Image.new("RGB", (48, 32), (10, 20, 30)) + path = tmp_path / "regions.json" + path.write_text(json.dumps(_geometry_manifest(source)), encoding="utf-8") + + loaded = load_verified_text_manifest(path, source) + + assert loaded.lines == (VerifiedTextLine((8, 8, 40, 24), angle=0.0),) + + +@pytest.mark.parametrize("field", ["text", "script"]) +def test_schema_one_still_requires_text_metadata(tmp_path, field) -> None: + source = Image.new("RGB", (48, 32), (10, 20, 30)) + payload = _manifest(source) + del payload["lines"][0][field] + path = tmp_path / "lines.json" + path.write_text(json.dumps(payload), encoding="utf-8") + + with pytest.raises(ValueError, match=field): + load_verified_text_manifest(path, source) + + +@pytest.mark.parametrize("schema_version", [True, 0, 3]) +def test_manifest_rejects_unsupported_schema_versions(tmp_path, schema_version) -> None: + source = Image.new("RGB", (48, 32), (10, 20, 30)) + payload = _geometry_manifest(source) + payload["schema_version"] = schema_version + path = tmp_path / "lines.json" + path.write_text(json.dumps(payload), encoding="utf-8") + + with pytest.raises(ValueError, match="Unsupported text manifest schema"): + load_verified_text_manifest(path, source) + + @pytest.mark.parametrize( ("mutation", "message"), [ @@ -120,7 +165,7 @@ def test_restoration_uses_lama_and_qwen_vae_core(monkeypatch) -> None: Image.fromarray(source), Image.fromarray(candidate), Image.fromarray(donor), - (VerifiedTextLine((8, 8, 48, 28), "Exact text", "alphabetic"),), + (VerifiedTextLine((8, 8, 48, 28)),), ) restored = np.asarray(result) From 0d11e4099c685736b3e698545316d37afaa8a202 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Tue, 25 Aug 2026 12:44:05 -0700 Subject: [PATCH 32/44] Fix adaptive text restoration crop bounds --- docs/module-internals.md | 7 +- .../_internal/text_restoration.py | 94 ++++++++++++++++--- tests/test_text_restoration.py | 14 +++ 3 files changed, 102 insertions(+), 13 deletions(-) diff --git a/docs/module-internals.md b/docs/module-internals.md index b6bfd64..a2cc872 100644 --- a/docs/module-internals.md +++ b/docs/module-internals.md @@ -972,7 +972,12 @@ The compositor derives binary source and candidate silhouettes, groups nearby li uses LaMa for the initial and residual-glyph erase passes, paints fresh silhouette edges, then copies the Qwen-VAE core with a 0.5-pixel feather. The evaluation script imports these same mask and compositing helpers so the two implementations cannot -silently drift. +silently drift. Silhouette crops start 12% of line height beyond each horizontal +side, then expand each side independently while a foreground component anchored +inside the detector box still reaches that boundary, up to one line height. They +extend 8% above and 25% below. The anchored-component gate covers clipped leading +flourishes, trailing punctuation, icons, and descenders without walking into +disconnected decoration or background texture. The stage is deliberately narrower than the engine: it rejects `sdxl-zimage`, tiles, resolution caps, humanize, unsharp, and adaptive polish. Those combinations change diff --git a/src/remove_ai_watermarks/_internal/text_restoration.py b/src/remove_ai_watermarks/_internal/text_restoration.py index ab995c6..286e776 100644 --- a/src/remove_ai_watermarks/_internal/text_restoration.py +++ b/src/remove_ai_watermarks/_internal/text_restoration.py @@ -25,6 +25,8 @@ TEXT_MANIFEST_SCHEMA = 2 _SUPPORTED_TEXT_MANIFEST_SCHEMAS = frozenset({1, TEXT_MANIFEST_SCHEMA}) FIDELITY_BLEND_ALPHA = 0.15 GLYPH_FEATHER = 0.5 +GLYPH_SIDE_PAD_RATIO = 0.12 +GLYPH_SIDE_PAD_LIMIT = 1.0 @dataclass(frozen=True) @@ -194,28 +196,41 @@ def restore_verified_text( return Image.fromarray(restored) -def _glyph_crop_box(box: tuple[int, int, int, int], width: int, height: int) -> tuple[int, int, int, int]: - """Widen the detector box so descenders stay inside the silhouette crop. +def _glyph_crop_box( + box: tuple[int, int, int, int], + width: int, + height: int, + left_pad: int, + right_pad: int, +) -> tuple[int, int, int, int]: + """Widen the detector box so glyph edges stay inside the silhouette crop. - Paddle line boxes sit 2-5 px above the true ink bottom on the poster - fixtures. The crop is the box itself, so those pixels never reached the - donor composite. Expand only on Y: 8% up, 25% down, clamped to the frame. + Detector boxes can stop inside a leading flourish or trailing punctuation, + and Paddle line boxes sit 2-5 px above the true ink bottom on the poster + fixtures. The crop is otherwise the box itself, so those pixels never reach + the donor composite. Start 12% sideways, expand each side independently + while foreground reaches its boundary, and pad 8% up and 25% down. """ x1, y1, x2, y2 = box line_h = max(1, y2 - y1) pad_top = max(1, round(line_h * 0.08)) pad_bot = max(2, round(line_h * 0.25)) - return max(0, x1), max(0, y1 - pad_top), min(width, x2), min(height, y2 + pad_bot) + return ( + max(0, x1 - left_pad), + max(0, y1 - pad_top), + min(width, x2 + right_pad), + min(height, y2 + pad_bot), + ) -def source_silhouette_mask( +def _silhouette_crop_mask( source_rgb: NDArray[Any], - box: tuple[int, int, int, int], - angle: float = 0.0, + crop_box: tuple[int, int, int, int], + angle: float, ) -> NDArray[Any]: - """Recover a thresholded glyph shape without retaining source amplitudes.""" + """Threshold one candidate crop so its horizontal boundaries can be tested.""" height, width = source_rgb.shape[:2] - x1, y1, x2, y2 = _glyph_crop_box(box, width, height) + x1, y1, x2, y2 = crop_box gray = cv2.cvtColor(source_rgb[y1:y2, x1:x2], cv2.COLOR_RGB2GRAY) support = np.ones(gray.shape, dtype=np.uint8) if angle: @@ -234,7 +249,7 @@ def source_silhouette_mask( background_luma = float(np.median(values)) else: ring_pad = max(6, min(20, (y2 - y1) // 4)) - rx1, ry1, rx2, ry2 = _clip_box((x1, y1, x2, y2), width, height, pad=ring_pad) + rx1, ry1, rx2, ry2 = _clip_box(crop_box, width, height, pad=ring_pad) context = cv2.cvtColor(source_rgb[ry1:ry2, rx1:rx2], cv2.COLOR_RGB2GRAY) ring = np.ones(context.shape, dtype=bool) ring[y1 - ry1 : y2 - ry1, x1 - rx1 : x2 - rx1] = False @@ -248,6 +263,61 @@ def source_silhouette_mask( else: crop_mask = (gray.astype(np.float32) <= background_luma - threshold).astype(np.uint8) * 255 crop_mask[support == 0] = 0 + return crop_mask + + +def _anchored_components_reach_sides( + crop_mask: NDArray[Any], + anchor_box: tuple[int, int, int, int], +) -> tuple[bool, bool]: + """Whether anchored foreground reaches the left and right crop sides.""" + count, labels, stats, _centroids = cv2.connectedComponentsWithStats(crop_mask, 8) + x1, y1, x2, y2 = anchor_box + anchor_counts = np.bincount(labels[y1:y2, x1:x2].reshape(-1), minlength=count) + anchor_counts[0] = 0 + + def reaches(edge_labels: NDArray[Any]) -> bool: + return any( + label != 0 and anchor_counts[label] >= max(3, round(stats[label, cv2.CC_STAT_AREA] * 0.1)) + for label in np.unique(edge_labels) + ) + + return reaches(labels[y1:y2, :2]), reaches(labels[y1:y2, -2:]) + + +def source_silhouette_mask( + source_rgb: NDArray[Any], + box: tuple[int, int, int, int], + angle: float = 0.0, +) -> NDArray[Any]: + """Recover a thresholded glyph shape without retaining source amplitudes.""" + height, width = source_rgb.shape[:2] + box_x1, box_y1, box_x2, box_y2 = box + line_h = max(1, box_y2 - box_y1) + step = max(2, round(line_h * GLYPH_SIDE_PAD_RATIO)) + limit = max(step, round(line_h * GLYPH_SIDE_PAD_LIMIT)) + left_pad = right_pad = step + while True: + x1, y1, x2, y2 = _glyph_crop_box(box, width, height, left_pad, right_pad) + crop_mask = _silhouette_crop_mask(source_rgb, (x1, y1, x2, y2), angle) + core_y1 = max(0, box_y1 - y1) + core_y2 = min(y2 - y1, box_y2 - y1) + anchor_box = (max(0, box_x1 - x1), core_y1, min(x2 - x1, box_x2 - x1), core_y2) + can_expand_left = x1 > 0 and left_pad < limit + can_expand_right = x2 < width and right_pad < limit + left_anchored, right_anchored = ( + _anchored_components_reach_sides(crop_mask, anchor_box) + if can_expand_left or can_expand_right + else (False, False) + ) + left_touches = can_expand_left and left_anchored + right_touches = can_expand_right and right_anchored + if not left_touches and not right_touches: + break + if left_touches: + left_pad = min(limit, left_pad + step) + if right_touches: + right_pad = min(limit, right_pad + step) result = np.zeros((height, width), dtype=np.uint8) result[y1:y2, x1:x2] = crop_mask return result diff --git a/tests/test_text_restoration.py b/tests/test_text_restoration.py index 6b79bd3..7d3ee54 100644 --- a/tests/test_text_restoration.py +++ b/tests/test_text_restoration.py @@ -183,3 +183,17 @@ def test_silhouette_includes_descender_below_the_detector_box() -> None: assert mask[24, 20] == 255 assert mask[16, 20] == 255 + + +def test_silhouette_includes_glyph_edges_beside_the_detector_box() -> None: + source = np.full((40, 70, 3), 240, dtype=np.uint8) + source[10:22, 20:50] = 20 + source[14:18, 10:20] = 20 # leading flourish, left of the detector box + source[14:18, 50:58] = 20 # punctuation or icon edge, right of the box + source[14:18, 2:5] = 20 # separate decoration must stay outside the crop + + mask = source_silhouette_mask(source, (20, 10, 50, 22)) + + assert mask[16, 12] == 255 + assert mask[16, 56] == 255 + assert mask[16, 3] == 0 From 4beb61463aeb960f5aea7becfe52db6475081c92 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Tue, 25 Aug 2026 13:22:38 -0700 Subject: [PATCH 33/44] Say metadata check and strip results are not a clean verdict --- docs/cli.md | 9 +++++++++ docs/module-internals.md | 4 ++++ src/remove_ai_watermarks/cli.py | 19 +++++++++++++++++++ tests/test_cli.py | 3 +++ tests/test_video.py | 2 ++ 5 files changed, 37 insertions(+) diff --git a/docs/cli.md b/docs/cli.md index 6184790..6f33868 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -157,6 +157,11 @@ remove-ai-watermarks metadata image.png --remove -o clean.png When `-o` is omitted, removal overwrites the source. Standard metadata is kept unless you pass `--remove-all`. +A quiet `--check` or a successful `--remove` is not a clean verdict. The command +only inspects and strips embedded AI metadata; a pixel watermark such as SynthID +has no local decoder once that metadata proxy is gone. `identify` reports the +same limit. + The command also supports the audio and video containers listed in [supported signals](supported-signals.md). ffmpeg must be available for the non-ISOBMFF audio and video path. @@ -225,6 +230,10 @@ remove-ai-watermarks video metadata input.mp4 --check remove-ai-watermarks video metadata input.mp4 --remove -o clean.mp4 ``` +As with the generic `metadata` command, a quiet check or a successful strip is +not a clean verdict: video SynthID is not decoded locally after the metadata +proxy is gone. + Supported containers are MP4, MOV, M4V, WebM, MKV, AVI, and FLV. The operation delegates to the same verified metadata scanner and stripper as the generic `metadata` command, so detection and removal stay in parity. Video and audio diff --git a/docs/module-internals.md b/docs/module-internals.md index a2cc872..0966384 100644 --- a/docs/module-internals.md +++ b/docs/module-internals.md @@ -42,6 +42,10 @@ Important contracts: - `invisible` writes no output when no supported local signal is found, unless `--force` is supplied. - The two no-signal conditions currently share exit code `2`. +- A quiet `metadata --check` and a successful `metadata --remove` (same for + `video metadata`) end by repeating the `identify` limit: the pixel channel is + untouched and a watermark such as SynthID has no local decoder once its + metadata proxy is gone, so neither outcome is a clean verdict. - Hard processing and write failures exit with code `1`. - `all` can still write the completed visible and metadata stages when the diffusion dependencies are unavailable, but exits with code `1` so the diff --git a/src/remove_ai_watermarks/cli.py b/src/remove_ai_watermarks/cli.py index 1f15a3d..5e475a4 100644 --- a/src/remove_ai_watermarks/cli.py +++ b/src/remove_ai_watermarks/cli.py @@ -904,10 +904,27 @@ def cmd_invisible( # ── Metadata operations ── +def _print_metadata_not_a_clean_verdict() -> None: + """Repeat the identify empty-scan limit on metadata check and strip success. + + ``metadata --check`` and ``metadata --remove`` answer a narrower question than + ``identify``: they report embedded AI metadata only. A quiet result used to + stop at "No AI metadata found" / "AI metadata stripped", which readers treat + as a clean-image verdict. The pixel channel is unchanged, and this project has + no local SynthID decoder, so the command must say so in the same words + ``identify`` already uses. + """ + console.print( + " This is not the same as 'clean': a pixel watermark such as SynthID cannot be\n" + " detected here once its metadata proxy is absent." + ) + + def _print_metadata_report(source: Path, has_ai: bool, metadata: dict[str, str]) -> None: """Render one metadata inspection result for the generic and video commands.""" if not has_ai: console.print(f" No AI metadata found in {source.name}") + _print_metadata_not_a_clean_verdict() return console.print(f" Warning: AI metadata detected in {source.name}:") @@ -974,6 +991,7 @@ def cmd_metadata( console.print(" the file could not be decoded, so it was copied through unchanged") raise SystemExit(1) console.print(f" AI metadata stripped -> {out}") + _print_metadata_not_a_clean_verdict() # ── Video pipeline ── @@ -1115,6 +1133,7 @@ def cmd_video_metadata( console.print(f" still present: {', '.join(sorted(result.remaining))}") raise SystemExit(1) console.print(f" AI metadata stripped -> {result.output}") + _print_metadata_not_a_clean_verdict() @cmd_video.command("invisible") diff --git a/tests/test_cli.py b/tests/test_cli.py index bb6a259..8bdc26a 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -629,11 +629,13 @@ class TestMetadataCommand: result = runner.invoke(main, ["metadata", str(tmp_clean_png), "--check"]) assert result.exit_code == 0 assert "No AI metadata" in result.output + assert "not the same as 'clean'" in result.output def test_metadata_check_ai(self, runner, tmp_png_with_ai_metadata): result = runner.invoke(main, ["metadata", str(tmp_png_with_ai_metadata), "--check"]) assert result.exit_code == 0 assert "AI metadata detected" in result.output + assert "not the same as 'clean'" not in result.output def test_metadata_remove(self, runner, tmp_png_with_ai_metadata, tmp_path): output = tmp_path / "stripped.png" @@ -649,6 +651,7 @@ class TestMetadataCommand: ) assert result.exit_code == 0 assert "stripped" in result.output + assert "not the same as 'clean'" in result.output def test_metadata_remove_reports_failure_when_the_strip_was_a_no_op(self, runner, tmp_path): """A file PIL cannot decode is copied through UNCHANGED by the fail-safe. diff --git a/tests/test_video.py b/tests/test_video.py index 2b2cbb9..9600fb3 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -951,6 +951,7 @@ class TestVideoMetadataCli: assert result.exit_code == 0, result.output assert "AI metadata detected" in result.output + assert "not the same as 'clean'" not in result.output def test_remove_reports_output(self, tmp_path: Path): runner = CliRunner() @@ -961,6 +962,7 @@ class TestVideoMetadataCli: assert result.exit_code == 0, result.output assert "AI metadata stripped" in result.output + assert "not the same as 'clean'" in result.output assert C2PA_UUID not in output.read_bytes() def test_rejects_image_input(self, tmp_clean_png: Path): From 44ec203c22bda4a1cb0ead5a2a149cd9762260cf Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Tue, 25 Aug 2026 13:22:49 -0700 Subject: [PATCH 34/44] Drop the stale untiled restriction from text restoration docs --- docs/cli.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cli.md b/docs/cli.md index 6f33868..01821d5 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -447,7 +447,7 @@ container changes remain valid while a resized or edited source fails closed. Th experimental helper `remove_ai_watermarks._internal.text_restoration.source_pixel_sha256` computes it. -This mode is supported only by `qwen-zimage` at native untiled geometry with +This mode is supported only by `qwen-zimage` at native geometry with `humanize=0`, `unsharp=0`, and adaptive polish disabled. `all` also accepts the flag, but its manifest must match the pixels entering the invisible stage; if visible-mark removal changes those pixels, the hash check rejects the run. One oracle verdict does From 8871eb3f67301b5beec17f3471ed01c5dc938d34 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Tue, 25 Aug 2026 13:25:33 -0700 Subject: [PATCH 35/44] Release 0.29.0 pre-commit: 1) maintain.sh - exit 1 on uv-secure, lightning PYSEC-2026-3624 unchanged from 0.28.1, no fixed release exists, vulnerable API unreachable (no load_from_checkpoint in project or trustmark); ruff, pyright src/, and 1386 tests passed separately; 2) /simplify - version bump only; 3) docs sync - no version refs outside pyproject, __init__.py, uv.lock; 4) CLAUDE.md - no change --- pyproject.toml | 2 +- src/remove_ai_watermarks/__init__.py | 2 +- uv.lock | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8dd36ad..3fefa43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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.28.1" +version = "0.29.0" description = "AI watermark remover for visible, invisible, and provenance marks in images and video" readme = "README.md" requires-python = ">=3.11,<3.15" diff --git a/src/remove_ai_watermarks/__init__.py b/src/remove_ai_watermarks/__init__.py index 66f3c0b..e13bea3 100644 --- a/src/remove_ai_watermarks/__init__.py +++ b/src/remove_ai_watermarks/__init__.py @@ -32,7 +32,7 @@ _os.environ.setdefault("TRANSFORMERS_VERBOSITY", "error") _warnings.filterwarnings("ignore", message=r".*ImageProcessorFast.*") -__version__ = "0.28.1" +__version__ = "0.29.0" __all__ = [ "BatchSummary", diff --git a/uv.lock b/uv.lock index 2accb60..f366e50 100644 --- a/uv.lock +++ b/uv.lock @@ -3131,7 +3131,7 @@ wheels = [ [[package]] name = "remove-ai-watermarks" -version = "0.28.1" +version = "0.29.0" source = { editable = "." } dependencies = [ { name = "c2pa-python" }, From 10da166f3ed2e6ac1cb5111e3a020f43d45f7473 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Tue, 25 Aug 2026 13:36:50 -0700 Subject: [PATCH 36/44] Record the release-verification uv cache caveat and the pending lightning bump pre-commit: 1) maintain.sh - docs-only change, last full run minutes ago: exit 1 on the known uv-secure lightning triage, ruff/pyright/1386 tests green; 2) /simplify - docs only; 3) docs sync - these are the doc updates; 4) CLAUDE.md - no change --- docs/known-limitations.md | 6 +++++- docs/release-and-distribution.md | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/known-limitations.md b/docs/known-limitations.md index 137df50..934bde7 100644 --- a/docs/known-limitations.md +++ b/docs/known-limitations.md @@ -407,7 +407,11 @@ remain identical because isolated decoder hits can otherwise be content noise. It accepts Variant P schemas 0-2. Variant Q requires a different model, and schema 3 is rejected at the measured precision threshold. Its NumPy 1.x runtime limits the extra to Python 3.11-3.12; the rest of the package remains supported -through Python 3.14. The calibration history is in +through Python 3.14. Through 0.29.0 the extra also resolves lightning 2.6.5 +(PYSEC-2026-3624, no fixed release yet); the vulnerable `load_from_checkpoint` +path is unreachable here because TrustMark loads its checksummed checkpoints +with plain `torch.load`. Bump lightning and cut a patch release when a fix +ships. The calibration history is in [module internals](module-internals.md#metadata-and-provenance). External AI versus real image classifiers are out of scope. The project diff --git a/docs/release-and-distribution.md b/docs/release-and-distribution.md index 347beb2..917f157 100644 --- a/docs/release-and-distribution.md +++ b/docs/release-and-distribution.md @@ -117,3 +117,9 @@ After publication, verify: - the distribution workflow completed successfully; - the ComfyUI Registry node requires the new library version; - a clean install can run `remove-ai-watermarks --version`. + +A clean-install check run immediately after publication can fail with "no +version of remove-ai-watermarks==X.Y.Z" even though the simple index already +lists it: uv serves its cached pre-release view of the index. Re-run with a +fresh cache (`UV_CACHE_DIR=$(mktemp -d)`) before treating it as a release +failure. From 809d980fb7625613600d6ecbe2990e9d54369ad8 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Tue, 25 Aug 2026 15:00:24 -0700 Subject: [PATCH 37/44] Describe the real Hugging Face Space release mechanism The Space demo is deployed from wiltodelta/raiw-hf-space: its uv lock pins the library version, its sync workflow mirrors the files, and a factory reboot alone re-installs the pinned version rather than upgrading. Document the manual pin bump, the smoke-test CI in that repository, and live-call verification of the running Space. pre-commit: 1) maintain.sh - docs-only change, full gate ran this session with identical code state (exit 1 on the known lightning triage, rest green); 2) /simplify - docs only; 3) docs sync - this is the sync, no other references found; 4) CLAUDE.md - existing Space mention remains accurate --- docs/release-and-distribution.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/release-and-distribution.md b/docs/release-and-distribution.md index 917f157..cb66e8c 100644 --- a/docs/release-and-distribution.md +++ b/docs/release-and-distribution.md @@ -11,7 +11,7 @@ A release is complete only after all four published surfaces are verified: |---|---|---| | PyPI | The `remove-ai-watermarks` wheel and source distribution | `publish.yml` | | Homebrew | The `remove-ai-watermarks` formula in `wiltodelta/homebrew-tap` | `distribute.yml` | -| Hugging Face Space | A factory rebuild of `wiltodelta/remove-ai-watermarks` against the new PyPI release | `distribute.yml` | +| Hugging Face Space | The Space demo deployed on the new release through a requirements-pin bump in `wiltodelta/raiw-hf-space` | `raiw-hf-space` sync workflow; the pin bump is manual, `distribute.yml` only re-installs the pinned version | | ComfyUI Registry | A compatible release of `wiltodelta/ComfyUI-remove-ai-watermarks` with its own node version | `distribute.yml` and the node repository's workflows | The GitHub Release is the trigger and release record for this flow. Conda is @@ -58,9 +58,17 @@ PyPI API token from the repository. waits for the matching source distribution to appear on PyPI, then: - updates the Homebrew tap formula URL and SHA-256; -- triggers a factory rebuild of the Hugging Face Space; +- triggers a factory rebuild of the Hugging Face Space: this re-installs the + version pinned in the Space repo, it does NOT upgrade the demo; - synchronizes, tests, versions, and publishes the ComfyUI nodes. +Upgrading the Hugging Face Space is a separate, manual step: bump +`remove-ai-watermarks[visible,heif]` in `wiltodelta/raiw-hf-space` (`pyproject.toml`, +`uv lock`, and the re-exported `requirements.txt`), then push. That repository's +`sync-to-hf.yml` mirrors the files onto the Space, which rebuilds on the new +pin. Its callback smoke tests run in that repository's CI on every push; they +are the scenario check for the demo surface. + The workflow can also be started manually with an optional version input. If a distribution job fails because a repository or Hugging Face credential is @@ -116,6 +124,10 @@ After publication, verify: - the Homebrew formula points to the new source distribution; - the distribution workflow completed successfully; - the ComfyUI Registry node requires the new library version; +- the Hugging Face Space serves the new release: its `requirements.txt` pin + matches (raw file via the HF API), the runtime stage is `RUNNING` after the + rebuild, and one live `identify` and one live `visible` API call succeed + against the running Space; - a clean install can run `remove-ai-watermarks --version`. A clean-install check run immediately after publication can fail with "no From 97cba11efe91fee8261f802e0a837bb221f6a80c Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Tue, 25 Aug 2026 15:54:26 -0700 Subject: [PATCH 38/44] Read the InvisMark soft-binding algorithm and its signed watermark id Reachable c2pa.soft-binding assertions now surface their exact alg and the bounded printable block value next to the normalized vendor label; a value without its algorithm stays hidden because it cannot be attributed. com.microsoft.invismark.1 uses that value as the pixel-watermark identifier in Microsoft Paint output, so identify names it, metadata carries it, and an InvisMark soft binding keeps the invisible-removal gate fail-safe even after C2PA asset binding goes invalid. Content fingerprints still do not trigger pixel regeneration. Removal is verified against Microsoft's Content Provenance Detection API, which reports Watermark and C2PA separately; the protocol and the pixel-identical control requirement are documented. Implemented in a parallel session; verified, gated, and committed by pi. pre-commit: 1) maintain.sh - exit 1 on the known uv-secure lightning PYSEC-2026-3624 triage (no fix available, unchanged from 0.29.0); ruff, pyright src/, and 1391 tests passed separately; 2) /simplify - single-pass, clean; 3) docs sync - five docs updated by the author session, no remaining references found; 4) CLAUDE.md - invariants recorded in module-internals, no change needed --- docs/cli.md | 5 +- docs/module-internals.md | 9 ++++ docs/supported-signals.md | 13 +++++ docs/verification-plan.md | 12 +++-- docs/watermarking-landscape.md | 3 +- src/remove_ai_watermarks/_internal/c2pa.py | 20 +++++++- src/remove_ai_watermarks/identify.py | 18 ++++++- src/remove_ai_watermarks/metadata.py | 2 + tests/test_identify.py | 30 +++++++++++ tests/test_metadata_internals.py | 59 ++++++++++++++++++++++ 10 files changed, 161 insertions(+), 10 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index 01821d5..11a836e 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -43,7 +43,10 @@ is found, it reports the origin as unknown. It does not claim the image is clean. For C2PA files, the text report shows asset integrity, claim-signature, signer-trust, and signer-validity results separately. An intact claim from an untrusted or expired signer is reported at medium confidence; a failed asset -binding or signature does not confirm the claimed origin. +binding or signature does not confirm the claimed origin. When a structured +C2PA soft binding is present, the report also names its exact algorithm and +signed value; removing the manifest does not remove the referenced pixel +watermark or content fingerprint. Machine readable output: diff --git a/docs/module-internals.md b/docs/module-internals.md index 0966384..439d10b 100644 --- a/docs/module-internals.md +++ b/docs/module-internals.md @@ -392,6 +392,15 @@ manifest names only `c2pa-tool` while a validated ingredient names Dreamina, and Firefly chains that identify `Adobe_Firefly` without repeating a digital source type. Unreachable manifests remain excluded. +Reachable `c2pa.soft-binding*` assertions retain their exact `alg` and bounded, +printable block `value` in addition to the normalized vendor label. A block value +without its algorithm is not surfaced because it cannot be attributed to a +decoder or registry entry. `com.microsoft.invismark.1` uses that value as the +pixel-watermark identifier in Microsoft Paint output. An InvisMark soft binding +keeps the invisible-removal gate fail-safe even when the C2PA asset binding has +since become invalid, because metadata damage does not prove the pixel carrier +disappeared. Content-fingerprint soft bindings do not trigger pixel regeneration. + The SDK default enables trust verification but supplies no production trust anchors. Consequently, an installation without an explicitly maintained C2PA trust bundle reports otherwise valid signer chains as untrusted and keeps their diff --git a/docs/supported-signals.md b/docs/supported-signals.md index 0082b8a..27a8e87 100644 --- a/docs/supported-signals.md +++ b/docs/supported-signals.md @@ -138,6 +138,18 @@ all-media watermark policy, and current OpenAI C2PA carrying an explicit assert SynthID. After provenance metadata is removed, a local negative result is still inconclusive. +Microsoft Paint can name `com.microsoft.invismark.1` in a C2PA soft-binding +assertion. Inspection reports both that exact algorithm and its signed `value`, +which Paint uses as the identifier carried by the pixel watermark. Photos has a +parallel local writer path, but no public output sample was available for this +work. Metadata stripping removes the embedded manifest, not the pixel carrier. +The project has no validated local InvisMark decoder. 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. The diffusion profiles have not been broadly certified against that +oracle, so a control-positive, output-negative pair is still a per-file result, +not a universal guarantee. + For MP4, MOV, and M4V, `video invisible` or the explicit `video all --invisible` option can regenerate the video through a VAE and strip source metadata. The shipped profile is oracle-certified, but it is not a local @@ -158,6 +170,7 @@ not a universal clean verdict. | Google Gemini | Sparkle | Diffusion regeneration for SynthID | C2PA and related source signals | | Google Veo video | Veo diamond and legacy text | Oracle-certified VAE removal for SynthID | C2PA and related source signals | | OpenAI image generators | None registered | Diffusion regeneration for supported invisible signals | C2PA and generator provenance | +| Microsoft Paint and Photos | None registered | External Microsoft oracle for InvisMark; no validated local decoder | Paint C2PA soft-binding algorithm and identifier | | Stable Diffusion and SDXL | None registered | Diffusion regeneration; optional open decoder | Embedded parameters and text metadata | | FLUX | None registered | Diffusion regeneration; optional open decoder | C2PA for supported sources | | Adobe Firefly | None registered | Optional TrustMark Variant P decoder | C2PA | diff --git a/docs/verification-plan.md b/docs/verification-plan.md index f249757..fa2113f 100644 --- a/docs/verification-plan.md +++ b/docs/verification-plan.md @@ -186,10 +186,14 @@ report recall from the detector-sampled set. ## Tier D -- external oracles (manual, not automatable here) -SynthID removal cannot be verified locally by design -- no public decoder exists. Each -vendor has its own oracle and it covers only that vendor's content: `openai.com/verify` for -OpenAI (more accessible, the automation candidate), the Gemini app for Google (manual, -rate-limited). A quiet metadata proxy is **not** proof the pixel watermark is gone. +Proprietary watermark removal cannot be verified locally by design -- no public decoder +exists. Each vendor has its own oracle and it covers only that vendor's content: +`openai.com/verify` for OpenAI, the Gemini app for Google, and Microsoft's +[Content Provenance Detection API](https://learn.microsoft.com/en-us/azure/ai-services/content-safety/how-to/how-to-provenance-detection) +for InvisMark. The Microsoft API reports `Watermark` and `C2PA` separately. It therefore +needs a pixel-identical metadata-stripped control: the control must lose `C2PA` while +remaining `Watermark`-positive before a candidate's negative result can be attributed to +pixel regeneration. A quiet metadata proxy is **not** proof the pixel watermark is gone. Scope honestly: this tier certifies strength floors on a handful of images per vendor, and that is all it can do. See `docs/synthid.md`. diff --git a/docs/watermarking-landscape.md b/docs/watermarking-landscape.md index 572a8a1..eeafcbb 100644 --- a/docs/watermarking-landscape.md +++ b/docs/watermarking-landscape.md @@ -65,7 +65,8 @@ payloads. Removal remuxes either container through ffmpeg with stream copy. DWT-DCT decoder only applies when the producing pipeline actually ran its 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, and locally DECODE the one open scheme, Adobe TrustMark (`trustmark_detector`); the rest (Digimarc/Imatag/Steg.AI/...) stay name-only (proprietary decoders). +- **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 - **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 diff --git a/src/remove_ai_watermarks/_internal/c2pa.py b/src/remove_ai_watermarks/_internal/c2pa.py index 0cb3918..3c6f873 100644 --- a/src/remove_ai_watermarks/_internal/c2pa.py +++ b/src/remove_ai_watermarks/_internal/c2pa.py @@ -307,7 +307,9 @@ def c2pa_info_has_invalid_credential(info: dict[str, Any]) -> bool: def c2pa_info_has_removal_hint(info: dict[str, Any]) -> bool: """Return whether a C2PA AI or watermark claim should keep removal fail-safe.""" - return bool(info.get("ai_source_kind") or info.get("synthid_watermark")) + soft_bindings = info.get("soft_binding_vendors") + has_invismark = isinstance(soft_bindings, list) and "Microsoft InvisMark" in soft_bindings + return bool(info.get("ai_source_kind") or info.get("synthid_watermark") or has_invismark) def _has_suffix(codes: list[str], suffixes: tuple[str, ...]) -> bool: @@ -390,6 +392,7 @@ def _structured_manifest_fields(store: dict[str, Any]) -> dict[str, Any]: actions: list[str] = [] source_types: list[str] = [] soft_binding_algorithms: list[str] = [] + soft_binding_values: list[str] = [] claim_generator_asserts_ai = False def add_tool_matches(value: str, *, asserts_ai: bool = False) -> None: @@ -431,9 +434,18 @@ def _structured_manifest_fields(store: dict[str, Any]) -> dict[str, Any]: label = assertion.get("label") data = assertion.get("data") if isinstance(label, str) and label.startswith("c2pa.soft-binding") and isinstance(data, dict): - algorithm = cast("dict[object, object]", data).get("alg") + soft_binding = cast("dict[object, object]", data) + algorithm = soft_binding.get("alg") if isinstance(algorithm, str): soft_binding_algorithms.append(algorithm) + blocks = soft_binding.get("blocks") + if isinstance(blocks, list): + for block_value in cast("list[object]", blocks): + if not isinstance(block_value, dict): + continue + value = cast("dict[object, object]", block_value).get("value") + if isinstance(value, str) and value.isprintable() and len(value) <= 256: + soft_binding_values.append(value) if not (isinstance(label, str) and label.startswith("c2pa.actions") and isinstance(data, dict)): continue action_values = cast("dict[object, object]", data).get("actions") @@ -491,11 +503,15 @@ def _structured_manifest_fields(store: dict[str, Any]) -> dict[str, Any]: info["synthid_watermark"] = synthid_verdict(", ".join(synthid)) if soft_binding_algorithms: + soft_binding_algorithms = list(dict.fromkeys(soft_binding_algorithms)) algorithms = "\n".join(soft_binding_algorithms).encode() soft_bindings = soft_binding_vendors_in(algorithms) if soft_bindings: info["soft_binding_vendors"] = soft_bindings info["soft_binding"] = ", ".join(soft_bindings) + info["soft_binding_algorithm"] = ", ".join(soft_binding_algorithms) + if soft_binding_values: + info["soft_binding_value"] = ", ".join(dict.fromkeys(soft_binding_values)) return info diff --git a/src/remove_ai_watermarks/identify.py b/src/remove_ai_watermarks/identify.py index ecdeafc..8b2c75f 100644 --- a/src/remove_ai_watermarks/identify.py +++ b/src/remove_ai_watermarks/identify.py @@ -145,6 +145,10 @@ _C2PA_CLOUD_CAVEAT = ( "It marks Content Credentials, not AI origin: the cloud manifest may describe a " "human edit, and reading it needs a network fetch this tool does not make." ) +_SOFT_BINDING_CAVEAT = ( + "Removing the embedded C2PA manifest does not remove its soft binding: the named " + "watermark or fingerprint may remain in the pixels and re-link the asset to provenance." +) _SAMSUNG_GENAI_CAVEAT = ( "Samsung's genAIType marker shows a Galaxy AI editing tool (Generative Edit, " "Sketch to Image, ...) touched the image; it is an undocumented proprietary " @@ -388,6 +392,8 @@ def evidence_from_metadata_record( "actions", "synthid_watermark", "soft_binding", + "soft_binding_algorithm", + "soft_binding_value", ): if key in c2pa_info: ai_metadata.setdefault(key, str(c2pa_info[key])) @@ -1269,12 +1275,20 @@ def _identify_from_evidence( # 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) 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") + soft_binding_details = "; ".join( + str(value) for value in (soft_binding, soft_binding_algorithm, soft_binding_value) if value + ) signals.append( Signal( - "soft_binding", f"C2PA soft binding: {soft_binding}", "high" if c2pa_level == "verified" else "medium" + "soft_binding", + f"C2PA soft binding: {soft_binding_details}", + "high" if c2pa_level == "verified" else "medium", ) ) - watermarks.append(f"Forensic watermark soft binding ({soft_binding})") + watermarks.append(f"Forensic watermark soft binding ({soft_binding_details})") + caveats.append(_SOFT_BINDING_CAVEAT) # ── IPTC "Made with AI" (Meta etc.), only meaningful without C2PA ─ iptc = any(m in head for m in IPTC_AI_MARKERS) diff --git a/src/remove_ai_watermarks/metadata.py b/src/remove_ai_watermarks/metadata.py index 84286eb..8d8f528 100644 --- a/src/remove_ai_watermarks/metadata.py +++ b/src/remove_ai_watermarks/metadata.py @@ -1160,6 +1160,8 @@ def get_ai_metadata(image_path: Path) -> dict[str, str]: "actions", "synthid_watermark", "soft_binding", + "soft_binding_algorithm", + "soft_binding_value", "c2pa_validation_source", "c2pa_validation_state", "c2pa_integrity", diff --git a/tests/test_identify.py b/tests/test_identify.py index 706037e..36aa041 100644 --- a/tests/test_identify.py +++ b/tests/test_identify.py @@ -1144,6 +1144,36 @@ class TestIdentifySoftBinding: assert any("Digimarc" in w for w in r.watermarks) assert any(s.name == "soft_binding" for s in r.signals) + def test_invismark_signal_lists_signed_watermark_id(self, tmp_path: Path): + watermark_id = "83424621-03cb-40e3-9808-a9fae837156d" + record = { + "c2pa_store": { + "active_manifest": "paint", + "manifests": { + "paint": { + "assertions": [ + { + "label": "c2pa.soft-binding", + "data": { + "alg": "com.microsoft.invismark.1", + "blocks": [{"scope": "the entire image", "value": watermark_id}], + }, + } + ] + } + }, + } + } + evidence = evidence_from_metadata_record(record, path=tmp_path / "paint.png") + + report = identify_from_evidence(evidence) + + assert evidence.ai_metadata["soft_binding_value"] == watermark_id + signal = next(signal for signal in report.signals if signal.name == "soft_binding") + assert "com.microsoft.invismark.1" in signal.detail + assert watermark_id in signal.detail + assert any("may remain in the pixels" in caveat for caveat in report.caveats) + class TestIdentifyIptcAi: """IPTC 2025.1 AISystemUsed drives an AI verdict + platform attribution.""" diff --git a/tests/test_metadata_internals.py b/tests/test_metadata_internals.py index 5a4e6ef..7f0120a 100644 --- a/tests/test_metadata_internals.py +++ b/tests/test_metadata_internals.py @@ -13,6 +13,7 @@ from PIL import Image from remove_ai_watermarks._internal.c2pa import ( _parse_c2pa_chunk, c2pa_info_from_manifest_store, + c2pa_info_has_removal_hint, cbor_text_after, extract_c2pa_chunk, extract_c2pa_info, @@ -209,6 +210,64 @@ class TestC2PA: assert info["ai_tool"] == "Dreamina" assert info["c2pa_identity_ai"] is True + def test_structured_invismark_exposes_algorithm_and_watermark_id(self): + watermark_id = "83424621-03cb-40e3-9808-a9fae837156d" + store = { + "active_manifest": "paint", + "manifests": { + "paint": { + "assertions": [ + { + "label": "c2pa.soft-binding", + "data": { + "alg": "com.microsoft.invismark.1", + "blocks": [ + { + "scope": "the entire image", + "value": watermark_id, + } + ], + }, + } + ] + } + }, + } + + info = c2pa_info_from_manifest_store(store) + + assert info["soft_binding"] == "Microsoft InvisMark" + assert info["soft_binding_algorithm"] == "com.microsoft.invismark.1" + assert info["soft_binding_value"] == watermark_id + + def test_soft_binding_value_requires_its_algorithm(self): + store = { + "active_manifest": "broken", + "manifests": { + "broken": { + "assertions": [ + { + "label": "c2pa.soft-binding", + "data": {"blocks": [{"value": "not-attributable"}]}, + } + ] + } + }, + } + + assert "soft_binding_value" not in c2pa_info_from_manifest_store(store) + + def test_soft_binding_keeps_invisible_removal_fail_safe(self): + assert c2pa_info_has_removal_hint({"soft_binding_vendors": ["Microsoft InvisMark"]}) is True + + def test_content_fingerprint_does_not_trigger_invisible_removal(self): + info = { + "soft_binding": "Adobe (content fingerprint)", + "soft_binding_vendors": ["Adobe (content fingerprint)"], + } + + assert c2pa_info_has_removal_hint(info) is False + def test_invalid_ingredient_does_not_taint_active_validation_or_supply_claims(self): store = { "active_manifest": "update", From c5764661b4e929057cd804762c511f7d26311bec Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Tue, 25 Aug 2026 16:52:27 -0700 Subject: [PATCH 39/44] Release 0.30.0 pre-commit: 1) maintain.sh - exit 1 on uv-secure, lightning PYSEC-2026-3624 unchanged from 0.29.0, no fixed release exists, vulnerable API unreachable (no load_from_checkpoint in project or trustmark); ruff, pyright src/, and 1391 tests passed separately; 2) /simplify - version bump only; 3) docs sync - no version refs outside pyproject, __init__.py, uv.lock; 4) CLAUDE.md - no change --- pyproject.toml | 2 +- src/remove_ai_watermarks/__init__.py | 2 +- uv.lock | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3fefa43..691d530 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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.29.0" +version = "0.30.0" description = "AI watermark remover for visible, invisible, and provenance marks in images and video" readme = "README.md" requires-python = ">=3.11,<3.15" diff --git a/src/remove_ai_watermarks/__init__.py b/src/remove_ai_watermarks/__init__.py index e13bea3..f4c4d02 100644 --- a/src/remove_ai_watermarks/__init__.py +++ b/src/remove_ai_watermarks/__init__.py @@ -32,7 +32,7 @@ _os.environ.setdefault("TRANSFORMERS_VERBOSITY", "error") _warnings.filterwarnings("ignore", message=r".*ImageProcessorFast.*") -__version__ = "0.29.0" +__version__ = "0.30.0" __all__ = [ "BatchSummary", diff --git a/uv.lock b/uv.lock index f366e50..c154bfc 100644 --- a/uv.lock +++ b/uv.lock @@ -3131,7 +3131,7 @@ wheels = [ [[package]] name = "remove-ai-watermarks" -version = "0.29.0" +version = "0.30.0" source = { editable = "." } dependencies = [ { name = "c2pa-python" }, From 251ba9ac79608d030d210112d1123c8c8f080b1b Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Tue, 25 Aug 2026 17:17:17 -0700 Subject: [PATCH 40/44] Record the ComfyUI sync PyPI CDN race and its rerun recovery pre-commit: 1) maintain.sh - docs-only change, full gate ran this session with identical code state (exit 1 on the known lightning triage, rest green); 2) /simplify - docs only; 3) docs sync - this is the sync; 4) CLAUDE.md - no change --- docs/release-and-distribution.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/release-and-distribution.md b/docs/release-and-distribution.md index cb66e8c..71cea0f 100644 --- a/docs/release-and-distribution.md +++ b/docs/release-and-distribution.md @@ -135,3 +135,9 @@ version of remove-ai-watermarks==X.Y.Z" even though the simple index already lists it: uv serves its cached pre-release view of the index. Re-run with a fresh cache (`UV_CACHE_DIR=$(mktemp -d)`) before treating it as a release failure. + +The ComfyUI sync run can fail the same way on its own: it resolves the version +from the PyPI JSON API (which updates first) but installs the test dependency +with pip against the simple index, whose CDN edges lag by minutes. Rerun the +failed `distribute.yml` comfyui job once the simple index lists the release; +nothing about the release itself is wrong. From f83da5aee4a9fc4c79c432015e42caf1cfc559d7 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Tue, 25 Aug 2026 18:52:26 -0700 Subject: [PATCH 41/44] Expose Microsoft InvisMark removal signal --- README.md | 4 + docs/module-internals.md | 4 +- docs/python-api.md | 5 + docs/supported-signals.md | 16 +- pyproject.toml | 2 +- src/remove_ai_watermarks/__init__.py | 2 +- src/remove_ai_watermarks/_internal/c2pa.py | 10 +- src/remove_ai_watermarks/identify.py | 12 + tests/test_identify.py | 3 + tests/test_metadata_internals.py | 6 +- uv.lock | 405 ++++++++++++--------- 11 files changed, 273 insertions(+), 196 deletions(-) diff --git a/README.md b/README.md index aa7cc6a..2c87b15 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,10 @@ removal. | Run visible, invisible, and metadata removal | `all` | Recommended | | Process a directory | `batch` | Depends on mode | +Microsoft Paint and Photos InvisMark declarations are routed automatically to +pixel regeneration. The `all` command removes both the hidden pixel watermark and +its linked C2PA manifest; metadata stripping alone removes only the manifest. + ## Installation modes | Need | Install | diff --git a/docs/module-internals.md b/docs/module-internals.md index 439d10b..99d5f85 100644 --- a/docs/module-internals.md +++ b/docs/module-internals.md @@ -399,7 +399,9 @@ decoder or registry entry. `com.microsoft.invismark.1` uses that value as the pixel-watermark identifier in Microsoft Paint output. An InvisMark soft binding keeps the invisible-removal gate fail-safe even when the C2PA asset binding has since become invalid, because metadata damage does not prove the pixel carrier -disappeared. Content-fingerprint soft bindings do not trigger pixel regeneration. +disappeared. `identify` retains the generic `soft_binding` signal for schema-1 +compatibility and adds `invismark` as the stable pixel-removal signal. +Content-fingerprint soft bindings do not trigger pixel regeneration. The SDK default enables trust verification but supplies no production trust anchors. Consequently, an installation without an explicitly maintained C2PA diff --git a/docs/python-api.md b/docs/python-api.md index bff2d77..3bbe758 100644 --- a/docs/python-api.md +++ b/docs/python-api.md @@ -233,6 +233,11 @@ the schema 1 serializer remains available for rolling upgrades. Asking a release an unsupported schema raises `ValueError` rather than silently returning another shape. +Microsoft InvisMark declarations emit both the compatible generic `soft_binding` +signal and the additive `invismark` signal. Consumers should use `invismark` to route +the image through pixel removal; the generic signal also covers content fingerprints +that must not trigger regeneration. + A record carries metadata regions, not the primary coded-pixel stream: marker segments before the JPEG scan, every PNG chunk except `IDAT`, RIFF chunks except the coded image, the ISOBMFF provenance boxes, the container's trailer, the parsed EXIF tags the diff --git a/docs/supported-signals.md b/docs/supported-signals.md index 27a8e87..2c48f16 100644 --- a/docs/supported-signals.md +++ b/docs/supported-signals.md @@ -140,15 +140,17 @@ is still inconclusive. Microsoft Paint can name `com.microsoft.invismark.1` in a C2PA soft-binding assertion. Inspection reports both that exact algorithm and its signed `value`, -which Paint uses as the identifier carried by the pixel watermark. Photos has a -parallel local writer path, but no public output sample was available for this -work. Metadata stripping removes the embedded manifest, not the pixel carrier. -The project has no validated local InvisMark decoder. Microsoft's official +which Paint uses as the identifier carried by the pixel watermark, and emits an +additive `invismark` signal so callers can select pixel removal without parsing +the generic `soft_binding` detail. Photos uses a parallel local writer path. +Metadata stripping removes only the embedded manifest; `invisible` and `all` +guarantee the supported InvisMark removal contract by regenerating the pixel +layer as well. The project has no validated local InvisMark decoder, so local +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. The diffusion profiles have not been broadly certified against that -oracle, so a control-positive, output-negative pair is still a per-file result, -not a universal guarantee. +separately; a control-positive, output-negative pair is the available per-file +verification path. For MP4, MOV, and M4V, `video invisible` or the explicit `video all --invisible` option can regenerate the video through a VAE and strip diff --git a/pyproject.toml b/pyproject.toml index 691d530..8c37886 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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.30.0" +version = "0.30.1" description = "AI watermark remover for visible, invisible, and provenance marks in images and video" readme = "README.md" requires-python = ">=3.11,<3.15" diff --git a/src/remove_ai_watermarks/__init__.py b/src/remove_ai_watermarks/__init__.py index f4c4d02..18d355e 100644 --- a/src/remove_ai_watermarks/__init__.py +++ b/src/remove_ai_watermarks/__init__.py @@ -32,7 +32,7 @@ _os.environ.setdefault("TRANSFORMERS_VERBOSITY", "error") _warnings.filterwarnings("ignore", message=r".*ImageProcessorFast.*") -__version__ = "0.30.0" +__version__ = "0.30.1" __all__ = [ "BatchSummary", diff --git a/src/remove_ai_watermarks/_internal/c2pa.py b/src/remove_ai_watermarks/_internal/c2pa.py index 3c6f873..b0e6f32 100644 --- a/src/remove_ai_watermarks/_internal/c2pa.py +++ b/src/remove_ai_watermarks/_internal/c2pa.py @@ -305,11 +305,15 @@ def c2pa_info_has_invalid_credential(info: dict[str, Any]) -> bool: return info.get("c2pa_integrity") == "invalid" or info.get("c2pa_signature") == "invalid" +def c2pa_info_has_invismark(info: dict[str, Any]) -> bool: + """Return whether parsed C2PA info declares Microsoft InvisMark.""" + soft_bindings = info.get("soft_binding_vendors") + return isinstance(soft_bindings, list) and "Microsoft InvisMark" in soft_bindings + + def c2pa_info_has_removal_hint(info: dict[str, Any]) -> bool: """Return whether a C2PA AI or watermark claim should keep removal fail-safe.""" - soft_bindings = info.get("soft_binding_vendors") - has_invismark = isinstance(soft_bindings, list) and "Microsoft InvisMark" in soft_bindings - return bool(info.get("ai_source_kind") or info.get("synthid_watermark") or has_invismark) + return bool(info.get("ai_source_kind") or info.get("synthid_watermark") or c2pa_info_has_invismark(info)) def _has_suffix(codes: list[str], suffixes: tuple[str, ...]) -> bool: diff --git a/src/remove_ai_watermarks/identify.py b/src/remove_ai_watermarks/identify.py index 8b2c75f..fc38e1e 100644 --- a/src/remove_ai_watermarks/identify.py +++ b/src/remove_ai_watermarks/identify.py @@ -29,6 +29,7 @@ from typing import TYPE_CHECKING, Any, cast from remove_ai_watermarks._internal.c2pa import ( c2pa_info_from_manifest_store, c2pa_info_has_invalid_credential, + c2pa_info_has_invismark, c2pa_info_has_removal_hint, cbor_text_after, extract_c2pa_info, @@ -1287,6 +1288,17 @@ def _identify_from_evidence( "high" if c2pa_level == "verified" else "medium", ) ) + if c2pa_info_has_invismark(info): + # Keep the generic soft-binding row for schema-1 compatibility, and add + # the pixel-removal target as its own stable signal for clients that need + # to select diffusion without parsing human-readable detail. + signals.append( + Signal( + "invismark", + f"Microsoft InvisMark pixel watermark: {soft_binding_details}", + "high" if c2pa_level == "verified" else "medium", + ) + ) watermarks.append(f"Forensic watermark soft binding ({soft_binding_details})") caveats.append(_SOFT_BINDING_CAVEAT) diff --git a/tests/test_identify.py b/tests/test_identify.py index 36aa041..3d4c7b4 100644 --- a/tests/test_identify.py +++ b/tests/test_identify.py @@ -1172,6 +1172,9 @@ class TestIdentifySoftBinding: signal = next(signal for signal in report.signals if signal.name == "soft_binding") assert "com.microsoft.invismark.1" in signal.detail assert watermark_id in signal.detail + invismark = next(signal for signal in report.signals if signal.name == "invismark") + assert "com.microsoft.invismark.1" in invismark.detail + assert watermark_id in invismark.detail assert any("may remain in the pixels" in caveat for caveat in report.caveats) diff --git a/tests/test_metadata_internals.py b/tests/test_metadata_internals.py index 7f0120a..877a699 100644 --- a/tests/test_metadata_internals.py +++ b/tests/test_metadata_internals.py @@ -13,6 +13,7 @@ from PIL import Image from remove_ai_watermarks._internal.c2pa import ( _parse_c2pa_chunk, c2pa_info_from_manifest_store, + c2pa_info_has_invismark, c2pa_info_has_removal_hint, cbor_text_after, extract_c2pa_chunk, @@ -258,7 +259,10 @@ class TestC2PA: assert "soft_binding_value" not in c2pa_info_from_manifest_store(store) def test_soft_binding_keeps_invisible_removal_fail_safe(self): - assert c2pa_info_has_removal_hint({"soft_binding_vendors": ["Microsoft InvisMark"]}) is True + info = {"soft_binding_vendors": ["Microsoft InvisMark"]} + + assert c2pa_info_has_invismark(info) is True + assert c2pa_info_has_removal_hint(info) is True def test_content_fingerprint_does_not_trigger_invisible_removal(self): info = { diff --git a/uv.lock b/uv.lock index c154bfc..ead926a 100644 --- a/uv.lock +++ b/uv.lock @@ -431,76 +431,117 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.9" +version = "3.5.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/3f/143b048436775b0f76ac3eec145c019e8173ccc2885c8f20319b996d5e83/charset_normalizer-3.5.1.tar.gz", hash = "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3", size = 171764, upload-time = "2026-08-15T08:20:44.807Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/e3/85ec501f206fb049259288c1f3506e53876937fb00edb47009348e66756b/charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5", size = 317075, upload-time = "2026-07-07T14:32:56.021Z" }, - { url = "https://files.pythonhosted.org/packages/c3/69/2a5385192e67175f7d8bd5ce4f57c24bc956439adeae5c13a99aa28a53d1/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2", size = 213837, upload-time = "2026-07-07T14:32:57.78Z" }, - { url = "https://files.pythonhosted.org/packages/b3/46/03ddc7da576d814fe0a36dd1f0fd3258e95404b4b2e3c026b7923d7e133f/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a", size = 235503, upload-time = "2026-07-07T14:32:59.205Z" }, - { url = "https://files.pythonhosted.org/packages/4e/6e/de0229a7ef40f6f9d28a837eebf4ec47bdca5dab4e900c84f22919af636a/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4773092f8019072343a7447203308b176e10199920eb02d6195e81bbb3274c29", size = 229944, upload-time = "2026-07-07T14:33:00.803Z" }, - { url = "https://files.pythonhosted.org/packages/a5/34/49b9060e8418b14fb5cba9cf6bfb383111e2538a03a1fb18e66a95aeb3d5/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c", size = 221276, upload-time = "2026-07-07T14:33:02.199Z" }, - { url = "https://files.pythonhosted.org/packages/44/95/80282cce0fae9c3061203d723ee87da996aed79679e65d8935050ee7ca1f/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:c0323c9daef75ef2e5083624b4585018a0c9d5e3b40f607eed81a311270b934b", size = 205260, upload-time = "2026-07-07T14:33:03.698Z" }, - { url = "https://files.pythonhosted.org/packages/0c/74/2f62c8821b969ea3bd67cc2e6976834f48ca5d12664d2559ebcd9bcfbed7/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:871ff67ea1aad4dfd91736464934d56b32dac49f9fbe16cddba36198a7b3a0db", size = 217786, upload-time = "2026-07-07T14:33:05.12Z" }, - { url = "https://files.pythonhosted.org/packages/d9/8d/feabb82cb49fcad14515b1d7d1ca4787b0da7fc723a212bf89bc9e0fac52/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67830fc78e67501f47bb950471b2dcb9b35b140084429318e862895a8e89c993", size = 216798, upload-time = "2026-07-07T14:33:06.629Z" }, - { url = "https://files.pythonhosted.org/packages/a5/ff/c946d63bc3786d5b84d960b0f7ab7e25b828486a946b5aa997625bcaf6a6/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d92613ec25e43b05f042302531ec0f00b8445190e43325880cbd6ab7c2581da", size = 206429, upload-time = "2026-07-07T14:33:08.006Z" }, - { url = "https://files.pythonhosted.org/packages/af/ba/5e5007c370702f85d2ef75791fac7943ed41e080364a673b20142e430e3e/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:280081916dc341820640489a66e4696049401ef1cf6dd672f672e70ad915aca3", size = 223066, upload-time = "2026-07-07T14:33:09.783Z" }, - { url = "https://files.pythonhosted.org/packages/83/d5/9096aa3cf532dfad237861544eb47a0f20d5adbf1039760fed8eaae935d9/charset_normalizer-3.4.9-cp311-cp311-win32.whl", hash = "sha256:ac351b3b8014eead140e77e9717e2992c6bbe30b63bc3422422eb84865412e3d", size = 150456, upload-time = "2026-07-07T14:33:11.217Z" }, - { url = "https://files.pythonhosted.org/packages/ed/a1/e29995109e455dc8eff8d0fac6ae509be39561318a7cfeac5d33ad029213/charset_normalizer-3.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1", size = 161410, upload-time = "2026-07-07T14:33:12.743Z" }, - { url = "https://files.pythonhosted.org/packages/4f/8d/1569f4d0032d6ba2a4fe4591c35bf87868c600c41a71eb5c2e1ffa8464c2/charset_normalizer-3.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:1d22856ffbe153a602df38e4a5464f0b748a54002e0d69ac6d2ad0a197cc99ec", size = 152649, upload-time = "2026-07-07T14:33:14.173Z" }, - { url = "https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0", size = 319300, upload-time = "2026-07-07T14:33:15.666Z" }, - { url = "https://files.pythonhosted.org/packages/ec/96/5d9364e3342d69f3a045e1777bc47c85c383e6e9466d561b33fdb419d1f9/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9", size = 215802, upload-time = "2026-07-07T14:33:17.031Z" }, - { url = "https://files.pythonhosted.org/packages/4b/4c/5361f9aa7f2cb58d94f2ab831b3d493f69efb1d239654b4744e3c09527cb/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44", size = 237171, upload-time = "2026-07-07T14:33:18.576Z" }, - { url = "https://files.pythonhosted.org/packages/50/78/ce342ca4ff30b2eb49fe6d9578df85974f90c67d294113e94efdd9664cbd/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9", size = 233075, upload-time = "2026-07-07T14:33:20.084Z" }, - { url = "https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd", size = 224256, upload-time = "2026-07-07T14:33:21.747Z" }, - { url = "https://files.pythonhosted.org/packages/87/3a/ad914516df7e358a81aae018caa5e0470ba827fa6d763b1d2e87d920a5f6/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84", size = 208784, upload-time = "2026-07-07T14:33:23.313Z" }, - { url = "https://files.pythonhosted.org/packages/d7/74/3c12f9755717dfe5c5c87da63f35d765fa0c00382ec26bf23f7fae34f2ba/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b", size = 219928, upload-time = "2026-07-07T14:33:24.814Z" }, - { url = "https://files.pythonhosted.org/packages/33/9a/895095b83e7907abd6d3d99aad3a38ad0d9686cc186cb0c94c24320fe63e/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde", size = 218489, upload-time = "2026-07-07T14:33:26.42Z" }, - { url = "https://files.pythonhosted.org/packages/a1/34/ef5c05f412f42520d7709b7d3784d19640839eb7366ded1755511585429f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39", size = 210267, upload-time = "2026-07-07T14:33:27.952Z" }, - { url = "https://files.pythonhosted.org/packages/83/dc/9b29fa4412b318bf3bfea985c35d67eb55e04b59a7c3f2237168b0e0be6f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62", size = 226030, upload-time = "2026-07-07T14:33:29.397Z" }, - { url = "https://files.pythonhosted.org/packages/0e/42/6dbc00b8cd16011691203e33570fa42ed5746599a2e878112d16eab403a3/charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642", size = 151185, upload-time = "2026-07-07T14:33:30.781Z" }, - { url = "https://files.pythonhosted.org/packages/80/cc/f920afd1a23c58ccd53c1d36085a71893a4737ff5e66e0371efab6809850/charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0", size = 162557, upload-time = "2026-07-07T14:33:32.176Z" }, - { url = "https://files.pythonhosted.org/packages/f0/e6/0386d43a261ff4e4b30c5857af7df877254b46bec7b9d1b74b6bf969a90b/charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2", size = 152665, upload-time = "2026-07-07T14:33:33.711Z" }, - { url = "https://files.pythonhosted.org/packages/b2/06/97ec2aeae780b31d742b6352218b43841a6871e2564578ca522dce4a45c3/charset_normalizer-3.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614", size = 317688, upload-time = "2026-07-07T14:33:35.408Z" }, - { url = "https://files.pythonhosted.org/packages/d0/39/8ff066c672434225f8d25f8b739f992af250944392173dcc88362681c9bf/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698", size = 214982, upload-time = "2026-07-07T14:33:36.996Z" }, - { url = "https://files.pythonhosted.org/packages/92/8f/3a47a3667c83c2df9483d91644c6c107de3bf8874aa1793da9d3012eb986/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b", size = 236460, upload-time = "2026-07-07T14:33:38.536Z" }, - { url = "https://files.pythonhosted.org/packages/f1/60/b22cdbee7e4013dab8b0d7647fc6181120fbbbc8f7025c226d15bd5a47fc/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9", size = 232003, upload-time = "2026-07-07T14:33:40.059Z" }, - { url = "https://files.pythonhosted.org/packages/ea/f8/72eb13dcabe7257035cea8aefd922caad2f110d252bf9f67c4c2ca763aee/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33", size = 223149, upload-time = "2026-07-07T14:33:41.631Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3e/faee8f9de92b14ee1198e9163252bb15efee7301b31256a3b6d9ebfdd0dd/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63", size = 207901, upload-time = "2026-07-07T14:33:43.209Z" }, - { url = "https://files.pythonhosted.org/packages/3a/25/45f30093ae27dd7b92a793b61882a38685f993700113ca36e0c9c14965e1/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0", size = 219176, upload-time = "2026-07-07T14:33:44.725Z" }, - { url = "https://files.pythonhosted.org/packages/48/18/c8f397329c35e32f6a837e488986f4ae03bd2abebc453b48714991630c2f/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe", size = 217356, upload-time = "2026-07-07T14:33:46.192Z" }, - { url = "https://files.pythonhosted.org/packages/86/7e/5ce0bba863470fd1902d5e5843968951bddf38abe4742fc97116ef4598b3/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35", size = 209614, upload-time = "2026-07-07T14:33:47.705Z" }, - { url = "https://files.pythonhosted.org/packages/6c/ef/2473d3c4d869155be4af1191111d59c4d5c4e0173026f7e85b176e23bf65/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8", size = 224991, upload-time = "2026-07-07T14:33:49.238Z" }, - { url = "https://files.pythonhosted.org/packages/d0/a3/53ddae3db108a088156aa8ddfafd411ebbc1340f48c5573f697b27f69a39/charset_normalizer-3.4.9-cp313-cp313-win32.whl", hash = "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9", size = 150622, upload-time = "2026-07-07T14:33:50.711Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ef/6953a77c7cf2c2ff9998e6f575ab3e380119f100223381565a4f94c1f836/charset_normalizer-3.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115", size = 161947, upload-time = "2026-07-07T14:33:52.197Z" }, - { url = "https://files.pythonhosted.org/packages/6e/fb/d560d1d1555debbfe7849d9cac6145c1b537709d79576bf22557ed803b82/charset_normalizer-3.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012", size = 152594, upload-time = "2026-07-07T14:33:53.486Z" }, - { url = "https://files.pythonhosted.org/packages/7e/8d/496817fa0944239ecae662dd57ea765cfeaec6a735f9f025d4b7b72e7143/charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380", size = 317253, upload-time = "2026-07-07T14:33:54.994Z" }, - { url = "https://files.pythonhosted.org/packages/2b/f9/ef4a69ea338ad3c0deceea0f5f7d2380ae8b52132b06d652cb0d2cd86706/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9", size = 215898, upload-time = "2026-07-07T14:33:56.334Z" }, - { url = "https://files.pythonhosted.org/packages/8c/e7/5ddfd76fc061eb52de219658a4aa431cbacadf0a0219c8854f00da50d289/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4", size = 236718, upload-time = "2026-07-07T14:33:57.9Z" }, - { url = "https://files.pythonhosted.org/packages/49/ba/768fa3f36048d81c477a0ce61f813bc1454d80917ccfe550abd9f44f5e24/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a", size = 232519, upload-time = "2026-07-07T14:33:59.811Z" }, - { url = "https://files.pythonhosted.org/packages/f4/c4/b3e049d2aa3766180c78507110543d9d50894cc97f57de543f1be521dcdc/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046", size = 223143, upload-time = "2026-07-07T14:34:01.517Z" }, - { url = "https://files.pythonhosted.org/packages/19/79/55c32d06d76ae4feafe053f061f3e3ab70bcf19f4007797ce8c3efda7830/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81", size = 206742, upload-time = "2026-07-07T14:34:03.04Z" }, - { url = "https://files.pythonhosted.org/packages/10/e0/47c079dd82d217c807479cd59ffd30af56307ea31c108b75758970459ad3/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917", size = 219191, upload-time = "2026-07-07T14:34:04.657Z" }, - { url = "https://files.pythonhosted.org/packages/42/ab/b9bc2e77d6b44a7e46ef62ec5cac1c9a6ba7b9135a5d560f002696ec9995/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41", size = 218328, upload-time = "2026-07-07T14:34:06.115Z" }, - { url = "https://files.pythonhosted.org/packages/f1/78/c9c71d599f5aa2d42bcdd35cbbd46d7f535351a57e40ff7d8e5a7e219401/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1", size = 207406, upload-time = "2026-07-07T14:34:07.554Z" }, - { url = "https://files.pythonhosted.org/packages/f6/39/c914445c321a845097ce4f6ac7de9a18228a77b766272125a1ce00d851eb/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf", size = 225157, upload-time = "2026-07-07T14:34:09.061Z" }, - { url = "https://files.pythonhosted.org/packages/9b/f2/c0d4b8508565a36bc5c624e88ed297f5b0b1095011034d7f5b83a69908b5/charset_normalizer-3.4.9-cp314-cp314-win32.whl", hash = "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48", size = 151095, upload-time = "2026-07-07T14:34:10.901Z" }, - { url = "https://files.pythonhosted.org/packages/49/fd/a1d26144398c67486422a72bf5812cda22cb4ccfcd95a290fb41ceb4b8e2/charset_normalizer-3.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b", size = 162796, upload-time = "2026-07-07T14:34:12.47Z" }, - { url = "https://files.pythonhosted.org/packages/20/95/d75e82f8ce9fd323ebf059c16c9aadefb22a1ecde13b7840b35835e4886c/charset_normalizer-3.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519", size = 153334, upload-time = "2026-07-07T14:34:14.044Z" }, - { url = "https://files.pythonhosted.org/packages/00/5e/17398df3a139985ba9d11ed072531986f408c8fca952835ef1ab1820c02b/charset_normalizer-3.4.9-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198", size = 338848, upload-time = "2026-07-07T14:34:15.688Z" }, - { url = "https://files.pythonhosted.org/packages/cd/91/7253a32e86b7e1d1239b1b36ba6dd0f021a21107ab33054b53119cc083b9/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32", size = 223022, upload-time = "2026-07-07T14:34:17.248Z" }, - { url = "https://files.pythonhosted.org/packages/cb/32/2e64bd2be10e89c61e57ebe6a93fd98ae88eb7ebe414b5121f22c96c69eb/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632", size = 241590, upload-time = "2026-07-07T14:34:18.813Z" }, - { url = "https://files.pythonhosted.org/packages/3d/ef/d96ec496cfea0c21db43b0ad03891308b02388d054cc902cf0e5a1ad6a88/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf", size = 239584, upload-time = "2026-07-07T14:34:20.52Z" }, - { url = "https://files.pythonhosted.org/packages/d4/ce/9af95f7876194bd7a14e3dfe4a4de2e0bff02666a3910d72beafd06cc297/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990", size = 230224, upload-time = "2026-07-07T14:34:22.189Z" }, - { url = "https://files.pythonhosted.org/packages/52/94/af74dde74a3996bd959c350709bfe50e297823d70a8c1cbd54b838880863/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d", size = 212667, upload-time = "2026-07-07T14:34:23.857Z" }, - { url = "https://files.pythonhosted.org/packages/ee/f0/f1c4fe746c395922961b5916ed1d7d6e7d4c84851d19ed43cc89980ec953/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e", size = 227179, upload-time = "2026-07-07T14:34:25.586Z" }, - { url = "https://files.pythonhosted.org/packages/e4/56/6c745619ac397e8871e2bcd3cea1eec86b877488f33888b3aef5c3ed506e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c", size = 225372, upload-time = "2026-07-07T14:34:27.212Z" }, - { url = "https://files.pythonhosted.org/packages/78/ad/98aae8630ac71f16711968e38a5acfecce41b778bf2f0312851020f565a8/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2", size = 215222, upload-time = "2026-07-07T14:34:28.774Z" }, - { url = "https://files.pythonhosted.org/packages/f7/40/9593d54209765207a7f11073c06494c1721e4ca4a0a426c597679bf7f91e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534", size = 231958, upload-time = "2026-07-07T14:34:30.345Z" }, - { url = "https://files.pythonhosted.org/packages/b1/27/693ee5e8a18191eb38647360c51cd505013e2bd3b366aa43fd5344c21e3c/charset_normalizer-3.4.9-cp314-cp314t-win32.whl", hash = "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226", size = 155580, upload-time = "2026-07-07T14:34:31.884Z" }, - { url = "https://files.pythonhosted.org/packages/80/3f/bd97d3d9c613013d07cb7733d299385b41df37f0471310f5a73dc359f0b8/charset_normalizer-3.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177", size = 167620, upload-time = "2026-07-07T14:34:33.438Z" }, - { url = "https://files.pythonhosted.org/packages/3d/c6/eee9dca4439b1061f76373f06ea855678cc4a64c1c3c90b50e479edbb8eb/charset_normalizer-3.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501", size = 158037, upload-time = "2026-07-07T14:34:35.018Z" }, - { url = "https://files.pythonhosted.org/packages/98/2b/f97f1c193fb855c345d678f5077d6926034db0722df74c8f057020e05a25/charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5", size = 64538, upload-time = "2026-07-07T14:34:56.993Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b6/034f6802e9c3f6418966cfabb7db8c9252cc2429c5098f41cc43af804149/charset_normalizer-3.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eda059b6bc8bc0812d626fd91a7ce01bf583df0a61296eff390fd94141a34e30", size = 363585, upload-time = "2026-08-15T08:16:46.646Z" }, + { url = "https://files.pythonhosted.org/packages/d5/fa/6a7e2a7c4b5451912b8c417732df79574354443592a88d616de03da66ae5/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa2bb0b37202dca27175591f761108b5d34096ade1191ffe4808bdf6b1571488", size = 251189, upload-time = "2026-08-15T08:16:48.287Z" }, + { url = "https://files.pythonhosted.org/packages/a4/c8/ab42b07cfd82e919f427fcfaa7c41abae8242833ad1aad66d42bae40b669/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b2b1b3fa5670c127b246df1d0c059defd41f689a868a3b9d79df9b1cac42d22", size = 239724, upload-time = "2026-08-15T08:16:49.67Z" }, + { url = "https://files.pythonhosted.org/packages/e7/80/b9348b5d3041209f98b4cdad7655766369233f1d533f4f4f7558e9717bec/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6e5e4d73d588ca5ed09df1b7dcd1b203d1df3c542e3f50d126c947d432b10731", size = 280078, upload-time = "2026-08-15T08:16:51.228Z" }, + { url = "https://files.pythonhosted.org/packages/82/38/083a24028304bc85bb9e376fed801178423dcbb67495f73b6ea0624e1894/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b54e7e13267d49ffbfe68e25b3cbd774dab38fa37238f71265e91b36146eb21c", size = 276650, upload-time = "2026-08-15T08:16:52.625Z" }, + { url = "https://files.pythonhosted.org/packages/0d/35/731ac04aa0a097fc1c97f0994c375bdb230c6c96619db794208fe664e9ce/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7b742bf31c88566b4bb6335a7f393bb322e580b6bb98df7bd0c25e6e3519ce8", size = 262325, upload-time = "2026-08-15T08:16:54.085Z" }, + { url = "https://files.pythonhosted.org/packages/f5/28/c2028e7021fb89c6e56868ed0e387b8e9aa811abdd2ab3208d6578d2c930/charset_normalizer-3.5.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ba32c4d2abf1d2fe7cf27d280f4cca5664233b0f885549c7761719eb977f486", size = 261140, upload-time = "2026-08-15T08:16:55.604Z" }, + { url = "https://files.pythonhosted.org/packages/28/f0/0c0ceec6d98b7daa62e361e418135d59685811d79ba11529aad5cdf15e84/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0722590aabf9dc6a6c0343d523c05458fa2b5047dbe6302fd526bb570600753f", size = 252791, upload-time = "2026-08-15T08:16:57.103Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3e/48f4cd187b1c33189d86039e9cbe4f92c05454175504b44ff81806d4d1bf/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:aa1099b956fb795e686d073568f6dc002a0bb89765ea6d5b055dd7d9bf1b116c", size = 240730, upload-time = "2026-08-15T08:16:58.418Z" }, + { url = "https://files.pythonhosted.org/packages/42/85/f9e22af69af67c54cce42be9455d9c81294f918b4ccc454db01f66efcac2/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd6c173f04743d483881bffa1478d5a4624475b8cd1d2194956a75548e191c18", size = 280791, upload-time = "2026-08-15T08:16:59.918Z" }, + { url = "https://files.pythonhosted.org/packages/fd/4c/9044135f42127630b6fa742feb51256353f6ab87a78f2fdd1de3de955a7f/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f298e218441525d3794428b4c8b8fb8662c6d3ea79925d4807ee6b9a96a3bca5", size = 259598, upload-time = "2026-08-15T08:17:01.421Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ed/1dd7cfebb4e75812934c49ca3b79757d11948053f7937ab7070c151f3c55/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6e2912d4babbc65196ac13c2f53468dc57fb8b9c25ef913e8c59ddf7c6dc0e1b", size = 278217, upload-time = "2026-08-15T08:17:02.782Z" }, + { url = "https://files.pythonhosted.org/packages/bf/eb/239c84503cc9e3ba6eb34686a24bc66e84f3924efdd7e38e751a19f6bc10/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3d27167433c0d5f18dc850f07d0b3816221984fecdc405d6c157a6f0b8f8e9e6", size = 263417, upload-time = "2026-08-15T08:17:04.216Z" }, + { url = "https://files.pythonhosted.org/packages/37/ab/4e4510e1e288478e2c8333131d1c1382382ba8cd2165053c79e39d1da961/charset_normalizer-3.5.1-cp311-cp311-win32.whl", hash = "sha256:ac00177c4831ffa650f8609e4bdddd5fe09c03b1c0c47acece7e6ea20421598b", size = 181774, upload-time = "2026-08-15T08:17:05.58Z" }, + { url = "https://files.pythonhosted.org/packages/e3/57/32f0ccea59e8612057c61d6fd22ef2cb63cca93c9fe594094919696ac170/charset_normalizer-3.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:f9b1e28d0e8dbfa858abdba91d6b547beaf2df1a59bec6da6faae7b96a4991a9", size = 206653, upload-time = "2026-08-15T08:17:07.075Z" }, + { url = "https://files.pythonhosted.org/packages/17/d4/b65c433fc521e58b5f54293982a5e51c05cb5f2dd3f1c7a6acb65b75324e/charset_normalizer-3.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:ae31a1a1db2ee6cc2942fccaf695c934bc7f3db9f2133a3fef1f367cf1a4ab10", size = 185630, upload-time = "2026-08-15T08:17:08.502Z" }, + { url = "https://files.pythonhosted.org/packages/30/27/78873dc8b6a56357517b74b6bb9568b80450e7bb4f6ef7e3fa9d22aa0bd7/charset_normalizer-3.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f", size = 344456, upload-time = "2026-08-15T08:17:10.072Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4c/be49ada26b1f0232d57aa89bbebf997a5cc2332a5616b6eca26ff680044d/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa", size = 238530, upload-time = "2026-08-15T08:17:11.563Z" }, + { url = "https://files.pythonhosted.org/packages/76/84/6f1290fa07ae6978d3960caa3eb1b8019bf9284ab7c2297b00c099ef4250/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369", size = 230200, upload-time = "2026-08-15T08:17:12.919Z" }, + { url = "https://files.pythonhosted.org/packages/e7/a0/47b18adeed31c8f16ba9700f32c1b18594cfa09f47eb672a488c273c22bf/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e6621fb2a4988d6e53eedc455e5903e2679f3967b8acb3d639f1b63c14a2e893", size = 262222, upload-time = "2026-08-15T08:17:14.571Z" }, + { url = "https://files.pythonhosted.org/packages/38/fe/341861ac118dae06f3ec0eb487488af52128f2ef2faf0b11003944d22259/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7c0c10730342b0c9b35dd1d619beb8214e520bd96a1f870f452680b238aab3e0", size = 258951, upload-time = "2026-08-15T08:17:16.158Z" }, + { url = "https://files.pythonhosted.org/packages/6f/89/bb5108dc6c3651dca963f2b0a3ba19bbcb370c94e1b6d3e0e844a58e6dca/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08", size = 248801, upload-time = "2026-08-15T08:17:17.683Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ba/ef83ae3aca816393decfa3530976f38a79812d707b80b580ac33b83f9877/charset_normalizer-3.5.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9f8405c2c758532c74fed975dbee57be1f31a6e865c031870c79a6ed3212ada", size = 244070, upload-time = "2026-08-15T08:17:19.191Z" }, + { url = "https://files.pythonhosted.org/packages/f6/0b/c5292a2462d69b7378ea89793bbb5b2b6fcf6f7dd6d1667f9619094ad553/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:96fef3e886d6a9874b14f27fc193fbdc69d5d8035783d86aa4e1cea594e695f9", size = 240110, upload-time = "2026-08-15T08:17:20.547Z" }, + { url = "https://files.pythonhosted.org/packages/46/22/111e5be3b740d5c2a5bfcedb3d237b6591e5c2e82ae9d6ffcb121fe0909c/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5d8531a6569d025f68e2321e7638fb7978f23db58e5f69f56913837aae03816e", size = 232836, upload-time = "2026-08-15T08:17:21.895Z" }, + { url = "https://files.pythonhosted.org/packages/f9/d2/d2aad6fe0dbb44b194bf3becb60f5a0ac48446ade999a47fe7bb41eb09a7/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aae2ee51122d3ae968a3837d97dc24a0aeebb0dea23694422cd172bd30017cd6", size = 262712, upload-time = "2026-08-15T08:17:23.727Z" }, + { url = "https://files.pythonhosted.org/packages/35/5a/337e4663a5eae6de99db940ee8066d4145caafb61327db62deda15313cce/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7235dc28fc6dd9d832ac7c7bce95367dedb85929f17368a0c2bee1e080b9acbf", size = 242977, upload-time = "2026-08-15T08:17:25.157Z" }, + { url = "https://files.pythonhosted.org/packages/ca/85/f82f8a92e31c7519410e2e1afdc630f28ec47490ce2c09a11c1a43cbb459/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4abdc5f9ad448c1ecbfae2974b820535d6bc6e7eef63babbab3d81cf46968c71", size = 260207, upload-time = "2026-08-15T08:17:26.602Z" }, + { url = "https://files.pythonhosted.org/packages/b7/52/643d11ffd60e9ac2fd1fb87e167a19285b9eefeff4a40e63c87cbfbeab36/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba501e667c17d8411f98e67a022d9604ef179aff0e459b7e292c796837c13573", size = 250562, upload-time = "2026-08-15T08:17:27.971Z" }, + { url = "https://files.pythonhosted.org/packages/62/16/46556278c2168d12df9da7fede5dc6fc70e60301b26a82bbeec238c9cfe3/charset_normalizer-3.5.1-cp312-cp312-win32.whl", hash = "sha256:cfa1c0cc3a8f9f53f1243a5a99ac36fd003880199383b37672e86ddda9cb07e2", size = 178507, upload-time = "2026-08-15T08:17:29.277Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7a/4c6c298171e6b3e745633180ff59350fc0ca0db1ffd28df1e369e0579f71/charset_normalizer-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3617ac3cfd8b9888f145ad89dd6e692285834b0201c6074a5eeaad3fd4d668c2", size = 200551, upload-time = "2026-08-15T08:17:30.668Z" }, + { url = "https://files.pythonhosted.org/packages/cd/d7/eb95a042f0dd22e304b0b6472b154f3546a1a039a9ee89ccb2a7f61591fc/charset_normalizer-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:88e85ab89cb822c1e635f51d6d32e488f94e002e70e2f492bdb8b945543f345a", size = 180700, upload-time = "2026-08-15T08:17:32.028Z" }, + { url = "https://files.pythonhosted.org/packages/bc/61/2cb6ad133dbbb449fa2d37ccae973232f4827e799af258d15e589a3d1e9e/charset_normalizer-3.5.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:4f298bdadb8f0b9e5672877f647d1be9373ef5320c9e2f049795e26cad28b6a9", size = 211584, upload-time = "2026-08-15T08:17:33.597Z" }, + { url = "https://files.pythonhosted.org/packages/18/57/a305c968be1ca13f3dd1b32f445877e97addf55d80b65c7cb35fac82b777/charset_normalizer-3.5.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:88ca277405c2d3b71c4e1c2ee0e7966e807bcba86a69d11e19ba199d18ae4491", size = 223359, upload-time = "2026-08-15T08:17:35.022Z" }, + { url = "https://files.pythonhosted.org/packages/09/0a/d3646670292ce8d8f8cc11ac067d44885e697a5591f57a9221128da5e7b3/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9362dd90aa7dab48c0054a21187791ccf05473f7dba5d92b8033ae62164675e7", size = 194464, upload-time = "2026-08-15T08:17:36.452Z" }, + { url = "https://files.pythonhosted.org/packages/de/93/d51ec556e01042fed6f993ea859311bc7917b466684182fbbceb6ca24762/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:977cdbd483a9cff38179bea4fd754289a6f2195c7abd414aba85410b3e66cc5e", size = 197676, upload-time = "2026-08-15T08:17:37.819Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a0/562247944386f7d4ef94467e84876600cc1e0f1b93239aaa9213d2bc3cbd/charset_normalizer-3.5.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e90251c0c7bdd54a100a0dce3c07b7e637278c93af29dbf78ebb89a58c4bac7d", size = 340473, upload-time = "2026-08-15T08:17:39.303Z" }, + { url = "https://files.pythonhosted.org/packages/31/e7/1d994be1b93d41e9502b8b0460eaa88a1dd8df335df415db87d6c3e91ab2/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94d78ecec2605a8d0398b0f365d5f12a63248438516f5dac536a5eff7337df4a", size = 240156, upload-time = "2026-08-15T08:17:40.66Z" }, + { url = "https://files.pythonhosted.org/packages/09/53/27923ce5cc6cbccb832037b27dca98882d9c53e9b69e866bbbef4aae7fc8/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d59b75732e9b6f27388e10c14b0259cc5f2e48c78627d185e6a177b58ad3cffe", size = 228246, upload-time = "2026-08-15T08:17:42.003Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/5a97e84d63af1d55c07439cb80e56d99a8efb4295700eb4e18c0d1615d2c/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0d929fc574b4d6fd9e7c0f5c2ede8716a41911923aa7fa5fce38e0818aa4a1ac", size = 263660, upload-time = "2026-08-15T08:17:43.627Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c2/071575791dcc88316c0a9a65ce38897a82e4cfe4a325f0f7fe1b1ac47bcf/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:394fea06235c8543390050ed5f529187074b029fb027213f6c46ac11ab5d950e", size = 260354, upload-time = "2026-08-15T08:17:45.094Z" }, + { url = "https://files.pythonhosted.org/packages/fb/af/63240b0c0248c075c2535a1f1bd992821d8251b9f173abc13329661d09e4/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62b55f6722735a6c472f88361cde6640608773d9443cebdbb51abf436a1fcdd3", size = 250638, upload-time = "2026-08-15T08:17:46.496Z" }, + { url = "https://files.pythonhosted.org/packages/4d/66/70dfad64f15be09c15ccfee81330a7e515895dbe296dd23114e9a231268a/charset_normalizer-3.5.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa48b1b63d639f9483e0633e092f5851e2348c352f1f9bb6c8182f87884ef876", size = 244583, upload-time = "2026-08-15T08:17:47.963Z" }, + { url = "https://files.pythonhosted.org/packages/c0/24/ef36367d38b9ddd4bccbf72888c342e8de1f5ae506fa0b2dcf970e2732a1/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c71fb0d56c920c269cd3e2e3fe7c610e3f1fdb21a6ce60efa6430ff63676cea6", size = 242038, upload-time = "2026-08-15T08:17:49.481Z" }, + { url = "https://files.pythonhosted.org/packages/db/ab/55e683ba0fff2e43adafc10daa3001eac90fdaa419a97227d5a7067eedde/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:485a0d363cafefcd2538a73c7c838daa2035f09b2c9f9b5e3133f80c6aeb84c2", size = 233677, upload-time = "2026-08-15T08:17:50.845Z" }, + { url = "https://files.pythonhosted.org/packages/bd/67/0f40eaf8d1b6e7cf15e82382a2965efaca787fc1c2794b7021d37aaf5036/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c0ea61a470e070686aa30892fed79e297d2c8d0ab46b8bcdf027d38c51da591", size = 264491, upload-time = "2026-08-15T08:17:52.61Z" }, + { url = "https://files.pythonhosted.org/packages/5c/64/12b4c2a11ee8df4fcc518c78b0d93e3a92bd3d5253d1617ce74ff0e8c7ef/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:90b7481fb62fbe172c558bc6fd1c4c98d82004a54a7551f20e11ac9bf0b8708c", size = 245196, upload-time = "2026-08-15T08:17:54.023Z" }, + { url = "https://files.pythonhosted.org/packages/37/2e/651d910af6d0fba325eee1cda37ec5443462ed25360e666c144166eb6091/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:35fe081843b35aad20ffeccec3eeffbe637b15d14f3fb22cc1b59cd8ec17e93c", size = 261660, upload-time = "2026-08-15T08:17:55.491Z" }, + { url = "https://files.pythonhosted.org/packages/90/c6/b09e05e6db7f64338e0dc067c79577b1138da86c1e38369096851d96be88/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd0350afdc3aabd5576f60ea109228bd5538139713c7b094c5cd27c73a98bc6f", size = 252618, upload-time = "2026-08-15T08:17:57.025Z" }, + { url = "https://files.pythonhosted.org/packages/76/4e/362d4f9fdcdf5556fb2aa3ce7d4a58ebce03ed1ff03aa1d9aca8d02f13f3/charset_normalizer-3.5.1-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:9d9a0dc7cbe9bec24c3f767c9122c41fe5a1bc43f47cd099d00d393e09769de4", size = 140362, upload-time = "2026-08-15T08:17:58.425Z" }, + { url = "https://files.pythonhosted.org/packages/b4/d4/703be739b26acce318bd29eb3b25b7209e1b1f527f9eae3d1f1f01fdde2b/charset_normalizer-3.5.1-cp313-cp313-win32.whl", hash = "sha256:d63600d620ad0064c3a748b950ac5ea38a80190e5498532efefa4b7b3f1da1f3", size = 177755, upload-time = "2026-08-15T08:18:00.037Z" }, + { url = "https://files.pythonhosted.org/packages/8a/33/56d97ade41c8db611e727168c52ae46c9224c362ec28d4b65d7e9869e8da/charset_normalizer-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:aea996a6aba25260827c9ea511d1addfde2da9eb686ac961838509086188b7e6", size = 199295, upload-time = "2026-08-15T08:18:01.506Z" }, + { url = "https://files.pythonhosted.org/packages/5b/75/5b20dd1e6573a01a08158fe104104fa2c8abf941745596954185726cd46c/charset_normalizer-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:fd0a274c0e5f9a21565cd9d3dd749b61f96b7aa1e20a93aa1ba4029518f2e5c0", size = 179856, upload-time = "2026-08-15T08:18:02.929Z" }, + { url = "https://files.pythonhosted.org/packages/29/cd/2b812ce5e888f1ce69a5350281e58aab07ae64a958ecae8912f30865718e/charset_normalizer-3.5.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:774d157f112367ff4abd29019f38f023c24e00e56edc7829c20e358a5a913ad8", size = 212318, upload-time = "2026-08-15T08:18:04.403Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4a/a6ee107430768a5334e6d63f31f148a04a1a491ef161a1ac9415a73f2fa8/charset_normalizer-3.5.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:26422d45fd13551cf564c58932f7d72b4f58b93b0fcf18c35ba6be12b46bb102", size = 224897, upload-time = "2026-08-15T08:18:05.997Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d9/35ae3f64f29d0179c35c3baefe575904df2913dde519129c7f75995a2b1d/charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:09a7bba9f739468c8e78c36a75c33768e53cb1959fc638f510454c14683f00d5", size = 194848, upload-time = "2026-08-15T08:18:07.397Z" }, + { url = "https://files.pythonhosted.org/packages/74/76/f2fc7380f056cc273a53af37f50d08ad54b2c59f61078f31432edcf1c2bd/charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4c9548dc78002099910abaebc0a72ac58b7d30931869e0351c09b507dff4ece3", size = 198163, upload-time = "2026-08-15T08:18:08.989Z" }, + { url = "https://files.pythonhosted.org/packages/e9/40/095ce62fa078483cccc1fa2b36e6bc9580b85422a20ee9f925341c50e44f/charset_normalizer-3.5.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c428c6c31eb5f4277d7f8eccaf767fbd548ddd5ce3c8b4f4cbbfab3d96b5904c", size = 341823, upload-time = "2026-08-15T08:18:10.458Z" }, + { url = "https://files.pythonhosted.org/packages/f1/5a/0e58b1c04a1596e0256f407274a92d5fb2ee21324409d1fab1da48a65b5b/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f06b7eae9dbe77fe1d644ca244dad508de8d302870a43f3c559b521270938a0", size = 242458, upload-time = "2026-08-15T08:18:11.989Z" }, + { url = "https://files.pythonhosted.org/packages/22/95/b4618ce912e6db0b1aae89ba788e38e8a7eba0f3025cc66e8c0699f977b2/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b7430cf5728e68f6c462254009a6ef4086e1bea43cf2f57aa9c55fb4f50ff96", size = 226717, upload-time = "2026-08-15T08:18:13.401Z" }, + { url = "https://files.pythonhosted.org/packages/8a/76/c681192bbda3d55356db5dadd64381d5202b37c6b598fcda5282e88b5d3d/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab743e9bc90c1f73552ec33e10e3331315acd2c397b36065b591b0181de533cc", size = 266111, upload-time = "2026-08-15T08:18:14.961Z" }, + { url = "https://files.pythonhosted.org/packages/88/be/55127bfca72c0cff6c022488d140d7c5b04c771e3b72e9bdb4836d54979d/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6f7deae3feb4edfa2efaf7c574fe88cbf055038a6abdb40188e4fff66d5699f", size = 263128, upload-time = "2026-08-15T08:18:16.515Z" }, + { url = "https://files.pythonhosted.org/packages/e0/91/39c3af510b0aa32bbda03374259200f28430febfd1bf5e511fe765282ce5/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15f024313246a4ed976c60f440bb8d257815513a681d212ff74fd46f7d715a90", size = 251240, upload-time = "2026-08-15T08:18:18.127Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a5/cbe418bbc6ecdfc3e05a0116002897c4b403a5e838d697e64c78e9f0190d/charset_normalizer-3.5.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:823f82903d189af463d7df250ef1f7f696f3cee08cc8d91deb565e8d425f6506", size = 245282, upload-time = "2026-08-15T08:18:19.625Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a4/689bb42e8e7cd492f3cb64907c6bc00ad247ec9a3628cd3f8eed126e8ae1/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:01e93745f7f219b703b60ba7afead36cfc4242782be5af484673fc500df12da5", size = 244597, upload-time = "2026-08-15T08:18:21.121Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ce/9962938e179cf9f699d3f1e7b3114b5d7642dee6a893745229f9dd04f274/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:329fc3ccb63ad22d867d84c2adea759a64079a37ba4a343433b02c7a2816871e", size = 231376, upload-time = "2026-08-15T08:18:22.57Z" }, + { url = "https://files.pythonhosted.org/packages/85/54/46000450ada53bd9eac5429a2c8c54cd2d9b39c0c255f229aea9af0948a5/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:bb57753e36e4855b8ca375069482250a6246372331a3e4f3407eaebb007443f5", size = 266715, upload-time = "2026-08-15T08:18:24.235Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bb/618749d70f792b44252a777bf89bfb86823b9bbc1ea13fe8ce759b07f38a/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fce8cbd4997efeb450bd298b54f755dcdff18d496f7a5ddbb4867c6d7c88fdc3", size = 245848, upload-time = "2026-08-15T08:18:25.726Z" }, + { url = "https://files.pythonhosted.org/packages/7e/3f/ffb64458527c7668031d5eb095d978de561958dc9f5b53f8e488a533e603/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6c9cdde8becb25a7fde49924511aa2644d6f8081cc8df8e9452724303348d8e3", size = 264521, upload-time = "2026-08-15T08:18:27.193Z" }, + { url = "https://files.pythonhosted.org/packages/4f/ab/74a55fd803916a35ac461daf002708191aac19b546b80dc8cabfedc63d98/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9ac4444d8d4fd4c4bd08bf451ed3167aa9e7ec6cdb41b648794f1d1103652e36", size = 253054, upload-time = "2026-08-15T08:18:28.568Z" }, + { url = "https://files.pythonhosted.org/packages/a0/2a/6a9034b7d3c60b17499afb482df5878bf9fa20b50cc3887d5ef017a833db/charset_normalizer-3.5.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:f03ac127268b43ef4fe9e6ab6794a6794b49485a0cc0c1db79876d2f33f75bc7", size = 140580, upload-time = "2026-08-15T08:18:30.214Z" }, + { url = "https://files.pythonhosted.org/packages/f3/46/1d362e1a00d035d66b9869e1281eee115907f7e390a16a07824ab5737360/charset_normalizer-3.5.1-cp314-cp314-win32.whl", hash = "sha256:1f5883d77fd409a261abb5dc8ccbe335720d798b1de4abb3b1d47ccbbc76b53b", size = 180325, upload-time = "2026-08-15T08:18:31.877Z" }, + { url = "https://files.pythonhosted.org/packages/7a/7c/4938c329b6a9d446f6a59aa2092ff7118f274209b5ed0e26893d1d30a63c/charset_normalizer-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:c658c50ac0c98cd755a2dd50b7977d3bca7df401dcc47fbdfa87db53ef7d4e8b", size = 204175, upload-time = "2026-08-15T08:18:33.466Z" }, + { url = "https://files.pythonhosted.org/packages/ac/33/eeb384dbd8dec570661354592f4f2e1b2fcc92585624d146a000caf53841/charset_normalizer-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:4bea7f8ebe90bbd7f0e4a2de42ca6924ba23e3e76418c408ff82f1d46fabd687", size = 184123, upload-time = "2026-08-15T08:18:34.913Z" }, + { url = "https://files.pythonhosted.org/packages/1c/6c/c73fa9d5a85f6ab05395de61c5f6984e0a9ff40bb5ff888d46dff02526c6/charset_normalizer-3.5.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fbc597639158fd7c14d55e808718848319540f51b0e6746e3eefa59723a4a348", size = 381682, upload-time = "2026-08-15T08:18:36.349Z" }, + { url = "https://files.pythonhosted.org/packages/30/c7/63565f860921457feba93bae6c86fb7746deb4cffeed2f375cb845318146/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e71c909f353863b2b89c83de2ebed71ea6d0df8a6ef65a128193c5e650766bef", size = 240826, upload-time = "2026-08-15T08:18:37.887Z" }, + { url = "https://files.pythonhosted.org/packages/06/ae/7ae8807410dfa33f8e6f1715740adeaafa8a816cc4cb33508f54b1f7c896/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7ac76cf9afd34929d76eb7fcb63be476a4853d8a96f0dcf2d0db68a0cbdf9885", size = 227861, upload-time = "2026-08-15T08:18:39.315Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a3/887c1642f0da26000b0e0652d91071113c0e72cea33952e225cf589f49a9/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a3a370082ce34d0612f421e15fe011c53bb1feff21a26d06ad4fb244dab5a375", size = 260758, upload-time = "2026-08-15T08:18:40.88Z" }, + { url = "https://files.pythonhosted.org/packages/3e/11/e6f5b9a3d0e55b0ef7505cd3765cdd48f22db89994c947b316f52f801fd8/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:256dd4d85d9e4dc595e2bc983c980e73f62ddeb3165c58b4c3dfe78c5c8548c1", size = 259950, upload-time = "2026-08-15T08:18:42.351Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ee/e4e10a94d51cd1ee638aa7e00b65399e6b2a4e8376ab6d2eac9f95586671/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:58d4aa13a59c969dbfdf9e6a9560e242cbfd9e8a8f50c2747714df1a423adf65", size = 249329, upload-time = "2026-08-15T08:18:43.914Z" }, + { url = "https://files.pythonhosted.org/packages/c4/25/d5f4198819e6059735a84e8d0bfb72dc33976da67b97adcd3fb5a5e07ec6/charset_normalizer-3.5.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0c6dfb5ca6723eeed15aa8e564a014d69fcb8812f94eef11fe3631e0508199f5", size = 243137, upload-time = "2026-08-15T08:18:45.368Z" }, + { url = "https://files.pythonhosted.org/packages/a5/e9/e925ca7569cf9fb9701fd82503fee73eea5268fdb856bdd64947092d3daa/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c010f5581d9c612804cc59fcf7b524b707fbcb72828551237ab545bb5c7034af", size = 242820, upload-time = "2026-08-15T08:18:46.842Z" }, + { url = "https://files.pythonhosted.org/packages/34/17/672c251a888ed2aebcdd2fe830ad0104e25ff83c43f5c4f9c15e9fc6853c/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:52ec005752a56ae79547a05c0139ca2501a0c866390b6115008456b9f0e7cde1", size = 230504, upload-time = "2026-08-15T08:18:48.353Z" }, + { url = "https://files.pythonhosted.org/packages/3f/fc/f6a85abebd42ce4da2f1db0aa56cc6a0df1995e318b3875d14401b8381d1/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2bced4061f000f7187254a02ad3433ae17eaf991747ceea2f478422590a5bba9", size = 263087, upload-time = "2026-08-15T08:18:49.859Z" }, + { url = "https://files.pythonhosted.org/packages/98/66/7c42677e739ba66746b297e2046918d793078094dc239e1e72768cffccc6/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9eea3ab2597a5e65fe65296e2d6a84570845a6b55532d90333d740d48bbc850a", size = 243269, upload-time = "2026-08-15T08:18:51.601Z" }, + { url = "https://files.pythonhosted.org/packages/de/d8/a50b79237f417af10f8c2a501ce8d1ca87829a22e69117891ca4ba20a69e/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:496846868fea80e479324862fa877f02411f2fd0f83b79ccee2607aa68b2a032", size = 258766, upload-time = "2026-08-15T08:18:53.23Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1d/0fc91aeaeb3c83b748f532399ce67cf84604b48297405d740000f7a9e786/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:85d5855daafc240cc045c026d7a15fd198a09b0fc8ff6f5ecbb5297b509cb11e", size = 250814, upload-time = "2026-08-15T08:18:54.768Z" }, + { url = "https://files.pythonhosted.org/packages/ae/10/3d8c777cf9024615295aa1b808324ad5b4a77855869c00824bad74ffaf8a/charset_normalizer-3.5.1-cp314-cp314t-win32.whl", hash = "sha256:58d3e12c88e0950bca850ae1f7c256055c097639c2edb9eb123af9807d8b15e4", size = 191074, upload-time = "2026-08-15T08:18:56.305Z" }, + { url = "https://files.pythonhosted.org/packages/4d/81/ae557d3c44d1a1d688696d60563413a0866a91b7ebc50f20df838be3d8c8/charset_normalizer-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:acaf604462bf330b0d07e7a07c1d6e4adac79e5fb13e9c5140590542cafacc00", size = 216476, upload-time = "2026-08-15T08:18:57.889Z" }, + { url = "https://files.pythonhosted.org/packages/27/e9/61c01fb8b804692569c036b3fc50495814502dcf13a60649c6055390b02c/charset_normalizer-3.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:fdb8a068947befafba9952162645dc2fecaeb400e64584829ed5e9b2fbe21a7f", size = 194115, upload-time = "2026-08-15T08:18:59.418Z" }, + { url = "https://files.pythonhosted.org/packages/5b/97/fb4e82231aba271ffd775a1b4993b0defc4e3059f286ae41d9433409fe85/charset_normalizer-3.5.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2", size = 331467, upload-time = "2026-08-15T08:19:50.959Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2f/fe3f187327aac18e2d54e9d2b08e15d27bf9b642d9e51c219f130fc34d1a/charset_normalizer-3.5.1-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99", size = 253057, upload-time = "2026-08-15T08:19:52.654Z" }, + { url = "https://files.pythonhosted.org/packages/d7/c7/9e48cee5c161fe24da823b61bf381921d77cb994a0a4de148e95018c1984/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2", size = 240930, upload-time = "2026-08-15T08:19:54.163Z" }, + { url = "https://files.pythonhosted.org/packages/49/e0/716601f3cc69be7b198951150c75ead1ece33c3c8036ff6ffa46029659a0/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235", size = 230822, upload-time = "2026-08-15T08:19:55.807Z" }, + { url = "https://files.pythonhosted.org/packages/d3/05/71bfc5caa0abcc45aea1f6a4d50ac68e59605ddc7666fe8494f4cd229665/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598", size = 260037, upload-time = "2026-08-15T08:19:57.312Z" }, + { url = "https://files.pythonhosted.org/packages/c3/92/de7e32ed05341e7a9c4c877c318418197b7f2d66a3b68d561bf2ac57ca3e/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96", size = 255097, upload-time = "2026-08-15T08:19:59.056Z" }, + { url = "https://files.pythonhosted.org/packages/f5/7b/ade0a122600319dfa0b1000ab0f9731c94a817904cf3c5de408c73a4ede7/charset_normalizer-3.5.1-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962", size = 250166, upload-time = "2026-08-15T08:20:00.612Z" }, + { url = "https://files.pythonhosted.org/packages/75/9c/019fbb9f4834491a160951349b1a3714439376f66e5f7cf18b4f18f0c7aa/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3", size = 241821, upload-time = "2026-08-15T08:20:02.321Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b8/11d4840bfc99330cc7fbcc2681ee5a044553a6e77655508d8f9b2bff7b34/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950", size = 232529, upload-time = "2026-08-15T08:20:04.008Z" }, + { url = "https://files.pythonhosted.org/packages/18/96/2b3a21492d9f65171ac75d872f5018260013d00bfa0ff70ec9f179148cbd/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8", size = 260348, upload-time = "2026-08-15T08:20:05.877Z" }, + { url = "https://files.pythonhosted.org/packages/d6/aa/a69a2028e8bd052476c245460ab19d7de595de084dd968f2d75cd50c3e25/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031", size = 247234, upload-time = "2026-08-15T08:20:07.487Z" }, + { url = "https://files.pythonhosted.org/packages/35/8a/3d130aeabcaf3d2466af76b7b141c08d9e89c9016ab4b7cdd0f7dc2d1c62/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072", size = 256917, upload-time = "2026-08-15T08:20:09.142Z" }, + { url = "https://files.pythonhosted.org/packages/80/c2/a7379b840292d0c1ab9fbd17d1f3967aa81794dc95bc74be8999d7fedcf7/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d", size = 254846, upload-time = "2026-08-15T08:20:10.727Z" }, + { url = "https://files.pythonhosted.org/packages/01/65/d43b714731bb2f40d4053dfa00ecfc1c5a301f8e3316c5db3a09af59fe94/charset_normalizer-3.5.1-cp37-abi3-win32.whl", hash = "sha256:dd732602a7009217f658d5863d12d79d373a4de0eebc111094bcdd3bb8e0a6cc", size = 174216, upload-time = "2026-08-15T08:20:12.334Z" }, + { url = "https://files.pythonhosted.org/packages/35/4f/b911ed898b26a09789eba9c9200c999aff6c61b4bafaf4838e56d1a1e1a3/charset_normalizer-3.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959", size = 199764, upload-time = "2026-08-15T08:20:13.908Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a7/920baf467bfd9bf689f3b318340f37aee4572a71f162bd8db51da55ba4fa/charset_normalizer-3.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:87e4f41d375c0b9be2fb5251aee4b8a689169e134535aed81bf085c3b647451e", size = 287318, upload-time = "2026-08-15T08:20:15.551Z" }, + { url = "https://files.pythonhosted.org/packages/cc/61/d01fc49b8dea277640b55a9e15960dbca9fdc8c9fde18e572d39c59f4019/charset_normalizer-3.5.1-py3-none-any.whl", hash = "sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6", size = 68658, upload-time = "2026-08-15T08:20:43.306Z" }, ] [[package]] @@ -694,58 +735,58 @@ wheels = [ [[package]] name = "cryptography" -version = "50.0.0" +version = "50.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/41/6cbdcf9142d00fe82836fbb51e503e58088575cf7a0fe1dbff6695bf0840/cryptography-50.0.0.tar.gz", hash = "sha256:eeac2acb5a20ed25e0ad6d1df9891a520b78b404266b6d11778f25d5d691a6c9", size = 880201, upload-time = "2026-07-31T14:25:10.11Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/ad/5d6702db60b1e40b41ef513b6967ff5848f307d50f8449baf1634f5908f1/cryptography-50.0.1.tar.gz", hash = "sha256:5dd9bda1c12b4162f6ff568eeb5e0ff956c28d14406e875cfe8a63a2d414ff20", size = 880381, upload-time = "2026-08-25T19:45:45.499Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/5c/59086b4aac5e879d38ddbcf74e4be7ade89cebc3eb199a55da998c3bb46a/cryptography-50.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:031e2d5dd4bb9caa3ca9c82e5a197fd8ae680232cee62603d1a813f3f07e3d03", size = 4001252, upload-time = "2026-07-31T14:23:33.331Z" }, - { url = "https://files.pythonhosted.org/packages/57/ef/8f2df13c7216bcad3e1c74e07f6e193d93e998e114f524a53877c9af27ad/cryptography-50.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd9192b7b70c573d7f214eb1ae35e00d359f6f5e4b27c7e21e30de1fc6204645", size = 4719554, upload-time = "2026-07-31T14:23:35.611Z" }, - { url = "https://files.pythonhosted.org/packages/d9/41/029086c34d91052fc3b88bcc8056f709a7c915c7a23b235a54eb800b1c97/cryptography-50.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:06a32a980526a6ab9a4b9bf8f7385800791e2bb960903cb6b530e4817509a3b7", size = 4702130, upload-time = "2026-07-31T14:23:37.635Z" }, - { url = "https://files.pythonhosted.org/packages/7d/ff/b6ce0954962e7f7b969f850a883744197bb3910bdfd7b6da162eab7d9f68/cryptography-50.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a1b30560f2acc95aa8b2e06e716a13dbfc97314747b80d9707e307f77b40d6b3", size = 4725244, upload-time = "2026-07-31T14:23:39.471Z" }, - { url = "https://files.pythonhosted.org/packages/06/1e/63a1027cb7fec360a182208e1b7767d5aa1fe57be3d6aa856e69a321edc0/cryptography-50.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:8d89f3976b10b4ce31118de72329025f70d2c6ead14a8217c5514dd2c6d5a78f", size = 5342265, upload-time = "2026-07-31T14:23:41.286Z" }, - { url = "https://files.pythonhosted.org/packages/6b/72/a1116d683a6d7ece94590013882515de087edf9ef0e6292aae615a44df73/cryptography-50.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b42a28c1844fd9de8f3f7d540e36b66f3a9c83fceac7170ebc7a6a19edd9dcae", size = 4734609, upload-time = "2026-07-31T14:23:43.139Z" }, - { url = "https://files.pythonhosted.org/packages/15/37/36a9c479bbe49acea2636c7fd3360d20f7b7e079c300352011c44850b181/cryptography-50.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:900131fafd8aead39ac7dd3a7e833be754c17a95cfd91221636949fe4eb0aa8a", size = 4356517, upload-time = "2026-07-31T14:23:44.939Z" }, - { url = "https://files.pythonhosted.org/packages/32/98/8a151d64367204cbc63ec65d37502f1d9c53cf4bfc6ec3c532614dbec60d/cryptography-50.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:07949c449a1abcf60d1ee6e88956d89404c7df3c8258f46589e912988e551987", size = 4724529, upload-time = "2026-07-31T14:23:46.93Z" }, - { url = "https://files.pythonhosted.org/packages/22/f6/ec13b470172126464a86bf54d2294a46d29837fc51ba3e45d4047946fb5e/cryptography-50.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f89831ef99dd7dd169ab06d63a831adb9e20a87aac6d380266bbda5823349169", size = 5299852, upload-time = "2026-07-31T14:23:48.851Z" }, - { url = "https://files.pythonhosted.org/packages/da/3a/f05e32c99d440c9bb891ea0e36c9091891e36be5a9a87ab2ee6ea20729f6/cryptography-50.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:82148ec5bddac30b51a5b3c1945075f896fa022cb93f8e4a01e9f6ee95292c5f", size = 4734462, upload-time = "2026-07-31T14:23:50.861Z" }, - { url = "https://files.pythonhosted.org/packages/ca/dc/bd72b26be8953f80625f63151efd38eee71c76ca6cf591c08ff34615a79e/cryptography-50.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1489e263a8048bb8b6a8bac662eb2d402ea5d2b7b4699b72f385f1e2772db105", size = 4852708, upload-time = "2026-07-31T14:23:52.715Z" }, - { url = "https://files.pythonhosted.org/packages/27/20/c930314a2ab476d15dec966ec87e2e9637bb02b06106b12c0396c57bb603/cryptography-50.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7cec5b856506da6defb290f30c9ee687d5f5e8cb0bd3f6459dde43b0b4fa40ef", size = 5004179, upload-time = "2026-07-31T14:23:54.887Z" }, - { url = "https://files.pythonhosted.org/packages/32/2e/c9db68a0c4bfa28e310707527c0ee3a2bd254104d2e02e68f368e197aa4c/cryptography-50.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:bd1c592e4d5974f0d08d4888e432157adba757c66da0246918e43677fafa2d30", size = 3840395, upload-time = "2026-07-31T14:23:56.677Z" }, - { url = "https://files.pythonhosted.org/packages/c3/fb/951032a3bf22a5697c83183fb6294a4843772947a70e616c57b3ff5f522e/cryptography-50.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:49e7d93abdbd2990caced757e5fade25302f719c3c8fb6e6fff2dde98999fc41", size = 3989258, upload-time = "2026-07-31T14:23:58.881Z" }, - { url = "https://files.pythonhosted.org/packages/d4/67/91eb047e69c5e845f2f14b8a2e4a1aab0f283cb885531e9e22c8adb176bc/cryptography-50.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:19736989797678c6af1e55cd49055cdbcb55d8f6b5583ac5335f933aba9101dc", size = 4700648, upload-time = "2026-07-31T14:24:00.702Z" }, - { url = "https://files.pythonhosted.org/packages/30/82/85f0f7425c856b9f96459411eb12e74ef72df9caf6f8f15bf23a33ff131f/cryptography-50.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80b63928fa35083b33966ce1efb70e5b9607181e49dcd1c22c8c005e319f667f", size = 4682442, upload-time = "2026-07-31T14:24:02.538Z" }, - { url = "https://files.pythonhosted.org/packages/1a/28/b555a365adff1cca2fbe7b9e487d68a40de6bc67ff2cb587473eb43de0e7/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:d58c3db7cd6eed54e6c06744db55456b65ebd7492ddeae9c1e93cfca7aa857d3", size = 4707596, upload-time = "2026-07-31T14:24:04.394Z" }, - { url = "https://files.pythonhosted.org/packages/72/d8/f52538140cc719df62a01cf87d1c7142318d235817109d6f4054d7c352d6/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:df2a58a472f332225671c35b0a830208b86d004f82baa8530fa3782c85646533", size = 5314552, upload-time = "2026-07-31T14:24:06.31Z" }, - { url = "https://files.pythonhosted.org/packages/38/14/6120e5bd7c5aa022ad15424ba4d5c5269d0d9448ed4d55e492ea91e3c1c4/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:11b74db56cdbe3cdee6e3f6982ecb70334fa10dce99ed58bf7894aaaa3b2a037", size = 4717113, upload-time = "2026-07-31T14:24:08.349Z" }, - { url = "https://files.pythonhosted.org/packages/fa/71/190bf38c3ee2e0f8efc9860ae100c9df4169742eef274b91e7aa1cb133b9/cryptography-50.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f59e38625469987d7ef6d495323c55e7db6c212eaf6112267e0d3b565a2e9c9f", size = 4338580, upload-time = "2026-07-31T14:24:10.227Z" }, - { url = "https://files.pythonhosted.org/packages/3a/63/504ccfbbe61fd8aa983f7f146399cdf034c72c2fc55f5b2dfdcdcdb20c99/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ecfed7367f965a0328cfbdd70da860f15441f002f613185668c6e6ebf5a0ac11", size = 4707038, upload-time = "2026-07-31T14:24:12.169Z" }, - { url = "https://files.pythonhosted.org/packages/01/77/2cf79bbfc4d12ca106437a6e170d6aaa01a373e93093118aaaef0e801bd4/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:9aa87839c383bdbab6ef865787a1fb877af8dd03464c4400322726feaaadfc6d", size = 5273110, upload-time = "2026-07-31T14:24:14.38Z" }, - { url = "https://files.pythonhosted.org/packages/e5/45/8aae2972c520145377ea3559a605a899bebe227bf070b33cdb445929a9b9/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:6ba6a53445bd3cfa809ef3ef5f1589aa6ba08784a1d962bf47d0940e871dab1c", size = 4716439, upload-time = "2026-07-31T14:24:16.415Z" }, - { url = "https://files.pythonhosted.org/packages/7b/20/4fe50b619a48c2525cc46e2dbc1ac490708d704be5d467bdaac6dc955682/cryptography-50.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3f5735ffe4996d28b809371756219f5354864902a3b9e7c0b9ee87041209fc9c", size = 4837383, upload-time = "2026-07-31T14:24:18.553Z" }, - { url = "https://files.pythonhosted.org/packages/92/91/3a31366e183343d3703f8995c095f5734676bd6938118047e50fcf279eb4/cryptography-50.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1b4a266766514614f8aa60416e71f2fc6e575d36e7bdc90f644fadb2f4b75b95", size = 4985772, upload-time = "2026-07-31T14:24:20.385Z" }, - { url = "https://files.pythonhosted.org/packages/74/9a/02ffe35b2853d121689871eb5dce862092562b3a1ed5cc98f1aaed441506/cryptography-50.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:12b9c6996425c76ea6c457ace4f3073e715b8c545add07cd1a8f3a4f90691269", size = 3816291, upload-time = "2026-07-31T14:24:22.125Z" }, - { url = "https://files.pythonhosted.org/packages/03/37/73d005be173aff344af30e9fd2a576575cb2391a7101d9cd3842e1fa8cce/cryptography-50.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ccdc4a71a4dabae05de219404f9f4abc38e3b58422177ff93d0da05967dafa07", size = 4036009, upload-time = "2026-07-31T14:24:24.122Z" }, - { url = "https://files.pythonhosted.org/packages/ff/c6/7a6202a534e32103a285b7834a120869557fe198d51d7cfe59754c8bda9c/cryptography-50.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:910e1d2668e7de9648f2bcee30e180db2a6b15c30f887d7c4c93ddf96e3992e3", size = 4745252, upload-time = "2026-07-31T14:24:26.118Z" }, - { url = "https://files.pythonhosted.org/packages/85/4f/0fa8c2f4428198f15d9ff8d63400e27afbf94ce833f6108da1eb3753f945/cryptography-50.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a91296cb61e8df6f86d0c19cc4068228da256bf59bf86049fbd821084565327f", size = 4728939, upload-time = "2026-07-31T14:24:27.994Z" }, - { url = "https://files.pythonhosted.org/packages/d1/63/54dd723490ba2dc09b299682c10b38db38f159728bcaae8c591b8af2f22d/cryptography-50.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e722f16708d854fe924790e051061f6704a472c3bac347b6fd88033ea8dd0dc5", size = 4748483, upload-time = "2026-07-31T14:24:30.254Z" }, - { url = "https://files.pythonhosted.org/packages/1d/dd/7c77d26285cc7f6991efce64a0f5b4f9383bfa5dd8c5033003eaf7db4cdb/cryptography-50.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d764dcf130c428ef66786f866dd750f53182bc608813489915e9fc106bb0c82f", size = 5367599, upload-time = "2026-07-31T14:24:32.457Z" }, - { url = "https://files.pythonhosted.org/packages/46/c9/f60aed34c013f317f92817b6c171c2d22a78270fa41109bd4b08af26b194/cryptography-50.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:105110f43a471dbd0060b9c9516cb8a6a79233631a04cc2ba16f28323ac6e025", size = 4762647, upload-time = "2026-07-31T14:24:34.599Z" }, - { url = "https://files.pythonhosted.org/packages/be/f3/f9a0173b139372c3a48ed98154b45cc6b9de17c789d5ab552e621c293609/cryptography-50.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:828743d939e9629bc267b8e2d08d8bb67cd4319c771a33d4b18b22dd8fb7440a", size = 4385197, upload-time = "2026-07-31T14:24:36.647Z" }, - { url = "https://files.pythonhosted.org/packages/d8/36/83bb81f6e569bc38e1e4a7bc80f29b46bb9601920bc455fc8e888f5d5742/cryptography-50.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a8183b489dc1f7f80f135780fadc1108f14b31b8a40411c7a5b17425f65f28b", size = 4748095, upload-time = "2026-07-31T14:24:39.493Z" }, - { url = "https://files.pythonhosted.org/packages/6b/16/d3008eff98c764979865834c3d386d4fd041b5f52e7f34fc29ac1a5eb515/cryptography-50.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6e7d61120573a7f2cd94cc095f9e81f6967c61ccdf194285aa143ecec8e0b708", size = 5325948, upload-time = "2026-07-31T14:24:41.556Z" }, - { url = "https://files.pythonhosted.org/packages/9c/f8/d97f9603efda3888187bfdb893f26c41be4735c10631d05d284ee6b047c4/cryptography-50.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:37fdb0d0111f1e2ff07139dfb79f1b49531f8e213c46f1163dd7642979b58c47", size = 4762400, upload-time = "2026-07-31T14:24:43.636Z" }, - { url = "https://files.pythonhosted.org/packages/64/a2/4615c8f7d81a00b1d6e6afe19f694e1543582349fb5f4076f6cb5dc36485/cryptography-50.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87f62a3d3b9888ed0fdde100ec06aa61ca9cd44bad9057d1dff9a516b5f5bb9", size = 4878208, upload-time = "2026-07-31T14:24:45.522Z" }, - { url = "https://files.pythonhosted.org/packages/d2/1a/efcfb02f91407149a0dacffffab791f7e19bf6385f63b3666dc8b5e5c9c8/cryptography-50.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:65c2c3add92b45fd0709db8594536aea39c2a67af0e27ffcf049c498501140b7", size = 5037050, upload-time = "2026-07-31T14:24:47.697Z" }, - { url = "https://files.pythonhosted.org/packages/57/30/4a22984d4f1bdfb8c054f07a92bc176b97a3134cc1d6c4b3bffb1f3688b4/cryptography-50.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:d24fead1d4d076e1bfb006dcec392074a3cd8d7b4fc8a595aa64073b2b7a96ba", size = 3874135, upload-time = "2026-07-31T14:24:50.085Z" }, - { url = "https://files.pythonhosted.org/packages/9d/3e/e54cde8c01631a5a8226ccd617eab9e57fd5cfdad90f1a9e6bb570794631/cryptography-50.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5e34edd123674534acd70147f0ca331eaa2c74e6325fb2028c886aa26ba0b68c", size = 3963170, upload-time = "2026-07-31T14:24:51.968Z" }, - { url = "https://files.pythonhosted.org/packages/01/b6/0b9e125e90f3d2dcf599a218a899cda7326a3158cfa258723f0b398b08f6/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8eb5e1172eb569ea8a872796576e6a67c276351728b6455d5beb01242b027c6a", size = 4692441, upload-time = "2026-07-31T14:24:53.743Z" }, - { url = "https://files.pythonhosted.org/packages/53/c9/a5151588710785a96d7bc4de27d4cd62f263bbbcb203cfe29df537eb6505/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:910d11e1a385c654bf738bf3e6b8e6ed5de0f5610fcae2be9e5b398d8081d20e", size = 4699810, upload-time = "2026-07-31T14:24:55.746Z" }, - { url = "https://files.pythonhosted.org/packages/c7/1a/15b92b25eb6ce3089cd49377ae990a0f3ad485a510f968aed1f19dbdcdf2/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:62598a8a57f815db4c6259a4e97d857dab56697e7de8e8ab02352ab74da1995d", size = 4691924, upload-time = "2026-07-31T14:24:58.082Z" }, - { url = "https://files.pythonhosted.org/packages/62/15/219075012ab13e8905f3cd572204f4acb4b111df787104346b9bc0cea789/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:07479a1cb08219ab719147e742e76090c9c773321959bb94946fffdd397a6437", size = 4699593, upload-time = "2026-07-31T14:24:59.951Z" }, - { url = "https://files.pythonhosted.org/packages/8e/b5/c2c5fce26f0ee40d21bafe7f191d29a34b35a65ac4fe8a1191d1983612e9/cryptography-50.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c99c003e088647b8a5b7c145d6f78c335f6348332b62e142d411c4b63d1460b9", size = 3813796, upload-time = "2026-07-31T14:25:02.298Z" }, + { url = "https://files.pythonhosted.org/packages/ba/19/797e2aaac9df6a66f1550f49979dc1b1e39ecd2077501c30efa81e8d5d67/cryptography-50.0.1-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:b8f852c65863251b9e3a1b8c150ce21e59b522dbb6a7d4bc80e680d38388e986", size = 4010153, upload-time = "2026-08-25T19:44:03.155Z" }, + { url = "https://files.pythonhosted.org/packages/90/34/9ce9a62ed9dc82ca9fd6a34445b6904af56e5f38b3eae2ed32e49c36053d/cryptography-50.0.1-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:53e279950892dc102c6b4e52af03ae5ea92fac572a1ddab78ca73a997f62b69f", size = 4723133, upload-time = "2026-08-25T19:44:05.461Z" }, + { url = "https://files.pythonhosted.org/packages/57/26/e6d4fc8512a51a5f9ee7bfdbfb853bce1197087df40c9ad993ad370b846f/cryptography-50.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ff838d62ec1bfce4f9ba7fa16f4a7b554cd8d0c299e6be37502161a660c84eef", size = 4712478, upload-time = "2026-08-25T19:44:07.375Z" }, + { url = "https://files.pythonhosted.org/packages/e6/de/d3cdc2815697aae84126cbd6a030ca7b6b452e28a88b501b836bd3aa7a86/cryptography-50.0.1-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e74591e283fe6eb956416c929eb58262a719fe0311fd9054c62c3350ed8760d8", size = 4730726, upload-time = "2026-08-25T19:44:09.294Z" }, + { url = "https://files.pythonhosted.org/packages/55/32/38c0d344b98c06d34b5df8946565a9c0d6dbf32c8e0730a7f05f0a3c6cab/cryptography-50.0.1-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5fe002589592ed749ce77fe0695fcbd3500dd61d7d6db5858a7544c612fa8e45", size = 5353524, upload-time = "2026-08-25T19:44:11.96Z" }, + { url = "https://files.pythonhosted.org/packages/e1/1b/82f0f0d8858d4432be1af790477edf62aef90324041aa07c57e57bef1af7/cryptography-50.0.1-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:51593d180cf6d179bde5c5d065bed81386b1f381656ae7d042b7ffc87a9895ad", size = 4746720, upload-time = "2026-08-25T19:44:14.051Z" }, + { url = "https://files.pythonhosted.org/packages/29/ba/042ca458b8c64348c768284b5d23e69b92ed53d057ab779fee628564676d/cryptography-50.0.1-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:359e62deae718bce96170e223fdcb6357e4fbd3bb7a3a75f4430763532560e49", size = 4361866, upload-time = "2026-08-25T19:44:16.167Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/e96c1ef71edef71057c7e3c3d982ce8fda554e0c52d0cc19c18845cde3eb/cryptography-50.0.1-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e2ca8fd1b6b4b82a1c4cb02841d0837e3c12336c2e24b520ab8ab3b969733d8f", size = 4730028, upload-time = "2026-08-25T19:44:18.085Z" }, + { url = "https://files.pythonhosted.org/packages/e3/38/45abd72ef63f2e7d0754a6cacf97bd8b69512ace7f6130d24c39ece65da2/cryptography-50.0.1-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:76de83fbd91ac49c0feaaa983d0748fd7a53176afac5fb3bf7478d244f0eb527", size = 5308405, upload-time = "2026-08-25T19:44:20.197Z" }, + { url = "https://files.pythonhosted.org/packages/85/66/6ccca4722987ddedaa7fc9c3f4708af7431f5535666c174350830888c6b7/cryptography-50.0.1-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:51afcfceb15597cf2635068e4ac9a56b2abde622edde17f37d85fd7b5306497a", size = 4746230, upload-time = "2026-08-25T19:44:22.376Z" }, + { url = "https://files.pythonhosted.org/packages/13/0e/b1f92e013228111413f2e6743948b80bc24dfd3c1b87ba98ceea16f5df89/cryptography-50.0.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:be224a65493ec5b74a158ff22a5522ce4a5ca1e543c647a3a4730d4a09e5f959", size = 4862596, upload-time = "2026-08-25T19:44:24.472Z" }, + { url = "https://files.pythonhosted.org/packages/7e/22/c3654cccc856e9d682817b04ac3ee79731cb09ca6f95996a95c904de2883/cryptography-50.0.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9ebcdd5519be9b652a46f507817a74591774fc3d6923ac364e4dfa64e36b291b", size = 5014082, upload-time = "2026-08-25T19:44:26.709Z" }, + { url = "https://files.pythonhosted.org/packages/42/8b/cb12b1b60c91b074ca6bf0fdd59aa8f10d8bc5f73af8faece86ef0421b37/cryptography-50.0.1-cp311-abi3-win_amd64.whl", hash = "sha256:aed8db4f6d71c51efb89530e12d9464e7bf2923d46c3205dc794a2a93f8c0648", size = 3842826, upload-time = "2026-08-25T19:44:28.784Z" }, + { url = "https://files.pythonhosted.org/packages/5b/f0/424cb557d99aa86ac55da5e2add02e2882e44047b6264f93ade1b975a993/cryptography-50.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:30a125032e5642a21ff816e021152bd4e7e94f03eff3f4b7fca41cd22bc3110f", size = 3973525, upload-time = "2026-08-25T19:44:30.7Z" }, + { url = "https://files.pythonhosted.org/packages/4d/72/3a2711d967977ab5fc80b782837c7e8d1ac7445e764c20c381a265c57ef3/cryptography-50.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a0b1a59e3a089064a0ec309e9428c8e3ae4e161419d20ac33600767e83fc658a", size = 4708817, upload-time = "2026-08-25T19:44:32.773Z" }, + { url = "https://files.pythonhosted.org/packages/b4/f2/bb1f56e10815b789df0b409a69fa4992ff3d3fef9c72747f4a6b26fed38e/cryptography-50.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8921d58f426793c5f1b47f0b59575780de9a095214958d0eb37d909593db8367", size = 4697300, upload-time = "2026-08-25T19:44:35.144Z" }, + { url = "https://files.pythonhosted.org/packages/08/bd/ed5396be499ffcf8807a585bfe38b71a1fbdd1c342b4f9b6d0ef5162a946/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a8f40ea47330e71b594a7e246898f93177c259490c63183dbaf9e571d71ed9a5", size = 4716039, upload-time = "2026-08-25T19:44:37.192Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6e/1cf405c5c8e8df7545378048e954792f00b7f2367af8863ce8b8f3e10607/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:a255449073358275b64b67d3f595f268bbef70e72b6edb65e0c70c735bf739c9", size = 5332388, upload-time = "2026-08-25T19:44:39.16Z" }, + { url = "https://files.pythonhosted.org/packages/47/92/b4317e8c32c4f47b062f5398bd79106b220a124546f42be83bf32b761e2a/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:8df2de9102026855887e4587084f6eabd80ed0f345b8ad8a7ac27ab9bf4723e0", size = 4730293, upload-time = "2026-08-25T19:44:41.298Z" }, + { url = "https://files.pythonhosted.org/packages/39/0d/a1e7633e2c744d0f2983320a27e924ef2264c79c56e1a58d5fb0a1cfd413/cryptography-50.0.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:ac02b07824d4d1001bd4367599f839c19cb171924c796e52c23508ac14c2c0cc", size = 4346031, upload-time = "2026-08-25T19:44:43.245Z" }, + { url = "https://files.pythonhosted.org/packages/88/dd/b215616f9bab3fc18510c78a4e5c9f362d77838503c363dc747c7d4f5c6f/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:cbf74a81765ee67413503ca6e26dcc4f6f5a519822436cc0a1b97aab6c1b8a17", size = 4715344, upload-time = "2026-08-25T19:44:45.291Z" }, + { url = "https://files.pythonhosted.org/packages/b1/1b/ec3ebd31741d0e963612c4fe43caa39341b9b1e031e469820e42e4c83918/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:16c5ecd954b3330ebfb6605eca4fd952da8bef376551d5cc264534e3770a9ee6", size = 5287201, upload-time = "2026-08-25T19:44:47.297Z" }, + { url = "https://files.pythonhosted.org/packages/1a/01/0127d11a762b31a9ee0221894f540318761783f3fdc4bc5d057698caebd5/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:79bf008d1f9af6071c797ad133e39915dfee7614f18f18f4db9072eb715064a3", size = 4730023, upload-time = "2026-08-25T19:44:49.435Z" }, + { url = "https://files.pythonhosted.org/packages/9e/b9/e7425ebfb599241a0c1d7000f1b466c3062da66c19d9525031315dff7213/cryptography-50.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:330fbb252391c596f1ae42c5754449dc924e6ad012dca8efe0d703f9f2d12ec6", size = 4847362, upload-time = "2026-08-25T19:44:51.94Z" }, + { url = "https://files.pythonhosted.org/packages/2d/fd/60d0ddf4defa12e482c9d5e0f554384d6e8ab25341fd15f060028fd92e6a/cryptography-50.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:42be3bb70596b3abe4ac097b75be223e8b3ab614a0e5de068e3dcc54d71d6149", size = 4999247, upload-time = "2026-08-25T19:44:53.876Z" }, + { url = "https://files.pythonhosted.org/packages/4d/56/bc4f2b209e766c93372cfcd59b781a0b2b59700f62a969580415b699c2b2/cryptography-50.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f74455bb086a85d5e81246412602aaa97ed095e504cd40dd261ef50be42205bf", size = 3825806, upload-time = "2026-08-25T19:44:56.209Z" }, + { url = "https://files.pythonhosted.org/packages/84/a9/ee16a903f13755e914d1eecc482fe64d1f10761c3960e5d8fa6837377aff/cryptography-50.0.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ca83d00d9e69cd5eb63f2e69c3a5a59e0cecae5ae14c6ae0b35830fe3b37bad0", size = 4035307, upload-time = "2026-08-25T19:44:58.305Z" }, + { url = "https://files.pythonhosted.org/packages/5e/a5/9ec7e81e8526c0d7a387d73386b2daed3f39e10d81a85930bd1b6bfba65c/cryptography-50.0.1-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:05ba322c4da95b262a212c345af888ef2c37c88c0509756ea00a0e6d68850f23", size = 4751900, upload-time = "2026-08-25T19:45:00.401Z" }, + { url = "https://files.pythonhosted.org/packages/7e/3c/0e77bd5ffcf078e9dd27d3074aad6c030d9b10d0bf69329d573c927a188c/cryptography-50.0.1-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e22dfed744bd4002e909464cb23d2f0b05c6f3113a79ef2e9864a53db737c733", size = 4738357, upload-time = "2026-08-25T19:45:02.786Z" }, + { url = "https://files.pythonhosted.org/packages/27/3a/3c5f80daa4dcd47323c7af8a2fcb90de27a33564d4fcac69846c0972691a/cryptography-50.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4c4188f7c0cf655be5c06342b817ed0f9595b69ffa2b12026e5353eed29dea88", size = 4758474, upload-time = "2026-08-25T19:45:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/6e/2b/214cf0cf93db9628c3c20c896b229f327f6fb1b20e4b3743d8ad3f00af8b/cryptography-50.0.1-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2ebbfb0f1fed745e91796e3e1080a1440423fdae8ece1b995a1d80883a409054", size = 5375862, upload-time = "2026-08-25T19:45:07.163Z" }, + { url = "https://files.pythonhosted.org/packages/d6/51/3f9701867a46b6c1740c9b52fc4d3bed6cbdcfedcc9b6e64305c07f39cff/cryptography-50.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:407fe2b6db00939c05c0e945e9914238f2f0a430974839429dafc82b1ee6bee5", size = 4772942, upload-time = "2026-08-25T19:45:09.396Z" }, + { url = "https://files.pythonhosted.org/packages/0d/5c/13ea642e08e2544d0f5396122055f4820cfacb3203562197b5967125ea97/cryptography-50.0.1-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:2b34d76a652ea2b6faf777c35df230c5637842cd904e04f16230c3f9f03e4361", size = 4383347, upload-time = "2026-08-25T19:45:11.659Z" }, + { url = "https://files.pythonhosted.org/packages/84/d5/7d1fe1cb93f91c428093ff234e128c89ba8ea61a6f26aab406081f9b996e/cryptography-50.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:01f41478cf33fc605a6a089cd56d28b45c6c0b45a1928b61797f2621a04bac71", size = 4758050, upload-time = "2026-08-25T19:45:13.745Z" }, + { url = "https://files.pythonhosted.org/packages/dd/04/557fc5ead96a829e0bc812a3b9dc4a52a2f27e4f7f5950da7ff27653a805/cryptography-50.0.1-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:fc3ed7ebd2a8c96f5b166de0ab9b624996bef3b07bbeb19364dfb78222c22c80", size = 5332955, upload-time = "2026-08-25T19:45:16.193Z" }, + { url = "https://files.pythonhosted.org/packages/8c/eb/5d7124083e8d8cda8f5b348f544b71ad6f707ad63193758ef4d8e569da02/cryptography-50.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9dde0a357190eb3b1da1bb9ab750e9c85cba82ca5977aa0836cbb94e92611239", size = 4772694, upload-time = "2026-08-25T19:45:18.315Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/f1f955e0921dd2b6d22eae7e8d24a4c4b638d10735ffbf6a71f99eb0fcb8/cryptography-50.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd3718b960d0b5dd213cdf03f3bcb7000e69dda0de8b956061947ff6bcff5558", size = 4888413, upload-time = "2026-08-25T19:45:20.4Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ab/89e2b798d2c3925f82e2bb72d5979f3d2f6da2dd22ef4a8cd8b70d920039/cryptography-50.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2a93d05e34d5f67fba6f891fe85d929999baa7195e853923ea6d7576c9e68c5e", size = 5044355, upload-time = "2026-08-25T19:45:22.353Z" }, + { url = "https://files.pythonhosted.org/packages/99/89/87ef49ffe383ef4e147d27b7bf2088fb0b54ea409dd87b5a89442e5828a5/cryptography-50.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:55d16b1ef3ee0958d893a977b19777887e546c9954ea81b200c3301a864013f2", size = 3875429, upload-time = "2026-08-25T19:45:24.418Z" }, + { url = "https://files.pythonhosted.org/packages/c7/27/8d207af749c453ee17ea087340b3f2b4adef75aadd1d277b1b129bdda84e/cryptography-50.0.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9cb3cb952cf5a8abd50c782a98a89d71699715e802fe349704b47f2425b42a94", size = 3974350, upload-time = "2026-08-25T19:45:26.551Z" }, + { url = "https://files.pythonhosted.org/packages/14/9a/6d3a4d7852e22d657438b7bf51f66102c7d71c0e1fafeec652281d0403e5/cryptography-50.0.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5fe939deeb161024a6be98229c953b6591fef1f41214497a78fe793a244c017f", size = 4698675, upload-time = "2026-08-25T19:45:28.658Z" }, + { url = "https://files.pythonhosted.org/packages/73/35/5c3717edf9e68a0550ce04e28eab493fe545eccd81742af03f6a75fe260b/cryptography-50.0.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fb4b9672d389c738b175c4166e78310f8a70358886aacd9173ee03a85ffdc671", size = 4707410, upload-time = "2026-08-25T19:45:30.816Z" }, + { url = "https://files.pythonhosted.org/packages/1d/e0/e786934472e3ac4ecdecc7b129a0ca1a2a40dffdafcf2c3ea9d4397f8def/cryptography-50.0.1-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d63ae8f6481fec907ac0f588eee8a90aefde112c633131fe540e5711ddbb5a4e", size = 4698378, upload-time = "2026-08-25T19:45:33.043Z" }, + { url = "https://files.pythonhosted.org/packages/51/cf/5b3f53a0b74d122f023476ede40ba5d3e70d5cf475f73b899740d26a4fb2/cryptography-50.0.1-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:804728ce710890870f3aaa344b2e161172d258d768ac139d02cfd9092d0d94e6", size = 4706889, upload-time = "2026-08-25T19:45:35.086Z" }, + { url = "https://files.pythonhosted.org/packages/71/44/711e61f7d014be825ef79b285b047292d1bf893732ac1bc030a351fb517f/cryptography-50.0.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:693c99b49bd37d0d096e4334c10232c77248c415b98d35236094cdf96d57258b", size = 3824006, upload-time = "2026-08-25T19:45:37.281Z" }, ] [[package]] @@ -753,7 +794,7 @@ name = "cuda-bindings" version = "13.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cuda-pathfinder", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "cuda-pathfinder" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/51/6b/457ca12dad3ee9bfcc9a545cfd6b64b359ba49de40f776f6e028e678f262/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5879712accf6e14bb01aa5e67440eb84998b8d104b509cc7a6dc0b8f656a474", size = 6053539, upload-time = "2026-05-29T23:11:43.19Z" }, @@ -786,43 +827,43 @@ wheels = [ [package.optional-dependencies] cublas = [ - { name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] cudart = [ - { name = "nvidia-cuda-runtime", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cuda-runtime", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] cufft = [ - { name = "nvidia-cufft", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cufft", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] cufile = [ - { name = "nvidia-cufile", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cufile", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] cupti = [ - { name = "nvidia-cuda-cupti", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cuda-cupti", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] curand = [ - { name = "nvidia-curand", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-curand", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] cusolver = [ - { name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nvidia-cusolver", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cusolver", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] cusparse = [ - { name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] nvjitlink = [ - { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] nvrtc = [ - { name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] nvtx = [ - { name = "nvidia-nvtx", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-nvtx", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] [[package]] @@ -1164,11 +1205,11 @@ wheels = [ [[package]] name = "idna" -version = "3.18" +version = "3.19" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, + { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] [[package]] @@ -1268,15 +1309,15 @@ name = "lightning" version = "2.6.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "fsspec", extra = ["http"], marker = "python_full_version < '3.13'" }, - { name = "lightning-utilities", marker = "python_full_version < '3.13'" }, - { name = "packaging", marker = "python_full_version < '3.13'" }, - { name = "pytorch-lightning", marker = "python_full_version < '3.13'" }, - { name = "pyyaml", marker = "python_full_version < '3.13'" }, - { name = "torch", marker = "python_full_version < '3.13'" }, - { name = "torchmetrics", marker = "python_full_version < '3.13'" }, - { name = "tqdm", marker = "python_full_version < '3.13'" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "fsspec", extra = ["http"] }, + { name = "lightning-utilities" }, + { name = "packaging" }, + { name = "pytorch-lightning" }, + { name = "pyyaml" }, + { name = "torch" }, + { name = "torchmetrics" }, + { name = "tqdm" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c9/1d/83be8536bec71a0173e762a9a1fd92a24a5ad0d0f74c59550c3c4e6c103b/lightning-2.6.5.tar.gz", hash = "sha256:16a30310ed69afde3748491feb5d13508908effd70390d2bfc203dc0812a4b4a", size = 659201, upload-time = "2026-05-27T14:33:41.806Z" } wheels = [ @@ -1288,8 +1329,8 @@ name = "lightning-utilities" version = "0.15.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "packaging", marker = "python_full_version < '3.13'" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "packaging" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f1/45/7fa8f56b17dc0f0a41ec70dd307ecd6787254483549843bef4c30ab5adce/lightning_utilities-0.15.3.tar.gz", hash = "sha256:792ae0204c79f6859721ac7f386c237a33b0ed06ba775009cb894e010a842033", size = 33553, upload-time = "2026-02-22T14:48:53.348Z" } wheels = [ @@ -1722,7 +1763,7 @@ name = "nvidia-cublas" version = "13.1.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cuda-nvrtc", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "nvidia-cuda-nvrtc" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" }, @@ -1761,7 +1802,7 @@ name = "nvidia-cudnn-cu13" version = "9.20.0.48" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "nvidia-cublas" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/56/c5/83384d846b2fd17c44bd499b36c75a45ed4f095fbbb2252294e89cea5c5c/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:e31454ae00094b0c55319d9d15b6fa2fc50a9e1c0f5c8c80fb75258234e731e1", size = 444574296, upload-time = "2026-03-09T19:28:27.751Z" }, @@ -1773,7 +1814,7 @@ name = "nvidia-cufft" version = "12.0.0.61" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "nvidia-nvjitlink" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, @@ -1803,9 +1844,9 @@ name = "nvidia-cusolver" version = "12.0.4.66" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "nvidia-cusparse", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "nvidia-cublas" }, + { name = "nvidia-cusparse" }, + { name = "nvidia-nvjitlink" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, @@ -1817,7 +1858,7 @@ name = "nvidia-cusparse" version = "12.6.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "nvidia-nvjitlink" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, @@ -1874,8 +1915,8 @@ name = "omegaconf" version = "2.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "antlr4-python3-runtime", marker = "python_full_version < '3.13'" }, - { name = "pyyaml", marker = "python_full_version < '3.13'" }, + { name = "antlr4-python3-runtime" }, + { name = "pyyaml" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ce/3d/e4b57b8d9008c6ebe0d5eff901f91d5700cf7bdb8c8863df817463a7fd5e/omegaconf-2.3.1.tar.gz", hash = "sha256:e5e7de64aeebeddaf8e6d3f7a783b32ac2a01c0fbd9c878012caecb891a1f42a", size = 3298472, upload-time = "2026-06-11T05:05:12.885Z" } wheels = [ @@ -2708,11 +2749,11 @@ wheels = [ [[package]] name = "pygments" -version = "2.20.0" +version = "2.21.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, + { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, ] [[package]] @@ -2916,14 +2957,14 @@ name = "pytorch-lightning" version = "2.6.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "fsspec", extra = ["http"], marker = "python_full_version < '3.13'" }, - { name = "lightning-utilities", marker = "python_full_version < '3.13'" }, - { name = "packaging", marker = "python_full_version < '3.13'" }, - { name = "pyyaml", marker = "python_full_version < '3.13'" }, - { name = "torch", marker = "python_full_version < '3.13'" }, - { name = "torchmetrics", marker = "python_full_version < '3.13'" }, - { name = "tqdm", marker = "python_full_version < '3.13'" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "fsspec", extra = ["http"] }, + { name = "lightning-utilities" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "torch" }, + { name = "torchmetrics" }, + { name = "tqdm" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/52/2c/8e73a3929b4c4bd600cafd38a97aaf7242a8cf518fb9f33d27c274ec898f/pytorch_lightning-2.6.5.tar.gz", hash = "sha256:1c32cefa76a1a9c4c5250338272d961d1e48b180e68396849efe128538ddb28e", size = 661673, upload-time = "2026-05-27T14:33:41.961Z" } wheels = [ @@ -3131,7 +3172,7 @@ wheels = [ [[package]] name = "remove-ai-watermarks" -version = "0.30.0" +version = "0.30.1" source = { editable = "." } dependencies = [ { name = "c2pa-python" }, @@ -3684,10 +3725,10 @@ name = "torchmetrics" version = "1.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "lightning-utilities", marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "packaging", marker = "python_full_version < '3.13'" }, - { name = "torch", marker = "python_full_version < '3.13'" }, + { name = "lightning-utilities" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "torch" }, ] sdist = { url = "https://files.pythonhosted.org/packages/81/34/39b8b749333db56c0585d7a11fa62a283c087bb1dfc897d69fb8cedbefb1/torchmetrics-1.9.0.tar.gz", hash = "sha256:a488609948600df52d3db4fcdab02e62aab2a85ef34da67037dc3e65b8512faa", size = 581765, upload-time = "2026-03-09T17:41:22.443Z" } wheels = [ @@ -3782,13 +3823,13 @@ name = "trustmark" version = "0.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "einops", marker = "python_full_version < '3.13'" }, - { name = "lightning", marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "omegaconf", marker = "python_full_version < '3.13'" }, - { name = "six", marker = "python_full_version < '3.13'" }, - { name = "torch", marker = "python_full_version < '3.13'" }, - { name = "torchvision", marker = "python_full_version < '3.13'" }, + { name = "einops" }, + { name = "lightning" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, + { name = "omegaconf" }, + { name = "six" }, + { name = "torch" }, + { name = "torchvision" }, ] sdist = { url = "https://files.pythonhosted.org/packages/87/0a/0a4232030c6a62d12b6a02ae73bdce6e99c8532bc8f05a5a2e6ce103da82/trustmark-0.9.1.tar.gz", hash = "sha256:dc79e3fb070f5d94765acf8868a51f50a612cc05b53223cf1e6b605d4ff1e0ae", size = 63949, upload-time = "2026-04-09T08:59:52.472Z" } @@ -3944,14 +3985,14 @@ wheels = [ [[package]] name = "wheel" -version = "0.47.0" +version = "0.48.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/39/62/75f18a0f03b4219c456652c7780e4d749b929eb605c098ce3a5b6b6bc081/wheel-0.47.0.tar.gz", hash = "sha256:cc72bd1009ba0cf63922e28f94d9d83b920aa2bb28f798a31d0691b02fa3c9b3", size = 63854, upload-time = "2026-04-22T15:51:27.727Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/20/50ed6bdf27dec98b568a8ae25dc599f35baa3d9709f9e83fd1edb56b9a90/wheel-0.48.0.tar.gz", hash = "sha256:94800765601e9171bf5d58d066e640662842bcedcbab982b2c90787a2c987322", size = 66471, upload-time = "2026-08-11T22:02:27.327Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/1b/9e33c09813d65e248f7f773119148a612516a4bea93e9c6f545f78455b7c/wheel-0.47.0-py3-none-any.whl", hash = "sha256:212281cab4dff978f6cedd499cd893e1f620791ca6ff7107cf270781e587eced", size = 32218, upload-time = "2026-04-22T15:51:26.296Z" }, + { url = "https://files.pythonhosted.org/packages/2e/29/69cfbb602cd91690c55d38ba9fe53e6a7e76a6fa647bf38f19c138d25449/wheel-0.48.0-py3-none-any.whl", hash = "sha256:3217dcc807155e45db462d7ef2431f5ddda0d7273b700d05a67b271ceb1287ab", size = 33320, upload-time = "2026-08-11T22:02:26.1Z" }, ] [[package]] From c9275606145529c77c142652818ccb98b9509957 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Tue, 25 Aug 2026 20:04:25 -0700 Subject: [PATCH 42/44] Stop gating C2PA confidence on a trust anchor that never ships High-confidence C2PA attribution required signingCredential.trusted, a status code the reader emits only when a trust anchor list is loaded. None ships, so from 0.27.0 through 0.30.0 the branch was unreachable in production for every vendor: an intact, cryptographically bound manifest scored the same medium as a fallback parse that validated nothing, which collapsed the one distinction the official reader exists to draw. A hand-built info dict stamping that code kept the branch green in the suite. Confidence now follows the binding. Signer trust and certificate expiry stay visible as their own dimensions and as caveats, because a trust list that was never configured is a missing input, not a finding against the credential. Every committed provenance fixture with a reader result and an intact binding now reaches high confidence, and test_no_committed_fixture_reports_a_trusted_signer guards the reachability itself rather than a synthesized status set. Revocation joins binding and signature failures as disqualifying. It arrives only on signer_validity, so a check reading the other two returned a confident AI verdict off a credential the issuer had disowned, with an empty integrity_clashes -- quieter than a hash mismatch on the same file. Expiry stays non-disqualifying: it does not imply the signed bytes changed, and a signature genuinely made outside validity already arrives as claimSignature.outsideValidity. The rule now lives in one place. _validation_fields maps status codes to the four dimensions and names the failures that moved one; c2pa_info_has_invalid_credential maps dimensions to disqualified. The ingredient-reachability walk and the user-visible reason both consume that path instead of re-classifying raw codes, so adding this one rule no longer means editing three layers in lockstep. Co-Authored-By: Claude Opus 5 --- .claude/rules/development.md | 14 +++ docs/cli.md | 8 +- docs/module-internals.md | 48 +++++++-- docs/python-api.md | 23 ++++- docs/supported-signals.md | 8 +- src/remove_ai_watermarks/_internal/c2pa.py | 50 ++++++++-- src/remove_ai_watermarks/identify.py | 59 ++++++----- tests/test_identify.py | 109 +++++++++++++++++++-- tests/test_metadata_internals.py | 27 ++--- 9 files changed, 273 insertions(+), 73 deletions(-) diff --git a/.claude/rules/development.md b/.claude/rules/development.md index 9db0237..90aa39a 100644 --- a/.claude/rules/development.md +++ b/.claude/rules/development.md @@ -65,6 +65,15 @@ that is what actually happened. Use availability checks only for paths that actually load large models. +A verdict branch must be reachable with the dependency stack as shipped. High-confidence +C2PA attribution required `signingCredential.trusted`, a code the reader emits only when a +trust anchor list is loaded, and none ships -- so the branch was dead in production for +every vendor while a hand-built info dict stamping that code kept it green from 0.27.0 +through 0.30.0. Assert reachability against the committed fixtures rather than a synthesized +status set; `TestIdentifyRealSamples::test_no_committed_fixture_reports_a_trusted_signer` +is that guard. Read a capability the stack does not have as a missing input, never as a +negative finding. + ## One measurement, one gate seam A detector is split into a trust-level-blind scan and a verdict that applies the @@ -98,6 +107,11 @@ record is byte-identical, and a green test suite does not establish that on its change that is meant to FIX detection is the exception that proves the rule: the diff must then be exactly the files you intended to change, named in advance. +When the baseline is a published release rather than the previous commit, get it with +`uv run --isolated --no-project --with 'remove-ai-watermarks[heif]==' python