How to Extract Frames at Specific Timestamps Using the `--timestamps` Option in claude-video
Pass a comma-separated list of timestamps to watch.py with --timestamps to extract exact frames at those positions—for example, --timestamps 4:32,7:10,9:55 creates cue_0000.jpg, cue_0001.jpg, and cue_0002.jpg.
The claude-video repository provides a frame extraction engine that lets you request precise video frames by absolute timestamps. This feature is implemented in skills/watch/scripts/frames.py and exposed through the watch.py CLI entry point. Whether you need frames at specific transcript cues or arbitrary time positions, the --timestamps option gives you direct control over which moments get captured.
Parsing Timestamp Strings with parse_timestamps()
The first step in the extraction pipeline converts raw timestamp strings into normalized floating-point seconds.
In skills/watch/scripts/frames.py (lines 95–103), the parse_timestamps() function:
- Splits comma-separated values (e.g.,
"4:32,7:10,9:55") - Parses each token as
MM:SSorHH:MM:SSformat - Returns a sorted, de-duplicated list of seconds
from skills.watch.scripts.frames import parse_timestamps
timestamps = parse_timestamps("4:32,7:10,9:55")
print(timestamps) # [272.0, 430.0, 595.0]
Deduplication and sorting ensure predictable output and prevent redundant ffmpeg invocations.
Selecting Valid Frames with extract_at_timestamps()
Once parsed, timestamps are filtered against user constraints before extraction begins.
The extract_at_timestamps() function (lines 124–158 in frames.py) performs three operations:
- Range filtering — Drops timestamps outside
--start/--endwindow boundaries - Even-sampling capping — Applies
_even_indices()ifmax_frameslimits the total cue count - Metadata preparation — Builds frame dictionaries with
"reason": "transcript-cue"
This design lets you specify many timestamps while respecting overall frame budgets. Cue frames are never silently dropped after extraction—they're protected through the merge step described below.
Extracting JPEG Frames via ffmpeg
For each retained timestamp, the engine spawns an ffmpeg subprocess that seeks exactly to the requested position.
In frames.py (lines 160–175), the extraction logic:
# Simplified representation of the actual implementation
cmd = [
"ffmpeg",
"-ss", str(timestamp), # seek to absolute position
"-i", str(video_path),
"-frames:v", "1", # output exactly one frame
"-q:v", "2", # high-quality JPEG
f"cue_{idx:04d}.jpg"
]
The -ss parameter precedes -i for fast, input-accurate seeking—critical for processing long videos efficiently. Output files follow the pattern cue_0000.jpg, cue_0001.jpg, etc., maintaining chronological correspondence with the input list.
Merging Cue Frames into the Final Frame Set
Cue frames coexist with frames from other extraction engines through merge_frames() (lines 112–119 in frames.py).
Key merge behaviors:
- Preserves chronological order across all frame sources
- Re-indexes the combined list for consistent referencing
- Protects cue frames from downstream even-sampling that might affect scene/keyframe selections
This matters when combining --timestamps with --detail balanced or high modes. Your explicitly requested frames remain guaranteed in the output.
Complete CLI Example
Request three exact frames from a YouTube video:
python3 "$SKILL_DIR/scripts/watch.py" "https://youtu.be/abc123" \
--detail balanced \
--timestamps 4:32,7:10,9:55
Execution flow:
- Downloads the video stream (if not cached)
- Parses timestamps to
[272.0, 430.0, 595.0] - Extracts
cue_0000.jpg,cue_0001.jpg,cue_0002.jpg - Includes them in final JSON with
"reason": "transcript-cue"
Programmatic Usage Without the CLI
Call the frame engine directly for custom workflows:
from pathlib import Path
from skills.watch.scripts.frames import (
parse_timestamps,
extract_at_timestamps,
)
video_path = "/tmp/video.mp4"
out_dir = Path("/tmp/frames")
timestamps = parse_timestamps("4:32,7:10,9:55")
frames, meta = extract_at_timestamps(
video_path,
out_dir,
timestamps,
resolution=512,
max_frames=None, # keep all cue frames
)
print(frames) # [{'path': 'cue_0000.jpg', 'timestamp_seconds': 272.0, ...}]
print(meta) # {'engine': 'timestamps', 'requested': 3, 'extracted': 3}
Combine with scene-based extraction:
from skills.watch.scripts.frames import extract_scene_or_uniform, merge_frames
scene_frames, _ = extract_scene_or_uniform(video_path, out_dir, detail="balanced")
cue_frames, _ = extract_at_timestamps(video_path, out_dir, timestamps)
all_frames = merge_frames(scene_frames, cue_frames)
Summary
parse_timestamps()(lines 95–103) converts"MM:SS,MM:SS"strings to sorted float secondsextract_at_timestamps()(lines 124–158) filters by range, applies caps, and prepares frame metadata- ffmpeg seeks (
-ss) generatecue_*.jpgfiles (lines 160–175) merge_frames()(lines 112–119) protects cue frames in mixed-engine outputs--timestampsintegrates with all--detailmodes without losing your requested frames
Frequently Asked Questions
What timestamp formats does --timestamps accept?
The parser accepts MM:SS and HH:MM:SS formats with comma separation. Values like "4:32,7:10,9:55" and "1:30:00,2:15:30" both work. Colons are required—decimal seconds like "4.5" are not supported by default.
Do timestamps outside the --start/--end window cause errors?
No. The extract_at_timestamps() function silently drops out-of-range timestamps before extraction. Only valid frames within your specified window are processed, preventing wasted ffmpeg calls.
How do cue frames differ from scene or keyframe selections?
Cue frames receive "reason": "transcript-cue" in their metadata and are produced by timestamp-specific seeking rather than content analysis. They merge with scene/keyframe outputs but bypass the visual analysis pipeline used for automatic scene detection.
Can I use --timestamps with --max-frames limits?
Yes. The engine applies even-sampling to your timestamp list if the count exceeds max_frames, similar to how scene frames get sampled. However, once extracted, cue frames are protected by merge_frames() and won't be dropped by later sampling stages applied to other frame types.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →