118 lines
5.8 KiB
Markdown
118 lines
5.8 KiB
Markdown
# Blurt
|
|
|
|
Tray-based push-to-talk voice-to-text for Linux and Windows. Toggle recording
|
|
with a hotkey, speak, toggle off — Blurt transcribes the clip and copies the
|
|
text to your clipboard.
|
|
|
|
If a transcript starts with `to my agent`, the rest is dispatched as an agent
|
|
task instead of copied.
|
|
|
|
## Setup
|
|
|
|
Requires [uv](https://docs.astral.sh/uv/) and Python 3.11+.
|
|
|
|
```sh
|
|
uv sync
|
|
```
|
|
|
|
### API key
|
|
|
|
Blurt reads the key for its current model's provider in this order:
|
|
environment variable → OS keyring → local file.
|
|
|
|
| Provider | Env var | Keyring (`service` / `user`) | File |
|
|
|------------|----------------------|---------------------------------|----------------------|
|
|
| OpenAI | `OPENAI_API_KEY` | `openai-api-key` / `felixm` | `api_key.txt` |
|
|
| OpenRouter | `OPENROUTER_API_KEY` | `openrouter-api-key` / `felixm` | `openrouter_key.txt` |
|
|
|
|
Key files and runtime config stay local (gitignored).
|
|
|
|
## Running
|
|
|
|
**Linux** — `uv run blurt` starts the tray icon. Bind a hotkey to
|
|
`./blurt-toggle`, which signals the running instance to start/stop recording.
|
|
|
|
**Windows** — launch `blurt.vbs` (runs Blurt with no console window). The
|
|
global hotkey is **F20**.
|
|
|
|
## Configuration
|
|
|
|
Set from the tray menu, or via files / env vars:
|
|
|
|
- **Model** — `model.txt` / `BLURT_TRANSCRIPTION_MODEL`. For lowest latency use
|
|
`openai/whisper-large-v3-turbo` (OpenRouter, served by Groq).
|
|
- **Language** — `language.txt` / `BLURT_TRANSCRIPTION_LANGUAGE` (`en`, `de`).
|
|
- **Input device** — tray menu (persisted by name in `device.txt` /
|
|
`BLURT_INPUT_DEVICE`). Pick an explicit mic: PortAudio device indices shift
|
|
when USB gear comes and goes, and the OS default source can change under you.
|
|
The menu hides playback endpoints and raw ALSA `hw:` devices that the sound
|
|
server already owns; `BLURT_INPUT_DEVICE` can still name one directly.
|
|
Devices that refuse 16 kHz (raw ALSA and PipeWire's JACK endpoints do) are
|
|
captured at their native rate and resampled.
|
|
|
|
By default, Blurt transcribes while you speak. It conditions the incoming audio,
|
|
then seals and uploads phrases as pauses provide natural boundaries. A chunk is
|
|
at least about 8 seconds long; early cuts require roughly 800 ms of quiet, while
|
|
the required pause falls towards 500 ms as the chunk approaches the 20-second
|
|
target. After 45 seconds it uses the quietest recent point even without a full
|
|
pause, so one uninterrupted passage cannot hold up the whole recording. Pause
|
|
detection follows the microphone's noise floor and only skips near-digital
|
|
silence; ordinary quiet speech and the silence around phrase boundaries remain
|
|
in the audio.
|
|
|
|
Up to three chunks are transcribed concurrently. Their results are retained by
|
|
position and delivered once, in recording order, after you stop and the final
|
|
chunk finishes. Starting another recording does not mix the two sessions, and
|
|
each session keeps the model, language, provider, and API key selection it had
|
|
when recording began even if you change the tray settings meanwhile. The tray
|
|
icon shows progress as chunks finish.
|
|
While recording, the red icon brightens and its three level bars rise with
|
|
microphone volume, settling back down during pauses. This shows incoming audio
|
|
activity; it does not indicate that the transcription service has recognized words.
|
|
|
|
Each session has a recovery directory under `spool/` (gitignored). Sealed chunks,
|
|
completed text, and a checkpoint of the unsealed tail are kept there until the
|
|
complete transcript reaches the clipboard or agent. The tail is checkpointed
|
|
about once a second; an abrupt crash can lose audio since the last checkpoint.
|
|
**Retry failed transcriptions (N)** re-uploads unfinished chunks and reconstructs
|
|
the saved recording, including text that succeeded before a network error,
|
|
timeout, server error, or empty response. A partial result is copied with ` […] `
|
|
and retained for retry; retried results are
|
|
copied rather than dispatched to an agent. The upload timeout scales with each
|
|
chunk: 30 seconds plus 30 seconds per minute of audio, capped at five minutes.
|
|
|
|
Set `BLURT_STREAMING=0` before starting Blurt to use the legacy whole-clip flow.
|
|
That mode trims up to 150 ms of stream-open click, filters DC bias, normalises
|
|
towards -20 dBFS, and rejects near-silent clips. It spools the complete clip
|
|
before upload, splits recordings longer than five minutes near pauses into
|
|
roughly four-minute parallel uploads, retains retryable failures, and marks
|
|
missing chunks with ` […] `. Rejected requests (4xx other than 408 and 429) are
|
|
discarded because retrying the same request cannot fix them.
|
|
|
|
If a transcript comes back missing words, check `blurt.log`; streaming mode logs
|
|
chunk boundaries, noise floor, and `dead=`, the longest run of digital silence
|
|
per chunk, plus the time from Stop to delivery. A non-zero `dead=` can indicate
|
|
an input dropout. Pinning an
|
|
input device avoids the system default moving between sources. In legacy mode,
|
|
set `BLURT_SAVE_CAPTURES=1` (or to a directory) to retain raw and uploaded WAVs
|
|
and hear exactly what the API received.
|
|
|
|
## Checking the microphone
|
|
|
|
`uv run mic_check.py [device] [seconds]` records from Blurt's configured input,
|
|
prints levels before and after conditioning, saves the WAV it would have
|
|
uploaded, and transcribes it with the current model. Use it when transcripts
|
|
come back wrong: bad audio shows up as a DC bias, clipping, or a speech level
|
|
far below -30 dBFS.
|
|
|
|
Logs are written to `blurt.log` in your temp directory (openable from the tray
|
|
menu).
|
|
|
|
## Moving to another machine
|
|
|
|
`uv run export.py` writes a zip of just the source (no 200 MB+ `.venv`, no
|
|
caches, no key files) to your temp directory. It's pure Python, so it runs the
|
|
same on Linux and Windows — no shell needed — and it only reads, so it's safe
|
|
while Blurt is recording. Unzip on the other machine and `uv sync` to rebuild
|
|
`.venv` for that OS. (Or simply `git clone` the repo fresh.)
|