Files
blurt/README.md
T
felixmandClaude Opus 5 e3faf32f55 Fix Linux capture: negotiate rate, condition audio, pin the device
Transcripts were far worse on Linux than on Windows for the same model, because
of what reached the API rather than the model itself.

Picking any mic from the tray menu never worked: PortAudio exposes the friendly
names through PipeWire's JACK host API, which only accepts 48 kHz, so opening at
16 kHz failed outright and recording always fell back to the system default —
whichever source PipeWire happened to prefer at that moment. That source is
often the raw AMD digital mic, which ships a +0.14 FS DC bias, a full-scale
clipped click in the first 15 ms of every stream, and speech around -50 dBFS.
Windows' vendor driver hides all of this; PipeWire hands over the raw stream.

- Negotiate rate and channels per device, then downmix and resample with
  resample_poly, so every listed device can actually be recorded from.
- Hide devices that cannot work: playback endpoints JACK re-exposes as inputs,
  and raw ALSA hw: devices a sound server already owns (acp: hw:2,0 blocks
  forever on open).
- Persist the chosen device by name in device.txt; PortAudio indices shift as
  USB gear comes and goes. Add a "System default" menu entry.
- Condition every clip before upload: trim the warm-up click, high-pass out DC,
  normalise towards -20 dBFS under a -1 dBFS ceiling.
- Refuse near-silent clips instead of paying for a hallucination, and log
  capture levels (speech, rms, peak, DC, clipping) plus PortAudio status
  warnings, which previously went to a discarded stderr.
- Import scipy.signal as dsp: it was shadowing the stdlib signal module that
  the SIGUSR1 hotkey toggle depends on.

mic_check.py records from Blurt's configured input, reports levels before and
after conditioning, saves the WAV, and transcribes it with the current model.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 18:58:39 -04:00

76 lines
3.1 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.
Every clip is conditioned before upload — the first 150 ms are dropped (mics
click when the stream opens), DC bias is filtered out, and the level is
normalised towards -20 dBFS. Near-silent clips are refused instead of being
sent, since transcription models answer silence with confident hallucinations.
## 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.)