unslothai/unsloth · warning · HTTPException

Unsupported format. Use webm or gif.

Error message

Unsupported format. Use webm or gif.

What it means

400 from the export route: the `format` query parameter, after strip().lower(), is not one of the two supported transcode targets ('webm' or 'gif'). The allowlist exists because each format pulls a specific encoder dependency chain, so anything else is rejected before any disk/CPU work starts.

Source

Thrown at studio/backend/routes/video.py:540

        media_type = "video/mp4",
        headers = {"Cache-Control": "private, max-age=31536000, immutable"},
    )


@router.get("/video/gallery/{video_id}/export")
async def export_gallery_video(
    video_id: str,
    format: str = "webm",
    current_subject: str = Depends(get_current_subject),
):
    """Download-menu transcodes: WebM (VP9) or GIF, re-encoded on demand from the
    stored MP4 (which the /file route serves verbatim). 501 with a clear message
    when the codec/deps for the requested format are missing."""
    from core.inference import video_gallery

    fmt = format.strip().lower()
    if fmt not in ("webm", "gif"):
        raise HTTPException(status_code = 400, detail = "Unsupported format. Use webm or gif.")
    try:
        path = await asyncio.to_thread(video_gallery.transcode_to_file, video_id, fmt)
    except RuntimeError as exc:
        raise HTTPException(status_code = 501, detail = str(exc)) from exc
    if path is None:
        raise HTTPException(status_code = 404, detail = "Video not found.")
    from fastapi.responses import FileResponse
    from starlette.background import BackgroundTask

    def _cleanup() -> None:
        try:
            path.unlink(missing_ok = True)
        except OSError as e:  # noqa: BLE001 -- a leaked temp file must not fail the download
            logger.debug(f"Could not remove the export temp file {path}: {e}")

    # FileResponse streams from disk, so a large VP9 export is never fully resident. The temp file is deleted once sent.
    return FileResponse(
        path,

View on GitHub (pinned to 203007d190)

Solutions

  1. Use format=webm or format=gif only.
  2. For the original MP4, link to the /file (or /file-signed) route instead of export.
  3. If a new format is genuinely needed, extend the tuple and add a matching branch in video_gallery.transcode_to_file.

Example fix

# before
<a href={`/video/gallery/${id}/export?format=mp4`}>Download</a>

# after
<a href={`/video/gallery/${id}/export?format=webm`}>WebM</a>
<a href={`/video/gallery/${id}/export?format=gif`}>GIF</a>
<a href={signedFileUrl}>MP4</a>
Defensive patterns

Strategy: validation

Validate before calling

const FORMATS = new Set(['webm', 'gif']);
function exportUrl(id: string, fmt: string) {
  const f = fmt.trim().toLowerCase();
  if (!FORMATS.has(f)) throw new RangeError(`format must be webm|gif, got ${fmt}`);
  return `/video/gallery/${id}/export?format=${f}`;
}

Type guard

function isExportFormat(v: string): v is 'webm' | 'gif' {
  return ['webm', 'gif'].includes(v.trim().toLowerCase());
}

Prevention

When it happens

Trigger: GET /video/gallery/{id}/export?format=mp4 / ?format=webm%20 / ?format=GIF (lowercased fine) / ?format=mov; a download menu that gained a new option without a backend counterpart; a typo'd or default-missing query param.

Common situations: Frontend adds 'Download MP4' to the menu assuming pass-through exists (it doesn't — /file serves the MP4 verbatim, use that instead); URL-encoded whitespace from a template string.

Related errors


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