From 2152ebcd32b783f210197f748ccfa9bd064803ad Mon Sep 17 00:00:00 2001 From: test-user Date: Wed, 25 Mar 2026 12:19:29 -0700 Subject: [PATCH] v0.2.1: Code review fixes, platform-neutral docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix f-string logging → %-style (face_protector, invisible_engine) - Fix logger name: hardcoded string → __name__ - Add module docstrings to humanizer.py, face_protector.py - Break long warning string into multiple lines (PEP 8) - Make docs platform-neutral (macOS/Linux/Windows) - Rename 'optional' → 'additional setup' in README --- .agents/skills/get-api-docs/SKILL.md | 89 +++ .agents/skills/python-code-review/SKILL.md | 626 +++++++++++++++++++ pyproject.toml | 2 +- src/remove_ai_watermarks/__init__.py | 2 +- src/remove_ai_watermarks/face_protector.py | 12 +- src/remove_ai_watermarks/humanizer.py | 6 + src/remove_ai_watermarks/invisible_engine.py | 2 +- tests/test_cli.py | 2 +- uv.lock | 2 +- 9 files changed, 734 insertions(+), 9 deletions(-) create mode 100644 .agents/skills/get-api-docs/SKILL.md create mode 100644 .agents/skills/python-code-review/SKILL.md diff --git a/.agents/skills/get-api-docs/SKILL.md b/.agents/skills/get-api-docs/SKILL.md new file mode 100644 index 0000000..6e4caeb --- /dev/null +++ b/.agents/skills/get-api-docs/SKILL.md @@ -0,0 +1,89 @@ +--- +name: get-api-docs +description: > + Use this skill when you need documentation for a third-party library, SDK, or API + before writing code that uses it — for example, "use the OpenAI API", "call the + Stripe API", "use the Anthropic SDK", "query Pinecone", or any time the user asks + you to write code against an external service and you need current API reference. + Fetch the docs with chub before answering, rather than relying on training knowledge. +--- + +# Get API Docs via chub + +When you need documentation for a library or API, fetch it with the `chub` CLI +rather than guessing from training data. This gives you the current, correct API. + +## Step 1 — Find the right doc ID + +```bash +chub search "" --json +``` + +Pick the best-matching `id` from the results (e.g. `openai/chat`, `anthropic/sdk`, +`stripe/api`). If nothing matches, try a broader term. + +## Step 2 — Fetch the docs + +```bash +chub get --lang py # or --lang js, --lang ts +``` + +Omit `--lang` if the doc has only one language variant — it will be auto-selected. + +## Step 3 — Use the docs + +Read the fetched content and use it to write accurate code or answer the question. +Do not rely on memorized API shapes — use what the docs say. + +## Step 4 — Annotate what you learned + +After completing the task, if you discovered something not in the doc — a gotcha, +workaround, version quirk, or project-specific detail — save it so future sessions +start smarter: + +```bash +chub annotate "Webhook verification requires raw body — do not parse before verifying" +``` + +Annotations are local, persist across sessions, and appear automatically on future +`chub get` calls. Keep notes concise and actionable. Don't repeat what's already in +the doc. + +## Step 5 — Give feedback + +Always rate the doc after using it. This helps authors fix outdated or incorrect +docs and prioritize improvements. Include a label and a brief comment explaining +what was good or what needs fixing — specific feedback is the most valuable. + +```bash +chub feedback up --label accurate "Clear examples, models are current" +chub feedback down --label outdated "Lists gpt-4o as latest but gpt-5.4 is out" +``` + +Available labels: `outdated`, `inaccurate`, `incomplete`, `wrong-examples`, +`wrong-version`, `poorly-structured`, `accurate`, `well-structured`, `helpful`, +`good-examples`. + +If you notice the doc has wrong model names, deprecated APIs, missing features, +or incorrect code patterns, always leave a downvote with details so it can be fixed. + +## Quick reference + +| Goal | Command | +|------|---------| +| List everything | `chub search` | +| Find a doc | `chub search "stripe"` | +| Exact id detail | `chub search stripe/api` | +| Fetch Python docs | `chub get stripe/api --lang py` | +| Fetch JS docs | `chub get openai/chat --lang js` | +| Save to file | `chub get anthropic/sdk --lang py -o docs.md` | +| Fetch multiple | `chub get openai/chat stripe/api --lang py` | +| Save a note | `chub annotate stripe/api "needs raw body"` | +| List notes | `chub annotate --list` | +| Rate a doc | `chub feedback stripe/api up` | + +## Notes + +- `chub search` with no query lists everything available +- IDs are `/` — confirm the ID from search before fetching +- If multiple languages exist and you don't pass `--lang`, chub will tell you which are available diff --git a/.agents/skills/python-code-review/SKILL.md b/.agents/skills/python-code-review/SKILL.md new file mode 100644 index 0000000..d762a73 --- /dev/null +++ b/.agents/skills/python-code-review/SKILL.md @@ -0,0 +1,626 @@ +--- +name: python-code-review +description: Performs comprehensive code reviews for Python files following PEP 8 and Google Python Style Guide standards. Checks code quality, best practices, security, performance, maintainability, and style compliance. Use when reviewing Python code or when asked to check, audit, or improve Python code quality. +--- + +# Python Code Review (PEP 8 + Google Style Guide) + +Perform systematic code reviews of Python files following PEP 8 and Google Python Style Guide standards. + +## Review Philosophy + +> "Code is read much more often than it is written." - Guido van Rossum + +**Key Principle**: A foolish consistency is the hobgoblin of little minds. Consistency within a project is more important than rigid adherence to rules. When in doubt, prioritize: +1. Consistency within one function/module (most important) +2. Consistency within the project +3. Consistency with PEP 8/Google Style Guide + +Know when to be inconsistent: +- When applying the guideline makes code less readable +- To match surrounding code style (but consider refactoring) +- When code predates the guideline +- For backwards compatibility + +## Review Process + +When reviewing Python code: + +1. **Read the entire file** to understand its purpose, structure, and context + +2. **Analyze against these standards** in order of priority: + +### 1. Style & Formatting (PEP 8) + +**Line Length** +- Maximum 80 characters (Google) +- Docstrings/comments: 72 characters max +- Use implicit line continuation (parentheses/brackets) over backslashes + +**Indentation** +- Always 4 spaces per level, never tabs +- Continuation lines: align vertically or use 4-space hanging indent +- Closing brackets: align under first non-whitespace or under opening bracket + +**Blank Lines** +- 2 blank lines between top-level functions/classes +- 1 blank line between methods +- Sparingly within functions for logical sections +- No blank line after `def` line + +**Whitespace Rules** +- No whitespace inside parentheses/brackets/braces: `spam(ham[1], {eggs: 2})` +- No whitespace before comma/semicolon/colon (except in slices) +- No whitespace before function call parentheses: `spam(1)` not `spam (1)` +- No whitespace before indexing brackets: `dct['key']` not `dct ['key']` +- Single space around binary operators: `i = i + 1` +- No spaces around `=` in keyword args: `complex(real, imag=0.0)` +- BUT use spaces when combining annotation + default: `def munge(input: AnyStr = None)` +- Don't align operators vertically across lines (maintenance burden) + +**String Quotes** +- Be consistent: pick `'` or `"` and stick with it in a file +- Use the other quote to avoid backslashes: `"He said 'hello'"` +- Always use `"""` for docstrings (never `'''`) + +**Trailing Commas** +- Recommended for multi-line structures when closing bracket is on new line +- Mandatory for single-element tuples: `FILES = ('setup.cfg',)` +- Not on same line as closing bracket (except singleton tuples) + +### 2. Imports (PEP 8 + Google) + +**Import Order** (with blank line between groups): +1. `from __future__` imports +2. Standard library imports +3. Third-party imports +4. Local application/library imports + +**Import Style** +- Separate lines: `import os` and `import sys` (not `import os, sys`) +- Exception: OK to import multiple items from one module: `from subprocess import Popen, PIPE` +- Exception: Typing imports: `from typing import Any, NewType` +- Use absolute imports (recommended): `import mypkg.sibling` +- Relative imports acceptable for complex packages: `from . import sibling` +- Never use wildcard imports: `from module import *` +- Import full package paths (Google): `from doctor.who import jodie` not `import jodie` + +**Import Format** +- `import x` for packages and modules +- `from x import y` where x is package prefix, y is module name +- `from x import y as z` if y conflicts or is inconveniently long +- `import y as z` only for standard abbreviations: `import numpy as np` + +**Module Dunders** +- After module docstring, before imports (except `__future__`) +- Order: `__all__`, `__version__`, `__author__`, etc. + +### 3. Naming Conventions (PEP 8 + Google) + +| Type | Convention | Examples | +|------|------------|----------| +| **Modules** | `lower_with_under.py` | `my_module.py` | +| **Packages** | `lower_with_under` | `my_package` | +| **Classes** | `CapWords` | `MyClass`, `HTTPServerError` | +| **Exceptions** | `CapWords` + `Error` suffix | `ValueError`, `ConnectionError` | +| **Functions** | `lower_with_under()` | `my_function()` | +| **Methods** | `lower_with_under()` | `my_method()` | +| **Constants** | `CAPS_WITH_UNDER` | `MAX_OVERFLOW`, `TOTAL` | +| **Global/Class Variables** | `lower_with_under` | `global_var` | +| **Instance Variables** | `lower_with_under` | `instance_var` | +| **Parameters** | `lower_with_under` | `function_param` | +| **Local Variables** | `lower_with_under` | `local_var` | +| **Type Variables** | `_T`, `_P` (leading underscore) | `_T = TypeVar("_T")` | + +**Special Prefixes/Suffixes** +- `_single_leading`: weak "internal use" indicator (not imported by `from M import *`) +- `single_trailing_`: avoid keyword conflicts (`class_`) +- `__double_leading`: name mangling in classes (discouraged by Google - impacts testability) +- `__double_leading_and_trailing__`: magic methods (never invent these) + +**Names to Avoid** +- Never use `l` (lowercase L), `O` (uppercase o), `I` (uppercase i) as single-char names +- No dashes in any package/module name +- Avoid abbreviations unless well-known +- No offensive terms +- No needless type info: `id_to_name_dict` → `id_to_name` + +**Descriptive Names** +- Names should be descriptive and clear +- Descriptiveness proportional to scope (wider scope = more descriptive) +- Single-char names OK for: counters (`i`, `j`, `k`), exceptions (`e`), file handles (`f`), type vars (`_T`, `_P`) +- Avoid vague names: `thing`, `stuff`, `data` (without context) + +### 4. Documentation (PEP 257 + Google) + +**Module Docstrings** (required) +```python +"""One-line summary ending with period. + +Longer description of the module or program. May include usage +examples, exported classes/functions, etc. + +Typical usage example: + foo = ClassFoo() + bar = foo.function_bar() +""" +``` + +**Function/Method Docstrings** (required for complex/public functions) +```python +def fetch_data( + table: str, + keys: Sequence[str], + require_all: bool = False, +) -> Mapping[str, tuple[str, ...]]: + """Fetches rows from database. + + Retrieves rows pertaining to given keys. Longer description + of what the function does and any important details. + + Args: + table: Name of the database table. + keys: List of row keys to fetch. Strings will be UTF-8 encoded. + require_all: If True, only return rows with all keys present. + + Returns: + Dict mapping keys to row data. Each row is a tuple of strings. + Returns empty dict if no rows found. + + Raises: + IOError: Error accessing the database. + ValueError: Invalid table name provided. + """ +``` + +**Docstring Sections** (Google style): +- **Summary line**: One physical line (≤80 chars), ends with period +- **Blank line** after summary (if more content follows) +- **Args**: Describe each parameter (with type if not annotated) +- **Returns**: Describe return value (omit for None, generators use "Yields") +- **Raises**: Document exceptions that callers should handle +- **Yields**: For generators, document yielded values + +**Class Docstrings** +```python +class SampleClass: + """Summary of class here. + + Longer class information describing what the class represents + (not that it "is a class"). + + Attributes: + likes_spam: A boolean indicating spam preference. + eggs: An integer count of eggs we have. + """ + + def __init__(self, likes_spam: bool = False): + """Initializes the instance. + + Args: + likes_spam: Defines instance preference. + """ + self.likes_spam = likes_spam + self.eggs = 0 +``` + +**Comments** +- Block comments: Full sentences, capitalized, period at end +- Inline comments: 2+ spaces from code, used sparingly +- Tricky code: Comment before the operation +- Non-obvious code: Comment at end of line +- TODO format: `# TODO: bug-reference - Description` or `# TODO(username): Description` +- Keep comments up-to-date with code changes! +- Comments in English unless 120% sure code never read by non-speakers + +**Override Methods** +- Use `@override` decorator (from `typing_extensions`) when overriding +- No docstring needed if behavior unchanged +- Add docstring if behavior differs or side effects added + +### 5. Type Hints (PEP 484 + Google) + +**Basic Rules** +- Strongly encouraged for function signatures +- Use for complex functions, public APIs, when types aren't obvious +- Don't annotate `self` or `cls` (except when needed for proper type info - use `Self`) +- Don't annotate `__init__` return (always `None`) + +**Type Hint Style** +```python +def my_method( + self, + first_var: int, + second_var: Foo, + third_var: Bar | None, +) -> int: + ... +``` + +**Modern Syntax (Python 3.10+)** +- Use `|` for unions: `str | None` (not `Optional[str]` or `Union[str, None]`) +- Use built-in types: `list[int]`, `dict[str, int]` (not `List[int]`, `Dict[str, int]`) +- Use `collections.abc` for parameters: `Sequence`, `Mapping` (not concrete types) + +**Specific Guidelines** +- Use explicit `X | None` not implicit (`a: str = None` is wrong) +- Specify generic parameters: `list[int]` not bare `list` +- Use `Any` when best type unknown (but prefer `TypeVar` when possible) +- Type aliases: `CapWords` naming, use `TypeAlias` annotation +- Forward references: use `from __future__ import annotations` or string quotes +- Conditional imports: use `if TYPE_CHECKING:` for type-only imports + +**Type Variable Naming** +```python +_T = TypeVar("_T") # Good: leading underscore, descriptive +_P = ParamSpec("_P") # Good: leading underscore +AddableType = TypeVar("AddableType", int, float, str) # Good: descriptive +``` + +### 6. Code Quality & Best Practices + +**Implicit False (PEP 8)** +```python +# Good +if not seq: +if foo is None: +if not x: + +# Bad +if len(seq) == 0: +if foo == None: +if x == False: +``` + +**Comparisons** +- Singletons: use `is`/`is not`: `if x is None:` +- Use `is not` rather than `not ... is` +- Don't compare booleans to True/False: `if greeting:` not `if greeting == True:` +- Type checking: use `isinstance(obj, int)` not `type(obj) is int` +- String prefixes/suffixes: use `.startswith()`/`.endswith()` not slicing + +**Sequences** +- Use empty sequence truth value: `if seq:` not `if len(seq):` +- Works for strings, lists, tuples + +**Exception Handling** +- Never use bare `except:` (catches SystemExit/KeyboardInterrupt!) +- Use specific exceptions: `except ValueError:` not `except Exception:` +- Minimize try block scope (avoid masking bugs) +- Use `finally` for cleanup or prefer context managers +- Exception chaining: `raise X from Y` or `raise X from None` +- Derive from `Exception` not `BaseException` +- Exception names end in `Error` (if they are errors) + +**String Formatting** +```python +# Good - Modern (preferred) +x = f'name: {name}; score: {n}' + +# Good - Classic +x = 'name: %s; score: %d' % (name, n) +x = 'name: {}; score: {}'.format(name, n) + +# Bad - Don't concatenate in loops +employee_table = '' +for last, first in employees: + employee_table += f'' # BAD! + +# Good - Use list + join +items = ['
{last}, {first}
'] +for last, first in employees: + items.append(f'') +employee_table = ''.join(items) +``` + +**Logging** +```python +# Good - Use %-style (not f-strings!) +logger.info('TensorFlow version: %s', tf.__version__) + +# Bad - Don't use f-strings (prevents lazy evaluation) +logger.info(f'TensorFlow version: {tf.__version__}') +``` + +**Resource Management** +```python +# Good - Always use context managers +with open('file.txt') as f: + data = f.read() + +# Good - For non-context objects +import contextlib +with contextlib.closing(urllib.urlopen("http://...")) as page: + for line in page: + print(line) + +# Bad - Don't rely on __del__ or manual close +f = open('file.txt') +data = f.read() +f.close() # May not run if exception occurs! +``` + +**Function Defaults** +```python +# Good - No mutable defaults +def foo(a, b=None): + if b is None: + b = [] + +# Good - Immutable default +def foo(a, b: Sequence = ()): + ... + +# Bad - Mutable default (shared across calls!) +def foo(a, b=[]): + ... +``` + +**Comprehensions** +```python +# Good +result = [mapping_expr for value in iterable if filter_expr] + +# Bad - Multiple for clauses +result = [(x, y) for x in range(10) for y in range(5) if x * y > 10] + +# If complex, use regular loop +result = [] +for x in range(10): + for y in range(5): + if x * y > 10: + result.append((x, y)) +``` + +**Lambdas & Operators** +```python +# OK for simple cases +sorted_list = sorted(items, key=lambda x: x.name) + +# Better - Use operator module +from operator import attrgetter +sorted_list = sorted(items, key=attrgetter('name')) + +# Bad - Multi-line lambda +complicated = lambda x: x.filter(something).map( + another_thing).reduce(final_thing) + +# Good - Use def +def complicated(x): + return x.filter(something).map(another_thing).reduce(final_thing) +``` + +**Statements** +```python +# Good +if foo == 'blah': + do_blah_thing() +do_one() +do_two() + +# Bad - Compound statements +if foo == 'blah': do_blah_thing() +do_one(); do_two(); do_three() +``` + +**Return Statements** +- Be consistent: all return expressions or all return None +- Explicit is better: use `return None` not bare `return` if other returns have values + +**Properties** +- Use `@property` decorator (not manual descriptors) +- Only for trivial computations (cheap, straightforward) +- Don't use for expensive operations or complex logic +- Don't use just to wrap simple attribute access (make it public) + +**Decorators** +- Use judiciously when clear advantage +- Never use `@staticmethod` (use module-level function - Google) +- Use `@classmethod` sparingly (named constructors, class-specific state) + +**Global State** +- Avoid mutable global state +- Module-level constants OK: `MAX_TIMEOUT = 30` +- Name private globals with leading underscore: `_internal_cache` + +**Power Features (Avoid)** +- No custom metaclasses +- No bytecode manipulation +- No `__del__` for cleanup +- No reflection hacks (some `getattr` use is OK) +- No import hacks +- Standard library can use these (e.g., `abc.ABCMeta`, `dataclasses`, `enum`) + +### 7. Security + +**SQL Injection** +```python +# Bad - SQL injection risk! +query = f"SELECT * FROM users WHERE id = {user_id}" +query = "SELECT * FROM users WHERE id = " + user_id + +# Good - Use parameterized queries +query = "SELECT * FROM users WHERE id = %s" +cursor.execute(query, (user_id,)) +``` + +**Input Validation** +- Validate all external input +- Use allowlists not denylists +- Sanitize before using in system commands + +**Hardcoded Secrets** +- Never hardcode passwords, API keys, tokens +- Use environment variables or secret management +- Check for: `password = "..."`, `api_key = "..."`, etc. + +**Unsafe Functions** +- Avoid: `eval()`, `exec()`, `compile()`, `__import__()` +- Be careful with: `pickle`, `yaml.load()` (use `safe_load`) + +### 8. Performance + +**String Concatenation** +- Never use `+` or `+=` in loops (quadratic time!) +- Use `''.join(items)` or `io.StringIO` + +**Generators** +- Use generators for large sequences (memory efficient) +- Use comprehensions over `map()`/`filter()` with lambda + +**Default Iterators** +```python +# Good +for key in adict: +for line in afile: +for k, v in adict.items(): + +# Bad +for key in adict.keys(): +for line in afile.readlines(): +``` + +### 9. Maintainability + +**Function Length** +- Prefer < 40 lines (Google guideline, not hard limit) +- Break up long functions unless it harms structure +- If >40 lines, consider if it can be split + +**Main Guard** +```python +# Good +def main(): + ... + +if __name__ == '__main__': + main() + +# Or with absl +from absl import app + +def main(argv): + ... + +if __name__ == '__main__': + app.run(main) +``` + +**Assertions** +- Don't use `assert` for critical logic (can be disabled with `-O`) +- OK for validating test expectations +- Use `if` + `raise` for preconditions + +## Output Format + +Structure your review as: + +### Summary +- **Overall Assessment**: Excellent/Good/Fair/Needs Improvement +- **PEP 8 Compliance**: High/Medium/Low +- **Google Style Compliance**: High/Medium/Low +- **Key Strengths**: 2-4 well-implemented aspects +- **Critical Issues**: Issues requiring immediate attention (if any) + +### Detailed Findings + +Group by category. For each issue: + +**[Category: Style/Documentation/Quality/Security/Performance/Maintainability]** + +**Issue #**: Brief title +- **Severity**: Critical/High/Medium/Low +- **Lines**: Specific line numbers +- **PEP 8/Google Reference**: Section reference (if applicable) +- **Description**: Clear explanation of the issue +- **Current Code**: + ```python + # Problematic code excerpt + ``` +- **Recommended Fix**: + ```python + # Corrected code + ``` +- **Rationale**: Why this matters (readability/safety/performance/maintainability) + +### Positive Highlights +- Well-implemented patterns worth noting +- Good adherence to standards +- Exemplary practices + +### Recommendations +- Priority-ordered list of improvements +- Consider quick wins vs. larger refactors +- Balance consistency with practical constraints + +### References +- [PEP 8 Style Guide](https://peps.python.org/pep-0008/) +- [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html) +- [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/) +- [PEP 484 Type Hints](https://peps.python.org/pep-0484/) + +## Enforcement Tools + +**Recommended**: +- **pylint**: Comprehensive linter ([Google's pylintrc](https://google.github.io/styleguide/pylintrc)) +- **pytype**: Type checker (Google's tool) +- **mypy**: Alternative type checker +- **Black** or **Pyink**: Auto-formatters (Google uses these) +- **flake8**: Alternative linter +- **isort**: Import sorting + +**Suppression**: +- Use `# pylint: disable=rule-name` with explanation +- Use `# type: ignore` for type checking (sparingly) +- Document why suppression is needed + +## Review Guidelines + +**Be Constructive** +- Explain *why* something matters +- Provide specific, actionable recommendations +- Include code examples for fixes +- Acknowledge good practices + +**Context Matters** +- Consider project conventions +- Match surrounding code style when editing +- Balance improvement with backwards compatibility +- Know when rules have valid exceptions + +**Prioritize** +1. **Critical**: Security issues, correctness bugs +2. **High**: Significant readability/maintainability issues +3. **Medium**: Style violations, minor best practices +4. **Low**: Nitpicks, suggestions + +**Pragmatic Approach** +- Focus on changes being made (not rewriting entire codebase) +- Suggest incremental improvements +- Consider team capacity and priorities +- "Perfect is the enemy of good" + +## Special Cases + +**Legacy Code** +- Focus on new/modified code +- Don't require full refactor to meet standards +- Suggest incremental modernization + +**Mathematical/Scientific Code** +- Short variable names OK if match notation: `i`, `j`, `x`, `y` +- Reference paper/algorithm in comments +- Use `# pylint: disable=invalid-name` if needed + +**Test Files** +- PEP 8 compliant names: `test__` +- Or legacy style: `testMethodUnderTest_state` +- Less strict docstring requirements + +**Backwards Compatibility** +- Don't break compatibility just to comply with PEP 8 +- Consider deprecation path for API changes + +--- + +This skill combines the authoritative standards of PEP 8 with Google's battle-tested practices to provide comprehensive, practical Python code reviews. diff --git a/pyproject.toml b/pyproject.toml index 16049cb..ddb72b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "remove-ai-watermarks" -version = "0.2.0" +version = "0.2.1" description = "Unified tool for removing visible and invisible AI watermarks from images" readme = "README.md" requires-python = ">=3.10" diff --git a/src/remove_ai_watermarks/__init__.py b/src/remove_ai_watermarks/__init__.py index e4c1ac5..e2c03ed 100644 --- a/src/remove_ai_watermarks/__init__.py +++ b/src/remove_ai_watermarks/__init__.py @@ -1,3 +1,3 @@ """Remove-AI-Watermarks: Unified tool for removing visible and invisible AI watermarks.""" -__version__ = "0.2.0" +__version__ = "0.2.1" diff --git a/src/remove_ai_watermarks/face_protector.py b/src/remove_ai_watermarks/face_protector.py index 50d674d..bd9832c 100644 --- a/src/remove_ai_watermarks/face_protector.py +++ b/src/remove_ai_watermarks/face_protector.py @@ -1,3 +1,5 @@ +"""YOLO-based face detection and soft-blend restoration for diffusion pipelines.""" + import logging from pathlib import Path @@ -11,7 +13,7 @@ try: except ImportError: HAS_YOLO = False -logger = logging.getLogger("face_protector") +logger = logging.getLogger(__name__) class FaceProtector: @@ -29,12 +31,14 @@ class FaceProtector: if self.use_yolo: # Fix SSL certificate issues on macOS (fresh Python installs) self._fix_ssl_certs() - logger.info(f"Loading YOLO model '{model_name}' for face protection...") + logger.info("Loading YOLO model '%s' for face protection...", model_name) self.detector = YOLO(model_name) else: if use_yolo and not HAS_YOLO: logger.warning( - "ultralytics YOLO is not installed. Falling back to OpenCV Haar Cascades. Install ultralytics with `pip install ultralytics` for better face detection." + "ultralytics YOLO is not installed. Falling back to OpenCV Haar " + "Cascades. Install ultralytics with `pip install ultralytics` " + "for better face detection." ) logger.info("Loading OpenCV Haar Cascade for face protection...") cascade_path = Path(cv2.__file__).parent / "data" / "haarcascade_frontalface_default.xml" @@ -139,6 +143,6 @@ class FaceProtector: blended = src_roi * mask + target_roi * (1.0 - mask) result[y1:y2, x1:x2] = blended.astype(np.uint8) except Exception as e: - logger.warning(f"Failed to restore face at {x1},{y1} to {x2},{y2}: {e}") + logger.warning("Failed to restore face at %d,%d to %d,%d: %s", x1, y1, x2, y2, e) return result diff --git a/src/remove_ai_watermarks/humanizer.py b/src/remove_ai_watermarks/humanizer.py index 59a93c0..a8f2928 100644 --- a/src/remove_ai_watermarks/humanizer.py +++ b/src/remove_ai_watermarks/humanizer.py @@ -1,3 +1,9 @@ +"""Analog Humanizer: film grain and chromatic aberration injection. + +Simulates analog film imperfections to defeat digital AI perfection +classifiers. Ported from NeuralBleach. +""" + import cv2 import numpy as np from numpy.typing import NDArray diff --git a/src/remove_ai_watermarks/invisible_engine.py b/src/remove_ai_watermarks/invisible_engine.py index 6d19958..47d421c 100644 --- a/src/remove_ai_watermarks/invisible_engine.py +++ b/src/remove_ai_watermarks/invisible_engine.py @@ -175,7 +175,7 @@ class InvisibleEngine: if self._progress_callback: self._progress_callback(f"Extracted {len(original_faces)} face(s) for protection.") except Exception as e: - logger.error(f"Failed to extract faces: {e}") + logger.error("Failed to extract faces: %s", e) out_path = self._remover.remove_watermark( image_path=image_path, diff --git a/tests/test_cli.py b/tests/test_cli.py index 61b7d7f..122c8ae 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -79,7 +79,7 @@ class TestMainGroup: def test_version(self, runner): result = runner.invoke(main, ["--version"]) assert result.exit_code == 0 - assert "0.2.0" in result.output + assert "0.2.1" in result.output def test_no_command_shows_banner(self, runner): result = runner.invoke(main, []) diff --git a/uv.lock b/uv.lock index 525b7e8..ae7d314 100644 --- a/uv.lock +++ b/uv.lock @@ -1993,7 +1993,7 @@ wheels = [ [[package]] name = "remove-ai-watermarks" -version = "0.2.0" +version = "0.2.1" source = { editable = "." } dependencies = [ { name = "accelerate" },
{last}, {first}