32blogby Studio Mitsu

Genera subtítulos automáticos con FFmpeg y Whisper

Combina Whisper de OpenAI con FFmpeg para agregar subtítulos automáticamente a cualquier video. Construye un pipeline que extrae audio, genera SRT y quema los subtítulos.

by omitsu11 min read
FFmpegWhispersubtitlesautomationAI
Contenido

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.

Input VideoMP4 / MOVWAV 16kHzFFmpegAudio ExtractTranscribeWhisperTranscriptionSubtitleSRT + FFmpegBurn-in

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.

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

ModeloVRAMVelocidadPrecisión
tiny~1GBMás rápidoBaja
base~1GBRápidoModerada
small~2GBMediaBuena
medium~5GBLentoAlta
large-v3~10GBMás lentoMejor
turbo~6GBRápidoCasi 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.

bash
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

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

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

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

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

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

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

bash
pip install faster-whisper
python
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: