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
- Re-encode the video to H.264: ffmpeg -i input.mp4 -c:v libx264 -pix_fmt yuv420p -c:a aac output.mp4
- Disable HEVC/'High Efficiency' recording in the capture device (iPhone: Settings > Camera > Formats > Most Compatible) and re-record
- 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
- 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
- Always encode to H.264 with yuv420p pixel format for browser delivery
- Never remux HEVC .mov to .mp4 with -c copy; transcode instead
- Pre-flight every file with ffprobe codec_name before upload
- Set capture devices to 'Most Compatible' (H.264) rather than 'High Efficiency' (HEVC)
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
- Video has no decodable frame
- Input videos are {width}x{height}; H.264 encoding requires e
- Video {video_name} is {width}x{height}; H.264 encoding requi
- Video dimensions {width}x{height} exceed the maximum decodab
- Unable to open video at {video_path}
AI-assisted analysis of invoke-ai/InvokeAI@0b6a024f2f (2026-08-29).
Data as JSON: /api/errors/35ce37b3741c26b8.
Report an issue: GitHub.