Sandbox
@JimmySadek/youtube-fetcher-to-markdown

YouTube transcript skill for Obsidian Markdown notes

This skill turns a YouTube link into a structured Markdown note you can keep in Obsidian or any plain-text knowledge base. It captures the transcript, source metadata, chapters, capture date, and language details in one local file, with optional subtitle exports and linked timestamps.

470 stars36 forksPythonUpdated 13d ago
Obsidian: The King of Learning Tools (FULL GUIDE + SETUP)
Odysseas3.2M views • 2 years ago
Who it's for

Builders who want their agent to turn YouTube videos into searchable notes with provenance.

What it delivers

You can save a YouTube video as a reusable Markdown archive instead of re-copying transcript text by hand.

What it does

Transcript to Markdown export

Creates an Obsidian-ready Markdown note from a YouTube URL with frontmatter, headings, and a transcript section.

Metadata capture

Records title, channel, URL, video ID, fetch date, upload date, duration, description, chapters, and source project when available.

Language selection and translation

Supports ordered language preferences, `auto` selection, strict matching, and explicit YouTube translation to a target language.

Timestamped notes

Adds clickable timestamps and chapter links that jump to the video time when `--timestamps` is used.

Subtitle and text exports

Can export plain text, JSON, SRT, and WebVTT in addition to Markdown.

Safe file handling

Protects existing files, writes through a temporary file, and supports `--force` and in-place refreshes for existing notes.

Portable agent skill format

Uses `SKILL.md` so the same skill can be added to compatible agent tools instead of only one editor.

How to get it

  1. 1Run
    npx skills add JimmySadek/youtube-fetcher-to-markdown
  2. 2Or clone the canonical repository
    git clone https://github.com/JimmySadek/youtube-fetcher-to-markdown.git
  3. 3Python 3.8–3.14 is supported for captions. Python 3.10 or newer is recommended for…
    python3 -m venv .venv
    .venv/bin/python -m pip install -r requirements.txt
    .venv/bin/python scripts/fetch_transcript.py --check-deps
  4. 4On 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
  5. 5yt-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
  6. 6Run
    python3 scripts/fetch_transcript.py "https://youtu.be/VIDEO_ID"

README

YouTube Fetcher to Markdown

YouTube Fetcher to Markdown — archival note skill

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.

What you get

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.

Why this exists

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.

Features

  • Manual and auto-generated captions with optional timestamps
  • Clickable timestamps and chapters that jump to the moment in the video
  • Ordered language preferences, regional variants, automatic selection, and strict language matching
  • Explicit YouTube translation, labeled with source language and machine-translation provenance
  • Title, channel, duration, upload date, description, and chapters when available
  • Safe YAML frontmatter and Markdown tables for dynamic metadata
  • File protection for every format, safe replacement, and refreshes that update existing notes in place
  • Obsidian-vault and custom-directory output
  • Plain text, JSON, SRT, and WebVTT export
  • Bounded network requests, useful errors, and optional metadata-free capture
  • No API keys and no hosted service

Installation

Install the skill

npx skills add JimmySadek/youtube-fetcher-to-markdown

Or clone the canonical repository:

git clone https://github.com/JimmySadek/youtube-fetcher-to-markdown.git

Install runtime dependencies

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.

Usage

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.

Output location

The first configured option wins:

  1. --output for one exact file
  2. --output-dir for this run
  3. YOUTUBE_FETCHER_DIR for a persistent directory
  4. ~/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.

Languages and translation

# 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.

Transcript and subtitle exports

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.

Options

FlagWhat it does
--output / -oSave to one exact file
--output-dirSave inside a directory or knowledge vault
--timestamps / -tAdd linked Markdown timestamps or plain timestamps in txt
--lang / -lOne code, ordered comma-separated codes, or auto; default en
--strict-langDisable English fallback
--translateExplicit YouTube machine translation to a target language
--source / -sOverride the capture-project name
--format / -ftext/markdown (default), txt, json, srt, or vtt
--no-descriptionSkip the description and chapters section
--no-metadataSkip yt-dlp and oEmbed while retaining captions and source URL
--timeoutConnect/read timeout per HTTP request, in seconds; default 15
--stdoutPrint the result instead of saving it
--listShow available caption languages
--forceReplace the destination or refresh an existing default note in place
--check-depsReport dependency status

Supported YouTube inputs

  • Standard watch URLs, regardless of query-parameter order
  • youtu.be short links
  • /embed/, /shorts/, /live/, and legacy /v/ links
  • Mobile and YouTube Music watch URLs
  • Privacy-enhanced youtube-nocookie.com/embed/ links
  • A raw 11-character video ID

