Files
remove-ai-watermarks/pyproject.toml
T
Victor KuznetsovandClaude Opus 5 38399e4eb0 Release 0.25.0
Breaking, on top of the released 0.24.0.

Removed from the CLI: --model, --steps, --guidance-scale, --device, and the
deprecated --auto. Removed from InvisibleEngine and WatermarkRemover: the
model_id, num_inference_steps and guidance_scale parameters, remove_watermark_batch,
and the remover's region/region_feather path. Each pinned a value the two
remaining profiles fix -- the model stack, the per-stage distilled schedule,
CFG 1.0, CUDA -- so their only outcome was an error raised several frames below
the caller.

Also removed: the public remove_ai_watermarks.upscaler module, the
--min-resolution and --upscaler options and the published esrgan extra (0.24.0
shipped them unreachable); the pytorch-xpu index; and "diffusion" from the "all"
extra, which qwen-zimage already pulls.

Behaviour changes a caller can see:
- invisible-watermark removal now requires the qwen-zimage extra, not diffusion.
  Both profiles run the DiffSynth Z-Image face stage, so a torch+diffusers-only
  environment used to pass the availability gate and then die there. Every
  install hint names qwen-zimage now.
- get_device() answers cuda or cpu only, and the CUDA-only refusal names the
  resolved device rather than the raw argument.
- adaptive_polish is tri-state. Unset follows the profile (off for qwen-zimage,
  on for sdxl-zimage) and is resolved inside the engine, so a library caller and
  a CLI caller on one profile now produce the same pixels; they did not before.

ComfyUI node 0.1.15 is already published and tracks this surface.

pre-commit: 1) maintain.sh - exit 0 (1093 tests, Pyright 0 errors, no
vulnerabilities); 2) /simplify - n/a, version bump only; 3) docs sync - version
appears in pyproject.toml, __init__.py and uv.lock, all three updated; 4)
CLAUDE.md - no rule change

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 16:59:10 -07:00

240 lines
8.6 KiB
TOML

[project]
name = "remove-ai-watermarks"
version = "0.25.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