Subtitulación automática con python

1. Verifica que Whisper está instalado correctamente

Ejecuta en la terminal el siguiente comando para asegurarte de que tienes instalado el paquete correcto:

pip install -U openai-whisper

Nota: El paquete correcto es openai-whisper, no whisper.

2. Prueba el entorno de instalación

Asegúrate de estar usando el entorno de Python correcto donde instalaste Whisper. Para verificar, ejecuta en terminal:

pip show openai-whisper

Deberías ver algo como:

Name: openai-whisper
Version: X.X.X
Location: /ruta/de/instalacion/

Si no aparece, puede que Whisper esté instalado en otro entorno virtual. Asegúrate de activar el entorno correcto antes de ejecutar el script.

3. Prueba la carga del modelo

Abre una sesión de Python e intenta lo siguiente:

import whisper
model = whisper.load_model("base")
print("Modelo cargado correctamente")

Si esto funciona sin errores, entonces el problema está en tu script o en cómo lo estás ejecutando.

En la carpeta donde esta el py debe estar el audio llamado: “imput.wav”, el resultado es: “output.srt”

El python

#!/usr/bin/env python3

import whisper
import os

def split_text_with_timing(text, start, end, word_limit=3, char_limit=35):
    """
    Divide un texto en fragmentos limitados por palabras o caracteres, ajustando los tiempos.
    """
    words = text.split()
    total_words = len(words)
    segments = []
    temp = []
    char_count = 0

    duration_per_word = (end - start) / total_words
    segment_start = start

    for index, word in enumerate(words):
        if len(temp) < word_limit and char_count + len(word) <= char_limit:
            temp.append(word)
            char_count += len(word) + 1  # +1 por el espacio
        else:
            segment_end = segment_start + len(temp) * duration_per_word
            segments.append((" ".join(temp), segment_start, segment_end))
            temp = [word]
            char_count = len(word)
            segment_start = segment_end

    # Añade el último segmento
    if temp:
        segment_end = segment_start + len(temp) * duration_per_word
        segments.append((" ".join(temp), segment_start, segment_end))

    return segments

def transcribe_with_limited_output(input_file, output_file, word_limit=3, char_limit=35):
    """
    Transcribe un audio y limita las líneas a un número de palabras o caracteres,
    ajustando los tiempos correctamente.
    """
    model = whisper.load_model("base")
    result = model.transcribe(input_file)

    with open(output_file, 'w') as f:
        for i, segment in enumerate(result['segments']):
            start = segment['start']
            end = segment['end']
            text = segment['text']

            # Divide el texto según los límites y ajusta los tiempos
            lines_with_timing = split_text_with_timing(text, start, end, word_limit, char_limit)

            for line, line_start, line_end in lines_with_timing:
                f.write(f"{i}\n")
                f.write(f"{format_time(line_start)} --> {format_time(line_end)}\n")
                f.write(f"{line}\n\n")

def format_time(seconds):
    """
    Convierte un tiempo en segundos a formato SRT (hh:mm:ss,ms).
    """
    ms = int((seconds % 1) * 1000)
    seconds = int(seconds)
    mins, secs = divmod(seconds, 60)
    hours, mins = divmod(mins, 60)
    return f"{hours:02}:{mins:02}:{secs:02},{ms:03}"

if __name__ == "__main__":
    input_file = "imput.wav"  # Cambia a tu archivo de audio
    output_file = "output.srt"  # Archivo de salida
    transcribe_with_limited_output(input_file, output_file)
    print("Transcripción completada con tiempos ajustados.")

update a ejecutar a todo archivo wav y mp3 que este al lado

#!/usr/bin/env python3

import whisper
import os

def format_time(seconds):
    """Convierte segundos a formato SRT (hh:mm:ss,ms)."""
    ms = int((seconds % 1) * 1000)
    seconds = int(seconds)
    mins, secs = divmod(seconds, 60)
    hours, mins = divmod(mins, 60)
    return f"{hours:02}:{mins:02}:{secs:02},{ms:03}"

