sttlab-tech 3525e6b8fb initial commit: live meeting transcription pipeline
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>
2026-08-09 13:18:57 +02:00

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 by scripts/build-dist.sh below. 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.

  • audiotee — the system/mic audio capture CLI this project depends on.
  • antirez/qwen-asr — the speech-to-text engine used.
S
Description
No description provided
Readme 67 KiB
Languages
Go 97.6%
Shell 2.4%