chrome-youtube-skill
# YouTube Transcript Extractor and Summarizer
Choose how to use this skill
You do not need every option. Choose the path your AI client supports. The stable page stays the same; versioned files are immutable.
1. Native installer
This listing has no registered native installer command. Use the complete package or source fallback below, depending on what your client supports.
Do not guess an installer command or replace an existing version without reviewing the diff.
2. Complete package recommended
Download the ZIP when available. It includes SKILL.md plus the references, security notes and version metadata.
No complete ProSkills package is published for this listing yet.3. Prompt-only
Copy the prompt above when the agent can read the stable page or when you want to adopt the workflow without installing a skill.
Need only the instruction file?
Download SKILL.md only if your client requires a single file. The complete ZIP is safer for a full installation because it preserves the references and release context.
No path installs or executes anything by itself. Your agent still needs access to the project files. Before updating, compare the installed version and review the diff.
// RATINGS
Not yet listed on ClawHub or SkillsMP
// README
YouTube Transcript Extractor and Summarizer
Extract YouTube video transcripts by connecting to a visible Chrome instance via CDP (Chrome DevTools Protocol). Clicks "Show transcript" and reads the DOM — the same approach used by real users.
OpenClaw usage
OpenClaw skills are loaded from ~/.openclaw/workspace/skills/ and require a SKILL.md.
# Install as a local skill
mkdir -p ~/.openclaw/workspace/skills
ln -s /path/to/chrome-youtube-skill ~/.openclaw/workspace/skills/youtube
Codex usage
Codex skills are loaded from $CODEX_HOME/skills (usually ~/.codex/skills) and require a SKILL.md.
This repo includes SKILL.md and agents/openai.yaml.
# Install as a local skill
mkdir -p ~/.codex/skills
ln -s /path/to/chrome-youtube-skill ~/.codex/skills/youtube
Restart Codex after installing so the new skill is detected.
Invoke it in prompts as $youtube, for example:
$youtube https://www.youtube.com/watch?v=dQw4w9WgXcQ
Claude usage
Claude Code skills are loaded from ~/.claude/skills and require a SKILL.md.
This repo includes SKILL.md with Claude Code-compatible metadata.
# Install as a local skill
mkdir -p ~/.claude/skills
ln -s /path/to/chrome-youtube-skill ~/.claude/skills/youtube
Invoke it with the /youtube slash command, for example:
/youtube https://www.youtube.com/watch?v=dQw4w9WgXcQ
Skill Configuration
When used as a skill (OpenClaw, Codex, or Claude Code), summaries are saved to a configurable output directory.
To configure the output directory:
-
Copy the example config:
cp skill.config.example skill.config -
Edit
skill.configand set your desired path:OUTPUT_DIR=/path/to/your/summaries -
The config file is local to the project and ignored by git.
Default behavior:
- If no
skill.configexists, summaries are saved to~/youtube_transcripts - The output directory is created automatically if it doesn't exist
CLI usage
Single video
# Plain text output
./extract "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
# JSON output
./extract "https://www.youtube.com/watch?v=dQw4w9WgXcQ" --json
# JSON output (also write to a file)
./extract "https://www.youtube.com/watch?v=dQw4w9WgXcQ" --json --json-out /tmp/yt_transcript.json
# From stdin
echo "https://youtu.be/dQw4w9WgXcQ" | ./extract --stdin
# Save transcript to a directory
./extract "https://www.youtube.com/watch?v=dQw4w9WgXcQ" --output-dir ~/Downloads/yt_transcripts
# Disable file saving
./extract "https://www.youtube.com/watch?v=dQw4w9WgXcQ" --no-save
# Custom CDP port
./extract "https://www.youtube.com/watch?v=dQw4w9WgXcQ" --port 9333
Batch channel export
Export transcripts from the N latest videos on a YouTube channel in a single Chrome session:
# Export the 10 latest videos
./extract batch https://www.youtube.com/@channelhandle/videos --count 10 --output-dir ~/transcripts
# Export all videos (scrolls until the page is fully loaded)
./extract batch https://www.youtube.com/@channelhandle/videos --count 10000 --output-dir ~/transcripts
# Short flags
./extract batch https://www.youtube.com/@channelhandle/videos -n 25 -o ~/transcripts
The batch command:
- Opens the channel's
/videospage and scrolls until no new videos load - Collects up to
--countvideo URLs in chronological order (newest first) - Extracts each transcript in the same Chrome session (no restart between videos)
- Saves one
.txtfile per video; skips videos with no captions and reports them at the end
How It Works
┌──────────┐ CDP HTTP ┌─────────────┐ WebSocket ┌──────────────┐
│ Script │ ──────────────→│ Chrome :9222 │ ←────────────→ │ YouTube Tab │
│ (Python) │ open/close │ (visible) │ Runtime.eval │ (visible) │
└──────────┘ tab └─────────────┘ └──────────────┘
Everything happens in a real, visible Chrome window. The script clicks buttons and reads the page just like a user would — no headless mode, no API calls that get rate-limited.
Step-by-Step Extraction Flow
-
Launch Chrome — The script automatically finds Chrome on your system, kills any existing debug instance using the same profile (
~/.chrome-debug-profile), and launches a fresh Chrome process with--remote-debugging-port=9222. It then pollshttp://127.0.0.1:9222/json/versionuntil the CDP endpoint is ready (up to 15 seconds). -
Open a new tab — Sends
PUT /json/new?{url}to Chrome's CDP HTTP API to open the YouTube video in a new visible tab. (Falls back toGETfor Chrome versions older than 145 that return 405 on PUT.) Chrome returns a JSON object with the tab'sidandwebSocketDebuggerUrl. -
Wait for page load — Sleeps 5 seconds to let the page navigate and YouTube's JavaScript initialize. This delay is necessary because navigating a tab resets its WebSocket connection — connecting too early would get disconnected.
-
Connect WebSocket — Opens a WebSocket to the tab's
webSocketDebuggerUrl. All subsequent interaction with the page happens throughRuntime.evaluatemessages over this WebSocket, which execute JavaScript in the tab's context and return results. -
Wait for YouTube's player — Polls
window.ytInitialPlayerResponse(YouTube's internal player state object) up to 15 seconds until it's defined. This object contains video metadata and caption track information. -
Extract metadata — Reads
videoDetails.title,videoDetails.author, and the first English caption track'slanguageCodefromytInitialPlayerResponse. -
DOM extraction (primary method) — Injects JavaScript that:
- Waits up to 8 seconds for the
ytd-video-description-transcript-section-renderer buttonelement (the "Show transcript" button below the video description) - Clicks the button to open the transcript panel
- Waits up to 5 seconds for
#segments-containerto appear in the DOM - Waits 500ms for transcript segments to populate
- Reads text from all
yt-formatted-string.segment-textelements inside the container - Joins all segment text into a single string with whitespace normalized
- Closes the transcript panel if it was opened by the script
- Waits up to 8 seconds for the
-
API extraction (fallback method) — If DOM extraction fails (e.g., the "Show transcript" button isn't present), the script falls back to fetching captions via YouTube's internal caption API. This runs entirely inside the browser tab using
fetch()with cookies — avoiding the 429 rate limits that happen when fetching from outside the browser. It:- Gets the caption track URL from
ytInitialPlayerResponse.captions.playerCaptionsTracklistRenderer.captionTracks - Prefers English tracks; falls back to the first available language
- Fetches the
json3format first, parsingevents[].segs[].utf8into text - Falls back to XML format if JSON fails, parsing
<text>elements viaDOMParser
- Gets the caption track URL from
-
Close tab — Sends
GET /json/close/{targetId}to Chrome's CDP API. -
Shutdown Chrome — Terminates the Chrome process that was launched in step 1. Uses
SIGTERMfirst, escalating toSIGKILLif Chrome doesn't exit within 5 seconds.
Why This Approach?
- No API keys or authentication — Uses the same method a human would: clicking buttons and reading the page
- Avoids rate limiting — All network requests happen inside Chrome with the user's cookies, so YouTube sees normal browser traffic
- Works with auto-generated and manual captions — The DOM method reads whatever transcript YouTube displays in its UI
- Visible Chrome — You can see exactly what's happening; no hidden headless browser
Setup
Install Python dependencies:
pip install . # from project root (uses pyproject.toml)
# or manually:
pip3 install requests websocket-client
Chrome is found automatically. The script searches these paths in order:
/Applications/Google Chrome.app/Contents/MacOS/Google Chrome(macOS)google-chrome(Linux, via PATH)google-chrome-stable(Linux package managers)chromium-browser/chromium(Chromium)
Output
Plain text (default):
Title: Video Title
Channel: Channel Name
==================================================
This is the full transcript text extracted from the video captions...
Saved transcript files (with --output-dir or batch):
Files are named YYYY-MM-DD - Channel - Title.txt and start with a metadata header:
Title: Video Title
Channel: Channel Name
URL: https://www.youtube.com/watch?v=dQw4w9WgXcQ
Views: 1,234,567
Published: 2024-06-01
============================================================
This is the full transcript text...
JSON (--json):
{
"success": true,
"video_id": "dQw4w9WgXcQ",
"title": "Video Title",
"channel": "Channel Name",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"transcript": "This is the full transcript text...",
"language": "en",
"method": "dom",
"view_count": "1234567",
"publish_date": "2024-06-01T10:00:00-07:00",
"error": ""
}
Use --json-out /path/to/result.json to write the JSON result to disk.
The method field indicates which extraction path succeeded: "dom" for the primary transcript panel scrape, or "api" for the caption track URL fallback.
Supported URL Formats
https://www.youtube.com/watch?v=VIDEO_IDhttps://youtu.be/VIDEO_IDhttps://www.youtube.com/shorts/VIDEO_IDhttps://www.youtube.com/embed/VIDEO_IDhttps://m.youtube.com/watch?v=VIDEO_ID
All formats are normalized to https://www.youtube.com/watch?v=VIDEO_ID before loading.
Files
| File | Purpose |
|---|---|
src/yt_transcript/__init__.py | Package exports, __version__ |
src/yt_transcript/extractor.py | Core — Chrome lifecycle, CDP communication, transcript extraction |
src/yt_transcript/cli.py | CLI entry point, argument parsing, file saving |
src/yt_transcript/__main__.py | python -m yt_transcript support |
src/yt_transcript/js/get_metadata.js | Extract title, channel, view count, publish date from ytInitialPlayerResponse |
src/yt_transcript/js/extract_dom.js | Click "Show transcript" and scrape the transcript panel DOM |
src/yt_transcript/js/extract_api.js | Fallback: fetch caption track URL via in-page fetch() |
src/yt_transcript/js/get_channel_videos.js | Scroll a channel /videos page and collect video URLs |
extract | Bash wrapper for CLI invocation |
scripts/run_transcript.py | Run from repo without installation (sets up sys.path) |
tests/test_extractor.py | Unit tests (73 tests, runs in <1s) |
pyproject.toml | PEP 621 packaging metadata and dependencies |
SKILL.md | OpenClaw skill manifest |
CLAUDE.md | Developer/architecture notes |
Testing
PYTHONPATH=src python3 -m pytest tests/ -v
All tests are fully mocked — no Chrome instance is needed to run them.
Sequential Execution Lock
The script uses an exclusive file lock on /tmp/yt-extract.lock (fcntl.flock on Unix, msvcrt.locking on Windows) to ensure only one extraction runs at a time. If you invoke the script twice concurrently, the second invocation blocks until the first one finishes.
The lock is explicitly released and the lock file is deleted in a finally block after each run. If the process is killed before cleanup, the OS releases the lock when the owning process dies, and the script recreates the file on the next run.
Limitations
- Requires Chrome or Chromium installed on the system
- Only works with videos that have captions (auto-generated or manual)
- Prefers English captions; falls back to first available language
- Reuses an already-running Chrome instance by default (
--no-reuseto force a fresh launch)