A voice recorder for Omarchy that writes the transcript while you talk.
Pick a microphone, a playing app, or the system output. Press the red button.
Watch the words land as you speak. Stop, and there is an audio file in ~/Recordings
with a .txt of the same name beside it. Nothing ever leaves the machine.
![]() Recording. Live captions arrive every few seconds, the meter shows what the input hears. |
![]() Ready. Input, format, optional title, and the recent takes with play, copy, open, rename, trash. |

Every save posts a notification. Click it to open the folder.
| Microphones, every app that is currently playing audio, and the system output monitor. Choose a browser tab's stream and nothing else on the system ends up in the file. The list refreshes itself as apps start and stop. |
Live captions from a warm whisper-server, toggleable with one switch.
Every chunk clears a Silero voice activity check first, so a pause stays a pause
instead of becoming invented text. When you stop, the whole take is transcribed
again and the sidecar is replaced with the accurate version.
|
Opus, MP3, M4A/AAC, FLAC, WAV. Audio is captured losslessly and encoded on stop, so the format never touches the transcript. Opus at 48 kbps is the default and a one hour talk is about 20 MB. |
| Pause and resume without stitching. A live level meter, and a banner within seconds if the chosen input goes quiet, before you lose a whole take to a muted mic. |
2026-09-05_14-32-10-Weekly-sync.opus next to
2026-09-05_14-32-10-Weekly-sync.txt. Rename or trash from the app and
both files move together.
|
omavoice --toggle starts a take, or stops the one in progress, from any
keybinding. Media players are paused for mic takes and left alone for app captures.
|
On Arch or Omarchy:
git clone https://github.com/nixfred/omavoice.git
cd omavoice
makepkg -siOmavoice then shows up in the Omarchy menu: press Super+Space and type its name.
Everything it needs is in the official repositories:
| Dependency | Why |
|---|---|
python-gobject gtk4 libadwaita |
The window |
pipewire-audio pipewire-pulse libpulse |
Capture and input enumeration |
ffmpeg |
Resampling and encoding |
whisper-cpp ggml-cpu |
Speech recognition, on the CPU, on this machine |
To run straight from a checkout without installing:
python -m omavoiceOmavoice looks for whisper.cpp ggml-*.bin models in ~/.local/share/omavoice/models
and, if you use Voxtype, in ~/.local/share/voxtype/models. With no model present,
open Preferences and download one.
| Model | Size | Use it for |
|---|---|---|
base.en |
142 MB | Live captions on a laptop CPU. The default choice. |
small.en |
466 MB | A stronger final pass. Set it as the final transcript model. |
medium.en |
1.5 GB | Final pass when accuracy matters more than waiting. |
silero-v5.1.2 |
1 MB | Voice activity detection. Fetched automatically, and required for live captions. |
From the shell:
scripts/download-model.sh small.enomavoice --toggle starts a recording, or stops the one in progress, in the running
instance. In Omarchy add a binding to ~/.config/hypr/bindings.lua:
o.bind("SUPER SHIFT, R", "exec", "omavoice --toggle", "Toggle voice recording")Inside the window Ctrl+R does the same, Ctrl+, opens Preferences.
flowchart LR
subgraph Input
MIC[🎙️ Microphone]
APP[🌐 App stream]
MON[🔊 System output]
end
MIC & APP & MON --> PW[pw-record<br/>raw 48 kHz PCM]
PW --> MASTER[(master.raw)]
PW -. every few seconds .-> CUT[cut at the<br/>quietest point]
CUT --> RS[ffmpeg → 16 kHz]
RS --> WS[whisper-server<br/>model stays loaded]
WS --> LIVE[📝 Live transcript]
MASTER -- on stop --> ENC[ffmpeg encode]
ENC --> AUDIO[🎵 2026-09-05_14-32-10.opus]
MASTER -- on stop --> CLI[whisper-cli + VAD]
CLI --> TXT[📄 2026-09-05_14-32-10.txt]
- Capture.
pw-recordstreams raw 48 kHz mono PCM into a master file under~/Recordings/.omavoice-tmp/. Pausing drops incoming blocks, so resume needs no stitching, and a crash leaves the raw audio intact. Microphones are targeted by node name. A sink monitor or a single application's stream is captured withstream.capture.sink = true, which is how PipeWire isolates one app's audio. Explicit targets carrynode.dont-fallbackandnode.dont-reconnect, so a stream that vanishes ends the take with an error instead of quietly recording something else. - Live captions. Every few seconds the newest audio is cut at its quietest point and resampled by ffmpeg. Before whisper sees it, the chunk is put to the Silero voice activity detector, and it is only sent on if there is real speech in it. This matters more than it sounds: handed audio with no voice in it, whisper does not return nothing, it returns fluent invented text. Loudness alone cannot tell the difference, because room tone, music and keystrokes all clear a volume threshold. The chunk is sent with no prompt. Carrying the previous chunk forward as context makes whisper replay that text verbatim over a pause, and makes it delete real words at a boundary it mistakes for a repeat. Live captions refuse to start without the detector rather than run unguarded.
- Finish. ffmpeg encodes the master into the chosen format, the live text is written
as a provisional transcript, and
whisper-cliproduces the final transcript in the background with Silero voice activity detection. The app holds itself alive until that is done, even if you close the window.
Settings live in ~/.config/omavoice/config.json. The whisper server log is in
~/.cache/omavoice/whisper-server.log.
python -m unittest discover -s testsThe PKGBUILD runs the test suite during the build. Pure logic (PCM analysis, chunk cut points, naming, source parsing, config validation, transcript rendering) is covered by unit tests. The recorder, encoder, and whisper integration are exercised by headless end-to-end runs against real PipeWire and whisper.cpp.
whisper.cpp by Georgi Gerganov and contributors. PipeWire and WirePlumber. GTK and libadwaita. Omarchy by DHH and the community.
MIT

