invoke-ai/InvokeAI · error · ValueError

Video must use a browser-compatible H.264/AVC codec

Error message

Video must use a browser-compatible H.264/AVC codec

What it means

Raised by _probe_decodable_video in invokeai/app/api/routers/videos.py:227-228 after ffmpeg (probe_video_with_codec) reports the video's codec. InvokeAI only accepts videos whose codec is browser-compatible H.264/AVC (h264, avc, avc1, libx264); any other codec (HEVC/H.265, VP9, AV1, MPEG-4 Part 2) would be stored but fail to play in browsers, so the upload is rejected. The ValueError is caught by upload_video and surfaced as HTTP 415 'Failed to read video'.

Source

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

                    return len(major_brand) == 4 and major_brand != b"qt  "
                position += box_size
    except OSError:
        return False
    return False


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,

View on GitHub (pinned to 0b6a024f2f)

Solutions

  1. Re-encode the video to H.264: ffmpeg -i input.mp4 -c:v libx264 -pix_fmt yuv420p -c:a aac output.mp4
  2. Disable HEVC/'High Efficiency' recording in the capture device (iPhone: Settings > Camera > Formats > Most Compatible) and re-record
  3. Verify the codec before uploading with ffprobe -v error -select_streams v:0 -show_entries stream=codec_name input.mp4 and confirm it is h264/avc/avc1/libx264
  4. Check ffprobe is installed and functioning on the server — a missing/broken ffmpeg makes probe_video_with_codec return codec=None, triggering the same error

Example fix

// before: remux only (keeps HEVC codec)
ffmpeg -i hevc.mov -c copy out.mp4
// after: transcode to browser-compatible H.264
ffmpeg -i hevc.mov -c:v libx264 -pix_fmt yuv420p -c:a aac out.mp4
Defensive patterns

Strategy: validation

Validate before calling

import subprocess, json
def codec_is_h264(path):
    out = subprocess.run(["ffprobe", "-v", "error", "-select_streams", "v:0",
                          "-show_entries", "stream=codec_name", "-of", "json", path],
                         capture_output=True, text=True, check=True)
    codec = (json.loads(out.stdout).get("streams") or [{}])[0].get("codec_name", "")
    return codec.lower() in {"h264", "avc", "avc1", "libx264"}
# upload only if codec_is_h264("video.mp4")

Type guard

def is_h264_codec(codec: str | None) -> bool:
    return codec is not None and codec.lower() in {"h264", "avc", "avc1", "libx264"}

Try / catch

try:
    await client.upload_video(file)
except httpx.HTTPStatusError as e:
    if e.response.status_code == 415 and "Failed to read video" in e.response.text:
        transcode_to_h264(path)  # ffmpeg -c:v libx264 -pix_fmt yuv420p
        await client.upload_video(open(path, "rb"))
    else:
        raise

Prevention

When it happens

Trigger: POST /v1/videos/upload with an MP4 whose video track is not H.264/AVC — e.g. HEVC (iPhone default 'High Efficiency'), VP9/AV1 webm-renamed-to-mp4, or MPEG-4 Part 2 (Xvid/DivX). Also fires when ffprobe returns no codec at all (corrupt video stream).

Common situations: Recording on iOS/Android with HEVC enabled, exporting from screen recorders that default to H.265, converting containers without re-encoding (remux of a .mov HEVC file to .mp4), or uploading a file with a damaged/no video stream so ffprobe cannot read a codec name.

Related errors


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