Why each piece was chosen and what would force a swap.
mlx-whisper wraps OpenAI's Whisper model with Apple's MLX framework. On Apple Silicon it runs the encoder/decoder on the unified-memory GPU and Neural Engine.
Why:
- 6× realtime on a base M-series chip (measured against Whisper Large v3) without any quantisation tricks.
- The CLI accepts an
--initial-promptflag, which is exactly the vocab-anchoring mechanism Plaudio uses. - Drop-in:
mlx_whisper audio.mp3 --output-format jsonproduces the segment shape we need.
What would force a swap:
- If MLX-side maintenance lapsed for ≥6 months. Fallback:
faster-whispervia CTranslate2, with the CPU/CUDA branches gated behind a backend abstraction (a v0.3 conversation). - A new ASR family that materially beats Whisper Large v3 on Dutch + English + technical vocabulary. Currently none does at this latency/cost.
pyannote-audio 3.1's pyannote/speaker-diarization-3.1 pipeline produces speaker turns and per-cluster mean embeddings in one shot. The same package exposes pyannote/embedding as a sliding-window embedder for Plaudio's voice-bank matcher.
Why:
- The de facto open-source standard for offline diarisation. Active maintenance, broad benchmarks, MPS-compatible on Apple Silicon (via torch).
DiarizeOutput.speaker_embeddingsgives per-speaker mean embeddings without a separate model call. One gated download covers both clustering and embeddings.- The 256-dim embedding has good discriminative power across same-language speakers in real meeting conditions.
What would force a swap:
- A faster Apple Silicon-native diariser (someone is doing pyannote-on-MLX work, watch that space).
- A gated-model change that makes the HuggingFace acceptance flow worse than it already is.
Workaround we ship: pyannote 4.x defaults to torchcodec for audio loading. Torchcodec wants libavutil 56/57 (ffmpeg 4/5). Homebrew ships ffmpeg 7+ (libavutil 59+). We sidestep by pre-converting audio to 16 kHz mono WAV via ffmpeg, then loading with soundfile, then passing {waveform, sample_rate} to the pyannote pipeline. Documented in src/plaudio/core/diarise.py.
The transcripts corpus is a single-file SQLite database at ~/Library/Application Support/plaudio/transcripts.db, with an FTS5 virtual table over segments content using the trigram tokenizer.
Why SQLite at all:
- Single-file, zero-server, runs everywhere Python runs.
- Concurrency for a single user is fine; we don't need PostgreSQL.
- FTS5 is shipped in Python's
sqlite3module on macOS by default. No external dependency.
Why trigram (not unicode61):
- The default
unicode61tokenizer treats CJK characters as a single token glued to adjacent Latin words. A segment like让我看看这个 nanolab 的人becomes tokenized in a way that searching for "nanolab" misses the segment. - The
trigramtokenizer breaks every text into overlapping 3-character grams. This handles ZH+EN code-switching cleanly. The price is a 3-character minimum query length, which is fine for a search command.
What would force a swap:
- A real need for multi-process concurrent writes (then Postgres + tsvector).
- A scale jump past tens of thousands of meetings (then a tantivy or Meilisearch backend).
The CLI uses argparse with a dispatcher in cli/main.py and per-command modules in cli/commands/.
Why argparse over click / typer:
- Zero extra dependency. Plaudio's import surface stays small.
- argparse is enough for the surface we have (10 subcommands, no fancy callbacks).
- Subcommand modules can register themselves via a uniform
register(sub: argparse._SubParsersAction)function. The dispatcher just callsargs.func(args).
What would force a swap:
- Rich shell completion across subcommands becoming a priority.
- Sub-sub-sub-command nesting beyond two levels.
The voicebank is a single JSON file with an array of profile entries. Each profile has an embedding vector, a name, an enrolment timestamp, and a few audit fields. Multi-sample per name is intentional and the sliding matcher averages them.
Why JSON:
- Human-readable. Easy to inspect, export, copy between machines.
- No schema migration framework needed at v0.1 (we have one field,
schema_version). - Mode 0600 is enforced on every save.
What would force a swap:
- Banks growing past a few hundred profiles (then SQLite or a binary format).
- Need for encrypted-at-rest (then GPG-wrapped JSON or sqlite + sqlcipher).
Why AGPL:
- Keeps the option of a future dual-license open. As the original author, the maintainer can sell commercial exceptions to parties that don't want AGPL obligations.
- Forces forks that operate as a network service to share their source. The bar is reasonable for a project of this size and stops a SaaS-wrapping fork from quietly extracting all the value.
Why not MIT:
- MIT closes the commercial-pivot path (once code is MIT, you can't sell exclusive rights). Plaudio is intentionally keeping that option open.
What would change this:
- A clear signal from contributors that AGPL is a barrier to adoption. Most likely path is dual-licensing rather than relicensing.
Every commit and CI build runs against a private wordlist of personal terms (colleague surnames, project codes, etc.) maintained by the maintainer. The scanner (plaudio_audit.wordlist.scan) is word-boundary case-insensitive matching; an audit-allowed-terms.txt file in the repo provides per-term overrides with justifications.
Why a separate package:
- The wordlist scanner has zero runtime cost to Plaudio users (they don't install
plaudio_audit). - The pre-commit hook just pipes
git diff --cached -U0intopython3.13 -m plaudio_audit.precommit. - CI duplicates the check.
What would change this:
- If the project moves to a multi-maintainer model, the audit moves to a per-PR review process (and the wordlist becomes a community concern, which is a much bigger conversation).
Pytest with one mandatory marker (integration, opt-in). No real meeting audio in the repo. All test data is synthetic embeddings, fabricated names (Alice Smith, Bob Jones), and a stub .plaud.json shape.
Why no real audio:
- A real meeting recording in
tests/fixtures/would either leak voices or require a consent log that doesn't scale. - The unit tests verify algorithms (cosine, coalesce_runs, FTS5 trigram), not the speech models themselves.
- The opt-in integration test uses a LibriVox public-domain clip plus a self-recorded clip with a documented consent log.
- No web UI. Riffado already covers that surface; Plaudio is a terminal tool.
- No Docker. macOS-only means we lean on the system Python + Homebrew, not containers.
- No background daemon. Every Plaudio invocation is a discrete CLI call. State is on disk.
- No telemetry. Plaudio never phones home. The maintainer doesn't know how many people use it.