pi-media-transcribe
Local-first transcription to structured Markdown notes: transcribe YouTube, podcasts and meetings offline with Whisper or Qwen3-ASR, then optionally have an LLM proofread, segment, summarize and file into any notes folder or Obsidian vault.
Package details
Install pi-media-transcribe from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-media-transcribe- Package
pi-media-transcribe- Version
0.2.5- Published
- Nov 4, 2026
- Downloads
- 680/mo · 680/wk
- Author
- cyub
- License
- MIT
- Types
- skill
- Size
- 110.3 KB
- Dependencies
- 0 dependencies · 0 peers
Pi manifest JSON
{
"skills": [
"./skills"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
A Pi skill package that turns a YouTube video, podcast, webinar, meeting recording or any
local media file into a note you would actually keep. Transcription runs fully offline on your own
hardware (Whisper, Qwen3-ASR, Parakeet, SenseVoice via
handy-transcribe) — no audio upload, no per-minute bill.
An LLM then proofreads the transcript, splits it into sections and writes the summary, but only if you
want it: --raw skips the model entirely.
- Local-first: audio never leaves the machine; the default model runs on CPU and needs no API key.
- Three separable modes: transcript only, verbatim note, or polished note — pick per job.
- Any notes folder: Obsidian vaults, plain Markdown folders, anything. Frontmatter included.
- Nine heading languages: English, 中文, 日本語, 한국어, Русский, Deutsch, Español, Français, Português, detected from the transcript's script.
- Transcription model:
model/quantpin the ASR model.transcribe.shinjects them when you do not pass--model, so a machine keeps one stable default instead of guessing per run. The first run downloads it (Qwen3-ASR-0.6BQ8_0 ≈ 850 MB);scripts/doctor.shshows what would be used and whether it is already cached. - Long recordings: one request for a normal talk, chunk-and-merge for a three-hour podcast.
- Never loses work: if the LLM step fails, the raw transcript is still written as a note.
- Bring your own model: OpenAI, OpenRouter, DeepSeek, or a local Ollama / LM Studio server.
Demo
$ scripts/to-obsidian.sh "https://www.youtube.com/watch?v=Uz7K757psEU"
==> transcribing
==> correcting, segmenting, summarizing, filing
LLM: deepseek-chat via https://api.deepseek.com (note language: zh, 12 folders offered)
saved: 资料/02-人工智能/AI智能体/高阶Graph RAG:从向量检索困境到图谱增强推理.md
A 16-minute Mandarin tech talk becomes an 18.8 KB note — abstract plus 17 sections — with the
transcription itself taking 49 s on an Apple M1 Pro (≈20× realtime, Qwen3-ASR-0.6B).
Install
pi install npm:pi-media-transcribe
pi install ./pi-media-transcribe # or from a local checkout
Requirements:
cargo install handy-transcribe # the offline ASR engine (or: cargo binstall handy-transcribe)
brew install yt-dlp ffmpeg # only needed for URL input
# python3 3.9+ is used for the note stage
Check your setup — the doctor prints exactly what is missing and how to fix it:
skills/media-transcribe/scripts/doctor.sh
Use it
Ask Pi:
"Transcribe this video and save it to my notes: https://www.youtube.com/watch?v=..."
Or drive the scripts directly:
# transcript only — cached, so repeat runs are instant
scripts/transcribe.sh "https://www.youtube.com/watch?v=..."
scripts/transcribe.sh ./meeting.m4a -o meeting.txt
# transcript + polished note (LLM)
scripts/to-obsidian.sh "https://www.youtube.com/watch?v=..."
scripts/to-obsidian.sh ./recording.m4a --vault ~/Documents/Obsidian\ Vault --base Resources
scripts/to-obsidian.sh ./talk.wav --model Qwen3-ASR-1.7B -l zh
# transcript + verbatim note, no LLM at all (offline, free)
scripts/to-obsidian.sh ./lecture.mp4 --raw
scripts/to-obsidian.sh ./meeting.m4a --raw --vault ~/notes/inbox
Pass-through options for handy-transcribe: -m/--model, -q/--quant, -l/--language,
--translate, --device, --no-chunk, --keep-fillers, --custom-words, --json, -o.
Note-level flags: --raw, --vault, --base, --locale, --overwrite.
If the scripts arrive without the executable bit (some package managers drop it), either
chmod +x scripts/*.shor invoke them asbash scripts/transcribe.sh ….
Configuration
Optional. Copy config.example.json to
~/.config/pi-media-transcribe/config.json:
{
"vault": "~/Documents/Obsidian Vault",
"baseSubdir": "Resources",
"locale": "auto",
"tags": ["auto-transcribed"],
"model": "Qwen3-ASR-0.6B",
"quant": "Q8_0",
"llm": {
"baseUrl": "https://api.deepseek.com",
"model": "deepseek-chat",
"apiKeyEnv": "DEEPSEEK_API_KEY",
"maxDirectChars": 48000,
"chunkChars": 24000
},
"frontmatter": { "createdField": "created_at", "modifiedField": "last_modify_at" },
"headings": { "source": "Source", "summary": "Core ideas", "body": "Transcript" }
}
- Notes folder:
--vault→$OBSIDIAN_VAULT→ config → the vault Obsidian itself has open →~/Documents/Obsidian Vault. Any directory works; Obsidian is just the default. - Language: the body always follows the transcript. Headings are localized for
en,zh,ja,ko,ru,de,es,fr,ptand detected from the script (kana → Japanese, hangul → Korean, CJK → Chinese, Cyrillic → Russian). Other scripts keep English labels; override withheadings. - Other LLMs: anything OpenAI-compatible.
"llm": { "provider": "openai", "model": "gpt-5-mini" }
"llm": { "provider": "openrouter", "model": "anthropic/claude-sonnet-5" }
"llm": { "baseUrl": "http://localhost:11434/v1", "model": "qwen3:8b" }
llm.provider picks the endpoint (deepseek, openai, openrouter) together with the matching
$PROVIDER_API_KEY variable. Keys are provider-bound: a key you gave Pi is only used when the
endpoint belongs to the same provider, so an OpenAI or OpenRouter credential is never posted to
DeepSeek (or vice versa) — if nothing matches, the run stops and tells you why instead of reusing a
foreign credential. A local server (Ollama, LM Studio, vLLM) keeps transcription and polishing on
the machine and needs no key at all.
- Long recordings: under
llm.maxDirectChars(default 48 000 chars) one request; beyond that,llm.chunkCharschunks (default 24 000) are compressed and merged. - Safety: the LLM may only choose from the offered folders, the final path is validated against
the notes root, and existing notes are never overwritten (a numeric suffix is added;
--overwriteforces it).
What you get
---
created_at: 2026/10/31 22:14
last_modify_at: 2026/10/31 22:14
tags:
- youtube
- auto-transcribed
- RAG
- knowledge-graph
---
Source: https://www.youtube.com/watch?v=...
## Core ideas
<150–300 word abstract>
---
## Transcript
### Why vector search hits a ceiling
<proofread, segmented body>
How it works
URL ──yt-dlp──▶ audio ──▶ handy-transcribe (symphonia → Silero VAD → transcribe.cpp)
│
transcript (cached)
│
LLM (optional): proofread → segment → summarize → pick folder/tags
│
Markdown note (frontmatter + summary + body)
Troubleshooting
| Symptom | Fix |
|---|---|
yt-dlp fails or times out |
Configure a proxy (export https_proxy=http://127.0.0.1:7890), or pass a local file |
| Model download stalls | Models come from Hugging Face; point HF_ENDPOINT at a mirror or internal proxy, e.g. https://your-hub-mirror.example (SHA-256 still verified) |
handy-transcribe not found |
scripts/doctor.sh, or point HANDY_TRANSCRIBE_BIN at the binary |
| HTTP 401/402 from the LLM | Wrong/missing key or no credit — check llm.apiKeyEnv, or use --raw |
| HTTP 413 / context overflow | Lower llm.maxDirectChars and llm.chunkChars |
| Note landed at the folder root | No candidate folders: set baseSubdir or create one |
| LLM failed but you got a note | That's the built-in fallback (raw transcript). Rerun to retry polishing |
Limits
- Audio only — no slides, on-screen text, or frame understanding.
- The polished mode costs tokens: one request for a normal recording, one per chunk plus a merge for
very long ones.
--rawcosts nothing and stays offline. --rawnotes are the transcript verbatim, ASR mistakes included.- bash + Python 3.9+ on macOS/Linux; on Windows use WSL or Git Bash.
License
MIT — see LICENSE. Built on Handy and transcribe.cpp; the ASR CLI it drives is handy-transcribe.