Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
talk-rs
⚠️ Disclaimer: this project is entirely "vibe coded".
Every line of code, test, and documentation in this repository was produced by an AI coding agent under human direction. No part of it has been written, line-by-line audited, or formally reviewed by a human engineer. It works on the author's machine and ships with tests, but treat it accordingly: read the source before trusting it with anything important, and expect rough edges, dead code paths, and the occasional architectural oddity that a human would have caught.
Voice dictation for Linux. Record, transcribe, and paste text into any application -- all from a single keyboard shortcut.
talk-rs captures audio from your microphone, sends it to a
transcription API (Mistral Voxtral or OpenAI Whisper), and types the
result into the focused window. A small X11 overlay badge shows the
current state (recording / transcribing) so you always know what is
happening.
Features
- Dictation workflow -- press a key to start recording, press again to stop; transcribed text is pasted automatically.
- Multiple providers -- Mistral (Voxtral) and OpenAI (Whisper / GPT-4o) for batch transcription; Mistral and OpenAI realtime streaming via WebSocket.
- Text-to-speech -- the
speakcommand synthesizes text and plays it (or saves a WAV). Two providers mirror the transcription side:kokoro(local, offline, multi-language via sherpa-onnx) andmistral(remote Voxtral TTS). Language handling is config-driven and agnostic. - Speaker diarization -- identify who is speaking (
--diarize); output is tagged with speaker labels. Currently supported with Mistral V2 models in batch mode. - Multi-candidate picker -- with
--pick, run several providers in parallel and choose the best transcription from a GTK picker window. A waterfall spectrogram of the recording is shown above the candidate list; it loads asynchronously and adapts to window width. Non-default models are transcribed on demand via a per-candidate button to avoid unnecessary API calls. - Visual overlay -- non-intrusive X11 badge at the top of the screen (works without a compositor).
- Live transcription overlay -- a dynamic phase-coloured waterfall replaces the static "transcribing" badge; three independent throughput tracks (upload bytes, download bytes, paste characters) show progress through the pipeline, with a time-grid layer over the spectrogram.
- Dead audio detection -- overlay badge shows a red prohibit icon and "NO SOUND" warning when no real microphone is detected (e.g. headset unplugged); a centered full-screen overlay and repeating alert tone reinforce the signal, and a notification also appears on the text panel.
- Auto-pause -- automatically pauses audio forwarding during
silence, trimming dead air from transcription input. Resumes
instantly with a 300 ms lookback buffer to preserve speech onset.
The badge shows yellow pause bars and "LISTENING" during pauses.
Disable with
--no-auto-pause. - Audio visualizers -- optional in-badge visualization during
recording (
--viz waterfall,--viz amplitude,--viz spectrum); monochrome mode with--mono. - Audio feedback -- start/stop tones plus a periodic boop while
the badge shows
LISTENING(i.e. during auto-pause silence); the boop is silent while you are actively speaking, and is never heard when--no-auto-pauseis used. Fully configurable or disabled. - Bluetooth headset auto-switch -- when a Bluetooth headset is
connected in A2DP mode (high-quality stereo, no microphone),
talk-rsautomatically switches it to its Hands-Free Profile (HFP) for the duration of the recording so the headset microphone is available, then restores the original profile on stop. Survives unclean termination: a state file at$XDG_RUNTIME_DIR/talk-rs/card-profile.jsonis written before the switch, and the next invocation restores the original profile from that file before starting a new recording. Works with any HFP-capable headset (uses PulseAudio's standarddevice.form_factorproperty, not vendor-specific identifiers). Disable per-invocation with--no-bt-auto-switchor globally via theaudio.bt_auto_switchconfig key. - Context bias -- supply domain-specific vocabulary to improve transcription accuracy.
- Daemon toggle mode -- first invocation spawns a short-lived daemon that records; second invocation signals it to stop, transcribe, paste, and exit. Ideal for global shortcuts.
- No idle daemon -- between dictations, zero
talk-rsprocesses stay resident. The toggle daemon spawns on press, records, and exits after pasting. No memory footprint when idle. - Retry last -- re-transcribe the last cached recording without
speaking again (
--retry-last). - Recordings browser --
record --uiopens a GTK4 window listing all recordings (OGG fromoutput_dir) and dictation cache (OGG). Imported audio dropped intooutput_dir--.m4a,.mp4, or.aac(e.g. iPhone voice memos) -- is listed and playable alongside native OGG recordings, with the same waterfall and transcription workflow. Play, delete, open in the file manager, or transcribe on demand. A waterfall spectrogram is shown for every recording, even those without transcripts; sections auto-refresh via inotify when files change externally. - Per-segment timing export --
--output-yamlincludes per-segment start/end timestamps in the metadata sidecar, suitable for subtitles and post-processing. - Standalone commands --
recordandtranscribecan be used independently for scripting. - Environment overrides -- every config value can be set via
TALK_RS_*environment variables.
Prerequisites
Build dependencies
# Debian / Ubuntu
# Fedora
libpulse-dev / pulseaudio-libs-devel provides the PulseAudio client
library used to switch a Bluetooth headset's PulseAudio card profile
between A2DP (high-quality stereo) and HFP (microphone-enabled) when
recording. On PipeWire systems, pipewire-pulse supplies the
libpulse.so shared object at runtime, so no PulseAudio daemon is
needed -- only the development headers for compilation.
A working Rust toolchain is required (1.87+). Install via rustup if needed.
Runtime dependencies
PipeWire must be running (used for audio capture). Most modern Linux
desktops ship with PipeWire by default.
An API key for at least one transcription provider is required:
Installation
If building from a git clone, run ./autogen.sh first to resolve
version placeholders in Cargo.toml:
The binary is at target/release/talk-rs. Copy it somewhere in your
$PATH:
Configuration
talk-rs reads $XDG_CONFIG_HOME/talk-rs/config.yaml (typically
~~/.config/talk-rs/config.yaml~).
Copy the example and fill in your values:
Minimal working configuration:
output_dir: ~/talk-rs-output
providers:
mistral:
api_key: YOUR_MISTRAL_API_KEY
Required fields
| Field | Description |
| output_dir | Absolute path to a writable directory for recordings (a leading ~~~ is expanded to your home) |
| providers.mistral.api_key | Mistral API key (if using Mistral) |
| providers.openai.api_key | OpenAI API key (if using OpenAI) |
Optional fields
| Field | Default | Description |
| providers.mistral.url | https://api.mistral.ai | Mistral API base URL |
| providers.mistral.model | voxtral-mini-2507 | Mistral transcription model |
| providers.mistral.context_bias | none | Comma-separated words for accuracy |
| providers.mistral.tts_model | voxtral-mini-tts-latest | Voxtral TTS model for speak --provider mistral (shares the STT api_key) |
| providers.mistral.tts_voice | none | Default Mistral preset voice id for speak (else pass --voice) |
| providers.openai.url | https://api.openai.com | OpenAI API base URL |
| providers.openai.model | gpt-transcribe | OpenAI batch model |
| providers.openai.realtime_model | gpt-live-transcribe | OpenAI realtime model |
| providers.openai.prompt | none | Prompt for spelling, punctuation, and context hints |
| providers.openai.keywords | none | Expected vocabulary list for the new models |
| providers.openai.languages | none | Ordered expected-language list |
| providers.openai.realtime_delay | none | Realtime delay: minimal, low, medium, high, or xhigh |
| providers.kokoro.model_dir | XDG data dir | Kokoro TTS model cache dir (auto-downloaded on first speak) |
| providers.kokoro.voice | per-language default | Default Kokoro voice name (af_heart, ff_siwis, …) |
| providers.kokoro.num_threads | 4 | Kokoro inference threads |
| providers.kokoro.lang | en (model-baked) | Default Kokoro phonemization language |
| transcription.default_provider | mistral | Default transcription provider when unspecified |
| speak.default_provider | kokoro-if-configured | Default speak provider (kokoro or mistral) |
| indicators.boop_interval_ms | 5000 | Periodic boop interval in ms (0 disables boops; also --no-boop) |
| indicators.visual_overlay | true | Show X11 overlay badge |
| indicators.viz | none | In-badge visualizer: waterfall, amplitude, or spectrum (also --viz; env TALK_RS_INDICATORS_VIZ) |
| indicators.mono | false | Monochrome visualizer (also --mono) |
| paste.chunk_chars | 150 | Max chars per paste chunk (0 disables chunking; also --no-chunk-paste) |
| audio.bt_auto_switch | true | Auto-switch a connected Bluetooth headset to HFP for the duration of a recording, then restore (also --no-bt-auto-switch; env TALK_RS_AUDIO_BT_AUTO_SWITCH) |
| recording.sample_rate | 48000 | Sample rate (Hz) of the record command's .ogg output (env TALK_RS_RECORDING_SAMPLE_RATE) |
| recording.channels | 1 | Channels for record output: 1 mono, 2 stereo (env TALK_RS_RECORDING_CHANNELS) |
| recording.bitrate | 128000 | Opus bitrate (bps) of the record command's .ogg output (env TALK_RS_RECORDING_BITRATE) |
The recording.* settings control the quality of recordings meant
for a human to listen to or share. They are independent of
transcription: audio sent to the providers is always downsampled to
16 kHz mono internally (both Voxtral and Whisper operate at 16 kHz),
so these knobs do not affect transcription accuracy or upload size.
OpenAI model migration
The OpenAI batch default is exactly gpt-transcribe, and the realtime
default is exactly gpt-live-transcribe. Existing configuration files
remain valid: prompt, keywords, languages, and realtime_delay are
optional, and omitting them sends no hint fields.
gpt-transcribesendsprompt, repeatedkeywords[], repeatedlanguages[], andresponse_format=jsonto the batch endpoint.gpt-live-transcribenestsmodel,prompt,keywords,languages, anddelayundersession.audio.input.transcriptionin the realtimesession.update.whisper-1remains available for segment/word timestamps, subtitles, and translation workflows. It retainsresponse_format=verbose_json, acceptspromptand one unambiguous singularlanguage, and does not acceptkeywordsor multiple languages.gpt-4o-transcribeandgpt-4o-mini-transcriberemain available as legacy batch choices.gpt-realtime-whisperremains available as a legacy realtime choice, using singularlanguagefor one expected language and rejectingkeywords, multiple languages, andrealtime_delay.
Configured hints that the selected model cannot represent fail locally before HTTP or WebSocket traffic; talk-rs never silently drops them. There is no structured previous-turn configuration field in the official schema. Earlier-turn context is service-managed where supported.
Direct-OpenAI prices checked 2026-07-31: gpt-transcribe costs
$0.0045/min ($0.27/hr), while whisper-1 costs $0.006/min ($0.36/hr).
The new batch default is 25% lower, a $0.09 per hour savings.
Environment overrides
Every config value can be overridden via environment variables:
TALK_RS_PROVIDERS_OPENAI_KEYWORDS and
TALK_RS_PROVIDERS_OPENAI_LANGUAGES are comma-separated lists. talk-rs
trims whitespace and ignores empty entries deterministically; environment
values override YAML in both existing and environment-created OpenAI
sections.
See config.example.yaml for the full list.
Usage
Global options
| Flag | Effect |
| -v | Increase logging verbosity (-vv debug, -vvv trace) |
| --log-file <PATH> | Write logs to a file in addition to stderr (env TALK_RS_LOG_FILE); propagated to the toggle daemon |
Dictate (main workflow)
Record, transcribe, and paste into the focused application:
Toggle mode (ideal for keyboard shortcuts):
First call starts a background daemon that records. Second call stops recording, transcribes, and pastes the result.
Options:
| Flag | Effect |
| --toggle | Daemon toggle mode |
| --provider | Choose mistral or openai |
| --model | Override model for this invocation |
| --diarize | Enable speaker diarization (batch mode only) |
| --realtime | Stream audio via WebSocket (incremental text) |
| --pick | Show multi-candidate picker (GTK window) |
| --retry-last | Re-transcribe the last cached recording |
| --replace-last-paste | Delete previous paste before inserting new text |
| --save <PATH> | Save audio recording to a file |
| --output-yaml <FILE> | Write transcription metadata YAML |
| --input-audio-file <FILE> | Feed a pre-recorded audio file instead of live mic |
| --monitor | Mix system audio (monitor) with mic input |
| --no-sounds | Disable audio indicators |
| --no-boop | Disable periodic boop sounds (keep start/stop) |
| --no-chunk-paste | Paste all text in one shot (disable chunking) |
| --no-overlay | Disable visual overlay |
| --no-auto-pause | Disable auto-pause during silence (forward all audio) |
| --no-paste | Skip pasting transcription into the focused application |
| --upload-format <FORMAT> | Audio format for batch uploads: wav (default) or ogg |
| --viz <MODE> | In-badge visualizer: waterfall, amplitude, or spectrum |
| --mono | Monochrome visualizer (theme-aware) |
| --no-bt-auto-switch | Disable Bluetooth headset HFP auto-switch (overrides audio.bt_auto_switch) |
Record
Capture audio to an OGG/Opus file:
Toggle mode starts a background recorder on the first call. The second
call sends SIGINT; the recorder stops capture, finalizes and syncs the
audio file, then exits. Its PID remains published during finalization,
so repeated stop calls cannot start a second writer for the same toggle
slot. Toggle mode conflicts with --ui.
Foreground and toggle recording use the same feedback as dictation: the
start tone finishes before capture begins, an optional X11 badge visualizes
the captured PCM, and the periodic boop sounds only while the badge detects
silence. On stop, the boop and badge are torn down before capture closes.
The stop tone sounds only after the encoder has finalized the file and
sync_all() has made it durable. If X11 is unavailable, recording and
sound feedback continue without the badge.
Options:
| Flag | Effect |
| --toggle | Toggle background recording on the first/second call |
| --monitor | Mix system audio (monitor) with microphone input |
| --no-sounds | Disable start, stop, and boop sounds |
| --no-boop | Disable silence-gated boops (keep start/stop) |
| --no-overlay | Disable the recording badge |
| --viz <MODE> | In-badge visualizer: waterfall, amplitude, or spectrum |
| --mono | Monochrome visualizer (theme-aware) |
| --ui | Open GTK4 recordings browser (play, delete, open folder) |
| --no-bt-auto-switch | Disable Bluetooth headset HFP auto-switch (overrides audio.bt_auto_switch) |
Toggle state is stored under $XDG_CACHE_HOME/talk-rs/ (normally
~~/.cache/talk-rs/~). Dictation keeps its existing daemon.pid,
daemon.lock, and daemon.log files. Standalone recording uses the
separate record.pid, record.lock, and record.log files, so the two
commands cannot stop or overwrite each other's toggle state.
Transcribe
Transcribe an existing audio file:
Options:
| Flag | Effect |
| --provider | Choose mistral or openai |
| --model | Override model for this invocation |
| --diarize | Enable speaker diarization (tag by speaker) |
Provider overload
Cloud providers periodically answer 503 high load or
429 backend_out_of_capacity, especially on long recordings.
talk-rs treats every 5xx and 429 as "busy, try again later" and
waits between attempts: 5 s, 15 s, 30 s, 60 s, 120 s, 120 s (about
5.5 minutes in total, seven attempts). A Retry-After header from
the provider overrides the schedule slot (capped at 120 s). Each wait
is logged at -v as server retry N/6. Other 4xx answers are
permanent and fail immediately.
Connection failures (DNS, unreachable host, TLS timeout) use a separate, shorter schedule and never consume the server-retry budget. Large uploads are given the whole request time budget on every attempt, so an 80-minute recording is not cut off by the early connection slots.
If all seven attempts fail the recording is kept in the cache;
re-run transcribe on the .ogg (or dictate --retry-last) once
the provider has recovered.
Speak (text-to-speech)
The architectural mirror of transcribe: text in, speech out.
Synthesize text and play it through the speakers, or save it to a
WAV file. Two providers -- kokoro (local, offline, on-device via
sherpa-onnx) and mistral (remote Voxtral TTS).
|
Text is resolved in priority order: the positional argument, then
--file, then stdin (when stdin is not a TTY).
The default provider is resolved as --provider >
speak.default_provider (config) > the local kokoro backend when a
providers.kokoro section exists, else mistral. On first use the
Kokoro model (350 MB) is downloaded after an interactive consent
prompt (or a non-interactive proceed-with-log, exactly like the
Parakeet ASR model) into
~~/.local/share/talk-rs/models/kokoro-multi-lang-v1_0/.
Options:
| Flag | Effect |
| --provider | Choose kokoro (local) or mistral (remote Voxtral) |
| --voice | Kokoro voice name (af_heart, am_michael, ff_siwis, …) or a Mistral preset voice id |
| --lang | Phonemization language for Kokoro (en, fr, …); ignored by Mistral |
| --speed | Speech rate multiplier for Kokoro (1.0 = normal) |
| -f, --file | Read the text to speak from a file |
| -o, --output | Save synthesized audio to a WAV file instead of playing it |
Language handling is config-driven and agnostic: the requested
language selects the phonemizer. The stock Kokoro model ships one
baked language; other languages are derived on demand by patching the
model's ONNX voice metadata, cached as model-<lang>.onnx next to
the stock model.
Supported input audio formats
Commands that read audio from disk -- transcribe,
dictate --input-audio-file, dictate --retry-last, and the
record --ui recordings browser -- accept the following formats:
| Extension | Container / codec | How it is handled |
| .ogg | Ogg Opus | Native format written by talk-rs record |
| .m4a / .mp4 | MP4 / AAC | Decoded via symphonia; ideal for iPhone voice memos and similar imports |
| .aac | Raw AAC stream | Decoded via symphonia |
| .wav | 16-bit PCM | Decoded directly; primarily used for legacy cache entries |
Imported files keep their original extension on upload, so the
transcribe command sends .m4a to the provider as-is -- both
Mistral Voxtral and OpenAI Whisper accept these natively. Playback
and waterfall spectrograms in the records browser work for every
listed format.
GNOME keyboard shortcut
Bind talk-rs dictate --toggle to a key (e.g. Super+/):
BASE="org.gnome.settings-daemon.plugins.media-keys"
BPATH="/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings"
CURRENT=
N=0
while | ; do
N=
done
SLOT="/custom/"
SCHEMA=".custom-keybinding:"
if [; then
else
fi
First press starts recording, second press stops, transcribes, and pastes into the focused application.
To change the key, replace <Super>slash with the desired binding
(e.g. <Super>semicolon, <Super>d). Add --realtime to use
streaming transcription instead of batch mode.
Development
Cargo feature flags
The default build enables everything (full dictation CLI). Library
consumers can opt out of the desktop stack with
default-features = false:
| Feature | Default | Pulls in | Provides |
| (core) | always | reqwest, tokio-tungstenite, opus, rubato, symphonia, ... | Transcription providers (Mistral/OpenAI batch + realtime WS), Mistral Voxtral TTS, resampler, Opus/OGG encode, file decode, config, cache |
| parakeet | yes | sherpa-onnx (static C++), tar, bzip2 | Local ASR backend (Parakeet TDT, CPU) |
| kokoro | yes | sherpa-onnx (static C++), tar, bzip2 | Local TTS backend (Kokoro multi-lang, CPU) for speak |
| playback | yes | cpal | Local audio playback (the speak command's speakers output; shared player) |
| capture | yes | pipewire, cpal (via playback), libpulse-binding | Live mic/monitor capture, BT headset HFP switch, indicator tones |
| ui | yes | gtk4, x11rb, png, fontdue, dark-light, ... (+ playback) | X11 overlay, visualizers, clipboard/paste, picker, recordings browser |
Example headless consumer (cloud transcription only):
= { = "../talk-rs", = false }
License
MIT -- see LICENSE.