invoke-ai/InvokeAI · error · FileNotFoundError

Unable to open video at {video_path}

Error message

Unable to open video at {video_path}

What it means

Raised (as FileNotFoundError) by probe_video_with_codec when the decode worker's 'probe' command returns None, meaning the worker could not open the video (cv2/ffmpeg open failed) or the worker run failed. It reports the video as unopenable rather than returning partial metadata. Callers (_probe_decodable_video, probe_video) treat this as 'not a decodable video'.

Source

Thrown at invokeai/app/util/video_thumbnails.py:419

        Path(tmp_name).unlink(missing_ok=True)


def probe_video_with_codec(
    video_path: Path, timeout: float = VIDEO_DECODE_TIMEOUT_SECONDS
) -> tuple[int, int, float, Optional[float], Optional[str]]:
    """Returns (width, height, duration_seconds, fps_or_none, codec_or_none) for a video file.

    Raises FileNotFoundError if the file cannot be read — including when the decode
    times out, since a file we cannot probe within the bound is treated as unreadable —
    or when the decoder reports metadata no sane video has (non-positive or over-limit
    dimensions, non-finite or negative duration). Decoder-reported values are untrusted:
    they come from the uploaded container, and the upload path persists them and sizes
    thumbnail decoding by them. A non-finite or non-positive fps is coerced to None
    (unknown) rather than rejected, matching the decoder's own unknown-fps behavior.
    """
    result = _run_worker(["probe", str(video_path)], timeout)
    if result is None:
        raise FileNotFoundError(f"Unable to open video at {video_path}")
    try:
        width = int(result["width"])
        height = int(result["height"])
        duration = float(result["duration"])
        fps_raw = result.get("fps")
        fps: Optional[float] = float(fps_raw) if fps_raw else None
        codec_raw = result.get("codec")
        codec = str(codec_raw).lower() if codec_raw else None
    except (KeyError, TypeError, ValueError, OverflowError) as e:
        raise FileNotFoundError(f"Unable to open video at {video_path}") from e
    if width <= 0 or height <= 0 or width * height > MAX_VIDEO_FRAME_PIXELS:
        raise FileNotFoundError(f"Video at {video_path} reports invalid dimensions {width}x{height}")
    if not math.isfinite(duration) or duration < 0:
        raise FileNotFoundError(f"Video at {video_path} reports an invalid duration {duration}")
    if fps is not None and (not math.isfinite(fps) or fps <= 0):
        fps = None
    return width, height, duration, fps, codec

View on GitHub (pinned to 0b6a024f2f)

Solutions

  1. Check os.path.isfile(path) and read permissions before probing.
  2. Distinguish 'bad file' from 'busy decoder': a None/timeout under load is not corrupt — retry with a longer timeout or check worker logs.
  3. Probe only after the upload is complete and the file is flushed/closed.
  4. Validate the container with ffprobe -v error -select_streams v:0 to confirm a real video stream before calling the library.
  5. Catch FileNotFoundError and return a user-facing 'unsupported or missing video' error.

Example fix

// before
info = probe_video(path)  # FileNotFoundError: Unable to open video at path
// after
if not path.is_file() or path.stat().st_size == 0:
    raise MissingUploadError(path)
info = probe_video(path, timeout=60)
if info is None:
    raise UnsupportedMediaError(path)
Defensive patterns

Strategy: validation

Validate before calling

import os
def probe_target_ready(path: str) -> bool:
    p = os.path.realpath(path)
    return os.path.isfile(p) and os.access(p, os.R_OK) and os.path.getsize(p) > 0

Try / catch

try:
    info = probe_video(path, timeout=60)
except FileNotFoundError as e:
    if "Unable to open video" in str(e):
        return None  # treat as unsupported/missing, surface 415 to user

Prevention

When it happens

Trigger: Calling probe_video/probe_video_with_codec on a non-existent path, a path with permission denied, a file whose container OpenCV/FFmpeg cannot open, an empty file, or when _run_worker returned None due to slot exhaustion or timeout (raise_on_timeout=False).

Common situations: Uploads not yet fully written to disk when probing; files deleted between upload and probe; formats like MKV with unusual headers, or MOV files from drones with exotic codecs; transient slot exhaustion under load being misread as 'bad file'.

Related errors


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