# Extract Youtube Transcript

> Use this skill when the user wants to extract audio from a YouTube video and transcribe it. Triggers include: 'extract transcript from youtube', 'transcribe this video', 'get transcript from YouTube', 'extract audio and transcribe', or any message that includes a YouTube URL and asks for a transcript or transcription.

- Skill: `rtemm/extract-youtube-transcript` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add rtemm/extract-youtube-transcript`
- Raw SKILL.md: https://api.skillmd.com/api/skills/rtemm/extract-youtube-transcript/raw
- Safety review: pending (external: skill-scanner WARNING, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Rtemm (https://skillmd.com/u/rtemm)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/rtemm/extract-youtube-transcript

---


# extract-transcript Skill

When invoked, follow these steps in order. Do not skip steps.

---

## Step 0 — Verify and install dependencies

Check each tool and install if missing. Stop and report clearly if any install fails.

### youtube-transcript-api
```bash
pip install youtube-transcript-api
```
Always needed — used in Step 3 to check for an existing YouTube transcript.

### yt-dlp
```bash
yt-dlp --version
```
If not found:
```bash
pip install yt-dlp
```

### Whisper
```bash
whisper --help
```
If not found:
```bash
pip install openai-whisper
```
> Whisper is only used if no YouTube transcript is found in Step 3. Install it regardless — it is fast and needed as a fallback.

### ffmpeg
```bash
ffmpeg -version
```
If not found, install via winget (Windows 11):
```bash
winget install --id Gyan.FFmpeg -e
```
If winget fails, stop and tell the user:
> "ffmpeg is required but could not be installed automatically. Please download it from https://ffmpeg.org/download.html, extract it, and add the `bin/` folder to your system PATH. Then re-run this skill."

### python-docx
```bash
pip install python-docx
```
Always needed — used in Step 6 to generate the summary `.docx`.

After any install, re-verify the tool is available before continuing. If it still fails, stop and report the error.

---

## Step 1 — Get the YouTube URL

If the user already provided a URL in their message, use it. If not, ask:
> "Please provide the YouTube URL you'd like to transcribe."

---

## Step 2 — Create the base output folder

Create `doc summaries output` inside the current working directory if it does not already exist:

```bash
mkdir -p "doc summaries output"
```

The per-video subfolder will be created in Step 3 once the video title is known.

---

## Step 3 — Resolve video title and check for a YouTube transcript

This step does two things in one script:
1. Fetches the video title from YouTube metadata (always needed for the subfolder name and filenames).
2. Checks whether YouTube already provides a transcript. If yes, saves it to disk and skips audio download entirely.

Write and run this Python script:

```python
import re, os, subprocess, json
from youtube_transcript_api import YouTubeTranscriptApi
from youtube_transcript_api._errors import NoTranscriptFound, TranscriptsDisabled

url = "<URL>"
base_output = "doc summaries output"

# --- Fetch video title ---
meta = subprocess.run(
    ["yt-dlp", "--dump-json", "--skip-download", url],
    capture_output=True, text=True
)
raw_title = json.loads(meta.stdout)["title"]

# Sanitize title for use as a folder/file name (strip Windows-illegal characters)
illegal = r'\/:*?"<>|'
title = "".join(c for c in raw_title if c not in illegal).strip()

# --- Create per-video subfolder ---
video_folder = os.path.join(base_output, title)
os.makedirs(video_folder, exist_ok=True)

# --- Check for YouTube transcript ---
video_id = re.search(r"(?:v=|youtu\.be/)([^&?/]+)", url).group(1)
ytt = YouTubeTranscriptApi()

try:
    transcript_list = ytt.list(video_id)

    # Prefer manual English captions → auto-generated English → anything available
    transcript_obj = None
    source = None
    for t in transcript_list:
        if not t.is_generated and t.language_code.startswith("en"):
            transcript_obj = t
            source = "manual"
            break
    if transcript_obj is None:
        for t in transcript_list:
            if t.is_generated and t.language_code.startswith("en"):
                transcript_obj = t
                source = "auto-generated"
                break
    if transcript_obj is None:
        transcript_obj = next(iter(transcript_list))
        source = "auto-generated" if transcript_obj.is_generated else "manual"

    fetched = transcript_obj.fetch()
    text = " ".join(entry.text for entry in fetched)

    txt_path = os.path.join(video_folder, f"{title}.txt")
    with open(txt_path, "w", encoding="utf-8") as f:
        f.write(text)

    print(f"TRANSCRIPT_FOUND|{source}|{title}|{video_folder}|{txt_path}")

except (NoTranscriptFound, TranscriptsDisabled):
    print(f"NO_TRANSCRIPT|{title}|{video_folder}")
except Exception as e:
    print(f"NO_TRANSCRIPT|{title}|{video_folder}|error: {e}")
