Local speech-to-text for Linux. Record from the command line or a desktop keybinding, transcribe locally with NVIDIA Parakeet or Whisper, and deliver the result to the clipboard or the focused window.
Local Wisper keeps the transcription model warm in a background daemon, which makes repeated recordings and integrations such as Sway and Neovim much faster.
- Linux
- Python 3 with virtual environment support
pw-record(PipeWire) orffmpegwith PulseAudio input supportwl-copy,xclip, orxselfor clipboard outputwtypewhen typing directly into a Wayland window- NVIDIA GPU support is optional; CPU transcription works out of the box
git clone https://github.com/none23/local-wisper.git
cd local-wisper
python -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
./install.shThe installer creates:
~/.local/bin/lw, pointing at this checkout and its virtual environment~/.config/local-wisper/env, containing integration defaults~/.config/local-wisper/glossary.txt, containing reusable transcript corrections
Existing configuration files are left untouched. Make sure ~/.local/bin is in PATH, then run:
lw --helpStart an interactive recording session:
lw --backend parakeet --device cuda --compute-type float16 --no-vad-filterPress Enter to finish recording. The transcript is copied to the clipboard, and the model remains available through the background daemon for the next recording.
Whisper on CPU:
lw --backend whisper --model small --compute-type int8 --device cpuUseful commands:
lw: record interactively and transcribelw preload: start the daemon and load the model ahead of timelw sway-start: begin a detached recordinglw sway-stop: stop a detached recording and deliver its transcriptlw sway-cancel: discard a detached recordinglw sway-toggle: start or stop a detached recording
Run lw --help for recording, daemon, output, and post-processing options.
The supplied wrapper reads ~/.config/local-wisper/env, forwards its settings to lw, and uses wtype to type completed transcripts into the focused window.
For a new Sway setup, copy it into your configuration:
install -Dm755 integrations/sway/local-wisper.sh ~/.config/sway/scripts/local-wisper.shA minimal Sway configuration looks like this:
set $local_wisper $HOME/.config/sway/scripts/local-wisper.sh
set $mode_local_wisper local-wisper
exec_always $local_wisper preload
mode "$mode_local_wisper" {
bindsym $mod+grave mode "default", exec $local_wisper sway-stop
bindsym Return mode "default", exec $local_wisper sway-stop
bindsym Escape mode "default", exec $local_wisper sway-cancel
}
bindsym $mod+grave exec $local_wisper sway-start, mode "$mode_local_wisper"
The generated environment file defaults to Parakeet on CUDA with direct typing. Common overrides are:
export LW_BACKEND='parakeet'
export LW_COMPUTE_TYPE='float16'
export LW_DEVICE='cuda'
export LW_VAD_FILTER='false'
export LW_OUTPUT_MODE='type' # use "clipboard" to disable wtype outputSway users upgrading an existing checkout do not need to copy the new wrapper or run the installer again. The existing wrapper continues to call the unchanged lw command and LW_* interface.
Local Wisper always performs conservative local cleanup. Optional OpenAI post-processing can improve punctuation and recurring technical terms. Luna post-processing runs with reasoning disabled:
export OPENAI_API_KEY='...'
export LW_POST_PROCESS_MODEL='gpt-5.6-luna'
export LW_POST_PROCESS_TIMEOUT='20'
export LW_POST_PROCESS_GLOSSARY_FILE="$HOME/.config/local-wisper/glossary.txt"The glossary supports four sections:
[always]
engine x -> nginx
[likely]
cloud code -> Claude Code
[contextual]
codecs -> Codex
[terms]
TypeScript
TanStack Query
[always]applies deterministic, case-insensitive local replacements.[likely]asks model post-processing to prefer the replacement unless context contradicts it.[contextual]applies only when the surrounding text supports the replacement.[terms]supplies preferred spelling and capitalization without inserting absent terms.
Mappings use recognized phrase -> intended output. Blank lines and lines beginning with # are ignored. Existing unsectioned glossary files remain supported as legacy prompt text.
Neovim support remains available as an optional integration. With lazy.nvim:
{
"none23/local-wisper",
config = function()
require("lw").setup({
backend = "parakeet",
device = "cpu",
vad_filter = false,
sample_rate = 16000,
post_process_model = "gpt-5.6-luna",
post_process_glossary_file = "~/.config/local-wisper/glossary.txt",
})
vim.keymap.set("n", "<leader>lw", "<cmd>LW<CR>", { desc = "Local Speech" })
end,
}Use :LW to start recording, then press Enter to stop and insert the transcript below the cursor. Use :LWInstallDeps to install dependencies manually.
If a Python environment is not configured, the plugin creates one at stdpath("data") .. "/lw.nvim/.venv" on first use. The first dependency installation and model preload can take several minutes.
Setup options:
python_bin: explicit Python executable; disables automatic dependency bootstrapvenv_dir: custom plugin virtual environment directoryauto_install_deps: install missing dependencies automatically; defaulttruebackend:parakeetorwhisper; defaultparakeetmodel: model name or pathcompute_type: backend compute typedevice: inference device; defaultcpuvad_filter: enable voice activity detection; defaulttruesample_rate: recording sample rate; default16000recorder_cmd: custom recording command prefixpreload_on_setup: warm the daemon duringsetup(); defaulttruepost_process_model: optional OpenAI text modelpost_process_prompt: custom cleanup promptpost_process_glossary_file: correction glossary pathpost_process_timeout: cleanup timeout in seconds; default20
No system changes are required after merging or pulling this restructure:
- Existing
~/.local/bin/lwlaunchers still execute the rootwisper_cli.pycompatibility entry point. - Existing root
.venvenvironments remain in the same location. - Existing Sway scripts continue using the same commands, environment variables, configuration, state, and cache paths.
- Neovim plugin managers still discover
plugin/lw.luaandlua/lw/init.luaat the repository root. require("lw"),:LW,:LWInstallDeps, and all setup options are unchanged.
Update the checkout with git pull, or update the plugin through the normal Neovim plugin-manager command. You do not need to rerun install.sh, reinstall Python dependencies, or modify Sway or Neovim configuration.
Rerun install.sh only if the checkout itself is moved to another directory, because the installed lw launcher intentionally stores absolute paths to the checkout and its virtual environment.
- Parakeet with
device = "cuda",compute_type = "float16", and VAD disabled is generally the lowest-latency configuration on a supported NVIDIA GPU. - The installed PyTorch wheel supplies the CUDA runtime used by Parakeet; Local Wisper discovers and preloads its NVIDIA libraries automatically.
- Whisper works on CPU out of the box. Whisper CUDA may require a separate CTranslate2-compatible CUDA runtime.
- The daemon socket and Sway recording state remain under
~/.cache/lw.nvim, or$XDG_CACHE_HOME/lw.nvimwhen set.
- Recording fails: install
pw-record, or installffmpegwith PulseAudio support. - Clipboard delivery fails: install
wl-clipboard,xclip, orxsel. - Sway typing fails: install
wtypeand keepLW_OUTPUT_MODE=type. - Neovim dependency installation fails: check
:messages, ensurepython3is available, and rerun:LWInstallDeps. - A moved checkout makes
lwfail: run./install.shagain from the new checkout location.
The primary Python application lives in local_wisper/. Stable launchers remain at wisper_cli.py and scripts/ for existing installations. Optional integrations live in integrations/, with the small root lua/ and plugin/ adapters required by Neovim's runtime discovery.
Run the Python tests with:
python -m unittest discover -s tests -p 'test_*.py' -v