From 758fda619ef69a844f0ec166279d2a70ebf5dc66 Mon Sep 17 00:00:00 2001 From: Claudio Date: Sun, 10 May 2026 21:20:53 +0200 Subject: [PATCH] fix(raw): PIL fallback for iPhone Apple ProRAW / Linear DNG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LibRaw (rawpy 0.26.1, libraw 0.22.0) rejects Apple ProRAW Linear DNG with 'Unsupported file format or not RAW file'. These files aren't Bayer-pattern RAW — they're TIFF containers holding an already-developed RGB image, so PIL opens them directly. iPhone Linear DNG also has no embedded preview exiftool can extract, so the existing fallback chain ran out of options. Added PIL Image.open(src_path) as the last fallback in both code paths (_generate_proxy_webp for /photos/{id}/proxy, and tasks.thumbs.process_raw_image for thumbnail generation). Covers ~1,300 iPhone DNG files in the library that were 415-ing on every detail view. Co-Authored-By: Claude Opus 4.7 (1M context) --- backend/app/routers/photos.py | 12 ++++++++++-- backend/app/tasks/thumbs.py | 16 +++++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/backend/app/routers/photos.py b/backend/app/routers/photos.py index b86adc2..9c76500 100644 --- a/backend/app/routers/photos.py +++ b/backend/app/routers/photos.py @@ -745,8 +745,16 @@ def _generate_proxy_webp(src_path: str, dst_path: str) -> None: from io import BytesIO img = Image.open(BytesIO(thumb.data)) except Exception as e2: - logger.error(f"RAW preview extraction also failed for {src_path}: {e2}") - raise HTTPException(status_code=415, detail="Unable to decode RAW file") + # Last-resort: iPhone "Apple ProRAW" / Linear DNG isn't a + # Bayer-pattern RAW — LibRaw rejects it. The file IS a TIFF + # container with a developed RGB image inside, so PIL opens + # it directly. Same fallback covers misnamed TIFFs. + logger.warning(f"RAW preview extraction failed for {src_path}: {e2}; trying PIL TIFF fallback") + try: + img = Image.open(src_path) + except Exception as e3: + logger.error(f"PIL fallback also failed for {src_path}: {e3}") + raise HTTPException(status_code=415, detail="Unable to decode RAW file") # HEIC/HEIF — pillow-heif registers a PIL plugin elif ext in {'.heic', '.heif'}: diff --git a/backend/app/tasks/thumbs.py b/backend/app/tasks/thumbs.py index cd70e34..f466d6f 100644 --- a/backend/app/tasks/thumbs.py +++ b/backend/app/tasks/thumbs.py @@ -74,9 +74,19 @@ def process_raw_image(filepath: str) -> Image.Image: # Convert numpy array to PIL Image return Image.fromarray(rgb, 'RGB') except Exception as e: - logger.error(f"Error processing RAW file {filepath}: {e}") - # Try to extract embedded JPEG preview - return extract_raw_preview(filepath) + logger.warning(f"rawpy failed for {filepath}: {e}; trying embedded preview") + preview = extract_raw_preview(filepath) + if preview is not None: + return preview + # iPhone "Apple ProRAW" / Linear DNG has no embedded preview and + # LibRaw rejects it as not-a-RAW. It IS a TIFF container with a + # developed RGB image inside, so PIL opens it directly. + try: + logger.warning(f"embedded preview missing for {filepath}; trying PIL TIFF fallback") + return Image.open(filepath) + except Exception as e2: + logger.error(f"PIL fallback also failed for {filepath}: {e2}") + raise else: # Use exiftool to extract embedded preview return extract_raw_preview(filepath)