Este tutorial te explica paso a paso cómo usar el script para generar archivos SRT (subtítulos) automáticamente usando la API de Forced Alignment de ElevenLabs.
sk_...)Para instalar la librería de ElevenLabs en Python, ejecuta el siguiente comando en tu terminal:
pip install elevenlabs
Crea una carpeta de trabajo con esta estructura:
mi_proyecto/
├── audio.mp3 # Tu archivo de audio
├── texto.txt # Tu texto línea por línea
└── generar_srt.py # El script
texto.txtCada línea en este archivo será un segmento de subtítulo separado en el SRT final. Asegúrate de que el texto coincida con lo que se dice en el audio.
Bienvenidos a nuestro tutorial de hoy
En este video aprenderemos sobre inteligencia artificial
La IA está transformando el mundo moderno
Espero que disfruten este contenido educativo
audio.mp3MP3, WAV, M4A, FLACtexto.txt debe coincidir aproximadamente con el contenido hablado en el archivo de audio.Copia el código completo del script proporcionado al final de este tutorial y guárdalo como generar_srt.py dentro de tu carpeta mi_proyecto.
Abre el archivo generar_srt.py con un editor de texto y busca esta línea:
ELEVENLABS_API_KEY = "tu_api_key_aqui" # ⬅️ REEMPLAZA ESTO CON TU API KEY REAL
Reemplázala con tu API key real de ElevenLabs (la que copiaste en el paso de requisitos):
ELEVENLABS_API_KEY = "sk_1234567890abcdef..." # ⬅️ Tu API key aquí
Abre tu terminal o símbolo del sistema y navega a la carpeta de tu proyecto:
cd mi_proyecto
Asegúrate de tener los tres archivos necesarios en la carpeta mi_proyecto:
audio.mp3texto.txtgenerar_srt.pyEjecuta el script de Python:
python generar_srt.py
Verás una salida similar a esta en tu terminal:
🎬 Generador de SRT con ElevenLabs Forced Alignment
==================================================
📝 Procesando 4 líneas de texto...
📄 Texto completo: Bienvenidos a nuestro tutorial de hoy En este video aprenderemos...
🎵 Archivo de audio: audio.mp3
🔑 API Key configurada: sk_12345...
🚀 Enviando solicitud a ElevenLabs API...
✅ ¡Forced alignment completado!
📊 Tipo de respuesta: <class 'elevenlabs.types.ForcedAlignmentResponse'>
📝 Palabras encontradas: 32
✅ ¡Archivo 'subtitulos.srt' creado exitosamente!
📁 Ubicación: /ruta/completa/mi_proyecto/subtitulos.srt
📝 Se procesaron 4 segmentos de subtítulo
--- Preview del SRT generado ---
1
00:00:00,000 --> 00:00:03,200
Bienvenidos a nuestro tutorial de hoy
2
00:00:03,200 --> 00:00:07,150
En este video aprenderemos sobre inteligencia artificial
3
00:00:07,150 --> 00:00:11,800
La IA está transformando el mundo moderno
...
subtitulos.srtEl script creará automáticamente el archivo subtitulos.srt en la misma carpeta donde se encuentran los otros archivos. Su contenido será similar a esto:
1
00:00:00,000 --> 00:00:03,200
Bienvenidos a nuestro tutorial de hoy
2
00:00:03,200 --> 00:00:07,150
En este video aprenderemos sobre inteligencia artificial
3
00:00:07,150 --> 00:00:11,800
La IA está transformando el mundo moderno
4
00:00:11,800 --> 00:00:15,450
Espero que disfruten este contenido educativo
❌ ERROR: Debes configurar tu API key en el script
Solución: Edita el script generar_srt.py y configura tu API key real en la línea ELEVENLABS_API_KEY = "tu_api_key_aqui".
❌ Error: No se encontró el archivo 'audio.mp3'
Solución: Verifica que los archivos audio.mp3, texto.txt y generar_srt.py estén en la misma carpeta donde ejecutas el script.
❌ Error: Insufficient credits or plan
Solución: El plan gratuito de ElevenLabs no incluye Forced Alignment. Actualiza tu suscripción a un plan Starter ($5/mes) o superior.
❌ Error: Text too long
Solución: El texto combinado en texto.txt no puede exceder los 675k caracteres.
❌ Error: File too large
Solución: El archivo de audio no puede exceder los 3GB de tamaño o las 10 horas de duración.
texto.txt debe coincidir lo más fielmente posible con lo que se dice en el audio.El script funciona con los 29 idiomas soportados por ElevenLabs, incluyendo:
Guarda este código como generar_srt.py en tu carpeta de proyecto:
#!/usr/bin/env python3
"""
Script completo para generar un archivo SRT usando la API de Forced Alignment de ElevenLabs.
Coloca este script en la misma carpeta que audio.mp3 y texto.txt
El archivo SRT resultante se guardará como 'subtitulos.srt' en la misma carpeta.
"""
import os
import json
import base64
from elevenlabs.client import ElevenLabs
# ========================================
# 🔑 CONFIGURACIÓN - COLOCA TU API KEY AQUÍ
# ========================================
ELEVENLABS_API_KEY = "tu_api_key_aqui" # ⬅️ REEMPLAZA ESTO CON TU API KEY REAL
# ========================================
def seconds_to_srt_time(seconds):
"""Convierte segundos a formato SRT (HH:MM:SS,mmm)"""
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
secs = int(seconds % 60)
milliseconds = int((seconds % 1) * 1000)
return f"{hours:02d}:{minutes:02d}:{secs:02d},{milliseconds:03d}"
def create_srt_from_alignment(alignment_result, text_lines):
"""
Crea contenido SRT basado en el resultado de forced alignment
y las líneas de texto originales
"""
srt_content = []
subtitle_index = 1
# Procesar la respuesta de forced alignment
if hasattr(alignment_result, 'words') and alignment_result.words:
words = alignment_result.words
current_line_index = 0
current_line_text = text_lines[current_line_index] if current_line_index < len(text_lines) else ""
current_line_words = []
# Procesar palabra por palabra
for word_info in words:
word = word_info.word if hasattr(word_info, 'word') else str(word_info.get('word', ''))
start_time = word_info.start if hasattr(word_info, 'start') else float(word_info.get('start', 0))
end_time = word_info.end if hasattr(word_info, 'end') else float(word_info.get('end', 0))
# Si una palabra tiene un final de tiempo antes de su inicio, la ignoramos o ajustamos
if start_time > end_time:
# print(f"⚠️ Advertencia: Palabra '{word}' tiene tiempo final menor que inicial. Ajustando o ignorando.")
if not current_line_words: # Si es la primera palabra de un segmento, ajustamos
end_time = start_time + 0.1 # Darle una duración mínima
else: # Si no, podemos ignorarla o dejarla como está si el siguiente word_info corregirá
pass
current_line_words.append({
'word': word.strip(),
'start': start_time,
'end': end_time
})
# Construir texto de las palabras actuales para comparación
current_words_text = ' '.join([w['word'] for w in current_line_words]).strip()
# Convertir el texto de la línea original a un formato comparable (minúsculas, sin puntuación)
line_clean = current_line_text.lower()
line_clean = ''.join(char for char in line_clean if char.isalnum() or char.isspace()).strip()
# Convertir el texto de las palabras actuales a un formato comparable
words_clean = current_words_text.lower()
words_clean = ''.join(char for char in words_clean if char.isalnum() or char.isspace()).strip()
# Verificar si hemos completado la línea actual (comparación más flexible)
# La lógica es que si el texto limpio de las palabras actuales contiene o es suficientemente
# cercano al texto limpio de la línea original, consideramos la línea completa.
# También si el número de palabras en el audio ya supera las de la línea original.
# Añado un umbral de coincidencia o longitud para mayor robustez
min_match_ratio = 0.75 # Porcentaje de palabras de la línea que deben estar en el texto actual
original_words_count = len(current_line_text.split())
current_aligned_words_count = len(current_line_words)
is_line_match = (
line_clean in words_clean or
(len(words_clean) > 0 and line_clean.startswith(words_clean) and current_aligned_words_count >= original_words_count * min_match_ratio) or
(current_aligned_words_count >= original_words_count and words_clean.startswith(line_clean))
)
if is_line_match or (current_aligned_words_count > 0 and word_info == words[-1]):
if current_line_words:
start_subtitle = current_line_words[0]['start']
end_subtitle = current_line_words[-1]['end']
# Asegurar que el tiempo final no sea menor que el inicial
if end_subtitle < start_subtitle:
end_subtitle = start_subtitle + 0.1 # Asignar duración mínima
srt_content.append(f"{subtitle_index}")
srt_content.append(f"{seconds_to_srt_time(start_subtitle)} --> {seconds_to_srt_time(end_subtitle)}")
srt_content.append(current_line_text)
srt_content.append("") # Línea vacía entre subtítulos
subtitle_index += 1
current_line_index += 1
current_line_words = []
if current_line_index < len(text_lines):
current_line_text = text_lines[current_line_index]
else:
break # No hay más líneas de texto para procesar
# Si no hay palabras procesadas, o si algo falla, crear una versión básica de fallback
elif hasattr(alignment_result, 'characters') and alignment_result.characters:
# Fallback usando información de caracteres
# Esto es menos preciso, pero asegura que algo se genera
total_duration = 0
if alignment_result.characters:
last_char_end = alignment_result.characters[-1].end if hasattr(alignment_result.characters[-1], 'end') else 0
if last_char_end > 0:
total_duration = last_char_end
elif alignment_result.words: # Si hay palabras pero sin tiempos, usar el último tiempo de palabra
total_duration = alignment_result.words[-1].end if hasattr(alignment_result.words[-1], 'end') else 0
# Si aún no tenemos duración, intentar con una heurística
if total_duration == 0 and text_lines:
# Asumiendo una velocidad de habla promedio de 3 palabras por segundo
# y 5 caracteres por palabra
estimated_duration_per_char = 1 / (3 * 5) # ~0.066 segundos por caracter
total_chars = sum(len(line) for line in text_lines)
total_duration = total_chars * estimated_duration_per_char
print(f"<span class="icon-warning">⚠️</span> Advertencia: No se encontraron tiempos de palabras/caracteres. Usando duración estimada: {total_duration:.2f}s")
# Distribuir tiempo uniformemente entre líneas
num_lines = len(text_lines)
time_per_line = total_duration / num_lines if num_lines > 0 else 0
for i, line in enumerate(text_lines):
start_time = i * time_per_line
end_time = (i + 1) * time_per_line
srt_content.append(f"{i + 1}")
srt_content.append(f"{seconds_to_srt_time(start_time)} --> {seconds_to_srt_time(end_time)}")
srt_content.append(line)
srt_content.append("")
else:
print("<span class="icon-warning">⚠️</span> Advertencia: No se encontraron datos de palabras o caracteres en la respuesta de alignment.")
# Último fallback: cada línea dura 3 segundos por defecto si no hay información de tiempo
default_duration_per_line = 3.0
for i, line in enumerate(text_lines):
start_time = i * default_duration_per_line
end_time = (i + 1) * default_duration_per_line
srt_content.append(f"{i + 1}")
srt_content.append(f"{seconds_to_srt_time(start_time)} --> {seconds_to_srt_time(end_time)}")
srt_content.append(line)
srt_content.append("")
print("<span class="icon-tip">🔍</span> Se generaron subtítulos con duración estimada (3s por línea). Revisa la precisión.")
return '\n'.join(srt_content)
def main():
print("<span class="icon-title">🎬</span> Generador de SRT con ElevenLabs Forced Alignment")
print("=" * 50)
# Verificar API key
if not ELEVENLABS_API_KEY or ELEVENLABS_API_KEY == "tu_api_key_aqui":
print("<span class="icon-error">❌</span> ERROR: Debes configurar tu API key en el script")
print("<span class="icon-tip">🔍</span> Abre el archivo y reemplaza 'tu_api_key_aqui' con tu API key real")
print("<span class="icon-key">🔑</span> Encuentra tu API key en: https://elevenlabs.io/app/settings/api-keys")
return
# Verificar que existen los archivos necesarios
if not os.path.exists('audio.mp3'):
print("<span class="icon-error">❌</span> Error: No se encontró el archivo 'audio.mp3' en la carpeta actual")
print("<span class="icon-folder">📂</span> Carpeta actual: ", os.getcwd())
return
if not os.path.exists('texto.txt'):
print("<span class="icon-error">❌</span> Error: No se encontró el archivo 'texto.txt' en la carpeta actual")
print("<span class="icon-folder">📂</span> Carpeta actual: ", os.getcwd())
return
# Leer el archivo de texto
try:
with open('texto.txt', 'r', encoding='utf-8') as f:
text_lines = [line.strip() for line in f.readlines() if line.strip()]
except Exception as e:
print(f"<span class="icon-error">❌</span> Error al leer texto.txt: {e}")
return
if not text_lines:
print("<span class="icon-error">❌</span> Error: El archivo texto.txt está vacío o solo contiene saltos de línea")
print("<span class="icon-tip">🔍</span> Asegúrate de que texto.txt contenga el texto del audio, una frase por línea.")
return
# Combinar todas las líneas en un solo texto para el forced alignment
full_text = ' '.join(text_lines)
print(f"<span class="icon-info">📝</span> Procesando {len(text_lines)} líneas de texto...")
print(f"<span class="icon-file">📄</span> Texto completo: {full_text[:100]}..." if len(full_text) > 100 else f"<span class="icon-file">📄</span> Texto completo: {full_text}")
print(f"<span class="icon-info">🎵</span> Archivo de audio: audio.mp3")
print(f"<span class="icon-key">🔑</span> API Key configurada: {ELEVENLABS_API_KEY[:8]}...")
# Crear cliente de ElevenLabs
try:
elevenlabs = ElevenLabs(api_key=ELEVENLABS_API_KEY)
except Exception as e:
print(f"<span class="icon-error">❌</span> Error al crear cliente ElevenLabs: {e}")
print("<span class="icon-tip">🔍</span> Verifica que tu API key sea correcta y que la librería 'elevenlabs' esté instalada.")
return
try:
# Realizar forced alignment
print("\n<span class="icon-rocket">🚀</span> Enviando solicitud a ElevenLabs API...")
with open('audio.mp3', 'rb') as audio_file:
alignment_result = elevenlabs.forced_alignment.create(
file=audio_file,
text=full_text
)
print("<span class="icon-success">✅</span> ¡Forced alignment completado!")
# Debug: Mostrar estructura de la respuesta
print(f"<span class="icon-info">📊</span> Tipo de respuesta: {type(alignment_result)}")
if hasattr(alignment_result, 'words') and alignment_result.words:
print(f"<span class="icon-info">📝</span> Palabras encontradas: {len(alignment_result.words)}")
elif hasattr(alignment_result, 'characters') and alignment_result.characters:
print(f"<span class="icon-info">📝</span> Caracteres encontrados: {len(alignment_result.characters)}")
# Crear archivo SRT
srt_content = create_srt_from_alignment(alignment_result, text_lines)
if not srt_content.strip():
print("<span class="icon-warning">⚠️</span> Advertencia: No se pudo generar contenido SRT válido")
print("<span class="icon-tip">🔍</span> Revisa que el texto coincida con el audio y que el audio sea claro.")
return
# Guardar archivo SRT en la misma carpeta
output_file = 'subtitulos.srt'
with open(output_file, 'w', encoding='utf-8') as f:
f.write(srt_content)
print(f"<span class="icon-success">✅</span> ¡Archivo '{output_file}' creado exitosamente!")
print(f"<span class="icon-folder">📁</span> Ubicación: {os.path.abspath(output_file)}")
print(f"<span class="icon-info">📝</span> Se procesaron {len(text_lines)} segmentos de subtítulo")
# Mostrar un preview del SRT
print("\n--- Preview del SRT generado ---")
preview = srt_content[:500] + "..." if len(srt_content) > 500 else srt_content
print(preview)
except Exception as e:
print(f"<span class="icon-error">❌</span> Error al procesar: {e}")
if "insufficient credits" in str(e).lower() or "plan" in str(e).lower():
print("<span class="icon-bulb">💡</span> Sugerencia: Verifica tu plan de ElevenLabs. El Forced Alignment requiere un plan de pago (Starter o superior).")
elif "api key" in str(e).lower() or "authentication" in str(e).lower():
print("<span class="icon-bulb">💡</span> Sugerencia: Verifica que tu API key sea correcta y esté bien configurada en el script.")
elif "too large" in str(e).lower() or "text too long" in str(e).lower():
print("<span class="icon-bulb">💡</span> Sugerencia: Revisa los límites de tamaño de audio y texto de ElevenLabs.")
if __name__ == "__main__":
main()