diff --git a/backend/app/tasks/thumbs.py b/backend/app/tasks/thumbs.py index 40f45a9..85f3d6a 100644 --- a/backend/app/tasks/thumbs.py +++ b/backend/app/tasks/thumbs.py @@ -102,17 +102,36 @@ def extract_raw_preview(filepath: str) -> Optional[Image.Image]: return None def process_heic_image(filepath: str) -> Image.Image: - """Process HEIC/HEIF image formats""" + """Process HEIC/HEIF image formats. + + Tries pillow-heif first (fast, native). Falls back to ffmpeg for + files that libheif rejects — e.g. iPhone photos with too many + auxiliary image references (depth maps, gain maps). + """ try: - # Use pillow-heif to open the image img = Image.open(filepath) - # Convert to RGB if needed if img.mode != 'RGB': img = img.convert('RGB') return img except Exception as e: - logger.error(f"Error processing HEIC file {filepath}: {e}") - raise + logger.warning(f"pillow-heif failed for {filepath}: {e} — trying ffmpeg") + + # ffmpeg fallback: decode HEIC to PNG in memory. + import subprocess + from io import BytesIO + try: + result = subprocess.run( + ['ffmpeg', '-i', filepath, '-frames:v', '1', + '-f', 'image2pipe', '-vcodec', 'png', 'pipe:1'], + capture_output=True, timeout=30, stdin=subprocess.DEVNULL, + ) + if result.returncode == 0 and result.stdout: + img = Image.open(BytesIO(result.stdout)).convert('RGB') + return img + logger.error(f"ffmpeg HEIC decode failed for {filepath}: {result.stderr.decode()[-200:]}") + except Exception as e2: + logger.error(f"ffmpeg fallback failed for {filepath}: {e2}") + raise RuntimeError(f"Cannot decode HEIC: {filepath}") def process_video_thumbnail(filepath: str) -> Image.Image: """Extract a still frame from a video file as a PIL Image."""