affaan-m/ECC · error · MediaToolUnavailable

ffmpeg is required for temporal/style feature extraction

Error message

ffmpeg is required for temporal/style feature extraction

What it means

Dependency guard in _run_ffmpeg_features: the ffmpeg binary was not found on PATH, so timestamped luma/saturation/scene-change features cannot be extracted from the media. The message fires before any probing runs.

Solutions

  1. Install ffmpeg (apt install ffmpeg / brew install ffmpeg / conda install ffmpeg)
  2. Ensure the ffmpeg binary's directory is on PATH for the process running the workflow
  3. Pin ffmpeg in your Dockerfile/CI setup so it is always present

Example fix

// before
$ python workflow.py --config cfg.json  # MediaToolUnavailable
// after
$ sudo apt-get update && sudo apt-get install -y ffmpeg
$ which ffmpeg && python workflow.py --config cfg.json
Defensive patterns

Strategy: fallback

Validate before calling

import shutil
if shutil.which("ffmpeg") is None:
    raise SystemExit("ffmpeg not found on PATH; install it before running tasteforge")

Type guard

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

Try / catch

try:
    features = probe_media(path)
except MediaToolUnavailable:
    features = fallback_features_without_temporal_style(path)  # or abort with clear message

Prevention

When it happens

Trigger: probe_media -> _run_ffmpeg_features is invoked on a media file while shutil.which('ffmpeg') returns None (ffmpeg not installed or not on PATH of the calling process).

Common situations: Minimal Docker images or CI runners without ffmpeg; virtualenv/conda environments lacking the binary; PATH differences between interactive shell and cron/systemd.

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/44561b7103466e35. Report an issue: GitHub.

Appendix: source

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

    style_samples = []
    scene_changes = []
    for record in records:
        if "luma_raw" in record:
            style_samples.append({
                "time": round(record["time"], 6),
                "luma": round(record["luma_raw"] / 255.0, 6),
                "saturation": round(record.get("saturation_raw", 0.0) / 100.0, 6),
                "hue": record.get("hue"),
            })
        if record.get("scene_score", 0.0) >= scene_threshold:
            scene_changes.append(round(record["time"], 6))
    return {"style_samples": style_samples, "scene_changes": scene_changes}


def _run_ffmpeg_features(path: Path) -> dict[str, Any]:
    ffmpeg = shutil.which("ffmpeg")
    if not ffmpeg:
        raise MediaToolUnavailable("ffmpeg is required for temporal/style feature extraction")
    filters = (
        "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")

View on GitHub (pinned to 8321021c54)