def split_text_with_timing(text, start, end, word_limit=3, char_limit=35):
    """Divide un texto en fragmentos ajustando los tiempos."""
    words = text.strip().split()
    if not words:
        return []
    
    total_words = len(words)
    segments = []
    temp = []
    char_count = 0

    duration_per_word = (end - start) / total_words
    segment_start = start

    for word in words:
        if len(temp) < word_limit and char_count + len(word) <= char_limit:
            temp.append(word)
            char_count += len(word) + 1
        else:
            segment_end = segment_start + (len(temp) * duration_per_word)
            segments.append((" ".join(temp), segment_start, segment_end))
            temp = [word]
            char_count = len(word)
            segment_start = segment_end

    if temp:
        segments.append((" ".join(temp), segment_start, end))

    return segments

def process_audio_files():
    # 1. Configuración de rutas
    current_dir = os.getcwd()
    output_dir = os.path.join(current_dir, "resultado")
    
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)
        print(f"Carpeta creada: {output_dir}")

    # 2. Cargar modelo (lo cargamos una sola vez fuera del bucle para ahorrar tiempo)
    print("Cargando modelo Whisper...")
    model = whisper.load_model("base")

    # 3. Extensiones soportadas
    extensions = ('.wav', '.mp3', '.m4a', '.flac', '.mp4')
    
    # 4. Listar archivos
    files_to_process = [f for f in os.listdir(current_dir) if f.lower().endswith(extensions)]

    if not files_to_process:
        print("No se encontraron archivos de audio en la carpeta.")
        return

    print(f"Se encontraron {len(files_to_process)} archivos. Iniciando proceso...\n")

    for filename in files_to_process:
        input_path = os.path.join(current_dir, filename)
        # Cambiamos la extensión a .srt y lo enviamos a la carpeta resultado
        base_name = os.path.splitext(filename)[0]
        output_path = os.path.join(output_dir, f"{base_name}.srt")

        print(f"Transcibiendo: {filename}...")
        result = model.transcribe(input_path)

        with open(output_path, 'w', encoding='utf-8') as f:
            counter = 1
            for segment in result['segments']:
                lines_with_timing = split_text_with_timing(
                    segment['text'], 
                    segment['start'], 
                    segment['end']
                )

                for line, start, end in lines_with_timing:
                    f.write(f"{counter}\n")
                    f.write(f"{format_time(start)} --> {format_time(end)}\n")
                    f.write(f"{line}\n\n")
                    counter += 1
        
        print(f"✓ Guardado: {output_path}")

    print("\n--- ¡Proceso completado para todos los archivos! ---")

if __name__ == "__main__":
    process_audio_files()

Cómo Funciona

  1. Función split_text:
    • Divide el texto en fragmentos según los límites especificados.
    • Usa tanto el límite de palabras como el de caracteres para ajustar cada línea.
  2. Modificaciones en la Transcripción:
    • Procesa cada segmento generado por Whisper.
    • Divide el texto del segmento en varias líneas si excede los límites.
  3. Salida en Formato SRT:
    • Genera un archivo SRT donde cada línea respeta el límite de palabras o caracteres.

Personalización

  • Cambia los valores de word_limit y char_limit según tus necesidades.
  • El código escribe un archivo SRT (output_limited.srt) con los subtítulos ajustados.

Y si ya tenemos el subtitulado y solo necesitamos dividirlo en palabras

import re

def parse_srt(srt_file):
    """
    Analiza un archivo SRT y devuelve una lista de segmentos con tiempos y textos.
    """
    with open(srt_file, 'r') as f:
        content = f.read()

    pattern = re.compile(r'(\d+)\n(\d{2}:\d{2}:\d{2},\d{3}) --> (\d{2}:\d{2}:\d{2},\d{3})\n(.*?)\n\n', re.DOTALL)
    matches = pattern.findall(content)

    segments = []
    for match in matches:
        index = int(match[0])
        start = parse_time(match[1])
        end = parse_time(match[2])
        text = match[3].replace('\n', ' ')  # Asegúrate de que el texto esté en una línea
        segments.append((index, start, end, text))
    return segments

