/watch-video — Transcribe and analyze any video at the depth you choose skill
When you want to extract content from a video — YouTube, Loom, Vimeo, Riverside, Zoom recording, local MP4, X/IG video, anything yt-dlp supports.
by coreyhaines31·MIT license·★ 855 Stars on the repo·GitHub ↗
npx degit coreyhaines31/makerskills/skills/watch-video#main ~/.claude/skills/watch-videoChecked ·commit main
Inside this skill
SKILL.md · the author's own words# /watch-video — Transcribe and analyze any video at the depth you choose Replaces and broadens the prior `youtube-transcript` skill. YouTube is now one of many sources; depth is user-controlled. ## Step 1 — Parse input Accept: - **YouTube**: full URL, `youtu.be/<id>`, `youtube.com/shorts/<id>`, raw 11-char ID - **Loom**: `loom.com/share/<id>` or `loom.com/embed/<id>` - **Vimeo**: `vimeo.com/<id>` - **Riverside**: download URL or local file - **Zoom**: local `.mp4` from a downloaded recording - **X / IG / TikTok video**: URL — defers to `social-fetch` for metadata, uses yt-dlp for the file - **Local file**: any path to an `.mp4` / `.mov` / `.webm` / `.mkv` Detect source from URL pattern or file extension. If ambiguous, ask.
Word-for-word excerpt, not a result. Read the full SKILL.md ↓
Files of /watch-video — Transcribe and analyze any video at the depth you choose
Show the full text322 lines
/watch-video — Transcribe and analyze any video at the depth you choose
Replaces and broadens the prior youtube-transcript skill. YouTube is now one of many sources; depth is user-controlled.
Step 1 — Parse input
Accept:
- YouTube: full URL,
youtu.be/<id>,youtube.com/shorts/<id>, raw 11-char ID - Loom:
loom.com/share/<id>orloom.com/embed/<id> - Vimeo:
vimeo.com/<id> - Riverside: download URL or local file
- Zoom: local
.mp4from a downloaded recording - X / IG / TikTok video: URL — defers to
social-fetchfor metadata, uses yt-dlp for the file - Local file: any path to an
.mp4/.mov/.webm/.mkv
Detect source from URL pattern or file extension. If ambiguous, ask.
Step 2 — Parse depth mode
| Invocation | Mode | What you get |
|---|---|---|
/watch-video <url> |
transcript (default) | Clean text, metadata, optional chapters |
/watch-video <url> transcript |
transcript | Same as default |
/watch-video <url> visual |
visual | Transcript + frames at intervals + Claude vision pass identifying key moments |
/watch-video <url> multimodal |
multimodal | Native video to Gemini (if $GEMINI_API_KEY), else dense Claude vision frame-by-frame |
If the depth isn't specified and the video is >10 minutes, ask before defaulting (visual/multimodal cost real money on long videos).
Step 3 — Pull metadata
For URL sources, use yt-dlp:
yt-dlp --print "%(title)s|%(uploader)s|%(duration_string)s|%(upload_date>%Y-%m-%d)s|%(description)s" \
--print "%(chapters)j" --skip-download "<url>"
Capture: title, uploader/channel, duration, upload date, description (first paragraph), chapters (JSON or null).
For local files, use ffprobe:
ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "<file>"
Step 4 — Build workdir
~/Documents/videos/<source>-<slug>-<date>/
Where:
source:youtube/loom/vimeo/riverside/zoom/localslug: kebab-case of title (first 4–6 words, max 50 chars)date:YYYY-MM-DD
Step 5 — Get the transcript
Backend selection (in order):
Platform-provided transcript if it exists and looks complete:
- YouTube:
yt-dlp --write-sub --write-auto-sub --skip-download --sub-lang en --sub-format vtt - Loom: fetch via
https://www.loom.com/share/<id>page metadata or Loom API if$LOOM_API_KEYset - Riverside: built-in transcripts available on the recording's share page
- If platform transcript exists and has timestamps, use it. Skip Whisper.
- YouTube:
MLX-Whisper local (default on Apple Silicon Macs):
# Install once: pip install mlx-whisper python3 -c "import mlx_whisper; mlx_whisper.transcribe('<file>', path_or_hf_repo='mlx-community/whisper-large-v3-turbo')" \ > "<workdir>/transcript-raw.json"Or via the CLI:
mlx_whisper <file> --model mlx-community/whisper-large-v3-turbo --output-dir <workdir>Not on Apple Silicon (Linux, Intel Mac, Windows, cloud agents):
faster-whisper(pip install faster-whisper, CPU or CUDA) orwhisper.cpp. Same model size, same output handling.
Download the video file first if it's a URL (use yt-dlp; Loom/Vimeo/YT all supported):
yt-dlp -f "bv*[height<=720]+ba/b[height<=720]" -o "<workdir>/video.%(ext)s" "<url>"
720p is plenty for transcription and frame analysis (smaller download, faster processing).
Clean the transcript (only needed for YouTube auto-subs which have rolling captions; Whisper output is already clean):
# YouTube VTT cleanup — de-dup rolling captions, strip tags, paragraph-break on cue gaps >2s
awk '
/^WEBVTT/ || /^Kind:/ || /^Language:/ || /^NOTE/ { next }
/-->/ { in_cue = 1; last = ""; next }
/^$/ { if (last) print last; in_cue = 0; last = ""; next }
in_cue { gsub(/<[^>]+>/, "", $0); last = $0 }
END { if (last) print last }
' "<workdir>/transcript.en.vtt" | awk '!seen[$0]++' > "<workdir>/transcript.txt"
Save final to <workdir>/transcript.txt.
Step 6 — If transcript mode: stop here
Output:
transcript.txtmetadata.json- One-line summary in chat: title, source, duration, word count
- Path to workdir
- (Optional) Step 9 — offer to capture to second-brain
Step 7 — If visual mode: extract frames + vision pass
Frame extraction (ffmpeg)
Cadence by source heuristic:
| Source type | Frame cadence |
|---|---|
| Screen-share / Loom / demo | 1 frame per 5s (UI changes fast) |
| Talking head / podcast | 1 frame per 30s (slow change) |
| Slide presentation | 1 frame per 10s + force a frame on each detected scene change |
| Default if unsure | 1 frame per 15s |
mkdir -p "<workdir>/frames"
ffmpeg -i "<workdir>/video.mp4" -vf "fps=1/15" "<workdir>/frames/frame-%04d.png" -y
For scene-change detection (slide decks especially):
ffmpeg -i "<workdir>/video.mp4" -vf "select='gt(scene,0.3)',showinfo" -vsync vfr "<workdir>/frames/scene-%04d.png" 2> "<workdir>/scene-detection.log"
Vision pass
Pair each frame with the transcript chunk for the same timestamp window. Then batch-send to Claude vision for synthesis.
Per-frame batch prompt (up to ~10 frames per call):
Here are N frames from a video at timestamps T1..TN. For each frame, describe what's on screen in 1–2 sentences. Flag: (a) UI changes from previous frame, (b) text visible on screen, (c) any moment that looks like a decision, action, or notable event. Also note the transcript text spoken during this window.
Save the output as <workdir>/moments.md:
# Key moments — <title>
## 00:00:15 (frame-001.png)
**On screen**: Login form, email field focused
**Transcript**: "So you just open it up and..."
**Note**: Beginning of UI demo
## 00:00:45 (frame-002.png)
**On screen**: Dashboard with 4 cards
**Transcript**: "And here's where you see all your projects."
**Note**: Major view change — first time the dashboard appears
Generate summary
After moments are identified, synthesize the whole video into <workdir>/summary.md:
# Summary — <title>
**Source:** <source URL / file>
**Duration:** <hh:mm:ss>
**Watched at:** <date>
**Mode:** visual
## TL;DR
<2–4 sentences>
## Key moments
- 00:00:15 — <one-line>
- 00:00:45 — <one-line>
## Action items flagged
- <item> [timestamp]
## Decisions flagged
- <decision> [timestamp] — consider routing to /decide
## Quotes worth keeping
- "..." [timestamp]
## Open questions
- <question raised but not answered>
Step 8 — If multimodal mode
Backend selection
Gemini native if
$GEMINI_API_KEYis set (much cheaper + faster than per-frame for long videos):Default model:
gemini-3.5-flash(released May 2026, ~$1.50 input / $9 output per 1M tokens; ~$0.15/sec of video; beats 3.1 Pro on coding/agentic benchmarks at 4× the speed). Override togemini-3.1-profor brand audits / high-stakes analysis where details matter;gemini-2.5-flash-litefor bulk cheap processing.# Step 1: Upload video via Files API FILE_URI=$(curl -s -X POST "https://generativelanguage.googleapis.com/upload/v1beta/files?key=$GEMINI_API_KEY" \ -H "X-Goog-Upload-Command: start, upload, finalize" \ -H "Content-Type: video/mp4" \ --data-binary "@<workdir>/video.mp4" | jq -r '.file.uri') # Wait until file is ACTIVE (Gemini processes the video first) while true; do STATE=$(curl -s "$FILE_URI?key=$GEMINI_API_KEY" | jq -r '.state') [ "$STATE" = "ACTIVE" ] && break sleep 3 done # Step 2: Generate content with the file + multimodal-analysis prompt curl -s -X POST "https://generativelanguage.googleapis.com/v1beta/models/gemini-3.5-flash:generateContent?key=$GEMINI_API_KEY" \ -H "Content-Type: application/json" \ -d "{ \"contents\":[{ \"parts\":[ {\"file_data\":{\"mime_type\":\"video/mp4\",\"file_uri\":\"$FILE_URI\"}}, {\"text\":\"<multimodal analysis prompt — see Step 7's summary template + use-case extensions>\"} ] }] }"Files persist in Gemini Files API for ~48 hours — useful for re-querying the same video with different prompts.
Dense Claude vision fallback if no Gemini key:
- Frame cadence: 1 frame per 3s (much denser than visual mode)
- Batch through Claude vision with the multimodal-analysis prompt
- Slower and more expensive than Gemini for long videos — warn the user before running on >10min content
Multimodal output
Same summary.md template as Step 7 + an extended section:
## Multimodal observations
- **Body language / delivery**: <observations on talking-head video>
- **Pacing**: <fast/slow/uneven>
- **Visual style**: <brand audit, ad review, design observations>
- **Audio quality / atmosphere**: <music, silence, background>
Exact extra sections depend on the use case (brand audit, ad review, talk delivery review, client-call read). Use case is inferred from the source + the user's verbal framing when invoking.
Step 9 — Optional: capture to second-brain
After any mode completes, offer:
"Want to capture this to second-brain? I'll write a
call-<slug>.md(ormeeting-/note-/resource-) to${SECOND_BRAIN_VAULT:-$HOME/Documents/SecondBrain}/raw/with the summary, source URL, and transcript link."
Type prefix by source:
| Source | Prefix |
|---|---|
| Loom / Zoom / Riverside / Otter / call recording | call- |
| Meeting (own notes, not a transcript) | meeting- |
| Talk / keynote / conference | note- |
| Ad / landing-page video / marketing reference / competitor video | resource- |
File body: 1-line source, the summary, link to full workdir.
Step 10 — Report
In chat:
- One-line headline:
<source> · <title> · <duration> · <mode> · <word count> words - Workdir path
- For
visual/multimodal: brief list of top 3 key moments - For all modes: any action items / decisions flagged for triage
- If captured to second-brain: that path too
Sources reference
| Source | Download | Built-in transcript | Notes |
|---|---|---|---|
| YouTube | yt-dlp |
Auto-subs (--write-auto-sub) |
Same as the prior youtube-transcript skill |
| Loom | yt-dlp (Loom supported) |
Yes — fetch via embed metadata or Loom API | Async screenshare focus — prime use case |
| Vimeo | yt-dlp |
Sometimes | Marketing/embed videos |
| Riverside | Direct URL from export, or local file | Yes — Riverside generates them | Podcast episodes |
| Zoom | Local .mp4 (downloaded recordings) |
Sometimes (Zoom audio transcript file) | Client calls |
| X / IG / TikTok | Defer to social-fetch for metadata, yt-dlp for file |
No | Short-form |
| Local file | n/a | n/a | Drop a path |
Composes with
social-fetch— for X/IG/TikTok URL metadata (engagement, author, replies) before video processingsecond-brain— capture summary asraw/call-<slug>.md,meeting-,note-, orresource-per source typedecide— when a video contains a flagged decision, route to/decidefor structured capturepm— action items flagged in summary can be triaged to project boardsslide-deck— talk recordings → outline extraction → deck draft (loop)jab-hook— quotes + clip-worthy moments from podcast/talk videos feed BIP/promo postsskillify from-video— primary use case forvisualmode on process recordings. the user records themselves doing a workflow (Loom/screen-share), this skill extracts transcript + key visual moments, thenskillifysynthesizes the workflow into a SKILL.md. "Record once, AI converts to skill."
Error handling
| Failure | Response |
|---|---|
| Video unavailable / private / region-locked | Report and stop |
| No subtitles + Whisper not installed | Tell the user: pip install mlx-whisper (Apple Silicon) or pip install faster-whisper (anything else) |
| ffmpeg missing (for visual/multimodal) | Tell the user: brew install ffmpeg |
| Vision pass returns empty / unclear | Lower the frame count, retry, or fall back to transcript-only with a note |
Multimodal requested but no $GEMINI_API_KEY and >30min video |
Warn cost, offer to fall back to visual mode |
yt-dlp binary missing |
brew install yt-dlp |
Notes on quality
- User picks depth, not the skill. Transcript / visual / multimodal are 3 different cost + latency profiles. Long videos (>10 min) always confirm before spending on visual/multimodal.
- Platform transcript first, Whisper second. YouTube auto-subs, Loom transcripts, Riverside built-in transcripts — all free + instant when they exist. Fall back to local Whisper only when nothing platform-provided works.
- Local Whisper is the fast path. MLX on M-series Macs transcribes faster than real-time; faster-whisper is the equivalent elsewhere. Cloud Whisper is a distant second choice — costs money, network dependency, worse latency on typical durations.
- Frame cadence by source type. Screen-share / demos need 1 frame per 5s (UI changes fast); talking-head podcasts need 1 per 30s (slow change). Default 15s if unsure. Wrong cadence = missed key moments OR wasted vision-pass cost.
- 720p is plenty. Downloading 1080p / 4K for transcription + frame analysis wastes bandwidth + storage.
yt-dlp -f "bv*[height<=720]+ba/b[height<=720]"is the default. - Scene-change detection catches slide transitions. When the video is a slide presentation, add
ffmpeg -vf "select='gt(scene,0.3)'"to force a frame on each detected slide change — more reliable than pure time-based sampling. - Multimodal cost warning is non-optional. Gemini multimodal on a 60-min video is meaningfully expensive. Warn before running; offer transcript-only as fallback if the user isn't sure.
- Summary format includes routing hints.
## Decisions flagged+## Action items flaggedsections signal/decideand/pmfollow-ups. Downstream composability lives in the summary structure.
| 1 | |
| 2 | name watch-video |
| 3 | description When you want to extract content from a video — YouTube, Loom, Vimeo, Riverside, Zoom recording, local MP4, X/IG video, anything yt-dlp supports. Three depth modes user picks per invocation — transcript (just words, fast/free), visual (transcript + ffmpeg frame extraction + Claude vision pass on key moments), multimodal (Gemini native video ingestion if $GEMINI_API_KEY set, else dense Claude vision). Uses local Whisper for transcription (MLX-Whisper on Apple Silicon, faster-whisper elsewhere), falls back to platform-provided transcripts when available (Loom, Riverside, YouTube auto-subs). Saves to ~/Documents/videos/<source>-<slug>-<date>/ and optionally captures summary to second-brain raw/ as call-/meeting-/note-. Triggers on "/watch-video <url>," "watch this video," "transcribe this loom," "analyze this video," "summarize this recording," "key moments from this," "what happened in this video." This skill replaces and broadens the prior youtube-transcript skill. |
| 4 | metadata |
| 5 | version 0.2.3 |
| 6 | |
| 7 | |
| 8 | # /watch-video — Transcribe and analyze any video at the depth you choose |
| 9 | |
| 10 | Replaces and broadens the prior `youtube-transcript` skill. YouTube is now one of many sources; depth is user-controlled. |
| 11 | |
| 12 | ## Step 1 — Parse input |
| 13 | |
| 14 | Accept: |
| 15 | **YouTube**: full URL, `youtu.be/<id>`, `youtube.com/shorts/<id>`, raw 11-char ID |
| 16 | **Loom**: `loom.com/share/<id>` or `loom.com/embed/<id>` |
| 17 | **Vimeo**: `vimeo.com/<id>` |
| 18 | **Riverside**: download URL or local file |
| 19 | **Zoom**: local `.mp4` from a downloaded recording |
| 20 | **X / IG / TikTok video**: URL — defers to `social-fetch` for metadata, uses yt-dlp for the file |
| 21 | **Local file**: any path to an `.mp4` / `.mov` / `.webm` / `.mkv` |
| 22 | |
| 23 | Detect source from URL pattern or file extension. If ambiguous, ask. |
| 24 | |
| 25 | ## Step 2 — Parse depth mode |
| 26 | |
| 27 | | Invocation | Mode | What you get | |
| 28 | |---|---|---| |
| 29 | | `/watch-video <url>` | **transcript** (default) | Clean text, metadata, optional chapters | |
| 30 | | `/watch-video <url> transcript` | transcript | Same as default | |
| 31 | | `/watch-video <url> visual` | visual | Transcript + frames at intervals + Claude vision pass identifying key moments | |
| 32 | | `/watch-video <url> multimodal` | multimodal | Native video to Gemini (if `$GEMINI_API_KEY`), else dense Claude vision frame-by-frame | |
| 33 | |
| 34 | If the depth isn't specified and the video is >10 minutes, ask before defaulting (visual/multimodal cost real money on long videos). |
| 35 | |
| 36 | ## Step 3 — Pull metadata |
| 37 | |
| 38 | For URL sources, use yt-dlp: |
| 39 | |
| 40 | |
| 41 | yt-dlp --print "%(title)s|%(uploader)s|%(duration_string)s|%(upload_date>%Y-%m-%d)s|%(description)s" \ |
| 42 | --print "%(chapters)j" --skip-download "<url>" |
| 43 | |
| 44 | |
| 45 | Capture: title, uploader/channel, duration, upload date, description (first paragraph), chapters (JSON or null). |
| 46 | |
| 47 | For local files, use ffprobe: |
| 48 | |
| 49 | |
| 50 | ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "<file>" |
| 51 | |
| 52 | |
| 53 | ## Step 4 — Build workdir |
| 54 | |
| 55 | |
| 56 | ~/Documents/videos/<source>-<slug>-<date>/ |
| 57 | |
| 58 | |
| 59 | Where: |
| 60 | `source`: `youtube` / `loom` / `vimeo` / `riverside` / `zoom` / `local` |
| 61 | `slug`: kebab-case of title (first 4–6 words, max 50 chars) |
| 62 | `date`: `YYYY-MM-DD` |
| 63 | |
| 64 | ## Step 5 — Get the transcript |
| 65 | |
| 66 | **Backend selection** (in order): |
| 67 | |
| 68 | **Platform-provided transcript** if it exists and looks complete: |
| 69 | YouTube: `yt-dlp --write-sub --write-auto-sub --skip-download --sub-lang en --sub-format vtt` |
| 70 | Loom: fetch via `https://www.loom.com/share/<id>` page metadata or Loom API if `$LOOM_API_KEY` set |
| 71 | Riverside: built-in transcripts available on the recording's share page |
| 72 | If platform transcript exists and has timestamps, use it. Skip Whisper. |
| 73 | |
| 74 | **MLX-Whisper local** (default on Apple Silicon Macs): |
| 75 | |
| 76 | # Install once: pip install mlx-whisper |
| 77 | python3 -c "import mlx_whisper; mlx_whisper.transcribe('<file>', path_or_hf_repo='mlx-community/whisper-large-v3-turbo')" \ |
| 78 | > "<workdir>/transcript-raw.json" |
| 79 | |
| 80 | Or via the CLI: `mlx_whisper <file> --model mlx-community/whisper-large-v3-turbo --output-dir <workdir>` |
| 81 | |
| 82 | **Not on Apple Silicon** (Linux, Intel Mac, Windows, cloud agents): `faster-whisper` (`pip install faster-whisper`, CPU or CUDA) or `whisper.cpp`. Same model size, same output handling. |
| 83 | |
| 84 | Download the video file first if it's a URL (use yt-dlp; Loom/Vimeo/YT all supported): |
| 85 | |
| 86 | |
| 87 | yt-dlp -f "bv*[height<=720]+ba/b[height<=720]" -o "<workdir>/video.%(ext)s" "<url>" |
| 88 | |
| 89 | |
| 90 | 720p is plenty for transcription and frame analysis (smaller download, faster processing). |
| 91 | |
| 92 | **Clean the transcript** (only needed for YouTube auto-subs which have rolling captions; Whisper output is already clean): |
| 93 | |
| 94 | |
| 95 | # YouTube VTT cleanup — de-dup rolling captions, strip tags, paragraph-break on cue gaps >2s |
| 96 | awk ' |
| 97 | /^WEBVTT/ || /^Kind:/ || /^Language:/ || /^NOTE/ { next } |
| 98 | /-->/ { in_cue = 1; last = ""; next } |
| 99 | /^$/ { if (last) print last; in_cue = 0; last = ""; next } |
| 100 | in_cue { gsub(/<[^>]+>/, "", $0); last = $0 } |
| 101 | END { if (last) print last } |
| 102 | ' "<workdir>/transcript.en.vtt" | awk '!seen[$0]++' > "<workdir>/transcript.txt" |
| 103 | |
| 104 | |
| 105 | Save final to `<workdir>/transcript.txt`. |
| 106 | |
| 107 | ## Step 6 — If `transcript` mode: stop here |
| 108 | |
| 109 | Output: |
| 110 | `transcript.txt` |
| 111 | `metadata.json` |
| 112 | One-line summary in chat: title, source, duration, word count |
| 113 | Path to workdir |
| 114 | (Optional) Step 9 — offer to capture to second-brain |
| 115 | |
| 116 | ## Step 7 — If `visual` mode: extract frames + vision pass |
| 117 | |
| 118 | ### Frame extraction (ffmpeg) |
| 119 | |
| 120 | Cadence by source heuristic: |
| 121 | |
| 122 | | Source type | Frame cadence | |
| 123 | |---|---| |
| 124 | | Screen-share / Loom / demo | 1 frame per **5s** (UI changes fast) | |
| 125 | | Talking head / podcast | 1 frame per **30s** (slow change) | |
| 126 | | Slide presentation | 1 frame per **10s** + force a frame on each detected scene change | |
| 127 | | Default if unsure | 1 frame per **15s** | |
| 128 | |
| 129 | |
| 130 | mkdir -p "<workdir>/frames" |
| 131 | ffmpeg -i "<workdir>/video.mp4" -vf "fps=1/15" "<workdir>/frames/frame-%04d.png" -y |
| 132 | |
| 133 | |
| 134 | For scene-change detection (slide decks especially): |
| 135 | |
| 136 | |
| 137 | ffmpeg -i "<workdir>/video.mp4" -vf "select='gt(scene,0.3)',showinfo" -vsync vfr "<workdir>/frames/scene-%04d.png" 2> "<workdir>/scene-detection.log" |
| 138 | |
| 139 | |
| 140 | ### Vision pass |
| 141 | |
| 142 | Pair each frame with the transcript chunk for the same timestamp window. Then batch-send to Claude vision for synthesis. |
| 143 | |
| 144 | **Per-frame batch prompt** (up to ~10 frames per call): |
| 145 | |
| 146 | > Here are N frames from a video at timestamps T1..TN. For each frame, describe what's on screen in 1–2 sentences. Flag: (a) UI changes from previous frame, (b) text visible on screen, (c) any moment that looks like a decision, action, or notable event. Also note the transcript text spoken during this window. |
| 147 | |
| 148 | Save the output as `<workdir>/moments.md`: |
| 149 | |
| 150 | |
| 151 | # Key moments — <title> |
| 152 | |
| 153 | ## 00:00:15 (frame-001.png) |
| 154 | **On screen**: Login form, email field focused |
| 155 | **Transcript**: "So you just open it up and..." |
| 156 | **Note**: Beginning of UI demo |
| 157 | |
| 158 | ## 00:00:45 (frame-002.png) |
| 159 | **On screen**: Dashboard with 4 cards |
| 160 | **Transcript**: "And here's where you see all your projects." |
| 161 | **Note**: Major view change — first time the dashboard appears |
| 162 | |
| 163 | |
| 164 | ### Generate summary |
| 165 | |
| 166 | After moments are identified, synthesize the whole video into `<workdir>/summary.md`: |
| 167 | |
| 168 | |
| 169 | # Summary — <title> |
| 170 | |
| 171 | **Source:** <source URL / file> |
| 172 | **Duration:** <hh:mm:ss> |
| 173 | **Watched at:** <date> |
| 174 | **Mode:** visual |
| 175 | |
| 176 | ## TL;DR |
| 177 | <2–4 sentences> |
| 178 | |
| 179 | ## Key moments |
| 180 | - 00:00:15 — <one-line> |
| 181 | - 00:00:45 — <one-line> |
| 182 | |
| 183 | ## Action items flagged |
| 184 | - <item> [timestamp] |
| 185 | |
| 186 | ## Decisions flagged |
| 187 | - <decision> [timestamp] — consider routing to /decide |
| 188 | |
| 189 | ## Quotes worth keeping |
| 190 | - "..." [timestamp] |
| 191 | |
| 192 | ## Open questions |
| 193 | - <question raised but not answered> |
| 194 | |
| 195 | |
| 196 | ## Step 8 — If `multimodal` mode |
| 197 | |
| 198 | ### Backend selection |
| 199 | |
| 200 | **Gemini native** if `$GEMINI_API_KEY` is set (much cheaper + faster than per-frame for long videos): |
| 201 | |
| 202 | **Default model: `gemini-3.5-flash`** (released May 2026, ~$1.50 input / $9 output per 1M tokens; ~$0.15/sec of video; beats 3.1 Pro on coding/agentic benchmarks at 4× the speed). Override to `gemini-3.1-pro` for brand audits / high-stakes analysis where details matter; `gemini-2.5-flash-lite` for bulk cheap processing. |
| 203 | |
| 204 | |
| 205 | # Step 1: Upload video via Files API |
| 206 | FILE_URI=$(curl -s -X POST "https://generativelanguage.googleapis.com/upload/v1beta/files?key=$GEMINI_API_KEY" \ |
| 207 | -H "X-Goog-Upload-Command: start, upload, finalize" \ |
| 208 | -H "Content-Type: video/mp4" \ |
| 209 | --data-binary "@<workdir>/video.mp4" | jq -r '.file.uri') |
| 210 | |
| 211 | # Wait until file is ACTIVE (Gemini processes the video first) |
| 212 | while true; do |
| 213 | STATE=$(curl -s "$FILE_URI?key=$GEMINI_API_KEY" | jq -r '.state') |
| 214 | [ "$STATE" = "ACTIVE" ] && break |
| 215 | sleep 3 |
| 216 | done |
| 217 | |
| 218 | # Step 2: Generate content with the file + multimodal-analysis prompt |
| 219 | curl -s -X POST "https://generativelanguage.googleapis.com/v1beta/models/gemini-3.5-flash:generateContent?key=$GEMINI_API_KEY" \ |
| 220 | -H "Content-Type: application/json" \ |
| 221 | -d "{ |
| 222 | \"contents\":[{ |
| 223 | \"parts\":[ |
| 224 | {\"file_data\":{\"mime_type\":\"video/mp4\",\"file_uri\":\"$FILE_URI\"}}, |
| 225 | {\"text\":\"<multimodal analysis prompt — see Step 7's summary template + use-case extensions>\"} |
| 226 | ] |
| 227 | }] |
| 228 | }" |
| 229 | |
| 230 | |
| 231 | Files persist in Gemini Files API for ~48 hours — useful for re-querying the same video with different prompts. |
| 232 | |
| 233 | **Dense Claude vision fallback** if no Gemini key: |
| 234 | Frame cadence: 1 frame per **3s** (much denser than visual mode) |
| 235 | Batch through Claude vision with the multimodal-analysis prompt |
| 236 | Slower and more expensive than Gemini for long videos — warn the user before running on >10min content |
| 237 | |
| 238 | ### Multimodal output |
| 239 | |
| 240 | Same `summary.md` template as Step 7 + an extended section: |
| 241 | |
| 242 | |
| 243 | ## Multimodal observations |
| 244 | - **Body language / delivery**: <observations on talking-head video> |
| 245 | - **Pacing**: <fast/slow/uneven> |
| 246 | - **Visual style**: <brand audit, ad review, design observations> |
| 247 | - **Audio quality / atmosphere**: <music, silence, background> |
| 248 | |
| 249 | |
| 250 | Exact extra sections depend on the use case (brand audit, ad review, talk delivery review, client-call read). Use case is inferred from the source + the user's verbal framing when invoking. |
| 251 | |
| 252 | ## Step 9 — Optional: capture to second-brain |
| 253 | |
| 254 | After any mode completes, offer: |
| 255 | |
| 256 | > *"Want to capture this to second-brain? I'll write a `call-<slug>.md` (or `meeting-` / `note-` / `resource-`) to `${SECOND_BRAIN_VAULT:-$HOME/Documents/SecondBrain}/raw/` with the summary, source URL, and transcript link."* |
| 257 | |
| 258 | Type prefix by source: |
| 259 | |
| 260 | | Source | Prefix | |
| 261 | |---|---| |
| 262 | | Loom / Zoom / Riverside / Otter / call recording | `call-` | |
| 263 | | Meeting (own notes, not a transcript) | `meeting-` | |
| 264 | | Talk / keynote / conference | `note-` | |
| 265 | | Ad / landing-page video / marketing reference / competitor video | `resource-` | |
| 266 | |
| 267 | File body: 1-line source, the summary, link to full workdir. |
| 268 | |
| 269 | ## Step 10 — Report |
| 270 | |
| 271 | In chat: |
| 272 | |
| 273 | One-line headline: `<source> · <title> · <duration> · <mode> · <word count> words` |
| 274 | Workdir path |
| 275 | For `visual` / `multimodal`: brief list of top 3 key moments |
| 276 | For all modes: any action items / decisions flagged for triage |
| 277 | If captured to second-brain: that path too |
| 278 | |
| 279 | ## Sources reference |
| 280 | |
| 281 | | Source | Download | Built-in transcript | Notes | |
| 282 | |---|---|---|---| |
| 283 | | YouTube | `yt-dlp` | Auto-subs (`--write-auto-sub`) | Same as the prior youtube-transcript skill | |
| 284 | | Loom | `yt-dlp` (Loom supported) | Yes — fetch via embed metadata or Loom API | Async screenshare focus — prime use case | |
| 285 | | Vimeo | `yt-dlp` | Sometimes | Marketing/embed videos | |
| 286 | | Riverside | Direct URL from export, or local file | Yes — Riverside generates them | Podcast episodes | |
| 287 | | Zoom | Local `.mp4` (downloaded recordings) | Sometimes (Zoom audio transcript file) | Client calls | |
| 288 | | X / IG / TikTok | Defer to `social-fetch` for metadata, yt-dlp for file | No | Short-form | |
| 289 | | Local file | n/a | n/a | Drop a path | |
| 290 | |
| 291 | ## Composes with |
| 292 | |
| 293 | `social-fetch` — for X/IG/TikTok URL metadata (engagement, author, replies) before video processing |
| 294 | `second-brain` — capture summary as `raw/call-<slug>.md`, `meeting-`, `note-`, or `resource-` per source type |
| 295 | `decide` — when a video contains a flagged decision, route to `/decide` for structured capture |
| 296 | `pm` — action items flagged in summary can be triaged to project boards |
| 297 | `slide-deck` — talk recordings → outline extraction → deck draft (loop) |
| 298 | `jab-hook` — quotes + clip-worthy moments from podcast/talk videos feed BIP/promo posts |
| 299 | **`skillify from-video`** — primary use case for `visual` mode on process recordings. the user records themselves doing a workflow (Loom/screen-share), this skill extracts transcript + key visual moments, then `skillify` synthesizes the workflow into a SKILL.md. "Record once, AI converts to skill." |
| 300 | |
| 301 | ## Error handling |
| 302 | |
| 303 | | Failure | Response | |
| 304 | |---|---| |
| 305 | | Video unavailable / private / region-locked | Report and stop | |
| 306 | | No subtitles + Whisper not installed | Tell the user: `pip install mlx-whisper` (Apple Silicon) or `pip install faster-whisper` (anything else) | |
| 307 | | ffmpeg missing (for visual/multimodal) | Tell the user: `brew install ffmpeg` | |
| 308 | | Vision pass returns empty / unclear | Lower the frame count, retry, or fall back to transcript-only with a note | |
| 309 | | Multimodal requested but no `$GEMINI_API_KEY` and >30min video | Warn cost, offer to fall back to visual mode | |
| 310 | | `yt-dlp` binary missing | `brew install yt-dlp` | |
| 311 | |
| 312 | ## Notes on quality |
| 313 | |
| 314 | **User picks depth, not the skill.** Transcript / visual / multimodal are 3 different cost + latency profiles. Long videos (>10 min) always confirm before spending on visual/multimodal. |
| 315 | **Platform transcript first, Whisper second.** YouTube auto-subs, Loom transcripts, Riverside built-in transcripts — all free + instant when they exist. Fall back to local Whisper only when nothing platform-provided works. |
| 316 | **Local Whisper is the fast path.** MLX on M-series Macs transcribes faster than real-time; faster-whisper is the equivalent elsewhere. Cloud Whisper is a distant second choice — costs money, network dependency, worse latency on typical durations. |
| 317 | **Frame cadence by source type.** Screen-share / demos need 1 frame per 5s (UI changes fast); talking-head podcasts need 1 per 30s (slow change). Default 15s if unsure. Wrong cadence = missed key moments OR wasted vision-pass cost. |
| 318 | **720p is plenty.** Downloading 1080p / 4K for transcription + frame analysis wastes bandwidth + storage. `yt-dlp -f "bv*[height<=720]+ba/b[height<=720]"` is the default. |
| 319 | **Scene-change detection catches slide transitions.** When the video is a slide presentation, add `ffmpeg -vf "select='gt(scene,0.3)'"` to force a frame on each detected slide change — more reliable than pure time-based sampling. |
| 320 | **Multimodal cost warning is non-optional.** Gemini multimodal on a 60-min video is meaningfully expensive. Warn before running; offer transcript-only as fallback if the user isn't sure. |
| 321 | **Summary format includes routing hints.** `## Decisions flagged` + `## Action items flagged` sections signal `/decide` and `/pm` follow-ups. Downstream composability lives in the summary structure. |
| 322 |
Discussion
Browse more free Claude skills.