From d416463242616a9d292bfa1f709c86ca3883d28d Mon Sep 17 00:00:00 2001 From: Phil Skentelbery Date: Tue, 16 Dec 2025 18:23:23 -0700 Subject: [PATCH] fix(media): Add MPO format support for iPhone portrait photos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iPhones use MPO (Multi-Picture Object) format for depth/portrait photos. This format contains multiple JPEG images (main + depth map). Pillow opens these as MPO format which wasn't in our allowed types. Added automatic MPO to JPEG conversion by extracting the primary image. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CHANGELOG.md | 3 ++- starpunk/media.py | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 971cd8e..0c0dcf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - HEIC/HEIF image format support for iPhone photo uploads -- Automatic HEIC to JPEG conversion (browsers cannot display HEIC) +- MPO (Multi-Picture Object) format support for iPhone depth/portrait photos +- Automatic HEIC/MPO to JPEG conversion (browsers cannot display these formats) - Graceful error handling if pillow-heif library not installed ### Dependencies diff --git a/starpunk/media.py b/starpunk/media.py index 1b650da..af981b6 100644 --- a/starpunk/media.py +++ b/starpunk/media.py @@ -160,6 +160,18 @@ def validate_image(file_data: bytes, filename: str) -> Tuple[bytes, str, int, in file_data = output.getvalue() img = Image.open(io.BytesIO(file_data)) + # MPO (Multi-Picture Object) conversion (v1.4.2) + # MPO is used by iPhones for depth/portrait photos - extract primary image as JPEG + if img.format == 'MPO': + output = io.BytesIO() + # Convert to RGB if needed + if img.mode in ('RGBA', 'P'): + img = img.convert('RGB') + img.save(output, format='JPEG', quality=95) + output.seek(0) + file_data = output.getvalue() + img = Image.open(io.BytesIO(file_data)) + # Check format is allowed if img.format: format_lower = img.format.lower()