YouTube transcript panel pull
Use this skill when you need the words from a YouTube video as text: your own episode or webinar to repurpose, or a prospect's talk or podcast appearance to prepare for a call. Produces one timestamped .txt transcript per video. "Get the transcript of this video", "pull captions from my channel", "what did they say on that podcast".
Applies when someone needs a YouTube video's spoken words as text. Produces one timestamped transcript file per video.
The reliable route is the transcript panel on the watch page, read by the agent in a browser. It works on public videos without signing in.
The play
- Collect the video IDs. For one video, take the
v=value from the URL. For a channel, open its Videos tab and collect the IDs from the page links (snippet inreferences/browser-snippets.md). Skip Shorts and IDs you already have. - Open each watch page. Pause the video, expand the description, then click "Show transcript". The button lives inside the description, so it does not exist until the description is expanded.
- Wait a few seconds, then read the transcript rows. Each row has a timestamp and a text span. Join them as
[m:ss] text. - Get the text off the page through a temporary textarea and a copy command. Return the row count and the first and last rows, check the clipboard matches, then write the file. This keeps a long transcript out of the agent's context.
- Zero rows usually means the panel hasn't loaded; it can also mean no captions or changed markup. Retry once, then report which; never save an empty file.
- Save one file per video with a short header: video ID, title, language and whether the captions are auto-generated. Read the language and caption type from the page; don't assume them.
- When pulling a batch, check that the counts agree: IDs collected, files written, unique IDs on disk.
Open references/browser-snippets.md for the exact selectors and the copy snippet.
What good looks like
- First check: the row count. A real 30-minute episode returns hundreds of rows; a handful means the panel was still loading, or you grabbed a chapter list instead.
- The mediocre version calls YouTube's caption endpoint directly. Without a session token it now returns an empty body, so the agent reports success on blank files.
- The other mediocre version pastes the whole transcript into the chat to save it. It works once, then burns the agent's context on the second video.
- The output is right when every file has rows, the batch counts agree, and a spot-checked timestamp in the file matches the video at that moment.
Rules
- MUST read the transcript panel, not the caption endpoint.
- MUST refuse to save a file with zero rows.
- MUST label auto-generated captions as auto-generated; speakers are not identified in them.
- NEVER quote a transcript line as a person's exact words without checking it against the audio. Auto-captions mishear names, numbers and jargon.
- NEVER pull a private or members-only video the reader has no right to.
references/browser-snippets.md
Browser snippets
Run these in the page with the agent's JavaScript tool. YouTube changes its markup from time to time. If a selector returns nothing, inspect the page and update it rather than falling back to the caption endpoint.
Collect video IDs from a channel's Videos tab
Scroll to the bottom first if the channel has more videos than the first screen loads.
[...new Set([...document.querySelectorAll('a[href*="/watch?v="]')]
.map(a => a.href.match(/v=([\w-]{11})/)?.[1]).filter(Boolean))]
Open the transcript panel
document.querySelector('video')?.pause();
document.querySelector('tp-yt-paper-button#expand')?.click();
document.querySelector('ytd-video-description-transcript-section-renderer button')?.click();
Read the rows and copy them
Wait a few seconds after opening the panel.
const rows = [...document.querySelectorAll('transcript-segment-view-model')].map(e => {
const ts = e.querySelector('[class*=Timestamp]')?.textContent.trim();
const tx = e.querySelector('span[role=text]')?.textContent.replace(/\s+/g, ' ').trim();
return `[${ts}] ${tx}`;
});
const ta = document.createElement('textarea');
ta.value = rows.join('\n');
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
ta.remove();
rows.length;
navigator.clipboard.writeText is often refused in an agent-driven browser because the page doesn't have focus. The textarea route works there.
On a Mac, write the file with pbpaste > <video-id>.txt. Elsewhere, use the platform's paste command, or have the agent return the text in chunks and write it.
File header
YouTube transcript
Video ID: <id>
Title: <title>
Language: <from the panel's language menu>
Captions: auto-generated | creator-provided
[0:00] ...
Failure signs
| You see | It means | Do |
|---|---|---|
rows.length is 0 | the panel hasn't rendered | wait 3 to 5 seconds and rerun |
| no transcript button | description not expanded, or the video has no captions | expand and retry once, then record "no captions" |
| a few rows with chapter titles | you read the chapters list | check the selector is transcript-segment-view-model |
| caption URL returns an empty body | the endpoint needs a token | use the panel |