Whisper en local: dictar a tu ordenador sin mandar tu voz a OpenAI


29 de abril de 2026

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:

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 -nt

Eso 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:

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.bin

Hay 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 16

Que hace cada flag:

Otras flags utiles:

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.wav

Tres detalles que se pasan por alto:

  1. 16 kHz mono: si grabas a 44.1 kHz estereo, Whisper lo reconvierte y pierdes tiempo. Mejor grabarlo bien de entrada.
  2. Pulse vs ALSA: en NixOS con PipeWire, -f pulse -i default funciona porque PipeWire expone el shim de PulseAudio. En macOS toca -f avfoundation -i ":0".
  3. 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.wav

Si 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 16

Uso:

$ 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

  1. Pulsas Mod+R. Aparece una notificacion 🎀 Voice Input - Recording: 00:03 que va contando.
  2. Hablas lo que sea, mirando a otra cosa o sin mirar.
  3. Pulsas Mod+R otra vez. La notificacion se cierra y aparece otra Transcribing....
  4. ~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:

  1. 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).
  2. whisper.cpp (Georgi Gerganov, ggerganov en GitHub, el mismo de llama.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 -nt

Tres lineas. Si tu uso pasa esa prueba, ya no hay vuelta atras.

Referencias

Comparte este post:

Es tu post

Estas seguro? Esto no se puede deshacer.

Comentarios (0)

Sin comentarios todavia. Se el primero!

Deja un comentario