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
- Use format=webm or format=gif only.
- For the original MP4, link to the /file (or /file-signed) route instead of export.
- 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
- Centralize the allowed-format list in one constant shared by menu and URL builder
- Use the /file route for raw MP4 instead of inventing format=mp4
- Lowercase and trim user/format input before it reaches the URL
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
- Invalid repo_id: {repo_id!r}
- Unsupported local dataset directory (expected parquet/json/j
- Unsupported file format: {dataset_path.suffix}
- Unsupported file type: {ext}. Allowed: {allowed}
- {str(exc)}
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/03734ae62a2df5c5.
Report an issue: GitHub.