{"record":{"id":"4748b1586fdd1519","repo":"unslothai/unsloth","slug":"gif-export-decoded-no-frames","errorCode":null,"errorMessage":"GIF export decoded no frames.","messagePattern":"GIF export decoded no frames\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":501,"severity":"error","filePath":"studio/backend/core/inference/video_gallery.py","lineNumber":253,"sourceCode":"                if i % step:\n                    continue\n                if len(frames) >= _GIF_MAX_FRAMES:\n                    # Frame count unknown up front (no stream metadata): stop at the cap.\n                    break\n                image = frame.to_image()\n                if max(image.size) > _GIF_MAX_EDGE:\n                    scale = _GIF_MAX_EDGE / max(image.size)\n                    image = image.resize(\n                        (max(1, round(image.width * scale)), max(1, round(image.height * scale))),\n                        Image.Resampling.LANCZOS,\n                    )\n                frames.append(image.convert(\"P\", palette = Image.Palette.ADAPTIVE))\n    except RuntimeError:\n        raise\n    except Exception as exc:  # noqa: BLE001 -- surface as \"decoder unavailable\"\n        raise RuntimeError(f\"GIF export failed to decode the clip: {exc}\") from exc\n    if not frames:\n        raise RuntimeError(\"GIF export decoded no frames.\")\n    duration_ms = max(20, int(1000 * step / rate))\n    buf = io.BytesIO()\n    frames[0].save(\n        buf,\n        format = \"GIF\",\n        save_all = True,\n        append_images = frames[1:],\n        duration = duration_ms,\n        loop = 0,\n    )\n    return buf.getvalue()\n\n\ndef _sidecar_path(video_id: str) -> Path:\n    return gallery_dir() / f\"{video_id}.json\"\n\n\n# Sidecar keys every genuine Studio record carries. delete()/clear() own a pair only when its sidecar has all of these, so a","sourceCodeStart":235,"sourceCodeEnd":271,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/core/inference/video_gallery.py#L235-L271","documentation":"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.","triggerScenarios":"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.","commonSituations":"Zero-byte-payload video files from interrupted writes; screen-recorder stubs that finalize an empty container.","solutions":["Check the source stream's frame count before exporting (in_v.frames).","Regenerate the clip; an empty video is an upstream failure, not an export settings problem.","Reject zero-frame uploads at the API boundary."],"exampleFix":"# before\n_transcode_gif(Path(\"headers_only.mp4\"))  # RuntimeError('decoded no frames')\n\n# after\nimport av\nwith av.open(str(p)) as c:\n    v = c.streams.video[0]\n    if not (v.frames or 0):\n        raise HTTPException(400, \"clip contains no frames\")","handlingStrategy":"validation","validationCode":"import av\n\ndef has_decoded_frames(path) -> bool:\n    with av.open(str(path)) as c:\n        if not c.streams.video:\n            return False\n        v = c.streams.video[0]\n        return bool(v.frames or 0) or next(iter(c.decode(video=0)), None) is not None","typeGuard":null,"tryCatchPattern":"try:\n    gif = _transcode_gif(path)\nexcept RuntimeError as e:\n    if str(e) == \"GIF export decoded no frames.\":\n        regenerate_clip(path)\n        gif = _transcode_gif(path)  # one retry on a fresh file only\n    else:\n        raise","preventionTips":["Check the stream's frame count before exporting.","Treat zero-frame files as upstream write failures, never as export-config problems.","Verify generation jobs wrote a nonzero frame count before marking clips done."],"tags":["video","gif","empty-file","validation"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}