fix(metadata): remove_ai_metadata is fail-safe on a truncated/corrupt image

PIL raises OSError decoding a truncated file, which crashed remove_ai_metadata
(the PNG/WebP PIL re-save path) -- a direct library caller like a web worker
500s on a partial upload. ~0.2% of the real upload corpus is truncated. The
strip now probes decodability first and, on failure, copies the input through
unchanged and returns rather than raising (we cannot strip what we cannot parse),
mirroring strip_c2pa_boxes' fail-safe. identify already handled these.

The CLI `metadata --remove` on an unreadable file therefore now exits 0 with the
input passed through, not a clean error (exit 1) -- `visible`, which must decode
to remove a mark, still exits 1. Test updated to the per-command contract.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Victor Kuznetsov
2026-07-13 11:54:33 +03:00
co-authored by Claude Opus 4.8
parent 1dc6fee91a
commit 220803c4d0
4 changed files with 53 additions and 8 deletions
+17
View File
@@ -1120,6 +1120,23 @@ def remove_ai_metadata(
):
return output_path
# Fail-safe for a truncated / corrupt image: PIL raises OSError when it decodes a
# partial file (`img.copy()` / `img.save()` below), which would crash a direct
# library caller (a web worker 500s on a partial upload). Probe decodability first;
# if it fails, copy the input through unchanged and return -- we cannot strip what we
# cannot parse, but we never raise (mirrors strip_c2pa_boxes' fail-safe).
try:
with Image.open(source_path) as _probe:
_probe.load()
except Exception:
logger.warning("Could not decode %s to strip metadata (truncated/corrupt); copied through", source_path)
if output_path != source_path:
import shutil
output_path.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(source_path, output_path)
return output_path
# Read image and filter metadata
with Image.open(source_path) as img:
img = img.copy()