wiltodelta/remove-ai-watermarksPublic

Remove visible and invisible AI watermarks and provenance metadata from images and video. Python library and CLI for SynthID, C2PA, EXIF, IPTC, XMP, and common generative-AI marks.

AI summary: A comprehensive library and CLI tool for removing both visible and invisible AI provenance watermarks from media.

Stars
5.7K
+29 today
Forks
527
Watchers
15
Open issues
1
Open PRs
1
Contributors
~8
Commits
658
Branches
2

PythonApache-2.0Created Mar 25, 2026Last push 2d agoLatest release v0.42.0+88 stars this week+355 this month

Quick answers

What is remove-ai-watermarks?
A comprehensive library and CLI tool for removing both visible and invisible AI provenance watermarks from media.
What does remove-ai-watermarks do?
Remove AI Watermarks provides a robust suite of tools designed specifically for eliminating AI provenance marks from user-generated images and videos. It effectively strips out well-known visible labels, such as Gemini sparkles and specific vendor text marks. Beyond visible elements, the software completely regenerates invisible pixel watermarks using advanced diffusion models. The tool meticulously cleans out embedded metadata including C2PA, EXIF, XMP, and IPTC standards that denote AI generation. It includes extensive support for various video formats, handling visible mark removal for tools like Sora, Veo, and Kling.
Who is remove-ai-watermarks for?
Content creators, researchers, and developers who legally generate their own AI media and wish to remove automatic vendor tracking marks. It requires basic command-line proficiency and an understanding of media metadata structures.
How do I get started with remove-ai-watermarks?
https://raiw.cc
How popular is remove-ai-watermarks on GitHub?
wiltodelta/remove-ai-watermarks has 5,749 stars and 527 forks on GitHub, and gained 88 stars in the last 7 days.
What license does remove-ai-watermarks use?
wiltodelta/remove-ai-watermarks is released under the Apache-2.0 license.

Star history

since Jul 29, 2026
02K4KJul 2026Aug 2026Sep 2026Oct 2026
5.7K stars as of Oct 3, 2026. Measured daily since Jul 29, 2026; GitHub no longer exposes earlier star timestamps.

Contribution activity

