Local transcription recorder and operator dashboard. TapScribe records one WAV per utterance per speaker over a WebSocket, runs Whisper / Voxtral / Parakeet batch transcription on demand, and supervises a WhisperLiveKit child process for live captions. Nothing leaves the machine.
Audio arrives via a bridge — usually a browser extension that taps the
meeting platform's audio tracks and streams raw PCM to the /tap WebSocket.
The included bridges/spacialchat-bridge/ targets spatial.chat; see
bridges/README.md for the wire protocol.
Beyond record + transcribe: a hallucination filter (substring / exact: /
re: rules; suppressed segments kept in an audit array), silence-stripping
(trimmed copies in <session>/stripped/, originals untouched), summarization
(bundled local model, operator CLI, or an OpenAI-compatible API), a People
registry (cross-session speaker identity → display name, with merge/detach),
and an end-of-meeting pipeline (strip → transcribe → summarize as one job a
Bridge can trigger and poll with the tap token — no operator in the loop).
CONTEXT.md has the domain model.
One operator console at / — the "Stages" UI. A slim left spine navigates the
global views (Taps · Sessions · People · Settings) and the per-session journey
(Capture → Recordings → Transcript → Summary); the active-taps rail follows you
across views. Live captions stream in Capture; silence-stripping and the
per-WAV files live in Recordings; the ▶ transcribe range button, the
meeting-languages declaration (ADR-0011), and the merged transcript live in
Transcript; the live and batch engine pickers live in Settings.
Captured live by the browser E2E test: the Apollo 11 fixture through the
bridge and a real faster-whisper tiny.en. Bigger models clean up the
self-flagged low-confidence output considerably.
bash start.sh # localhost only
bash start.sh --lan # bind 0.0.0.0The script finds Python 3.12+, creates .venv, base-installs the package, and
launches the recorder on port 8001. Pick transcription models in the browser at
http://localhost:8001/setup (/ redirects there until a model is installed);
/setup doubles as the manage-models surface. Other flags: --non-interactive
(headless — install the saved/default model selection without the browser),
--no-mlx (skip MLX on Apple Silicon; live and batch both use MLX there by
default), --auto-live (boot with live captions running), --tls, --no-auth
(dev only).
Live captions are off by default: start whisperlivekit-server from the
dashboard. The recorder supervises it as a child on an ephemeral internal port
(pin one with SX_PORT_WLK); child logs are prefixed [wlk]. Ctrl+C stops the
recorder and any child.
On first run two secrets are generated and printed:
- a dashboard password for HTTP Basic auth, persisted to
.auth-password; - a
/tapbearer token for bridges, persisted to.tap-token— paste it into the bridge popup along with host and port.
Rotate with .venv/bin/tapscribe --rotate-password / --rotate-tap-token.
start.sh --tls serves https:// + wss:// with a self-signed cert generated
on first boot (.tapscribe-cert.pem, .tapscribe-key.pem) and reused after;
to supply your own, run tapscribe directly with --tls --cert <path> --key <path> (recorder flags, not start.sh flags).
Download TapScribe-Setup-win-x64.exe from the
latest release and run
it. Per-user install (no admin prompt), carries its own interpreter; a tray
icon starts the dashboard and its Copy password item gives you the
generated login. The Bundle is unsigned, so SmartScreen warns — click More
info → Run anyway. Program files land in %LOCALAPPDATA%\Programs\TapScribe;
recordings, transcripts and settings live in %USERPROFILE%\TapScribe and
survive uninstall; model weights cache in %USERPROFILE%\.cache\huggingface.
See packaging/README.md and
ADR-0015.
.\start.ps1 # flags mirror start.sh: -Lan, -NoMlx, -AutoLive, -Tls, -NonInteractive- PyPI:
pip install tapscribe, thentapscribe. Models still install via/setup. - Docker (CPU-only):
docker run --rm -it -p 8001:8001 -v "$PWD/data:/data" ghcr.io/vortiago/tapscribe:latest
Editable config files live under config/ and are re-read on every job; all
are editable from the dashboard's Settings view (further knobs — chunk sizes,
timeouts, model idle TTL — live under Settings → Advanced).
| File | Shapes |
|---|---|
config/prompt.txt / live-prompt.txt |
Batch initial_prompt / live --init-prompt (independent). Prose under ~150 words. |
config/hotwords.txt |
Batch hotwords — proper nouns; faster-whisper only. |
config/hallucinations.txt |
Suppression rules: substring, exact:, or re:. Ships with common YouTube-trained Whisper hallucinations. |
config/batch-model.txt / live-model.txt |
Default batch / live model ids. |
config/languages.txt |
Candidate languages (ADR-0010); default da,no,en. |
config/summarizer.json |
Default summarizer (source, command/model/base_url, prompt; api_key write-only). |
One backend, one supervised child, N bridges. Audio flows in over WebSocket; captions and recordings come out.
flowchart LR
subgraph Meeting["Meeting platform (e.g. spatial.chat)"]
Bridge["Bridge<br/>(browser extension<br/>or native helper)"]
end
subgraph Host["TapScribe host"]
Backend["TapScribe backend<br/>FastAPI :8001<br/>/tap · /api · dashboard"]
WLK["whisperlivekit-server<br/>(supervised child, internal port)"]
WAVs[("recordings/<br/><session>/*.wav")]
end
Operator["Operator browser<br/>(dashboard)"]
Bridge -- "PCM 16k mono<br/>over WS /tap" --> Backend
Backend -- "forwards PCM" --> WLK
WLK -- "settled live captions" --> Backend
Backend -- "one WAV per utterance" --> WAVs
Operator <-- "HTTPS (500 ms /api/state poll)" --> Backend
One /tap WebSocket = one utterance. Each PCM frame is tee'd: appended to the
per-utterance WAV on disk and relayed to the WhisperLiveKit child for live
captions. The two sinks are independent — if WhisperLiveKit is down, recording
still works. Bridges never talk to the child directly.
| Family | Models | Backend | Languages | Notes |
|---|---|---|---|---|
| Whisper | tiny(.en)/base(.en)/small(.en)/medium(.en), large-v3, large-v3-turbo |
mlx-whisper (AS) / faster-whisper | English-only suffixes; else multilingual | large-v3-turbo is the default generalist (ADR-0010). |
| NB-Whisper | nb-whisper-tiny/base/small/medium/large |
faster-whisper on CT2 weights | Norwegian | Pulled from NbAiLab/nb-whisper-*/ct2/. No MLX. |
| Voxtral | voxtral-mini |
mlx-voxtral (AS) / HF transformers | EN/ES/FR/PT/HI/DE/NL/IT | Batch-only. First load downloads ~6 GB. |
| Parakeet | parakeet-tdt-0.6b-v3 |
parakeet-mlx (AS) / HF transformers | 25 EU languages, no Norwegian | Batch-only; replaced Canary (ADR-0006). |
| Moonshine | moonshine-tiny/base |
mlx-audio (AS) / ONNX-CPU | English | Live-only low-latency captions. Extras [moonshine-mlx] / [moonshine-cpu]. |
pip install -e ".[dev]"
python -m pytest -qUnit + route tests (backends stubbed, under 20 s), an HTTP pipeline E2E over
real /tap WebSockets, a real-Whisper E2E on committed CC-licensed fixtures,
a Playwright dashboard-UI E2E, and a headed bridge-extension E2E. See
CONTRIBUTING.md for how to run each layer. GitHub Actions
runs the suite and ruff on every push and PR across Python 3.12-3.14 on
Ubuntu, macOS, and Windows.
MIT. See LICENSE.
