Go orchestrator: spawns audiotee for capture (system audio + mic, two tracks), segments with an energy-threshold VAD, transcribes each segment via qwen-asr (subprocess per segment, vendored as a pinned git submodule in third_party/qwen-asr), and serves the live transcript over SSE while also writing it durably to a text file. Includes a glossary/prompt-leakage guard (internal/asr/leak.go) that discards a segment if the model echoes the biasing prompt instead of transcribing. cmd/transcriptor-ai is the main orchestrator; cmd/transcript-tail is a minimal terminal SSE client. See CLAUDE.md for the full architecture and the reasoning behind each choice (Go over Python/Swift/Rust, SSE over WebSocket, why the VAD is a hand-rolled heuristic, etc). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
4.8 KiB
transcriptor-ai
Local, no-cloud meeting transcription for macOS. Captures system audio and microphone as two separate tracks, transcribes both live, and serves a live-updating transcript over HTTP (SSE). Everything runs on-device — no external API calls at runtime.
How it works
audiotee (system audio + mic, 2 tracks)
→ VAD (speech segmentation, per track)
→ qwen-asr (speech-to-text, per track)
→ live transcript, served over SSE
Two tracks (system audio vs microphone) act as a cheap "me vs them" diarization: whatever you hear vs whatever you say, without needing a real speaker-diarization model.
Prerequisites
- macOS (Apple Silicon)
- Go 1.26+ (
brew install go) - audiotee — built and signed with a stable identity so its audio
permissions survive rebuilds. Follow that repo's own README/CONTEXT.md first; you should
end up with a working binary, e.g. at
~/bin/audiotee. - qwen-asr — vendored as a git submodule (
third_party/qwen-asr, pinned to a specific commit), built automatically byscripts/build-dist.shbelow. You still need to download a model yourself (not part of the build — see "Models"), since that's a multi-GB download, not something to fetch on every build:git submodule update --init third_party/qwen-asr # if you haven't run build-dist.sh yet cd third_party/qwen-asr && ./download_model.sh --model large # or --model small
llama.cpp (brew install llama.cpp) will be needed once post-processing (glossary
correction, LLM reread) is implemented, but isn't used yet — no need to install it today.
Build
git clone --recurse-submodules <this repo> # or: git submodule update --init
scripts/build-dist.sh
Builds qwen_asr (from the submodule) and transcriptor-ai into dist/. Also build
transcript-tail directly, it's not part of dist/ (that's for what transcriptor-ui, the
future native app, embeds — see CLAUDE.md):
go build -o transcript-tail ./cmd/transcript-tail
Usage
Start the transcriber:
./dist/transcriptor-ai \
-asr-binary ./dist/qwen_asr \
-asr-model-dir ./third_party/qwen-asr/qwen3-asr-1.7b \
-transcript-file ~/Desktop/meeting-transcript.txt
In another terminal, follow the live transcript:
./transcript-tail
Output looks like:
[mic ] Bonjour, ceci est un test.
[system…] Hello, this is a partial hypothesis that may still be revised
(no marker = a closed/final segment; … = a partial hypothesis, ASR-in-progress)
Stop with Ctrl+C — shutdown is graceful (in-flight transcriptions finish before exiting).
Flags
transcriptor-ai:
| Flag | Default | Description |
|---|---|---|
-audiotee-binary |
PATH, then ~/bin/audiotee |
Path to the audiotee binary |
-asr-binary |
(required) | Path to the qwen_asr binary |
-asr-model-dir |
(required) | Path to a qwen-asr model directory |
-prompt |
(empty) | Glossary biasing text passed to qwen-asr (e.g. "Preserve spelling: Kubernetes, PostgreSQL") |
-http-addr |
:8420 |
Address to serve the live transcript SSE endpoint on |
-transcript-file |
(empty) | Path to append the live transcript to as plain text, flushed to disk after every line. Not written if empty. |
-capture-system |
true |
Transcribe the system audio track. Note: audiotee's system tap always runs regardless of this flag — disabling it only stops transcribing that track, it doesn't skip the underlying capture (audiotee has no flag for that). |
-capture-mic |
true |
Capture and transcribe the microphone track. Unlike -capture-system, disabling this actually skips mic capture entirely (no --capture-mic passed to audiotee, no microphone permission requested). |
At least one of -capture-system/-capture-mic must stay enabled.
transcript-tail:
| Flag | Default | Description |
|---|---|---|
-url |
http://localhost:8420/events |
transcriptor-ai SSE endpoint to follow |
Consuming the transcript programmatically
The SSE endpoint (http://localhost:8420/events by default) streams one JSON object per
transcript segment:
{"track": "system", "text": "...", "is_final": false, "timestamp": "2026-08-07T14:32:01.123Z"}
Any SSE client works, e.g. curl -N http://localhost:8420/events, or a browser's
EventSource.
Status
Live transcription (capture → VAD → ASR → SSE) works end to end, tested with real speech
(English + French, code-switching) through an actual microphone. Not yet implemented:
post-processing (glossary fuzzy-matching, LLM reread) and real-time content analysis — see
CLAUDE.md for what's deliberately deferred and why.
Related
- audiotee — the system/mic audio capture CLI this project depends on.
- antirez/qwen-asr — the speech-to-text engine used.