# CLI guide The command line interface is organized around the type of work you want to do. ```text remove-ai-watermarks [OPTIONS] COMMAND [ARGS] ``` Run `remove-ai-watermarks COMMAND --help` for the complete option list and defaults. This page focuses on choosing the right command. ## Command dependency map | Command or signal | Required installation | | --- | --- | | `metadata` and metadata-only `identify` | Default package | | `classify` | `remove-ai-watermarks[classify]` | | 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]` | | `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]` | | `invisible` and `all` (needs CUDA) | `remove-ai-watermarks[qwen-zimage]` | | `video metadata` and `video identify --no-visible` | Default package | | `video identify` | `remove-ai-watermarks[video]` | | `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 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]`. ## Inspect an image ```bash 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. For C2PA files, the text report shows asset integrity, claim-signature, signer-trust, and signer-validity results separately. Confidence follows the binding: an intact asset binding and claim signature is high confidence, and an unanchored or expired signer is reported as a caveat rather than a lower score, because no trust anchor list ships with the reader. A failed asset binding or signature, or a revoked signing credential, 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 a referenced pixel watermark, and a referenced content fingerprint may still be recomputed. A fingerprint is reported as a durable-provenance signal, not as a watermark or a pixel-removal target. Machine readable output: ```bash remove-ai-watermarks identify image.png --json ``` Metadata only inspection: ```bash remove-ai-watermarks identify image.png --no-visible ``` Despite the historical option name, `--no-visible` skips all pixel detectors, including visible marks, open DWT-DCT, and TrustMark. Metadata inspection still runs. ## Classify a photograph from pixels This is not provenance. `identify` never starts it, including after a no-signal metadata scan. Install the extra first: ```bash uv tool install --force "remove-ai-watermarks[classify]" ``` The optional CPU ONNX backend is library-only (`classify_pixels(..., backend="onnx")`, extra `classify-onnx`), not a CLI flag. Then: ```bash remove-ai-watermarks classify image.png remove-ai-watermarks classify image.png --json ``` The command runs the frozen photo detector (CLIP-L-ft ridge AND freeze MLP). Only a DEFINITELY result is reported as `ai`. On that result only, the receipt-document gate abstains receipt photographs to `unknown` before the 124-d provider head, which may return `openai`, `google`, `bytedance`, or `muse-image`. POSSIBLY is `unknown`. Camera-like photographs are `human`. The call does not run cleanup and does not set `is_ai_generated`. The contract is AI versus camera. Receipts abstain through the gate but are not a supported verdict; UI and digital art are out of scope. Microsoft is not a pixel class: a DALL-E Bing image scores as `openai`, an Imagen Designer image as `google`. `bytedance` names the shared Doubao+Jimeng generator lineage. The rest of China's generators (Qwen, Kling, ...) are peers of the named ones but have no honest class yet; their residual head abstains, so that content publishes no provider. Weights download on first use from [`wiltodelta/raiw-photo-classify`](https://huggingface.co/wiltodelta/raiw-photo-classify), or from `RAIW_CLASSIFY_WEIGHTS` if that directory already holds the freeze files. Guide: [photo pixel classification](photo-classify.md). Hub card: [photo-classify-hf/README.md](photo-classify-hf/README.md). ## Remove known visible marks Install `remove-ai-watermarks[visible]` before using `visible` or `erase`. ```bash remove-ai-watermarks visible image.png -o clean.png ``` The default behavior: - checks every registered visible mark; - removes every detected match, except the weakly detected Jimeng label pill, which needs corroboration (see [supported signals](supported-signals.md)); - selects the best installed fill backend; - checks the filled region once without changing it or retrying the fill; - strips AI metadata from the output. The re-encode never costs display fidelity: the ICC colour profile and the EXIF orientation tag are carried over from the source (see [strip AI metadata](#strip-ai-metadata)). The automatic command reports `Removed and validated` when the detector no longer accepts an overlapping mark, `Partial` when an overlapping residual is still detected, and `Post-removal validation unavailable` when the check fails. The latter two still write the once-filled output and return success; they are result-quality statuses, not requests for an automatic second edit. Use a specific mark: ```bash remove-ai-watermarks visible image.png --mark gemini -o clean.png ``` Available mark names are printed by: ```bash remove-ai-watermarks visible --help ``` Keep metadata: ```bash remove-ai-watermarks visible image.png --keep-metadata -o clean.png ``` Use the strict visual gate without metadata or sibling corroboration: ```bash remove-ai-watermarks visible image.png --sensitivity strict -o clean.png ``` Act on a mark you can see but the detector will not confirm, by naming it and skipping the gate: ```bash remove-ai-watermarks visible image.png --mark gemini --no-detect -o clean.png ``` `--detect` is the default. `--no-detect` removes the named mark's footprint unconditionally, so it only makes sense together with an explicit `--mark`; on an image that never carried that mark it inpaints a region for nothing. Prefer `erase --region` when you know the geometry. When no known mark is detected, the command does not write a new output. Use `erase` if you can identify the affected region yourself. ## Erase a region ```bash remove-ai-watermarks erase image.png \ --region 1640,1930,400,100 \ -o clean.png ``` The region format is `x,y,width,height`. Repeat `--region` to erase more than one box: ```bash remove-ai-watermarks erase image.png \ --region 20,20,180,60 \ --region 1640,1930,400,100 \ -o clean.png ``` Choose the fill backend: ```bash remove-ai-watermarks erase image.png \ --region 1640,1930,400,100 \ --backend migan \ -o clean.png ``` `erase` accepts `cv2`, `migan`, and `lama`. The corresponding optional extra must be installed for a learned backend. Two more knobs tune the fill. `--dilate N` (default 3) grows every box by `N` pixels before inpainting, which helps when a mark has a soft edge or a drop shadow just outside the box you measured; it applies to every backend because it shapes the mask. `--inpaint-method telea|ns` selects the classical algorithm and only affects the `cv2` backend. Like `visible`, `erase` strips AI metadata from the output by default; pass `--keep-metadata` to retain it. The ICC colour profile and the EXIF orientation tag survive either way. ## Strip AI metadata Inspect metadata: ```bash remove-ai-watermarks metadata image.png --check ``` Remove AI metadata and write a new file: ```bash 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`; that includes the display tags -- the ICC colour profile and the EXIF orientation -- which also survive every pixel-command re-encode (`all`, `visible`, `invisible`, `erase`), because colour and geometry are display fidelity, not provenance. Orientation is re-tagged, not re-applied: the raster is untouched, so what displayed upright still displays upright. 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. ## Identify and clean video Install the video pixel and timestamp runtime for visible identification, removal, and the complete pipeline: ```bash uv tool install --force "remove-ai-watermarks[video]" ``` Inspect every locally supported video signal: ```bash remove-ai-watermarks video identify input.mp4 remove-ai-watermarks video identify input.mp4 --json remove-ai-watermarks video identify input.mp4 --no-visible ``` The default scans the complete clip for stable registered visible marks and inspects supported metadata. A result with no signals is reported as unknown, not clean, because proprietary pixel watermarks have no public local decoder. `--no-visible` performs metadata-only inspection. Use the complete locally verifiable cleaning path: ```bash remove-ai-watermarks video all input.mp4 -o clean.mp4 ``` It removes a stable supported visible mark when found and always strips verified AI metadata. When neither signal is found, it writes a same-container passthrough instead of returning a missing output. The source is never overwritten. Invisible regeneration is deliberately opt-in: ```bash remove-ai-watermarks video all input.mp4 -o clean.mp4 --invisible ``` That option is supported only for MP4, MOV, and M4V. It is lossy and uses the same oracle-certified profile as `video invisible`. Process all supported files in a top-level directory: ```bash remove-ai-watermarks video batch ./videos --mode all remove-ai-watermarks video batch ./videos --mode visible remove-ai-watermarks video batch ./videos --mode metadata ``` The batch runs sequentially, preserves successful outputs when another file fails, and exits nonzero if any item failed. Visible no-op files are copied byte-for-byte so the output directory remains complete. `--invisible` is available only with `--mode all`. ## Strip AI metadata from video Metadata inspection and removal are also available as an isolated operation: ```bash 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 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`, 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 are also inspected. 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 or generation key/value in place. Box sizes, media offsets, and encoded stream bytes do not move; the result is atomically published only after the complete copy succeeds. For MKV and WebM, the inspector reads the native TC260 `Segment.Tags.Tag.SimpleTag` entry. Removal uses ffmpeg stream copying to discard container tags and chapters without transcoding the streams. AVI uses the normative `LIST/INFO/AIGC` chunk, while FLV uses the `script.onMetaData.AIGC` AMF0 string. Their bounded readers skip media payloads, and removal also uses ffmpeg stream copying. When `-o` is omitted, the command writes `_clean` with the same extension. It never overwrites the source, and it rejects an output with a different container extension. Visible video labels and invisible video watermarks are not handled by this command. ## Apply the video-pixel SynthID-removal profile ```bash uv tool install --force "remove-ai-watermarks[video,diffusion]" remove-ai-watermarks video invisible input.mp4 -o clean.mp4 ``` The command supports MP4, MOV, and M4V. It samples the complete sequence at the configured frame rate, resizes frames to the configured long side, regenerates them through a VAE, and applies one deterministic latent-noise field to every frame. Reusing one spatial field avoids the unnecessary flicker caused by independent per-frame noise. Frames are regenerated in bounded batches and streamed directly to ffmpeg, which encodes the result, copies audio, and drops source metadata. The default `noise_std=0.15` profile is oracle-certified. The project has no local video SynthID decoder, so an optional per-file recheck is still useful for unusually important files or after provider changes. In a new Gemini chat, upload the original first, invoke the built-in verifier with `@synthid`, and ask: > For the video attached to this message, was it created or edited by Google > AI? Use the built-in SynthID content verification result. The source must be positive. Then upload the processed result in a separate new chat and repeat the same built-in check. Only a source-positive, output-negative pair is a fresh per-file verification. Do not ask an adversarial follow-up that tells the chat model to ignore the verifier and reason about raw pixels: that is ordinary Gemini reasoning, not a second oracle check. The default output is `_clean` in the same container. The source is never overwritten. Use `--noise-std`, `--long-side`, `--fps`, `--batch-size`, `--seed`, and `--device` to control the regeneration. The default noise level is `0.15`. It cleared both carriers in the 2026-07-29 short-clip calibration and the complete public eight-second Veo carrier in the 2026-07-31 full-clip check; `0.10` remained detected on that complete clip. This calibration certifies the shipped operating point; the paired check above is an optional runtime audit, not a separate result state. On completion the command reports that video pixels were regenerated with the calibrated profile, not that this file received a fresh negative verdict. It separately prints `Audio watermark: UNVERIFIED` because source audio is copied unchanged when present. `video all` and `video batch` use the same wording and never fold the audio status into the visual action. ## Remove a supported visible video mark ```bash 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 ``` The command supports the moving Sora mascot with or without its wordmark, two Veo corner variants, the Seedance boxed `AI` label, the `Dola AI` text label, the composite `MINIMAX | hailuo AI` label, and the bottom-right Kling AI label. Sora searches the whole frame at multiple scales and verifies the complete mascot shape when the wordmark is absent. The other detectors search bounded lower-frame regions with separate synthetic silhouettes. Kling additionally requires its bright low-saturation label near the frame edge. Every mark requires a spatially recurring candidate across adjacent frames. Fixed marks must also remain anchored instead of drifting with a scene object. Matching provider provenance may relax the visual score only for registered provenance-aware marks; metadata alone never creates a detection. `--mark auto` is the default. It evaluates all providers in one decode pass and selects the first stable match in specificity order: Sora, Veo, Seedance, Doubao, Dola, Hailuo AI, then Kling AI. Their confidence scores are independently calibrated and are not compared across providers. Valid Google AI-video provenance resolves a stable Sora/Veo match in favor of Veo when their regions overlap or Veo spans the full clip while Sora does not; it does not create a visual detection. Pass an explicit `--mark` to scan only that provider. The video stream is transcoded and the complete original audio stream is copied without truncating an audio tail that extends beyond the final video frame. The CLI reports that copied audio as watermark-unverified. The encoder probes the source stream and preserves supported 8-bit chroma sampling, color range/matrix/transfer/primaries tags, and MP4/MOV track timescale. For a variable-frame-rate source, decoded PTS are carried through a timestamped in-memory NUT bridge so the output retains the source frame intervals instead of flattening them to a constant rate. A non-zero source start PTS and the copied audio start offset are preserved as well. Supported input and output containers are MP4, MOV, M4V, WebM, MKV, AVI, and FLV; the output extension must match the input. The default `cv2` backend is fast but can smear structured backgrounds. Select `--backend migan` or `--backend lama` for a learned fill, or `--backend auto` to choose the best installed backend. `--temporal-consistency` is enabled by default. It motion-aligns the preceding accepted fill, requires overlapping removal masks and matching source context, and blends only the safely covered pixels. Scene cuts, disjoint moving marks, or a poor motion match keep the independent current-frame fill. Use `--no-temporal-consistency` for an exact frame-local baseline. The pixel path is intentionally limited to SDR 8-bit video. A high-bit-depth, PQ, or HLG source is rejected before ffmpeg starts, preserving any existing output instead of silently downconverting it through OpenCV's 8-bit boundary. On CPU, MI-GAN is the practical learned tier. LaMa remains an explicit offline quality option because full-sequence inference is too slow and memory-heavy for an online worker. AI metadata is stripped from the encoded output by default. Use `--keep-metadata` to retain mapped container metadata. When no temporally stable mark is found, the command writes no output and exits with the no-visible-mark status. The final path is replaced atomically only after ffmpeg completes, so a failed encode does not overwrite an existing result. ## Remove invisible watermarks Install the removal dependencies first. All profiles are CUDA-only and all run the DiffSynth Z-Image face stage, so this is the extra either one needs: ```bash uv tool install --force "remove-ai-watermarks[qwen-zimage]" ``` Then run: ```bash remove-ai-watermarks invisible image.png -o clean.png ``` The command normally skips regeneration when no supported local signal is detected. Use `--force` when you know the image should be processed: ```bash remove-ai-watermarks invisible image.png -o clean.png --force ``` ### Choose a strength cohort `--vendor` selects the cohort the default strength resolves from. `auto` (the default) derives it from product-specific C2PA provenance for OpenAI/Google/Microsoft. The standalone AI IPTC tag is a shared standard and does not select Meta; use `--vendor meta` only when the source is independently known. An explicit value both names the cohort for stripped files and implies the scrub runs -- naming the cohort asserts the pixel watermark is present -- exactly like `--force` plus a measured floor: ```bash remove-ai-watermarks invisible muse_output.webp -o clean.png --vendor meta ``` The same option exists on `all` and `batch`, and as `InvisibleOptions(vendor="meta")` in the Python API. ### Choose a pipeline | Pipeline | When to use it | | --- | --- | | `qwen-zimage` | Default. Qwen-Image-2512 global pass plus a SAM-masked Z-Image face stage | | `sdxl-zimage` | The same recipe and face stage on an SDXL global pass, at a higher denoise | | `chroma-zimage` | The same face stage on an Apache-2.0 Chroma1 global pass; explicit OpenAI use needs a higher floor than qwen-zimage | | `auto` | Pick the engine from provenance: sdxl-zimage for Google, chroma-zimage for Microsoft, qwen-zimage for OpenAI, unknown, and an explicitly selected Meta cohort | **All four are CUDA-only.** There is no CPU or MPS profile for invisible-watermark removal. The former `controlnet`, `sdxl`, `qwen` and `default` profiles were removed rather than kept as a CPU path: none of them matched this recipe's face preservation, so offering them implied a quality the library no longer delivers. Passing a retired name is rejected at parse time rather than remapped. Visible-mark removal and every identify path still run anywhere. Example: ```bash remove-ai-watermarks invisible image.png -o clean.png \ --pipeline qwen-zimage --force ``` There is no `--model`, `--steps`, `--guidance-scale` or `--device` option, and the deprecated `--auto` is gone. Each profile pins its model stack, its per-stage schedule, CFG 1.0 and CUDA, so every one of those flags existed only to be refused 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 selected profile's VAE, 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. The optional Qwen-only `--fidelity-anchor` described below additionally blends 15% of the reconstruction across the full frame. Install the combined extra and run only with an operator-verified 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 option works with `qwen-zimage`, `sdxl-zimage`, `chroma-zimage`, and `auto`. Each concrete profile reconstructs the donor through its own already loaded VAE; for Google provenance, `auto` therefore keeps the normal SDXL route. ``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 and remains available only with `qwen-zimage`: 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 `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 `remove_ai_watermarks._internal.text_restoration.source_pixel_sha256` computes it. This mode is supported by every profile at native geometry with `humanize=0` and `unsharp=0`. An unset adaptive-polish option is forced off so verified text remains the final pixel-writing stage; an explicit request to enable it is rejected. `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: ```bash remove-ai-watermarks invisible image.png -o clean.png \ --cpu-offload --force ``` Keep large images at native resolution while processing them in overlapping tiles: ```bash remove-ai-watermarks invisible image.png -o clean.png \ --tile --max-resolution 0 --force ``` Or set a resolution cap: ```bash remove-ai-watermarks invisible image.png -o clean.png \ --max-resolution 2048 --force ``` Tiling avoids the explicit downscale but each tile is regenerated separately. It is a memory strategy, not a guarantee of better quality. `--tile-size` and `--tile-overlap` set the tile geometry (defaults 1024 and 128 px); a larger overlap costs time and hides seams. ### Every tuning option These apply to `invisible`, `all`, and `batch`. Each has a measured default, so reach for one only when you know why the default is wrong for your file. | Option | Effect | | --- | --- | | `--strength` | Denoising strength, 0.0-1.0. Default is profile- and vendor-adaptive; see [choose a strength cohort](#choose-a-strength-cohort). Raising it removes more and drifts further from the original. | | `--vendor` | Strength cohort, and an assertion that the watermark is present. | | `--pipeline` | Profile: `qwen-zimage` (default), `sdxl-zimage`, `chroma-zimage`, `auto`. | | `--seed` | Fixed at 0 by default: every profile is certified at that seed, and removal near the floor is seed-dependent. | | `--controlnet-scale` | Canny ControlNet conditioning on the global stage. Higher keeps the original structure and text closer. | | `--humanize` | Analog film grain, 0 = off, typical 2.0-6.0. | | `--unsharp` | Unsharp-mask sharpening, 0 = off. | | `--adaptive-polish` / `--no-adaptive-polish` | Overrides the profile's own choice. Unset lets the profile decide: off for `qwen-zimage`, whose output already matches the input's detail level, on for `sdxl-zimage`. | | `--max-resolution` | Cap the long side before diffusion; 0 = native. | | `--tile`, `--tile-size`, `--tile-overlap` | Tiled regeneration for large inputs, as above. | | `--cpu-offload` | Stream model components between CPU and GPU to lower CUDA memory pressure, at the cost of speed. | | `--hf-token` | Hugging Face token for the model download; also read from the environment and a local `.env`. | There is no `--model`, `--steps`, `--guidance-scale` or `--device` option; the profile pins all four. See [exit behavior](#exit-behavior) for what each command returns. ## Run the full pipeline The `all` command and the `all` installation extra are separate concepts. The command runs every applicable stage. Installing `remove-ai-watermarks[all]` 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. ```bash remove-ai-watermarks all image.png -o clean.png ``` The command runs: 1. visible mark removal; 2. invisible watermark removal when available and applicable; 3. AI metadata stripping. The visible options and diffusion options are also available on `all`. After the visible stage, `all` reports whether the once-filled result was validated as cleaned, still produced an overlapping residual (`Partial`), or could not be validated. These statuses do not trigger another fill and do not change the exit code by themselves. When the `qwen-zimage` extra is unavailable, `all` still writes the result of the visible and metadata stages, prints a prominent warning, and exits with code 1. That happens on every run without the extra: the skipped stage is what would have decided whether a signal was there. This prevents a partial result from being reported as complete. If the extra is installed but the machine has no CUDA, which is the usual macOS case, the run fails at engine construction instead: `all` prints `Error: Invisible-watermark removal is CUDA-only ...`, writes no output at all, and exits with code 1. ## Process a directory ```bash remove-ai-watermarks batch ./images --mode visible ``` Modes: - `visible`; - `invisible`; - `metadata`; - `all`. Set an output directory: ```bash remove-ai-watermarks batch ./images \ --mode all \ --output-dir ./clean ``` The invisible and full modes accept the same main diffusion controls as their single image counterparts. Run `batch --help` for the authoritative option list. For `visible` and `all` modes, the final summary includes counts for `no watermark`, `cleaned`, `partial`, and `unvalidated`. Partial and unavailable validation results are printed explicitly, but the once-filled outputs are kept and the batch does not retry them automatically. ## Exit behavior The CLI uses nonzero exit codes for meaningful incomplete outcomes, including no detected target on commands that would otherwise regenerate or create a misleading unchanged result, processing errors, and a required invisible step that could not run. Scripts should check the process exit code and the output path. The detailed per-command contract is maintained in [module internals](module-internals.md#command-line-interface).