unslothai/unsloth · error · ValueError
Could not decode image: {exc}
Error message
Could not decode image: {exc} What it means
Raised when PIL fails to open or fully load the decoded blob — Image.open/size/load raised something other than the size ValueError (which is re-raised untouched). The bytes were valid base64 but not a decodable image: wrong format, corrupt file, truncated upload, or an unsupported codec.
Source
Thrown at studio/backend/core/inference/diffusion.py:388
# data:[<mime>][;base64],<payload>
_, _, raw = raw.partition(",")
try:
blob = base64.b64decode(raw, validate = False)
except (binascii.Error, ValueError) as exc:
raise ValueError(f"Invalid base64 image data: {exc}") from exc
# Bound the decoded size: 4096px covers txt2img 2048, upscales and outpaint canvases.
max_side = 4096
try:
img = Image.open(io.BytesIO(blob))
# Reject from the header before img.load() so a huge-dimension file cannot spike memory.
w, h = img.size
if w > max_side or h > max_side:
raise ValueError(f"Image is too large ({w}x{h}); maximum is {max_side}px per side.")
img.load()
except ValueError:
raise # the size guard's own message; don't wrap it as a decode error
except Exception as exc: # noqa: BLE001 — surfaced as a 400 to the client
raise ValueError(f"Could not decode image: {exc}") from exc
return img.convert(mode)
def _snap_to_multiple(img: Any, multiple: int = 16) -> Any:
"""Resize a PIL image so both sides are multiples of ``multiple`` (rounded to nearest,
minimum one multiple), preserving content with a high-quality resample.
Image-conditioned pipelines (Z-Image / Qwen / FLUX: 8x VAE downsample + 2x patch) reject
sizes that are not divisible by 16. Rather than error on an odd-sized upload, snap it so
the workflow just works; rounding to nearest keeps the rescale minimal/accurate."""
from PIL import Image
w, h = img.size
nw = max(multiple, int(round(w / multiple)) * multiple)
nh = max(multiple, int(round(h / multiple)) * multiple)
if (nw, nh) != (w, h):
img = img.resize((nw, nh), Image.LANCZOS)
return imgView on GitHub (pinned to 203007d190)
Solutions
- Convert the image to PNG or JPEG with a local tool before sending — these always decode
- For HEIC/AVIF/WebP sources, install Pillow with the needed codecs (pillow-heif, libwebp) on the producing side, or convert there
- Verify the file opens locally in an image viewer before uploading
Example fix
# before: sending HEIC bytes base64-encoded
# after: convert first
from PIL import Image
img = Image.open("photo.heic").convert("RGB")
img.save("photo.jpg") Defensive patterns
Strategy: try-catch
Validate before calling
def decodable_image(data: str) -> bool:
import base64, io
from PIL import Image
raw = data.strip().partition(",")[2] if data.strip().startswith("data:") else data.strip()
try:
with Image.open(io.BytesIO(base64.b64decode(raw))) as im:
im.size
return True
except Exception:
return False Try / catch
try:
img = parse_b64_image(data)
except ValueError as e:
if "Could not decode image" in str(e):
ask_user_to_reupload_as("PNG or JPEG") Prevention
- Convert HEIC/AVIF/exotic formats to PNG or JPEG before upload
- Ensure Pillow is built with libwebp if WebP inputs are expected
- Verify files open in a viewer before sending
When it happens
Trigger: Base64 of a PDF, WebP variant PIL cannot handle, or truncated multi-part file; HEIC/AVIF without decoder support in the installed Pillow; an image with a corrupt trailer failing at img.load().
Common situations: iPhone HEIC photos passed through untouched; files renamed to .png without conversion; partial uploads; Pillow built without libwebp.
Related errors
- Invalid base64 image data: {exc}
- Image is too large ({w}x{h}); maximum is {max_side}px per si
- Image '{original_name}' is too large; maximum is {_MAX_TRAIN
- Image '{original_name}' is too large ({width}x{height}); max
- No valid audio codes found after START_OF_SPEECH token
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/35400da3962dfcb9.
Report an issue: GitHub.