Skip to main content

voice2text

PyPI Downloads Total Downloads macOS Works on my machine

Local, MLX-first voice-to-text. Hold Right ⌘, talk, release — it transcribes with Parakeet, cleans the text up with a small local LLM, and pastes at your cursor. No cloud or account; after the one-time model download, it runs without a network connection.

Voice-to-text tools like Wispr Flow, MacWhisper, and VoiceInk are great until the network gets in the way — Wispr Flow isn't compatible with most VPNs, so behind a corporate SSL-intercepting gateway (Zscaler and friends) it stalls or fails. Once the models are cached, going local makes that whole class of problem disappear: the VPN is irrelevant. Speech models are good enough now that the basics fit in a small Python package on consumer hardware.

Heritage: this started as a single voice2text.py under 300 lines — that proof-of-concept is preserved forever at the nano tag. From 0.3.0 it's a small, modular package: pluggable MLX backends, one config file, a benchmark harness, and an optional one-file menu-bar app.

Design tenet — be communicative. An ergonomic tool is an expressive one: every action gets immediate, visible feedback. The optional menu-bar icon tracks live state (off · loading · ready · recording · transcribing · cleaning · error), so you always know whether the tool heard you.

What you get

  • Push-to-talk — hold Right ⌘ (configurable), release to transcribe + paste.
  • Parakeet (MLX) transcription by default — ~10× faster than Whisper on Apple Silicon, English + 24 European languages. Whisper stays available as a fallback for rare languages/accents.
  • In-process LLM cleanup via mlx-lm (Qwen2.5-1.5B-Instruct) — fixes punctuation, removes fillers while using about 1.3 GB less model memory than the previous 4B default. No Ollama, no daemon. Strict or casual. (Ollama optional — see Cleanup engine.)
  • Pastes at cursor, restoring your previous clipboard.
  • File transcriptionv2t transcribe memo.opus runs the same local models over audio you already have (see Transcribing files).
  • One config file at ~/.v2t/config.toml, plus a JSONL history of every transcription.
  • Optional one-file Swift menu — native permissions, instant state, and quick links; no window or Xcode project.

Install

Requires macOS on Apple Silicon (MLX). Parakeet transcription and in-process mlx-lm cleanup ship as dependencies, with no daemon. The models download from Hugging Face on first launch, then remain in the local cache:

uv tool install voice2text   # Parakeet STT + in-process Qwen2.5 cleanup
v2t setup                    # optional: pick models, detect Ollama, write config
v2t

Prefer Whisper for transcription? Add the extra (quote the brackets — zsh treats them as globs):

uv tool install 'voice2text[whisper]'   # adds the Whisper backend; select it in config
Other install methods (uvx, pip, dev)
# quick try (fresh venv each run — slower startup)
uvx --from voice2text v2t

# pip
pip install voice2text && v2t

# from source
git clone https://github.com/lucharo/voice2text.git && cd voice2text
uv sync --no-dev && uv run v2t

Usage

v2t                      # run push-to-talk (strict cleanup, parakeet)
v2t --casual             # light cleanup (punctuation + fillers only)
v2t --no-cleanup         # paste raw transcription, skip the LLM
v2t --backend whisper    # use the whisper backend for this run
v2t --pause-music        # pause media while recording (needs nowplaying-cli)

v2t transcribe memo.opus # transcribe a file you already have (no microphone)

v2t setup                # guided config: pick models, detect Ollama
v2t status               # running / idle (also used by the menu app)
v2t stop                 # stop a running v2t gracefully  (--force if it is stuck)
v2t config               # show resolved config + paths  (--init writes a template)
v2t menubar install      # optional: compile + open the tiny native menu app
v2t service install      # optional: start that menu app at login

Hold Right Command to record, release to transcribe and paste.

Transcribing files

v2t transcribe points the same local models at audio already on disk — anything ffmpeg reads (.opus, .m4a, .mp3, video files too), no microphone or permissions involved.

v2t transcribe memo.opus              # prints the transcript, copies it to the clipboard
v2t transcribe memo.opus > notes.txt  # redirected: clean text, no clipboard copy
v2t transcribe *.m4a                  # several files, each under a `# filename` heading
v2t transcribe --clean memo.opus      # add the LLM cleanup pass (--casual / --strict imply it)

Files transcribe verbatim by default: cleanup rewrites, and that is rarely what you want for someone else's voice note. Every step reports live elapsed time — a 3½-minute WhatsApp note takes about 11s with Parakeet on an M1 Max (~19× realtime). Each result is appended to the same history as your dictations, with the file it came from (save_history = false to opt out).

Strict vs Casual