commits per day, last 52 weeks
OctNovDecJanFebMarAprMayJunJulAugSepMonWedFri2025-10-05: 0 commits2025-10-06: 0 commits2025-10-07: 0 commits2025-10-08: 0 commits2025-10-09: 0 commits2025-10-10: 0 commits2025-10-11: 0 commits2025-10-12: 0 commits2025-10-13: 0 commits2025-10-14: 0 commits2025-10-15: 0 commits2025-10-16: 0 commits2025-10-17: 0 commits2025-10-18: 0 commits2025-10-19: 0 commits2025-10-20: 0 commits2025-10-21: 0 commits2025-10-22: 0 commits2025-10-23: 0 commits2025-10-24: 0 commits2025-10-25: 0 commits2025-10-26: 0 commits2025-10-27: 0 commits2025-10-28: 0 commits2025-10-29: 0 commits2025-10-30: 0 commits2025-10-31: 0 commits2025-11-01: 0 commits2025-11-02: 0 commits2025-11-03: 0 commits2025-11-04: 0 commits2025-11-05: 0 commits2025-11-06: 0 commits2025-11-07: 0 commits2025-11-08: 0 commits2025-11-09: 0 commits2025-11-10: 0 commits2025-11-11: 0 commits2025-11-12: 0 commits2025-11-13: 0 commits2025-11-14: 0 commits2025-11-15: 0 commits2025-11-16: 0 commits2025-11-17: 0 commits2025-11-18: 0 commits2025-11-19: 0 commits2025-11-20: 0 commits2025-11-21: 0 commits2025-11-22: 0 commits2025-11-23: 0 commits2025-11-24: 0 commits2025-11-25: 0 commits2025-11-26: 0 commits2025-11-27: 0 commits2025-11-28: 0 commits2025-11-29: 0 commits2025-11-30: 0 commits2025-12-01: 0 commits2025-12-02: 0 commits2025-12-03: 0 commits2025-12-04: 0 commits2025-12-05: 0 commits2025-12-06: 0 commits2025-12-07: 0 commits2025-12-08: 0 commits2025-12-09: 0 commits2025-12-10: 0 commits2025-12-11: 0 commits2025-12-12: 0 commits2025-12-13: 0 commits2025-12-14: 0 commits2025-12-15: 0 commits2025-12-16: 0 commits2025-12-17: 0 commits2025-12-18: 0 commits2025-12-19: 0 commits2025-12-20: 0 commits2025-12-21: 0 commits2025-12-22: 0 commits2025-12-23: 0 commits2025-12-24: 0 commits2025-12-25: 0 commits2025-12-26: 0 commits2025-12-27: 0 commits2025-12-28: 0 commits2025-12-29: 0 commits2025-12-30: 0 commits2025-12-31: 0 commits2026-01-01: 0 commits2026-01-02: 0 commits2026-01-03: 0 commits2026-01-04: 0 commits2026-01-05: 0 commits2026-01-06: 0 commits2026-01-07: 0 commits2026-01-08: 0 commits2026-01-09: 0 commits2026-01-10: 0 commits2026-01-11: 0 commits2026-01-12: 0 commits2026-01-13: 0 commits2026-01-14: 0 commits2026-01-15: 0 commits2026-01-16: 0 commits2026-01-17: 0 commits2026-01-18: 0 commits2026-01-19: 0 commits2026-01-20: 0 commits2026-01-21: 0 commits2026-01-22: 0 commits2026-01-23: 0 commits2026-01-24: 0 commits2026-01-25: 0 commits2026-01-26: 0 commits2026-01-27: 0 commits2026-01-28: 0 commits2026-01-29: 0 commits2026-01-30: 0 commits2026-01-31: 0 commits2026-02-01: 0 commits2026-02-02: 0 commits2026-02-03: 0 commits2026-02-04: 0 commits2026-02-05: 0 commits2026-02-06: 0 commits2026-02-07: 0 commits2026-02-08: 0 commits2026-02-09: 0 commits2026-02-10: 0 commits2026-02-11: 0 commits2026-02-12: 0 commits2026-02-13: 0 commits2026-02-14: 0 commits2026-02-15: 0 commits2026-02-16: 0 commits2026-02-17: 0 commits2026-02-18: 0 commits2026-02-19: 0 commits2026-02-20: 0 commits2026-02-21: 0 commits2026-02-22: 0 commits2026-02-23: 0 commits2026-02-24: 0 commits2026-02-25: 0 commits2026-02-26: 0 commits2026-02-27: 0 commits2026-02-28: 0 commits2026-03-01: 0 commits2026-03-02: 0 commits2026-03-03: 0 commits2026-03-04: 0 commits2026-03-05: 0 commits2026-03-06: 0 commits2026-03-07: 0 commits2026-03-08: 0 commits2026-03-09: 0 commits2026-03-10: 0 commits2026-03-11: 0 commits2026-03-12: 0 commits2026-03-13: 0 commits2026-03-14: 0 commits2026-03-15: 0 commits2026-03-16: 0 commits2026-03-17: 0 commits2026-03-18: 0 commits2026-03-19: 0 commits2026-03-20: 0 commits2026-03-21: 0 commits2026-03-22: 0 commits2026-03-23: 0 commits2026-03-24: 1 commit2026-03-25: 14 commits2026-03-26: 4 commits2026-03-27: 1 commit2026-03-28: 0 commits2026-03-29: 0 commits2026-03-30: 1 commit2026-03-31: 1 commit2026-04-01: 5 commits2026-04-02: 0 commits2026-04-03: 1 commit2026-04-04: 0 commits2026-04-05: 0 commits2026-04-06: 0 commits2026-04-07: 0 commits2026-04-08: 1 commit2026-04-09: 0 commits2026-04-10: 0 commits2026-04-11: 0 commits2026-04-12: 0 commits2026-04-13: 0 commits2026-04-14: 0 commits2026-04-15: 0 commits2026-04-16: 0 commits2026-04-17: 0 commits2026-04-18: 0 commits2026-04-19: 0 commits2026-04-20: 0 commits2026-04-21: 0 commits2026-04-22: 1 commit2026-04-23: 1 commit2026-04-24: 0 commits2026-04-25: 0 commits2026-04-26: 1 commit2026-04-27: 0 commits2026-04-28: 0 commits2026-04-29: 0 commits2026-04-30: 0 commits2026-05-01: 0 commits2026-05-02: 0 commits2026-05-03: 0 commits2026-05-04: 0 commits2026-05-05: 0 commits2026-05-06: 0 commits2026-05-07: 0 commits2026-05-08: 0 commits2026-05-09: 0 commits2026-05-10: 2 commits2026-05-11: 0 commits2026-05-12: 0 commits2026-05-13: 0 commits2026-05-14: 0 commits2026-05-15: 0 commits2026-05-16: 0 commits2026-05-17: 5 commits2026-05-18: 0 commits2026-05-19: 0 commits2026-05-20: 1 commit2026-05-21: 0 commits2026-05-22: 0 commits2026-05-23: 1 commit2026-05-24: 15 commits2026-05-25: 22 commits2026-05-26: 11 commits2026-05-27: 9 commits2026-05-28: 9 commits2026-05-29: 4 commits2026-05-30: 11 commits2026-05-31: 19 commits2026-06-01: 5 commits2026-06-02: 5 commits2026-06-03: 13 commits2026-06-04: 4 commits2026-06-05: 1 commit2026-06-06: 0 commits2026-06-07: 0 commits2026-06-08: 35 commits2026-06-09: 7 commits2026-06-10: 6 commits2026-06-11: 5 commits2026-06-12: 4 commits2026-06-13: 2 commits2026-06-14: 1 commit2026-06-15: 0 commits2026-06-16: 0 commits2026-06-17: 0 commits2026-06-18: 7 commits2026-06-19: 10 commits2026-06-20: 12 commits2026-06-21: 0 commits2026-06-22: 7 commits2026-06-23: 2 commits2026-06-24: 4 commits2026-06-25: 0 commits2026-06-26: 1 commit2026-06-27: 0 commits2026-06-28: 0 commits2026-06-29: 0 commits2026-06-30: 0 commits2026-07-01: 0 commits2026-07-02: 0 commits2026-07-03: 0 commits2026-07-04: 0 commits2026-07-05: 0 commits2026-07-06: 1 commit2026-07-07: 3 commits2026-07-08: 0 commits2026-07-09: 6 commits2026-07-10: 1 commit2026-07-11: 4 commits2026-07-12: 1 commit2026-07-13: 6 commits2026-07-14: 4 commits2026-07-15: 1 commit2026-07-16: 2 commits2026-07-17: 0 commits2026-07-18: 0 commits2026-07-19: 0 commits2026-07-20: 9 commits2026-07-21: 3 commits2026-07-22: 6 commits2026-07-23: 8 commits2026-07-24: 1 commit2026-07-25: 7 commits2026-07-26: 5 commits2026-07-27: 0 commits2026-07-28: 2 commits2026-07-29: 11 commits2026-07-30: 5 commits2026-07-31: 16 commits2026-08-01: 2 commits2026-08-02: 7 commits2026-08-03: 12 commits2026-08-04: 3 commits2026-08-05: 9 commits2026-08-06: 2 commits2026-08-07: 1 commit2026-08-08: 2 commits2026-08-09: 5 commits2026-08-10: 1 commit2026-08-11: 0 commits2026-08-12: 3 commits2026-08-13: 2 commits2026-08-14: 0 commits2026-08-15: 5 commits2026-08-16: 0 commits2026-08-17: 1 commit2026-08-18: 5 commits2026-08-19: 7 commits2026-08-20: 0 commits2026-08-21: 1 commit2026-08-22: 0 commits2026-08-23: 0 commits2026-08-24: 0 commits2026-08-25: 13 commits2026-08-26: 6 commits2026-08-27: 8 commits2026-08-28: 7 commits2026-08-29: 0 commits2026-08-30: 10 commits2026-08-31: 7 commits2026-09-01: 16 commits2026-09-02: 1 commit2026-09-03: 3 commits2026-09-04: 3 commits2026-09-05: 15 commits2026-09-06: 9 commits2026-09-07: 14 commits2026-09-08: 14 commits2026-09-09: 9 commits2026-09-10: 10 commits2026-09-11: 3 commits2026-09-12: 1 commit2026-09-13: 2 commits2026-09-14: 2 commits2026-09-15: 11 commits2026-09-16: 3 commits2026-09-17: 0 commits2026-09-18: 0 commits2026-09-19: 0 commits2026-09-20: 0 commits2026-09-21: 5 commits2026-09-22: 2 commits2026-09-23: 1 commit2026-09-24: 0 commits2026-09-25: 4 commits2026-09-26: 0 commits2026-09-27: 0 commits2026-09-28: 0 commits2026-09-29: 0 commits2026-09-30: 0 commits2026-10-01: 0 commits2026-10-02: 0 commits2026-10-03: 0 commits
621 commits in the last yearLessMore

