invoke-ai/InvokeAI · error · ValueError

Video has no decodable frame

Error message

Video has no decodable frame

What it means

Raised by _probe_decodable_video in invokeai/app/api/routers/videos.py:234-235 when extract_video_frame returns None for frame_index=0 without timing out. ffprobe read the container and codec fine, but no first frame could actually be decoded — the video is structurally valid but has no renderable picture data. The ValueError is caught by upload_video and returned as HTTP 415 'Failed to read video'.

Source

Thrown at invokeai/app/api/routers/videos.py:235

def _probe_decodable_video(path: Path) -> tuple[tuple[int, int, float, Optional[float]], Optional[PILImage.Image]]:
    """Probes metadata and proves the video has a decodable first frame.

    Returns the metadata plus the decoded frame so the save path can reuse it as the
    thumbnail source instead of spawning another decode worker. A decode timeout is
    contention on a loaded server, not evidence the video is bad — probe_video already
    succeeded — so it yields (metadata, None) and the upload proceeds, with save-time
    thumbnail extraction as the backstop.
    """
    width, height, duration, fps, codec = probe_video_with_codec(path)
    if codec is None or codec.lower() not in {"h264", "avc", "avc1", "libx264"}:
        raise ValueError("Video must use a browser-compatible H.264/AVC codec")
    metadata = (width, height, duration, fps)
    try:
        first_frame = extract_video_frame(path, frame_index=0, raise_on_timeout=True)
    except VideoDecodeTimeoutError:
        return metadata, None
    if first_frame is None:
        raise ValueError("Video has no decodable frame")
    return metadata, first_frame


@videos_router.post(
    "/upload",
    operation_id="upload_video",
    responses={
        201: {"description": "The video was uploaded successfully"},
        415: {"description": "Video upload failed"},
    },
    status_code=201,
    response_model=VideoDTO,
)
async def upload_video(
    current_user: CurrentUserOrDefault,
    file: UploadFile,
    request: Request,
    response: Response,

View on GitHub (pinned to 0b6a024f2f)

Solutions

  1. Re-obtain or re-export the video file — the frame data itself is corrupt, remuxing will not help
  2. Re-encode fully with ffmpeg -i input.mp4 -c:v libx264 -pix_fmt yuv420p output.mp4; if ffmpeg also errors, the source is unrecoverable
  3. Verify the download completed: compare file size/checksum with the source, and confirm the MP4 has a moov box (ffmpeg -v error -i input.mp4 -f null -)
  4. If serving from object storage, confirm the upload to storage was complete before passing the file on

Example fix

// before: passing a partially-downloaded file straight to the API
await api.upload_video(truncated_file)
// after: verify decodability first
subprocess.run(["ffmpeg", "-v", "error", "-i", path, "-f", "null", "-"], check=True)
await api.upload_video(open(path, "rb"))
Defensive patterns

Strategy: validation

Validate before calling

import subprocess
def decodes_without_error(path):
    r = subprocess.run(["ffmpeg", "-v", "error", "-i", path, "-f", "null", "-"],
                       capture_output=True, text=True)
    return r.returncode == 0 and r.stderr.strip() == ""

Type guard

def has_decodable_first_frame(first_frame) -> bool:
    return first_frame is not None

Try / catch

try:
    await client.upload_video(f)
except httpx.HTTPStatusError as e:
    if e.response.status_code == 415:
        raise RuntimeError("Video has no decodable frame; re-export or re-download the file") from e
    raise

Prevention

When it happens

Trigger: POST /v1/videos/upload with an MP4 that has a valid container/codec but undecodable frame data: truncated mdat (incomplete download), corrupted or missing moov/sync samples, a video track whose samples are all corrupt, or a zero-length/empty video stream.

Common situations: Downloads interrupted mid-transfer, files produced by a crashed encoder, heavily damaged storage media, files that were concatenated incorrectly, or synthetic/test files with valid headers but no actual compressed frames.

Related errors


AI-assisted analysis of invoke-ai/InvokeAI@0b6a024f2f (2026-08-29). Data as JSON: /api/errors/3a81472a8d437b5b. Report an issue: GitHub.