mattrguerra/transcription-pipelinemainPrivate on GitHub, published here in full

README.md

Language Markdown4.9 KB122 lines

README.md

  1. # transcription-pipeline
  2. [![tests](https://github.com/mattrguerra/transcription-pipeline/actions/workflows/tests.yml/badge.svg)](https://github.com/mattrguerra/transcription-pipeline/actions/workflows/tests.yml)
  3. Drop a recording in a folder; get back a transcript that says who said what,
  4. stamped to the word, filed where the rest of your notes live.
  5. ```
  6. inbox/ --launchd WatchPaths--> transcribe inbox
  7. 1. take files whose size has stopped changing; one run at a time (lock file)
  8. 2. ffmpeg any audio or video -> 16 kHz mono WAV
  9. 3. transcribe local: faster-whisper large-v3-turbo, int8 (default)
  10. deepinfra: hosted Whisper large-v3-turbo (optional)
  11. 4. align wav2vec2 forced alignment -> a start and end time for every word
  12. 5. diarize pyannote -> who spoke when; each word gets a speaker
  13. 6. shape sentences (split at punctuation and at every change of speaker),
  14. speakers renumbered "Speaker 1, 2, ..." by first appearance
  15. 7. write transcripts/<name>.txt .md .json (+ .srt .vtt); the .md also to a vault
  16. 8. file recording -> done/, or failed/ with <name>.error.txt
  17. ```
  18. Steps 3 to 5 are [WhisperX](https://github.com/m-bain/whisperX). The rest is
  19. this package: the watch folder, the sentence and speaker shaping, the writers,
  20. and the plumbing that makes it safe to leave running.
  21. ## What a transcript looks like
  22. From the integration test, which runs on two LibriSpeech readers taking turns
  23. (`tests/fixtures/two-speakers.flac`):
  24. ```
  25. [00:00:00] Speaker 1: for a full hour he had paced up and down waiting but he could wait no longer
  26. [00:00:06] Speaker 2: harangue the tiresome product of a tireless tongue
  27. [00:00:13] Speaker 1: pride after satisfaction uplifted him like long slow waves
  28. [00:00:19] Speaker 2: anger pain painful to hear
  29. ```
  30. The model returned that clip as one unpunctuated segment. It still comes out
  31. as four turns, because sentences are also split wherever the speaker changes.
  32. The `.md` is a vault note: this frontmatter, then one paragraph per speaker turn.
  33. The `.json` keeps every segment, every timed word and the run's metadata.
  34. ```markdown
  35. ---
  36. type: transcript
  37. title: "two-speakers"
  38. recorded: 2026-09-28
  39. transcribed: 2026-09-28T06:15:09
  40. duration: "0:00:24"
  41. engine: local
  42. language: en
  43. source: "two-speakers.flac"
  44. speakers:
  45. - "Speaker 1"
  46. - "Speaker 2"
  47. tags:
  48. - transcript
  49. ---
  50. ```
  51. ## Use it
  52. ```bash
  53. transcribe call.m4a # local, with speakers; outputs beside the file
  54. transcribe *.mp3 --out ~/transcripts --formats txt,srt
  55. transcribe interview.mov --speakers 2 # you know how many voices there are
  56. transcribe memo.m4a --no-diarize # just the words
  57. transcribe call.m4a --engine deepinfra # hosted Whisper; uploads the audio
  58. transcribe inbox --root ~/Transcription # what launchd runs
  59. ```
  60. ## Install
  61. ```bash
  62. python3.12 -m venv .venv && .venv/bin/pip install -e ".[test]"
  63. ```
  64. Python 3.10 to 3.13 (WhisperX does not support 3.14 yet). Tokens, the gated
  65. diarization model and the launchd job: [docs/SETUP.md](docs/SETUP.md).
  66. ## Tests
  67. ```bash
  68. pytest -q # unit tier: no network, no models (CI)
  69. TP_INTEGRATION=1 pytest -q tests/integration # real models; add HF_TOKEN for pyannote
  70. ```
  71. The integration tier checks every reference word, monotonic word times and the four
  72. turn boundaries on four utterances from LibriSpeech test-clean (Panayotov, Chen,
  73. Povey and Khudanpur, 2015), used under CC BY 4.0.
  74. ## Choices worth knowing
  75. - **Local by default.** DeepInfra's hosted Whisper has transcribed noticeably better
  76. than local int8 faster-whisper; use it when quality matters and uploading is fine.
  77. - **Timed to the word.** A sentence starts at its first aligned word; where nothing
  78. aligns, the time is interpolated and the JSON marks it `"timed": false`.
  79. - **Sentence rules pinned by a golden file.** `sentences.py` ports a JavaScript splitter;
  80. `tests/fixtures/sentences-golden.json` holds that splitter's output.
  81. - **Speakers.** Sentences split at every change of speaker; a one-word run is diarization
  82. noise; labels are renumbered by first appearance.
  83. - **Safe to leave running.** Settle window, one run at a time with stale-lock takeover,
  84. `failed/` with the reason, never an overwrite; tokens from the env or the Keychain.
  85. ## Layout
  86. | Path | What it is |
  87. |---|---|
  88. | `transcription_pipeline/` | The package: `cli`, `pipeline`, `inbox`, `audio`, `engines/`, `align`, `diarize`, `sentences`, `shape`, `render`, `filing`, `secrets`, `steps`. |
  89. | `tests/` | Unit tier (runs in CI) and `tests/integration` (real models on real speech). |
  90. | `scheduling/` | The launchd plist and the script it runs. |
  91. | `docs/SETUP.md` | Install, tokens, the watch folder. |
  92. ## Where it came from
  93. It grew out of a small DeepInfra transcriber with a one-page UI. The pipeline kept its
  94. sentence rules, `[HH:MM:SS]` lines and never-overwrite filing, and added the rest.
  95. ## License
  96. MIT.