unslothai/unsloth · error · RuntimeError

WebM export failed: the clip has no video stream.

Error message

WebM export failed: the clip has no video stream.

What it means

Raised inside _transcode_webm right after opening the source clip when src.streams.video is empty, meaning the container opened but contains no video stream at all. It is a distinct, deliberate check before any encoder work so a bad input is not misreported as an encoder problem.

Source

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

    dest = transcode_to_file(video_id, fmt)
    if dest is None:
        return None
    try:
        return dest.read_bytes()
    finally:
        dest.unlink(missing_ok = True)


def _transcode_webm(path: Path, dest: Path) -> None:
    """Transcode ``path`` to VP9 (+ Opus when the clip has audio) at ``dest``."""
    try:
        import av
    except Exception as exc:  # noqa: BLE001 -- no PyAV -> no transcode
        raise RuntimeError("WebM export needs the 'av' package (PyAV).") from exc
    try:
        with av.open(str(path)) as src, av.open(str(dest), "w", format = "webm") as dst:
            if not src.streams.video:
                raise RuntimeError("WebM export failed: the clip has no video stream.")
            in_v = src.streams.video[0]
            rate = in_v.average_rate or 24
            out_v = dst.add_stream("libvpx-vp9", rate = rate)
            out_v.width = in_v.codec_context.width
            out_v.height = in_v.codec_context.height
            out_v.pix_fmt = "yuv420p"
            # Realtime settings: VP9's default "good" profile is slow; cpu-used 8 + row-mt is much faster at a small quality cost.
            out_v.options = {"deadline": "realtime", "cpu-used": "8", "row-mt": "1"}
            # An LTX-2 clip carries a synchronized audio track and WebM is the web-embed format, so dropping it would hand back half the
            # result. Opus is WebM's audio codec: resample to its 48 kHz grid and feed whole frames through a FIFO (960 samples per frame).
            in_a = src.streams.audio[0] if src.streams.audio else None
            out_a = fifo = resampler = None
            if in_a is not None:
                try:
                    stereo = (getattr(in_a.codec_context.layout, "nb_channels", 1) or 1) > 1
                    layout = "stereo" if stereo else "mono"
                    out_a = dst.add_stream("libopus", rate = 48000, layout = layout)
                    resampler = av.audio.resampler.AudioResampler(

View on GitHub (pinned to 203007d190)

Solutions

  1. Inspect the source file: ffprobe input.mp4 (or av.open + src.streams) to confirm it has a video stream.
  2. Fix or re-generate the source clip so it actually contains video frames.
  3. If the file is valid elsewhere, check for codec support in the installed PyAV build.

Example fix

# before
_transcode_webm(Path("audio_only.mp4"), dest)  # RuntimeError

# after: verify the source has a video track first
import av
with av.open(str(src_path)) as c:
    assert c.streams.video, "source has no video stream"
Defensive patterns

Strategy: validation

Validate before calling

import av

def has_video_stream(path) -> bool:
    with av.open(str(path)) as c:
        return bool(c.streams.video)

Try / catch

try:
    _transcode_webm(src, dest)
except RuntimeError as e:
    if "no video stream" in str(e):
        reject_source_clip()  # input defect; do not retry, re-encode or regenerate
    else:
        raise

Prevention

When it happens

Trigger: Exporting to WebM a file that is audio-only or a still-image container; a truncated/corrupt clip whose video track is unparseable so PyAV reports no video streams; passing a file with only a cover-art stream.

Common situations: Pipeline bug upstream wrote an empty or audio-only file where a video was expected; user-uploaded file with the wrong extension (.mp4 that is really an audio file).

Related errors


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