# Pre‑flight Checks in setup.py for ffmpeg, yt‑dlp, and API Key Validation

> Discover essential setup.py preflight checks in bradautomates/claude-video for ffmpeg, yt-dlp, and API key validation ensuring smooth operation before execution. Learn more now.

- Repository: [bradautomates/claude-video](https://github.com/bradautomates/claude-video)
- Tags: how-to-guide
- Published: 2026-08-10

---

**The [`setup.py`](https://github.com/bradautomates/claude-video/blob/main/setup.py) script in bradautomates/claude-video runs three mandatory pre‑flight checks: binary availability for `ffmpeg`, `ffprobe`, and `yt‑dlp`; detection of a Whisper API key via `_have_api_key()`; and an aggregated readiness status in `_status()` that returns `"ready"` only when all conditions pass.**

This article breaks down the validation logic implemented in [`skills/watch/scripts/setup.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/setup.py) so you understand exactly what gates must clear before the **/watch** video‑processing skill can execute.

---

## Binary Availability Check for ffmpeg, ffprobe, and yt‑dlp

The `_check_binaries()` function validates that three command‑line tools are present on the host system.

It uses `shutil.which` to probe the **PATH** for each binary:

- `ffmpeg` — required for video and audio transcoding
- `ffprobe` — required for media introspection
- `yt‑dlp` — required for YouTube/video downloads

```python

# From skills/watch/scripts/setup.py lines 35-68

def _check_binaries():
    required = ["ffmpeg", "ffprobe", "yt-dlp"]
    missing = []
    for binary in required:
        if shutil.which(binary) is None:
            missing.append(binary)
    return missing

```

If any binary is missing, it is appended to the `missing_binaries` list. This list drives subsequent user‑facing error messages and determines whether the installer logic triggers.

---

## API Key Presence Check for Whisper Backends

The second pre‑flight test determines whether a transcription backend is configured. The `_have_api_key()` function scans the environment (or a generated `.env` file) for credentials.

| Variable | Backend | Priority |
|----------|---------|----------|
| `GROQ_API_KEY` | groq | checked first |
| `OPENAI_API_KEY` | openai | fallback |

```python

# From skills/watch/scripts/setup.py lines 16-21

def _have_api_key():
    groq = os.getenv("GROQ_API_KEY")
    openai = os.getenv("OPENAI_API_KEY")
    if groq:
        return (True, "groq")
    if openai:
        return (True, "openai")
    return (False, None)

```

The function returns a tuple `(has_key: bool, backend: str | None)`. This enables downstream logic to report **which** provider is active, not merely **whether** a key exists.

---

## Aggregated Readiness Status in _status()

The `_status()` function combines binary and key checks into a single structured snapshot. According to the source code, four states are possible:

| State | Condition |
|-------|-----------|
| `"ready"` | No binaries missing **and** API key present |
| `"needs_install"` | Binaries missing, key present |
| `"needs_key"` | Binaries present, key missing |
| `"needs_install_and_key"` | Both binaries and key missing |

```python

# Logic derived from skills/watch/scripts/setup.py lines 29-44

def _status():
    missing = _check_binaries()
    has_key, backend = _have_api_key()
    
    if not missing and has_key:
        status = "ready"
    elif missing and not has_key:
        status = "needs_install_and_key"
    elif missing:
        status = "needs_install"
    else:
        status = "needs_key"
    
    can_proceed = (not missing) and (has_key or os.getenv("SETUP_COMPLETE") == "true")
    return {
        "status": status,
        "missing_binaries": missing,
        "has_key": has_key,
        "backend": backend,
        "can_proceed": can_proceed
    }

```

The `can_proceed` boolean merits attention: it evaluates to **True** when all binaries exist **and** the user either (a) has an API key or (b) has previously completed the installer with `SETUP_COMPLETE=true` persisted. This supports headless re‑runs without re‑authenticating.

---

## Silent and JSON Output Modes

The setup script supports two invocation patterns for CI and programmatic consumption.

**Silent check (exits 0 on success, non‑zero on failure):**

```bash
python3 skills/watch/scripts/setup.py --check

```

This is the mode used by [`hooks/scripts/check-setup.sh`](https://github.com/bradautomates/claude-video/blob/main/hooks/scripts/check-setup.sh) to gate Git operations.

**Machine‑readable JSON status:**

```bash
python3 skills/watch/scripts/setup.py --json

```

Example output structure:

```json
{
  "status": "needs_install",
  "missing_binaries": ["yt-dlp"],
  "has_key": true,
  "backend": "groq",
  "can_proceed": false
}

```

---

## Common Failure Scenarios and Messages

When checks fail, the script emits actionable guidance:

| Scenario | Output |
|----------|--------|
| Missing binaries, no key | `[watch] setup incomplete (missing binaries: ffmpeg, yt-dlp; no Whisper API key). Run: python3 /path/to/setup.py` |
| All checks pass | `[setup] ready. whisper backend: groq` |

Running the script without arguments launches the **interactive installer**, which:
- Installs missing binaries via Homebrew on macOS
- Scaffolds a `.env` file for API keys
- Writes `SETUP_COMPLETE=true` upon successful key detection

---

## Summary

- **Binary validation** — `_check_binaries()` uses `shutil.which` to confirm `ffmpeg`, `ffprobe`, and `yt-dlp` are on **PATH**
- **API key detection** — `_have_api_key()` returns `(bool, str)` tuple indicating presence and backend type
- **Readiness aggregation** — `_status()` computes four discrete states and a `can_proceed` boolean
- **CLI interface** — `--check` for silent exit codes, `--json` for structured output
- **Integration point** — [`hooks/scripts/check-setup.sh`](https://github.com/bradautomates/claude-video/blob/main/hooks/scripts/check-setup.sh) invokes `--check` to enforce pre‑conditions before Git push

---

## Frequently Asked Questions

### What happens if ffmpeg is installed but ffprobe is not?

The `_check_binaries()` function treats each binary independently as implemented in [`skills/watch/scripts/setup.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/setup.py) lines 35-68. Missing `ffprobe` alone will populate `missing_binaries` with `["ffprobe"]`, causing `_status()` to return `"needs_install"` and `can_proceed: false` until that binary is available.

### Can I bypass the API key check and still run /watch?

Yes, conditionally. The `can_proceed` logic evaluates to **True** if `SETUP_COMPLETE=true` is set in the environment, even without an API key. This allows re‑runs after initial setup. However, transcription features will fail at runtime if no key is configured when Whisper processing is requested.

### How does setup.py distinguish between Groq and OpenAI backends?

The `_have_api_key()` function checks `GROQ_API_KEY` first, then `OPENAI_API_KEY`, returning the matching backend string. This priority order means Groq takes precedence if both variables are set. The backend identifier propagates through `_status()` to user‑facing messages and JSON output.

### Why does --check produce no output on success?

This design supports shell scripting and Git hooks. The `cmd_check()` wrapper exits with code 0 when `_status()["can_proceed"]` is **True**, enabling constructs like `python3 setup.py --check || echo "Setup required"`. Failure cases print diagnostic context to **stderr** before exiting non‑zero.