Extracts PNG frames from a YouTube video at a specified interval, generates a markdown manifest mapping filenames to timestamps, and uses an LLM to describe each frame's visual content and speaker quality.
Say it in your own words; the assistant finds this recipe by itself. Examples:
Use this skill when you need to visually analyze a YouTube video by extracting screenshots at regular intervals. It is ideal for creating a visual summary of a video, identifying key slides or diagrams, or selecting high-quality frames of speakers for further processing. It is specifically designed for workflows that require a structured list of video moments with textual descriptions.
Run the capture_video_frames.py script:
uv run .agents/skills/capture-video-frames/capture_video_frames.py <youtube_url> <output_dir> [--interval SECONDS]
youtube_url (required): YouTube video URL (same formats accepted by the extract-transcript skill).output_dir (required): Directory to save frames and the manifest file. Created if it doesn't exist.--interval (optional): Seconds between captured frames. Defaults to 30.Example frames_manifest.md:
| File | Timestamp | Description |
|------|-----------|-------------|
| frame_0000.png | [00:00] | |
| frame_0030.png | [00:30] | |
| frame_0060.png | [01:00] | |
brew install yt-dlp or pip install yt-dlpbrew install ffmpeg or apt-get install ffmpegdescribe-frame subagentAfter capturing frames, describe each frame by running the describe-frame custom agent as a subagent. Each subagent invocation gets an isolated context, so frame images won't accumulate and exhaust the context window.
The describe-frame agent is defined in .github/agents/describe-frame.md.
describe-frame agent as a subagent with a prompt that includes:(same as previous) if the frame is essentially identical to the previous one).Use this as the prompt when invoking the describe-frame subagent (fill in the bracketed values):
Describe the current frame image at: [CURRENT_FRAME_ABSOLUTE_PATH]
[If previous frame exists, include these two lines:]
The previous frame image is at: [PREVIOUS_FRAME_ABSOLUTE_PATH]
The previous frame was described as: "[PREVIOUS_DESCRIPTION]"
After describing all frames, frames_manifest.md should look like:
| File | Timestamp | Description |
|------|-----------|-------------|
| frame_0000.png | [00:00] | Title slide introducing "Building RAG apps with Python" |
| frame_0030.png | [00:30] | Speaker showing the agenda with four main topics |
| frame_0060.png | [01:00] | (same as previous) |
| frame_0090.png | [01:30] | Architecture diagram of a retrieval-augmented generation pipeline |
After all frames are described, groups of consecutive (same as previous) rows represent the same visual content captured at different moments. Within each group, speaker faces may differ — eyes open vs closed, mouth open vs closed, facing camera vs turned away.
For each group of duplicate frames, keep only one frame — the one with the best speaker face quality — and remove the rest.
(same as previous). Each group starts with the "anchor" frame (the one with an actual description) followed by one or more (same as previous) rows.describe-frame subagent to compare faces across the anchor frame and each duplicate. Use this prompt template:Compare these two frames focusing ONLY on the speaker faces visible in webcam feeds. Which frame has better speaker faces — eyes open, facing camera, mouth open (mid-speech), not mid-blink or turned away?
Frame A: [ANCHOR_FRAME_ABSOLUTE_PATH]
Frame B: [DUPLICATE_FRAME_ABSOLUTE_PATH]
Reply with ONLY one of:
- "A BETTER" if the anchor frame has better speaker faces
- "B BETTER" if the duplicate frame has better speaker faces
- "EQUAL" if both are equivalent
- "NO SPEAKERS" if no speaker faces are visible in either frame
Then add a brief reason.
(better speaker faces than frame_XXXX: eyes open, facing camera)(same as previous) rows from the manifest.After deduplication, if the best frame in a group still has the speaking person's mouth closed (both speakers have mouths closed), try recapturing at nearby timestamps:
bash
yt-dlp -f "bestvideo[height<=720]" --no-playlist -o "<output_dir>/video.%(ext)s" "<youtube_url>"bash
ffmpeg -ss <SECONDS> -i <output_dir>/video.mp4 -frames:v 1 -q:v 2 <output_dir>/alt_<FRAME>_<SECONDS>.png -ydescribe-frame subagent to check if the speaker's mouth is open in any alternative, AND that the slide/demo content is still the same.cp alt_XXXX.png frame_XXXX.png).rm -f <output_dir>/alt_*.png <output_dir>/video.mp4LICENSESKILL.mdcapture_video_frames.pyVendored from pamelafox/presentation-skills, commit 2b809b3a8d2f, path .agents/skills/capture-video-frames, licence MIT. The upstream licence file ships inside the skill.