Raw transcription Strict Casual
"Hey um I'll see you tomorrow at 9 actually no make it 10" "Hey, I'll see you tomorrow at 10." "Hey, I'll see you tomorrow at 9, actually no, make it 10."
"So basically I was thinking we could um you know maybe try the other approach" "I was thinking we could try the other approach." "So basically, I was thinking we could maybe try the other approach."

Strict (default) removes fillers, restructures for clarity. Casual only adds punctuation and removes "um/uh", keeping your phrasing.

Config — ~/.v2t/

Everything lives in one directory (override with $V2T_HOME, or $XDG_CONFIG_HOME/v2t):

~/.v2t/
  config.toml                    # all settings (v2t config --init to create)
  history/transcriptions.jsonl   # every transcription + metadata (toggle in config)
  run/                           # private runtime status + log

config.toml (every key optional — these are the defaults):

[transcription]
backend = "parakeet"   # parakeet (MLX) | whisper
model = ""             # blank = backend default

[cleanup]
enabled = true
engine = "mlx"         # mlx (in-process via mlx-lm) | ollama
model = ""             # blank = engine default
mode = "strict"        # strict | casual

[hotkey]
key = "cmd_r"          # cmd_r | cmd_l | alt_r | alt_l | ctrl_r | ctrl_l

[behavior]
pause_music = false
save_history = true

Cleanup engine

Cleanup runs in-process via mlx-lm by default (Qwen2.5-1.5B-Instruct, non-thinking) — no daemon, no HTTP, same MLX stack as transcription.

Already running Ollama? Switch to it (v2t setup offers this when it detects Ollama, or edit the config):

[cleanup]
engine = "ollama"
model = "qwen3:4b-instruct-2507"   # then: ollama pull qwen3:4b-instruct-2507

Either way, use a non-thinking model — a model that emits <think> blocks will paste its reasoning. The defaults don't.

Optional menu-bar app

v2t menubar install      # compile the bundled Swift file into ~/Applications/Voice2Text.app

The optional app is a single, inspectable Swift source file — no window, Xcode project, AppleScript, or separate settings system. It exists because macOS only grants microphone access to a real app identity. The menu requests the two native grants, starts one long-running Python process, shows state immediately, and links to config, history, and log. Run v2t in a terminal instead if you do not want the menu.

Start v2t loads Parakeet and the cleanup model once, then keeps them warm for every transcription. To start the same menu app at login, install the optional per-user LaunchAgent:

v2t service install       # install + start ~/Library/LaunchAgents/com.lucharo.voice2text.plist
v2t service status
v2t service uninstall

The service starts the same Voice2Text.app bundle, so manual and login launches share one stable permission identity. The engine lock still prevents duplicate Python processes.

Permissions. v2t needs Microphone (record) and Accessibility (global hotkey + paste). A terminal launch uses your terminal's grants. The menu app requests its own grants and shows their live state as flat rows; click a missing row to open the exact Settings pane.

Models & benchmarks

The contributor-only just bench harness writes ~/.v2t/benchmarks/results/<date>-<host>.md — two tables (speech-to-text RTF and cleanup TTFT/total), one column per model. It lives in the dev group, not the installed CLI. See benchmarks/ for the method and defaults.

default why
transcription parakeet-tdt-0.6b-v3 fastest on Apple Silicon, multilingual
cleanup Qwen2.5-1.5B-Instruct (mlx-lm) 831 MB active in the local cleanup benchmark; non-thinking, no daemon

This is macOS / Apple Silicon-only by design (MLX, native pasteboard/event APIs, nowplaying-cli, System Settings permission URLs). Fork it for Linux/Windows if you like.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

voice2text-0.3.0.tar.gz (48.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

voice2text-0.3.0-py3-none-any.whl (38.7 kB view details)

Uploaded Python 3

File details

Details for the file voice2text-0.3.0.tar.gz.

File metadata

  • Download URL: voice2text-0.3.0.tar.gz
  • Upload date:
  • Size: 48.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.31 {"installer":{"name":"uv","version":"0.11.31","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for voice2text-0.3.0.tar.gz
Algorithm Hash digest
SHA256 c56fbbdd0b74d028533ea1229a091797ef3363dee9be34da8f8c784d7a4f6553
MD5 824c659a20a04123d616dd8ab5a84f65
BLAKE2b-256 6a811ab8f9ad4ef02080c56fdf711050f0ad4e1aec4e8b2baee64dfc5bcb1a75

See more details on using hashes here.

File details

Details for the file voice2text-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: voice2text-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 38.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.31 {"installer":{"name":"uv","version":"0.11.31","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for voice2text-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c5ad9df3af9c0dcc5736b60e04744362f3182836ef22ce699f3bc1cb2b64ab38
MD5 4db4bb63543013a8a56270ae72ce2d44
BLAKE2b-256 9515c009ae4d51574781cd55c509d00f95873f9eddd451d35dd39ffca17e91b3

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 files

0.2.0

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page