unslothai/unsloth · error · RuntimeError

GIF export decoded no frames.

Error message

GIF export decoded no frames.

What it means

Raised after the decode loop in _transcode_gif when the loop completed without appending a single frame ('if not frames'). Distinct from decode errors: the container opened, the stream exists, but zero frames were produced (e.g. the stream reports zero frames, or the step-skipping loop never yielded). There is nothing to feed frames[0].save(...), so it refuses before touching Pillow.

Source

Thrown at studio/backend/core/inference/video_gallery.py:253

                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:
                    scale = _GIF_MAX_EDGE / max(image.size)
                    image = image.resize(
                        (max(1, round(image.width * scale)), max(1, round(image.height * scale))),
                        Image.Resampling.LANCZOS,
                    )
                frames.append(image.convert("P", palette = Image.Palette.ADAPTIVE))
    except RuntimeError:
        raise
    except Exception as exc:  # noqa: BLE001 -- surface as "decoder unavailable"
        raise RuntimeError(f"GIF export failed to decode the clip: {exc}") from exc
    if not frames:
        raise RuntimeError("GIF export decoded no frames.")
    duration_ms = max(20, int(1000 * step / rate))
    buf = io.BytesIO()
    frames[0].save(
        buf,
        format = "GIF",
        save_all = True,
        append_images = frames[1:],
        duration = duration_ms,
        loop = 0,
    )
    return buf.getvalue()


def _sidecar_path(video_id: str) -> Path:
    return gallery_dir() / f"{video_id}.json"


# Sidecar keys every genuine Studio record carries. delete()/clear() own a pair only when its sidecar has all of these, so a

View on GitHub (pinned to 203007d190)

Solutions

  1. Check the source stream's frame count before exporting (in_v.frames).
  2. Regenerate the clip; an empty video is an upstream failure, not an export settings problem.
  3. Reject zero-frame uploads at the API boundary.

Example fix

# before
_transcode_gif(Path("headers_only.mp4"))  # RuntimeError('decoded no frames')

# after
import av
with av.open(str(p)) as c:
    v = c.streams.video[0]
    if not (v.frames or 0):
        raise HTTPException(400, "clip contains no frames")
Defensive patterns

Strategy: validation

Validate before calling

import av

def has_decoded_frames(path) -> bool:
    with av.open(str(path)) as c:
        if not c.streams.video:
            return False
        v = c.streams.video[0]
        return bool(v.frames or 0) or next(iter(c.decode(video=0)), None) is not None

Try / catch

try:
    gif = _transcode_gif(path)
except RuntimeError as e:
    if str(e) == "GIF export decoded no frames.":
        regenerate_clip(path)
        gif = _transcode_gif(path)  # one retry on a fresh file only
    else:
        raise

Prevention

When it happens

Trigger: A container whose video stream exists but contains zero frames (headers only); a clip where in_v.frames is 0 and demuxing yields nothing; edge-case files with all frames consumed by a bogus step computation.

Common situations: Zero-byte-payload video files from interrupted writes; screen-recorder stubs that finalize an empty container.

Related errors


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/4748b1586fdd1519. Report an issue: GitHub.