mirror of
https://github.com/wiltodelta/remove-ai-watermarks.git
synced 2026-08-10 08:00:32 +02:00
The CLI still advertised --model, --steps, --guidance-scale, --device and a deprecated --auto. Each pinned a value the two surviving profiles fix -- the model stack, the per-stage distilled schedule, CFG 1.0, CUDA -- so the only outcome any of them had was an error raised several frames below the caller, under a message naming an internal profile. A flag whose sole result is a refusal is worse than no flag: it advertises a capability that does not exist, and it lets a wrapper thread a value that will silently do nothing. They are gone from the parser, from InvisibleEngine, and from WatermarkRemover, so the failure is now a TypeError or a Click "No such option" at the point the caller can act on. The install hint was wrong in the same way. is_available() checked torch and diffusers, then told the user to install [diffusion] -- which contains neither DiffSynth nor the Z-Image face stage both profiles run. Following the advice produced a second, different failure. The module list and the extra name now live once in watermark_profiles (REMOVAL_MODULES, INVISIBLE_EXTRA) and are read by both the CLI gate and the remover's precondition, which cannot drift apart because they are the same tuple. The adaptive-polish default moved out of the argument parser. It was resolved by reading Click's parameter source, which put per-profile data in the CLI layer, left the engine declaring the opposite default (False vs True) so a library caller and a CLI caller on one profile got different output, and lost the polish entirely for anything that supplies the flag non-interactively. The flag is now tri-state (default=None) and resolve_adaptive_polish owns the per-profile answer. The seed follows the same rule: the CLI stopped pre-resolving it. Dead code removed with it: six scan_*_video wrappers and the _scan_video helper none of them had a caller for, PNG_METADATA_KEYS, feather_region_composite and the remover region path that was only reachable from a no-caller convenience wrapper, remove_watermark_batch on both layers, try_empty_device_cache, the _generate/_run_qwen_zimage pass-through pair, self.model_id, and the _internal PEP 562 shim that no caller ever went through. get_device now answers cuda or cpu only: mps and xpu travelled one frame to the same CUDA-only refusal while costing a device probe each, and that refusal now names the resolved device, so device=None on a CUDA-less host says 'cpu' rather than 'None'. The XPU wheel index went with them. Docs: README, cli, installation, python-api, supported-signals, known-limitations and module-internals all still described the removed profiles, the CPU/MPS/XPU ladder, a `default`->`sdxl` alias, and the wrong extra. known-limitations still listed the retired SDXL strength ladder as current. scripts/smoke_matrix.py and real_examples_e2e.py drove --device mps. Next release is 0.25.0, not a patch: this removes public parameters and narrows a published extra on top of the released 0.24.0. pre-commit: 1) maintain.sh - exit 0 (1091 tests, Pyright 0 errors, no vulnerabilities); 2) /simplify - 4 agents, 11 findings applied, 2 skipped (dropping the `device` parameter entirely, which raiw-app pins; folding diffsynth into the `diffusion` extra, which video-only callers do not need); 3) docs sync - grepped every removed identifier across README, docs/, scripts/, .claude/; updated 9 docs; 4) CLAUDE.md - added the no-error-only-knobs rule to .claude/rules/development.md Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
240 lines
8.6 KiB
TOML
240 lines
8.6 KiB
TOML
[project]
|
|
name = "remove-ai-watermarks"
|
|
version = "0.24.0"
|
|
description = "AI watermark remover for visible, invisible, and provenance marks in images and video"
|
|
readme = "README.md"
|
|
requires-python = ">=3.10.1"
|
|
license = {text = "Apache-2.0"}
|
|
keywords = [
|
|
"ai-watermark",
|
|
"ai-watermark-remover",
|
|
"watermark-remover",
|
|
"watermark-removal",
|
|
"remove-watermark",
|
|
"synthid",
|
|
"c2pa",
|
|
"content-credentials",
|
|
"nano-banana",
|
|
"gemini",
|
|
"gemini-watermark",
|
|
"ai-metadata",
|
|
"metadata-removal",
|
|
"provenance",
|
|
"exif",
|
|
"stable-diffusion",
|
|
"comfyui",
|
|
"ai-detection",
|
|
]
|
|
classifiers = [
|
|
"Development Status :: 4 - Beta",
|
|
"Environment :: Console",
|
|
"Intended Audience :: Developers",
|
|
"Intended Audience :: End Users/Desktop",
|
|
"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",
|
|
"Topic :: Multimedia :: Graphics",
|
|
"Topic :: Multimedia :: Graphics :: Graphics Conversion",
|
|
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
"Topic :: Scientific/Engineering :: Image Processing",
|
|
"Topic :: Security",
|
|
"Topic :: Utilities",
|
|
]
|
|
dependencies = [
|
|
"pillow>=10.0.0",
|
|
"piexif>=1.1.3",
|
|
"click>=8.0.0",
|
|
"python-dotenv>=1.0.0",
|
|
# Official C2PA reader (Content Authenticity Initiative, MIT/Apache-2.0). The
|
|
# primary, spec-tracking manifest parser for the identify/metadata path; the
|
|
# hand-rolled caBX/CBOR scanner in _internal/c2pa.py is kept only as a fallback for
|
|
# synthetic/partial blobs the validator rejects. Binary wheel (Rust), but the
|
|
# import is light (no torch/numpy) so it fits the dependency-light identify
|
|
# host. Prebuilt wheels cover the full CI matrix (linux/macos/windows).
|
|
"c2pa-python>=0.35.0",
|
|
]
|
|
|
|
[project.optional-dependencies]
|
|
pixels = [
|
|
"numpy>=1.24.0",
|
|
"opencv-python-headless>=4.8.0",
|
|
]
|
|
# Optional HEIC/AVIF pixel decode. Metadata scanning handles these containers
|
|
# without this plugin; combine `heif` with any pixel feature only when needed.
|
|
heif = [
|
|
"pillow-heif>=0.13.0",
|
|
]
|
|
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.
|
|
video = [
|
|
"remove-ai-watermarks[visible]",
|
|
"av>=16,<17; python_version < '3.11'",
|
|
"av>=18,<19; python_version >= '3.11'",
|
|
]
|
|
# Open DWT-DCT watermarks used by Stable Diffusion / SDXL / FLUX. The in-tree
|
|
# decoder avoids the upstream invisible-watermark package's mandatory torch and
|
|
# non-headless OpenCV dependencies.
|
|
detect = [
|
|
"remove-ai-watermarks[pixels]",
|
|
"PyWavelets>=1.1.1",
|
|
]
|
|
diffusion = [
|
|
"remove-ai-watermarks[pixels]",
|
|
# A CUDA-enabled torch build is required: invisible-watermark removal has no
|
|
# CPU, MPS or XPU path. The default PyPI wheel carries CUDA on Linux/Windows;
|
|
# on macOS there is no CUDA build and this extra installs only for the
|
|
# non-diffusion imports it shares.
|
|
"torch>=2.0.0",
|
|
"diffusers>=0.38.0",
|
|
# diffusers 0.38's auto-pipeline registry imports ``Qwen3VLForConditional
|
|
# Generation`` (its ``nucleusmoe_image`` pipeline), which only exists in
|
|
# transformers 5.x -- so ``from diffusers import AutoPipelineForImage2Image``
|
|
# fails on transformers 4.x. The real SDXL-loading break was NOT transformers
|
|
# 5.x but the tokenizers *release candidate* (0.23.0rc0) that the global
|
|
# ``prerelease = "allow"`` drags in: its CLIP tokenizer raises
|
|
# ``RobertaProcessing.__new__() got an unexpected keyword argument 'cls'``.
|
|
# Cap tokenizers to the stable 0.22 line (transformers 5.x accepts
|
|
# >=0.22,<=0.23.0) so the rc is excluded while SDXL still loads.
|
|
"transformers>=5,<6",
|
|
"tokenizers>=0.22,<0.23",
|
|
"accelerate>=0.25.0",
|
|
"safetensors",
|
|
]
|
|
# Full two-stage high-fidelity profile: Qwen-Image-2512 Lightning + DiffSynth
|
|
# Canny ControlNet for the frame, then SAM-masked Z-Image Turbo face repair.
|
|
# CUDA-only and intentionally separate from the normal diffusion extra because the
|
|
# additional model stack and DiffSynth runtime are large.
|
|
qwen-zimage = [
|
|
"remove-ai-watermarks[diffusion]",
|
|
"diffsynth>=2.0.17,<3",
|
|
"torchvision>=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()
|
|
# guards the import and skips the TrustMark signal when absent.
|
|
trustmark = [
|
|
"trustmark>=0.8.0",
|
|
]
|
|
# 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
|
|
# cached by huggingface_hub; it is never bundled in this repo. The default cv2
|
|
# 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'",
|
|
"huggingface-hub>=0.20.0",
|
|
]
|
|
# Lightweight inpaint backend -- MI-GAN via onnxruntime (andraniksargsyan/migan,
|
|
# MIT). CPU, no torch. Model (~28 MB) downloaded on first use and cached by
|
|
# huggingface_hub; never bundled. ~700-950 MB peak RAM / ~0.19 s/call -- the
|
|
# memory-tight learned tier (vs big-LaMa's ~4.7 GB). Select it explicitly when
|
|
# 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'",
|
|
"huggingface-hub>=0.20.0",
|
|
]
|
|
dev = [
|
|
"remove-ai-watermarks[video]",
|
|
"remove-ai-watermarks[detect]",
|
|
"pytest>=8.0.0",
|
|
"pytest-cov>=4.1.0",
|
|
"pytest-xdist>=3.5.0",
|
|
"packaging>=24.0",
|
|
"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.
|
|
all = ["remove-ai-watermarks[video,heif,detect,trustmark,qwen-zimage,lama,migan]"]
|
|
|
|
[project.scripts]
|
|
remove-ai-watermarks = "remove_ai_watermarks.cli:main"
|
|
|
|
[project.urls]
|
|
Repository = "https://github.com/wiltodelta/remove-ai-watermarks"
|
|
|
|
[build-system]
|
|
requires = ["hatchling"]
|
|
build-backend = "hatchling.build"
|
|
|
|
[tool.hatch.build.targets.wheel]
|
|
packages = ["src/remove_ai_watermarks"]
|
|
|
|
[tool.hatch.build.targets.sdist]
|
|
# Keep the source distribution small and public-safe: ship tracked source and
|
|
# metadata, not corpora or local research/session artifacts. The wheel ships
|
|
# only src/.
|
|
include = [
|
|
"/src",
|
|
"/LICENSE",
|
|
"/README.md",
|
|
"/pyproject.toml",
|
|
]
|
|
exclude = [
|
|
"/data",
|
|
"/tmp",
|
|
"/.sc",
|
|
]
|
|
|
|
[tool.pytest.ini_options]
|
|
testpaths = ["tests"]
|
|
pythonpath = ["src"]
|
|
addopts = "-v --tb=short"
|
|
|
|
[tool.ruff]
|
|
target-version = "py310"
|
|
line-length = 120
|
|
exclude = ["_refs"]
|
|
extend-exclude = ["*.md"]
|
|
|
|
[tool.ruff.lint]
|
|
select = ["E", "F", "B", "I", "S", "UP", "SIM", "RET", "COM", "C4", "G", "PT", "PIE", "T20", "DTZ", "ICN", "TCH", "RUF", "ANN"]
|
|
ignore = [
|
|
"COM812", # missing trailing comma (conflicts with ruff formatter)
|
|
"ANN401", # typing.Any — sometimes unavoidable with third-party libs
|
|
]
|
|
|
|
[tool.ruff.lint.per-file-ignores]
|
|
"scripts/*.py" = ["G004", "S108", "S310", "T20"]
|
|
"tests/*.py" = ["ANN", "S101", "S105", "S106", "S108"]
|
|
"src/remove_ai_watermarks/_internal/watermark_remover.py" = ["S603", "S606", "S607"] # nvidia-smi capability probe
|
|
"src/remove_ai_watermarks/_internal/c2pa.py" = ["S110"] # try-except-pass for corrupt file handling
|
|
|
|
[tool.ruff.format]
|
|
quote-style = "double"
|
|
indent-style = "space"
|
|
|
|
[tool.pyright]
|
|
pythonVersion = "3.10"
|
|
typeCheckingMode = "strict"
|
|
exclude = ["_refs"]
|
|
|
|
[[tool.pyright.executionEnvironments]]
|
|
root = "tests"
|
|
extraPaths = ["."]
|
|
reportAttributeAccessIssue = false
|
|
reportOptionalSubscript = false
|
|
reportOptionalMemberAccess = false
|
|
reportArgumentType = false
|
|
reportUnknownMemberType = false
|
|
reportUnknownArgumentType = false
|
|
reportUnknownVariableType = false
|
|
reportMissingTypeArgument = false
|