Skip to content

nirajrajgor/stt

Repository files navigation

stt

Privacy-first speech-to-text for macOS.

  • Works offline.
  • Hold a hotkey, speak, release, and text appears in the focused app.
  • Works in noisy env.
  • Handles quiet speech.
  • Avg latency 700ms.
  • Removes filler words ("um", "uh", "hmm").
  • Supports voice commands like scratch that and delete last 3 words.
  • Visual recording indicator.
  • Auto close on sleep.
  • Optimized for English language.

Demo

stt-demo.mp4

Requirements

  • macOS on Apple Silicon (M1 / M2 / M3 / …). MLX is arm64-only.
  • Recommended: 16GB RAM. Running the model uses approximately 1.5GB RAM.
  • Homebrew Python, not Anaconda. Anaconda can break macOS notifications and microphone permissions. Use brew install python@3.14 (or 3.12 / 3.13).

Setup

# 1. Install Homebrew Python (skip if you already have it)
brew install python@3.14

# 2. Clone and enter the project
git clone git@github.com:nirajrajgor/stt.git stt
cd stt

# 3. Create a virtual environment with Python's built-in venv module
python3 -m venv venv

# 4. Install dependencies into the virtual environment
venv/bin/python -m pip install -r requirements.txt

The first run downloads the Parakeet model from Hugging Face and caches it locally.

macOS permissions (required)

The script needs two permissions. macOS should prompt for each one the first time it triggers, but you can also grant them manually in System Settings → Privacy & Security:

  1. Microphone — for your terminal app (Terminal.app, iTerm2, etc.). Without this, recordings are silently empty.
  2. Accessibility — for your terminal app. Needed so the global hotkeys and the simulated Cmd+V paste work.

After granting either permission, fully quit and reopen the terminal (Cmd+Q, not just close the window) for it to take effect.

Usage

./stt.py

Click where you want the text to land, then use either:

  • Push-to-talk: hold Right Option, speak, release.
  • Toggle: tap Left Option + Left Command to start, tap again to stop.

Both modes warn 1 minute before the 10-minute limit and auto-stop at the limit.

The transcription is pasted into the focused input and appended to transcriptions.md. Your existing plain text clipboard contents is preserved and the paste is hidden from clipboard history managers (Raycast, Maccy, Alfred, Pastebot, etc.).

Works in any app with a focused text field, including Notes, Slack, Mail, ChatGPT, Cursor, and browser text fields.

Voice commands

Pause before and after a command so it is its own utterance:

  • scratch that — delete the previous utterance
  • delete last 3 words — delete the previous 3 words

Commands apply only within the current recording before paste.

Settings

Hotkeys and runtime settings are read from stt.config.toml, created on first run. See stt.config.example.toml for valid key names. fn is not supported.

In the [settings] section (all keys optional):

  • sounds = true|false — play a sound when transcribed text is pasted; default: true. (Replaces the removed STT_SOUNDS env var.)
  • utterance_gap = <seconds> — pause length for voice-command boundaries, above 0 up to 10; default: 0.7. (Replaces the removed STT_UTTERANCE_GAP env var.)
  • denoise = "auto"|"on"|"off" — noise reduction; "auto" detects noisy clips; default: "auto". (Replaces the removed STT_DENOISE env var; 0/1 are now "off"/"on".)
  • input_device = <index or "name"> — input device by index or name substring; omit for the macOS default input. (Replaces the removed STT_INPUT_DEVICE env var.)

List available devices:

venv/bin/python -c "import sounddevice as sd; print(sd.query_devices())"

Common transcription fixes live in text_cleanup.py. Filler-word cleanup is always on. The wpm and word count logged in transcriptions.md reflect what you actually spoke (fillers included) — only the saved text is cleaned.

Tests

venv/bin/python -m pip install -r requirements-dev.txt
venv/bin/python -m pytest          # fast suite, runs anywhere
venv/bin/python -m pytest -m e2e   # real model inference

The e2e tier loads the native MLX runtime and the Parakeet model, so it only runs on Apple Silicon Macs with Metal access (same requirement as the app itself); elsewhere MLX aborts the process at import. The fast suite has no such dependency and is the tier to run in CI or sandboxes.

Troubleshooting

  • AttributeError: 'NoneType' object has no attribute 'removeAllDeliveredNotifications' — you're running under Anaconda Python. Recreate the venv using Homebrew Python (see Setup).
  • No audio captured / silent recordings — the terminal app doesn't have microphone permission, or Anaconda Python failed to trigger the TCC prompt. Grant permission manually in System Settings → Privacy & Security → Microphone, then fully quit and reopen the terminal.
  • Hotkey does nothing — the terminal app doesn't have Accessibility permission. Grant it in System Settings → Privacy & Security → Accessibility and fully restart the terminal.
  • Transcribed text appears in the terminal instead of where you wanted — the terminal was the focused window when you stopped recording. Click into your target app before pressing the stop hotkey.
  • STT_* environment variables have no effect — they were replaced by the [settings] section in stt.config.toml (see Settings). The app prints a warning at startup naming the config key to use instead.

About

Speech to text macos app, using local model from Nvidia Parakeet.

Topics

Resources

License

Stars

2 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages