qwen_asr has no quantized-weights option, so the only lever available to reduce the 1.7B model's memory footprint is avoiding concurrent instances. Previously each track (system, mic) ran its own transcribeTrack goroutine with independent qwen_asr subprocesses, so simultaneous speech on both tracks meant two ~6.9GiB model instances alive at once - over half the 24GB target machine's memory. Merge both tracks onto a single worker (asrSegs channel, dispatch() routes both tracks onto it) so invocations are strictly serialized; per-track order is preserved since VAD's output is already chronological. Verified via pgrep -x qwen_asr polling during a real dual-track capture: never more than 1 concurrent process. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.