def parse_time(timestamp):
    """
    Convierte un timestamp en formato SRT a segundos.
    """
    hours, minutes, seconds = map(float, re.split('[:,]', timestamp))
    return hours * 3600 + minutes * 60 + seconds

def format_time(seconds):
    """
    Convierte un tiempo en segundos al formato SRT (hh:mm:ss,ms).
    """
    ms = int((seconds % 1) * 1000)
    seconds = int(seconds)
    mins, secs = divmod(seconds, 60)
    hours, mins = divmod(mins, 60)
    return f"{hours:02}:{mins:02}:{secs:02},{ms:03}"

def split_text_with_timing(text, start, end, word_limit=3, char_limit=35):
    """
    Divide un texto en fragmentos limitados por palabras o caracteres, ajustando los tiempos.
    """
    words = text.split()
    total_words = len(words)
    segments = []
    temp = []
    char_count = 0

    duration_per_word = (end - start) / total_words
    segment_start = start

    for index, word in enumerate(words):
        if len(temp) < word_limit and char_count + len(word) <= char_limit:
            temp.append(word)
            char_count += len(word) + 1  # +1 por el espacio
        else:
            segment_end = segment_start + len(temp) * duration_per_word
            segments.append((" ".join(temp), segment_start, segment_end))
            temp = [word]
            char_count = len(word)
            segment_start = segment_end

    # Añade el último segmento
    if temp:
        segment_end = segment_start + len(temp) * duration_per_word
        segments.append((" ".join(temp), segment_start, segment_end))

    return segments

def split_srt(input_file, output_file, word_limit=3, char_limit=35):
    """
    Lee un archivo SRT, divide los segmentos según los límites especificados,
    y escribe el nuevo archivo SRT ajustado.
    """
    segments = parse_srt(input_file)

    with open(output_file, 'w') as f:
        count = 1
        for _, start, end, text in segments:
            # Divide el texto y ajusta los tiempos
            lines_with_timing = split_text_with_timing(text, start, end, word_limit, char_limit)

            for line, line_start, line_end in lines_with_timing:
                f.write(f"{count}\n")
                f.write(f"{format_time(line_start)} --> {format_time(line_end)}\n")
                f.write(f"{line}\n\n")
                count += 1

if __name__ == "__main__":
    input_srt = "input.srt"  # Cambia por tu archivo SRT
    output_srt = "output_split.srt"  # Archivo SRT de salida
    split_srt(input_srt, output_srt)
    print("Segmentación completada.")

Explicación del Código

  1. parse_srt:
    • Lee y analiza el archivo SRT para extraer los índices, tiempos de inicio y fin, y el texto.
  2. split_text_with_timing:
    • Divide el texto de cada segmento en partes más cortas, ajustando los tiempos proporcionalmente.
  3. split_srt:
    • Procesa el SRT de entrada, divide los segmentos según los límites de palabras y caracteres, y escribe un nuevo archivo SRT.
  4. format_time y parse_time:
    • Convierte entre los formatos de tiempo en segundos y SRT (hh:mm:ss,ms).

Parámetros Configurables

  • word_limit: Máximo número de palabras por línea.
  • char_limit: Máximo número de caracteres por línea.

Creado fadein a los archivos .ass

#!/usr/bin/env python3

import os

