unslothai/unsloth · error · RuntimeError
GIF export needs the 'av' and 'Pillow' packages.
Error message
GIF export needs the 'av' and 'Pillow' packages.
What it means
_transcode_gif lazily imports both PyAV ('av') and Pillow ('PIL.Image') and raises this RuntimeError when either import fails, since GIF export decodes with PyAV and re-encodes paletted frames with Pillow. MP4 export needs neither, and WebM needs only av, so this is GIF-specific.
Source
Thrown at studio/backend/core/inference/video_gallery.py:218
raise
except Exception as exc: # noqa: BLE001 -- surface as "encoder unavailable"
raise RuntimeError(f"WebM export failed (libvpx-vp9 unavailable?): {exc}") from exc
# Ceilings for a GIF export, which must hold every kept frame in memory before encoding. 720 px and 300 frames (25s at the
# 12 fps target) bound that at roughly 150 MB for the widest clip a generate request allows.
_GIF_MAX_EDGE = 720
_GIF_MAX_FRAMES = 300
def _transcode_gif(path: Path) -> bytes:
import io
try:
import av
from PIL import Image
except Exception as exc: # noqa: BLE001 -- missing deps -> no transcode
raise RuntimeError("GIF export needs the 'av' and 'Pillow' packages.") from exc
frames: list[Any] = []
try:
with av.open(str(path)) as src:
if not src.streams.video:
raise RuntimeError("GIF export failed: the clip has no video stream.")
in_v = src.streams.video[0]
rate = float(in_v.average_rate or 24)
# Full-rate GIFs are huge and stutter; ~12 fps (skipping source frames) is the sweet spot.
step = max(1, round(rate / 12))
# Every kept frame is held as a paletted image until the encoder runs, so an unbounded walk is a memory bomb (a 2048x2048 clip of 1024
# frames is >4 GB). Bound both axes: downscale past _GIF_MAX_EDGE and widen the step to at most _GIF_MAX_FRAMES. MP4 keeps the full clip.
total = in_v.frames or 0
kept = (total + step - 1) // step if total else 0
if kept > _GIF_MAX_FRAMES:
step = -(-total // _GIF_MAX_FRAMES)
for i, frame in enumerate(src.decode(in_v)):
if i % step:
continueView on GitHub (pinned to 203007d190)
Solutions
- pip install av Pillow.
- Reinstall whichever import fails (test with python -c "import av; from PIL import Image").
- Export MP4 instead if installing is not an option.
Example fix
# before
pip install av
# GIF export -> RuntimeError('needs av and Pillow')
# after
pip install av Pillow Defensive patterns
Strategy: fallback
Validate before calling
def gif_deps_available() -> bool:
try:
import av
from PIL import Image # noqa: F401,F403
return True
except Exception:
return False Try / catch
try:
data = export_clip(clip, fmt="gif")
except RuntimeError as e:
if str(e).startswith("GIF export needs"):
data = export_clip(clip, fmt="mp4")
else:
raise Prevention
- Install both 'av' and 'Pillow' when GIF export is in scope.
- Gate the GIF option in the UI on an import probe.
- Remember dependency asymmetry: MP4 needs neither, WebM needs av, GIF needs both.
When it happens
Trigger: Requesting GIF export where Pillow or av (or both) is missing from the environment; virtualenvs created without the extras after a fresh clone.
Common situations: Headless servers without Pillow; partial dependency installs where av was added for WebM but Pillow never was; Python upgrades breaking one of the two packages.
Related errors
- WebM export needs the 'av' package (PyAV).
- Speech-to-text needs the PyAV package to decode audio. Run `
- Video generation needs the 'av' package (PyAV) to encode MP4
- MiniMax-H3 needs the Diffusers revision bundled with this St
- WebM export failed (libvpx-vp9 unavailable?): {exc}
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/b5cf03d12ddb3441.
Report an issue: GitHub.