{"record":{"id":"3a81472a8d437b5b","repo":"invoke-ai/InvokeAI","slug":"video-has-no-decodable-frame","errorCode":null,"errorMessage":"Video has no decodable frame","messagePattern":"Video has no decodable frame","errorType":"http","errorClass":"ValueError","httpStatus":415,"severity":"error","filePath":"invokeai/app/api/routers/videos.py","lineNumber":235,"sourceCode":"def _probe_decodable_video(path: Path) -> tuple[tuple[int, int, float, Optional[float]], Optional[PILImage.Image]]:\n    \"\"\"Probes metadata and proves the video has a decodable first frame.\n\n    Returns the metadata plus the decoded frame so the save path can reuse it as the\n    thumbnail source instead of spawning another decode worker. A decode timeout is\n    contention on a loaded server, not evidence the video is bad — probe_video already\n    succeeded — so it yields (metadata, None) and the upload proceeds, with save-time\n    thumbnail extraction as the backstop.\n    \"\"\"\n    width, height, duration, fps, codec = probe_video_with_codec(path)\n    if codec is None or codec.lower() not in {\"h264\", \"avc\", \"avc1\", \"libx264\"}:\n        raise ValueError(\"Video must use a browser-compatible H.264/AVC codec\")\n    metadata = (width, height, duration, fps)\n    try:\n        first_frame = extract_video_frame(path, frame_index=0, raise_on_timeout=True)\n    except VideoDecodeTimeoutError:\n        return metadata, None\n    if first_frame is None:\n        raise ValueError(\"Video has no decodable frame\")\n    return metadata, first_frame\n\n\n@videos_router.post(\n    \"/upload\",\n    operation_id=\"upload_video\",\n    responses={\n        201: {\"description\": \"The video was uploaded successfully\"},\n        415: {\"description\": \"Video upload failed\"},\n    },\n    status_code=201,\n    response_model=VideoDTO,\n)\nasync def upload_video(\n    current_user: CurrentUserOrDefault,\n    file: UploadFile,\n    request: Request,\n    response: Response,","sourceCodeStart":217,"sourceCodeEnd":253,"githubUrl":"https://github.com/invoke-ai/InvokeAI/blob/0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06/invokeai/app/api/routers/videos.py#L217-L253","documentation":"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'.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-obtain or re-export the video file — the frame data itself is corrupt, remuxing will not help","Re-encode fully with ffmpeg -i input.mp4 -c:v libx264 -pix_fmt yuv420p output.mp4; if ffmpeg also errors, the source is unrecoverable","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 -)","If serving from object storage, confirm the upload to storage was complete before passing the file on"],"exampleFix":"// before: passing a partially-downloaded file straight to the API\nawait api.upload_video(truncated_file)\n// after: verify decodability first\nsubprocess.run([\"ffmpeg\", \"-v\", \"error\", \"-i\", path, \"-f\", \"null\", \"-\"], check=True)\nawait api.upload_video(open(path, \"rb\"))","handlingStrategy":"validation","validationCode":"import subprocess\ndef decodes_without_error(path):\n    r = subprocess.run([\"ffmpeg\", \"-v\", \"error\", \"-i\", path, \"-f\", \"null\", \"-\"],\n                       capture_output=True, text=True)\n    return r.returncode == 0 and r.stderr.strip() == \"\"","typeGuard":"def has_decodable_first_frame(first_frame) -> bool:\n    return first_frame is not None","tryCatchPattern":"try:\n    await client.upload_video(f)\nexcept httpx.HTTPStatusError as e:\n    if e.response.status_code == 415:\n        raise RuntimeError(\"Video has no decodable frame; re-export or re-download the file\") from e\n    raise","preventionTips":["Verify checksums after downloads to catch truncation","Run ffmpeg -v error -i file -f null - locally before uploading to detect corrupt streams","Never concatenate MP4s with cat; use ffmpeg concat demuxer","Ensure the encoding process completed before distributing the file"],"tags":["video","decode","ffmpeg","corrupt-file","upload"],"backgroundTag":"undecodable-video-frame","analyzedSha":"0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06","analyzedAt":"2026-08-29T04:46:49.967Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}