RAW preview: exiftool fallback for MakerNote / SubIFD previews

kamadak-exif's In::PRIMARY / In::THUMBNAIL only address IFD0 and IFD1.
On modern Nikon NEFs the full-res review JPEG lives in the MakerNote's
PreviewIFD (and many Canon CR2s / DNGs put theirs in a SubIFD chain) —
both unreachable through the existing reader, so the previous patch
still produced no preview for those files and the pipeline fell through
to ffmpeg, which writes black frames when it can't decode the RAW.

Add a slow-path layer in extract_embedded_jpeg_preview that shells out
to exiftool for PreviewImage / JpgFromRaw / OtherImage (one process per
tag). All candidates from both layers are pooled and the largest valid
JPEG wins. exiftool not on PATH degrades to fast-path-only behavior
rather than breaking — the fallback is a strict superset.

Documented the new optional dependency in README.md and CLAUDE.md with
install commands for apt / brew / winget / choco.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cameron Cordes
2026-04-28 17:13:36 +00:00
parent 00b3c80141
commit 6521a328bf
3 changed files with 119 additions and 19 deletions

View File

@@ -111,6 +111,15 @@ All database access goes through trait-based DAOs (e.g., `ExifDao`, `SqliteExifD
2. Creates 200x200 thumbnails in THUMBNAILS directory (mirrors source structure)
3. Videos: extracts frame at 3-second mark via ffmpeg
4. Images: uses `image` crate for JPEG/PNG processing
5. RAW formats (NEF/CR2/ARW/DNG/etc.): the `image` crate can't decode RAW
pixel data, so the pipeline pulls an embedded JPEG preview instead. Fast
path is `exif::read_jpeg_at_ifd` against IFD0 (PRIMARY) and IFD1
(THUMBNAIL) — covers most older bodies and DNGs. Slow-path fallback shells
out to **`exiftool`** for `PreviewImage` / `JpgFromRaw` / `OtherImage`,
which reaches MakerNote / SubIFD-hosted previews kamadak-exif can't see
(e.g. Nikon's `PreviewIFD`, where modern Nikon bodies store the full-res
review JPEG). All candidates are pooled and the largest valid JPEG wins.
See `src/exif.rs::extract_embedded_jpeg_preview`.
**File Watching:**
Runs in background thread with two-tier strategy:
@@ -364,6 +373,8 @@ Configurable env:
## Dependencies of Note
### Rust crates
- **actix-web**: HTTP framework
- **diesel**: ORM for SQLite
- **jsonwebtoken**: JWT implementation
@@ -374,3 +385,18 @@ Configurable env:
- **opentelemetry**: Distributed tracing
- **bcrypt**: Password hashing
- **infer**: Magic number file type detection
### External binaries (must be on `PATH`)
- **`ffmpeg`** — video thumbnail extraction (`StreamActor`, HLS pipeline) and
the HEIF/HEIC/NEF/ARW thumbnail fallback in `generate_image_thumbnail_ffmpeg`.
Required for any deploy that holds video or HEIF files.
- **`exiftool`** — optional but strongly recommended for RAW-heavy libraries.
The thumbnail pipeline shells out to it as the slow-path fallback for
embedded preview extraction (Nikon MakerNote `PreviewIFD`, Canon SubIFDs,
etc. — anything kamadak-exif's IFD0/IFD1 readers can't reach). Without
exiftool installed, RAWs whose preview lives outside IFD0/IFD1 will fall
through to ffmpeg, which often produces black thumbnails. Install via
package manager: `apt install libimage-exiftool-perl`,
`brew install exiftool`, `winget install OliverBetz.ExifTool`, or
`choco install exiftool`.