v0.3.1: Fix opencv conflict, graceful GPU fallback, correct docs

- Remove opencv-python from [gpu] extra (conflicts with headless in base deps)
- Add graceful fallback in 'invisible' and 'all' commands when GPU deps missing
- Cache InvisibleEngine in batch mode (avoid reloading model per image)
- Fix --humanize help text (was '0.0-1.0', actual range is 0-6.0+)
- Fix stale docstring referencing non-existent [invisible] extra
- Add [gpu] extra install instructions to README
- Fix broken NeuralBleach placeholder URL in Credits
This commit is contained in:
test-user
2026-03-26 10:50:26 -07:00
parent 49b2b43f8d
commit b41c8e5aba
7 changed files with 115 additions and 66 deletions
+20 -16
View File
@@ -79,7 +79,7 @@ class TestMainGroup:
def test_version(self, runner):
result = runner.invoke(main, ["--version"])
assert result.exit_code == 0
assert "0.3.0" in result.output
assert "0.3.1" in result.output
def test_no_command_shows_banner(self, runner):
result = runner.invoke(main, [])
@@ -151,8 +151,9 @@ class TestInvisibleCommand:
def test_invisible_basic(self, runner, sample_png, tmp_path):
mock_cls, mock_engine = _mock_invisible_engine()
output = tmp_path / "clean.png"
with patch("remove_ai_watermarks.cli.InvisibleEngine", mock_cls, create=True), patch(
"remove_ai_watermarks.invisible_engine.InvisibleEngine", mock_cls
with (
patch("remove_ai_watermarks.cli.InvisibleEngine", mock_cls, create=True),
patch("remove_ai_watermarks.invisible_engine.InvisibleEngine", mock_cls),
):
result = runner.invoke(
main,
@@ -164,8 +165,9 @@ class TestInvisibleCommand:
def test_invisible_default_output(self, runner, sample_png):
mock_cls, mock_engine = _mock_invisible_engine()
with patch("remove_ai_watermarks.cli.InvisibleEngine", mock_cls, create=True), patch(
"remove_ai_watermarks.invisible_engine.InvisibleEngine", mock_cls
with (
patch("remove_ai_watermarks.cli.InvisibleEngine", mock_cls, create=True),
patch("remove_ai_watermarks.invisible_engine.InvisibleEngine", mock_cls),
):
result = runner.invoke(main, ["invisible", str(sample_png)])
assert result.exit_code == 0, result.output
@@ -188,8 +190,9 @@ class TestAllCommand:
def test_all_basic(self, runner, sample_png, tmp_path):
mock_cls, mock_engine = _mock_invisible_engine()
output = tmp_path / "clean.png"
with patch("remove_ai_watermarks.cli.InvisibleEngine", mock_cls, create=True), patch(
"remove_ai_watermarks.invisible_engine.InvisibleEngine", mock_cls
with (
patch("remove_ai_watermarks.cli.InvisibleEngine", mock_cls, create=True),
patch("remove_ai_watermarks.invisible_engine.InvisibleEngine", mock_cls),
):
result = runner.invoke(
main,
@@ -282,10 +285,11 @@ class TestBatchCommand:
input_dir = _make_batch_dir(tmp_path)
output_dir = tmp_path / "output"
mock_cls, mock_engine = _mock_invisible_engine()
with patch("remove_ai_watermarks.cli.InvisibleEngine", mock_cls, create=True), patch(
"remove_ai_watermarks.invisible_engine.InvisibleEngine", mock_cls
), patch("remove_ai_watermarks.cli.invisible_available", return_value=True, create=True), patch(
"remove_ai_watermarks.invisible_engine.is_available", return_value=True
with (
patch("remove_ai_watermarks.cli.InvisibleEngine", mock_cls, create=True),
patch("remove_ai_watermarks.invisible_engine.InvisibleEngine", mock_cls),
patch("remove_ai_watermarks.cli.invisible_available", return_value=True, create=True),
patch("remove_ai_watermarks.invisible_engine.is_available", return_value=True),
):
result = runner.invoke(
main,
@@ -298,10 +302,11 @@ class TestBatchCommand:
input_dir = _make_batch_dir(tmp_path)
output_dir = tmp_path / "output"
mock_cls, mock_engine = _mock_invisible_engine()
with patch("remove_ai_watermarks.cli.InvisibleEngine", mock_cls, create=True), patch(
"remove_ai_watermarks.invisible_engine.InvisibleEngine", mock_cls
), patch("remove_ai_watermarks.cli.invisible_available", return_value=True, create=True), patch(
"remove_ai_watermarks.invisible_engine.is_available", return_value=True
with (
patch("remove_ai_watermarks.cli.InvisibleEngine", mock_cls, create=True),
patch("remove_ai_watermarks.invisible_engine.InvisibleEngine", mock_cls),
patch("remove_ai_watermarks.cli.invisible_available", return_value=True, create=True),
patch("remove_ai_watermarks.invisible_engine.is_available", return_value=True),
):
result = runner.invoke(
main,
@@ -319,4 +324,3 @@ class TestBatchCommand:
assert result.exit_code == 0
expected_dir = tmp_path / "input_clean"
assert expected_dir.exists()