Dump a YouTube video's transcript as Markdown.
Fetches the transcript (no API key needed), looks up the video title, and emits a
Markdown document with a metadata header. By default the transcript is rendered as
flowing paragraphs; pass --timestamps for one timestamped line per snippet, or
--claude to reflow the raw transcript into readable prose with claude -p.
uv sync # dev setup inside the repo
# or as a tool:
uv tool install .yt2md "https://www.youtube.com/watch?v=jNQXAC9IVRw" # flowing paragraphs to stdout
yt2md jNQXAC9IVRw -o transcript.md # bare ID, write to file
yt2md "https://youtu.be/jNQXAC9IVRw" --timestamps # one timestamped line per snippet
yt2md "https://youtu.be/jNQXAC9IVRw" --claude # reflow into prose via claude -p
yt2md "https://www.youtube.com/watch?v=..." -l it,en # language preference orderAccepted inputs: bare 11-character video IDs and watch, youtu.be, shorts,
embed, live URLs on any youtube.com subdomain (www., m., music., ...).
| Flag | Meaning |
|---|---|
-l, --languages |
Comma-separated language codes in order of preference (default: en) |
--timestamps |
One timestamped line per snippet instead of the default flowing paragraphs |
--claude |
Consolidate the transcript into readable prose via claude -p (see below) |
-o, --output |
Write to a file instead of stdout |
The default paragraph mode preserves the raw transcript wording, broken every
~minute of video. --claude instead pipes the transcript into claude -p, which
restores punctuation, joins fragments into complete sentences, and groups them into
thematic paragraphs, without summarising or altering meaning. It requires the
claude CLI on your PATH; if it is missing or fails, yt2md prints a
yt2md: warning: ... line and falls back to the raw paragraphs, so the run still
succeeds. Because the reflowed prose has no per-snippet positions, --claude
overrides --timestamps when it succeeds.
The document header is the same in every mode; only the ## Transcript body
differs. With --timestamps:
# Me at the zoo
- **Source:** <https://www.youtube.com/watch?v=jNQXAC9IVRw>
- **Language:** English (en, manual)
## Transcript
**[00:01]** All right, so here we are, in front of the elephantsWithout it (the default), the body is flowing paragraphs of the same text; with
--claude, it is that text reflowed into punctuated prose.
Exit code is 0 on success, 1 on bad input or when no transcript is available
(disabled subtitles, unavailable video, no matching language), and 2 for a
usage error (missing argument, empty --languages).
A Claude Code skill turns yt2md into a
one-line /yt2md command that fetches a transcript and then acts on it with a
free-form prompt:
/yt2md <video-url-or-id> [prompt]
/yt2md jNQXAC9IVRw— no prompt: a structured summary (TL;DR + key points)./yt2md jNQXAC9IVRw draft a LinkedIn post— the prompt drives the output./yt2md jNQXAC9IVRw where does he mention the trunks?— position-style prompts fetch with--timestampsso answers can cite[MM:SS].
The skill runs a globally installed yt2md if present, otherwise it falls back
to uvx --from git+https://github.com/mauromedda/yt2md yt2md, so no manual
install is required. Everything is grounded in the transcript; the skill never
answers from the title alone.
Install it by copying skill/SKILL.md to
~/.claude/skills/yt2md/SKILL.md (personal, available everywhere) or to
.claude/skills/yt2md/SKILL.md inside a project.
Adversarial testing shaped the error handling; the tool aims to fail cleanly, never with a stack trace, on the following:
- Environment errors (unwritable
-opath, output to a directory, a closed pipe such as| head, a non-UTF-8 terminal) print a singleyt2md: error: ...line. - Library errors surface their one-line cause (e.g.
Subtitles are disabled for this video) rather than the multi-line boilerplate. - Hostile transcript or video-title content (embedded newlines, markdown metacharacters) is neutralized so it cannot inject fake headers or metadata into the document.
- Non-finite or negative snippet timestamps render as
00:00. - A bare video ID beginning with
-is accepted (e.g.yt2md -wtIMTCHWuI).
This is not legal advice. Read this before you use
yt2md.
yt2md fetches transcripts through the youtube-transcript-api library,
which reads YouTube's internal (undocumented) caption endpoints rather than the
official YouTube Data API. That has consequences you should understand:
- YouTube Terms of Service. YouTube's ToS prohibit accessing content by automated means except through the API or the embeddable player. Fetching captions this way is very likely a violation of those terms. The practical enforcement is technical and account-level (rate limiting, IP blocks, or a ban on the Google account associated with the requests), not criminal.
- Copyright. A transcript is the spoken content of a copyrighted work.
yt2mddownloads it locally and never hosts, republishes, or redistributes anything, which keeps personal use low-harm. Redistributing, republishing, or feeding the output into a product or model is a materially different and higher-risk activity that this tool takes no position on and does not license you to do. - Scope of intended use.
yt2mdis meant as a personal, local utility for content you are authorised to access (e.g. your own videos, or videos whose captions you are entitled to use). Using it to bulk-harvest third-party content, or as the backend of a hosted service, is outside its intended scope.
The title lookup uses YouTube's public, documented oEmbed endpoint and
is not subject to the concerns above. The --claude reflow runs entirely on your
machine and is governed only by your own agreement with Anthropic.
You are responsible for how you use this tool. If ToS compliance matters for
your use case, the sanctioned path is the official YouTube Data API v3
(captions.download), which only permits caption downloads for videos you own or
where the owner has enabled third-party contributions.
yt2md is an independent project and is not affiliated with, endorsed by, or
sponsored by YouTube or Google LLC. "YouTube" is a trademark of Google LLC.
uv run pytest # tests
uv run ruff check . && uv run ruff format . # lint + format
uvx ty check # type checkMIT © Mauro Medda