Signals and awards

derived from tracked data
  • Very active

    621 commits in 52 weeks

  • Permissive license

    Apache-2.0

  • Continuous integration

    Automated checks passing

What remove-ai-watermarks does

Remove AI Watermarks provides a robust suite of tools designed specifically for eliminating AI provenance marks from user-generated images and videos. It effectively strips out well-known visible labels, such as Gemini sparkles and specific vendor text marks. Beyond visible elements, the software completely regenerates invisible pixel watermarks using advanced diffusion models. The tool meticulously cleans out embedded metadata including C2PA, EXIF, XMP, and IPTC standards that denote AI generation. It includes extensive support for various video formats, handling visible mark removal for tools like Sora, Veo, and Kling.

Content creators, researchers, and developers who legally generate their own AI media and wish to remove automatic vendor tracking marks. It requires basic command-line proficiency and an understanding of media metadata structures.

  • Visible mark removal: Automatically identifies and strips out known vendor labels and visual indicators from generated media.
  • Invisible watermark regeneration: Uses advanced diffusion models to completely overwrite and remove hidden pixel-level watermarks.
  • Metadata cleansing: Thoroughly strips all AI provenance data from embedded C2PA, EXIF, XMP, and IPTC tags.
  • Comprehensive video support: Handles provenance identification and mark removal across multiple complex generative video formats.
  • Batch processing capabilities: Allows users to run complete visible and metadata cleaning operations across entire directories simultaneously.

Where teams use it

Clean media generation

Creators completely remove vendor-specific sparkles and text marks from images they generated for cleaner presentations.

Metadata sanitization

Privacy-conscious users strip extensive C2PA and EXIF data from files before publishing them publicly online.

Invisible watermark elimination

Researchers utilize diffusion regeneration to effectively bypass hidden pixel-level tracking inserted by AI models.

Batch video processing

Editors run automated cleaning scripts over entire directories of generated video content to remove diverse AI labels quickly.

Getting started: https://raiw.cc

README

main branch

Remove AI Watermarks

Remove AI provenance marks from images and video you generated yourself:

  • known visible labels such as the Google Gemini sparkle watermark and vendor text marks;
  • invisible pixel watermarks through direct local-format disruption or diffusion regeneration;
  • C2PA, EXIF, XMP, IPTC, and related AI metadata.

Video support covers provenance identification, complete visible-plus-metadata cleaning, directory batches, visible Sora, Veo, Seedance, Doubao, Dola, Hailuo AI, and Kling AI mark removal, and oracle-certified VAE regeneration for video SynthID removal.

raiw.cc runs this library as a hosted service, with the GPU included and nothing to install. Visible mark and metadata removal at Standard output up to 12 MP are free there; original resolution above 12 MP and invisible watermark removal are paid.

PyPI Python Downloads License Tests Sponsor skills.sh

This project is for lawful use on content you own. It does not target stock agency previews or other watermarks that protect third party paid content. See scope, safety, and legal notes.

Choose what you want to do

Goal Command GPU
Find provenance signals and watermarks identify No
Classify a photograph from pixels (opt-in, not provenance) classify No
Remove known visible AI marks visible No
Erase a region you select erase No
Strip AI metadata metadata No
Identify supported video provenance video identify No
Remove visible marks and AI metadata from video video all No
Strip AI metadata from video video metadata No
Remove a registered visible AI mark from video video visible No
Process a directory of videos video batch Depends on mode
Apply the calibrated video-pixel SynthID-removal profile video invisible Recommended
Regenerate an image to disrupt invisible watermarks invisible Required (CUDA)
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. A specialized Python API can inspect and disrupt the validated local Watermarker.dll payload without diffusion.

Installation modes

Need Install
Metadata inspection and stripping remove-ai-watermarks
Photograph AI-versus-camera classification remove-ai-watermarks[classify]
OpenAI/Google/unknown source-export classification remove-ai-watermarks[source-classify]
Visible detection and removal remove-ai-watermarks[visible]
Visible video processing remove-ai-watermarks[video]
Video SynthID removal remove-ai-watermarks[video,diffusion]
Torch-free DWT-DCT detection remove-ai-watermarks[detect]
Direct local Paint InvisMark disruption remove-ai-watermarks[pixels]
Invisible image removal (needs CUDA) remove-ai-watermarks[qwen-zimage]
Every production feature available on the active Python remove-ai-watermarks[all]

Lower-level and specialized extras include pixels, heif, trustmark, migan, lama, diffusion, classify-onnx, and source-classify. The installation guide documents their exact dependency composition, Python compatibility, and model requirements.

Quick start

Install the metadata-focused default CLI:

uv tool install remove-ai-watermarks

Inspect an image:

remove-ai-watermarks identify image.png

To classify a photograph from pixels (AI versus camera, optional provider), install the extra and call classify. identify never starts it:

