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

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 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:

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. 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 (lines 31-34).


# 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 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 of 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →