watch — Claude watches a video end to end
Splits a video into what Claude reads natively: frame images (seen with the Read tool) + a transcript (text). The result is understanding not just the words, but what's happening on screen.
Bilingual. This is the English version. A full Russian mirror is
SKILL.ru.md. Work in whatever language the user uses; the workflow and commands are identical.
Dependencies (installed once via install.sh)
Check with: command -v yt-dlp ffmpeg whisper node.
yt-dlp→~/.local/bin/yt-dlp(under Python 3.12 in~/.local/python312— the system Python is usually too old; recent yt-dlp releases need 3.10+);ffmpeg→ fromimageio-ffmpeg, symlinked to~/.local/bin/ffmpeg;whisper→openai-whisper(under the system Python; the model downloads on first use);node→~/.local/node(v24) — needed to get past YouTube's defenses.
For YouTube specifically, install.sh also sets up:
- a PO-token provider (
bgutil), built in script mode under~/.local/bgutil-pot/server/build/; - a Node.js runtime + an EJS component, to solve YouTube's n-challenge;
- cookies from a logged-in YouTube account, at
~/.config/watch/cookies.txt.
If cookies go stale (YouTube starts asking to "confirm you're not a bot" again), ask
the user to re-export cookies.txt with a browser extension like "Get cookies.txt
LOCALLY" and drop it at ~/.config/watch/cookies.txt.
Always download through the wrapper bash ~/.claude/skills/watch/yt.sh … — it adds
the right YouTube flags automatically. For direct mp4/Loom/Zoom URLs the extra flags are
harmless.
Workflow
1. Setup
- Make a working folder:
mkdir -p /tmp/watch/<slug>(slug = a short name from the URL/topic). - Parse the URL (YouTube / Loom / a Zoom share link / a direct video file). A local file skips the download steps — work with it directly.
2. Transcript — subtitles first (free, when available)
bash ~/.claude/skills/watch/yt.sh --write-subs --write-auto-subs --sub-langs "en,ru" \
--skip-download --convert-subs srt -o "/tmp/watch/<slug>/subs.%(ext)s" "<URL>"
If a .srt shows up, that's a ready transcript with timestamps — use it.
3. Download the video (needed both for frames and as a Whisper fallback)
bash ~/.claude/skills/watch/yt.sh -f "bv*[height<=480]+ba/b[height<=480]/18/best" \
-o "/tmp/watch/<slug>/video.%(ext)s" "<URL>"
You'll get a .mp4 or .webm — use the real filename from here on (video.*).
If there were no subtitles, transcribe the audio with Whisper (model small; base is
faster for a quick test):
whisper "/tmp/watch/<slug>/video.webm" --model small --language en \
--output_format srt --output_dir "/tmp/watch/<slug>/"
The first run downloads the model (~460 MB for small) — that's normal, one-time.
Drop --language en for non-English audio, or set the right language code. Whisper is
slow: a 34-minute clip takes several minutes — run it in the background.
4. Frames BY SCENE CHANGE (the key step — not on a timer)
ffmpeg -i "/tmp/watch/<slug>/video.webm" -vf "select='gt(scene,0.3)',showinfo" \
-vsync vfr "/tmp/watch/<slug>/frames/frame_%04d.png" 2> "/tmp/watch/<slug>/scenes.log"
- Threshold
0.3— tune0.2–0.4for scene density (a lower threshold = more frames). - Get each frame's timestamp from the showinfo log (
pts_time) inscenes.log. - If there are too many frames (a long video), keep around 60–150 of the most informative ones, thinning out frames that are close together in time. Decoding a long video takes a while — run it in the background.
FALLBACK — if there are too few scenes (0–2 frames). Smooth, uncut footage (one continuous shot, a drone, a talking head) produces few scene changes. In that case, sample at a fixed interval instead:
ffmpeg -i "/tmp/watch/<slug>/video.webm" -vf "fps=1/5,scale=960:-1,showinfo" \
-vsync vfr "/tmp/watch/<slug>/frames/frame_%04d.png" 2> "/tmp/watch/<slug>/scenes.log"
Adjust the interval (1/5 = one frame every 5s) to the video's length so you stay
around ~150 frames. The i-th frame's timestamp = i × interval.
4a. YouTube: why everything goes through yt.sh
YouTube (2025+) requires all three of these at once, or you get HTTP 403 / "not a
bot" / "only images":
- cookies from a logged-in account (
~/.config/watch/cookies.txt); - a PO-token (proof-of-origin) — generated locally by
bgutil(script mode, Node); - solving the n-challenge — a Node runtime + an EJS component
(
--remote-components ejs:github). All of this is already built intoyt.sh— just call it. Direct mp4/Loom/Zoom sources don't have these defenses. If it asks to "confirm you're not a bot" again, the cookies went stale — ask for a freshcookies.txt.
5. Analysis
- Read the transcript (
.srt). - Look at the extracted frames (vision, via the Read tool): slides, code, demos, graphics.
- Tie the visuals to the transcript using timestamps.
6. Report
Deliver:
- a TL;DR (3–5 lines);
- key concepts with timestamps;
- what's shown ON SCREEN that isn't in the text (charts, UIs, demos);
- notable moments / quotes with timestamps.
7. Saving to a knowledge base (ask first)
Ask: "Save this breakdown to your knowledge base (Obsidian)?" If yes, write a markdown note into the right folder with links to related notes. Don't save without confirmation.
Rules
- Working files go into a temp folder
/tmp/watch/<slug>— don't clutter the project. - Don't publish or send anything externally without an explicit request.
- Don't bypass a login-gated private video — tell the user instead.