uv tool install --force "remove-ai-watermarks[classify]"
remove-ai-watermarks classify image.png

Guide: photo pixel classification.

For a lightweight, abstaining OpenAI/Google/unknown source-export signal after metadata removal, use the separate Python API. It is not a SynthID detector:

import remove_ai_watermarks as raiw

result = raiw.classify_source("image.png")
print(result.label, result.reason)

Install remove-ai-watermarks[source-classify]. Guide: source-pipeline classification.

Signed provenance is the supported route for SynthID and identify reads it. There is no local SynthID pixel detector in the package. Research on a periodic lattice expert is in scripts/synthid_runtime/ and synthid-detector-research.md.

For visible watermark removal, install the pixel dependencies:

uv tool install --force "remove-ai-watermarks[visible]"

Then remove a known visible mark and AI metadata:

remove-ai-watermarks visible image.png -o clean.png

Strip metadata without running visible inpainting or diffusion:

remove-ai-watermarks metadata image.png --remove -o clean.png

Without -o this command overwrites the source in place.

Inspect or remove AI metadata from an MP4, MOV, M4V, WebM, MKV, AVI, or FLV file:

remove-ai-watermarks video metadata input.mp4 --check
remove-ai-watermarks video metadata input.mp4 --remove -o clean.mp4

The video metadata command does not transcode video or audio streams. Unlike the image command above, when -o is omitted it writes <source>_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, plus the QuickTime-form meta variants Doubao's iOS export writes (a bare meta box as a direct moov child, and a keyless hdlr=mdir metadata list). Keyed workflow and prompt entries in the same metadata-list structure are also detected and removed, including ComfyUI exports. MKV and WebM inspection reads the normative Segment.Tags.Tag.SimpleTag placement. AVI uses LIST/INFO/AIGC, while FLV uses script.onMetaData.AIGC. The non-ISOBMFF formats are remuxed with stream copy for removal.

Use the product-oriented video path to identify or clean a file:

uv tool install --force "remove-ai-watermarks[video]"
remove-ai-watermarks video identify input.mp4
remove-ai-watermarks video all input.mp4 -o clean.mp4

video all removes a stable registered visible mark when present and always strips verified AI metadata. If neither signal is found, it still writes a same-container passthrough, so application callers get one predictable output contract. Proprietary invisible-video removal is excluded by default. --invisible opts into the lossy, oracle-certified video SynthID profile.

Process a directory with the same contract:

remove-ai-watermarks video batch ./videos --mode all

Remove a supported visible video mark:

remove-ai-watermarks video visible input.mp4 -o clean.mp4
remove-ai-watermarks video visible veo.mp4 --mark veo -o veo_clean.mp4
remove-ai-watermarks video visible seedance.mp4 --mark seedance -o seedance_clean.mp4
remove-ai-watermarks video visible dola.mp4 --mark dola -o dola_clean.mp4
remove-ai-watermarks video visible hailuo.mp4 --mark hailuo -o hailuo_clean.mp4
remove-ai-watermarks video visible kling.mp4 --mark kling -o kling_clean.mp4

This path scans the complete sequence before changing pixels. It accepts only a mark that repeats at a stable position across adjacent frames, then reuses the same OpenCV, MI-GAN, or LaMa fill backends as image removal. Audio is copied without re-encoding and is allowed to reach its natural end; the video stream is transcoded because its pixels change. By default, a guarded optical-flow pass motion-aligns the preceding accepted fill and blends it only when the nearby source context agrees; use --no-temporal-consistency to disable it. The encoder preserves supported 8-bit source chroma sampling, color tags, and MP4/MOV track timescale instead of relying on ffmpeg's implicit raw-BGR defaults. Variable frame intervals are preserved through a timestamped in-memory NUT bridge instead of being flattened to the average frame rate. Non-zero source start timestamps are retained together with the copied audio offset. The default --mark auto scans all providers in one decode pass and selects the first stable match in the specificity order shown below. Valid Google AI-video provenance breaks a Sora/Veo cross-match in favor of Veo when the diamond is nested in the Sora region or Veo persists through the complete clip while Sora does not. Pass an explicit mark to restrict detection to one provider. Sora covers the moving Sora 2 mascot, with or without the wordmark; mascot-only matches also require the complete two-eye shape. Veo covers both the current four-point diamond and the legacy Veo text. Seedance covers the fixed boxed AI label, Dola covers the fixed Dola AI text, Hailuo AI covers the composite MINIMAX | hailuo AI label, and Kling AI covers the bottom-right KLING AI or KlingAI label with its version suffix. A completed encode is published atomically. No output is written when no stable mark is found. HDR, PQ/HLG, and greater-than-8-bit inputs are rejected before encoding rather than silently reduced through OpenCV's 8-bit BGR boundary.

Apply the calibrated video-pixel SynthID-removal profile:

uv tool install --force "remove-ai-watermarks[video,diffusion]"
remove-ai-watermarks video invisible input.mp4 -o clean.mp4

This path regenerates the complete sequence with one latent-noise field shared across time, copies complete audio, strips source metadata, and publishes the completed encode atomically. The default noise_std=0.15 profile passed both the two-carrier calibration and a complete public eight-second Veo oracle check. Google does not publish a local decoder, so a fresh provider check remains useful for unusually important files or after provider changes, but it is not a product result state. The CLI therefore describes video-pixel regeneration instead of claiming a per-file SynthID verdict, and reports the copied audio watermark as UNVERIFIED.

For invisible watermark removal through the CLI or high-level API, install the qwen-zimage extra. An NVIDIA GPU is required: all diffusion profiles are CUDA-only, and there is no CPU or MPS fallback. The specialized local Paint API uses the pixels extra without CUDA.

uv tool install --force "remove-ai-watermarks[qwen-zimage]"
remove-ai-watermarks invisible image.png -o clean.png

If the local detectors cannot confirm an invisible watermark but you know the 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 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:

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 for the manifest schema, compatibility restrictions, and oracle caveats.