def procesar_subtitulos():
    # 1. Configuración de carpetas
    directorio_actual = os.getcwd() # Directorio donde está el script
    carpeta_destino = os.path.join(directorio_actual, "resultado")
    
    # Crear la carpeta 'resultado' si no existe
    if not os.path.exists(carpeta_destino):
        os.makedirs(carpeta_destino)
        print(f"Carpeta creada: {carpeta_destino}")

    # 2. Recorrer los archivos del directorio
    for filename in os.listdir(directorio_actual):
        # Solo procesamos archivos .ass
        if filename.lower().endswith('.ass'):
            filepath = os.path.join(directorio_actual, filename)
            output_path = os.path.join(carpeta_destino, filename)
            
            print(f"Procesando: {filename}...")
            
            # 3. Leer y modificar el contenido
            with open(filepath, 'r', encoding='utf-8') as f:
                lineas = f.readlines()

            nuevas_lineas = []
            fade_tag = "{\\fad(250,250)}"
            
            for linea in lineas:
                if linea.startswith("Dialogue:"):
                    partes = linea.split(',', 9)
                    if len(partes) == 10:
                        # Evitar duplicar el tag si ya existe
                        if "\\fad" not in partes[9]:
                            partes[9] = fade_tag + partes[9]
                        linea = ",".join(partes)
                nuevas_lineas.append(linea)

            # 4. Guardar en la carpeta resultado
            with open(output_path, 'w', encoding='utf-8') as f:
                f.writelines(nuevas_lineas)

    print("\n--- ¡Proceso finalizado con éxito! ---")
    print(f"Los archivos modificados están en: {carpeta_destino}")

if __name__ == "__main__":
    procesar_subtitulos()

Y que tal si agregamos un movimiento al .ass?

Este evalúa la ubicación según la resolución, y agrega el la ubicación final a cada linea

#!/usr/bin/env python3

import os
import re

def procesar_subtitulos_inteligente():
    directorio_actual = os.getcwd()
    carpeta_destino = os.path.join(directorio_actual, "resultado")
    
    if not os.path.exists(carpeta_destino):
        os.makedirs(carpeta_destino)

    for filename in os.listdir(directorio_actual):
        if filename.lower().endswith('.ass'):
            filepath = os.path.join(directorio_actual, filename)
            output_path = os.path.join(carpeta_destino, filename)
            
            with open(filepath, 'r', encoding='utf-8') as f:
                contenido = f.read()

            # 1. Detectar Resolución (PlayResX y PlayResY)
            res_x = int(re.search(r"PlayResX:\s*(\d+)", contenido).group(1)) if re.search(r"PlayResX:\s*(\d+)", contenido) else 384
            res_y = int(re.search(r"PlayResY:\s*(\d+)", contenido).group(1)) if re.search(r"PlayResY:\s*(\d+)", contenido) else 288

            # 2. Detectar valores del Estilo Default (MarginV y Alignment)
            # Buscamos la línea que empieza con Style: Default
            style_match = re.search(r"Style:\s*Default,.*", contenido)
            margin_v = 10 # valor por defecto
            if style_match:
                # El formato tiene muchos campos, el margen vertical suele ser el antepenúltimo
                partes_estilo = style_match.group(0).split(',')
                margin_v = int(partes_estilo[21]) # Basado en tu formato: MarginV es el campo 21

            # 3. Calcular Coordenadas (Basado en Alignment 2: Centro-Abajo)
            # X es la mitad de la pantalla
            x = res_x / 2
            # Y es el fondo de la pantalla menos el margen vertical
            y_final = res_y - margin_v
            y_inicio = y_final + 10 # Empezamos 10 píxeles más abajo

            animacion = f"{{\\fad(250,250)\\move({x:.1f},{y_inicio:.1f},{x:.1f},{y_final:.1f},0,250)}}"

            # 4. Aplicar a los diálogos
            lineas = contenido.split('\n')
            nuevas_lineas = []
            for linea in lineas:
                if linea.startswith("Dialogue:"):
                    partes = linea.split(',', 9)
                    if len(partes) == 10 and "\\move" not in partes[9]:
                        partes[9] = animacion + partes[9]
                    linea = ",".join(partes)
                nuevas_lineas.append(linea)

            with open(output_path, 'w', encoding='utf-8') as f:
                f.write('\n'.join(nuevas_lineas))
            
            print(f"Procesado con éxito: {filename} (Resolución: {res_x}x{res_y})")

if __name__ == "__main__":
    procesar_subtitulos_inteligente()
Qué piensas?
Share your love
What Our Clients Say
1 review