Make all fail loudly when the gpu extra is missing

Step 2 (invisible/SynthID) was skipped with a quiet inline warning and the
run still exited 0, so a missing [gpu] extra was mistaken for a clean result
(recurring #14/#47). Add a prominent end-of-run banner and a non-zero exit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Victor Kuznetsov
2026-06-11 09:58:49 -07:00
parent ad7e4ee08b
commit a8e218acf6
4 changed files with 45 additions and 2 deletions
+2 -2
View File
File diff suppressed because one or more lines are too long
+4
View File
@@ -232,6 +232,10 @@ After installation the `remove-ai-watermarks` command is available system-wide.
> pip install -e ".[gpu]" # or: uv pip install -e ".[gpu]"
> ```
>
> Without the `[gpu]` extra, `all` still runs the visible and metadata steps, but
> it skips the invisible (SynthID) step, prints a clear warning, and exits with a
> non-zero status so a skipped step is not mistaken for a clean result.
>
> To let `identify` decode the open Stable Diffusion / SDXL / FLUX invisible
> watermarks, install the `detect` extra (adds the `invisible-watermark` decoder):
>
+26
View File
@@ -918,6 +918,13 @@ def cmd_all(
t0 = time.monotonic()
# Tracks whether step 2 (invisible / SynthID removal) was skipped because the
# GPU extra is missing. A skipped step 2 still produces an output file (visible
# mark + metadata stripped), so without a loud end-of-run notice + non-zero exit
# the user mistakes it for a clean result and ships an image that still carries
# the invisible watermark (recurring reports: #14, #47).
synthid_skipped = False
# Use a temp file for intermediate results so the user doesn't see
# a partial output file during long model downloads.
import tempfile
@@ -954,6 +961,7 @@ def cmd_all(
from remove_ai_watermarks.invisible_engine import is_available as invisible_available
if not invisible_available():
synthid_skipped = True
console.print(
" Warning: Skipped - GPU dependencies not installed.\n"
" Install them with: pip install 'remove-ai-watermarks[gpu]'"
@@ -1028,6 +1036,24 @@ def cmd_all(
size_kb = output.stat().st_size / 1024
console.print(f"\n Done: {output} ({size_kb:.0f} KB, {elapsed:.1f}s total)")
# A skipped invisible step is the single most common "it didn't work" report:
# the output looks processed but still carries the SynthID watermark. Make that
# impossible to miss -- a prominent banner plus a non-zero exit so scripts and
# batch callers can detect the incomplete run instead of trusting the file.
if synthid_skipped:
console.print(
"\n =====================================================================\n"
" WARNING: the invisible (SynthID) watermark was NOT removed.\n"
" Step 2 was skipped because the GPU dependencies are not installed,\n"
" so this output still carries the invisible watermark -- only the\n"
" visible mark and metadata were stripped.\n"
"\n"
" Install the extra and rerun to remove it:\n"
" pip install 'remove-ai-watermarks[gpu]'\n"
" ====================================================================="
)
raise SystemExit(1)
# -- Batch command ----------------------------------------------------
+13
View File
@@ -389,6 +389,19 @@ class TestAllCommand:
assert result.exit_code == 0, result.output
mock_best.assert_called() # the registry auto-detector drove the visible pass
def test_all_loud_warning_and_nonzero_exit_when_gpu_missing(self, runner, sample_png, tmp_path):
"""Regression (#14/#47): when the GPU extra is absent the invisible step is
skipped, but the output still looks processed -- the run must fail loudly
(prominent banner + non-zero exit) so a skipped SynthID pass is not mistaken
for a clean result. The output file is still written (visible + metadata)."""
output = tmp_path / "clean.png"
with patch("remove_ai_watermarks.invisible_engine.is_available", return_value=False):
result = runner.invoke(main, ["all", str(sample_png), "-o", str(output)])
assert result.exit_code != 0, result.output
assert "NOT removed" in result.output
assert "remove-ai-watermarks[gpu]" in result.output
assert output.exists() # visible + metadata still produced a file
def test_all_preserves_rgba_across_invisible_step(self, runner, tmp_path):
"""Regression: ``all`` must keep transparency even when the invisible
step writes a 3-channel result (as the real diffusion engine does).