{"record":{"id":"5120ad61eb9ccca6","repo":"affaan-m/ECC","slug":"decoded-zero-frames-from-path","errorCode":null,"errorMessage":"decoded zero frames from {path}","messagePattern":"decoded zero frames from (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/taste/frames.py","lineNumber":81,"sourceCode":"        cap.release()\n        return frames\n\n    lo = int(total * skip_edges)\n    hi = int(total * (1.0 - skip_edges))\n    idxs = np.linspace(lo, max(lo + 1, hi - 1), num=min(n, max(1, hi - lo)))\n    idxs = np.unique(idxs.astype(int))\n\n    out: list[np.ndarray] = []\n    for i in idxs:\n        cap.set(cv2.CAP_PROP_POS_FRAMES, int(i))\n        ok, bgr = cap.read()\n        if not ok:\n            continue\n        out.append(_prep(bgr, max_edge))\n    cap.release()\n\n    if not out:\n        raise RuntimeError(f\"decoded zero frames from {path}\")\n    return out\n\n\ndef _sequential_sample(cap, n: int, max_edge: int) -> list[np.ndarray]:\n    frames = []\n    while True:\n        ok, bgr = cap.read()\n        if not ok:\n            break\n        frames.append(bgr)\n    if not frames:\n        return []\n    idxs = np.unique(np.linspace(0, len(frames) - 1, num=min(n, len(frames))).astype(int))\n    return [_prep(frames[i], max_edge) for i in idxs]\n\n\ndef _prep(bgr: np.ndarray, max_edge: int) -> np.ndarray:\n    h, w = bgr.shape[:2]","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/taste/frames.py#L63-L99","documentation":"`sample_frames()` decodes sampled frames and raises a RuntimeError if zero frames were successfully read even though the video opened. This catches containers where seeking/read failed for every frame (or the file is effectively empty of video). Downstream grade statistics would otherwise be computed from nothing.","triggerScenarios":"Calling `sample_frames()` on a video that opens but yields no decodable frames at the requested positions — corrupt index, zero-length video stream, every `cap.read()` returning `ok=False`, or the sequential fallback also decoding nothing.","commonSituations":"Truncated or interrupted downloads; videos whose container metadata lies about frame count and whose stream is actually empty; audio-only files passed as video; hardware/driver decode failures producing unreadable frames.","solutions":["Verify with `ffprobe -count_frames` that the file actually contains decodable video frames.","Re-encode/re-mux the file: `ffmpeg -i bad.mp4 -c copy fixed.mp4` to rebuild the index.","Confirm the input is a video, not an audio-only or image file.","Pre-check duration/frame_count via `probe()` and reject videos with frame_count <= 0."],"exampleFix":"// before\nframes = sample_frames('truncated.mp4', n=32)   # raises decoded zero frames\n// after\ninfo = probe('truncated.mp4')\nif info.frame_count <= 0:\n    raise ValueError(f\"no decodable frames in {info.path}\")\nframes = sample_frames('truncated.mp4', n=32)","handlingStrategy":"fallback","validationCode":"info = probe(src)\nif info.frame_count <= 0:\n    raise ValueError(f\"no decodable frames in {src}\")","typeGuard":null,"tryCatchPattern":"try:\n    frames = sample_frames(src, n=24)\nexcept RuntimeError as e:\n    if 'decoded zero frames' in str(e):\n        frames = fallback_extract_via_ffmpeg(src, n=24)  # ffmpeg -vf select\n    else:\n        raise","preventionTips":["Check frame_count via probe() before sampling","Remux truncated files with ffmpeg -c copy to rebuild indexes","Reject audio-only or image files at ingestion","Prefer explicit FFmpeg extraction as a fallback decode path"],"tags":["video","decoding","empty-result","runtime-error"],"backgroundTag":"empty-result-set","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}