See the installation guide for Homebrew, uv, optional features, and development setup.

Examples

Visible Gemini mark

Before After
Image with a visible Gemini watermark Image after visible watermark removal

The after raster is generated from the tracked before raster by the public path:

uv run remove-ai-watermarks visible demo_banana_before.png \
  --backend cv2 -o demo_banana_after.png

High quality invisible removal

qwen-zimage is the default profile: a Qwen-Image-2512 Lightning pass under Canny ControlNet, followed by SAM-masked Z-Image repair of any detected face. The alternative, sdxl-zimage, swaps the global stage for SDXL and keeps the same face stage. A third profile, chroma-zimage, uses the Apache-2.0 Chroma1 global pass with its own flat vendor floors; see docs/chroma1-engine-research.md for the calibration. --pipeline auto picks sdxl-zimage for Google, chroma-zimage for Microsoft, and qwen-zimage otherwise. All are CUDA only.

uv tool install --force "remove-ai-watermarks[qwen-zimage]"
remove-ai-watermarks invisible image.png -o clean.png --force
OpenAI example before OpenAI example after
OpenAI portrait grid before qwen-zimage OpenAI portrait grid after qwen-zimage
Gemini example before Gemini example after
Gemini sign before qwen-zimage Gemini sign after qwen-zimage

These exact output files were checked with the matching provider verifiers. That result applies to these files, not to every seed, image, or future watermark version.

Common recipes

Remove every detected visible mark

remove-ai-watermarks visible image.png -o clean.png

The default --mark auto checks all registered visible marks and removes every match. If the mark is visible to you but the detector misses it, select its region explicitly:

remove-ai-watermarks erase image.png \
  --region 1640,1930,400,100 \
  -o clean.png

--region uses x,y,width,height and may be repeated.

Use a learned fill backend

The visible extra uses OpenCV inpainting when no learned backend is installed. For more difficult backgrounds, the learned-backend extras include the same pixel dependencies automatically:

uv tool install --force "remove-ai-watermarks[migan]"
remove-ai-watermarks visible image.png -o clean.png --backend migan
uv tool install --force "remove-ai-watermarks[lama]"
remove-ai-watermarks visible image.png -o clean.png --backend lama

Reduce CUDA memory use

remove-ai-watermarks invisible image.png -o clean.png \
  --cpu-offload --force

CPU offload lowers CUDA memory pressure by moving model components between CPU and GPU, at the cost of speed.

Process a directory

remove-ai-watermarks batch ./images --mode visible
remove-ai-watermarks batch ./images --mode all

What the tool can recognize

Visible mark support includes:

  • Google Gemini and Nano Banana visible sparkle watermark;
  • Doubao, the Jimeng wordmark and top-left AI生成 pill, Qwen, Kling AI, Yuanbao, Baidu, LiblibAI's bottom-center wordmark and compact top-left pill, and RunningHub labels;
  • one calibrated Microsoft top-right white AI-badge variant;
  • one calibrated Samsung Galaxy AI label variant;
  • a generic brand-less bottom-right AI生成 fallback for OS/gallery-level AI-edit stamps that carry no vendor wordmark of their own (vivo and Xiaomi Gallery confirmed); synthetic-only calibration, weaker evidence than the vendor-tuned detectors above;
  • the OpenArt wordmark, placed at the frame center rather than a corner; uncalibrated (one confirmed real case, no captured corpus).

Metadata and provenance inspection covers C2PA, EXIF, XMP, IPTC, common generator parameters in image and video containers, China TC260 AIGC labels, and several vendor specific signals. Optional decoders add support for open DWT-DCT watermarks and Adobe TrustMark.

The exact support matrix, including important locale and detector limits, lives in supported signals. The synthetic example gallery carries one canonical sample for every registered image and video mark.

How it works

Visible removal follows three steps:

  1. Detect a registered mark in its expected area.
  2. Build a mask around the mark.
  3. Fill only the masked region with OpenCV, MI-GAN, or LaMa.

Metadata removal uses format aware stripping. JPEG metadata removal preserves the encoded image scan instead of recompressing it. Native MP4/MOV TC260 values and keyed generation parameters are blanked without changing box sizes or media offsets. Other supported containers use their corresponding metadata path.

Invisible removal through the CLI and high-level API regenerates the image through a diffusion pipeline. Specialized callers can instead decode and disrupt the positively identified local Microsoft Paint format with much smaller pixel changes. Neither path can guarantee that every proprietary verifier will reject every output.

See supported signals and known limitations for the full technical boundary.

Python API

The visible-removal API requires remove-ai-watermarks[visible].

import remove_ai_watermarks as raiw

result, removed = raiw.remove_visible("watermarked.png", "clean.png")
print(removed)

report = raiw.remove_visible_detailed("watermarked.png", "clean.png")
print(report.status)  # cleaned | partial | unvalidated | no_watermark

complete = raiw.remove_all("watermarked.png", "clean.png")
print(complete.visible_status, complete.visible_marks)

images = raiw.remove_batch("images", "images_clean", mode="visible")
for item in images.items:
    print(item.source, item.visible_status, item.visible_marks)

provenance = raiw.identify_video("input.mp4")
report = raiw.inspect_video_metadata("input.mp4")
complete = raiw.remove_video_all("input.mp4", "clean.mp4")
print(complete.visual_invisible_action, complete.audio.watermark_status)
batch = raiw.remove_video_batch("videos", "videos_clean")
cleaned = raiw.remove_video_metadata("input.mp4")
synthid_cleaned = raiw.remove_video_invisible("input.mp4", "synthid_clean.mp4")
print(synthid_cleaned.visual_invisible_action, synthid_cleaned.audio.watermark_status)
visible = raiw.remove_video_visible("input.mp4", "clean.mp4")
print(visible.mark)
veo = raiw.remove_video_visible("veo.mp4", "veo_clean.mp4", mark="veo")
seedance = raiw.remove_video_visible(
    "seedance.mp4",
    "seedance_clean.mp4",
    mark="seedance",
)
dola = raiw.remove_video_visible("dola.mp4", "dola_clean.mp4", mark="dola")

