{"record":{"id":"eddf01a035e351e1","repo":"jamiepine/voicebox","slug":"invalid-format-img-format-allowed-formats-pn","errorCode":null,"errorMessage":"Invalid format '{img_format}'. Allowed formats: PNG, JPEG, WEBP","messagePattern":"Invalid format '(.+?)'\\. Allowed formats: PNG, JPEG, WEBP","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"warning","filePath":"backend/services/profiles.py","lineNumber":649,"sourceCode":") -> VoiceProfileResponse:\n    \"\"\"\n    Upload and process avatar image for a profile.\n\n    Args:\n        profile_id: Profile ID\n        image_path: Path to uploaded image file\n        db: Database session\n\n    Returns:\n        Updated profile\n    \"\"\"\n    profile = db.query(DBVoiceProfile).filter_by(id=profile_id).first()\n    if not profile:\n        raise ValueError(f\"Profile {profile_id} not found\")\n\n    is_valid, error_msg = validate_image(image_path)\n    if not is_valid:\n        raise ValueError(error_msg)\n\n    if profile.avatar_path:\n        old_avatar = config.resolve_storage_path(profile.avatar_path)\n        if old_avatar is not None and old_avatar.exists():\n            old_avatar.unlink()\n\n    # Determine file extension from uploaded file\n    from PIL import Image\n\n    with Image.open(image_path) as img:\n        # Normalize JPEG variants (MPO is multi-picture format from some cameras)\n        img_format = img.format\n        if img_format in (\"MPO\", \"JPG\"):\n            img_format = \"JPEG\"\n\n        ext_map = {\"PNG\": \".png\", \"JPEG\": \".jpg\", \"WEBP\": \".webp\"}\n        ext = ext_map.get(img_format, \".png\")\n","sourceCodeStart":631,"sourceCodeEnd":667,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/services/profiles.py#L631-L667","documentation":"Returned by validate_image() when PIL successfully loads the image but its normalized format is not in {PNG, JPEG, WEBP}. MPO and JPG are normalized to JPEG before the check. upload_avatar() re-raises this message as a ValueError. The allow-list keeps avatar storage predictable and avoids rarely-supported formats.","triggerScenarios":"Uploading a GIF, BMP, TIFF, WEBP-variant, or HEIC file as an avatar; a file with a misleading extension whose actual PIL-detected format is unsupported.","commonSituations":"Users exporting from design tools to GIF/BMP; screenshots saved as TIFF; HEIC photos from iPhones that PIL reads as HEIF (requires plugins); animated stickers in GIF form.","solutions":["Convert the image to PNG, JPEG, or WEBP before uploading.","On the client, restrict the file picker accept attribute to image/png,image/jpeg,image/webp.","If you genuinely need another format, add it to ALLOWED_FORMATS and the post-normalization set in backend/utils/images.py (and confirm process_avatar handles it).","For HEIC, install pillow-heif and normalize to JPEG."],"exampleFix":"// before: uploading avatar.gif\n// after: convert to PNG\nfrom PIL import Image\nImage.open('avatar.gif').convert('RGB').save('avatar.png', 'PNG')","handlingStrategy":"validation","validationCode":"from PIL import Image\n\ndef is_allowed_image_format(path: str) -> bool:\n    with Image.open(path) as img:\n        fmt = img.format\n        if fmt in ('MPO', 'JPG'):\n            fmt = 'JPEG'\n        return fmt in {'PNG', 'JPEG', 'WEBP'}","typeGuard":null,"tryCatchPattern":"try:\n    profile = await upload_avatar(profile_id, image_path, db)\nexcept ValueError as e:\n    if 'Invalid format' in str(e):\n        raise HTTPException(415, str(e))\n    raise","preventionTips":["Restrict the file picker to image/png,image/jpeg,image/webp on the client.","Convert unsupported formats before upload.","Keep ALLOWED_FORMATS and the post-normalization set in sync if you extend formats."],"tags":["avatar","image","format","validation","voicebox"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}