```

**If the output starts with `TRANSCRIPT_FOUND`:**
- Parse: `source`, `title`, `video_folder`, `txt_path` from the pipe-delimited output.
- Tell the user: "YouTube transcript found ({source}). Skipping audio download and Whisper."
- **Skip Steps 4 and 5. Go directly to Step 6.**

**If the output starts with `NO_TRANSCRIPT`:**
- Parse: `title`, `video_folder` from the output.
- Tell the user: "No YouTube transcript available — downloading audio and transcribing with Whisper."
- Continue to Step 4.

---

## Step 4 — Download audio

*(Only reached if Step 3 found no transcript.)*

Download the MP3 directly into the video subfolder resolved in Step 3:

```bash
yt-dlp -x --audio-format mp3 "<URL>" -o "doc summaries output/<title>/%(title)s.%(ext)s"
```

Replace `<title>` with the sanitized title from Step 3. If yt-dlp reports an error (private video, age-restricted, unavailable), tell the user and stop.

---

## Step 5 — Transcribe

*(Only reached if Step 3 found no transcript.)*

Find the `.mp3` file inside `doc summaries output/<title>/`. Run Whisper on it, outputting the transcript into the same subfolder:

```bash
whisper "doc summaries output/<title>/<filename>.mp3" --output_format txt --output_dir "doc summaries output/<title>" --device cuda
```

If CUDA is unavailable (no NVIDIA GPU), retry with CPU:
```bash
whisper "doc summaries output/<title>/<filename>.mp3" --output_format txt --output_dir "doc summaries output/<title>" --device cpu
```

Let the user know transcription is running. GPU typically finishes in under a minute; CPU may take several minutes for longer videos.

After Whisper finishes, locate the `.txt` it created inside the subfolder and set `txt_path` to that file's full path for use in Step 6.

---

## Step 6 — Generate summary .docx and .txt

Read the transcript from `txt_path`. Generate **two** summary files inside `doc summaries output/<title>/` using a single Python script:

| File | Purpose |
|---|---|
| `<title> - Summary.txt` | Plain text — readable in VS Code, easy to copy-paste |
| `<title> - Summary.docx` | Formatted Word doc — upload to Google Drive to open as a Google Doc |

Both files must contain the same content in these sections:

1. **Title** — video title
2. **Overview** — 2-3 sentence summary of what the video is about
3. **Detailed Summary** — subsections matching the main topics covered in the video
4. **Key Points** — numbered list, each with a bold label (in the .docx) and a 1-2 sentence explanation
5. **Main Takeaways** — bulleted list of the most actionable/important conclusions
6. **Tools or Concepts Referenced** — 2-column table (Name | Description) of every tool, platform, or concept mentioned

### Plain text formatting (.txt)

Use plain ASCII so the file is clean in any editor:

```
================================================
VIDEO TITLE HERE
================================================

OVERVIEW
--------
Paragraph text here.

DETAILED SUMMARY
----------------

  Topic Heading
  ~~~~~~~~~~~~~
  Paragraph text here.

KEY POINTS
----------
1. Bold Label — Explanation sentence here.
2. Bold Label — Explanation sentence here.

MAIN TAKEAWAYS
--------------
• Takeaway one
• Takeaway two

TOOLS AND CONCEPTS REFERENCED
------------------------------
Name                  | Description
----------------------|---------------------------------------------
Tool name             | Description here.
```

Save both files to `doc summaries output/<title>/`.

---

## Step 7 — Report results

When complete, tell the user:

1. The full path of the video subfolder: `<cwd>/doc summaries output/<title>/`
2. List every file inside it:
   - `<title>.mp3` — downloaded audio *(only present if Whisper path was taken)*
   - `<title>.txt` — raw transcript
   - `<title> - Summary.txt` — plain text summary (open in VS Code)
   - `<title> - Summary.docx` — formatted summary (upload to Google Drive)
3. Whether the transcript came from YouTube (manual or auto-generated) or was generated by Whisper.
4. Print the first ~300 characters of the raw transcript so the user can verify it looks correct.
5. Remind the user they can upload the `.docx` to Google Drive and it will open automatically as a Google Doc.

---

## Error handling reference

| Error | Action |
|---|---|
| pip not found | Tell user to install Python from https://www.python.org/downloads/ |
| Private / unavailable video | Tell user yt-dlp cannot download this video |
| ffmpeg not in PATH after install | Instruct user to restart terminal or add ffmpeg to PATH manually |
| Whisper CUDA out of memory | Retry with `--model base` or `--model tiny` to reduce VRAM usage |
| Whisper CUDA not available | Retry with `--device cpu` (will be slower) |
| No audio track / unsupported format | yt-dlp will report this; relay the message to the user |
| youtube-transcript-api import error | Run `pip install youtube-transcript-api` and retry Step 3 |
| Illegal characters in folder name | The Step 3 script sanitizes the title — if mkdir still fails, strip all non-alphanumeric characters except spaces and dashes |
| yt-dlp title parse fails (empty stdout) | The video may be private or geo-blocked; tell the user and stop |

