Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
@@ -9,7 +9,7 @@ import gradio as gr
|
|
9 |
import torch
|
10 |
from transformers import GPT2Tokenizer, GPT2LMHeadModel
|
11 |
from keybert import KeyBERT
|
12 |
-
#
|
13 |
from moviepy.editor import VideoFileClip, concatenate_videoclips, AudioFileClip, CompositeAudioClip, concatenate_audioclips, AudioClip
|
14 |
import re
|
15 |
import math
|
@@ -35,7 +35,7 @@ logger.info("="*80)
|
|
35 |
PEXELS_API_KEY = os.environ.get("PEXELS_API_KEY")
|
36 |
if not PEXELS_API_KEY:
|
37 |
logger.critical("NO SE ENCONTRÓ PEXELS_API_KEY EN VARIABLES DE ENTORNO")
|
38 |
-
# raise ValueError("API key de Pexels no configurada")
|
39 |
|
40 |
# Inicialización de modelos
|
41 |
MODEL_NAME = "datificate/gpt2-small-spanish"
|
@@ -387,21 +387,19 @@ def crear_video(prompt_type, input_text, musica_file=None):
|
|
387 |
primary_voice = "es-ES-JuanNeural"
|
388 |
fallback_voice = "es-ES-ElviraNeural" # Otra voz en español
|
389 |
tts_success = False
|
390 |
-
retries = 3
|
391 |
|
392 |
for attempt in range(retries):
|
393 |
current_voice = primary_voice if attempt == 0 else fallback_voice
|
394 |
if attempt > 0: logger.warning(f"Reintentando TTS ({attempt+1}/{retries})...")
|
395 |
logger.info(f"Intentando TTS con voz: {current_voice}")
|
396 |
try:
|
397 |
-
# Llamar a la función async text_to_speech
|
398 |
tts_success = asyncio.run(text_to_speech(guion, voz_path, voice=current_voice))
|
399 |
if tts_success:
|
400 |
logger.info(f"TTS exitoso en intento {attempt + 1} con voz {current_voice}.")
|
401 |
-
break
|
402 |
except Exception as e:
|
403 |
-
|
404 |
-
pass # Continuar al siguiente intento
|
405 |
|
406 |
if not tts_success and attempt == 0 and primary_voice != fallback_voice:
|
407 |
logger.warning(f"Fallo con voz {primary_voice}, intentando voz de respaldo: {fallback_voice}")
|
@@ -409,14 +407,12 @@ def crear_video(prompt_type, input_text, musica_file=None):
|
|
409 |
logger.warning(f"Fallo con voz {current_voice}, reintentando...")
|
410 |
|
411 |
|
412 |
-
# Verificar si el archivo fue creado después de todos los intentos
|
413 |
if not tts_success or not os.path.exists(voz_path) or os.path.getsize(voz_path) <= 100:
|
414 |
logger.error(f"Fallo en la generación de voz después de {retries} intentos. Archivo de audio no creado o es muy pequeño.")
|
415 |
raise ValueError("Error generando voz a partir del guion (fallo de TTS).")
|
416 |
|
417 |
-
temp_intermediate_files.append(voz_path)
|
418 |
|
419 |
-
# Continuar cargando el archivo de audio generado
|
420 |
audio_tts_original = AudioFileClip(voz_path)
|
421 |
|
422 |
if audio_tts_original.reader is None or audio_tts_original.duration is None or audio_tts_original.duration <= 0:
|
@@ -709,8 +705,8 @@ def crear_video(prompt_type, input_text, musica_file=None):
|
|
709 |
|
710 |
if musica_audio_looped:
|
711 |
composite_audio = CompositeAudioClip([
|
712 |
-
musica_audio_looped.volumex(0.2),
|
713 |
-
audio_tts_original.volumex(1.0)
|
714 |
])
|
715 |
|
716 |
if composite_audio.duration is None or composite_audio.duration <= 0:
|
@@ -721,7 +717,7 @@ def crear_video(prompt_type, input_text, musica_file=None):
|
|
721 |
else:
|
722 |
logger.info("Mezcla de audio completada (voz + música).")
|
723 |
final_audio = composite_audio
|
724 |
-
musica_audio = musica_audio_looped
|
725 |
|
726 |
except Exception as e:
|
727 |
logger.warning(f"Error procesando música de fondo: {str(e)}", exc_info=True)
|
@@ -847,12 +843,12 @@ def crear_video(prompt_type, input_text, musica_file=None):
|
|
847 |
logger.info(f"Directorio temporal intermedio {temp_dir_intermediate} persistirá para que Gradio lea el video final.")
|
848 |
|
849 |
|
850 |
-
#
|
851 |
def run_app(prompt_type, prompt_ia, prompt_manual, musica_file):
|
852 |
logger.info("="*80)
|
853 |
logger.info("SOLICITUD RECIBIDA EN INTERFAZ")
|
854 |
|
855 |
-
#
|
856 |
input_text = prompt_ia if prompt_type == "Generar Guion con IA" else prompt_manual
|
857 |
|
858 |
output_video = None
|
@@ -861,6 +857,7 @@ def run_app(prompt_type, prompt_ia, prompt_manual, musica_file):
|
|
861 |
|
862 |
if not input_text or not input_text.strip():
|
863 |
logger.warning("Texto de entrada vacío.")
|
|
|
864 |
return None, None, gr.update(value="⚠️ Por favor, ingresa texto para el guion o el tema.", interactive=False)
|
865 |
|
866 |
logger.info(f"Tipo de entrada: {prompt_type}")
|
@@ -872,14 +869,14 @@ def run_app(prompt_type, prompt_ia, prompt_manual, musica_file):
|
|
872 |
|
873 |
try:
|
874 |
logger.info("Llamando a crear_video...")
|
875 |
-
# Pasar el input_text elegido y el archivo de música
|
876 |
video_path = crear_video(prompt_type, input_text, musica_file)
|
877 |
|
878 |
if video_path and os.path.exists(video_path):
|
879 |
logger.info(f"crear_video retornó path: {video_path}")
|
880 |
logger.info(f"Tamaño del archivo de video retornado: {os.path.getsize(video_path)} bytes")
|
881 |
-
output_video = video_path
|
882 |
-
output_file = video_path
|
883 |
status_msg = gr.update(value="✅ Video generado exitosamente.", interactive=False)
|
884 |
else:
|
885 |
logger.error(f"crear_video no retornó un path válido o el archivo no existe: {video_path}")
|
@@ -893,6 +890,7 @@ def run_app(prompt_type, prompt_ia, prompt_manual, musica_file):
|
|
893 |
status_msg = gr.update(value=f"❌ Error inesperado: {str(e)}", interactive=False)
|
894 |
finally:
|
895 |
logger.info("Fin del handler run_app.")
|
|
|
896 |
return output_video, output_file, status_msg
|
897 |
|
898 |
|
@@ -914,11 +912,12 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
|
|
914 |
)
|
915 |
|
916 |
# Contenedores para los campos de texto para controlar la visibilidad
|
|
|
917 |
with gr.Column(visible=True) as ia_guion_column:
|
918 |
prompt_ia = gr.Textbox(
|
919 |
label="Tema para IA",
|
920 |
lines=2,
|
921 |
-
placeholder="Ej: Un paisaje natural con montañas y ríos al amanecer...",
|
922 |
max_lines=4,
|
923 |
value=""
|
924 |
)
|
@@ -927,7 +926,7 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
|
|
927 |
prompt_manual = gr.Textbox(
|
928 |
label="Tu Guion Completo",
|
929 |
lines=5,
|
930 |
-
placeholder="Ej: En este video exploraremos los misterios del océano
|
931 |
max_lines=10,
|
932 |
value=""
|
933 |
)
|
@@ -960,31 +959,38 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
|
|
960 |
value="Esperando entrada..."
|
961 |
)
|
962 |
|
963 |
-
#
|
|
|
964 |
prompt_type.change(
|
965 |
lambda x: (gr.update(visible=x == "Generar Guion con IA"),
|
966 |
gr.update(visible=x == "Usar Mi Guion")),
|
967 |
inputs=prompt_type,
|
968 |
-
#
|
969 |
outputs=[ia_guion_column, manual_guion_column]
|
970 |
)
|
971 |
|
972 |
# Evento click del botón de generar video
|
973 |
generate_btn.click(
|
974 |
# Acción 1 (síncrona): Resetear salidas y establecer estado a procesando
|
975 |
-
|
|
|
976 |
outputs=[video_output, file_output, status_output],
|
977 |
-
queue=True,
|
978 |
).then(
|
979 |
# Acción 2 (asíncrona): Llamar a la función principal de procesamiento
|
980 |
run_app,
|
981 |
-
#
|
982 |
inputs=[prompt_type, prompt_ia, prompt_manual, musica_input],
|
|
|
983 |
outputs=[video_output, file_output, status_output]
|
984 |
).then(
|
985 |
# Acción 3 (síncrona): Hacer visible el enlace de descarga si se retornó un archivo
|
986 |
-
|
987 |
-
|
|
|
|
|
|
|
|
|
988 |
outputs=[file_output]
|
989 |
)
|
990 |
|
|
|
9 |
import torch
|
10 |
from transformers import GPT2Tokenizer, GPT2LMHeadModel
|
11 |
from keybert import KeyBERT
|
12 |
+
# Importación correcta: Solo 'concatenate_videoclips'
|
13 |
from moviepy.editor import VideoFileClip, concatenate_videoclips, AudioFileClip, CompositeAudioClip, concatenate_audioclips, AudioClip
|
14 |
import re
|
15 |
import math
|
|
|
35 |
PEXELS_API_KEY = os.environ.get("PEXELS_API_KEY")
|
36 |
if not PEXELS_API_KEY:
|
37 |
logger.critical("NO SE ENCONTRÓ PEXELS_API_KEY EN VARIABLES DE ENTORNO")
|
38 |
+
# raise ValueError("API key de Pexels no configurada")
|
39 |
|
40 |
# Inicialización de modelos
|
41 |
MODEL_NAME = "datificate/gpt2-small-spanish"
|
|
|
387 |
primary_voice = "es-ES-JuanNeural"
|
388 |
fallback_voice = "es-ES-ElviraNeural" # Otra voz en español
|
389 |
tts_success = False
|
390 |
+
retries = 3
|
391 |
|
392 |
for attempt in range(retries):
|
393 |
current_voice = primary_voice if attempt == 0 else fallback_voice
|
394 |
if attempt > 0: logger.warning(f"Reintentando TTS ({attempt+1}/{retries})...")
|
395 |
logger.info(f"Intentando TTS con voz: {current_voice}")
|
396 |
try:
|
|
|
397 |
tts_success = asyncio.run(text_to_speech(guion, voz_path, voice=current_voice))
|
398 |
if tts_success:
|
399 |
logger.info(f"TTS exitoso en intento {attempt + 1} con voz {current_voice}.")
|
400 |
+
break
|
401 |
except Exception as e:
|
402 |
+
pass
|
|
|
403 |
|
404 |
if not tts_success and attempt == 0 and primary_voice != fallback_voice:
|
405 |
logger.warning(f"Fallo con voz {primary_voice}, intentando voz de respaldo: {fallback_voice}")
|
|
|
407 |
logger.warning(f"Fallo con voz {current_voice}, reintentando...")
|
408 |
|
409 |
|
|
|
410 |
if not tts_success or not os.path.exists(voz_path) or os.path.getsize(voz_path) <= 100:
|
411 |
logger.error(f"Fallo en la generación de voz después de {retries} intentos. Archivo de audio no creado o es muy pequeño.")
|
412 |
raise ValueError("Error generando voz a partir del guion (fallo de TTS).")
|
413 |
|
414 |
+
temp_intermediate_files.append(voz_path)
|
415 |
|
|
|
416 |
audio_tts_original = AudioFileClip(voz_path)
|
417 |
|
418 |
if audio_tts_original.reader is None or audio_tts_original.duration is None or audio_tts_original.duration <= 0:
|
|
|
705 |
|
706 |
if musica_audio_looped:
|
707 |
composite_audio = CompositeAudioClip([
|
708 |
+
musica_audio_looped.volumex(0.2), # Volumen 20% para música
|
709 |
+
audio_tts_original.volumex(1.0) # Volumen 100% para voz
|
710 |
])
|
711 |
|
712 |
if composite_audio.duration is None or composite_audio.duration <= 0:
|
|
|
717 |
else:
|
718 |
logger.info("Mezcla de audio completada (voz + música).")
|
719 |
final_audio = composite_audio
|
720 |
+
musica_audio = musica_audio_looped # Asignar para limpieza
|
721 |
|
722 |
except Exception as e:
|
723 |
logger.warning(f"Error procesando música de fondo: {str(e)}", exc_info=True)
|
|
|
843 |
logger.info(f"Directorio temporal intermedio {temp_dir_intermediate} persistirá para que Gradio lea el video final.")
|
844 |
|
845 |
|
846 |
+
# La función run_app ahora recibe todos los inputs de texto y el archivo de música
|
847 |
def run_app(prompt_type, prompt_ia, prompt_manual, musica_file):
|
848 |
logger.info("="*80)
|
849 |
logger.info("SOLICITUD RECIBIDA EN INTERFAZ")
|
850 |
|
851 |
+
# Elegir el texto de entrada basado en el prompt_type
|
852 |
input_text = prompt_ia if prompt_type == "Generar Guion con IA" else prompt_manual
|
853 |
|
854 |
output_video = None
|
|
|
857 |
|
858 |
if not input_text or not input_text.strip():
|
859 |
logger.warning("Texto de entrada vacío.")
|
860 |
+
# Retornar None para video y archivo, actualizar estado con mensaje de error
|
861 |
return None, None, gr.update(value="⚠️ Por favor, ingresa texto para el guion o el tema.", interactive=False)
|
862 |
|
863 |
logger.info(f"Tipo de entrada: {prompt_type}")
|
|
|
869 |
|
870 |
try:
|
871 |
logger.info("Llamando a crear_video...")
|
872 |
+
# Pasar el input_text elegido y el archivo de música a crear_video
|
873 |
video_path = crear_video(prompt_type, input_text, musica_file)
|
874 |
|
875 |
if video_path and os.path.exists(video_path):
|
876 |
logger.info(f"crear_video retornó path: {video_path}")
|
877 |
logger.info(f"Tamaño del archivo de video retornado: {os.path.getsize(video_path)} bytes")
|
878 |
+
output_video = video_path # Establecer valor del componente de video
|
879 |
+
output_file = video_path # Establecer valor del componente de archivo para descarga
|
880 |
status_msg = gr.update(value="✅ Video generado exitosamente.", interactive=False)
|
881 |
else:
|
882 |
logger.error(f"crear_video no retornó un path válido o el archivo no existe: {video_path}")
|
|
|
890 |
status_msg = gr.update(value=f"❌ Error inesperado: {str(e)}", interactive=False)
|
891 |
finally:
|
892 |
logger.info("Fin del handler run_app.")
|
893 |
+
# Retornar las tres salidas esperadas por el evento click
|
894 |
return output_video, output_file, status_msg
|
895 |
|
896 |
|
|
|
912 |
)
|
913 |
|
914 |
# Contenedores para los campos de texto para controlar la visibilidad
|
915 |
+
# Nombrados para que coincidan con los outputs del evento change
|
916 |
with gr.Column(visible=True) as ia_guion_column:
|
917 |
prompt_ia = gr.Textbox(
|
918 |
label="Tema para IA",
|
919 |
lines=2,
|
920 |
+
placeholder="Ej: Un paisaje natural con montañas y ríos al amanecer, mostrando la belleza de la naturaleza...",
|
921 |
max_lines=4,
|
922 |
value=""
|
923 |
)
|
|
|
926 |
prompt_manual = gr.Textbox(
|
927 |
label="Tu Guion Completo",
|
928 |
lines=5,
|
929 |
+
placeholder="Ej: En este video exploraremos los misterios del océano. Veremos la vida marina fascinante y los arrecifes de coral vibrantes. ¡Acompáñanos en esta aventura subacuática!",
|
930 |
max_lines=10,
|
931 |
value=""
|
932 |
)
|
|
|
959 |
value="Esperando entrada..."
|
960 |
)
|
961 |
|
962 |
+
# Evento para mostrar/ocultar los campos de texto según el tipo de prompt
|
963 |
+
# Apuntar a los componentes Column padre para controlar la visibilidad
|
964 |
prompt_type.change(
|
965 |
lambda x: (gr.update(visible=x == "Generar Guion con IA"),
|
966 |
gr.update(visible=x == "Usar Mi Guion")),
|
967 |
inputs=prompt_type,
|
968 |
+
# Pasar los componentes Column
|
969 |
outputs=[ia_guion_column, manual_guion_column]
|
970 |
)
|
971 |
|
972 |
# Evento click del botón de generar video
|
973 |
generate_btn.click(
|
974 |
# Acción 1 (síncrona): Resetear salidas y establecer estado a procesando
|
975 |
+
# Retorna None para los 3 outputs iniciales
|
976 |
+
lambda: (None, None, gr.update(value="⏳ Procesando... Esto puede tomar varios minutos.", interactive=False)),
|
977 |
outputs=[video_output, file_output, status_output],
|
978 |
+
queue=True, # Usar la cola de Gradio para tareas largas
|
979 |
).then(
|
980 |
# Acción 2 (asíncrona): Llamar a la función principal de procesamiento
|
981 |
run_app,
|
982 |
+
# PASAR TODOS LOS INPUTS DE LA INTERFAZ que run_app espera
|
983 |
inputs=[prompt_type, prompt_ia, prompt_manual, musica_input],
|
984 |
+
# run_app retornará los 3 outputs esperados aquí
|
985 |
outputs=[video_output, file_output, status_output]
|
986 |
).then(
|
987 |
# Acción 3 (síncrona): Hacer visible el enlace de descarga si se retornó un archivo
|
988 |
+
# Esta función recibe las salidas de la Acción 2 (video_path, file_path, status_msg)
|
989 |
+
# Solo necesitamos video_path o file_path para decidir si mostrar el enlace
|
990 |
+
lambda video_path, file_path, status_msg: gr.update(visible=file_path is not None),
|
991 |
+
# Inputs son las salidas de la función .then() anterior
|
992 |
+
inputs=[video_output, file_output, status_output],
|
993 |
+
# Actualizamos la visibilidad del componente file_output
|
994 |
outputs=[file_output]
|
995 |
)
|
996 |
|