earwig (v.) — to listen in on a conversation.
A local command-line tool that turns a YouTube podcast URL into a verbatim, per-paragraph-timestamped, speaker-labeled Markdown transcript.
earwig "https://youtube.com/watch?v=..."
# → writes ./the-podcast-episode-title.md# How I Built a $40K/Month Vending Machine Route
*The Small Bets Podcast · 32:10 · [source](https://youtube.com/watch?v=EXAMPLE)*
---
**Dana Alvarez** `[00:00]`
Welcome back to the show. Today I'm sitting down with someone who turned a
boring, overlooked business into a genuinely passive income stream...
**Marcus Webb** `[00:12]`
Thanks for having me. The whole thing started because I wanted something that
didn't need me glued to a laptop all day...The example above is fictional. See
examples/example-transcript.mdfor a fuller synthetic sample of earwig's output.
Status: beta (
0.x) — pre-1.0, so flags and output may still change. Release history lives inCHANGELOG.md.
Most transcript tools give you an undifferentiated wall of text. earwig is built for conversations — it separates who is speaking and labels them with real names, so the output reads like a script. It's aimed at podcasts and interviews.
- Runs locally. Audio download, transcription, and speaker diarization all happen on your machine. Speaker naming is off by default (no network call at all). The optional
claudeandlocalnamers send only a small text slice (never the audio) toclaude -por a local Ollama server, respectively. - Speaker diarization via whisperX +
pyannote. - Opt-in speaker naming. By default speakers keep their anonymous
SPEAKER_xxlabels. Opt into names with--namer manual(type them yourself as you review) or theclaude/localLLM namers, which infer names from intros and context. You review and correct names before anything is written (or--autoto skip the review). - Verbatim output. Transcript text is never paraphrased — only speaker labels change.
- Per-paragraph timestamps you can seek to.
YouTube URL
→ yt-dlp download audio + metadata
→ whisperX transcribe + word timestamps + speaker diarization (SPEAKER_00/01/…)
→ merge group segments into readable, timestamped paragraphs
→ namer optionally label the anonymous speakers (off by default;
manual/claude/local opt-in; review or --auto)
→ render write speaker-labeled Markdown
- Python 3.11+
- ffmpeg on your
PATH(brew install ffmpeg/apt install ffmpeg) - A Hugging Face token (free) for the gated diarization models — see Setup
- Nothing else for speaker naming — it's off by default, and
--namer manual(type names yourself) needs no dependencies. Optionally, the Claude CLI (claude) on yourPATHenables--namer claude, and a local Ollama server onlocalhost:11434enables--namer local. See Speaker naming.
The one-line installer sets everything up — it installs earwig with uv (or pipx), warns you if ffmpeg is missing, and launches earwig setup:
curl -fsSL https://raw.githubusercontent.com/gunn4r/earwig/main/install.sh | sh
It's safe to re-run (it upgrades in place) and never uses sudo. On a machine with neither uv nor pipx it tells you how to get uv rather than installing anything behind your back.
Prefer to run it yourself? Install straight from git:
uv tool install git+https://github.com/gunn4r/earwig@main # or: pipx install git+https://github.com/gunn4r/earwig@main
(From a local checkout, uv tool install . works too.)
earwig update upgrades in place — it detects how earwig was installed (uv tool, pipx, or pip) and runs the matching upgrade, pulling the latest build from main. If you're running from a source checkout, it tells you to git pull instead. Check what you're on with earwig --version.
To remove it, use the same tool that installed it — e.g. uv tool uninstall earwig or pipx uninstall earwig.
- macOS (Apple Silicon): confirmed working end-to-end (Python 3.12).
- macOS (Intel): unverified, but expected to work — it uses the same wheels.
- Linux: the fast unit suite runs in CI on Python 3.11 and 3.12. The full torch/whisperX/pyannote pipeline ships Linux wheels and is expected to work, but is not yet verified end-to-end.
- Windows: unverified and likely rough (torch and ffmpeg pathing). Use WSL2 and follow the Linux path.
- Python: 3.11 and 3.12 are tested (the CI matrix). 3.13 is not yet supported (dependency wheel availability); 3.12 is the safe pick.
- Hardware / GPU: CPU works but is slow — the default
large-v3model runs roughly 10–30+ minutes per hour of audio. Use--model baseor--model mediumto trade some accuracy for a much faster run. On an NVIDIA GPU,--device cudaspeeds transcription substantially (it usesfloat16). Apple Silicon transcribes on CPU regardless: the underlying faster-whisper/CTranslate2 backend has no Apple-GPU support, which is why there is nompsoption. The--device cudapath is built against whisperX's documented API but is not yet verified on CUDA hardware.
Run the setup wizard — it explains what's needed, opens the right pages, stores your token, and verifies everything:
earwig setup
It walks you through creating a free Hugging Face token and accepting the licenses for the two gated models that speaker diarization needs, saves the token to ~/.config/earwig/env (mode 0600, never printed), records your default speaker namer, and then checks that ffmpeg is installed, your token works, both model licenses are accepted, and your namer is available. Every failed check tells you exactly what to do about it.
earwig setup --namer off skips the namer question, and --no-open-browser stops it from opening pages for you.
earwig reads settings from, in order of precedence: the environment, a .env file in the current directory, then ~/.config/earwig/env (or $XDG_CONFIG_HOME/earwig/env). earwig setup writes the last one, so an installed earwig works from any directory; a .env in a checkout is handy for development and overrides it.
If you'd rather not use the wizard: create a Hugging Face token, click "Agree and access repository" on both
- https://huggingface.co/pyannote/speaker-diarization-community-1
- https://huggingface.co/pyannote/segmentation-3.0
then either export the token or put it in one of the files above:
export HF_TOKEN=hf_...
# or: echo 'HF_TOKEN=hf_...' >> .env
ffmpeg must also be on your PATH (macOS: brew install ffmpeg; Debian/Ubuntu: sudo apt install ffmpeg).
earwig "https://youtube.com/watch?v=..." # default: raw SPEAKER_xx labels, writes .md
earwig "<url>" --namer manual # type each speaker's name as you review
earwig "<url>" --namer claude # use the Claude CLI to infer names
earwig "<url>" --namer local # use a local Ollama model to infer names
earwig "<url>" --namer claude --auto # infer names, skip the review step
earwig "<url>" --namer off # explicit no-naming (same as default)
earwig "<url>" --model medium --output ep.md # faster model, explicit output path
earwig "<url>" --device cuda # transcribe on an NVIDIA GPU (much faster)
earwig --version # print the installed version
earwig update # upgrade earwig to the latest build
By default earwig writes the transcript non-interactively with anonymous SPEAKER_xx labels. When you opt into a namer (manual, claude, or local), review mode shows a sample line for each detected speaker and its guessed name; press Enter to accept or type a correction (add --auto to skip the review). Assigning the same name to two speaker IDs merges them — handy when diarization over-splits a speaker.
The default model is large-v3 (most accurate, slow on CPU). Use --model base or --model medium for much faster runs at some cost to transcription quality. A GPU helps a lot for the larger models.
Naming is opt-in. earwig selects a strategy with --namer {off,manual,claude,local}:
off(the default) — skip naming and keep the rawSPEAKER_xxlabels, non-interactively. Nothing is guessed, so a wrong name can never ship.manual— no inference: earwig prompts you to type each speaker's name as you review the samples. Zero dependencies, fully offline.claude— shells out to the Claude CLI (claude -p) with a small text slice from the transcript. Requires theclaudeCLI on yourPATH, authenticated.local— sends the same kind of prompt to a local Ollama server (http://localhost:11434). Requires Ollama running with a model pulled.
If --namer is omitted, earwig checks the EARWIG_NAMER environment variable, then falls back to off. Both claude and local degrade gracefully to raw speaker ids if the underlying service is unavailable at runtime — they never crash the run. earwig setup records your choice for you, so you don't have to set EARWIG_NAMER by hand.
GatedRepoError/ 403 on a pyannote model — you haven't accepted that model's terms. Open the model page (see Setup) and click "Agree and access repository".torchcodec/libtorchcodecwarning about ffmpeg versions — harmless. whisperX falls back to another audio backend; transcription and diarization still run.- A speaker stays
SPEAKER_xx— expected under the default--namer off. It also happens when aclaude/localnamer couldn't infer that person's name (e.g. a host who never says their own name) or the service was unreachable and degraded to raw labels. To label speakers, re-run with--namer manual(type them) or--namer claude/local; in review mode just type the name (--autokeeps whatever was guessed).
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest -q # fast unit suite (no token/network needed)
pytest -m slow # end-to-end test (needs HF_TOKEN, network, ffmpeg)
earwig's own code is MIT licensed (see LICENSE). The pyannote diarization models it downloads have their own licenses and gating terms, which you accept during setup — those govern your use of the models, separately from this tool's license.
Loading those models runs code and deserialized weights published by their authors on Hugging Face — the same trust you extend to any Python package you install. earwig only ever fetches the official pyannote repositories named in setup; if you point it at other model repositories, vet them as you would any third-party code.