{"record":{"id":"ae44c7ce27a11165","repo":"calesthio/OpenMontage","slug":"files-api-failed-to-process-the-uploaded-video","errorCode":null,"errorMessage":"Files API failed to process the uploaded video","messagePattern":"Files API failed to process the uploaded video","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"tools/video/gemini_omni_video.py","lineNumber":275,"sourceCode":"        )\n        upload_resp.raise_for_status()\n        file_info = upload_resp.json().get(\"file\", {})\n\n        # Wait until the uploaded video is processed before referencing it.\n        deadline = time.time() + _MAX_POLL_SECONDS\n        while str(file_info.get(\"state\", \"\")).upper() == \"PROCESSING\":\n            if time.time() > deadline:\n                raise TimeoutError(\"Uploaded video did not finish processing in time\")\n            time.sleep(_POLL_INTERVAL_SECONDS)\n            status_resp = requests_mod.get(\n                f\"{_BASE_URL}/{file_info.get('name')}\",\n                headers={\"x-goog-api-key\": api_key},\n                timeout=15,\n            )\n            status_resp.raise_for_status()\n            file_info = status_resp.json()\n        if str(file_info.get(\"state\", \"\")).upper() == \"FAILED\":\n            raise RuntimeError(\"Files API failed to process the uploaded video\")\n\n        uri = file_info.get(\"uri\")\n        if not uri:\n            raise RuntimeError(f\"Files API response missing uri: {file_info}\")\n        return uri\n\n    @staticmethod\n    def _extract_output_video(data: dict[str, Any]) -> dict[str, Any] | None:\n        \"\"\"Find the output video payload ({'data': b64} or {'uri': files/...}).\"\"\"\n        for key in (\"output_video\", \"outputVideo\"):\n            video = data.get(key)\n            if isinstance(video, dict) and (video.get(\"data\") or video.get(\"uri\")):\n                return video\n        # REST responses may also carry the video inside steps[].content[].\n        for step in data.get(\"steps\") or []:\n            for item in step.get(\"content\") or []:\n                if isinstance(item, dict) and (item.get(\"data\") or item.get(\"uri\")):\n                    if \"video\" in str(item.get(\"type\", \"\")).lower() or item.get(\"mime_type\", \"\").startswith(\"video/\"):","sourceCodeStart":257,"sourceCodeEnd":293,"githubUrl":"https://github.com/calesthio/OpenMontage/blob/95e1c3d0ab93482159818560f6a8c8e866b9139f/tools/video/gemini_omni_video.py#L257-L293","documentation":"Raised when the Google Files API reports state=FAILED for an uploaded video. The Files API validates and transcodes uploads asynchronously; if the media is rejected (corrupt, unsupported, or violates policy) the file transitions from PROCESSING to FAILED instead of ACTIVE, and the tool raises RuntimeError before ever using the file.","triggerScenarios":"After a video upload, the polling loop exits with file_info.state == 'FAILED' on the status GET to {BASE_URL}/{file.name}. Caused by corrupt/truncated uploads, unsupported codecs, or content rejected by Google's media pipeline.","commonSituations":"Truncated upload (network drop mid-PUT so the stored bytes are invalid); unsupported container/codec (e.g. some ProRes/HEVC variants); zero-byte file passed by mistake; policy-flagged content; partial disk write left an incomplete file.","solutions":["Verify the source file plays locally (ffprobe/quick look) and is not truncated, then re-upload.","Re-encode to H.264 MP4 with AAC audio — the most reliably supported format for the Files API.","Check the file size matches expectation before passing it in; reject zero-byte files upstream.","If the file is valid and small, inspect the file's error details via the Files API status response for a specific reason.","Retry once — occasional transient transcode failures do occur."],"exampleFix":"// before\n{\"video_path\": \"clip.mov\"}  // ProRes 4444, Files API marks FAILED\n\n// after (shell)\nffmpeg -i clip.mov -c:v libx264 -pix_fmt yuv420p -c:a aac clip.mp4\n// then\n{\"video_path\": \"clip.mp4\"}","handlingStrategy":"validation","validationCode":"from pathlib import Path\nimport subprocess\n\ndef validate_video_for_files_api(path: str) -> None:\n    p = Path(path)\n    if p.stat().st_size == 0:\n        raise ValueError(\"empty file will be marked FAILED\")\n    subprocess.run(\n        [\"ffprobe\", \"-v\", \"error\", \"-select_streams\", \"v:0\", \"-show_entries\",\n         \"stream=codec_name\", \"-of\", \"csv=p=0\", str(p)],\n        check=True, capture_output=True,\n    )  # unreadable stream -> non-zero exit, don't upload","typeGuard":null,"tryCatchPattern":"try:\n    result = gemini_omni_video(inputs)\nexcept RuntimeError as e:\n    if \"failed to process the uploaded video\" in str(e).lower():\n        inputs = {**inputs, \"video_path\": reencode_h264(inputs[\"video_path\"])}\n        result = gemini_omni_video(inputs)  # one retry with clean encoding\n    else:\n        raise","preventionTips":["Always upload H.264/AAC MP4 — most reliably accepted by the Files API.","Never pass files whose size is 0 or differs from what your pipeline wrote.","Treat one FAILED as a media problem first: fix the file, not the deadline."],"tags":["gemini","files-api","video-upload","media-validation","codec"],"backgroundTag":null,"analyzedSha":"95e1c3d0ab93482159818560f6a8c8e866b9139f","analyzedAt":"2026-08-15T06:31:20.014Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}