Hold a key, say what you want, let go — the words type themselves into whatever window your cursor is in. Chat box, code editor, email, anywhere. It runs entirely on your own machine: no API key, no subscription, no audio ever leaving your laptop. $0, forever. The catch is that on Windows it usually refuses to start, and the error it gives you is a lie. This guide is the fix — and the working repo.
No window to open. No "upload your audio." No button to click when you're done. You hold a key you never use anyway — Pause/Break, sitting there unloved since 1995 — and the text lands wherever you were already typing.
Here's the part people underestimate: you talk about three times faster than you type, and you think out loud better than you write. Long, rambling, context-rich prompts stop being a chore. That changes what you bother to ask an AI, which is the actual win.
Whisper is OpenAI's speech model, but you do not have to call OpenAI to use it. The weights are public. One config line decides everything:
# src/config.yaml model_options: use_api: false # ← the whole value proposition
false means the model runs on your own hardware. Three consequences, all good:
One naming trap worth flagging: there is a paid product literally called WhisperKey. Different thing, costs money. What you want is whisper-writer — open source, MIT, free.
You install it. You run it. You get this:
So you go install the NVIDIA CUDA Toolkit. Doesn't help. You download cuDNN, make an account, unzip it into your CUDA folder. Doesn't help. You add things to PATH, reboot, try a different Python. Doesn't help. Somewhere around hour three you conclude your machine is cursed.
Your machine is fine. The DLL is already on your disk. It shipped inside your virtual environment as a pip wheel — it's sitting in venv\Lib\site-packages\nvidia\cudnn\bin right now. Windows just isn't looking there.
And it gets one turn worse. PyQt5 and pynput — the window toolkit and the keyboard hook — mutate the DLL search path when they import. So even registering the right directory doesn't stick if you do it too late.
# src/main.py — this must run BEFORE PyQt5/pynput are imported if sys.platform == 'win32': for _pkg in ('cublas', 'cudnn', 'cuda_nvrtc'): _dll_dir = os.path.join(sys.prefix, 'Lib', 'site-packages', 'nvidia', _pkg, 'bin') if os.path.isdir(_dll_dir): os.add_dll_directory(_dll_dir) import ctranslate2 # initialize CUDA before PyQt/pynput poison the loader import faster_whisper
Point Windows at the wheel directories, then load the CUDA library first, before anything else gets a chance to rearrange the furniture. That's it. That's the three hours.
The transferable lesson: when a library says a file is missing, check whether it's actually missing before you start installing things. "Not found" and "not found yet, from here, in this order" are different bugs, and only one of them is fixed by downloading more software.
Second scar. Even with the DLLs loading, running a GPU model in the same process as a desktop window toolkit can hang the whole app — Qt's event loop and CUDA's streams deadlock, and you get a frozen tray icon and no error at all.
The fix is architectural and it's a pattern worth having in your pocket: when two libraries can't share a process, stop making them. The model lives in a subprocess that never imports Qt. The app talks to it over plain stdin/stdout:
Length-prefixed framing, so you always know where a message ends. JSON for structure, raw bytes for the audio — no pickle, no deserializing anything you wouldn't want to execute. And because the worker stays alive between requests, the model loads exactly once, at startup. Every transcription after that is pure inference, which is why it feels instant.
One running instance shows up as about six pythonw.exe processes in Task Manager. Everyone's first instinct is to kill five of them. Don't — you'll tear down your own app.
The venv's launchers are redirector stubs: each logical process (run.py, main.py, the worker) appears as a stub plus the real interpreter it delegates to. Three processes × two = six. Check worker.log for a single model ready line and you'll see the model loaded once, exactly as intended.
No NVIDIA card? Work laptop, ThinkPad, MacBook-shaped life? You lose the "blazing" and keep the "useful." Swap two lines and install the CPU requirements, which drop about a gigabyte of CUDA wheels you'd never load:
# src/config.cpu.yaml — copy over src/config.yaml model: base.en # tiny.en if your machine is slow, small.en for accuracy device: cpu compute_type: int8 # float16 is GPU-only
Measured on a normal laptop: roughly 8× faster than real time, near-perfect on clean speech. It will fumble a proper noun now and then.
Which does not matter — and this is the reframe that makes the CPU path genuinely good rather than a consolation prize. An LLM is reading this, not your boss. Claude does not care that "Postgres" came out "post grass." It has the context to fix it silently. You are not writing; you are steering. Optimize for speed, not for perfection you don't need.
| path | model | speed | good for |
|---|---|---|---|
| NVIDIA GPU | large-v3-turbo | near-instant | everything, including punctuation you'd publish |
| CPU only | base.en | ~8× real time | talking to an AI, notes, prompts, search |
Why the GPU model is quick despite being called "large": large-v3-turbo is large-v3 with the decoder pruned from 32 layers down to 4 — about 8× faster to decode, with accuracy essentially intact. And there's no PyTorch anywhere in this install. It runs on CTranslate2 directly: 80 packages, no multi-gigabyte download.
You don't have to read a line of Python to get this running. Below is the brief — paste it into Claude Code (or whatever you drive) and let it do the install, including the two traps that cost me the evening. It's written so the assistant explains as it goes.
All the real speech-to-text work is savbell/whisper-writer — excellent, MIT, credit where it's due. My fork adds the four things that make it actually start and stay running on Windows:
That last one is worth a sentence, because it's the meta-lesson: upstream pins ctranslate2 4.2.1, which needs cuDNN 8, against wheels that ship cuDNN 9. That mismatch is the DLL error. A lockfile that isn't verified is just a rumor. Both of mine were installed from scratch and run before this page went up.
Below is the loader fix in full — the highest-value 20 lines in the repo, and the piece you'd want even if you never use this app.
★ whisper-writer-gpu on GitHub (MIT)Clone → py -3.11 -m venv venv → pip install -r requirements.txt → python run.py. Needs Python 3.11 specifically. First run downloads the model. Then hold Pause and talk.