affaan-m/ECC · error · MediaToolUnavailable

ffprobe is required for reference probing

Error message

ffprobe is required for reference probing

What it means

probe_media needs ffprobe on PATH to read timestamped style/temporal features from local media; shutil.which('ffprobe') returned nothing, so the environment is missing the ffmpeg toolset.

Solutions

  1. Install the full ffmpeg package so ffprobe ships alongside it (apt install ffmpeg)
  2. Verify with `which ffprobe` in the same environment that runs the workflow
  3. Fix PATH for the execution context (cron/systemd/CI often have a minimal PATH)

Example fix

# before
$ which ffprobe   # (empty)
# after
$ sudo apt-get install -y ffmpeg && which ffprobe
/usr/bin/ffprobe
Defensive patterns

Strategy: validation

Validate before calling

import shutil
missing = [t for t in ("ffmpeg", "ffprobe") if shutil.which(t) is None]
if missing:
    raise SystemExit(f"missing media tools: {', '.join(missing)}")

Type guard

def has_ffprobe() -> bool:
    import shutil
    return shutil.which("ffprobe") is not None

Try / catch

try:
    probe = probe_media(path)
except MediaToolUnavailable as e:
    logging.error("media tooling unavailable: %s", e)
    raise SystemExit(2)

Prevention

When it happens

Trigger: probe_media(path) invoked while shutil.which('ffprobe') is None — ffprobe not installed, or ffmpeg installed without ffprobe (rare minimal builds).

Common situations: Slim container images with ffmpeg but stripped ffprobe; partially installed/removed ffmpeg packages; PATH not including /usr/bin in service contexts.

Understand the failure class

Background: "not installed", "pip install", "required for": how missing-dependency errors surface across open-source libraries — this error's family across 34 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/648cbfa834f3dee9. Report an issue: GitHub.

Appendix: source

Thrown at skills/taste-application/scripts/tasteforge/workflow.py:371

        "scale=320:-2,"
        "select='not(mod(n\\,12))+gt(scene\\,0.30)',"
        "signalstats,metadata=print:file=-"
    )
    result = subprocess.run(
        [ffmpeg, "-v", "error", "-i", str(path), "-vf", filters,
         "-an", "-vsync", "0", "-f", "null", "-"],
        check=True,
        capture_output=True,
        text=True,
    )
    return parse_feature_output(result.stderr + "\n" + result.stdout)


def probe_media(path: Path) -> dict[str, Any]:
    """Probe local media and extract timestamped style/temporal features."""
    ffprobe = shutil.which("ffprobe")
    if not ffprobe:
        raise MediaToolUnavailable("ffprobe is required for reference probing")
    command = [
        ffprobe, "-v", "error", "-show_streams", "-show_format",
        "-of", "json", str(path),
    ]
    result = subprocess.run(command, check=True, capture_output=True, text=True)
    payload = json.loads(result.stdout)
    video: dict[str, Any] = next(
        (stream for stream in payload.get("streams", []) if stream.get("codec_type") == "video"),
        {},
    )
    duration = float(video.get("duration") or payload.get("format", {}).get("duration") or 0)
    rate = str(video.get("avg_frame_rate") or video.get("r_frame_rate") or "0/1")
    num, _, den = rate.partition("/")
    fps = float(num) / float(den or 1) if float(den or 1) else 0.0
    feature_data = _run_ffmpeg_features(path)
    measured_times = sorted({
        float(sample["time"]) for sample in feature_data["style_samples"]
    } | set(feature_data["scene_changes"]))

View on GitHub (pinned to 8321021c54)