The high level API accepts a file path or a BGR NumPy array. For path inputs it also reads provenance metadata, preserves alpha, and can strip AI metadata from the written result.

See the Python API guide for visible removal, the full remove_all and remove_batch pipeline, provenance inspection, metadata stripping, and diffusion usage.

Agent skill

Coding agents can install the published skill and drive this CLI:

npx skills add wiltodelta/remove-ai-watermarks

Claude Code can add this repository as a plugin marketplace:

/plugin marketplace add wiltodelta/remove-ai-watermarks
/plugin install remove-ai-watermarks@remove-ai-watermarks

The skill refuses stock-agency and other third-party paid-asset marks. See the agent skill guide.

ComfyUI

The separate ComfyUI Remove AI Watermarks package provides nodes for visible removal, detection, region erasing, and invisible removal.

Important limitations

  • A missing local signal means unknown, not clean. Proprietary pixel watermarks may remain after metadata has been stripped.
  • Visible removal reconstructs a small region. Results depend on the background and selected fill backend.
  • Invisible removal changes the whole image and may alter faces, text, or fine detail.
  • Visible video removal recognizes the moving Sora 2 wordmark, the current Veo diamond plus legacy Veo text, the Seedance boxed AI label, and the fixed Doubao, Dola, Hailuo AI, and Kling AI labels. It does not recognize the older Sora Turbo corner swirl or unregistered layouts from those providers. The classical OpenCV backend can smear structured backgrounds; use MI-GAN or LaMa when recovery quality matters.
  • Video SynthID regeneration changes resolution, frame rate, and image detail. The shipped profile is oracle-certified, but no public local decoder can certify an arbitrary output at runtime. Recheck unusually important outputs after provider changes.
  • Invisible-watermark removal through the CLI and high-level API requires CUDA. All diffusion profiles refuse any other device at construction rather than falling back to one that cannot run them. The specialized local Paint API, visible removal, metadata stripping and identify run without CUDA.
  • Provider watermark systems can change. Validate important outputs with the provider's own verifier when one is available.

The shipped video invisible command uses the certified noise_std=0.15 profile. The companion scripts/video_synthid_sweep.py research harness builds a matched re-encode control plus VAE-regenerated candidates and leaves the verifier verdict blank:

uv run --extra video --extra diffusion python scripts/video_synthid_sweep.py input.mp4 -o sweep/

To score what an output actually cost, use scripts/video_fidelity_probe.py: the engine's own PSNR is measured after the input resize and before final encoding, so only the probe sees the delivered picture.

uv run --extra video python scripts/video_fidelity_probe.py input.mp4 input_clean.mp4

The control must still be SynthID-positive before a negative candidate can count as removal evidence. In the 2026-07-29 two-clip calibration, both matched controls were positive in Gemini's built-in SynthID verifier; the stronger candidate was negative on both carriers, while a weaker candidate was negative on one. A later adversarial follow-up that asked ordinary Gemini to reinterpret the pixel result returned UNAVAILABLE; that follow-up was not a verifier rerun and does not invalidate the built-in verdicts. A 2026-07-31 full-clip check on a public eight-second Veo sample found 0.10 still detected and 0.15 not detected, so 0.15 is now the certified default. The reproducible hashes and verdicts for that full-clip check -- one carrier, two rows -- live in data/evaluations/video-synthid-oracle.csv. The earlier two-clip calibration is narrative only: its verdicts were not recorded in a tracked manifest, so treat the certified default as resting on the 2026-07-31 rows.

Documentation

Start with the documentation index.

Research notes and historical experiments are listed separately in the documentation index. They explain past decisions but do not define the current public API.

Contributing

Install the development environment and run the project gate:

uv sync --frozen --extra dev
bash maintain.sh

See module internals before changing a subsystem with documented invariants.

License

Apache 2.0. Copyright 2025-2026 wiltodelta.

View on GitHub

Recent activity

commits and pull requests

Releases and announcements

105 total
  1. Image-source classifier derivative freeze v1image-source-freeze-2026-09-25-v1Sep 25, 2026pre-release5 downloads

    Derivative model weights only. No training corpus or source images. SHA-256: 0e94e565c181416e4f63a2bfc06cd5179f4201befda7481ac510f6a168c612f4. This is not a remove-ai-watermarks package release.

  2. ## Highlights - Add OpenArt visible watermark detection and removal, including alternate-size gallery coverage. - Add generic bare `AI生成` detection as a brand-neutral TC260 fallback. - Raise the OpenAI Qwen removal strength floor. - Improve C2PA registry-sync reliability with authenticated GitHub requests. - Improve Gemini sparkle observability and pin provider-oracle proxy sessions per batch. **Full Changelog**: https://github.com/wiltodelta/remove-ai-watermarks/compare/v0.41.1...v0.42.0

  3. v0.41.1v0.41.1Sep 16, 2026

    ## What's Changed * Bump the minor-and-patch group with 6 updates by @dependabot[bot] in https://github.com/wiltodelta/remove-ai-watermarks/pull/109 **Full Changelog**: https://github.com/wiltodelta/remove-ai-watermarks/compare/v0.41.0...v0.41.1

  4. v0.41.0v0.41.0Sep 16, 2026

    ## Source pipeline classification - Adds the optional `classify_source()` API and `source-classify` extra for classifying an image as matching an OpenAI export pipeline, a Google export pipeline, or neither supported pipeline. - Uses a small NumPy runtime with an exact, checksum-pinned model artifact from `wiltodelta/raiw-source-classify`; PyTorch and Transformers are not runtime dependencies. - Keeps source classification separate from watermark identification: it is not a SynthID detector and does not feed `identify()`. - Abstains unless a class-specific calibrated threshold is met and exposes the decision reason, score, threshold, and model revision. - Documents the measured clean-export scope and the sharp degradation after resize or JPEG recompression. The private training corpus is not distributed. The release includes only the learned model artifact, aggregate evaluation metrics, runtime code, and public documentation.

  5. ## What's changed - Verified the existing OpenAI provenance and invisible-removal path against fresh GPT Image 2.5 Flare and Sunburst API outputs. - Confirmed valid bound OpenAI C2PA and source-positive SynthID after pixel-identical metadata stripping. - Confirmed the shipped qwen-zimage profile produces oracle-negative outputs for both controlled sources. - Documented the model-independent routing contract, measured fidelity, and the one-source-per-model limitation. No CLI or Python API changes are required for GPT Image 2.5 compatibility.

