Skip to content

About

Private local STT for Apple Silicon. Option+Space. Agents get timecodes on the same loopback API. No cloud, no subscription.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

VoiceToText

Private local STT on your Mac. Speech never leaves the machine. No cloud. No $20/month subscription.

VoiceToText icon

Menu bar while dictating

  • Option+Space — speak, text pastes at the cursor
  • $0/month — not SuperWhisper or cloud dictation
  • Native menu bar app + one local model (Qwen 1.7B 8-bit by default; Whisper from the menu)
  • Same API for agents — full transcript with timecodes, language per request, loopback only
curl -fsS http://127.0.0.1:8765/healthz
curl -fsS -F file=@sample.wav http://127.0.0.1:8765/v1/audio/transcriptions
curl -fsS -F file=@meeting.wav -F response_format=verbose_json -F language=auto \
  http://127.0.0.1:8765/v1/audio/transcriptions

Dictation stays Russian (qwen3_asr.language: ru). Agents override the language on a single request.


Quick start

Apple Silicon Mac, macOS 13+, uv.

git clone https://github.com/FUYOH666/VoiceToText.git
cd VoiceToText
uv sync --extra qwen
macos/scripts/build.sh
uv run python src/vtt2/main.py --install

That starts two LaunchAgents: ai.vtt2.stt (model + HTTP on 127.0.0.1:8765) and ai.vtt2 (Swift VoiceToText.app).

Grant Microphone, Accessibility, and Input Monitoring to VoiceToText — not Terminal, not python.

After an ad-hoc rebuild, macOS treats the binary as a new app: remove the old VoiceToText row in Privacy, add macos/dist/VoiceToText.app again, turn the toggles on.

uv run python src/vtt2/main.py --status
curl -fsS http://127.0.0.1:8765/healthz

Deploy this for your business

Open source — run it yourself.

Or I can deploy, customize, and integrate it for your team in 2 weeks.

Free consultation: private@scanovich.ai · Telegram @ScanovichAI


How it works

  1. Option+Space starts recording
  2. Speak
  3. Option+Space stops
  4. The menu bar POSTs audio to local STT and pastes the text

The orange microphone pill is macOS (privacy). The waveform is VoiceToText.

Agents use the same POST /v1/audio/transcriptions — docs/STT_API.md. verbose_json returns the full text plus segment start/end. language=auto or a code such as language=en applies to that file only.

Default profile loads Qwen3-ASR 8-bit on demand and unloads after 15 minutes idle. Always-on, same checkpoint: config.resident.yaml. Whisper is the menu item, not a second process.


Configuration (short)

stt_server:
  host: "127.0.0.1"
  port: 8765
  engine: qwen3_asr
  preload_on_start: false
  idle_unload_seconds: 900

transcription:
  engine: local_stt
  qwen3_asr:
    model_name: "moona3k/mlx-qwen3-asr-1.7b-8bit"
    quantization_bits: null
    language: "ru"

The checkpoint is pre-quantized 8-bit, group 64, about 2 GB. quantization_bits: null so it is not quantized again. Install it with the server: uv sync --extra qwen. A later plain uv sync removes that extra and the default server exits instead of falling back to Whisper.

uv sync --extra qwen
launchctl kickstart -k "gui/$(id -u)/ai.vtt2.stt"

On this Mac the first request downloaded 2.0 GB and answered a 3-second clip in 43 seconds. The same clip with the model already loaded took 0.22 seconds. After 15 minutes idle the weights unload; the next request loads that snapshot again and does not quantize it.

Whisper stays in the menu. That item rewrites stt_server.engine and restarts only the STT process. On 8 GB RAM the Whisper checkpoint to pick is whisper-medium-mlx. Remote GPU ASR stays under transcription.engine: remote_asr (host in gitignored .env.vtt2). model= on the POST does not switch checkpoints. Details: docs/STT_API.md.


Troubleshooting

Paste does nothing: Privacy → Accessibility and Input Monitoring → VoiceToText. Text is also on the clipboard (Cmd+V). Menu: «Разрешения…».

Daily builds sign with the login-keychain identity vtt-local-codesign, so those two grants survive the next rebuild. Create it once: macos/scripts/setup-local-signing.sh. The first rebuild after an ad-hoc signature still needs one grant. Without that identity, build.sh signs ad-hoc and macOS drops the grants every time.

STT slow / readyz 503: idle-unload. healthz is enough; the next POST loads the model. Always-on: cp config.resident.yaml config.yaml && uv run python src/vtt2/main.py --install.

After a git pull of STT: restart the model process so agents see the new contract — launchctl kickstart -k gui/$(id -u)/ai.vtt2.stt. Menubar can stay running.

Two menu bar icons: uv run python src/vtt2/main.py --install kills leftover UI (not --serve-stt).

Logs: ~/Library/Logs/vtt2/ (voicetotext.log, stt.stdout.log).


Development

uv run pytest
macos/scripts/test.sh

Notarized .app needs Apple Developer + macos/Signing.xcconfig (not in git) and macos/scripts/release.sh. No signed public zip is attached until a Developer ID exists.

MIT. Built with MLX.

About

Private local STT for Apple Silicon. Option+Space. Agents get timecodes on the same loopback API. No cloud, no subscription.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages