Whisper en local: dictar a tu ordenador sin mandar tu voz a OpenAI
Whisper es el modelo de speech-to-text de OpenAI. Lo conoces probablemente por la API: pagas, mandas un audio, recibes texto. Lo que pocos saben es que el mismo modelo corre en tu propio ordenador sin pagar nada, sin internet y, lo mas importante, sin que tu voz pase por servidores ajenos.
Este post es la receta concreta para montarlo: que paquete instalar, que modelo bajar, como grabar audio desde el microfono, y, sobre todo, como atar todo a un atajo de teclado para dictar en cualquier ventana de tu sistema. El resultado: pulsas una tecla, hablas, sueltas la tecla, y el texto aparece donde tengas el cursor.
Funciona en NixOS, Ubuntu, macOS, Arch, lo que sea. Lo unico que necesitas es un microfono y unos pocos megas de RAM libres.
Por que local y no la API
Whisper de OpenAI tiene tres formas de uso:
| Forma | Latencia | Coste | Tu voz va a⦠|
|---|---|---|---|
| API REST de OpenAI | ~3-5s | $0.006/min | Sus servidores |
Python openai-whisper |
varios seg | gratis | Tu maquina |
whisper.cpp (C++ + GGML) |
sub-segundo | gratis | Tu maquina |
Por que prefiero la tercera:
- Sin coste. Da igual cuanto transcribas. 10 minutos de audio diarios durante un ano son 0 euros.
- Sin internet. Funciona en avion, sin VPN, en una Pi sin red.
- Privacidad real. Si lo que dicto es codigo, una idea, una conversacion privada, nadie escucha.
- Es mas rapida que la API. Cuando tienes GPU, GGML aprovecha, y la API tiene latencia de red que no compensa por mucho que escale.
La unica razon legitima para usar la API es no tener hardware. Si tienes 8 GB de RAM y una iGPU pasable, ya te vale.
whisper.cpp en una linea
# nix-shell o instalado declarativamente:
nix-shell -p whisper-cpp-vulkan
# Bajar modelo (una vez):
mkdir -p ~/.local/share/whisper/models
cd ~/.local/share/whisper/models
curl -LO https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin
# Transcribir un .wav (tiene que ser 16 kHz mono):
whisper-cli -m ~/.local/share/whisper/models/ggml-small.bin \
-f audio.wav -l es -ntEso es todo. whisper-cli mira si hay
GPU al arrancar y la usa via Vulkan. Si no, se va a CPU sin
protestar.
En NixOS: dos paquetes que importan
En el dotfiles de Pascual (clone-first, todas las maquinas iguales)
la pieza vive en modules/home-manager/programs/whisper.nix:
home.packages = lib.optionals (!isAarch64) (with pkgs; [
whisper-cpp-vulkan # backend Vulkan (NVIDIA/AMD) con CPU fallback
ffmpeg-full # grabacion de microfono
sox # rec con deteccion de silencio
pulseaudio # pactl para detectar dispositivos de audio
xdotool # inyectar texto transcrito en la app activa
jq bc # plumbing
]);whisper-cpp-vulkan se compila con
soporte Vulkan y carga el backend en runtime. Si la maquina no tiene GPU
compatible, el mismo binario hace fallback a CPU automaticamente.
Mismo paquete, todas las maquinas, sin condicionales por
hardware. Eso es clone-first puro.
En un Arch o Ubuntu equivalentes:
# Arch
pacman -S whisper.cpp # cuda variant: whisper.cpp-cuda
yay -S whisper-cpp-vulkan-bin
# Debian/Ubuntu (compilar a mano):
git clone https://github.com/ggerganov/whisper.cpp
cd whisper.cpp
cmake -B build -DGGML_VULKAN=1 # o -DGGML_CUDA=1 con NVIDIA
cmake --build build -j
sudo cp build/bin/whisper-cli /usr/local/bin/Que modelo bajar (spoiler: small)
Whisper viene en cinco tamanos. Los relevantes:
| Modelo | Tamano | RAM | Calidad ES | Cuando usar |
|---|---|---|---|---|
tiny |
75 MB | <1 GB | mediocre | Casi nunca |
base |
142 MB | ~1 GB | OK | Maquinas pequenas |
small |
466 MB | ~2 GB | muy buena | El sweet spot |
medium |
1.5 GB | ~5 GB | excelente | Si tienes hardware |
large-v3 |
3 GB | ~10 GB | top | Cuando la calidad importa |
Pascual usa ggml-small.bin (466 MB) en
TODAS las maquinas. Razon:
- En espanol da transcripcion limpia, casi sin errores en audio claro.
- Cabe entero en VRAM de cualquier GPU modesta.
- En el MacBook Pro 2016 (Intel sin GPU dedicada) tarda lo justo para no ser molesto.
large-v3 es perceptiblemente mejor en
audios sucios (ruido, multiples voces, accentos raros) pero no compensa
el tamano y la RAM cuando lo tipico es voz limpia frente al
microfono.
Bajar:
mkdir -p ~/.local/share/whisper/models
cd ~/.local/share/whisper/models
curl -LO https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.binHay versiones quantizadas (ggml-small-q5_0.bin, ~190 MB) para CPU mas
lentas. Probadas, conclusion: el ahorro no compensa la perdida sutil de
calidad. Stick to small.
El comando que importa
whisper-cli \
-m ~/.local/share/whisper/models/ggml-small.bin \
-f audio.wav \
-l es \
-nt \
-t 16Que hace cada flag:
-m: ruta al modelo. Sin esto no arranca.-f: el fichero de audio. Soporta wav, mp3, flac, ogg.-l es: idioma. Si no lo pones, hace auto-detect, que es mas lento y a veces falla con frases cortas. Siempre fija el idioma si lo sabes.-nt: "no timestamps". Por defecto Whisper imprime cada segmento como[00:00:00.000 --> 00:00:03.500] frase. Para uso conversacional eso es ruido; con-ntsolo sale el texto.-t 16: threads. Util en CPU. En GPU casi da igual.
Otras flags utiles:
-otxt/-ovtt/-osrt/-oj: salida en fichero (txt, vtt, srt, json). Utiles para subtitulos.-tr: traduce el audio al ingles mientras transcribe. Util si grabas en espanol y quieres subtitulos en ingles.-tp 0.2: temperatura para el sampling. Por defecto0.0. Subir un poco si fallan bucles raros.-bs 5: beam size. Por defecto5. Mas lento, ligeramente mejor.
La pieza que falta: grabar el audio
Whisper transcribe un fichero. Para grabar desde microfono hace falta
ffmpeg o sox. La receta canonica:
# Grabar desde el microfono por defecto a 16 kHz mono (lo que Whisper espera)
ffmpeg -f pulse -i default -ar 16000 -ac 1 audio.wavTres detalles que se pasan por alto:
- 16 kHz mono: si grabas a 44.1 kHz estereo, Whisper lo reconvierte y pierdes tiempo. Mejor grabarlo bien de entrada.
- Pulse vs ALSA: en NixOS con PipeWire,
-f pulse -i defaultfunciona porque PipeWire expone el shim de PulseAudio. En macOS toca-f avfoundation -i ":0". - Detectar microfono USB especifico: Pascual tiene un RΓDE NT-USB Mini. El script auto-detecta:
RODE=$(pactl list sources short \
| grep "alsa_input" \
| grep -i "NT-USB" \
| awk '{print $2}' \
| head -1)
INPUT_SOURCE="${RODE:-default}"
ffmpeg -f pulse -i "$INPUT_SOURCE" -ar 16000 -ac 1 audio.wavSi esta el RΓDE conectado, lo usa. Si no, default. Mismo script en aurin, vespino, macbook.
El wrapper completo: whisper-brutal
Pascual tiene el script publicado en su dotfiles. La esencia:
#!/usr/bin/env bash
set -euo pipefail
MODEL="$HOME/.local/share/whisper/models/ggml-small.bin"
TEMP_AUDIO="/tmp/whisper-$$.wav"
trap 'rm -f "$TEMP_AUDIO"' EXIT
# 1. Detectar microfono
RODE=$(pactl list sources short | grep "alsa_input" | grep -i "NT-USB" \
| awk '{print $2}' | head -1)
INPUT="${RODE:-default}"
# 2. Grabar (Ctrl+C para parar)
echo "π€ Recording from: $INPUT"
echo "π€ Press Ctrl+C to stop..."
ffmpeg -f pulse -i "$INPUT" -ar 16000 -ac 1 "$TEMP_AUDIO" \
2>&1 | grep -v "^size=" | grep -v "Press \[q\]"
# 3. Transcribir
[ -s "$TEMP_AUDIO" ] || { echo "β No audio recorded"; exit 1; }
whisper-cli -m "$MODEL" -f "$TEMP_AUDIO" -l es -nt -t 16Uso:
$ whisper-brutal
π€ Recording from: alsa_input.usb-RODE_NT-USB_Mini-00.mono-fallback
π€ Press Ctrl+C to stop...
^C
β
Audio recorded: 320K
Transcribing...
Vale, esto es una prueba para ver si Whisper transcribe bien
cuando le hablo deprisa y con ruido de fondo del ventilador.
whisper_print_timings: total time = 2614.50 ms
Treinta lineas de bash. No hace falta mas.
Performance real
Medido en el enjambre de Pascual:
| Maquina | CPU/GPU | 18.5s de audio | Real-time factor |
|---|---|---|---|
| Aurin | RTX 2060 (Vulkan) | 2.6s | 0.14x (rapido) |
| MacBook | Intel i5-6267U (CPU) | 35s | 1.9x (decente) |
| Vespino | AMD FX-8350 (CPU, 8 cores) | ~25s | 1.4x (decente) |
"Real-time factor" significa: 0.14x = transcribir 1 segundo de audio tarda 0.14 segundos. Cualquier valor por debajo de 1 es "mas rapido que el audio". Por encima de 1 es "tu pipe se atasca".
La diferencia entre GPU y CPU es brutal: la RTX 2060 β que ya es una
GPU vieja β es 13x mas rapida que el i5 del MacBook. Si transcribes a
menudo y tienes GPU dedicada, uses el binario Vulkan.
Si solo tienes Intel/AMD iGPU, whisper-cpp
con CPU sigue siendo aceptable para uso esporadico.
La pieza importante: dictar con Mod+R en cualquier ventana
Esto es lo que de verdad usa Pascual a diario. Un atajo, dos pulsaciones, y el texto aparece donde tenga el cursor: terminal, Emacs, navegador, Slack, cualquier campo de texto.
El flujo desde fuera
- Pulsas
Mod+R. Aparece una notificacionπ€ Voice Input - Recording: 00:03que va contando. - Hablas lo que sea, mirando a otra cosa o sin mirar.
- Pulsas
Mod+Rotra vez. La notificacion se cierra y aparece otraTranscribing.... - ~3 segundos despues, el texto transcrito se escribe en la ventana que tengas activa, como si lo tecleases.
Notificacion final con preview de los primeros 60 caracteres: Text Inserted: vale, esto es una prueba para...
Tres dictados al dia te ahorran teclear cosas largas. La velocidad real es: hablar 30s, esperar 3s, tienes 5 lineas de texto en el editor.
Como esta montado por dentro
El script voice-input-toggle es el
cerebro. Se llama dos veces β la primera arranca, la segunda para y
transcribe e inyecta. Toggle puro. Lock file en /tmp para evitar que dos pulsaciones simultaneas
arranquen dos grabaciones.
Mod+R (1Βͺ pulsacion)
|
v
+----------------------+
| voice-input-toggle |
| ΒΏExiste PIDFILE? |
+----------+-----------+
no |
v
+----------------------+
| START |
| - check_model |
| - record_audio() |
| -> ffmpeg PID |
| - dunstify timer |
| (cada 3s) |
| - PID > /tmp/...pid |
+----------------------+
Mod+R (2Βͺ pulsacion)
|
v
+----------------------+
| voice-input-toggle |
| ΒΏExiste PIDFILE? |
+----------+-----------+
si |
v
+----------------------+
| STOP |
| - kill ffmpeg PID |
| - sleep 0.5 (*) |
| - dunstify -C |
| - transcribe_audio() |
| - xdotool type |
| --clearmodifiers |
| - notif final |
+----------------------+
(*) sleep 0.5 es CRITICO: ffmpeg necesita medio segundo
para cerrar bien el header WAV. Sin eso, fichero corrupto.
El comando que inyecta el texto
Es la linea que mas trabajo me costo entender:
xdotool type --clearmodifiers "$TRANSCRIPT"Detalle critico: --clearmodifiers.
Cuando pulsas Mod+R por segunda vez, el
script se ejecuta con la tecla Mod aun pulsada. Si
xdotool type no limpia los modifiers,
escribe basura: Mod+v Mod+a Mod+l Mod+e en
vez de "vale". --clearmodifiers libera
Mod, escribe, y restaura.
El otro detalle: sleep 0.1 antes de
xdotool. Sin eso, XMonad puede no haber
estabilizado el foco aun y el texto va a la ventana equivocada. 100 ms
es invisible al humano pero salva.
Si xdotool no esta instalado, fallback
a clipboard:
echo "$TRANSCRIPT" | xclip -selection clipboard
dunstify "Text Copied to Clipboard" "Paste with Ctrl+V"El script entero (resumen)
#!/usr/bin/env bash
# voice-input-toggle - Toggle dictation: pulsas, hablas, pulsas, escribe.
source ~/dotfiles/scripts/whisper-core.sh # detect_microphone, transcribe_audio
PIDFILE="/tmp/voice-input.pid"
STATE_FILE="/tmp/voice-input-state"
# ΒΏHay grabacion en curso? -> STOP
if [ -f "$PIDFILE" ] && kill -0 "$(cat $PIDFILE)" 2>/dev/null; then
NOTIF_ID=$(sed -n '1p' "$STATE_FILE")
AUDIO_FILE=$(sed -n '2p' "$STATE_FILE")
PID=$(cat "$PIDFILE")
kill "$PID" 2>/dev/null
wait "$PID" 2>/dev/null
sleep 0.5 # ffmpeg cierra header WAV
dunstify -C "$NOTIF_ID"
TRANS=$(dunstify -p "Transcribing..." "Processing audio")
TRANSCRIPT=$(transcribe_audio "$AUDIO_FILE" "es")
dunstify -C "$TRANS"
if [ -n "$TRANSCRIPT" ]; then
sleep 0.1 # XMonad asienta foco
xdotool type --clearmodifiers "$TRANSCRIPT"
dunstify "Text Inserted" "$(echo "$TRANSCRIPT" | head -c 60)"
else
dunstify -u critical "No Speech Detected"
fi
rm -f "$AUDIO_FILE" "$PIDFILE" "$STATE_FILE"
exit 0
fi
# No hay grabacion -> START
check_model || exit 1
AUDIO_FILE="/tmp/voice-recording-$(date +%s)-$RANDOM.wav"
FFMPEG_PID=$(record_audio "$AUDIO_FILE")
echo "$FFMPEG_PID" > "$PIDFILE"
NOTIF_ID=$(dunstify -p -u critical -t 0 -i microphone-sensitivity-high \
"π€ Voice Input" "Press Mod+r to stop")
printf "%s\n%s\n" "$NOTIF_ID" "$AUDIO_FILE" > "$STATE_FILE"
# Timer en background (actualiza notificacion cada 3s)
(
START=$(date +%s)
while kill -0 "$FFMPEG_PID" 2>/dev/null; do
ELAPSED=$(($(date +%s) - START))
dunstify -r "$NOTIF_ID" -u critical -t 0 \
-i microphone-sensitivity-high \
"π€ Voice Input" "Recording: $(format_duration $ELAPSED)"
sleep 3
done
) &Atarlo a Mod+R
En XMonad (lo que tiene Pascual). En xmonad.hs:
, ("M-r", spawn "/home/passh/.local/bin/voice-input-toggle")En Ubuntu (GNOME): Settings β Keyboard β View and Customize Shortcuts β Custom Shortcuts β Add:
Name: Voice Input
Command: /home/USER/.local/bin/voice-input-toggle
Shortcut: Super+R
(En GNOME Mod por defecto es la tecla
Super = tecla Windows.)
En KDE Plasma: System Settings β Shortcuts β Custom Shortcuts β Edit β New β Global Shortcut β Command/URL:
Action: /home/USER/.local/bin/voice-input-toggle
Trigger: Meta+R
En i3wm/sway. En ~/.config/i3/config (o sway/config):
bindsym $mod+r exec /home/USER/.local/bin/voice-input-toggle
(Ojo: $mod+r por defecto en i3 abre el
modo "resize". Si quieres conservarlo, usa $mod+Shift+r o cualquier otra combinacion
libre.)
En Hyprland. En ~/.config/hypr/hyprland.conf:
bind = SUPER, R, exec, /home/USER/.local/bin/voice-input-toggle
En Wayland (Hyprland, sway, GNOME) hay un detalle:
xdotool no funciona en sesiones Wayland
puras. Hay que usar wtype o ydotool:
# En vez de:
xdotool type --clearmodifiers "$TRANSCRIPT"
# Wayland (instalar wtype):
wtype "$TRANSCRIPT"
# Wayland alternativo (ydotool, requiere root o setuid):
ydotool type "$TRANSCRIPT"Otros casos que tenemos montados
Grabador de
reuniones (meeting-recorder-toggle)
Otro atajo. Pulsas al inicio de la reunion, vuelves a pulsar al
final. Guarda el audio en ~/recordings/
con timestamp y un fichero .json con
metadata. Lo transcribe en background. Al dia siguiente tienes el .txt listo para repasar.
Ojo legal: graba tu lado de la conversacion solo si
solo tu microfono entra. Para grabar tambien al otro hay que mezclar el
monitor del sink con pactl load-module module-loopback, y eso depende
de la legislacion donde estes.
Pipeline completo voz a voz
Una vez tienes Whisper local, el siguiente paso obvio es un asistente de voz:
Microfono β ffmpeg β Whisper (local) β LLM β TTS (local) β Altavoces
El LLM puede ser cloud (Claude API, OpenAI) o local (Ollama con Llama
3, Mistral, lo que prefieras). El TTS puede ser Piper, Kokoro o F5-TTS,
todos con bindings de Python o binarios autonomos. Si quieres el bucle
entero local, Ollama + whisper.cpp + Piper
te lo cierra: cero llamadas a internet, todo en tu maquina.
Cosas que me han mordido
small no entiende numeros perfectamente
Si dictas "20 22" puede transcribir "veintidos" o "2022". Para
programar pasa porque las dudas son evidentes. Para finanzas o codigos
seriales, mejor medium.
Acentos cerrados regionales
Andaluz cerrado, catalan con muchas palabras castellanizadas. small en espanol tira pero pierde matices. large-v3 ayuda. No esperes milagros: la voz
humana real es mas variada de lo que parece.
Frases muy cortas
Whisper tiene VAD (voice activity
detection) interno, pero le cuesta con audios de menos de 1 segundo. Si
quieres dictar palabras sueltas, mejor encadenar varias o subir el -no-speech-thold.
Auto-detect de idioma falla
Si no pasas -l es, en frases cortas o
ambiguas a veces detecta ingles o portugues. Siempre que sepas
el idioma, fijalo. Cuesta cero y evita bugs.
El .wav 16 kHz mono no es opcional
Whisper espera ese formato. Si le das otra cosa, lo convierte
internamente y pierdes 100-300 ms. En tiempo real eso se nota. Mejor
grabarlo bien de entrada con -ar 16000 -ac 1.
Por que NO usar OpenAI Whisper en Python
Hay dos versiones de Whisper:
- La oficial de OpenAI: Python, PyTorch, modelo en
.pt. Funciona, pero es pesada (PyTorch + CUDA libs ~2 GB extra) y lenta de arrancar (~2 segundos solo cargando). - whisper.cpp (Georgi Gerganov,
ggerganoven GitHub, el mismo dellama.cpp): C++, GGML, modelo en.bin. Arranque instantaneo, mismo modelo, igual o mejor calidad.
No hay razon para usar la version Python en 2026 a menos que estes
integrando con Python pipelines. Para CLI / scripts / wrappers, whisper.cpp gana siempre.
Hay tambien faster-whisper (Python con
CTranslate2) que es competitivo. Si ya estas en Python ecosystem,
tirale; si no, whisper.cpp es mas
simple.
Cierre
Whisper local ha cambiado mi forma de trabajar con texto. Notas de
voz al volante que aparecen como .txt,
mensajes largos respondidos por escrito desde el terminal, reuniones con
la transcripcion lista al final del dia. Todo sin que un solo byte de
voz haya salido del ordenador.
Esa es la promesa real: privacidad sin asteriscos, sin TOS de 30 paginas, sin "tus datos pueden ser usados para entrenar nuestros modelos en el futuro". Tu microfono. Tu CPU. Tu texto. Punto.
El umbral de entrada es bajo:
nix-shell -p whisper-cpp-vulkan
curl -LO https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin
whisper-cli -m ggml-small.bin -f audio.wav -l es -ntTres lineas. Si tu uso pasa esa prueba, ya no hay vuelta atras.
Referencias
- github.com/ggerganov/whisper.cpp β el repo del proyecto.
- huggingface.co/ggerganov/whisper.cpp β todos los modelos GGML.
modules/home-manager/programs/whisper.nixen pascualmg/dotfiles β la receta NixOS completa.scripts/whisper-brutal,scripts/whisper-core.sh,scripts/voice-input-toggle,scripts/meeting-recorder-toggleβ los wrappers de Pascual, BSD-style sin licencia explicita pero copiables.
Comentarios (0)
Sin comentarios todavia. Se el primero!
Deja un comentario