unslothai/unsloth · error · RuntimeError
GIF export failed: the clip has no video stream.
Error message
GIF export failed: the clip has no video stream.
What it means
Raised by _transcode_gif right after opening the clip when src.streams.video is empty — the container has no video stream to decode into frames. Identical in shape to the WebM path's check (error 504) but on the GIF path, which decodes frames into memory rather than transcoding stream-to-stream.
Source
Thrown at studio/backend/core/inference/video_gallery.py:223
# 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:
continue
if len(frames) >= _GIF_MAX_FRAMES:
# Frame count unknown up front (no stream metadata): stop at the cap.
break
image = frame.to_image()
if max(image.size) > _GIF_MAX_EDGE:View on GitHub (pinned to 203007d190)
Solutions
- Verify the input with ffprobe or an av.streams check before exporting.
- Regenerate or repair the source clip.
- Guard the export UI/API to reject files without a video stream early.
Example fix
# before
_transcode_gif(Path("empty.mp4")) # RuntimeError
# after
import av
with av.open(str(src_path)) as c:
if not c.streams.video:
raise HTTPException(400, "file has no video track") Defensive patterns
Strategy: validation
Validate before calling
import av
def gif_exportable(path) -> bool:
try:
with av.open(str(path)) as c:
return bool(c.streams.video)
except Exception:
return False Try / catch
try:
gif = _transcode_gif(path)
except RuntimeError as e:
if "no video stream" in str(e):
raise HTTPException(400, "source clip has no video track") from e
raise Prevention
- Check streams.video before any export call.
- Regenerate clips that fail the check — the file itself is defective.
- Reject video-less uploads at the request boundary.
When it happens
Trigger: GIF-exporting an audio-only or subtitle-only container; a zero-length video file; a corrupt clip whose video track failed to parse.
Common situations: Upstream generation crashed but left a stub file; user passes a music file to a GIF export endpoint.
Related errors
- WebM export failed: the clip has no video stream.
- GIF export failed to decode the clip: {exc}
- GIF export decoded no frames.
- That reference video decoded to no frames.
- Unknown model_kind '{model_kind}'. Expected one of {sorted(_
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/a30aa881f205a250.
Report an issue: GitHub.