| .benchmarks | ||
| .gsd/gates | ||
| docs | ||
| examples | ||
| references | ||
| scripts | ||
| src | ||
| tests | ||
| .gitignore | ||
| .pre-commit-config.yaml | ||
| Containerfile | ||
| pyproject.toml | ||
| README.md | ||
| uv.lock | ||
whisper-transcribe
A command-line wrapper around WhisperX
that transcribes an audio URL (YouTube, podcast RSS, direct MP4, …) or a
local file into a segmented JSON transcript, with optional forced
alignment and optional speaker diarization via pyannote. The package
exposes a single transcribe console script (src/whisper_transcribe/cli.py,
pyproject.toml [project.scripts]) that accepts URL or local file
input and writes {language, duration_sec, segments[]} JSON either to
--out or to <input>.json next to the source.
Smoke test (2026-09-01)
End-to-end runs against real sources on an RTX 2060 (CUDA, float16,
align on, diarize off). Numbers are lifted from the canonical JSON
files under /tmp/whisperx-smoke/ — the smoke-test log was not
retained, so per-run transcribe time is n/a for this revision (see
docs/ follow-up). Word counts were computed as the sum of
len(seg.text.split()) over each segments[] array.
| Source | File | Duration | Model | Transcribe time | Words | Outcome |
|---|---|---|---|---|---|---|
| Big Buck Bunny clip | /tmp/whisperx-smoke/transcript.json |
~15:03 | base | n/a (log not retained) | 2,295 | OK |
| Big Buck Bunny clip (re-run) | /tmp/whisperx-smoke/jobs_transcript_small.json |
15:04 | small | n/a (log not retained) | 2,292 | OK |
| Lex Fridman (long-form, 3.4 h) | /tmp/whisperx-smoke/lex_long_transcript.json |
3:23:41 | base | n/a (log not retained) | 36,143 | OK |
| Spanish-language clip | /tmp/whisperx-smoke/spanish_transcript.json |
11:25 | base | n/a (log not retained) | 1,568 | OK (es) |
Hardware: RTX 2060, CUDA, base/small model, float16, align on,
diarize off. Run by the author on 2026-09-01.
The smoke-test log was not retained, so per-run wall-clock transcribe time is not recoverable from disk. The JSON files in
/tmp/whisperx-smoke/are the canonical record; re-running the smoke test is deferred until CI lands (see feature matrix below).
Quickstart
Five commands on a Linux box with ffmpeg on PATH and an NVIDIA
GPU. Steps 2 and 3 are only required if you intend to pass
--diarize.
# 1. Install the package (from the repo root).
pip install -e .
# 2. Set up Hugging Face access for diarization. This opens the required
# model pages when possible, explains both EULAs, and saves a Read token.
transcribe auth
# Manual alternative:
# https://huggingface.co/pyannote/segmentation-3.0
# https://huggingface.co/pyannote/speaker-diarization-3.1
huggingface-cli login
# 3. Export the token issued by `huggingface-cli login`.
export HF_TOKEN=hf_REPLACE_THIS
# 4. Transcribe a YouTube URL. (Any short public URL works.)
transcribe https://www.youtube.com/watch?v=dQw4w9WgXcQ --format chapters -o out.md
# 5. Inspect the output: chapters, detected language, duration.
head -20 out.md
Output formats
transcribe accepts --format <name> and renders the canonical
segment JSON into one of seven human-readable shapes. --format json
preserves the historical JSON output byte-for-byte; everything else is
pure-Python post-processing on the in-memory payload (no extra model
calls).
| Format | Default extension | Best for |
|---|---|---|
json (default) |
.json |
Round-tripping, machine consumption, the historical contract. |
chapters |
.md |
Timestamped chapter list grouped by silence gap + topic shift. |
summary |
.md |
5–10 sentence overview paragraph. |
chapter_summaries |
.md |
A # HH:MM:SS - label header plus a 3–6 sentence summary each. |
thread |
.txt |
Numbered tweet-style posts (1/n, 2/n, …), each ≤280 chars. |
blog |
.md |
Title, ## Introduction, one section per chapter, ## Takeaways. |
quotes |
.md |
Pull-quotes with — <speaker> at HH:MM:SS attribution. |
The underlying JSON (the --format json output and the payload every
other format is derived from) has the shape
{language, duration_sec, model, segments: [{start, end, text, speaker?}, …]}
(src/whisper_transcribe/cli.py). Exit codes: 0 ok, 2
usage, 10 network, 11 model not cached, 12 OOM, 13 unsupported
format (src/whisper_transcribe/cli.py).
Feature matrix
| Capability | Status |
|---|---|
| URL input (yt-dlp) | shipped (cli.py:71-110) |
| Local file input | shipped (cli.py:126-136) |
| Whisper transcription | shipped |
| Forced alignment | shipped, default on (cli.py:216, cli.py:321-330) |
| Speaker diarization | shipped behind --diarize, gated on HF_TOKEN + accepted pyannote EULAs (cli.py:332-344) |
| Output formats (`--format json | chapters |
| YouTube age-gate / cookie handling | tracked (see #11) |
| Batch / queue mode (RSS, episode range) | tracked (see #13) |
| Multi-language + code-switched audio | tracked (see #14) |
| GPU sizing + model compatibility matrix | tracked (see #10) |
| Per-source integration test matrix | tracked (see #9) |
| Hermes skill wrapper | tracked (see #4) |
| Rootless Podman Containerfile | tracked (see #5) |
Pre-cached model verification (doctor) |
tracked (see #1, "doctor subcommand" follow-up) |
Hardware requirements
VRAM tiers are derived from scripts/benchmark_gpu_sizing.py's
MODEL_PLAN list (scripts/benchmark_gpu_sizing.py:50-66) and the
RTX 2060 smoke test above. Compute-type is float16 unless noted.
| GPU tier | VRAM | Models that fit (float16) | Notes |
|---|---|---|---|
| Entry (4 GB) — verified | 4–6 GB | tiny, base (verified on RTX 2060, 6 GB) |
small/medium need --compute-type int8. RTX 2060 smoke test passed (see above). |
| Mid (8 GB) | 8 GB | small, medium |
large-v3 with --compute-type int8. |
| High (12 GB+) | 12 GB+ | large-v3, large-v3-turbo |
Diarize works on RTX 3060 12 GB and up. |
| Workstation (16–24 GB) | 16–24 GB | All models | All models + diarize without compromise. RTX 4080, A4000, A5000. |
| CPU only | n/a | tiny, base, small |
--device cpu --compute-type int8. No diarize. ~30–50× slower than CUDA. |
For authoritative per-model VRAM measurements, run
scripts/benchmark_gpu_sizing.py against a representative audio
clip.
Pyannote gating caveat
Heads up — pyannote models are gated. The diarization models (
pyannote/segmentation-3.0,pyannote/speaker-diarization-3.1) require you to (a) create a free Hugging Face account, (b) accept each model's EULA on its model page, and (c) pass aHF_TOKEN(env var or--hf-token) whose owner has done both. You can runtranscribe authfor the guided first-run setup. Until all three are true,--diarizewill fail with exit code11(ModelNotCachedError/DiarizationError,src/whisper_transcribe/errors.py). This is not a bug inwhisper-transcribe; it is upstream gating enforced by Hugging Face.
--diarizeon files ≥ 30 min auto-enables alignment so per-word speaker attribution works; pass--no-alignto opt out (speakers will be approximate).
YouTube-specific extraction quirks (JS runtime requirement, login
walls, anti-bot behavior) are documented separately in
references/youtube-extraction-quirks.md.
Multi-language behaviour (mid-file switches, --language-hints
semantics, batch output schema, known-good / known-bad cases for the
six fixtures shipped under tests/fixtures/multilang/) is documented
in docs/multilang-limitations.md,
with the user-facing how-to in
references/multi-language.md.
Status
Pre-alpha. No CI yet. The smoke-test table above is the only
automated check. See tests/ for the unit/integration
suite and scripts/benchmark_gpu_sizing.py
for the GPU sizing harness.