Puedes generar subtítulos automáticamente extrayendo el audio con FFmpeg, transcribiéndolo con OpenAI Whisper y quemando el SRT resultante en el video — todo en un solo script de Python. Sin transcripción manual.
Agregar subtítulos a un video es un trabajo tedioso. Transcribir el audio, alinear las marcas de tiempo, exportar el archivo — hacerlo a mano cada vez simplemente no escala. Yo pasé por eso con cada tutorial que publicaba.
Este artículo te muestra cómo combinar Whisper con FFmpeg para automatizar completamente la generación de subtítulos. Construiremos un pipeline que va desde el video en bruto hasta un archivo con subtítulos incrustados en un solo script: extracción de audio → transcripción → generación de SRT → quemado de subtítulos.
Elimina el cuello de botella de los subtítulos
El verdadero cuello de botella en la creación de subtítulos es la transcripción. Convertir palabras habladas a texto, decidir dónde dividir los segmentos y asignar códigos de tiempo consume el 90% del tiempo total.
Whisper resuelve esto casi por completo. Pásale un archivo de audio y se encarga de la transcripción y los códigos de tiempo automáticamente. Exporta el resultado como SRT y puedes canalizarlo directamente a FFmpeg para quemar los subtítulos en tu video.
Necesitas dos cosas:
- Python 3.8+ con Whisper instalado
- FFmpeg ya instalado y disponible en tu PATH
Si necesitas un repaso sobre los conceptos básicos de FFmpeg, consulta primero Primeros pasos con FFmpeg.
Instala Whisper y elige un modelo
Instala Whisper con pip.
pip install openai-whisper
Una vez instalado, elige el tamaño del modelo. Whisper incluye varias variantes con diferentes balances entre velocidad y precisión.
| Modelo | VRAM | Velocidad | Precisión |
|---|---|---|---|
| tiny | ~1GB | Más rápido | Baja |
| base | ~1GB | Rápido | Moderada |
| small | ~2GB | Media | Buena |
| medium | ~5GB | Lento | Alta |
| large-v3 | ~10GB | Más lento | Mejor |
| turbo | ~6GB | Rápido | Casi la mejor |
El modelo turbo fue lanzado en septiembre de 2024. Reduce las capas del decodificador de large-v3 de 32 a 4, logrando una velocidad aproximadamente 8× mayor mientras mantiene una precisión cercana a large-v3. Si tienes una GPU con 6GB+ de VRAM, turbo es la mejor opción para la mayoría de los flujos de trabajo.
Extrae el audio con FFmpeg
Whisper puede aceptar un archivo de video directamente, pero extraer el audio primero hace que el pipeline sea más rápido y limpio. Usa FFmpeg para convertir a WAV.
ffmpeg -i input.mp4 -vn -acodec pcm_s16le -ar 16000 -ac 1 audio.wav
Qué hace cada flag:
-vn— ignora el stream de video, procesa solo el audio-acodec pcm_s16le— exporta como WAV (PCM lineal de 16 bits)-ar 16000— establece la tasa de muestreo a 16kHz (la entrada recomendada por Whisper)-ac 1— mezcla a mono (Whisper es más consistente con audio mono)
El archivo audio.wav resultante es lo que se pasa a Whisper a continuación.
Transcribe el audio y genera SRT con Whisper
Con el archivo de audio listo, pásalo por Whisper. Puedes usar la CLI o la API de Python.
Usando la CLI
whisper audio.wav --model medium --language es --output_format srt --output_dir ./subtitles
Pasar --language es explícitamente es más rápido y preciso que dejar que Whisper lo detecte automáticamente. El resultado se guarda en ./subtitles/audio.srt y luce así:
1
00:00:00,000 --> 00:00:03,500
Hola. Hoy vamos a ver cómo usar FFmpeg y Whisper.
2
00:00:03,500 --> 00:00:07,200
Empecemos instalando Whisper.
Usando la API de Python
Usa la API cuando necesites más control sobre la salida.
import whisper
model = whisper.load_model("medium")
result = model.transcribe("audio.wav", language="es")
def format_timestamp(seconds: float) -> str:
ms = int((seconds % 1) * 1000)
s = int(seconds) % 60
m = int(seconds) // 60 % 60
h = int(seconds) // 3600
return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}"
with open("subtitles/audio.srt", "w", encoding="utf-8") as f:
for i, segment in enumerate(result["segments"], start=1):
start = format_timestamp(segment["start"])
end = format_timestamp(segment["end"])
text = segment["text"].strip()
f.write(f"{i}\n{start} --> {end}\n{text}\n\n")
print("SRT file written")
Cada elemento en result["segments"] contiene start, end (en segundos) y text. La función auxiliar los formatea en marcas de tiempo SRT.
Quema los subtítulos en el video con FFmpeg
Una vez que el archivo SRT existe, usa el filtro subtitles de FFmpeg para incrustarlos en el video de forma permanente.
ffmpeg -i input.mp4 -vf "subtitles=subtitles/audio.srt" -c:a copy output.mp4
Para personalizar la fuente, tamaño y color, pasa un argumento force_style.
ffmpeg -i input.mp4 \
-vf "subtitles=subtitles/audio.srt:force_style='FontName=Arial,FontSize=24,PrimaryColour=&HFFFFFF&,OutlineColour=&H000000&,Outline=2'" \
-c:a copy output.mp4
force_style sigue la especificación de estilos ASS/SSA. Los colores usan el formato &HBBGGRR& — nota el orden BGR (invertido respecto al RGB de HTML) y el & final. Los scripts no latinos deben usar una fuente que cubra el conjunto de caracteres requerido para evitar problemas de renderizado.
Combina todo en un solo script
Aquí está el pipeline completo en un solo script de Python. Apúntalo a un archivo de video y genera una versión con subtítulos.
import subprocess
import sys
import os
import whisper
def extract_audio(input_video: str, output_audio: str) -> None:
"""Extrae el audio de un archivo de video."""
cmd = [
"ffmpeg", "-i", input_video,
"-vn", "-acodec", "pcm_s16le",
"-ar", "16000", "-ac", "1",
output_audio, "-y"
]
subprocess.run(cmd, check=True, capture_output=True)
print(f"Audio extraído: {output_audio}")
def transcribe_to_srt(audio_path: str, srt_path: str, model_name: str = "medium") -> None:
"""Transcribe el audio con Whisper y genera un archivo SRT."""
model = whisper.load_model(model_name)
result = model.transcribe(audio_path, language="es")
def format_timestamp(seconds: float) -> str:
ms = int((seconds % 1) * 1000)
s = int(seconds) % 60
m = int(seconds) // 60 % 60
h = int(seconds) // 3600
return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}"
with open(srt_path, "w", encoding="utf-8") as f:
for i, segment in enumerate(result["segments"], start=1):
start = format_timestamp(segment["start"])
end = format_timestamp(segment["end"])
text = segment["text"].strip()
f.write(f"{i}\n{start} --> {end}\n{text}\n\n")
print(f"SRT generado: {srt_path}")
def burn_subtitles(input_video: str, srt_path: str, output_video: str) -> None:
"""Incrusta los subtítulos en el video."""
# Asume que srt_path contiene solo caracteres ASCII
vf = f"subtitles={srt_path}"
cmd = [
"ffmpeg", "-i", input_video,
"-vf", vf,
"-c:a", "copy",
output_video, "-y"
]
subprocess.run(cmd, check=True, capture_output=True)
print(f"Subtítulos incrustados: {output_video}")
def main(input_video: str) -> None:
base = os.path.splitext(input_video)[0]
audio_path = f"{base}_audio.wav"
srt_path = f"{base}_subtitles.srt"
output_video = f"{base}_subtitled.mp4"
print("=== Pipeline de generación automática de subtítulos ===")
extract_audio(input_video, audio_path)
transcribe_to_srt(audio_path, srt_path)
burn_subtitles(input_video, srt_path, output_video)
# Eliminar archivos intermedios
os.remove(audio_path)
print(f"\nListo: {output_video}")
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Uso: python subtitle_pipeline.py <input_video>")
sys.exit(1)
main(sys.argv[1])
Ejecútalo con un solo comando.
python subtitle_pipeline.py input.mp4
Si quieres procesar una carpeta completa de videos de una vez, el artículo sobre Automatización por lotes con FFmpeg y Python cubre ese patrón en detalle.
Aceléralo con faster-whisper
Si el tiempo de procesamiento importa — y siempre importa cuando procesas cientos de videos — prueba faster-whisper. Es una reimplementación de Whisper usando CTranslate2, y funciona hasta 4× más rápido con significativamente menos memoria.
pip install faster-whisper
from faster_whisper import WhisperModel
model = WhisperModel("turbo", compute_type="float16")
segments, info = model.transcribe("audio.wav", language="es")
with open("subtitles/audio.srt", "w", encoding="utf-8") as f:
for i, segment in enumerate(segments, start=1):
start = format_timestamp(segment.start)
end = format_timestamp(segment.end)
f.write(f"{i}\n{start} --> {end}\n{segment.text.strip()}\n\n")
La API es ligeramente diferente (segments es un generador, los atributos se acceden con puntos en vez de claves de diccionario), pero la salida es idéntica. Yo cambié el pipeline de videos de 32blog a faster-whisper y reduje el tiempo de procesamiento de 12 minutos a menos de 3 para un video de 30 minutos en una RTX 3060.
Preguntas frecuentes
¿Whisper funciona sin GPU?
Sí. Whisper corre en CPU, pero es significativamente más lento. Un video de 10 minutos toma alrededor de 30 segundos en una GPU de gama media con el modelo medium, contra 5–10 minutos en CPU. El modelo turbo ayuda mucho incluso en CPU.
¿Cuál es la diferencia entre subtítulos suaves y duros?
Los subtítulos duros (burn-in) se incrustan permanentemente en los píxeles del video — los espectadores no pueden desactivarlos. Los subtítulos suaves se almacenan como un stream separado dentro del contenedor, y los reproductores permiten activarlos o desactivarlos. Este artículo cubre subtítulos duros con el filtro subtitles. Para subtítulos suaves, usa ffmpeg -i input.mp4 -i subs.srt -c copy -c:s mov_text output.mp4.
¿Puede Whisper manejar múltiples idiomas en un solo video?
El flag --language de Whisper establece un solo idioma para todo el archivo. Si tu video cambia entre idiomas, omite el flag --language y deja que Whisper lo detecte automáticamente por segmento. La precisión baja comparada con el modo de idioma único, así que divide el audio en los puntos de cambio de idioma si la precisión importa.
¿Qué tan preciso es Whisper comparado con servicios de transcripción de pago?
Con habla clara en español, los modelos large-v3 y turbo de Whisper rivalizan con servicios comerciales. La precisión baja con acentos fuertes, ruido de fondo o hablantes superpuestos. Para uso profesional, siempre revisa el SRT generado antes de quemarlo en el video.
¿Puedo generar VTT en vez de SRT?
Sí. Usa --output_format vtt con la CLI de Whisper, o cambia el formato de marca de tiempo en el script de Python (VTT usa HH:MM:SS.mmm con punto, mientras SRT usa HH:MM:SS,mmm con coma). VTT es el estándar para video web mediante el elemento <track>.
¿Por qué extraer el audio primero en vez de pasar el video directamente a Whisper?
Whisper puede aceptar archivos de video, pero internamente extrae el audio usando FFmpeg. Hacerlo tú mismo te da control sobre la tasa de muestreo (16kHz mono es óptimo para Whisper) y evita re-extraer el audio si necesitas reintentar con diferentes configuraciones de modelo.
¿El filtro subtitles funciona en Windows?
Sí, pero cuidado con los problemas de rutas. En Windows, el filtro subtitles puede fallar si la ruta del archivo contiene caracteres no ASCII (español con tildes, espacios, etc.) debido a cómo el runtime de C de Windows maneja las rutas de archivos. Mantén las rutas solo con caracteres ASCII o copia el SRT a una ubicación simple.
¿Cuál es el mejor modelo para español?
Usa medium o large-v3 para español. Los modelos tiny y base frecuentemente confunden nombres propios y producen resultados ilegibles. El modelo turbo es un buen punto intermedio — precisión cercana a large-v3 con una velocidad mucho mayor.
Conclusión
Combinar FFmpeg y Whisper automatiza casi todo el flujo de trabajo de subtítulos.
- El pipeline tiene cuatro pasos: extraer audio, transcribir con Whisper, escribir SRT, quemar con FFmpeg
- El modelo turbo es la mejor opción por defecto — rápido y preciso
- Usa faster-whisper para una mejora de velocidad de 4×
- Mantén las rutas de archivos solo con ASCII en Windows para evitar errores del filtro
subtitles - El script de Python envuelve todo en un solo comando:
python subtitle_pipeline.py input.mp4
Deja de perder tiempo en transcripción manual. Deja que Whisper se encargue y concéntrate en la edición.
Artículos relacionados: