# Why MAX_FPS is Capped at 2.0 in claude-video and How It Interacts with max_frames

> Discover why MAX_FPS is capped at 2.0 in claude-video and how it interacts with max_frames to control token costs and prevent over-sampling.

- Repository: [bradautomates/claude-video](https://github.com/bradautomates/claude-video)
- Tags: internals
- Published: 2026-08-08

---

**The `MAX_FPS` constant is hard-coded at 2.0 frames per second to control token costs and prevent over-sampling, working in tandem with `max_frames` to bound the total extraction budget via the `_clamp_fps` helper function.**

The `claude-video` repository by bradautomates implements intelligent frame extraction to balance visual detail against API token consumption. Understanding why `MAX_FPS` is capped at 2.0 and how it interacts with `max_frames` is essential for optimizing video processing pipelines. This analysis examines the source code in [`skills/watch/scripts/frames.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/frames.py) to explain the architectural decisions behind these limits.

## Why the Frame Rate is Capped at 2.0 FPS

### Controlling Token Costs

Claude's API charges tokens per extracted frame. By enforcing a hard ceiling of 2.0 FPS, the `_clamp_fps` function ensures that short videos maintain dense sampling while longer videos are automatically throttled. This design prevents unexpected token explosions when processing extended content, keeping costs predictable regardless of video length.

### Preventing Over-Sampling

Visual content rarely changes significantly faster than two frames per second. Sampling at higher rates would generate near-duplicate images that add minimal semantic value while substantially increasing token usage and processing time. The 2.0 FPS cap represents a pragmatic balance between temporal resolution and information density.

## How MAX_FPS Interacts with max_frames

The coordination between these two limits occurs in the private helper `_clamp_fps` defined in [`skills/watch/scripts/frames.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/frames.py):

```python
def _clamp_fps(fps: float, duration_seconds: float, max_frames: int) -> tuple[float, int]:
    fps = min(fps, MAX_FPS)                     # ← enforce the 2 fps ceiling

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

```

`MAX_FPS` is defined at the top of the file on [line 19](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/frames.py#L19). Every automatic FPS calculation—whether from `auto_fps` (full video) or `auto_fps_focus` (specified range)—ultimately passes through this clamping mechanism.

### The Four-Step Workflow

1. **Determine effective duration** for the analysis window (full video or user-specified range).
2. **Calculate provisional fps** using `auto_fps` or `auto_fps_focus` based on content length.
3. **Clamp the fps** to `MAX_FPS` (2.0) via `_clamp_fps`.
4. **Compute final frame target** as `min(max_frames, round(fps * duration))`.

### Budget Scenarios

The interaction creates predictable extraction budgets:

- **Short clips**: A 10-second video with `max_frames=100` yields `min(100, 2×10) = 20` frames.
- **Long clips**: A 30-minute (1800s) video with `max_frames=100` yields `min(100, 2×1800) = 100` frames (capped by max_frames).
- **Uncapped detail**: With `max_frames=None`, the 2.0 FPS ceiling still limits output to `2 × duration_seconds` frames.

Because the FPS is never allowed to exceed 2.0, the maximum possible frame count is effectively `2.0 × duration_seconds`. If `max_frames` is lower than that product, the `max_frames` value wins and extraction stops early via ffmpeg's `-frames:v` flag.

## Configuring Frame Extraction via CLI

The `max_frames` parameter is parsed from `--max-frames` in [`skills/watch/scripts/watch.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/watch.py) ([lines 31-34](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/watch.py#L31)).

```bash

# Default: automatic fps (capped at 2.0) with 100-frame budget

python -m skills.watch.scripts.watch video.mp4

# Reduce frame budget to 40 (still capped at 2.0 fps)

python -m skills.watch.scripts.watch video.mp4 --max-frames 40

# Override fps cap entirely (requires explicit --fps)

python -m skills.watch.scripts.watch video.mp4 --fps 5 --max-frames 200

```

Explicit `--fps` arguments bypass the `MAX_FPS` ceiling, but `--max-frames` still constrains the total count, providing a hard upper bound on token consumption.

## Summary

- `MAX_FPS` is hard-coded at **2.0** in [`skills/watch/scripts/frames.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/frames.py) to limit token costs and prevent redundant frame extraction.
- The `_clamp_fps` function enforces this ceiling before calculating the final frame count.
- `max_frames` acts as a secondary, user-configurable hard limit on the absolute number of frames extracted.
- The effective frame count is always `min(max_frames, 2.0 × duration_seconds)` when using automatic FPS calculations.
- Explicit `--fps` CLI arguments can override the 2.0 cap, but `max_frames` remains the final safeguard against excessive token usage.

## Frequently Asked Questions

### What happens if max_frames is higher than 2.0 FPS × video duration?

If the product of the 2.0 FPS cap and video duration exceeds `max_frames`, extraction stops at `max_frames`. For example, a 30-minute video could theoretically generate 3,600 frames at 2 FPS, but with `max_frames=100`, the pipeline stops after extracting 100 frames, preserving the token budget.

### Can I override the 2.0 FPS cap in claude-video?

Yes. Passing an explicit `--fps` value via the CLI bypasses the `MAX_FPS` ceiling enforced by `_clamp_fps`. However, the `max_frames` limit still applies to the final output count, ensuring you maintain control over the absolute frame budget even when increasing temporal resolution.

### Where is the MAX_FPS constant defined?

The constant is defined on [line 19](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/frames.py#L19) of [`skills/watch/scripts/frames.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/frames.py) as `MAX_FPS = 2.0`. This value is imported and referenced throughout the frame extraction logic.

### How do auto_fps and auto_fps_focus interact with the cap?

Both functions calculate a provisional frames-per-second value based on video duration and analysis scope. Before extraction begins, `_clamp_fps` passes these provisional values through `min(fps, MAX_FPS)`, ensuring the final rate never exceeds 2.0 FPS unless the user explicitly overrides it via command-line arguments.