
Youtube To Skill
- 8 installs
- Updated January 1, 2026
- yfe404/youtube-to-skill
Helps with ai & agent building tasks during AI-assisted development.
About
youtube-to-skill is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- youtube-to-skill
- AI & Agent Building
- AI-coding skill
Youtube To Skill by the numbers
- 8 all-time installs (skills.sh)
- Ranked #12,339 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/yfe404/youtube-to-skill --skill youtube-to-skillAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 8 |
|---|---|
| Last updated | January 1, 2026 |
| Repository | yfe404/youtube-to-skill ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
YouTube to Skill Transformer
Convert YouTube video content into effective, verified, up-to-date agent skills.
⚠️ Before You Generate ANY Skill
You MUST read [reference.md](./reference.md) first.
It contains the critical understanding of what makes skills actually work vs useless text files. Without this knowledge, you will create skills that the agent ignores.
---
⏰ CRITICAL: Check Current Date
LLMs are trained on historical data and often assume outdated dates.
Before verifying ANY technology: 1. Check today's date from your environment/system 2. Use this date when searching for "current" documentation 3. Compare video publish date against TODAY's date (not your training cutoff)
Example: If today is December 2025 and a video was published in January 2024, that's ~2 years old — significant API changes may have occurred.
When searching, always include the current year:
- ✅
"React hooks 2025"or"FastAPI latest version December 2025" - ❌
"React hooks"(may return outdated results)
---
🔬 CRITICAL: No Hallucinated Data
Never include numbers, estimates, or claims you haven't verified.
This applies to:
- Version numbers → Always look them up
- Performance claims → Only include if from official docs/benchmarks
- Cost estimates → Only if you've calculated from real pricing
- Time estimates → Don't guess, omit if unknown
- Statistics → Must have a verifiable source
The scientific method: 1. If you don't know → Look it up or omit it 2. If you can't verify → Don't include it 3. If it's an estimate → Label it clearly with your methodology 4. If the video claims something → Verify before including
Bad: "This approach is 10x faster" (unverified claim from video) Good: "The video claims improved performance" or omit entirely Best: Verify with benchmarks and cite source
---
The Pipeline
EXTRACT ──▶ IDENTIFY ──▶ VERIFY ──▶ TRANSFORM ──▶ GENERATE
│ │ │ │ │
transcript tools & current? narrative effective
+ meta versions enriched? ──▶ action skill---
Step 1: Extract Transcript
python .claude/skills/youtube-to-skill/scripts/extract_youtube.py "<URL>"- Single videos:
youtube.com/watch?v=...oryoutu.be/... - Playlists:
youtube.com/playlist?list=...
Output: JSON with title, publish date, channel, and transcript.
Note the publish date — older videos need more verification.
---
Step 2: Identify Technologies
Scan transcript and list ALL mentioned:
| Category | Examples |
|---|---|
| Languages | Python 3.9, TypeScript, Rust |
| Frameworks | React 18, Next.js 14, FastAPI |
| Libraries | axios, lodash, pandas |
| Tools | Docker, kubectl, terraform |
| Services | AWS S3, Stripe API, OpenAI |
| CLIs | npm, cargo, pip commands |
Create a verification checklist with version numbers if mentioned.
---
Step 3: Verify & Update (CRITICAL)
Videos get outdated. Your skill must not.
For Libraries/Frameworks → Use Context7 MCP
1. mcp__context7__resolve-library-id
→ Find the library ID (e.g., "react" → "/reactjs/react.dev")
2. mcp__context7__get-library-docs
→ Fetch current documentation for specific topics
→ Use mode='code' for API/examples, mode='info' for conceptsExample:
- Video mentions "React useEffect cleanup"
- Resolve:
/reactjs/react.dev - Fetch: topic="useEffect cleanup" mode="code"
- Compare video content with current docs
For Tools/Services/APIs → Use WebSearch
WebSearch: "<tool name> documentation <current year>" or "<api> latest version <current month year>"Always include the current date in searches to avoid stale results.
When You Find Outdated Information
STOP and ask the user:
⚠️ Outdated content detected in video (published <date>):
[Library/Tool] v<old> → Current: v<new>
Breaking changes found:
• <specific change 1>
• <specific change 2>
How should I proceed?
A) Update to current version (recommended)
B) Keep original with deprecation warnings
C) Skip this sectionAsk About Enrichment
Would you like me to enrich this skill with current official documentation?
This would add:
• Updated API references from Context7
• Additional code examples
• Edge cases not covered in the video
[Yes / No / Ask me for each topic]---
Step 4: Transform Content
This is where most skill generation fails.
See reference.md for the deep understanding, but the core principle:
You are not transcribing. You are creating executable instructions.
| ❌ Video Narration | ✅ Skill Instruction |
|---|---|
| "So what we're gonna do here is..." | (delete) |
| "You want to open your terminal and type..." | bash -c "command" |
| "The cool thing about this is..." | (delete or convert to a note) |
| "Make sure you don't forget to..." | ⚠️ Warning: ... |
| "If you see this error..." | Troubleshooting: ... |
| "I usually structure it like..." | Recommended structure: |
The test: Can the agent follow these instructions without watching the video?
---
Step 5: Choose Skill Structure
First, decide: Single file or multi-file?
┌─────────────────────────────────────┐
│ Analyze Content │
└──────────────────┬──────────────────┘
│
┌─────────┴─────────┐
▼ ▼
Short/Simple? Complex/Long?
│ │
▼ ▼
Single SKILL.md Multi-file structure:
├── Theory → references/
├── Code → scripts/
└── Variants → examples/Decision Criteria
| Criterion | Single File | Multi-File |
|---|---|---|
| Video length | < 10 min | > 20 min |
| Code examples | 1-2 small snippets | Complete runnable scripts |
| Theory vs practice | Mostly practical | Heavy theory + practice |
| Estimated word count | < 2000 words | > 3000 words |
| Multiple workflows | No | Yes (different use cases) |
Multi-File Structure (Progressive Disclosure)
skill-name/
├── SKILL.md # Core instructions (<5k words, always loaded)
├── references/ # Deep docs (loaded on-demand, saves tokens)
│ ├── concepts.md # Theory, explanations
│ └── api.md # API documentation
├── scripts/ # Executable code (can run without loading)
│ └── example.py # Complete runnable examples
└── assets/ # Templates, images (used in output)Why this matters:
- SKILL.md is always loaded (~tokens cost)
- references/ only loaded when Claude needs them (saves tokens)
- scripts/ can be executed directly without loading into context
- assets/ copied to output, never loaded
---
Step 6: Generate the Skill
Use template: templates/skill_template.md
Output Format
For SINGLE FILE output: Start directly with the YAML frontmatter:
---
name: skill-name
description: ...
---
# Content...For MULTI-FILE output: Use file markers to separate content:
<!-- FILE: SKILL.md -->
---
name: skill-name
description: ...
---
# Skill Title
Core actionable instructions here (keep under 5000 words).
For detailed concepts, see [concepts reference](references/concepts.md).
<!-- FILE: references/concepts.md -->
# Theoretical Concepts
Deep explanations, formulas, theory...
<!-- FILE: scripts/example.py -->
#!/usr/bin/env python3
Complete runnable code...Pre-delivery Checklist
- [ ] Description has trigger words users actually say
- [ ] Description includes "Use when..." — This is how the agent discovers the skill
- [ ] One skill = one capability (split if video covers multiple)
- [ ] Instructions are executable (commands, code, steps)
- [ ] No narrator voice ("so", "basically", "gonna")
- [ ] Sources documented (video URL + docs used)
- [ ] Versions specified for all tools/libraries
- [ ] Required sections present: Source, Prerequisites, Instructions, Troubleshooting
YAML Frontmatter Rules
ONLY these fields are allowed:
---
name: lowercase-with-hyphens
description: ACTION VERB + what it does. Use when user needs X, wants to Y, or asks about Z.
---name: lowercase, hyphens only, max 40 charsdescription: Must include "Use when..." trigger phrase
Ask for Placement
Where should I save this skill?
A) .claude/skills/<name>/ — Project skill (shared via git)
B) ~/.claude/skills/<name>/ — Personal skill (your machine only)---
Available Tools Reference
| Need | Tool | Example |
|---|---|---|
| Library docs | mcp__context7__resolve-library-id | Find React library ID |
| Library docs | mcp__context7__get-library-docs | Fetch React hooks docs |
| General search | WebSearch | "docker compose v2 migration" |
| Specific URL | WebFetch | Fetch a specific doc page |
Good vs Bad Skill Generation: Examples
Learn from these contrasting examples to understand what makes skills effective.
---
Example 1: Docker Tutorial Video
The Video Transcript (excerpt)
"Hey everyone! So today we're going to be looking at Docker, which is super cool. Basically, Docker lets you containerize your applications, which is really neat. So what you want to do is, first, you're going to create a file called Dockerfile - no extension, just Dockerfile. And in there, you're going to put... let me show you on my screen... FROM node:14, because we're using Node.js for this example. Pretty simple, right? Then you do WORKDIR /app, and this just sets up where your code is going to live inside the container. Cool, so next..."
---
❌ BAD: Transcription-Style Skill
---
name: docker-containers
description: Learn about Docker containerization
---
# Docker Containers
Docker is a containerization platform that lets you containerize your applications.
## What Docker Does
Docker is super cool and lets you containerize your apps. Basically, it creates containers
which is really neat for deployment.
## Getting Started
So what you want to do is first create a file called Dockerfile. No extension, just Dockerfile.
And in there you're going to put FROM node:14 because we're using Node.js for this example.
Pretty simple, right?
Then you do WORKDIR /app and this just sets up where your code is going to live inside the container.
## Next Steps
Next you would add your other Docker commands...Why it fails:
- Description has no trigger words ("deploy", "containerize", "create Dockerfile")
- Keeps video narrator voice ("super cool", "basically", "right?")
- Not actionable (the agent can't execute "you're going to put")
- Incomplete (trails off with "...")
- Uses outdated Node 14
---
✅ GOOD: Transformed Skill
---
name: docker-nodejs
description: Containerize Node.js applications with Docker. Use when user needs to create a Dockerfile, build Docker images, or deploy Node.js apps in containers.
---
# Dockerize Node.js Applications
Create production-ready Docker containers for Node.js applications.
## Source
Generated from: [Docker Tutorial for Beginners](https://youtube.com/watch?v=example)
- **Channel:** TechChannel
- **Published:** 2022-03-15
- **Verified:** 2024-12-15 against Docker and Node.js documentation
**Updates applied:**
- Node.js base image updated from node:14 to node:20-alpine (LTS)
- Added multi-stage build for smaller production images
- Added security best practices from Docker docs
## Prerequisites
- [ ] Docker installed: `docker --version` (should show 20.x+)
- [ ] Node.js project with `package.json`
## Instructions
### Step 1: Create Dockerfile
Create `Dockerfile` in your project root:
Build stage
FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --only=production
Production stage
FROM node:20-alpine WORKDIR /app COPY --from=builder /app/node_modules ./node_modules COPY . . USER node EXPOSE 3000 CMD ["node", "index.js"]
### Step 2: Build the Image
docker build -t myapp:latest .
Expected output: `Successfully tagged myapp:latest`
### Step 3: Run the Container
docker run -p 3000:3000 myapp:latest
Your app is now accessible at `http://localhost:3000`
## Troubleshooting
### "npm ci" fails with permission errors
**Cause:** Running as root with strict permissions
**Solution:** Add before npm ci:RUN chown -R node:node /app USER node
## Reference
| Command | Description |
|---------|-------------|
| `docker build -t name .` | Build image from Dockerfile |
| `docker run -p HOST:CONTAINER image` | Run container with port mapping |
| `docker ps` | List running containers |
| `docker logs CONTAINER` | View container logs |
## See Also
- [Official Docker Node.js Guide](https://docs.docker.com/language/nodejs/)Why it works:
- Description contains trigger words users say
- No narrator voice — professional and direct
- Every step is executable (complete commands)
- Updated to current versions with explanation
- Includes troubleshooting for common issues
- Source attribution with modification notes
---
Example 2: React Hooks Tutorial
The Video Transcript (excerpt)
"Alright so useEffect is probably one of the most confusing hooks when you're first learning React. I remember when I was learning this I was like, what is going on? But basically, useEffect is for side effects - things like fetching data, setting up subscriptions, that kind of thing. So let me show you how it works. You import it from React, and then inside your component you call useEffect and pass it a function. Like useEffect(() => { // do something }). Now the tricky part is the dependency array..."
---
❌ BAD: Confusing, Narrative-Style
---
name: react-hooks
description: Understanding React hooks
---
# React Hooks
## useEffect
useEffect is probably one of the most confusing hooks when you're first learning React.
Basically useEffect is for side effects - things like fetching data, setting up subscriptions.
You import it from React and then inside your component you call useEffect and pass it a function.
The tricky part is the dependency array...Why it fails:
- Vague description (won't trigger on "fetch data in React")
- Keeps confusion framing from video
- Incomplete ("tricky part is...")
- No actual code examples
- Doesn't teach, just summarizes
---
✅ GOOD: Actionable Patterns
---
name: react-useeffect-data-fetching
description: Fetch data in React components using useEffect. Use when user needs to load API data, handle async operations in React, or set up data fetching with loading states.
---
# Data Fetching with useEffect
Fetch data from APIs in React functional components with proper loading and error states.
## Source
Generated from: [React Hooks Tutorial](https://youtube.com/watch?v=example)
- **Channel:** ReactMaster
- **Published:** 2023-01-20
- **Verified:** 2024-12-15 against React 18 documentation
**Updates applied:**
- Added AbortController for cleanup (React 18 strict mode)
- Added error boundary pattern from React docs
## Prerequisites
- [ ] React 18+ project
- [ ] Basic understanding of async/await
## Instructions
### Pattern 1: Basic Data Fetching
import { useState, useEffect } from 'react';
function UserProfile({ userId }: { userId: string }) { const [user, setUser] = useState<User | null>(null); const [loading, setLoading] = useState(true); const [error, setError] = useState<Error | null>(null);
useEffect(() => { const controller = new AbortController();
async function fetchUser() { try { setLoading(true); const response = await fetch(/api/users/${userId}, { signal: controller.signal }); if (!response.ok) throw new Error('Failed to fetch'); const data = await response.json(); setUser(data); } catch (err) { if (err.name !== 'AbortError') { setError(err as Error); } } finally { setLoading(false); } }
fetchUser();
return () => controller.abort(); // Cleanup on unmount or userId change }, [userId]); // Re-run when userId changes
if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; if (!user) return null;
return <div>{user.name}</div>; }
### Pattern 2: With Custom Hook (Reusable)
function useFetch<T>(url: string) { const [data, setData] = useState<T | null>(null); const [loading, setLoading] = useState(true); const [error, setError] = useState<Error | null>(null);
useEffect(() => { const controller = new AbortController();
fetch(url, { signal: controller.signal }) .then(res => res.json()) .then(setData) .catch(err => { if (err.name !== 'AbortError') setError(err); }) .finally(() => setLoading(false));
return () => controller.abort(); }, [url]);
return { data, loading, error }; }
// Usage function MyComponent() { const { data, loading, error } = useFetch<User[]>('/api/users'); // ... }
## Dependency Array Rules
| Array | Behavior |
|-------|----------|
| `[]` | Run once on mount |
| `[dep]` | Run on mount + when `dep` changes |
| No array | Run on every render (usually wrong) |
## Troubleshooting
### "React Hook useEffect has a missing dependency"
**Cause:** ESLint detected a variable used in effect but not in deps array
**Solution:** Either add to deps array or wrap in useCallback:const fetchData = useCallback(() => { ... }, []); useEffect(() => { fetchData(); }, [fetchData]);
### Effect runs twice in development
**Cause:** React 18 Strict Mode intentionally double-invokes effects
**Solution:** This is expected. Ensure your cleanup function works correctly.
The AbortController pattern above handles this properly.
## See Also
- [React useEffect Documentation](https://react.dev/reference/react/useEffect)Why it works:
- Specific description with trigger words ("fetch data", "API", "async")
- Complete, copy-paste-ready code
- Multiple patterns for different needs
- Explains the "why" concisely (dependency array table)
- Addresses real issues (Strict Mode double-invoke)
- Updated for React 18
---
The Transformation Checklist
When converting video content, verify:
| Check | Question |
|---|---|
| ✅ Trigger words | Would the agent find this if user says "help me fetch data in React"? |
| ✅ Complete code | Can this code be pasted and run without modification? |
| ✅ No narrator | Is there any "so basically", "let me show you", "pretty cool"? |
| ✅ Current | Have I verified versions against current docs? |
| ✅ Actionable | Can the agent follow each step without watching the video? |
| ✅ Troubleshooting | Did I include common errors from the video + current issues? |
The Deep Knowledge: What Makes Skills Actually Work
This document contains the understanding you MUST have before generating any skill. Read it completely.
---
The Fundamental Truth
A skill is not documentation. A skill is a tool that the agent uses to help users.
When you create a skill from a YouTube video, you are NOT:
- Transcribing what someone said
- Summarizing video content
- Creating notes about a topic
You ARE:
- Building a capability the agent can invoke
- Creating actionable instructions the agent follows
- Extending what the agent can do for users
---
How the agent Discovers and Uses Skills
Understanding this mechanism is critical:
1. The Description is a Classifier
description: "Extract text from PDFs, fill forms, merge documents. Use when working with PDF files."the agent reads ALL skill descriptions when deciding how to help a user. The description is not for humans — it's for the agent's decision-making.
What happens: 1. User says: "I need to get text out of this PDF" 2. the agent scans all skill descriptions 3. the agent matches "get text" + "PDF" → activates the skill 4. the agent then reads the full SKILL.md
If your description doesn't contain trigger words, the agent will never find your skill.
2. Trigger Words Must Match User Language
Users don't speak formally. They say:
- "How do I..." / "Can you help me..."
- "I need to..." / "I want to..."
- Platform names: "Docker", "React", "AWS"
- Actions: "deploy", "fix", "set up", "create"
Your description must contain these natural phrases.
Bad: description: "Containerization workflow management" Good: description: "Build and deploy Docker containers. Use when user needs to dockerize an app, create Dockerfile, or run containers."
3. One Skill = One Capability
the agent gets confused when skills try to do everything.
Bad: A skill that covers "React, Vue, Angular, and Svelte components" Good: Separate skills for each, or one focused on "frontend component patterns"
The test: Can you describe what this skill does in one sentence without using "and"?
---
The Transformation Problem
The #1 failure mode when converting videos to skills:
Video Content is Narrative
Videos are designed for humans watching sequentially:
- "So what we're going to do here is..."
- "The cool thing about this approach is..."
- "Now, you might be wondering why..."
- "Let me show you what happens when..."
Skills Must Be Imperative
Skills are designed for the agent to execute:
- Step 1: Run
command - Step 2: Edit
fileto add... - If X, then Y
- Warning: Don't do Z because...
The Transformation Map
| Video Pattern | Skill Pattern |
|---|---|
| "So basically..." | (delete entirely) |
| "What you want to do is run..." | bash command |
| "The way this works is..." | Brief explanation, then steps |
| "I like to..." | "Recommended approach:" (if validated) |
| "Make sure you..." | "Prerequisites:" or "⚠️ Warning:" |
| "If you get this error..." | "Troubleshooting:" section |
| "The important thing here is..." | "Key point:" callout |
| "Let me show you..." | Concrete example with code |
| Rambling explanation | Bullet points |
| 10-minute walkthrough | Numbered steps |
---
The Quality Checklist
Before finalizing any generated skill, verify:
Description Quality
- [ ] Contains action verbs users would say ("deploy", "create", "fix")
- [ ] Mentions specific technologies by name
- [ ] Includes "Use when..." phrase listing 3-4 concrete scenarios
- [ ] Under 1024 characters
- [ ] No jargon users wouldn't use
- [ ] Covers both the technology AND the problem it solves (e.g., "off-grid messaging" not just "LoRa")
Instructions Quality
- [ ] Every step is actionable (the agent can do it)
- [ ] Commands are complete (can be copy-pasted)
- [ ] Code examples are runnable
- [ ] No assumptions about "obvious" context
- [ ] Error cases are handled
Voice Quality
- [ ] No "so", "basically", "gonna", "wanna"
- [ ] No "what we're doing here is"
- [ ] No first person ("I like to...")
- [ ] No rhetorical questions
- [ ] Professional, direct tone
Structure Quality
- [ ] Clear sections with headers
- [ ] Prerequisites before instructions (with verification commands)
- [ ] Warnings before dangerous steps (hardware safety, data loss)
- [ ] Decision tables when multiple approaches exist
- [ ] Troubleshooting at the end with specific error messages
- [ ] Sources/references documented with links
---
The Enrichment Decision
When you find a video's content can be enhanced with current documentation:
When to Enrich
- API has changed since video
- Video skips edge cases docs cover
- Official docs have better examples
- Security best practices have evolved
When NOT to Enrich
- Video approach is intentionally simplified
- Adding docs would bloat the skill
- User explicitly wants video-only content
- Docs are for different use case
How to Enrich Well
1. Use Context7 to fetch relevant docs 2. Integrate naturally — don't just append 3. Note what came from video vs docs 4. Keep the skill focused (don't scope-creep)
---
Common Mistakes and Fixes
Mistake 1: The Info Dump
# Bad: Just facts, no actions
Docker is a containerization platform that allows you to package applications
with their dependencies. It uses images and containers. Images are templates
and containers are running instances...# Good: Actionable instructions
## Create a Docker Container
1. Create `Dockerfile` in project root:FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm install COPY . . CMD ["npm", "start"]
2. Build the image:docker build -t myapp .
3. Run the container:docker run -p 3000:3000 myapp
Mistake 2: The Vague Instruction
# Bad: What does "set up" mean?
Set up your environment properly before starting.# Good: Specific steps
## Prerequisites
1. Install Node.js 20+: `brew install node` (macOS) or `winget install OpenJS.NodeJS` (Windows)
2. Verify installation: `node --version` (should show v20.x.x)
3. Install dependencies: `npm install`Mistake 3: Missing Context
# Bad: Assumes knowledge
Run the migration command to update the schema.# Good: Complete context
Run the database migration:npx prisma migrate dev --name init
This creates the `prisma/migrations/` folder and applies the schema from `prisma/schema.prisma` to your database.
**If you see "database does not exist":** Run `createdb myapp_dev` first.Mistake 4: Wrong Audience
# Bad: Written for video viewer
As you can see on screen, we're clicking the deploy button...# Good: Written for the agent to execute
Deploy to Vercel:vercel deploy --prod
When prompted, select:
- Project name: (use default or specify)
- Framework: Next.js
- Root directory: ./---
The Source Attribution Rule
Every generated skill MUST include:
## Source
Generated from: [Video Title](youtube-url)
- Channel: <channel name>
- Published: <date>
- Verified against: <current docs date>
**Updates applied:**
- <what was updated from original video>This is not optional. Users need to know the provenance.
---
Final Test
Before delivering a skill, ask yourself:
1. "If I gave this to the agent with no other context, could it help a user?"
- If no → Instructions aren't complete enough
2. "Would the agent find this skill when a user asks for help with X?"
- If no → Description doesn't have right trigger words
3. "Is there any 'video narrator' voice left?"
- If yes → Transform those sections
4. "Is every piece of information still accurate today?"
- If unsure → Verify with Context7 or WebSearch
5. "Does this skill try to do too many things?"
- If yes → Split into multiple skills
#!/usr/bin/env python3
"""
YouTube Transcript Extractor
Extracts transcripts and metadata from YouTube videos or playlists.
Supports YouTube captions and falls back to Whisper for audio transcription.
Usage:
python extract_youtube.py <video_or_playlist_url>
python extract_youtube.py <url> --whisper # Force Whisper transcription
Output:
JSON to stdout with video metadata and transcript(s)
Dependencies:
pip install youtube-transcript-api yt-dlp
# Optional for Whisper fallback: pip install openai-whisper
"""
import argparse
import json
import sys
import re
from datetime import datetime
from typing import Optional
def check_dependencies():
"""Check and report missing dependencies."""
missing = []
try:
from youtube_transcript_api import YouTubeTranscriptApi
except ImportError:
missing.append("youtube-transcript-api")
try:
import yt_dlp
except ImportError:
missing.append("yt-dlp")
if missing:
print(json.dumps({
"error": "Missing dependencies",
"install": f"pip install {' '.join(missing)}",
"missing": missing
}))
sys.exit(1)
check_dependencies()
from youtube_transcript_api import YouTubeTranscriptApi
import yt_dlp
def extract_video_id(url: str) -> Optional[str]:
"""Extract video ID from various YouTube URL formats."""
patterns = [
r'(?:v=|/v/|youtu\.be/)([a-zA-Z0-9_-]{11})',
r'(?:embed/)([a-zA-Z0-9_-]{11})',
r'^([a-zA-Z0-9_-]{11})$'
]
for pattern in patterns:
match = re.search(pattern, url)
if match:
return match.group(1)
return None
def extract_playlist_id(url: str) -> Optional[str]:
"""Extract playlist ID from YouTube URL."""
match = re.search(r'list=([a-zA-Z0-9_-]+)', url)
return match.group(1) if match else None
def get_video_metadata(video_id: str) -> dict:
"""Fetch video metadata using yt-dlp."""
ydl_opts = {
'quiet': True,
'no_warnings': True,
'extract_flat': False,
}
try:
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
info = ydl.extract_info(f"https://www.youtube.com/watch?v={video_id}", download=False)
# Parse upload date
upload_date = info.get("upload_date", "")
if upload_date and len(upload_date) == 8:
formatted_date = f"{upload_date[:4]}-{upload_date[4:6]}-{upload_date[6:]}"
else:
formatted_date = upload_date or "Unknown"
return {
"video_id": video_id,
"title": info.get("title", "Unknown"),
"channel": info.get("channel", info.get("uploader", "Unknown")),
"publish_date": formatted_date,
"duration_seconds": info.get("duration", 0),
"description": info.get("description", ""),
"url": f"https://www.youtube.com/watch?v={video_id}"
}
except Exception as e:
return {
"video_id": video_id,
"title": "Unknown",
"channel": "Unknown",
"publish_date": "Unknown",
"duration_seconds": 0,
"description": "",
"url": f"https://www.youtube.com/watch?v={video_id}",
"metadata_error": str(e)
}
def get_transcript_from_captions(video_id: str) -> Optional[dict]:
"""Try to get transcript from YouTube captions using new API (v1.0+)."""
try:
api = YouTubeTranscriptApi()
transcript_list = api.list(video_id)
# Convert to list for easier handling
available = list(transcript_list)
if not available:
return None
# Prefer manually created English transcripts
selected = None
for t in available:
if t.language_code in ['en', 'en-US', 'en-GB']:
if not t.is_generated:
selected = t
break
elif selected is None or selected.is_generated:
selected = t
# Fall back to any available transcript
if selected is None:
selected = available[0]
# Fetch the transcript
transcript_data = selected.fetch()
# Build full text and segments
full_text = ""
segments = []
for snippet in transcript_data.snippets:
segments.append({
"start": snippet.start,
"duration": snippet.duration,
"text": snippet.text
})
full_text += snippet.text + " "
return {
"source": "youtube_captions",
"language": selected.language,
"language_code": selected.language_code,
"is_generated": selected.is_generated,
"full_text": full_text.strip(),
"segments": segments
}
except Exception as e:
error_str = str(e)
if "disabled" in error_str.lower() or "no transcript" in error_str.lower():
return None
return {"error": f"Caption extraction failed: {error_str}"}
def get_transcript_from_whisper(video_id: str) -> Optional[dict]:
"""Download audio and transcribe with Whisper (if available)."""
try:
import whisper
import tempfile
import os
# Download audio only
ydl_opts = {
'format': 'bestaudio/best',
'quiet': True,
'no_warnings': True,
'outtmpl': os.path.join(tempfile.gettempdir(), f'{video_id}.%(ext)s'),
'postprocessors': [{
'key': 'FFmpegExtractAudio',
'preferredcodec': 'mp3',
}]
}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
ydl.download([f"https://www.youtube.com/watch?v={video_id}"])
audio_path = os.path.join(tempfile.gettempdir(), f"{video_id}.mp3")
# Transcribe with Whisper
model = whisper.load_model("base")
result = model.transcribe(audio_path)
# Clean up
os.remove(audio_path)
segments = [{
"start": seg["start"],
"duration": seg["end"] - seg["start"],
"text": seg["text"]
} for seg in result["segments"]]
return {
"source": "whisper",
"language": result.get("language", "en"),
"is_generated": True,
"full_text": result["text"],
"segments": segments
}
except ImportError:
return {"error": "Whisper not installed. Install with: pip install openai-whisper"}
except Exception as e:
return {"error": f"Whisper transcription failed: {str(e)}"}
def get_playlist_videos(playlist_id: str) -> list:
"""Get all video IDs from a playlist."""
ydl_opts = {
'quiet': True,
'no_warnings': True,
'extract_flat': True,
'playlistend': 50,
}
try:
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
playlist_info = ydl.extract_info(
f"https://www.youtube.com/playlist?list={playlist_id}",
download=False
)
videos = []
for entry in playlist_info.get("entries", []):
if entry:
videos.append({
"video_id": entry.get("id"),
"title": entry.get("title", "Unknown"),
"duration": entry.get("duration", 0)
})
return videos
except Exception as e:
return [{"error": str(e)}]
def process_video(video_id: str, use_whisper: bool = False) -> dict:
"""Process a single video: get metadata and transcript."""
result = get_video_metadata(video_id)
# Try captions first (unless whisper forced)
if not use_whisper:
transcript = get_transcript_from_captions(video_id)
if transcript and "error" not in transcript:
result["transcript"] = transcript
return result
# Fall back to Whisper or if forced
if use_whisper or not result.get("transcript"):
transcript = get_transcript_from_whisper(video_id)
if transcript:
result["transcript"] = transcript
else:
result["transcript"] = {
"error": "No transcript available. Video may not have captions and Whisper is not installed."
}
return result
def main():
parser = argparse.ArgumentParser(
description="Extract transcripts from YouTube videos or playlists"
)
parser.add_argument("url", help="YouTube video or playlist URL")
parser.add_argument(
"--whisper",
action="store_true",
help="Force Whisper transcription instead of YouTube captions"
)
parser.add_argument(
"--playlist-info-only",
action="store_true",
help="For playlists, only list videos without extracting transcripts"
)
args = parser.parse_args()
# Determine if URL is video or playlist
playlist_id = extract_playlist_id(args.url)
video_id = extract_video_id(args.url)
output = {
"extracted_at": datetime.utcnow().isoformat() + "Z",
"source_url": args.url
}
if playlist_id and "watch" not in args.url:
# It's a playlist URL
output["type"] = "playlist"
output["playlist_id"] = playlist_id
videos = get_playlist_videos(playlist_id)
output["video_count"] = len(videos)
if args.playlist_info_only:
output["videos"] = videos
else:
output["videos"] = []
for i, video_info in enumerate(videos, 1):
if "error" in video_info:
output["videos"].append(video_info)
continue
print(f"Processing video {i}/{len(videos)}: {video_info['title']}", file=sys.stderr)
result = process_video(video_info["video_id"], args.whisper)
output["videos"].append(result)
elif video_id:
output["type"] = "video"
output["video"] = process_video(video_id, args.whisper)
else:
output["error"] = "Could not extract video or playlist ID from URL"
print(json.dumps(output, indent=2, ensure_ascii=False))
if __name__ == "__main__":
main()
youtube-transcript-api>=1.0.0
yt-dlp>=2024.0.0
<Skill Name>
<One sentence: what capability does this skill provide?>
Source
Generated from: <Video Title>
- Channel: <channel name>
- Published: <YYYY-MM-DD>
- Verified: <YYYY-MM-DD> against current documentation
Modifications from original:
- <List any updates made due to outdated content>
- <List any enrichments from official docs>
Prerequisites
<What must be true before using this skill?>
- [ ] <Requirement 1 with verification command if applicable>
- [ ] <Requirement 2>
- [ ] <Hardware warning if applicable: ⚠️ CRITICAL safety notes>
Choose Your Approach (if multiple methods exist)
<Delete this section if only one approach. Use when video covers easy vs advanced methods.>
| Approach | Best For | Complexity |
|---|---|---|
| <Method A> | <use case> | Low |
| <Method B> | <use case> | Medium/High |
---
Instructions
<The core workflow. Every step must be actionable.>
Step 1: <Action Verb> <Thing>
<Brief context if needed — one sentence max>
<complete, runnable command><Expected output or result>
Step 2: <Action Verb> <Thing>
...
Configuration
<Any configuration files, environment variables, or settings>
```<language> <complete configuration example>
## Troubleshooting
<Common issues and their solutions>
### "<Error message or symptom>"
**Cause:** <why this happens>
**Solution:**<fix command>
### "<Another common issue>"
...
## Reference
<Additional commands, API references, or details that support the main workflow>
| Command | Description |
|---------|-------------|
| `<cmd>` | <what it does> |
## See Also
- [Official Documentation](<url>)
- [Related Skill](<path-to-related-skill>)