Live local streaming speech-to-text using faster-whisper (CTranslate2 port of OpenAI Whisper) running entirely on-device — no audio ever leaves your machine.
While you speak, micstt retranscribes overlapping snapshots with fast greedy decoding and shows a replaceable partial hypothesis. After VAD detects the end of the utterance, it runs a higher-quality final pass and commits the result. Audio and inference work queues are bounded so a slow machine cannot accumulate unlimited latency or memory.
Two ways to run it:
micstt— CLI, prints each transcribed utterance to stdoutmicstt-gui— cross-platform (macOS/Windows) system tray app with a transcript window, a mic selector, and a live input level meter
macOS
brew install portaudio
pip install -e .
Windows
No system package needed — the sounddevice wheel bundles PortAudio.
pip install -e .
ffmpeg is not required either way — audio is captured directly as raw PCM and never goes through file/codec decoding.
micstt-gui
Launches a system tray icon (yellow while loading the model, green while listening, red on error, gray once stopped). Click it — or right-click → "Show Transcript" — to open the transcript window, which has:
- Mic — pick the input device (right-click the tray icon → "Refresh Devices" if you plug in a new mic/headset after launch)
- a live dB level bar so you can see audio is actually reaching the mic before waiting on a transcription
The GUI always uses the small Whisper model and Korean (ko) — there is no model or language selector. The model downloads automatically from Hugging Face on first use.
Quit from the tray menu.
A GitHub Actions workflow (.github/workflows/build.yml) builds standalone micstt-gui apps via PyInstaller for both Windows (windows-latest, one-file .exe) and macOS (macos-latest, ad-hoc-signed .app bundle) on every push to main, or on demand with gh workflow run build.yml. It then publishes both to a rolling latest GitHub Release — the easiest way to grab a build is the Releases page rather than digging through Actions artifacts.
Both builds are unsigned/ad-hoc-signed, so:
- Windows: SmartScreen will warn on first run — "More info" → "Run anyway".
- macOS: unzip and move
micstt-gui.appto Applications. On current macOS, right-click → Open no longer offers a Gatekeeper bypass for ad-hoc-signed apps — you'll just get a "can't be opened" dialog with no override option. Instead, strip the quarantine attribute from Terminal, then launch normally:(Alternative: System Settings → Privacy & Security → scroll down to the blocked-app notice → "Open Anyway", which appears after the first blocked launch attempt.)xattr -cr /Applications/micstt-gui.app
micstt --list-devices # find your mic's device index/name
micstt # start listening (default model: small, language: ko)
micstt --model small --lang en
micstt --audio-file data/sample.wav --lang ko # replay WAV through the realtime pipeline
micstt --audio-file data/sample.wav --play-audio # ...and also hear it played back through speakers
Partial hypotheses are displayed on stderr while you speak. Pause briefly and each finalized utterance is printed as one line to stdout. This keeps stdout clean for piping, e.g. micstt | tee transcript.txt.
Ctrl+C to stop.
| Flag | Default | Description |
|---|---|---|
--model |
small |
tiny/base/small/medium/large — speed vs. accuracy tradeoff |
--lang |
ko |
Pin a language code (e.g. en, ko) to skip detection overhead |
--mic-device |
system default | Device index or name substring |
--audio-file |
none | Replay a 16 kHz mono 16-bit PCM WAV in realtime instead of using a mic |
--play-audio |
off | Also play --audio-file through speakers while streaming it |
--silence-duration |
800 |
ms of silence that ends an utterance |
--vad-aggressiveness |
2 |
0-3, VAD sensitivity |
--partial-interval |
1.0 |
Seconds between overlapping live hypotheses |
--debug-audio-dir |
none | Dump each finalized utterance's audio as a WAV file into this directory, for later replay via --audio-file when a misrecognition needs investigating |
Whisper sometimes mishears domain-specific jargon, product names, or people's names it hasn't seen before. Both the CLI and GUI always bias recognition using two hardcoded constants in src/micstt/vocab.py — hardcoded rather than a flag or config file since this rarely changes:
MEETING_PROMPT— free-text company/meeting background, e.g."올라핀테크는 ... 대안 금융 핀테크 스타트업입니다."JARGON_WORDS— a list of jargon terms/names, e.g.["올라선정산", "레븐", "리스크 스코어링"]
To update the vocabulary, edit vocab.py directly and commit — everyone gets the update on their next git pull (including the next prebuilt desktop app release, since it's just source code). If both constants are empty, no prompt is used.
It's a soft bias, not a guarantee: very unusual terms may still get missed, especially at smaller --model sizes. This has no relation to condition_on_previous_text (kept off deliberately, see transcriber.py) — the prompt is a fixed hint applied identically to every independent utterance, not accumulated transcript history, so it doesn't reintroduce the repetition-loop risk that setting was meant to avoid.
The model downloads once from Hugging Face (~150MB for base, more for larger variants) and is cached under ~/.cache/huggingface/hub/ (%USERPROFILE%\.cache\huggingface\hub on Windows). Subsequent runs are fully offline.
The first time you run micstt/micstt-gui from a given terminal app, macOS will prompt for microphone access — this dialog only appears in an interactive session, not when launched from a non-interactive/automated context. If it hangs with no prompt, check System Settings > Privacy & Security > Microphone, enable access for your terminal app (or the micstt-gui.app itself), then re-run.
Also check System Settings > Sound > Input — if the input volume is turned down, audio will look like it's capturing (no errors) but transcription will silently fail because the mic level is too quiet. The GUI's level bar makes this obvious; the CLI does not currently show levels.