# Understanding the MAX_FPS Cap in claude-video: Why It's Set to 2.0

> Discover why claude-video caps MAX_FPS at 2.0. Learn how this limit controls token costs and ensures predictable LLM processing for video analysis.

- Repository: [bradautomates/claude-video](https://github.com/bradautomates/claude-video)
- Tags: deep-dive
- Published: 2026-07-19

---

**The `MAX_FPS` cap limits frame extraction to 2.0 frames per second to prevent unbounded token costs and ensure predictable LLM processing budgets when analyzing video content.**

The `bradautomates/claude-video` repository implements a hard ceiling on frame extraction rates to balance visual fidelity against the computational and financial constraints of large language model (LLM) inference. This architectural safeguard lives in the frame extraction pipeline and directly impacts how video content is prepared for AI analysis.

## What Is the MAX_FPS Cap?

In [`skills/watch/scripts/frames.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/frames.py), line 19 defines a global constant that governs the entire extraction pipeline:

```python
MAX_FPS = 2.0

```

This constant enforces an upper bound on sampling rates regardless of user input or video duration. The cap is applied through the `_clamp_fps` helper function (lines 49-52), which adjusts requested frame rates downward before calculating extraction targets:

```python
def _clamp_fps(fps: float, duration_seconds: float, max_frames: int) -> tuple[float, int]:
    fps = min(fps, MAX_FPS)               # Hard ceiling enforced here

    target = min(max_frames, max(1, int(round(fps * duration_seconds))))
    return fps, target

```

By centralizing the limit in a single constant, the maintainers created a tunable bottleneck that protects downstream systems from accidental resource exhaustion.

## Why the 2.0 FPS Limit Exists

The **2.0 frames-per-second** threshold represents a calculated trade-off between three competing constraints: token economy, processing speed, and visual comprehension.

### Preventing Token Budget Overruns

Every extracted frame generates tokens when sent to an LLM for captioning or question answering. Without the `MAX_FPS` ceiling, a 30-minute video sampled at 30 fps would produce 54,000 frames, creating prohibitive API costs and context window overflows. At 2 fps, the same video yields a maximum of 3,600 frames, which the `max_frames` parameter (typically set to 100–200 in [`skills/watch/scripts/config.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/config.py)) further constrains to a manageable subset.

### Maintaining Extraction Performance

High frame rates trigger exponentially more `ffmpeg` decode operations. By capping extraction at 2 fps, the pipeline minimizes I/O overhead and keeps the preprocessing step responsive for interactive use cases. This is particularly critical when [`skills/watch/scripts/watch.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/watch.py) orchestrates batch processing of multiple video segments.

### Preserving Semantic Granularity

Visual changes in typical content—scene cuts, slide transitions, speaker gestures—occur at frequencies well below 2 fps. The 2 fps sampling captures sufficient semantic information for transcription and Q&A tasks while filtering redundant interstitial frames that add noise without meaning.

## How MAX_FPS Is Enforced in the Code

The enforcement mechanism is transparent and deterministic. When [`watch.py`](https://github.com/bradautomates/claude-video/blob/main/watch.py) initiates extraction, it passes requested parameters through `_clamp_fps`, which applies the `min(fps, MAX_FPS)` operation before calculating the target frame count.

Consider a 5-minute video (300 seconds) with a budget of 100 frames:

```python
duration = 300.0
max_frames = 100
requested_fps = 10.0

fps, target = _clamp_fps(fps=requested_fps, duration_seconds=duration, max_frames=max_frames)

# Result: fps = 2.0, target = 100

```

Even though the caller requested 10 fps, the function returns 2.0 fps and calculates the target as `min(100, round(2.0 * 300)) = 100`, respecting both the FPS cap and the frame budget.

## Working With the FPS Cap

Understanding the cap's behavior helps developers optimize their video analysis workflows without accidentally triggering resource constraints.

### Automatic Clamping Behavior

By default, all extraction requests pass through `_clamp_fps`, ensuring the 2 fps ceiling applies universally. This prevents pipeline failures due to accidental misconfiguration in calling code or user input.

### Focused Extraction Mode

The `auto_fps_focus` function (also in [`frames.py`](https://github.com/bradautomates/claude-video/blob/main/frames.py)) handles user-specified time ranges by calculating a higher proposed fps for short segments. However, the cap still applies: `_clamp_fps` limits the effective rate to 2 fps even when the focus mode suggests denser sampling. This ensures temporary zooms on specific timestamps don't explode the token budget.

```python
fps, target = auto_fps_focus(duration_seconds=8.0, max_frames=100)

# Proposed fps may exceed 2.0, but returned fps is clamped to MAX_FPS

```

### CLI Overrides

Advanced users can bypass the cap using the command-line interface's `--fps` flag, which allows explicit override of the default behavior:

```bash
python3 -m skills.watch.scripts.frames video.mp4 out_dir --fps 5

```

This override is intentional: it permits high-density extraction when token costs are acceptable and the user explicitly accepts the trade-off. The default path, however, strictly respects the 2.0 limit defined in the source.

## Summary

- **`MAX_FPS = 2.0`** in [`skills/watch/scripts/frames.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/frames.py) creates a hard ceiling on frame extraction rates to protect LLM token budgets.
- The **`_clamp_fps`** function enforces this limit using `min(fps, MAX_FPS)` before calculating target frame counts.
- The 2 fps threshold balances **cost efficiency**, **processing speed**, and **semantic completeness** for typical video content.
- **Focused extraction** and **CLI overrides** provide flexibility, but the default pipeline strictly adheres to the cap to prevent resource exhaustion.

## Frequently Asked Questions

### Can I increase MAX_FPS beyond 2.0 for higher quality?

Yes, but you must modify the source code in [`skills/watch/scripts/frames.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/frames.py) line 19 or use the `--fps` CLI flag to override it temporarily. Increasing the cap raises token costs linearly and may degrade performance for long videos, so adjust with caution and monitor your LLM API usage.

### Does the FPS cap apply to focused extraction mode?

Partially. The `auto_fps_focus` function may calculate a higher proposed fps for short time ranges, but the `_clamp_fps` function still applies the `MAX_FPS` ceiling to the final value. The cap protects the budget even when analyzing brief segments, though the target frame calculation may suggest denser sampling initially.

### Where is MAX_FPS defined in the codebase?

The constant is defined at line 19 of [`skills/watch/scripts/frames.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/frames.py), alongside the `_clamp_fps` helper function that enforces it. This centralized definition makes it easy to adjust the system-wide limit as LLM pricing or hardware capabilities evolve.

### How does MAX_FPS interact with max_frames?

These parameters work sequentially. First, `_clamp_fps` limits the fps to 2.0. Then it calculates the theoretical frame count as `fps * duration_seconds`. Finally, it applies `min(max_frames, theoretical_count)` to ensure the result never exceeds your absolute frame budget. This two-stage clamping guarantees both rate and volume constraints are respected.