YouTube video in, structured archival Markdown note out. Capture the transcript, creator metadata, description, chapters, actual caption language, and provenance in one Obsidian-ready file—without an API key.
$ npx -y skills add JimmySadek/youtube-fetcher-to-markdown --agent claude-code
Repo: JimmySadek/youtube-fetcher-to-markdown
What's inside
YouTube video in, structured archival Markdown note out. Capture the transcript, creator metadata, description, chapters, actual caption language, and provenance in one Obsidian-ready file—without an API key.
npx skills add JimmySadek/youtube-fetcher-to-markdown
Read the v1.2.0 release notes for the new language options, exports, and safer overwrite behavior.
Paste a YouTube link and receive a file such as:
~/yt_transcripts/2026-03-04_obsidian-the-king-of-learning-tools_[hSTy_BInQs8].md
---
title: "Obsidian: The King of Learning Tools (FULL GUIDE + SETUP)"
channel: "Odysseas"
url: "https://www.youtube.com/watch?v=hSTy_BInQs8"
video_id: "hSTy_BInQs8"
fetched: "2026-03-04"
source_project: "my-project"
language: "en"
caption_type: "manual"
duration: "36m 26s"
upload_date: "2024-04-24"
tags:
- yt-transcript
---
# Obsidian: The King of Learning Tools (FULL GUIDE + SETUP)
## Video Details
| Field | Value |
|----------|-------|
| URL | https://www.youtube.com/watch?v=hSTy_BInQs8 |
| Channel | Odysseas |
| Duration | 36m 26s |
| Uploaded | 2024-04-24 |
| Fetched | 2026-03-04 |
| Source | my-project |
| Language | en (manual) |
## Video Description
The creator's description, links, and chapter markers...
## Transcript
The complete caption text...
The YAML frontmatter makes a collection queryable through tools such as Dataview, while the Markdown remains portable to Logseq, other knowledge bases, and plain text workflows.
Most transcript extractors stop at raw caption text. An archival knowledge note also needs the source URL, creator, capture date, actual language, description, chapters, and a predictable filename. YouTube Fetcher keeps that complete record in one local file.
npx skills add JimmySadek/youtube-fetcher-to-markdown
Or clone the canonical repository:
git clone https://github.com/JimmySadek/youtube-fetcher-to-markdown.git
Python 3.8–3.14 is supported for captions. Python 3.10 or newer is recommended
for current optional yt-dlp releases. From the cloned or installed skill
directory, use an isolated environment so your system Python stays unchanged:
python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt
.venv/bin/python scripts/fetch_transcript.py --check-deps
On Windows PowerShell:
py -m venv .venv
.venv\Scripts\python.exe -m pip install -r requirements.txt
.venv\Scripts\python.exe scripts\fetch_transcript.py --check-deps
Activate that environment before using the python3 examples below (source .venv/bin/activate on macOS/Linux), or use the full interpreter path each time.
An agent should also use that interpreter. If your skill installation is read-only,
create the environment in a writable location and pass the full path to
requirements.txt.
yt-dlp is optional for descriptions, chapters, duration, and upload dates:
.venv/bin/python -m pip install yt-dlp
# Windows: .venv\Scripts\python.exe -m pip install yt-dlp
Put its executable on PATH by activating the environment. Without it, oEmbed
still supplies title and channel when accessible. The script never installs
packages automatically. --no-metadata skips both metadata providers.
python3 scripts/fetch_transcript.py "https://youtu.be/VIDEO_ID"
An agent using the skill resolves scripts/fetch_transcript.py relative to its
installed SKILL.md; it does not depend on one fixed home-directory path.
The first configured option wins:
--output for one exact file--output-dir for this runYOUTUBE_FETCHER_DIR for a persistent directory~/yt_transcripts/ by default# Save this note to an Obsidian vault
python3 scripts/fetch_transcript.py URL --output-dir ~/Notes/MyVault
# Set a persistent default
export YOUTUBE_FETCHER_DIR=~/Notes/MyVault
python3 scripts/fetch_transcript.py URL
# Save to one exact file
python3 scripts/fetch_transcript.py URL --output ~/Notes/video.md
Every format preserves an existing destination and exits with code 3, before
making a network request when the destination is already known. This is the same
in terminals and agent sessions; there is no hidden interactive prompt. --force
replaces the chosen file completely, including any annotations. A default
Markdown refresh reuses the existing note's path even if its title or capture date
has changed. An explicit --output is honored independently of other notes for
the same video, so distinct files can hold different languages or versions.
Writes use a temporary file beside the destination. Where the filesystem supports hard links, a new file appears only once its UTF-8 content is complete. Other filesystems use exclusive creation: they still refuse to open an existing file for writing, but a new file can be visible during the write. Handled write failures remove that partial file; abrupt termination or disk failure can leave it behind. Forced refreshes replace a completed temporary file and preserve existing POSIX permission modes. New notes use normal file-creation permissions.
If another process creates the destination during a fetch, the non-force write
still refuses to overwrite it. --stdout prints only the result
and creates no file, even when output-path options are present; diagnostics go to
stderr.
# Prefer French, then German, with English as the final fallback
python3 scripts/fetch_transcript.py --lang fr,de -- URL
# Require Japanese captions; fail clearly if unavailable
python3 scripts/fetch_transcript.py --lang ja --strict-lang -- URL
# Capture available captions when you do not know their language
python3 scripts/fetch_transcript.py --lang auto -- URL
# Explicit YouTube machine translation of an available track into English
python3 scripts/fetch_transcript.py --lang auto --translate en -- URL
# Inspect caption tracks and supported translation targets
python3 scripts/fetch_transcript.py --list -- URL
Language preference outranks caption type. For each requested language, exact
codes are tried before regional variants (es can select es-MX); manual
captions win within that match. All requested languages precede English fallback.
--strict-lang disables that fallback, while still allowing regional variants.
The default remains --lang en for compatibility.
auto selects a manual track if available, otherwise a generated track, using
YouTube's track order for ties. It does not establish the original audio
language. Selection and fallback are reported to stderr and recorded in the note.
Translation happens only with --translate, requires support from YouTube, and
is never described as a human translation.
Markdown records requested_language, source_language, language (the actual
output language), caption_type (the original track's type), translated, and
translation_provider when applicable. metadata_source distinguishes yt-dlp,
oembed, unavailable, and deliberately skipped metadata. Existing frontmatter
keys remain compatible. JSON keeps its existing array of {text, start, duration}
objects; raw exports have no provenance wrapper, so retain stderr or use Markdown
when that context matters.
python3 scripts/fetch_transcript.py --stdout --timestamps -- URL
python3 scripts/fetch_transcript.py --format txt --stdout -- URL
python3 scripts/fetch_transcript.py --format json --output captions.json -- URL
python3 scripts/fetch_transcript.py --format srt -- URL
python3 scripts/fetch_transcript.py --format vtt -- URL
text and markdown both produce an archival Markdown note; txt produces plain
caption text. Timestamped Markdown and chapter lists link to the corresponding
video time. Raw exports skip metadata requests. A raw ID starting with - works
after --; put all options before that separator.
| Flag | What it does |
|---|---|
--output / -o | Save to one exact file |
--output-dir | Save inside a directory or knowledge vault |
--timestamps / -t | Add linked Markdown timestamps or plain timestamps in txt |
--lang / -l | One code, ordered comma-separated codes, or auto; default en |
--strict-lang | Disable English fallback |
--translate | Explicit YouTube machine translation to a target language |
--source / -s | Override the capture-project name |
--format / -f | text/markdown (default), txt, json, srt, or vtt |
--no-description | Skip the description and chapters section |
--no-metadata | Skip yt-dlp and oEmbed while retaining captions and source URL |
--timeout | Connect/read timeout per HTTP request, in seconds; default 15 |
--stdout | Print the result instead of saving it |
--list | Show available caption languages |
--force | Replace the destination or refresh an existing default note in place |
--check-deps | Report dependency status |
youtu.be short links/embed/, /shorts/, /live/, and legacy /v/ linksyoutube-nocookie.com/embed/ linksLookalike hosts such as youtube.com.example.org are rejected.
The repository follows the portable SKILL.md format. The same install command
works with Codex, Claude Code, Cursor, Windsurf, Gemini CLI, and other compatible
agents. Manual users can run the Python script directly.
yt-dlp
for richer metadata.yt-dlp executable with user configuration,
playlist expansion, caching, and downloads disabled. Its process timeout is
twice --timeout; individual HTTP requests each have their own timeout.| Symptom | What to do |
|---|---|
Missing dependencies / exit 2 | Install requirements.txt into an isolated environment, then use that interpreter. Invalid options also use exit 2. |
FAQ
youtube-fetcher-to-markdown is a Claude Code plugin with 1 hand-picked skill for content work, indexed on Flowy. Install it with the command on its page. It includes youtube-fetcher-to-markdown. Its skills do not fire on their own yet. Request auto-invocation to have Flowy route them as you prompt. Free and open source.
Is this plugin yours?
Claim it with GitHubSubmit a pluginPromote it