Skip to content

Index and display .tif, .avif and .jfif images - #415

Merged
lstein merged 2 commits into
masterfrom
lstein/fix/image-taxonomy-gaps
Sep 29, 2026
Merged

lstein merged 2 commits into
masterfrom
lstein/fix/image-taxonomy-gaps

Conversation

@lstein

@lstein lstein commented Sep 29, 2026

Copy link
Copy Markdown
Owner

Fixes #364.

Problem

IMAGE_EXTENSIONS contained .tiff but not .tif (the spelling scanners, Photoshop and Windows tools produce), .avif or .jfif. An album of .tif scans indexed as silently empty, and the serving guard 403'd those suffixes.

Changes

  • Taxonomy: add .tif, .avif and .jfif to IMAGE_EXTENSIONS, and update the pinned literal in test_media_types.py.
  • Pillow floor: Pillow>=11.3.0. I checked the wheels: 11.1.0 and 11.2.1 report features.check('avif') == False, and 11.3.0 reports True. The lock is unchanged at 12.2.0.
  • HEIF opener: register_heif_opener() moves from an import side effect in embeddings.py into media_types.py. is_image("x.heic") now implies PIL can open it even for callers that import only media_types.
  • Displaying TIFF: only Safari renders TIFF, so a .tif that now indexed would still be a broken image in Chrome and Firefox. /images/ now converts TIFF to PNG as it already did for HEIC (BROWSER_CONVERTED_EXTENSIONS). The conversion:
    • runs in asyncio.to_thread, so a large scan doesn't stall other requests;
    • converts CMYK through the embedded ICC profile when there is one, and drops the stale profile otherwise;
    • rescales I;16B, I and F images to 16-bit instead of clipping them to a blank white or black page;
    • uses compress_level=1.
  • Downloads: single-item download now fetches ?original=1, so a saved .tif or .heic is the user's file and not the flattened PNG. This also fixes the same existing problem for HEIC.
  • Docs: the supported-format lists are updated.

Tests

The new tests/backend/test_image_formats.py covers:

  • every image suffix, generated at test time, lower- and upper-case, is picked up when indexing;
  • every suffix, plus a CMYK TIFF, is served in a form PIL decodes, and converted formats come back as image/png;
  • ?original=1 returns the file's exact bytes;
  • deep TIFF modes aren't clipped;
  • in a fresh interpreter, every is_image() format opens with embeddings never imported;
  • a completeness check fails if a suffix is added without a test image.

Full suite: 1224 backend and 953 frontend tests pass; ruff, eslint and prettier are clean.

Review

A fresh-context adversarial review found the download regression and the deep-TIFF clipping. Both are fixed here. It also flagged:

🤖 Generated with Claude Code

lstein and others added 2 commits September 29, 2026 08:08
IMAGE_EXTENSIONS lacked .tif (the spelling scanners and Windows tools
produce), .avif and .jfif, so albums of them indexed as silently empty
and the serving guard 403'd them.

- Add the three suffixes; require Pillow>=11.3.0, the first release
  whose wheels bundle AVIF.
- Register the HEIF opener in media_types rather than as a side effect
  of importing embeddings, so is_image() implies PIL can open the file.
- Serve TIFF as PNG, as HEIC already was, since only Safari renders it.
  Conversion runs off the event loop, handles CMYK (via the embedded ICC
  profile), big-endian 16-bit, 32-bit and float images without clipping,
  and uses fast PNG compression.
- Downloads request ?original=1 so a saved TIFF/HEIC is the user's own
  file, not the converted display copy.

Caching the conversions is tracked in #414.

Fixes #364

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lstein
lstein enabled auto-merge (squash) September 29, 2026 12:37
@lstein
lstein merged commit b5b78a3 into master Sep 29, 2026
11 checks passed
@lstein
lstein deleted the lstein/fix/image-taxonomy-gaps branch September 29, 2026 12:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Image taxonomy gaps: .tif, .avif and .jfif are silently skipped

1 participant