# How the Frame Cap Is Enforced After Deduplication in claude-video

> Discover how claude-video enforces frame caps after deduplication. Learn how _even_sample preserves key frames while optimizing your video content.

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

---

**The frame cap is enforced after deduplication by the `_even_sample` helper function, which evenly samples the deduplicated list down to the requested maximum while preserving the first and last frames.**

The `claude-video` repository provides a `watch` skill that extracts representative frames from video files for AI analysis. After removing near-identical images through perceptual deduplication, the system must ensure the final set respects the user-defined **frame cap**. This article explains exactly how that limit is applied in the source code, referencing the specific functions and file paths in [`skills/watch/scripts/frames.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/frames.py).

## The Two-Stage Processing Pipeline

Frame extraction in the `watch` skill follows a strict sequence: generate candidates, deduplicate, then enforce the cap. This separation ensures that deduplication works on the full visual content before any trimming occurs, preventing premature deletion of unique frames that might be needed later.

The pipeline intentionally decouples **deduplication** from **capping** to maximize content diversity. If the cap were applied first, the system might eliminate important scenes before discovering they were duplicates, resulting in a sparse, unevenly distributed frame set.

## Where Deduplication Ends and Capping Begins

The **deduplication** process runs via `dedupe_perceptual`, which compares frame fingerprints to remove near-identical images. However, this function operates independently of the `max_frames` parameter—it removes duplicates but does not count toward or enforce the user limit.

The cap is applied immediately afterward by calling `_even_sample` with the deduplicated list and the target count. This pattern appears consistently across all three extraction engines in [`skills/watch/scripts/frames.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/frames.py).

### Scene and Uniform Extraction Paths

In the scene detection engine (`extract_scene_or_uniform`), the code explicitly separates these two stages:

```python
deduped, n_dropped = dedupe_perceptual(scene_frames) if dedup else (scene_frames, 0)
cap = len(deduped) if max_frames is None else max_frames          # ← cap decision

selected = _even_sample(deduped, cap)                             # ← enforce cap

```

These operations occur at lines **443–452** in [`skills/watch/scripts/frames.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/frames.py). The `cap` variable resolves to either the full length of the deduplicated list (if no limit was specified) or the `max_frames` value provided by the user. The `_even_sample` function then performs the actual reduction.

### Key-Frame Extraction Path

The key-frame engine follows an identical pattern in `extract_keyframes` at lines **672–679**:

```python
deduped, n_dropped = dedupe_perceptual(candidates) if dedup else (candidates, 0)
cap = len(deduped) if max_frames is None else max_frames
selected = _even_sample(deduped, cap)

```

Whether the system uses scene detection, key-frame extraction, or the uniform fallback path, the architecture remains consistent: deduplicate first, then invoke `_even_sample` to enforce the limit.

## Inside the `_even_sample` Algorithm

The `_even_sample` function (lines **994–1007** in [`skills/watch/scripts/frames.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/frames.py)) handles the final enforcement logic through three distinct operations.

### Even Index Calculation

First, the function computes target indices using an internal `_even_indices(count, n)` helper. This generates **n** evenly spaced indices across the full range of deduplicated frames, mathematically guaranteeing that the first (index 0) and last (index count-1) frames are always retained. This preserves temporal boundaries while distributing the remaining selections uniformly.

### File System Cleanup

After determining which indices to keep, `_even_sample` deletes the physical JPEG files associated with the discarded indices. This cleanup ensures the output directory contains only the final, capped frame set, preventing storage bloat from intermediate files that exceeded the limit.

### Contiguous Re-indexing

Finally, the surviving frames are re-indexed to a contiguous `0..n-1` range. This normalization ensures downstream consumers receive a clean, predictable sequence regardless of how many frames were removed during deduplication or capping.

## Practical Implementation Examples

When calling the extraction functions in `claude-video`, the `max_frames` parameter controls the post-dedup cap:

```python

# Example: extract up to 50 frames, with deduplication

from pathlib import Path
from skills.watch.scripts.frames import extract_keyframes

frames, meta = extract_keyframes(
    video_path="demo.mp4",
    out_dir=Path("out"),
    max_frames=50,          # <-- desired cap

    dedup=True,
)

# `frames` now contains at most 50 entries, even-sampled after deduplication.

```

For scene-based extraction with the same enforcement:

```python
from skills.watch.scripts.frames import extract_scene_or_uniform

selected, meta = extract_scene_or_uniform(
    video_path="lecture.mp4",
    out_dir=Path("out"),
    fps=2.0,
    target_frames=80,       # <-- cap before dedup

    max_frames=80,
    dedup=True,
)

# `selected` respects the 80-frame limit after deduplication.

```

## Summary

- **Deduplication happens first**: `dedupe_perceptual` removes near-identical frames without considering the `max_frames` limit.
- **The cap is applied second**: `_even_sample` is called immediately after deduplication in both `extract_scene_or_uniform` and `extract_keyframes`.
- **Even sampling preserves boundaries**: The algorithm always retains the first and last frames while distributing selections evenly across the remaining temporal range.
- **Physical cleanup occurs**: Unselected frame files are deleted from disk, and survivors are re-indexed to a contiguous sequence.
- **Consistent across engines**: Whether using scene detection, key-frame extraction, or uniform sampling, the post-dedup capping logic remains identical in [`skills/watch/scripts/frames.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/frames.py).

## Frequently Asked Questions

### Does deduplication happen before or after the frame cap is applied?

Deduplication happens **before** the frame cap is applied. The `dedupe_perceptual` function processes the full candidate list to remove visual duplicates, then `_even_sample` enforces the `max_frames` limit on the resulting deduplicated set. This order ensures that unique frames are not prematurely discarded due to an initial cap that doesn't account for redundancy.

### What happens to the JPEG files of frames that exceed the cap?

The `_even_sample` function physically deletes the JPEG files for any frames that are not selected during the capping process. After computing the evenly spaced indices to retain, it removes the files at discarded indices from the output directory, ensuring only the capped frame set remains on disk.

### How does `_even_sample` decide which frames to keep?

`_even_sample` uses an internal `_even_indices` helper to calculate mathematically even spacing across the available frames. It always includes the first and last frames of the deduplicated list, then selects additional frames at regular intervals to reach the requested count. This guarantees temporal coverage from start to finish while respecting the maximum limit.

### Is the frame cap enforced differently for scene detection versus key-frame extraction?

No, the frame cap enforcement is **identical** across extraction methods. Both `extract_scene_or_uniform` (lines 443–452) and `extract_keyframes` (lines 672–679) use the same three-step pattern: deduplicate, calculate the cap value, then call `_even_sample`. The uniform fallback path also follows this same implementation in [`skills/watch/scripts/frames.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/frames.py).