fix: harden metadata parsers and engines; sync docs (full-repo review)

Apply fixes from a full-repo review (code, tests, docs).

Security / correctness:
- Clamp attacker-controlled PNG/caBX chunk lengths to the remaining file
  size in metadata.py and noai/c2pa.py (a malformed length no longer drives
  a multi-GB read); skipped chunks seek instead of read.
- noai/isobmff.strip_c2pa_boxes is now fail-safe on a malformed box: return
  the original bytes with a warning instead of silently truncating the tail,
  so metadata --remove can no longer emit a corrupt file.
- doubao_engine._fixed_alpha_map clamps the glyph box to the image (no crash
  on degenerate width-vs-height).
- watermark_remover._run_region_hires gates the phaseCorrelate offset on
  response and magnitude (a spurious shift no longer garbles text) and drops
  the generator after a CPU fallback (no MPS/CPU device mismatch).

Robustness:
- gemini_engine, doubao_engine, region_eraser normalize grayscale and RGBA
  inputs to BGR at the engine entry points.
- image_io.imwrite returns False on an unwritable path (matches cv2).
- invisible_engine guards a None imread result before use.
- trustmark_detector._decoder uses a double-checked threading lock.
- ctrlregen.tiling.tile_positions raises on overlap >= tile.
- humanizer chromatic shift no longer wraps opposite-edge pixels.
- identify OpenAI caveat keyed on the normalized vendor, not a substring.
- Remove the dead "visible --detect-threshold" CLI option.
- publish.yml verifies the release tag matches the package version.

Docs:
- README strength 0.05 to 0.10; .env.example HF_TOKEN marked optional;
  doubao_capture README updated to reverse-alpha-only; CLAUDE.md synced with
  the new behaviors and the batch command.

Tests: new test_security_clamp.py for the read clamp and isobmff fail-safe;
erase CLI coverage; integrity-clash rule 2 end-to-end; multi-tag EXIF
survival and cross-format strip guards; channel/size, tiling, humanizer, and
imwrite regressions. Full suite 493 passed, 2 skipped; ruff and pyright src/
clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Victor Kuznetsov
2026-05-30 18:00:39 -07:00
co-authored by Claude Opus 4.8
parent 5298dcc6a3
commit 5d0e6c3a65
29 changed files with 580 additions and 43 deletions
+10 -2
View File
@@ -1,5 +1,11 @@
# Doubao visible watermark capture
> **Status (completed 2026-05-29):** the capture described below was carried out (black + gray
> Doubao captures) and the exact alpha map was solved. Removal is now **reverse-alpha only**: at the
> captured native width recovery is pixel-exact and inpaint is OFF; a residual inpaint runs off-native
> only. See the `doubao_engine.py` notes in the root `CLAUDE.md`. The text below is kept as the
> historical capture plan.
Goal: capture the Doubao "豆包AI生成" visible watermark over known flat backgrounds so we can
build a per-pixel alpha map and a reverse-alpha-blend remover, the same way the Gemini sparkle
engine works (`src/remove_ai_watermarks/gemini_engine.py`).
@@ -16,8 +22,10 @@ engine works (`src/remove_ai_watermarks/gemini_engine.py`).
- Size **scales with resolution**. Third-party numbers (~90x18 at <=1024, ~180x40 at >1024) are
approximate and calibrated for ~1024-1280 outputs; at 2048 the strip is much larger. A shipped
third-party alpha map is only 120x20, too small for our 2K/4K target -> capture fresh.
- In practice clean inversion leaves residue on textured backgrounds, so the remover pairs the alpha
map with inpainting (our Gemini engine already does gradient-masked inpainting for residual edges).
- The planning assumption was that clean inversion leaves residue on textured backgrounds, so the
remover would pair the alpha map with inpainting. After the capture this turned out unnecessary at
the native width (recovery is pixel-exact there and inpaint is off); the shipped remover is
reverse-alpha only, with a residual inpaint applied off-native only.
## Use doubao.com specifically