jamiepine/voicebox · warning · ValueError

Invalid image file: {str(e)}

Error message

Invalid image file: {str(e)}

What it means

Returned by validate_image() when PIL.Image.open() or img.load() throws — the file could not be decoded as any image. The original exception string is embedded in the message. upload_avatar() re-raises it as a ValueError. Causes range from truncated/corrupt files to non-image files with an image extension.

Source

Thrown at backend/services/profiles.py:649

) -> VoiceProfileResponse:
    """
    Upload and process avatar image for a profile.

    Args:
        profile_id: Profile ID
        image_path: Path to uploaded image file
        db: Database session

    Returns:
        Updated profile
    """
    profile = db.query(DBVoiceProfile).filter_by(id=profile_id).first()
    if not profile:
        raise ValueError(f"Profile {profile_id} not found")

    is_valid, error_msg = validate_image(image_path)
    if not is_valid:
        raise ValueError(error_msg)

    if profile.avatar_path:
        old_avatar = config.resolve_storage_path(profile.avatar_path)
        if old_avatar is not None and old_avatar.exists():
            old_avatar.unlink()

    # Determine file extension from uploaded file
    from PIL import Image

    with Image.open(image_path) as img:
        # Normalize JPEG variants (MPO is multi-picture format from some cameras)
        img_format = img.format
        if img_format in ("MPO", "JPG"):
            img_format = "JPEG"

        ext_map = {"PNG": ".png", "JPEG": ".jpg", "WEBP": ".webp"}
        ext = ext_map.get(img_format, ".png")

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Re-save or re-download the source image so it is a complete, valid file.
  2. Open the file locally with an image viewer or PIL to confirm it decodes before uploading.
  3. Install required PIL plugins for the source format (e.g. pip install pillow-heif for HEIC).
  4. Verify the multipart upload completes and that no proxy truncates the body.

Example fix

// before: file is truncated/corrupt -> 'Invalid image file: ...'
// after: validate locally first
from PIL import Image
try:
    with Image.open('avatar.jpg') as im:
        im.load()  // raises if truncated
except Exception:
    # re-export or re-download the source
    ...
Defensive patterns

Strategy: validation

Validate before calling

from PIL import Image

def image_loads_cleanly(path: str) -> bool:
    try:
        with Image.open(path) as img:
            img.load()
        return True
    except Exception:
        return False

Try / catch

try:
    profile = await upload_avatar(profile_id, image_path, db)
except ValueError as e:
    if 'Invalid image file' in str(e):
        raise HTTPException(422, str(e))
    raise

Prevention

When it happens

Trigger: Uploading a truncated download (partial JPEG); a non-image file renamed to .jpg/.png; a zero-byte file; an image in a format PIL cannot decode without extra plugins (AVIF, HEIC without pillow-heif); a file corrupted in transit.

Common situations: Interrupted downloads; files renamed to spoof an extension; server-side buffer/truncation during multipart upload; clients sending the wrong file handle.

Related errors


AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12). Data as JSON: /api/errors/fb735c436b3bab2b. Report an issue: GitHub.