Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
@@ -859,7 +859,7 @@ def crear_video(prompt_type, input_text, selected_voice, musica_file=None):
|
|
859 |
except Exception as e:
|
860 |
logger.warning(f"Error ajustando duración del audio final: {str(e)}")
|
861 |
|
862 |
-
|
863 |
output_filename = f"video_{int(time.time())}.mp4" # Nombre único con timestamp
|
864 |
output_path = os.path.join(temp_dir_intermediate, output_filename)
|
865 |
|
@@ -881,7 +881,7 @@ def crear_video(prompt_type, input_text, selected_voice, musica_file=None):
|
|
881 |
# Mover a ubicación permanente en /tmp
|
882 |
permanent_path = f"/tmp/{output_filename}"
|
883 |
try:
|
884 |
-
shutil.copy(output_path, permanent_path)
|
885 |
logger.info(f"Video guardado permanentemente en: {permanent_path}")
|
886 |
except Exception as move_error:
|
887 |
logger.error(f"Error moviendo archivo: {str(move_error)}. Usando path original.")
|
@@ -957,16 +957,210 @@ def crear_video(prompt_type, input_text, selected_voice, musica_file=None):
|
|
957 |
logger.warning(f"Error cerrando video_base en finally: {str(e)}")
|
958 |
|
959 |
if temp_dir_intermediate and os.path.exists(temp_dir_intermediate):
|
960 |
-
final_output_in_temp = os.path.join(temp_dir_intermediate,
|
961 |
|
962 |
for path in temp_intermediate_files:
|
963 |
try:
|
964 |
-
if os.path.isfile(path) and path != final_output_in_temp:
|
965 |
logger.debug(f"Eliminando archivo temporal intermedio: {path}")
|
966 |
os.remove(path)
|
967 |
-
elif os.path.isfile(path) and path == final_output_in_temp:
|
968 |
logger.debug(f"Saltando eliminación del archivo de video final: {path}")
|
969 |
except Exception as e:
|
970 |
logger.warning(f"No se pudo eliminar archivo temporal intermedio {path}: {str(e)}")
|
971 |
|
972 |
-
logger.info(f"Directorio temporal intermedio {temp_dir_intermediate} persistirá para que Gradio lea el video final.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
859 |
except Exception as e:
|
860 |
logger.warning(f"Error ajustando duración del audio final: {str(e)}")
|
861 |
|
862 |
+
# 7. Crear video final (INDENTACIÓN ORIGINAL)
|
863 |
output_filename = f"video_{int(time.time())}.mp4" # Nombre único con timestamp
|
864 |
output_path = os.path.join(temp_dir_intermediate, output_filename)
|
865 |
|
|
|
881 |
# Mover a ubicación permanente en /tmp
|
882 |
permanent_path = f"/tmp/{output_filename}"
|
883 |
try:
|
884 |
+
shutil.copy(output_path, permanent_path) # Usamos copy() en lugar de move()
|
885 |
logger.info(f"Video guardado permanentemente en: {permanent_path}")
|
886 |
except Exception as move_error:
|
887 |
logger.error(f"Error moviendo archivo: {str(move_error)}. Usando path original.")
|
|
|
957 |
logger.warning(f"Error cerrando video_base en finally: {str(e)}")
|
958 |
|
959 |
if temp_dir_intermediate and os.path.exists(temp_dir_intermediate):
|
960 |
+
final_output_in_temp = os.path.join(temp_dir_intermediate, output_filename)
|
961 |
|
962 |
for path in temp_intermediate_files:
|
963 |
try:
|
964 |
+
if os.path.isfile(path) and path != final_output_in_temp and path != permanent_path:
|
965 |
logger.debug(f"Eliminando archivo temporal intermedio: {path}")
|
966 |
os.remove(path)
|
967 |
+
elif os.path.isfile(path) and (path == final_output_in_temp or path == permanent_path):
|
968 |
logger.debug(f"Saltando eliminación del archivo de video final: {path}")
|
969 |
except Exception as e:
|
970 |
logger.warning(f"No se pudo eliminar archivo temporal intermedio {path}: {str(e)}")
|
971 |
|
972 |
+
logger.info(f"Directorio temporal intermedio {temp_dir_intermediate} persistirá para que Gradio lea el video final.")
|
973 |
+
|
974 |
+
# run_app ahora recibe todos los inputs, incluyendo la voz seleccionada
|
975 |
+
def run_app(prompt_type, prompt_ia, prompt_manual, musica_file, selected_voice): # <-- Recibe el valor del Dropdown
|
976 |
+
logger.info("="*80)
|
977 |
+
logger.info("SOLICITUD RECIBIDA EN INTERFAZ")
|
978 |
+
|
979 |
+
# Elegir el texto de entrada basado en el prompt_type
|
980 |
+
input_text = prompt_ia if prompt_type == "Generar Guion con IA" else prompt_manual
|
981 |
+
|
982 |
+
output_video = None
|
983 |
+
output_file = None
|
984 |
+
status_msg = gr.update(value="⏳ Procesando...", interactive=False)
|
985 |
+
|
986 |
+
if not input_text or not input_text.strip():
|
987 |
+
logger.warning("Texto de entrada vacío.")
|
988 |
+
# Retornar None para video y archivo, actualizar estado con mensaje de error
|
989 |
+
return None, None, gr.update(value="⚠️ Por favor, ingresa texto para el guion o el tema.", interactive=False)
|
990 |
+
|
991 |
+
# Validar la voz seleccionada. Si no es válida, usar la por defecto.
|
992 |
+
# AVAILABLE_VOICES se obtiene al inicio. Hay que buscar si el voice_id existe en la lista de pares (nombre, id)
|
993 |
+
voice_ids_disponibles = [v[1] for v in AVAILABLE_VOICES]
|
994 |
+
if selected_voice not in voice_ids_disponibles:
|
995 |
+
logger.warning(f"Voz seleccionada inválida o no encontrada en la lista: '{selected_voice}'. Usando voz por defecto: {DEFAULT_VOICE_ID}.")
|
996 |
+
selected_voice = DEFAULT_VOICE_ID # <-- Usar el ID de la voz por defecto
|
997 |
+
else:
|
998 |
+
logger.info(f"Voz seleccionada validada: {selected_voice}")
|
999 |
+
|
1000 |
+
|
1001 |
+
logger.info(f"Tipo de entrada: {prompt_type}")
|
1002 |
+
logger.debug(f"Texto de entrada: '{input_text[:100]}...'")
|
1003 |
+
if musica_file:
|
1004 |
+
logger.info(f"Archivo de música recibido: {musica_file}")
|
1005 |
+
else:
|
1006 |
+
logger.info("No se proporcionó archivo de música.")
|
1007 |
+
logger.info(f"Voz final a usar (ID): {selected_voice}") # Loguear el ID de la voz final
|
1008 |
+
|
1009 |
+
try:
|
1010 |
+
logger.info("Llamando a crear_video...")
|
1011 |
+
# Pasar el input_text elegido, la voz seleccionada (el ID) y el archivo de música a crear_video
|
1012 |
+
video_path = crear_video(prompt_type, input_text, selected_voice, musica_file) # <-- PASAR selected_voice (ID) a crear_video
|
1013 |
+
|
1014 |
+
if video_path and os.path.exists(video_path):
|
1015 |
+
logger.info(f"crear_video retornó path: {video_path}")
|
1016 |
+
logger.info(f"Tamaño del archivo de video retornado: {os.path.getsize(video_path)} bytes")
|
1017 |
+
output_video = video_path # Establecer valor del componente de video
|
1018 |
+
output_file = video_path # Establecer valor del componente de archivo para descarga
|
1019 |
+
status_msg = gr.update(value="✅ Video generado exitosamente.", interactive=False)
|
1020 |
+
else:
|
1021 |
+
logger.error(f"crear_video no retornó un path válido o el archivo no existe: {video_path}")
|
1022 |
+
status_msg = gr.update(value="❌ Error: La generación del video falló o el archivo no se creó correctamente.", interactive=False)
|
1023 |
+
|
1024 |
+
except ValueError as ve:
|
1025 |
+
logger.warning(f"Error de validación durante la creación del video: {str(ve)}")
|
1026 |
+
status_msg = gr.update(value=f"⚠️ Error de validación: {str(ve)}", interactive=False)
|
1027 |
+
except Exception as e:
|
1028 |
+
logger.critical(f"Error crítico durante la creación del video: {str(e)}", exc_info=True)
|
1029 |
+
status_msg = gr.update(value=f"❌ Error inesperado: {str(e)}", interactive=False)
|
1030 |
+
finally:
|
1031 |
+
logger.info("Fin del handler run_app.")
|
1032 |
+
return output_video, output_file, status_msg
|
1033 |
+
|
1034 |
+
|
1035 |
+
# Interfaz de Gradio
|
1036 |
+
with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="""
|
1037 |
+
.gradio-container {max-width: 800px; margin: auto;}
|
1038 |
+
h1 {text-align: center;}
|
1039 |
+
""") as app:
|
1040 |
+
|
1041 |
+
gr.Markdown("# 🎬 Generador Automático de Videos con IA")
|
1042 |
+
gr.Markdown("Genera videos cortos a partir de un tema o guion, usando imágenes de archivo de Pexels y voz generada.")
|
1043 |
+
|
1044 |
+
with gr.Row():
|
1045 |
+
with gr.Column():
|
1046 |
+
prompt_type = gr.Radio(
|
1047 |
+
["Generar Guion con IA", "Usar Mi Guion"],
|
1048 |
+
label="Método de Entrada",
|
1049 |
+
value="Generar Guion con IA"
|
1050 |
+
)
|
1051 |
+
|
1052 |
+
# Contenedores para los campos de texto para controlar la visibilidad
|
1053 |
+
with gr.Column(visible=True) as ia_guion_column:
|
1054 |
+
prompt_ia = gr.Textbox(
|
1055 |
+
label="Tema para IA",
|
1056 |
+
lines=2,
|
1057 |
+
placeholder="Ej: Un paisaje natural con montañas y ríos al amanecer, mostrando la belleza de la naturaleza...",
|
1058 |
+
max_lines=4,
|
1059 |
+
value=""
|
1060 |
+
)
|
1061 |
+
|
1062 |
+
with gr.Column(visible=False) as manual_guion_column:
|
1063 |
+
prompt_manual = gr.Textbox(
|
1064 |
+
label="Tu Guion Completo",
|
1065 |
+
lines=5,
|
1066 |
+
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!",
|
1067 |
+
max_lines=10,
|
1068 |
+
value=""
|
1069 |
+
)
|
1070 |
+
|
1071 |
+
musica_input = gr.Audio(
|
1072 |
+
label="Música de fondo (opcional)",
|
1073 |
+
type="filepath",
|
1074 |
+
interactive=True,
|
1075 |
+
value=None
|
1076 |
+
)
|
1077 |
+
|
1078 |
+
# --- COMPONENTE: Selección de Voz ---
|
1079 |
+
voice_dropdown = gr.Dropdown(
|
1080 |
+
label="Seleccionar Voz para Guion",
|
1081 |
+
choices=AVAILABLE_VOICES,
|
1082 |
+
value=DEFAULT_VOICE_ID,
|
1083 |
+
interactive=True
|
1084 |
+
)
|
1085 |
+
# --- FIN COMPONENTE ---
|
1086 |
+
|
1087 |
+
generate_btn = gr.Button("✨ Generar Video", variant="primary")
|
1088 |
+
|
1089 |
+
with gr.Column():
|
1090 |
+
video_output = gr.Video(
|
1091 |
+
label="Previsualización del Video Generado",
|
1092 |
+
interactive=False,
|
1093 |
+
height=400
|
1094 |
+
)
|
1095 |
+
file_output = gr.File(
|
1096 |
+
label="Descargar Archivo de Video",
|
1097 |
+
interactive=False,
|
1098 |
+
visible=False
|
1099 |
+
)
|
1100 |
+
status_output = gr.Textbox(
|
1101 |
+
label="Estado",
|
1102 |
+
interactive=False,
|
1103 |
+
show_label=False,
|
1104 |
+
placeholder="Esperando acción...",
|
1105 |
+
value="Esperando entrada..."
|
1106 |
+
)
|
1107 |
+
|
1108 |
+
# Evento para mostrar/ocultar los campos de texto según el tipo de prompt
|
1109 |
+
prompt_type.change(
|
1110 |
+
lambda x: (gr.update(visible=x == "Generar Guion con IA"),
|
1111 |
+
gr.update(visible=x == "Usar Mi Guion")),
|
1112 |
+
inputs=prompt_type,
|
1113 |
+
outputs=[ia_guion_column, manual_guion_column]
|
1114 |
+
)
|
1115 |
+
|
1116 |
+
# Evento click del botón de generar video
|
1117 |
+
generate_btn.click(
|
1118 |
+
lambda: (None, None, gr.update(value="⏳ Procesando... Esto puede tomar varios minutos.", interactive=False)),
|
1119 |
+
outputs=[video_output, file_output, status_output],
|
1120 |
+
queue=True,
|
1121 |
+
).then(
|
1122 |
+
run_app,
|
1123 |
+
inputs=[prompt_type, prompt_ia, prompt_manual, musica_input, voice_dropdown],
|
1124 |
+
outputs=[video_output, file_output, status_output]
|
1125 |
+
).then(
|
1126 |
+
lambda video_path, file_path, status_msg: gr.update(visible=file_path is not None),
|
1127 |
+
inputs=[video_output, file_output, status_output],
|
1128 |
+
outputs=[file_output]
|
1129 |
+
)
|
1130 |
+
|
1131 |
+
gr.Markdown("### Instrucciones:")
|
1132 |
+
gr.Markdown("""
|
1133 |
+
1. **Clave API de Pexels:** Asegúrate de haber configurado la variable de entorno `PEXELS_API_KEY` con tu clave.
|
1134 |
+
2. **Selecciona el tipo de entrada**: "Generar Guion con IA" o "Usar Mi Guion".
|
1135 |
+
3. **Sube música** (opcional): Selecciona un archivo de audio (MP3, WAV, etc.).
|
1136 |
+
4. **Selecciona la voz** deseada del desplegable.
|
1137 |
+
5. **Haz clic en "✨ Generar Video"**.
|
1138 |
+
6. Espera a que se procese el video. Verás el estado.
|
1139 |
+
7. La previsualización aparecerá si es posible, y siempre un enlace **Descargar Archivo de Video** se mostrará si la generación fue exitosa.
|
1140 |
+
8. Revisa `video_generator_full.log` para detalles si hay errores.
|
1141 |
+
""")
|
1142 |
+
gr.Markdown("---")
|
1143 |
+
gr.Markdown("Desarrollado por [Tu Nombre/Empresa/Alias - Opcional]")
|
1144 |
+
|
1145 |
+
if __name__ == "__main__":
|
1146 |
+
logger.info("Verificando dependencias críticas...")
|
1147 |
+
try:
|
1148 |
+
from moviepy.editor import ColorClip
|
1149 |
+
try:
|
1150 |
+
temp_clip = ColorClip((100,100), color=(255,0,0), duration=0.1)
|
1151 |
+
temp_clip.close()
|
1152 |
+
logger.info("Clips base de MoviePy creados y cerrados exitosamente. FFmpeg parece accesible.")
|
1153 |
+
except Exception as e:
|
1154 |
+
logger.critical(f"Fallo al crear clip base de MoviePy. A menudo indica problemas con FFmpeg/ImageMagick. Error: {e}", exc_info=True)
|
1155 |
+
except Exception as e:
|
1156 |
+
logger.critical(f"Fallo al importar MoviePy. Asegúrate de que está instalado. Error: {e}", exc_info=True)
|
1157 |
+
|
1158 |
+
# Solución para el timeout de Gradio
|
1159 |
+
os.environ['GRADIO_SERVER_TIMEOUT'] = '6000' # 600 segundos = 10 minutos
|
1160 |
+
|
1161 |
+
logger.info("Iniciando aplicación Gradio...")
|
1162 |
+
try:
|
1163 |
+
app.launch(server_name="0.0.0.0", server_port=7860, share=False)
|
1164 |
+
except Exception as e:
|
1165 |
+
logger.critical(f"No se pudo iniciar la app: {str(e)}", exc_info=True)
|
1166 |
+
raise
|