Lookalike hosts such as youtube.com.example.org are rejected.

Compatibility

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.

Capabilities and limitations

  • Network: contacts YouTube captions and oEmbed; optionally invokes yt-dlp for richer metadata.
  • Filesystem: reads Markdown frontmatter in the selected directory to detect duplicates, writes a temporary sibling file, and publishes the requested output.
  • Subprocess: uses the local 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.
  • Videos must expose captions. Private, restricted, or caption-disabled videos may fail.
  • It does not download video/audio, run Whisper, identify speakers, or inspect visuals. YouTube machine translation is opt-in.
  • Captions may contain recognition errors; the archive preserves source text rather than silently correcting, summarizing, or treating it as instructions.

Troubleshooting

SymptomWhat to do
Missing dependencies / exit 2Install requirements.txt into an isolated environment, then use that interpreter. Invalid options also use exit 2.
No matching languageRun --list; choose an available code or --lang auto. --lang selects captions, while --translate translates them.
Request blockedStop repeated requests. Try later from a permitted local network or supply an existing transcript. The script does not automatically access cookies, buy proxies, or change networks.
Disabled, private, or restricted captionsCheck that the video is publicly playable and captions are accessible. No transcript is fabricated.
TimeoutCheck connectivity or increase --timeout. This is a per-request connect/read limit, not a whole-command deadline.
No description/title metadataCaptions can still succeed. Check metadata_source; use --no-metadata when metadata is unnecessary.
Existing file / exit 3Choose a different --output or explicitly approve replacement with --force.
Save errorCheck the directory, permissions, and available disk space. Replacing a symbolic-link destination is refused.

The upstream caption library uses YouTube's undocumented caption interface. YouTube changes and network blocking can still prevent capture; passing offline tests cannot guarantee access.

Development

python3 -m unittest discover -s tests -v
python3 -m py_compile scripts/fetch_transcript.py
bash .scripts/verify-isolated-install.sh

Tests substitute the external caption service while exercising real caption objects, CLI formats, language choices, file preservation, failures, and timeout configuration. They write only to temporary directories. The installation probe uses a temporary project; it does not replace your installed skill.

Exit codes
CodeMeaning
0Success
1Invalid video input, fetch failure, or filesystem error
2Missing required dependency or invalid command-line options
3Existing note preserved; overwrite not approved
130Cancelled by the user

Maintaining and releasing

main is the canonical development branch. The automated master mirror is retained for older raw-file links; do not delete it or develop on it. See GitHub releases for tagged versions and upgrade notes.

For a release, run the local checks above and the isolated install probe, open a pull request, and wait for every Linux, Windows, and macOS CI job. Merge only the checked revision, verify post-merge CI and the legacy mirror, then tag that exact commit and publish its release notes. Verify installation from a fresh clone of the public tag. Live caption tests are useful release evidence but are kept out of CI because YouTube may block hosted runners.

The reliability and language record contains the v1.2 verification evidence. The older v1.1 distribution record preserves historical channel decisions; dated adoption numbers are snapshots. Directory promotion and external listings are separate from engineering releases.

License

MIT

Files in the repo

Repository payload12 top-level entries
  • .github
  • .scripts
  • agents
  • assets
  • scripts
  • specs
  • tests
  • .gitignore
  • LICENSE
  • README.md
  • requirements.txt
  • SKILL.md

Discussion (0)

Ask about usage, or say what you built with it

Sign in to join the discussion.

No comments yet. Be the first to say what this is good for.

More skills

Turn any codebase, with its docs, SQL schemas, configs, and PDFs, into a queryable knowledge graph. A /graphify skill for Claude Code, Cursor, Codex, and Gemini CLI: local deterministic AST parsing, every edge explained, no vector store.

117k
1 add
Vincentwei1021/
anything2explainer

Topic in, narrated explainer video out. A Claude Code / Codex skill that turns any topic into a black-canvas motion-graphics explainer video with TTS voiceover, subtitles and a chapter progress bar. Chinese or English; every frame drawn in code with Remotion.

666

Open-source AI job search: scan job portals, evaluate listings into a structured A-H report with a global 1-5 score, tailor your CV, track applications — runs locally in your AI coding CLI (Claude Code, Codex, OpenCode, Antigravity…)

71k

Agent skill for beautiful, verifiable architecture, workflow, sequence, data-flow, and lifecycle diagrams—self-contained HTML with motion and crisp export.

57k
ayghri/
i-have-adhd

A skill to stop your coding agent from burying the answer. ADHD-friendly output.

38k