# HEIC Image Support Design - v1.4.2

**Status**: Ready for Implementation
**Type**: Patch Release (backward compatible bug fix)
**Date**: 2025-12-16

## Problem Statement

iPhones save photos in HEIC format by default (since iOS 11). When users upload photos from iPhones to StarPunk, they receive "Invalid image format" errors because:

1. HEIC is not in `ALLOWED_MIME_TYPES`
2. Pillow cannot open HEIC files without the `pillow-heif` plugin
3. iOS sometimes renames `.heic` files to `.jpeg` without converting, causing confusion

HEIC files cannot be displayed directly in browsers, so conversion to JPEG is required.

## Design Overview

This is a minimal patch release with a single conceptual change: detect HEIC/HEIF images and convert them to JPEG before processing. The implementation requires:

1. **Dependency Addition**: Add `pillow-heif` to requirements.txt
2. **Code Change**: Modify `validate_image()` in `starpunk/media.py` to detect and convert HEIC

## Implementation Specification

### 1. Dependency Update

**File**: `/home/phil/Projects/starpunk/requirements.txt`

Add after Pillow:

```
# HEIC/HEIF Support (v1.4.2 - iPhone photos)
pillow-heif==0.18.*
```

Note: `pillow-heif` automatically registers with Pillow on import, enabling HEIC support.

### 2. Code Changes

**File**: `/home/phil/Projects/starpunk/starpunk/media.py`

#### 2.1 Add Import (top of file, after existing imports)

```python
# HEIC/HEIF support - import registers with Pillow automatically
try:
    import pillow_heif
    pillow_heif.register_heif_opener()
    HEIC_SUPPORTED = True
except ImportError:
    HEIC_SUPPORTED = False
```

Rationale: Conditional import allows graceful degradation if pillow-heif is not installed (e.g., during development).

#### 2.2 Modify `validate_image()` Function

Insert HEIC detection and conversion immediately after the Pillow image verification (line ~99), before the format check (line ~104).

**Insert after line 99** (after `img = Image.open(io.BytesIO(file_data))`):

```python
    # HEIC/HEIF conversion (v1.4.2)
    # HEIC cannot be displayed in browsers, convert to JPEG
    if img.format in ('HEIF', 'HEIC'):
        if not HEIC_SUPPORTED:
            raise ValueError(
                "HEIC/HEIF images require pillow-heif library. "
                "Please convert to JPEG before uploading."
            )
        # Convert HEIC to JPEG in memory
        output = io.BytesIO()
        # Convert to RGB if needed (HEIC may have alpha channel)
        if img.mode in ('RGBA', 'P'):
            img = img.convert('RGB')
        img.save(output, format='JPEG', quality=95)
        output.seek(0)
        # Re-open as JPEG for further processing
        file_data = output.getvalue()
        img = Image.open(io.BytesIO(file_data))
```

**Modify the return statement** to return the potentially converted `file_data`:

The current function signature returns `Tuple[str, int, int]` (mime_type, width, height). We need to also return the converted bytes when HEIC conversion occurs.

**Change return type** (line 84):

```python
def validate_image(file_data: bytes, filename: str) -> Tuple[bytes, str, int, int]:
```

**Change return statement** (line 138):

```python
    return file_data, mime_type, width, height
```

#### 2.3 Update `save_media()` to Handle New Return Value

**Modify line 400** in `save_media()`:

```python
        # Validate image (returns 4-tuple with potentially converted bytes)
        try:
            file_data, mime_type, orig_width, orig_height = validate_image(file_data, filename)
        except ValueError as e:
```

Note: The `file_data` variable is already in scope, so this reassignment handles the HEIC conversion case transparently.

## Design Decisions

### D1: Convert at Validation Time

**Decision**: Convert HEIC to JPEG during `validate_image()` rather than in a separate step.

**Rationale**:
- Keeps the change minimal (single function modification)
- Converted data flows naturally through existing `optimize_image()` pipeline
- No new function signatures or abstractions needed
- Validation is the logical place to normalize input formats

### D2: Convert to JPEG (not WebP or PNG)

**Decision**: Convert HEIC to JPEG format.

**Rationale**:
- JPEG has universal browser support
- HEIC photos are typically photographic content, which JPEG handles well
- Quality 95 preserves detail while reducing file size
- Consistent with existing JPEG optimization pipeline

### D3: Graceful Degradation

**Decision**: Use conditional import with `HEIC_SUPPORTED` flag.

**Rationale**:
- Allows code to work without pillow-heif during development
- Provides clear error message if HEIC upload attempted without library
- No runtime crash if dependency missing

### D4: Quality Setting

**Decision**: Use quality=95 for HEIC to JPEG conversion.

**Rationale**:
- Preserves most detail from the original
- Subsequent `optimize_image()` call will further compress if needed
- Matches existing optimization tier behavior for high-quality inputs

## Testing Requirements

### Unit Tests

Add to existing media tests in `/home/phil/Projects/starpunk/tests/test_media.py`:

1. **Test HEIC detection and conversion**
   - Upload valid HEIC file
   - Verify output is JPEG format
   - Verify dimensions preserved

2. **Test HEIC with alpha channel**
   - Upload HEIC with transparency
   - Verify conversion to RGB (no alpha in JPEG)

3. **Test error handling without pillow-heif**
   - Mock `HEIC_SUPPORTED = False`
   - Verify appropriate error message

### Test Files

A sample HEIC file is needed for testing. Options:
- Create programmatically using pillow-heif
- Download from a public test file repository
- Use iPhone simulator to generate

## Migration Notes

- **Database**: No changes required
- **Configuration**: No changes required
- **Existing uploads**: Not affected (HEIC was previously rejected)
- **Backward compatibility**: Fully backward compatible

## Files Changed

| File | Change |
|------|--------|
| `requirements.txt` | Add `pillow-heif==0.18.*` |
| `starpunk/media.py` | Add HEIC import, modify `validate_image()` |
| `starpunk/__init__.py` | Update `__version__` to `"1.4.2"` |
| `CHANGELOG.md` | Add v1.4.2 release notes |
| `tests/test_media.py` | Add HEIC test cases |

## Changelog Entry

```markdown
## [1.4.2] - 2025-12-XX

### Added
- HEIC/HEIF image format support for iPhone photo uploads
- Automatic HEIC to JPEG conversion (browsers cannot display HEIC)

### Dependencies
- Added `pillow-heif` for HEIC image processing
```

## Implementation Checklist

- [ ] Add `pillow-heif==0.18.*` to requirements.txt
- [ ] Add HEIC import and registration to media.py
- [ ] Modify `validate_image()` return type to include bytes
- [ ] Add HEIC detection and conversion logic to `validate_image()`
- [ ] Update `save_media()` to handle new return value
- [ ] Update `__version__` to "1.4.2"
- [ ] Add HEIC test cases
- [ ] Update CHANGELOG.md
- [ ] Run full test suite
