/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 ↗

Use now

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

coreyhaines31/main1 file shown
SKILL.md
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> 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.

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 / local
  • slug: kebab-case of title (first 4–6 words, max 50 chars)
  • date: YYYY-MM-DD

Step 5 — Get the transcript

Backend selection (in order):

  1. 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_KEY set
    • Riverside: built-in transcripts available on the recording's share page
    • If platform transcript exists and has timestamps, use it. Skip Whisper.
  2. 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>

  3. 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.

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.txt
  • metadata.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
  1. Gemini native if $GEMINI_API_KEY is 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 to gemini-3.1-pro for brand audits / high-stakes analysis where details matter; gemini-2.5-flash-lite for 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.

  2. 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 (or meeting- / 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 processing
  • second-brain — capture summary as raw/call-<slug>.md, meeting-, note-, or resource- per source type
  • decide — when a video contains a flagged decision, route to /decide for structured capture
  • pm — action items flagged in summary can be triaged to project boards
  • slide-deck — talk recordings → outline extraction → deck draft (loop)
  • jab-hook — quotes + clip-worthy moments from podcast/talk videos feed BIP/promo posts
  • 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."

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 flagged sections signal /decide and /pm follow-ups. Downstream composability lives in the summary structure.
1---
2name: watch-video
3description: 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.
4metadata:
5 version: 0.2.3
6---
7 
8# /watch-video — Transcribe and analyze any video at the depth you choose
9 
10Replaces 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 
14Accept:
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 
23Detect 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 
34If 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 
38For URL sources, use yt-dlp:
39 
40```bash
41yt-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 
45Capture: title, uploader/channel, duration, upload date, description (first paragraph), chapters (JSON or null).
46 
47For local files, use ffprobe:
48 
49```bash
50ffprobe -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 
59Where:
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 
681. **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 
742. **MLX-Whisper local** (default on Apple Silicon Macs):
75 ```bash
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 
823. **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 
84Download the video file first if it's a URL (use yt-dlp; Loom/Vimeo/YT all supported):
85 
86```bash
87yt-dlp -f "bv*[height<=720]+ba/b[height<=720]" -o "<workdir>/video.%(ext)s" "<url>"
88```
89 
90720p 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```bash
95# YouTube VTT cleanup — de-dup rolling captions, strip tags, paragraph-break on cue gaps >2s
96awk '
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 
105Save final to `<workdir>/transcript.txt`.
106 
107## Step 6 — If `transcript` mode: stop here
108 
109Output:
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 
120Cadence 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```bash
130mkdir -p "<workdir>/frames"
131ffmpeg -i "<workdir>/video.mp4" -vf "fps=1/15" "<workdir>/frames/frame-%04d.png" -y
132```
133 
134For scene-change detection (slide decks especially):
135 
136```bash
137ffmpeg -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 
142Pair 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 
148Save the output as `<workdir>/moments.md`:
149 
150```markdown
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 
166After moments are identified, synthesize the whole video into `<workdir>/summary.md`:
167 
168```markdown
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 
2001. **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 ```bash
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 
2332. **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 
240Same `summary.md` template as Step 7 + an extended section:
241 
242```markdown
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 
250Exact 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 
254After 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 
258Type 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 
267File body: 1-line source, the summary, link to full workdir.
268 
269## Step 10 — Report
270 
271In 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