La mayoría de los tutoriales de Whisper en español que están circulando datan de 2023–2024. Recomiendan pydub (que en 2026 está sin mantenimiento activo), no mencionan Apple Silicon (donde Macs M3/M4 son la norma en LATAM para devs), e ignoran que faster-whisper ya viene con Silero VAD V6 y batched inference que cambian completamente los benchmarks.
Si vas a transcribir podcasts, calls de ventas, entrevistas para periodismo, o construir un asistente de voz —y quieres hacerlo sin que tu audio salga de tu máquina— este es el setup que tiene sentido en mayo de 2026.
Cubrimos cuatro cosas: el stack mínimo que funciona en cualquier OS, el camino optimizado para M-series Macs vía MLX, la alternativa nativa para Linux/Windows sin GPU NVIDIA usando whisper.cpp, y diarización + timestamps por palabra con WhisperX. Al final, una tabla con costos reales para que decidas si vale la pena correrlo local o pagar la API.
El stack en 2026
Cuatro herramientas, cada una para un caso:
faster-whisper 1.2.1 (Octubre 2026) es la base recomendada desde Python para CPU y GPU NVIDIA. La versión 1.1.0 introdujo batched inference 4× más rápida, soporte para large-v3-turbo, y VAD 3× más rápida. La 1.2.x agregó Silero-VAD V6 y distil-large-v3.5. La API es estable.
mlx-whisper es la implementación sobre el framework MLX de Apple, optimizada para los chips M-series. Aprovecha la GPU integrada y la memoria unificada vía Metal —algo que faster-whisper no toca, porque CTranslate2 no tiene backend Metal. Si tu máquina es Apple Silicon, este es el camino default.
whisper.cpp 1.8.3 (Enero 2026) es la implementación en C/C++. Sin Python, un binario portable, y soporte nativo para CUDA, Vulkan (iGPUs Intel/AMD), OpenBLAS y SYCL. La opción correcta para Linux/Windows sin GPU NVIDIA, o para deploy en contenedores delgados.
WhisperX 3.7.6 (Enero 2026) usa faster-whisper por debajo y le suma tres cosas: alineación con wav2vec2 para timestamps a nivel de palabra, VAD para saltar silencios, y diarización con pyannote-audio. Reemplaza un script de 200 líneas por una llamada.
Para procesamiento de audio, dejamos pydub (sin mantenimiento) y vamos directo a ffmpeg por subprocess. Es menos elegante en el código pero más predecible.
Setup
Necesitas Python 3.10+ y ffmpeg instalado en el sistema. Recomiendo virtual env.
bash
python -m venv whisper_env
source whisper_env/bin/activate # macOS / Linux
# whisper_env\Scripts\activate # Windows PowerShellInstala ffmpeg:
macOS (Homebrew):
brew install ffmpegUbuntu/Debian:
sudo apt install ffmpegWindows:
winget install Gyan.FFmpego desde ffmpeg.org
Verifica: ffmpeg -version debe responder.
Instala la librería según tu hardware:
bash
# CPU o NVIDIA GPU (Linux/Windows/Intel Mac)
pip install faster-whisper
# Apple Silicon (M1/M2/M3/M4)
pip install mlx-whisper
# Si quieres diarización + word timestamps
pip install whisperxPara GPU NVIDIA, faster-whisper 1.2 ya no requiere instalar cuBLAS/cuDNN aparte si usas pip install ctranslate2[cuda] —se resuelve por wheels. Valida con python -c "import torch; print(torch.cuda.is_available())" si vas a ejecutar WhisperX.
Pre-procesamiento sin pydub
Whisper espera 16 kHz, mono, PCM WAV. La forma directa, sin librerías intermedias, es invocar ffmpeg:
python
import subprocess
from pathlib import Path
def to_wav_16k_mono(input_path: str, output_path: str | None = None) -> str:
"""Convierte cualquier audio a WAV 16 kHz mono usando ffmpeg directamente."""
src = Path(input_path)
dst = Path(output_path) if output_path else src.with_suffix(".wav")
cmd = [
"ffmpeg", "-y", "-i", str(src),
"-ac", "1", # mono
"-ar", "16000", # 16 kHz
"-c:a", "pcm_s16le", # PCM 16-bit
str(dst),
]
subprocess.run(cmd, check=True, capture_output=True)
return str(dst)Tres ventajas sobre pydub: no depende de un wrapper sin mantener, no carga el archivo entero en memoria (importante para audios largos), y los errores de ffmpeg salen tal cual sin intermediarios.
Importante: faster-whisper y mlx-whisper aceptan formatos comprimidos directamente (MP3, M4A, OGG) porque internamente usan ffmpeg o pyav. Solo necesitas convertir a WAV explícitamente si vas a enviar a pyannote standalone, o si quieres cachear una versión normalizada.
Script base con faster-whisper 1.2
Esta es la versión actualizada del script: usa el modelo large-v3-turbo (más rápido y casi tan preciso como large-v3 para idiomas Tier 1 como español), batched inference, y Silero VAD para saltar silencios.
python
from faster_whisper import WhisperModel, BatchedInferencePipeline
def transcribir(audio_path: str, modelo: str = "large-v3-turbo",
device: str = "auto", idioma: str = "es") -> dict:
"""
Transcripción rápida con VAD + batched inference.
device: "cpu", "cuda", o "auto" (detecta automático).
compute_type: int8 en CPU, float16 en GPU.
"""
compute_type = "float16" if device == "cuda" else "int8"
base = WhisperModel(modelo, device=device, compute_type=compute_type)
pipeline = BatchedInferencePipeline(model=base)
segments, info = pipeline.transcribe(
audio_path,
batch_size=8,
language=idioma,
vad_filter=True,
vad_parameters={"min_silence_duration_ms": 500},
)
segments = list(segments) # forzamos evaluación del generator
texto_completo = " ".join(s.text.strip() for s in segments)
return {
"idioma_detectado": info.language,
"probabilidad": info.language_probability,
"duracion": info.duration,
"texto": texto_completo,
"segmentos": [
{"inicio": s.start, "fin": s.end, "texto": s.text.strip()}
for s in segments
],
}
if __name__ == "__main__":
resultado = transcribir("entrevista.mp3", device="auto")
print(f"Idioma: {resultado['idioma_detectado']} ({resultado['probabilidad']:.2f})")
print(f"Duración: {resultado['duracion']:.1f}s")
print(resultado["texto"])Dos detalles que importan:
BatchedInferencePipeline con batch_size=8 corre los chunks en paralelo. Según los benchmarks que SYSTRAN publica en el README de la 1.1.0, esto trae ~4× speed-up sobre la pipeline secuencial con la misma calidad.
vad_filter=True salta silencios largos antes de pasar al modelo. En grabaciones reales (entrevistas con pausas, podcasts con cortes), esto recorta otro 30–40% del tiempo total.
Si fijas language="es", evitas el paso de detección automática (que para audios cortos a veces se equivoca y manda transcripción a portugués).
Apple Silicon: usa MLX
Si tu máquina es M1/M2/M3/M4, faster-whisper corre en CPU porque CTranslate2 no tiene backend Metal. mlx-whisper resuelve eso usando el framework MLX nativo de Apple, que aprovecha la GPU integrada y la memoria unificada.
python
import mlx_whisper
def transcribir_mlx(audio_path: str,
modelo: str = "mlx-community/whisper-large-v3-turbo") -> dict:
"""
Transcripción optimizada para Apple Silicon usando MLX.
Modelos disponibles en: huggingface.co/mlx-community
"""
resultado = mlx_whisper.transcribe(
audio_path,
path_or_hf_repo=modelo,
language="es",
word_timestamps=False,
)
return {
"texto": resultado["text"],
"segmentos": resultado["segments"],
"idioma": resultado["language"],
}Los modelos viven en la org mlx-community de Hugging Face. Los más útiles para español:
mlx-community/whisper-large-v3-turbo— el default razonablemlx-community/whisper-large-v3-mlx— máxima precisión, más lentomlx-community/whisper-medium-mlx-q4— quantizado a 4-bit, corre en M1 base con 8 GB
En la práctica, mlx-whisper con large-v3-turbo corre varias veces más rápido en M3/M4 que faster-whisper en CPU del mismo equipo. La diferencia se hace muy visible en audios largos —donde el costo de inferencia secuencial en CPU se acumula. Vale la pena correr ambas opciones con un audio propio de 5–10 minutos y medir.
Caveat: si usas word_timestamps=True con MLX, la calidad de los timestamps por palabra es peor que la de WhisperX. Para sincronizar subtítulos seriamente, mejor pasar a WhisperX (que también corre en Apple Silicon, aunque sin acelerar la inferencia con MLX —usa CPU para el modelo Whisper y MPS para wav2vec2).
Linux y Windows sin NVIDIA: whisper.cpp
mlx-whisper resuelve el problema de "sacarle jugo al hardware" en Mac. El equivalente para Linux y Windows —especialmente si no tienes GPU NVIDIA— es whisper.cpp, la implementación en C/C++ de Georgi Gerganov.
Tres razones por las que vale la pena para este caso:
Sin dependencias de Python. Es un binario único, ideal si lo vas a embeber en otra app o correrlo desde un servidor delgado.
Aprovecha hardware "no NVIDIA" que faster-whisper no toca. La versión 1.8.3 (Enero 2026) introdujo soporte Vulkan optimizado para iGPUs de Intel y AMD, con hasta 12× más rendimiento que CPU puro en esos chips. También soporta CUDA si tienes NVIDIA, OpenBLAS/MKL para CPU, y SYCL para Intel discreto.
Modelos en formato GGML cuantizados. Ocupan menos espacio en disco y arrancan más rápido que los .bin o safetensors de PyTorch.
Build en Linux
bash
# Dependencias (Ubuntu/Debian)
sudo apt install build-essential cmake git libopenblas-dev
# Clonar y compilar
git clone https://github.com/ggml-org/whisper.cpp
cd whisper.cpp
# Build con OpenBLAS (CPU)
cmake -B build -DGGML_BLAS=1
cmake --build build --config Release -j
# O con CUDA si tienes NVIDIA
cmake -B build -DGGML_CUDA=1
cmake --build build --config Release -j
# O con Vulkan para iGPU Intel/AMD
cmake -B build -DGGML_VULKAN=1
cmake --build build --config Release -jBuild en Windows
Necesitas Git, CMake, y Visual Studio Build Tools con "Desktop development with C++". Después:
powershell
git clone https://github.com/ggml-org/whisper.cpp
cd whisper.cpp
cmake -B build
cmake --build build --config ReleaseSi prefieres no compilar, hay binarios precompilados en la página de releases (busca whisper-bin-x64.zip o whisper-blas-bin-x64.zip).
Descargar modelo y transcribir
Los modelos GGML viven en huggingface.co/ggerganov/whisper.cpp. Para español usa el multilingüe:
bash
# Descargar large-v3-turbo cuantizado (multilingüe)
bash ./models/download-ggml-model.sh large-v3-turbo-q5_0
# Transcribir
./build/bin/whisper-cli \
-m models/ggml-large-v3-turbo-q5_0.bin \
-l es \
-f entrevista.wav \
--output-srtEso te deja un entrevista.srt listo. Otros flags útiles:
-t 8para usar 8 threads de CPU--output-jsonsi lo vas a parsear desde otro programa--word-thold 0.01para timestamps a nivel de palabra (no tan precisos como WhisperX, pero útiles)-bs 5para beam size 5 (más calidad, más lento)
Llamarlo desde Python
Si ya tienes el resto de tu pipeline en Python pero quieres usar whisper.cpp para la inferencia, llamarlo por subprocess es directo:
python
import subprocess
import json
from pathlib import Path
def transcribir_cpp(audio_path: str, modelo: str = "large-v3-turbo-q5_0",
idioma: str = "es") -> dict:
"""
Wrapper de whisper.cpp. Asume que el binario está en ./whisper.cpp/build/bin/
y los modelos en ./whisper.cpp/models/
"""
base = Path("./whisper.cpp")
out = Path(audio_path).with_suffix("") # whisper.cpp agrega .json
cmd = [
str(base / "build/bin/whisper-cli"),
"-m", str(base / f"models/ggml-{modelo}.bin"),
"-l", idioma,
"-f", audio_path,
"--output-json",
"--output-file", str(out),
]
subprocess.run(cmd, check=True)
with open(f"{out}.json") as f:
return json.load(f)Cuándo conviene whisper.cpp vs faster-whisper
Si tienes NVIDIA con CUDA reciente, faster-whisper sigue siendo el camino default —es más sencillo desde Python y tiene batched inference.
Si estás en una laptop con iGPU (Intel UHD/Iris, AMD Radeon integrada), o un servidor sin GPU dedicada, whisper.cpp con Vulkan u OpenBLAS suele ser sustancialmente más rápido y consume menos RAM.
Para deploy en contenedores delgados (Alpine, distroless), whisper.cpp evita arrastrar PyTorch y CUDA libs —la imagen final puede pesar bajo 200 MB.
Diarización + word timestamps con WhisperX
Si necesitas saber quién dijo qué (entrevistas, calls de ventas, paneles), o quieres timestamps a nivel de palabra para subtítulos precisos, WhisperX es lo más práctico.
Antes de arrancar, dos pasos manuales una sola vez:
Crear un token de lectura en hf.co/settings/tokens.
Aceptar los términos en los dos modelos de pyannote que usa WhisperX: segmentation-3.0 y speaker-diarization-3.1 (botón "Agree and access repository" en cada uno).
python
import os
import whisperx
from whisperx.diarize import DiarizationPipeline
HF_TOKEN = os.environ["HF_TOKEN"]
def transcribir_con_hablantes(audio_path: str,
min_hablantes: int | None = None,
max_hablantes: int | None = None,
device: str = "cuda") -> list[dict]:
"""
Transcripción + alineación + diarización en un solo pipeline.
min/max_hablantes: si tienes idea del rango, pásalo. Mejora bastante.
"""
compute_type = "float16" if device == "cuda" else "int8"
batch_size = 16 if device == "cuda" else 4
# 1. Transcripción base con faster-whisper
modelo = whisperx.load_model("large-v3-turbo", device,
compute_type=compute_type, language="es")
audio = whisperx.load_audio(audio_path)
resultado = modelo.transcribe(audio, batch_size=batch_size)
# 2. Alineación: timestamps por palabra con wav2vec2
modelo_align, metadata = whisperx.load_align_model(language_code="es", device=device)
resultado = whisperx.align(resultado["segments"], modelo_align, metadata,
audio, device, return_char_alignments=False)
# 3. Diarización: quién habló cuándo
diarize_model = DiarizationPipeline(use_auth_token=HF_TOKEN, device=device)
kwargs = {}
if min_hablantes:
kwargs["min_speakers"] = min_hablantes
if max_hablantes:
kwargs["max_speakers"] = max_hablantes
diarize_segments = diarize_model(audio, **kwargs)
# 4. Asignar hablante a cada palabra/segmento
resultado = whisperx.assign_word_speakers(diarize_segments, resultado)
return resultado["segments"]
if __name__ == "__main__":
segmentos = transcribir_con_hablantes(
"call_ventas.wav", min_hablantes=2, max_hablantes=2
)
for seg in segmentos:
speaker = seg.get("speaker", "?")
print(f"[{seg['start']:.1f}s] {speaker}: {seg['text']}")Output típico:
[0.5s] SPEAKER_00: Hola, gracias por tomar el tiempo. ¿Me escuchas bien?
[3.2s] SPEAKER_01: Perfecto, te escucho. Cuéntame de qué se trata el producto.
[7.8s] SPEAKER_00: Claro, mira, lo que hacemos es...Notas para que esto funcione bien en LATAM:
Si la grabación es de una llamada de Zoom o Google Meet exportada, suele venir con cada participante en un canal separado (multi-track). En ese caso conviene transcribir cada track por separado y combinar con los timestamps —es más barato y más preciso que diarizar.
Pasar min_speakers/max_speakers ayuda mucho cuando conoces el rango. Para una entrevista 1-a-1, min=2, max=2. Para un panel de cuatro, min=2, max=4. Sin esa pista, pyannote a veces inventa un quinto speaker que en realidad es ruido de fondo.
La calidad de la diarización cae con audios de mala calidad (llamadas de teléfono, micrófono lejano). Para esos casos, considera pyannote-ai community-1, que mejora significativamente sobre la 3.1 en condiciones difíciles.
Local vs. API: los números en mayo de 2026
Esta es la decisión real. Asumiendo que quieres transcribir 100 horas de audio por mes (algo razonable para un podcast semanal de 2 horas, o un equipo de ventas con 25 calls/semana):
Opción | Costo por hora | 100 hs / mes | Notas |
|---|---|---|---|
OpenAI | $0.36 | $36 | $0.006/min, batch |
OpenAI | $0.18 | $18 | $0.003/min |
Deepgram Nova-3 (batch) | $0.26 | $26 | $0.0043/min, mejor para diarización |
Deepgram Nova-3 (streaming) | $0.46 | $46 | $0.0077/min |
Local en MacBook M3/M4 (que ya tienes) | $0 | $0 | varios minutos por hora con MLX |
Local en GPU rentada (RunPod RTX 4090) | ~$0.15 | $15 | $0.34/hr × 0.45 hs procesamiento |
Lectura rápida: si ya tienes un Mac con M-series, transcribir local es prácticamente gratis y respeta privacidad. Si necesitas escala (1000+ horas/mes) o tiempo real con mínima latencia, las APIs siguen ganando por throughput.
El punto donde la API gana es el tiempo del developer. Mantener un pipeline local con WhisperX implica cuidar versiones de PyTorch, drivers CUDA, modelos descargados, espacio en disco. Para un side project se puede vivir con eso. Para producción de un equipo de 3 personas, $36/mes le compite a una hora de tu tiempo.
Para casos LATAM donde compliance importa (legal, salud, RH), local sigue siendo la respuesta más simple: tu audio nunca cruza un firewall externo.
Calidad esperada en español
Whisper large-v3 y large-v3-turbo reportan WER (word error rate) entre 3% y 6% en español de audio limpio, comparable a inglés. Los puntos donde se degrada son predecibles:
Audio con ruido de fondo fuerte (calle, aire acondicionado): WER sube a 8–12%.
Acentos muy marcados o muy regionales (rural argentino, costa colombiana, andino peruano profundo): WER sube a 7–10%, y a veces el modelo "normaliza" hacia un español neutro perdiendo modismos.
Code-switching español/inglés (típico en tech LATAM: "el deploy del backend"): si pasas language="es" el modelo a veces traduce las palabras en inglés. Si lo dejas en multilingual=True (faster-whisper 1.1+) maneja el switch, pero a veces se confunde el idioma base. La opción más robusta es transcribir con language="es" y aceptar que algunas palabras técnicas en inglés salgan españolizadas.
Vocabulario muy técnico o nombres propios poco comunes: usa el parámetro initial_prompt para sembrar términos. Ejemplo:
python
segments, info = pipeline.transcribe(
"demo_producto.wav",
language="es",
initial_prompt="BitNeuronal, Anthropic, Claude, MCP, agentes, LATAM",
)Eso reduce sensiblemente errores en marca, jerga y términos técnicos repetidos.
Próximos pasos
Con este setup ya puedes transcribir cualquier audio en tu máquina, con o sin diarización, en español. Algunos lugares útiles para iterar:
Para una UI rápida sobre el script, Gradio monta un upload + output en 20 líneas y corre local en localhost:7860.
Para servirlo a un equipo, envolverlo en una FastAPI con cola de jobs (Celery o RQ) y procesar en background. Una RTX 4090 maneja sin problema 4–6 transcripciones simultáneas con large-v3-turbo.
Si te interesa el formato salida tipo SRT/VTT para subtítulos, WhisperX trae writers integrados (whisperx.utils.WriteSRT, WriteVTT). Útil para cualquier caso de captioning de video.
Si estás transcribiendo a escala industrial (miles de horas/mes), mira Modal o Replicate para correr WhisperX serverless con GPUs por segundo. A ese volumen, la combinación local + serverless burst sale mucho más barata que cualquier API managed.
¿Te gustó? Reenvíaselo a alguien que también esté lidiando con audio en español.
BitNeuronal · IA técnica para Latinoamérica