youtube-transcript-notes
Turn captioned lecture videos into readable notes and grounded summaries with clickable timestamps.
Give Claude Code or Codex a YouTube URL and ask for notes or a summary. The bundled skill finds the best available captions, records how trustworthy they are, and preserves a link back to the source for every substantive point. The command-line tool can also be used on its own.
Videos without captions are refused. The project does not download audio or run speech recognition.
Quick start
Python 3.10 or newer. There are two ways in, depending on whether you want the agent workflow or only the command-line tool.
Install the command-line tool
pipx install "youtube-transcript-notes[youtube]"
The [youtube] part brings in yt-dlp, which
is what fetches captions. Without it the tool installs and runs but cannot reach
YouTube. pip install "youtube-transcript-notes[youtube]" into a virtual
environment does the same job.
youtube-transcript-notes \
"https://www.youtube.com/watch?v=HtSuA80QTyo" --out notes/
Or clone it, for Claude Code and Codex
The bundled skills and the names.txt glossary are repository files rather than
part of the installed package, so the agent workflow starts from a clone:
git clone https://github.com/ekelly95/youtube-transcript-notes.git
cd youtube-transcript-notes
python -m venv .venv
Windows:
.venv/Scripts/python -m pip install -e ".[youtube]"
macOS or Linux:
.venv/bin/python -m pip install -e ".[youtube]"
Open the folder in Claude Code or Codex and ask:
Make me notes on https://www.youtube.com/watch?v=HtSuA80QTyo
Claude Code reads .claude/skills/; Codex reads the identical workflow from
.agents/skills/. To set a personal output folder or post-processing step,
create a gitignored LOCAL.md beside the relevant SKILL.md.
Either way, the result is ordinary Markdown:
# Lecture 1: Algorithmic Thinking, Peak Finding
*MIT OpenCourseWare · 14 January 2013 · [watch](https://www.youtube.com/watch?v=HtSuA80QTyo) · human-written captions (en)*
## Intro
**[0:22](https://www.youtube.com/watch?v=HtSuA80QTyo&t=22)** **PROFESSOR:** Hi. I'm
Srini Devadas. I'm a professor of electrical engineering and computer science…
What it adds
- Reassembles caption fragments into readable paragraphs that begin at sentence boundaries.
- Keeps paragraph timestamps, title, channel, publication date, chapters, and retrieval provenance.
- Prefers human-written captions and identifies automatic or translated tracks.
- Interprets common speaker and non-speech annotations without inventing speakers.
- Proposes visible corrections beside the original words; it never silently rewrites a transcript.
- Handles videos, playlists, caption files, and folders with per-item failure isolation.
- Caches captions and metadata so previously fetched lectures remain available during a YouTube or yt-dlp outage.
- Refuses to replace an existing, different note unless
--forceis explicit.
CLI
youtube-transcript-notes <source>… [--out DIR] [--force]
[--format markdown|plain|citation|jsonl|context] [--list]
[--languages LANG…] [--tiers TIER…] [--budget N]
[--glossary FILE] [--corrections FILE]
[--json] [--cache DIR | --no-cache] [--version]
<source> may be a YouTube URL, video ID, playlist URL, caption file, or
folder. Multiple sources are allowed; one failure does not discard the rest.
A caption file has no video to link to, so its timestamps are plain positions
rather than links.
Useful examples. An installed copy is called as youtube-transcript-notes; from
a clone, run them with the venv's python (.venv/Scripts/python or
.venv/bin/python, as in Quick start). The two are the same program.
# See available tracks without downloading caption payloads
python -m youtube_transcript_notes HtSuA80QTyo --list
# Prefer German, then English
python -m youtube_transcript_notes HtSuA80QTyo --languages de en
# Process a playlist or a folder into one file per lecture
python -m youtube_transcript_notes <playlist-url> --out notes/course
python -m youtube_transcript_notes ~/Downloads/course-captions --out notes/course
# Produce a bibliography entry or bounded agent context
python -m youtube_transcript_notes HtSuA80QTyo --format citation
python -m youtube_transcript_notes HtSuA80QTyo --format context --budget 6000
| Format | Also accepts | Purpose |
|---|---|---|
markdown |
md |
Timestamped study notes; default |
plain |
text |
Reading text without markup |
citation |
cite |
Bibliography entry and retrieval provenance |
jsonl |
One record per passage for other tools | |
context |
Metadata and outline followed by budgeted transcript text |
Documents go to stdout and failures to stderr. Exit code 0 means all items
succeeded, 1 means some failed, and 2 means nothing was produced. With
--json, results and failures share one machine-readable document.
Three combinations are refused before any work starts, because each one asks
for two different things at once: --out with --list (a listing is not a
document to file), --force without --out (nothing is being overwritten),
and --budget with a format that has no budget to spend — only context
does.
Trust and corrections
The selected track is recorded in every result except plain text, which is deliberately stripped of everything citable:
| Tier | Meaning | Quoting guidance |
|---|---|---|
manual |
Human-written captions | Quote directly |
asr_platform |
YouTube automatic captions | Verify technical terms |
asr_local |
Captions made by a separate transcription tool | Depends on that tool |
translated |
Machine-translated transcription | Use for gist, not quotation |
A note may also carry two marks of the captioner's own uncertainty:
(inaudible) where they could not make the words out, and (?) after a word
they guessed at — a cure(?) keeps the guess with the doubt attached.
The lecture title and chapter headings provide an initial spelling glossary.
Add recurring acoustic errors with --glossary:
Anthropic
Claude Code: quad code, Squad code, Cloud Code, Quack Co
Andrej Karpathy: Andrew Carpet
python -m youtube_transcript_notes <source> --glossary names.txt
The repository carries a starter list at
names.txt.
It is entirely agent-engineering vocabulary, so on a lecture from another field
it is a template rather than a list. An installed copy does not include it.
--corrections found.json accepts a list of wrong, right, and optional
evidence fields. Corrections appear as quad code [Claude Code] and in an
appendix. The original caption text remains intact.
Local caption filenames may include tier and language metadata, for example
week-01.auto.en.vtt. Unmarked local captions default to human-written; mark
automatic files with .auto. because the tier controls deduplication.
Files and cache
Remote output names include the source ID, such as
Peak Finding (HtSuA80QTyo).md. This prevents a video title from claiming an
unrelated note. Local files keep their stem.
If the chosen name already contains identical output, the run reports
unchanged. Different content is refused unless --force is given. A title
changed upstream creates a new file and leaves the old one alone.
The default cache is:
- Windows:
%LOCALAPPDATA%\youtube-transcript-notes\cache - macOS:
~/Library/Caches/youtube-transcript-notes - Other Unix:
$XDG_CACHE_HOME/youtube-transcript-notes, or~/.cache/youtube-transcript-noteswhen that variable is unset
Use YOUTUBE_TRANSCRIPT_NOTES_CACHE, --cache, or --no-cache to override it.
When yt-dlp cannot reach YouTube, cached lectures remain usable and are clearly
reported as stale. Updating yt-dlp is the usual repair for upstream changes.
The cache is an optimisation and never a reason for a run to fail: if it cannot be written — a full disk, a read-only directory — the lecture is still produced and the next run simply fetches again.
Python API
from youtube_transcript_notes import TranscriptFetcher
manifest = TranscriptFetcher().list("HtSuA80QTyo") # metadata only
lecture = manifest.find(["en"]).fetch()
excerpt = lecture.between(720, 1200) # 12:00–20:00, still citable
Limits
- Captioned videos only; no audio transcription fallback.
- Channel and search URLs are refused. Use individual video or playlist URLs.
- Playlists over 500 videos are refused rather than silently truncated.
- Speaker identity is kept only when the captions provide it.
- Paragraph sizing counts space-separated words, so it is less effective for languages such as Chinese, Japanese, and Thai.
- A
watch?v=…&list=…share link means one video; use the playlist URL to process the playlist.
Troubleshooting
Every failure carries a plain-language cause and something to try. With
--json the same failure also carries a stable code, so a script or an agent
can branch on NO_CAPTIONS_AVAILABLE rather than on English.
"Sign in to confirm you're not a bot", or every lecture failing at once. YouTube blocks addresses it thinks are automated, and it treats datacenter ranges as automated by default. This is the commonest first-run failure and it is almost never about the video. Run from an ordinary home connection rather than a VPN, a cloud box, or a CI runner. There is no cookie or sign-in option: the tool has no account handling, deliberately.
Errors mentioning extraction, the player response, or a signature.
YouTube changed something and yt-dlp has not caught up, or your copy is old.
These are reported as TRANSPORT_CONTRACT_CHANGED rather than as a problem
with the lecture, because the lecture is usually fine:
.venv/Scripts/python -m pip install -U yt-dlp
If yt-dlp is already current, wait a day or two — this is the one dependency whose upstream breaks on somebody else's schedule.
"has no caption tracks of any kind". The tool reads captions; it does not create them. There is no audio download and no speech recognition, on purpose. If you can obtain a transcript as a caption file from anywhere else — a course platform, or a separate transcription tool — pass that file's path as the source instead.
Age-restricted or region-blocked lectures cannot be retrieved. Both are reported by name rather than as a generic failure, so you can tell them from a video that was deleted.
"refusing to replace". The output file already exists and holds something
different. This is the guard that stops a lecture retitled upstream, or an
uploader who picked your filename, from overwriting a note. Read the file
first; if replacing it is what you want, add --force.
Nothing appears for a long time on a playlist. Lectures are fetched one at
a time and the report is printed at the end, so a large playlist is quiet while
it works. Exit code 1 afterwards means some items failed and the rest
succeeded — the run is not discarded because one lecture was.
Development
Windows paths shown, as in Quick start; on macOS and Linux the interpreter is
.venv/bin/python and the linter .venv/bin/ruff.
.venv/Scripts/python -m pip install -e ".[dev,youtube]"
.venv/Scripts/python -m coverage run -m pytest
.venv/Scripts/python -m coverage report
.venv/Scripts/ruff.exe format --check .
.venv/Scripts/ruff.exe check .
.venv/Scripts/python -m mypy
.venv/Scripts/python -m pytest -m canary # live YouTube; opt in
The offline suite enforces 100% branch coverage and never touches the network —
tests/conftest.py fails any test that opens a connection, so every test runs
against payloads captured once and replayed. mypy runs strict over src,
which is what makes the shipped py.typed marker a promise rather than a
claim.
Every push runs the full matrix: Linux, Windows and macOS across Python 3.10–3.14. The live canary is not part of it, because GitHub's runners are datacenter addresses and YouTube blocks them — run it yourself before a release.
License and credit
The code is MIT licensed. The captured lecture captions under
tests/fixtures/captions/ are MIT OpenCourseWare's, retain their CC BY-NC-SA
4.0 license, and are excluded from distributions; the hand-built fixture under
tests/fixtures/synthetic/ is covered by this project's own license.
Captions are retrieved through yt-dlp, which does the hardest and least thanked job here: keeping up with YouTube.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file youtube_transcript_notes-0.3.0.tar.gz.
File metadata
- Download URL: youtube_transcript_notes-0.3.0.tar.gz
- Upload date:
- Size: 105.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8e30303348308ef4f4cb986afdadcc8a76da8ad8654c534707caa77feb2ec458
|
|
| MD5 |
7a0596504e4e1efa5219ea347be5a399
|
|
| BLAKE2b-256 |
c73a14b97b0de8d15ef90fc5b6ef73016b775f42d646f96ca74d78273b823e90
|
Provenance
The following attestation bundles were made for youtube_transcript_notes-0.3.0.tar.gz:
Publisher:
release.yml on ekelly95/youtube-transcript-notes
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
youtube_transcript_notes-0.3.0.tar.gz -
Subject digest:
8e30303348308ef4f4cb986afdadcc8a76da8ad8654c534707caa77feb2ec458 - Sigstore transparency entry: 2458908536
- Sigstore integration time:
-
Permalink:
ekelly95/youtube-transcript-notes@2c7612ce7adbd8cfa2d2bd49ae5492b65fd993ff -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/ekelly95
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2c7612ce7adbd8cfa2d2bd49ae5492b65fd993ff -
Trigger Event:
push
-
Statement type:
File details
Details for the file youtube_transcript_notes-0.3.0-py3-none-any.whl.
File metadata
- Download URL: youtube_transcript_notes-0.3.0-py3-none-any.whl
- Upload date:
- Size: 126.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e58682eb1391929531539e5842cc1b134c2e310b9108f864d86b62666420826f
|
|
| MD5 |
39bf899e659664514bb2b14bb611741d
|
|
| BLAKE2b-256 |
9e1cf6e5ab791e642b1b8cde24f6c3a51413510139b3f1e6fb6781ab2ce29294
|
Provenance
The following attestation bundles were made for youtube_transcript_notes-0.3.0-py3-none-any.whl:
Publisher:
release.yml on ekelly95/youtube-transcript-notes
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
youtube_transcript_notes-0.3.0-py3-none-any.whl -
Subject digest:
e58682eb1391929531539e5842cc1b134c2e310b9108f864d86b62666420826f - Sigstore transparency entry: 2458908697
- Sigstore integration time:
-
Permalink:
ekelly95/youtube-transcript-notes@2c7612ce7adbd8cfa2d2bd49ae5492b65fd993ff -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/ekelly95
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2c7612ce7adbd8cfa2d2bd49ae5492b65fd993ff -
Trigger Event:
push
-
Statement type: