Spaces:
Running
Running
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
|