A local offline transcription CLI for macOS (Apple Silicon): one command turns a local audio/video file or a YouTube link into a marked-up transcript with speaker diarization. ASR runs on the Apple Neural Engine (NVIDIA Parakeet TDT v3 via FluidAudio) with pyannote-style diarization, vendored as a single binary — for humans and AI agents alike.
100% local after the first-run model cache. No cloud backend, no uploads, no API keys, no telemetry. The only network call is
yt-dlpfetching YouTube audio when the source is a YouTube URL.
Skill lineage: this CLI is the engine behind the transcribe agent skill
(SKILL.md); the run contract lives in one module
(ADR-0002) behind one engine seam
(ADR-0001).
Features · Install · Quick start · Documentation · Configuration · Output · Status · Contributing · Credits · License
- One command, one run —
transcribe <source>is a single foreground process: preflight, prepare audio, recognize, diarize, merge, write artifacts, exit. No daemons, no background state. Stages (prep→asr→diar→merge) are traced live inprogress.json. - Batch and watched folders — pass several sources for sequential batch
transcription, or use explicit
--watch DIRpolling for dropped media. Watch status persists across restarts;--retry-failedretries failed sources once. - Clean turns —
--clean-fillersremoves a conservative language-aware list from turns while preserving raw word timings. - Subtitle exports —
--formats srt,vttwrites timestamped subtitles from the same speaker turns. - Deterministic term cleanup —
--replacements terms.jsonnormalizes known recognition terms without modifying raw word timings. - Speaker diarization —
--speakers autodetects the speaker count,--speakers Nforces it,--speakers offdisables it for monologues. Fast streaming mode by default; an offline mode for accuracy. - Local files and YouTube — any file
ffmpegcan read (m4a, mp3, wav, mp4, mov, …) and watch URLs viayt-dlp, with output directories named from the real video title, never a URL slug. - Agent-first artifacts —
transcript.md(canonical reading file),transcript.json(turns + word timings),manifest.json(run metadata, written last as the commit marker),progress.json(live tracing). - Language auto-detection — resolves to
ru/en/mixed/auto(explicit flag → engine answer → text heuristic) and writes the label into every artifact. - Progress on demand —
transcribe statusreports a running or finished run without tailing logs, with ETA calibrated from the previous run's real-time factor. - Offline privacy — after the model cache (≈1–3 GB, first run), every run is fully local. Media never leaves the machine.
- One tested engine seam — the FluidAudio contract (subprocess, JSON
schemas, error classification) lives only behind
lib/engine.py(ADR-0001); the whole run contract — stages, ETA, artifact names, schemas — lives inlib/run.py(ADR-0002).
Requires macOS on Apple Silicon, ffmpeg/ffprobe on PATH, and (for
YouTube) yt-dlp:
brew install ffmpeg
brew install yt-dlp # only for YouTube sourcesgit clone https://github.com/speech115/transcribe.git ~/Projects/tools/transcribe
ln -s ~/Projects/tools/transcribe/bin/transcribe ~/bin/transcribe # put it on PATH
ln -s ~/Projects/tools/transcribe ~/.agents/skills/transcribe # skill for AI agentsThe ASR/diarization engine is vendored as a single arm64 binary
(vendor/fluidaudiocli) — no separate engine install. The first run
downloads the models (≈1–3 GB) and caches them; after that the tool is
offline.
# 1. Transcribe a local call
transcribe call.m4a --speakers auto
# 2. Force the speaker count and pick the output directory
transcribe video.mp4 --speakers 2 --out ~/Downloads/transcripts/video
# 3. Transcribe a YouTube video (named by the real video title)
transcribe "https://www.youtube.com/watch?v=…" --speakers auto
# 4. Transcribe several sources sequentially
transcribe call-a.wav call-b.m4a --out-root ~/Downloads/transcripts
# 5. Watch a folder for new media
transcribe --watch ~/Downloads/inbox --out-root ~/Downloads/transcripts
# 6. Watch a run from another terminal
transcribe status
transcribe status --json # machine-readableLanguage is auto-detected by default; use --lang ru or --lang en only
when a specific language is explicitly wanted.
Full guide: docs/guide/
| Area | Pages |
|---|---|
| Start | overview · install · quickstart |
| Operation | transcribe · output · status |
| Engine | engine |
| Reference | ADR index · changelog |
| Agents | SKILL.md — routing table and recipes · AGENTS.md — the contract every agent follows here |
There are no config files and no environment variables. Everything is per-invocation flags:
| Flag | Default | Effect |
|---|---|---|
--speakers auto|off|N |
auto |
detect the speaker count, disable diarization, or force N speakers |
--lang ru|en|auto |
auto |
force a language or auto-detect |
--out DIR |
— | explicit output directory for this run |
--out-root DIR |
~/Downloads/transcripts |
root for default output naming |
--clean-fillers |
off | remove conservative filler words from turns; raw words stay unchanged |
--formats srt,vtt |
— | write optional SRT/VTT subtitle artifacts from turn timings |
--replacements FILE |
— | apply a case-sensitive JSON replacement dictionary to turn text |
--retry-failed |
off | retry each persisted failed watch source once after startup |
--watch DIR |
— | poll a folder for stable new media; mutually exclusive with inputs and --out |
--diar-mode streaming|offline |
streaming |
fast diarization, or slower and more accurate |
--asr-model v3|v2 |
v3 |
Parakeet model generation |
--keep-tmp |
off | keep raw ASR/diarization JSON for debugging |
If neither --out nor --out-root is given, output goes to
~/Downloads/transcripts/<title> — the file name without extension for
local files, the actual video title for YouTube. Existing directories get a
numeric suffix (call, call (2), …).
Exit codes:
| Code | Meaning |
|---|---|
| 0 | success |
| 1 | run error (including any failed source in a batch) |
| 2 | argument parsing error |
Each run writes three deliverables and a live-tracing file into the output directory:
transcript.md— canonical reading file: turns by speaker (S1..Sn), ready for AI agents.transcript.json— structured turns and raw word timings. With--clean-fillers, only the turn text is cleaned.manifest.json— engine, source, canonical local source path, duration, RTF, speaker count, cleanup flag, and run metadata; written last, so its presence means the run completed..transcribe-watch.json— persistent watch state under the output root; it is not a transcript artifact and is ignored by git.progress.json— live run tracing, updated every ~2 s and finalizeddone/error.transcript.srt/transcript.vtt— optional subtitle artifacts when requested with--formats.
For a simple "transcribe this" request, report the transcript.md path and
compact metrics from manifest.json. Read transcript.md only for
follow-up work (summary, cleanup, extraction, QA); use transcript.json
only for exact timestamps or programmatic slicing. Details:
docs/guide/output.md.
v0.4, in daily local use. CI runs pytest on Linux and macOS for every
push and pull request (.github/workflows/ci.yml).
The suite is pure-stdlib and covers the engine seam, the language resolution
chain, the merge, and the run contract — no engine binary needed.
Welcome — this is a small single-maintainer tool, and CONTRIBUTING.md has the working rules: a bug fix starts from a reproducing test, tests run against public seams, and documentation duties are part of the change. AGENTS.md is the full contract every agent follows here. Report a privacy or security issue privately via SECURITY.md; never paste audio contents or transcripts into an issue.
- ASR: NVIDIA Parakeet TDT v3 running on the Apple Neural Engine via FluidAudio.
- Diarization: FluidAudio pyannote-style speaker diarization.
- YouTube audio:
yt-dlp. - Skill contract: conventions from the local agent-skills stack (docs/agents/).
Apache-2.0 © speech115. Parakeet and pyannote models ship under their own licenses via FluidAudio; this tool is not affiliated with NVIDIA, FluidAudio, or YouTube.