Code frequency

additions and deletions
+42.8K-42.8KWeek of 2026-03-22: +10,139 linesWeek of 2026-03-22: -295 linesWeek of 2026-03-29: +888 linesWeek of 2026-03-29: -1,565 linesWeek of 2026-04-05: +2 linesWeek of 2026-04-05: -2 linesWeek of 2026-04-12: +0 linesWeek of 2026-04-12: -0 linesWeek of 2026-04-19: +168 linesWeek of 2026-04-19: -36 linesWeek of 2026-04-26: +24 linesWeek of 2026-04-26: -24 linesWeek of 2026-05-03: +0 linesWeek of 2026-05-03: -0 linesWeek of 2026-05-10: +34 linesWeek of 2026-05-10: -34 linesWeek of 2026-05-17: +609 linesWeek of 2026-05-17: -125 linesWeek of 2026-05-24: +11,812 linesWeek of 2026-05-24: -2,203 linesWeek of 2026-05-31: +8,816 linesWeek of 2026-05-31: -4,397 linesWeek of 2026-06-07: +8,638 linesWeek of 2026-06-07: -8,464 linesWeek of 2026-06-14: +3,370 linesWeek of 2026-06-14: -390 linesWeek of 2026-06-21: +927 linesWeek of 2026-06-21: -344 linesWeek of 2026-06-28: +0 linesWeek of 2026-06-28: -0 linesWeek of 2026-07-05: +3,969 linesWeek of 2026-07-05: -2,866 linesWeek of 2026-07-12: +2,287 linesWeek of 2026-07-12: -573 linesWeek of 2026-07-19: +17,708 linesWeek of 2026-07-19: -4,542 linesWeek of 2026-07-26: +18,887 linesWeek of 2026-07-26: -9,515 linesWeek of 2026-08-02: +12,097 linesWeek of 2026-08-02: -7,147 linesWeek of 2026-08-09: +4,554 linesWeek of 2026-08-09: -1,801 linesWeek of 2026-08-16: +2,261 linesWeek of 2026-08-16: -472 linesWeek of 2026-08-23: +4,301 linesWeek of 2026-08-23: -1,248 linesWeek of 2026-08-30: +42,819 linesWeek of 2026-08-30: -2,947 linesWeek of 2026-09-06: +20,913 linesWeek of 2026-09-06: -5,026 linesWeek of 2026-09-13: +4,984 linesWeek of 2026-09-13: -1,087 linesWeek of 2026-09-20: +3,934 linesWeek of 2026-09-20: -1,488 linesWeek of 2026-09-27: +0 linesWeek of 2026-09-27: -0 linesMar 22, 2026Sep 27, 2026
+184.1K lines added, -56.6K removed over the last year.

Commits per week

last 52 weeks
810Week of 2025-10-05: 0 commitsWeek of 2025-10-12: 0 commitsWeek of 2025-10-19: 0 commitsWeek of 2025-10-26: 0 commitsWeek of 2025-11-02: 0 commitsWeek of 2025-11-09: 0 commitsWeek of 2025-11-16: 0 commitsWeek of 2025-11-23: 0 commitsWeek of 2025-11-30: 0 commitsWeek of 2025-12-07: 0 commitsWeek of 2025-12-14: 0 commitsWeek of 2025-12-21: 0 commitsWeek of 2025-12-28: 0 commitsWeek of 2026-01-04: 0 commitsWeek of 2026-01-11: 0 commitsWeek of 2026-01-18: 0 commitsWeek of 2026-01-25: 0 commitsWeek of 2026-02-01: 0 commitsWeek of 2026-02-08: 0 commitsWeek of 2026-02-15: 0 commitsWeek of 2026-02-22: 0 commitsWeek of 2026-03-01: 0 commitsWeek of 2026-03-08: 0 commitsWeek of 2026-03-15: 0 commitsWeek of 2026-03-22: 20 commitsWeek of 2026-03-29: 8 commitsWeek of 2026-04-05: 1 commitsWeek of 2026-04-12: 0 commitsWeek of 2026-04-19: 2 commitsWeek of 2026-04-26: 1 commitsWeek of 2026-05-03: 0 commitsWeek of 2026-05-10: 2 commitsWeek of 2026-05-17: 7 commitsWeek of 2026-05-24: 81 commitsWeek of 2026-05-31: 47 commitsWeek of 2026-06-07: 59 commitsWeek of 2026-06-14: 30 commitsWeek of 2026-06-21: 14 commitsWeek of 2026-06-28: 0 commitsWeek of 2026-07-05: 15 commitsWeek of 2026-07-12: 14 commitsWeek of 2026-07-19: 34 commitsWeek of 2026-07-26: 41 commitsWeek of 2026-08-02: 36 commitsWeek of 2026-08-09: 16 commitsWeek of 2026-08-16: 14 commitsWeek of 2026-08-23: 34 commitsWeek of 2026-08-30: 55 commitsWeek of 2026-09-06: 60 commitsWeek of 2026-09-13: 18 commitsWeek of 2026-09-20: 12 commitsWeek of 2026-09-27: 0 commitsOct 5, 2025Sep 27, 2026
621 commits in the last 52 weeks.

When work happens

