README.md
Language Markdown4.9 KB122 lines
# transcription-pipeline[](https://github.com/mattrguerra/transcription-pipeline/actions/workflows/tests.yml)Drop a recording in a folder; get back a transcript that says who said what,stamped to the word, filed where the rest of your notes live.```inbox/ --launchd WatchPaths--> transcribe inbox1. take files whose size has stopped changing; one run at a time (lock file)2. ffmpeg any audio or video -> 16 kHz mono WAV3. transcribe local: faster-whisper large-v3-turbo, int8 (default)deepinfra: hosted Whisper large-v3-turbo (optional)4. align wav2vec2 forced alignment -> a start and end time for every word5. diarize pyannote -> who spoke when; each word gets a speaker6. shape sentences (split at punctuation and at every change of speaker),speakers renumbered "Speaker 1, 2, ..." by first appearance7. write transcripts/<name>.txt .md .json (+ .srt .vtt); the .md also to a vault8. file recording -> done/, or failed/ with <name>.error.txt```Steps 3 to 5 are [WhisperX](https://github.com/m-bain/whisperX). The rest isthis package: the watch folder, the sentence and speaker shaping, the writers,and the plumbing that makes it safe to leave running.## What a transcript looks likeFrom the integration test, which runs on two LibriSpeech readers taking turns(`tests/fixtures/two-speakers.flac`):```[00:00:00] Speaker 1: for a full hour he had paced up and down waiting but he could wait no longer[00:00:06] Speaker 2: harangue the tiresome product of a tireless tongue[00:00:13] Speaker 1: pride after satisfaction uplifted him like long slow waves[00:00:19] Speaker 2: anger pain painful to hear```The model returned that clip as one unpunctuated segment. It still comes outas four turns, because sentences are also split wherever the speaker changes.The `.md` is a vault note: this frontmatter, then one paragraph per speaker turn.The `.json` keeps every segment, every timed word and the run's metadata.```markdown---type: transcripttitle: "two-speakers"recorded: 2026-09-28transcribed: 2026-09-28T06:15:09duration: "0:00:24"engine: locallanguage: ensource: "two-speakers.flac"speakers:- "Speaker 1"- "Speaker 2"tags:- transcript---```## Use it```bashtranscribe call.m4a # local, with speakers; outputs beside the filetranscribe *.mp3 --out ~/transcripts --formats txt,srttranscribe interview.mov --speakers 2 # you know how many voices there aretranscribe memo.m4a --no-diarize # just the wordstranscribe call.m4a --engine deepinfra # hosted Whisper; uploads the audiotranscribe inbox --root ~/Transcription # what launchd runs```## Install```bashpython3.12 -m venv .venv && .venv/bin/pip install -e ".[test]"```Python 3.10 to 3.13 (WhisperX does not support 3.14 yet). Tokens, the gateddiarization model and the launchd job: [docs/SETUP.md](docs/SETUP.md).## Tests```bashpytest -q # unit tier: no network, no models (CI)TP_INTEGRATION=1 pytest -q tests/integration # real models; add HF_TOKEN for pyannote```The integration tier checks every reference word, monotonic word times and the fourturn boundaries on four utterances from LibriSpeech test-clean (Panayotov, Chen,Povey and Khudanpur, 2015), used under CC BY 4.0.## Choices worth knowing- **Local by default.** DeepInfra's hosted Whisper has transcribed noticeably betterthan local int8 faster-whisper; use it when quality matters and uploading is fine.- **Timed to the word.** A sentence starts at its first aligned word; where nothingaligns, the time is interpolated and the JSON marks it `"timed": false`.- **Sentence rules pinned by a golden file.** `sentences.py` ports a JavaScript splitter;`tests/fixtures/sentences-golden.json` holds that splitter's output.- **Speakers.** Sentences split at every change of speaker; a one-word run is diarizationnoise; labels are renumbered by first appearance.- **Safe to leave running.** Settle window, one run at a time with stale-lock takeover,`failed/` with the reason, never an overwrite; tokens from the env or the Keychain.## Layout| Path | What it is ||---|---|| `transcription_pipeline/` | The package: `cli`, `pipeline`, `inbox`, `audio`, `engines/`, `align`, `diarize`, `sentences`, `shape`, `render`, `filing`, `secrets`, `steps`. || `tests/` | Unit tier (runs in CI) and `tests/integration` (real models on real speech). || `scheduling/` | The launchd plist and the script it runs. || `docs/SETUP.md` | Install, tokens, the watch folder. |## Where it came fromIt grew out of a small DeepInfra transcriber with a one-page UI. The pipeline kept itssentence rules, `[HH:MM:SS]` lines and never-overwrite filing, and added the rest.## LicenseMIT.