weekday and hour
SunMonTueWedThuFriSat036912151821Sun 0:00 — 1 commitsSun 1:00 — 0 commitsSun 2:00 — 0 commitsSun 3:00 — 0 commitsSun 4:00 — 0 commitsSun 5:00 — 0 commitsSun 6:00 — 0 commitsSun 7:00 — 0 commitsSun 8:00 — 1 commitsSun 9:00 — 0 commitsSun 10:00 — 3 commitsSun 11:00 — 4 commitsSun 12:00 — 12 commitsSun 13:00 — 5 commitsSun 14:00 — 2 commitsSun 15:00 — 9 commitsSun 16:00 — 12 commitsSun 17:00 — 12 commitsSun 18:00 — 4 commitsSun 19:00 — 5 commitsSun 20:00 — 7 commitsSun 21:00 — 5 commitsSun 22:00 — 0 commitsSun 23:00 — 0 commitsMon 0:00 — 0 commitsMon 1:00 — 0 commitsMon 2:00 — 0 commitsMon 3:00 — 0 commitsMon 4:00 — 1 commitsMon 5:00 — 0 commitsMon 6:00 — 0 commitsMon 7:00 — 0 commitsMon 8:00 — 4 commitsMon 9:00 — 11 commitsMon 10:00 — 9 commitsMon 11:00 — 11 commitsMon 12:00 — 16 commitsMon 13:00 — 4 commitsMon 14:00 — 7 commitsMon 15:00 — 6 commitsMon 16:00 — 14 commitsMon 17:00 — 0 commitsMon 18:00 — 5 commitsMon 19:00 — 12 commitsMon 20:00 — 9 commitsMon 21:00 — 11 commitsMon 22:00 — 7 commitsMon 23:00 — 1 commitsTue 0:00 — 3 commitsTue 1:00 — 0 commitsTue 2:00 — 0 commitsTue 3:00 — 0 commitsTue 4:00 — 0 commitsTue 5:00 — 0 commitsTue 6:00 — 0 commitsTue 7:00 — 0 commitsTue 8:00 — 2 commitsTue 9:00 — 4 commitsTue 10:00 — 6 commitsTue 11:00 — 9 commitsTue 12:00 — 5 commitsTue 13:00 — 8 commitsTue 14:00 — 5 commitsTue 15:00 — 7 commitsTue 16:00 — 6 commitsTue 17:00 — 10 commitsTue 18:00 — 4 commitsTue 19:00 — 14 commitsTue 20:00 — 7 commitsTue 21:00 — 8 commitsTue 22:00 — 4 commitsTue 23:00 — 1 commitsWed 0:00 — 0 commitsWed 1:00 — 0 commitsWed 2:00 — 0 commitsWed 3:00 — 0 commitsWed 4:00 — 0 commitsWed 5:00 — 0 commitsWed 6:00 — 0 commitsWed 7:00 — 0 commitsWed 8:00 — 5 commitsWed 9:00 — 3 commitsWed 10:00 — 11 commitsWed 11:00 — 11 commitsWed 12:00 — 14 commitsWed 13:00 — 6 commitsWed 14:00 — 4 commitsWed 15:00 — 5 commitsWed 16:00 — 7 commitsWed 17:00 — 11 commitsWed 18:00 — 8 commitsWed 19:00 — 4 commitsWed 20:00 — 5 commitsWed 21:00 — 10 commitsWed 22:00 — 4 commitsWed 23:00 — 3 commitsThu 0:00 — 1 commitsThu 1:00 — 0 commitsThu 2:00 — 0 commitsThu 3:00 — 0 commitsThu 4:00 — 1 commitsThu 5:00 — 0 commitsThu 6:00 — 0 commitsThu 7:00 — 0 commitsThu 8:00 — 0 commitsThu 9:00 — 5 commitsThu 10:00 — 7 commitsThu 11:00 — 9 commitsThu 12:00 — 3 commitsThu 13:00 — 1 commitsThu 14:00 — 4 commitsThu 15:00 — 2 commitsThu 16:00 — 16 commitsThu 17:00 — 8 commitsThu 18:00 — 7 commitsThu 19:00 — 2 commitsThu 20:00 — 5 commitsThu 21:00 — 3 commitsThu 22:00 — 0 commitsThu 23:00 — 2 commitsFri 0:00 — 0 commitsFri 1:00 — 1 commitsFri 2:00 — 0 commitsFri 3:00 — 0 commitsFri 4:00 — 0 commitsFri 5:00 — 0 commitsFri 6:00 — 0 commitsFri 7:00 — 0 commitsFri 8:00 — 0 commitsFri 9:00 — 6 commitsFri 10:00 — 14 commitsFri 11:00 — 8 commitsFri 12:00 — 4 commitsFri 13:00 — 2 commitsFri 14:00 — 7 commitsFri 15:00 — 3 commitsFri 16:00 — 1 commitsFri 17:00 — 2 commitsFri 18:00 — 0 commitsFri 19:00 — 4 commitsFri 20:00 — 5 commitsFri 21:00 — 2 commitsFri 22:00 — 0 commitsFri 23:00 — 0 commitsSat 0:00 — 0 commitsSat 1:00 — 0 commitsSat 2:00 — 1 commitsSat 3:00 — 0 commitsSat 4:00 — 0 commitsSat 5:00 — 0 commitsSat 6:00 — 0 commitsSat 7:00 — 0 commitsSat 8:00 — 0 commitsSat 9:00 — 3 commitsSat 10:00 — 3 commitsSat 11:00 — 11 commitsSat 12:00 — 7 commitsSat 13:00 — 6 commitsSat 14:00 — 6 commitsSat 15:00 — 8 commitsSat 16:00 — 1 commitsSat 17:00 — 2 commitsSat 18:00 — 1 commitsSat 19:00 — 2 commitsSat 20:00 — 1 commitsSat 21:00 — 9 commitsSat 22:00 — 1 commitsSat 23:00 — 0 commits
Commit volume by weekday and hour (UTC). Larger dots mean more commits.

Who is committing

last 52 weeks
Maintainer commits623 (95%)
Community commits35 (5%)

658 commits in total over the last year.

DateListRankStars gained
May 22, 2026daily#23+43