gnosticdev commited on
Commit
9c2cc80
·
verified ·
1 Parent(s): 536c10a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +23 -405
app.py CHANGED
@@ -764,6 +764,7 @@ def crear_video(prompt_type, input_text, musica_file=None):
764
 
765
  total_time = (datetime.now() - start_time).total_seconds()
766
  logger.info(f"PROCESO DE VIDEO FINALIZADO | Output: {output_path} | Tiempo total: {total_time:.2f}s")
 
767
  return output_path
768
 
769
  except ValueError as ve:
@@ -836,37 +837,43 @@ def crear_video(prompt_type, input_text, musica_file=None):
836
  logger.warning(f"No se pudo eliminar archivo temporal intermedio {path}: {str(e)}")
837
 
838
  logger.info(f"Directorio temporal intermedio {temp_dir_intermediate} persistirá para que Gradio lea el video final.")
839
-
840
  def run_app(prompt_type, prompt_ia, prompt_manual, musica_file):
841
  logger.info("="*80)
842
  logger.info("SOLICITUD RECIBIDA EN INTERFAZ")
 
843
  input_text = prompt_ia if prompt_type == "Generar Guion con IA" else prompt_manual
 
844
  output_video = None
845
  output_file = gr.update(value=None, visible=False)
846
  status_msg = gr.update(value="⏳ Procesando...", interactive=False)
 
847
  if not input_text or not input_text.strip():
848
  logger.warning("Texto de entrada vacío.")
849
  status_msg = gr.update(value="⚠️ Por favor, ingresa texto para el guion o el tema.", interactive=False)
850
  return output_video, output_file, status_msg
 
851
  logger.info(f"Tipo de entrada: {prompt_type}")
852
  logger.debug(f"Texto de entrada: '{input_text[:100]}...'")
853
  if musica_file:
854
  logger.info(f"Archivo de música recibido: {musica_file}")
855
  else:
856
  logger.info("No se proporcionó archivo de música.")
 
857
  try:
858
  logger.info("Llamando a crear_video...")
859
  video_path = crear_video(prompt_type, input_text, musica_file)
 
860
  if video_path and os.path.exists(video_path):
861
  logger.info(f"crear_video retornó path: {video_path}")
862
  logger.info(f"Tamaño del archivo de video retornado: {os.path.getsize(video_path)} bytes")
863
  output_video = video_path
864
  output_file = gr.update(value=video_path, visible=True)
865
  status_msg = gr.update(value="✅ Video generado exitosamente.", interactive=False)
866
- print(f"\n\nLINK DE DESCARGA DIRECTO: file://{video_path}\n\n")
867
  else:
868
  logger.error(f"crear_video no retornó un path válido o el archivo no existe: {video_path}")
869
  status_msg = gr.update(value="❌ Error: La generación del video falló o el archivo no se creó correctamente.", interactive=False)
 
870
  except ValueError as ve:
871
  logger.warning(f"Error de validación durante la creación del video: {str(ve)}")
872
  status_msg = gr.update(value=f"⚠️ Error de validación: {str(ve)}", interactive=False)
@@ -877,403 +884,14 @@ def run_app(prompt_type, prompt_ia, prompt_manual, musica_file):
877
  logger.info("Fin del handler run_app.")
878
  return output_video, output_file, status_msg
879
 
880
- def schedule_directory_deletion(directory_path, delay_hours=3):
881
- import threading
882
- import time
883
- import shutil
884
- def delete_directory():
885
- time.sleep(delay_hours * 3600)
886
- try:
887
- if os.path.exists(directory_path):
888
- shutil.rmtree(directory_path)
889
- logger.info(f"Directorio temporal autoeliminado: {directory_path}")
890
- except Exception as e:
891
- logger.warning(f"No se pudo eliminar directorio {directory_path}: {str(e)}")
892
- thread = threading.Thread(target=delete_directory)
893
- thread.daemon = True
894
- thread.start()
895
-
896
- def crear_video(prompt_type, input_text, musica_file=None):
897
- logger.info("="*80)
898
- logger.info(f"INICIANDO CREACIÓN DE VIDEO | Tipo: {prompt_type}")
899
- logger.debug(f"Input: '{input_text[:100]}...'")
900
- start_time = datetime.now()
901
- temp_dir_intermediate = None
902
- audio_tts_original = None
903
- musica_audio_original = None
904
- audio_tts = None
905
- musica_audio = None
906
- video_base = None
907
- video_final = None
908
- source_clips = []
909
- clips_to_concatenate = []
910
- try:
911
- if prompt_type == "Generar Guion con IA":
912
- guion = generate_script(input_text)
913
- else:
914
- guion = input_text.strip()
915
- logger.info(f"Guion final ({len(guion)} chars): '{guion[:100]}...'")
916
- if not guion.strip():
917
- logger.error("El guion resultante está vacío o solo contiene espacios.")
918
- raise ValueError("El guion está vacío.")
919
- guion = guion.replace("na hora", "A la hora")
920
- temp_dir_intermediate = tempfile.mkdtemp(prefix="video_gen_intermediate_")
921
- logger.info(f"Directorio temporal intermedio creado: {temp_dir_intermediate}")
922
- logger.info("Generando audio de voz...")
923
- voz_path = os.path.join(temp_dir_intermediate, "voz.mp3")
924
- tts_success = text_to_speech(guion, voz_path)
925
- if not tts_success or not os.path.exists(voz_path) or os.path.getsize(voz_path) <= 1000:
926
- logger.error(f"Fallo en la generación de voz. Archivo de audio no creado o es muy pequeño: {voz_path}")
927
- raise ValueError("Error generando voz a partir del guion (fallo de TTS).")
928
- audio_tts_original = AudioFileClip(voz_path)
929
- if audio_tts_original.reader is None or audio_tts_original.duration is None or audio_tts_original.duration <= 0:
930
- logger.critical("Clip de audio TTS inicial es inválido (reader is None o duración <= 0) *después* de crear AudioFileClip.")
931
- try: audio_tts_original.close()
932
- except: pass
933
- audio_tts_original = None
934
- raise ValueError("Audio de voz generado es inválido después de procesamiento inicial.")
935
- audio_tts = audio_tts_original
936
- audio_duration = audio_tts_original.duration
937
- logger.info(f"Duración audio voz: {audio_duration:.2f} segundos")
938
- if audio_duration < 1.0:
939
- logger.error(f"Duración audio voz ({audio_duration:.2f}s) es muy corta.")
940
- raise ValueError("Generated voice audio is too short (min 1 second required).")
941
- logger.info("Extrayendo palabras clave...")
942
- try:
943
- keywords = extract_visual_keywords_from_script(guion)
944
- logger.info(f"Palabras clave identificadas: {keywords}")
945
- except Exception as e:
946
- logger.error(f"Error extrayendo keywords: {str(e)}", exc_info=True)
947
- keywords = ["naturaleza", "paisaje"]
948
- if not keywords:
949
- keywords = ["video", "background"]
950
- logger.info("Buscando videos en Pexels...")
951
- videos_data = []
952
- total_desired_videos = 10
953
- per_page_per_keyword = max(1, total_desired_videos // len(keywords))
954
- for keyword in keywords:
955
- if len(videos_data) >= total_desired_videos: break
956
- try:
957
- videos = buscar_videos_pexels(keyword, PEXELS_API_KEY, per_page=per_page_per_keyword)
958
- if videos:
959
- videos_data.extend(videos)
960
- logger.info(f"Encontrados {len(videos)} videos para '{keyword}'. Total data: {len(videos_data)}")
961
- except Exception as e:
962
- logger.warning(f"Error buscando videos para '{keyword}': {str(e)}")
963
- if len(videos_data) < total_desired_videos / 2:
964
- logger.warning(f"Pocos videos encontrados ({len(videos_data)}). Intentando con palabras clave genéricas.")
965
- generic_keywords = ["nature", "city", "background", "abstract"]
966
- for keyword in generic_keywords:
967
- if len(videos_data) >= total_desired_videos: break
968
- try:
969
- videos = buscar_videos_pexels(keyword, PEXELS_API_KEY, per_page=2)
970
- if videos:
971
- videos_data.extend(videos)
972
- logger.info(f"Encontrados {len(videos)} videos para '{keyword}' (genérico). Total data: {len(videos_data)}")
973
- except Exception as e:
974
- logger.warning(f"Error buscando videos genéricos para '{keyword}': {str(e)}")
975
- if not videos_data:
976
- logger.error("No se encontraron videos en Pexels para ninguna palabra clave.")
977
- raise ValueError("No se encontraron videos adecuados en Pexels.")
978
- video_paths = []
979
- logger.info(f"Intentando descargar {len(videos_data)} videos encontrados...")
980
- for video in videos_data:
981
- if 'video_files' not in video or not video['video_files']:
982
- logger.debug(f"Saltando video sin archivos de video: {video.get('id')}")
983
- continue
984
- try:
985
- best_quality = None
986
- for vf in sorted(video['video_files'], key=lambda x: x.get('width', 0) * x.get('height', 0), reverse=True):
987
- if 'link' in vf:
988
- best_quality = vf
989
- break
990
- if best_quality and 'link' in best_quality:
991
- path = download_video_file(best_quality['link'], temp_dir_intermediate)
992
- if path:
993
- video_paths.append(path)
994
- logger.info(f"Video descargado OK desde {best_quality['link'][:50]}...")
995
- else:
996
- logger.warning(f"No se pudo descargar video desde {best_quality['link'][:50]}...")
997
- else:
998
- logger.warning(f"No se encontró enlace de descarga válido para video {video.get('id')}.")
999
- except Exception as e:
1000
- logger.warning(f"Error procesando/descargando video {video.get('id')}: {str(e)}")
1001
- logger.info(f"Descargados {len(video_paths)} archivos de video utilizables.")
1002
- if not video_paths:
1003
- logger.error("No se pudo descargar ningún archivo de video utilizable.")
1004
- raise ValueError("No se pudo descargar ningún video utilizable de Pexels.")
1005
- logger.info("Procesando y concatenando videos descargados...")
1006
- current_duration = 0
1007
- min_clip_duration = 0.5
1008
- max_clip_segment = 10.0
1009
- for i, path in enumerate(video_paths):
1010
- if current_duration >= audio_duration + max_clip_segment:
1011
- logger.debug(f"Video base suficiente ({current_duration:.1f}s >= {audio_duration:.1f}s + {max_clip_segment:.1f}s buffer). Dejando de procesar clips fuente restantes.")
1012
- break
1013
- clip = None
1014
- try:
1015
- logger.debug(f"[{i+1}/{len(video_paths)}] Abriendo clip: {path}")
1016
- clip = VideoFileClip(path)
1017
- source_clips.append(clip)
1018
- if clip.reader is None or clip.duration is None or clip.duration <= 0:
1019
- logger.warning(f"[{i+1}/{len(video_paths)}] Clip fuente {path} parece inválido (reader is None o duración <= 0). Saltando.")
1020
- continue
1021
- remaining_needed = audio_duration - current_duration
1022
- potential_use_duration = min(clip.duration, max_clip_segment)
1023
- if remaining_needed > 0:
1024
- segment_duration = min(potential_use_duration, remaining_needed + min_clip_duration)
1025
- segment_duration = max(min_clip_duration, segment_duration)
1026
- segment_duration = min(segment_duration, clip.duration)
1027
- if segment_duration >= min_clip_duration:
1028
- try:
1029
- sub = clip.subclip(0, segment_duration)
1030
- if sub.reader is None or sub.duration is None or sub.duration <= 0:
1031
- logger.warning(f"[{i+1}/{len(video_paths)}] Subclip generado de {path} es inválido. Saltando.")
1032
- try: sub.close()
1033
- except: pass
1034
- continue
1035
- clips_to_concatenate.append(sub)
1036
- current_duration += sub.duration
1037
- logger.debug(f"[{i+1}/{len(video_paths)}] Segmento añadido: {sub.duration:.1f}s (total video: {current_duration:.1f}/{audio_duration:.1f}s)")
1038
- except Exception as sub_e:
1039
- logger.warning(f"[{i+1}/{len(video_paths)}] Error creando subclip de {path} ({segment_duration:.1f}s): {str(sub_e)}")
1040
- continue
1041
- else:
1042
- logger.debug(f"[{i+1}/{len(video_paths)}] Clip {path} ({clip.duration:.1f}s) no contribuye un segmento suficiente ({segment_duration:.1f}s necesario). Saltando.")
1043
- else:
1044
- logger.debug(f"[{i+1}/{len(video_paths)}] Duración de video base ya alcanzada. Saltando clip.")
1045
- except Exception as e:
1046
- logger.warning(f"[{i+1}/{len(video_paths)}] Error procesando video {path}: {str(e)}", exc_info=True)
1047
- continue
1048
- logger.info(f"Procesamiento de clips fuente finalizado. Se obtuvieron {len(clips_to_concatenate)} segmentos válidos.")
1049
- if not clips_to_concatenate:
1050
- logger.error("No hay segmentos de video válidos disponibles para crear la secuencia.")
1051
- raise ValueError("No hay segmentos de video válidos disponibles para crear el video.")
1052
- logger.info(f"Concatenando {len(clips_to_concatenate)} segmentos de video.")
1053
- concatenated_base = None
1054
- try:
1055
- concatenated_base = concatenate_videoclips(clips_to_concatenate, method="chain")
1056
- logger.info(f"Duración video base después de concatenación inicial: {concatenated_base.duration:.2f}s")
1057
- if concatenated_base is None or concatenated_base.duration is None or concatenated_base.duration <= 0:
1058
- logger.critical("Video base concatenado es inválido después de la primera concatenación (None o duración cero).")
1059
- raise ValueError("Fallo al crear video base válido a partir de segmentos.")
1060
- except Exception as e:
1061
- logger.critical(f"Error durante la concatenación inicial: {str(e)}", exc_info=True)
1062
- raise ValueError("Fallo durante la concatenación de video inicial.")
1063
- finally:
1064
- for clip_segment in clips_to_concatenate:
1065
- try: clip_segment.close()
1066
- except: pass
1067
- clips_to_concatenate = []
1068
- video_base = concatenated_base
1069
- final_video_base = video_base
1070
- if final_video_base.duration < audio_duration:
1071
- logger.info(f"Video base ({final_video_base.duration:.2f}s) es más corto que el audio ({audio_duration:.2f}s). Repitiendo...")
1072
- num_full_repeats = int(audio_duration // final_video_base.duration)
1073
- remaining_duration = audio_duration % final_video_base.duration
1074
- repeated_clips_list = [final_video_base] * num_full_repeats
1075
- if remaining_duration > 0:
1076
- try:
1077
- remaining_clip = final_video_base.subclip(0, remaining_duration)
1078
- if remaining_clip is None or remaining_clip.duration is None or remaining_clip.duration <= 0:
1079
- logger.warning(f"Subclip generado para duración restante {remaining_duration:.2f}s es inválido. Saltando.")
1080
- try: remaining_clip.close()
1081
- except: pass
1082
- else:
1083
- repeated_clips_list.append(remaining_clip)
1084
- logger.debug(f"Añadiendo segmento restante: {remaining_duration:.2f}s")
1085
- except Exception as e:
1086
- logger.warning(f"Error creando subclip para duración restante {remaining_duration:.2f}s: {str(e)}")
1087
- if repeated_clips_list:
1088
- logger.info(f"Concatenando {len(repeated_clips_list)} partes para repetición.")
1089
- video_base_repeated = None
1090
- try:
1091
- video_base_repeated = concatenate_videoclips(repeated_clips_list, method="chain")
1092
- logger.info(f"Duración del video base repetido: {video_base_repeated.duration:.2f}s")
1093
- if video_base_repeated is None or video_base_repeated.duration is None or video_base_repeated.duration <= 0:
1094
- logger.critical("Video base repetido concatenado es inválido.")
1095
- raise ValueError("Fallo al crear video base repetido válido.")
1096
- if final_video_base is not video_base_repeated:
1097
- try: final_video_base.close()
1098
- except: pass
1099
- final_video_base = video_base_repeated
1100
- except Exception as e:
1101
- logger.critical(f"Error durante la concatenación de repetición: {str(e)}", exc_info=True)
1102
- raise ValueError("Fallo durante la repetición de video.")
1103
- finally:
1104
- for clip in repeated_clips_list:
1105
- if clip is not final_video_base:
1106
- try: clip.close()
1107
- except: pass
1108
- if final_video_base.duration > audio_duration:
1109
- logger.info(f"Recortando video base ({final_video_base.duration:.2f}s) para que coincida con la duración del audio ({audio_duration:.2f}s).")
1110
- trimmed_video_base = None
1111
- try:
1112
- trimmed_video_base = final_video_base.subclip(0, audio_duration)
1113
- if trimmed_video_base is None or trimmed_video_base.duration is None or trimmed_video_base.duration <= 0:
1114
- logger.critical("Video base recortado es inválido.")
1115
- raise ValueError("Fallo al crear video base recortado válido.")
1116
- if final_video_base is not trimmed_video_base:
1117
- try: final_video_base.close()
1118
- except: pass
1119
- final_video_base = trimmed_video_base
1120
- except Exception as e:
1121
- logger.critical(f"Error durante el recorte: {str(e)}", exc_info=True)
1122
- raise ValueError("Fallo durante el recorte de video.")
1123
- if final_video_base is None or final_video_base.duration is None or final_video_base.duration <= 0:
1124
- logger.critical("Video base final es inválido antes de audio/escritura (None o duración cero).")
1125
- raise ValueError("Video base final es inválido.")
1126
- if final_video_base.size is None or final_video_base.size[0] <= 0 or final_video_base.size[1] <= 0:
1127
- logger.critical(f"Video base final tiene tamaño inválido: {final_video_base.size}. No se puede escribir video.")
1128
- raise ValueError("Video base final tiene tamaño inválido antes de escribir.")
1129
- video_base = final_video_base
1130
- logger.info("Procesando audio...")
1131
- final_audio = audio_tts_original
1132
- musica_audio_looped = None
1133
- if musica_file:
1134
- musica_audio_original = None
1135
- try:
1136
- music_path = os.path.join(temp_dir_intermediate, "musica_bg.mp3")
1137
- shutil.copyfile(musica_file, music_path)
1138
- logger.info(f"Música de fondo copiada a: {music_path}")
1139
- musica_audio_original = AudioFileClip(music_path)
1140
- if musica_audio_original.reader is None or musica_audio_original.duration is None or musica_audio_original.duration <= 0:
1141
- logger.warning("Clip de música de fondo parece inválido o tiene duración cero. Saltando música.")
1142
- try: musica_audio_original.close()
1143
- except: pass
1144
- musica_audio_original = None
1145
- else:
1146
- musica_audio_looped = loop_audio_to_length(musica_audio_original, video_base.duration)
1147
- logger.debug(f"Música ajustada a duración del video: {musica_audio_looped.duration:.2f}s")
1148
- if musica_audio_looped is None or musica_audio_looped.duration is None or musica_audio_looped.duration <= 0:
1149
- logger.warning("Clip de música de fondo loopeado es inválido. Saltando música.")
1150
- try: musica_audio_looped.close()
1151
- except: pass
1152
- musica_audio_looped = None
1153
- if musica_audio_looped:
1154
- composite_audio = CompositeAudioClip([
1155
- musica_audio_looped.volumex(0.2),
1156
- audio_tts_original.volumex(1.0)
1157
- ])
1158
- if composite_audio.duration is None or composite_audio.duration <= 0:
1159
- logger.warning("Clip de audio compuesto es inválido (None o duración cero). Usando solo audio de voz.")
1160
- try: composite_audio.close()
1161
- except: pass
1162
- final_audio = audio_tts_original
1163
- else:
1164
- logger.info("Mezcla de audio completada (voz + música).")
1165
- final_audio = composite_audio
1166
- musica_audio = musica_audio_looped
1167
- except Exception as e:
1168
- logger.warning(f"Error procesando música de fondo: {str(e)}", exc_info=True)
1169
- final_audio = audio_tts_original
1170
- musica_audio = None
1171
- logger.warning("Usando solo audio de voz debido a un error con la música.")
1172
- if final_audio.duration is not None and abs(final_audio.duration - video_base.duration) > 0.2:
1173
- logger.warning(f"Duración del audio final ({final_audio.duration:.2f}s) difiere significativamente del video base ({video_base.duration:.2f}s). Intentando recorte.")
1174
- try:
1175
- if final_audio.duration > video_base.duration:
1176
- trimmed_final_audio = final_audio.subclip(0, video_base.duration)
1177
- if trimmed_final_audio is None or trimmed_final_audio.duration <= 0:
1178
- logger.warning("Audio final recortado es inválido. Usando audio final original.")
1179
- try: trimmed_final_audio.close()
1180
- except: pass
1181
- else:
1182
- if final_audio is not trimmed_final_audio:
1183
- try: final_audio.close()
1184
- except: pass
1185
- final_audio = trimmed_final_audio
1186
- logger.warning("Audio final recortado para que coincida con la duración del video.")
1187
- except Exception as e:
1188
- logger.warning(f"Error ajustando duración del audio final: {str(e)}")
1189
- logger.info("Renderizando video final...")
1190
- video_final = video_base.set_audio(final_audio)
1191
- if video_final is None or video_final.duration is None or video_final.duration <= 0:
1192
- logger.critical("Clip de video final (con audio) es inválido antes de escribir (None o duración cero).")
1193
- raise ValueError("Clip de video final es inválido antes de escribir.")
1194
- output_filename = "final_video.mp4"
1195
- output_path = os.path.join(temp_dir_intermediate, output_filename)
1196
- logger.info(f"Escribiendo video final a: {output_path}")
1197
- if not output_path or not isinstance(output_path, str):
1198
- logger.critical(f"output_path no es válido: {output_path}")
1199
- raise ValueError("El nombre del archivo de salida no es válido.")
1200
- try:
1201
- video_final.write_videofile(
1202
- filename=output_path,
1203
- fps=24,
1204
- threads=4,
1205
- codec="libx264",
1206
- audio_codec="aac",
1207
- preset="medium",
1208
- logger='bar',
1209
- bitrate="2000k",
1210
- ffmpeg_params=['-vf', 'scale=1280:720']
1211
- )
1212
- except Exception as e:
1213
- logger.critical(f"Error al escribir el video final: {str(e)}", exc_info=True)
1214
- raise ValueError(f"Fallo al escribir el video final: {str(e)}")
1215
- total_time = (datetime.now() - start_time).total_seconds()
1216
- logger.info(f"PROCESO DE VIDEO FINALIZADO | Output: {output_path} | Tiempo total: {total_time:.2f}s")
1217
- schedule_directory_deletion(temp_dir_intermediate)
1218
- return output_path
1219
- except ValueError as ve:
1220
- logger.error(f"ERROR CONTROLADO en crear_video: {str(ve)}")
1221
- raise ve
1222
- except Exception as e:
1223
- logger.critical(f"ERROR CRÍTICO NO CONTROLADO en crear_video: {str(e)}", exc_info=True)
1224
- raise e
1225
- finally:
1226
- logger.info("Iniciando limpieza de clips y archivos temporales intermedios...")
1227
- for clip in source_clips:
1228
- try:
1229
- clip.close()
1230
- except Exception as e:
1231
- logger.warning(f"Error cerrando clip de video fuente en finally: {str(e)}")
1232
- for clip_segment in clips_to_concatenate:
1233
- try:
1234
- clip_segment.close()
1235
- except Exception as e:
1236
- logger.warning(f"Error cerrando segmento de video en finally: {str(e)}")
1237
- if musica_audio is not None:
1238
- try:
1239
- musica_audio.close()
1240
- except Exception as e:
1241
- logger.warning(f"Error cerrando musica_audio (procesada) en finally: {str(e)}")
1242
- if musica_audio_original is not None and musica_audio_original is not musica_audio:
1243
- try:
1244
- musica_audio_original.close()
1245
- except Exception as e:
1246
- logger.warning(f"Error cerrando musica_audio_original en finally: {str(e)}")
1247
- if audio_tts is not None and audio_tts is not audio_tts_original:
1248
- try:
1249
- audio_tts.close()
1250
- except Exception as e:
1251
- logger.warning(f"Error cerrando audio_tts (procesada) en finally: {str(e)}")
1252
- if audio_tts_original is not None:
1253
- try:
1254
- audio_tts_original.close()
1255
- except Exception as e:
1256
- logger.warning(f"Error cerrando audio_tts_original en finally: {str(e)}")
1257
- if video_final is not None:
1258
- try:
1259
- video_final.close()
1260
- except Exception as e:
1261
- logger.warning(f"Error cerrando video_final en finally: {str(e)}")
1262
- elif video_base is not None and video_base is not video_final:
1263
- try:
1264
- video_base.close()
1265
- except Exception as e:
1266
- logger.warning(f"Error cerrando video_base en finally: {str(e)}")
1267
- if temp_dir_intermediate and os.path.exists(temp_dir_intermediate):
1268
- schedule_directory_deletion(temp_dir_intermediate)
1269
- logger.info(f"Directorio temporal {temp_dir_intermediate} programado para eliminación en 3 horas.")
1270
-
1271
  with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="""
1272
  .gradio-container {max-width: 800px; margin: auto;}
1273
  h1 {text-align: center;}
1274
  """) as app:
 
1275
  gr.Markdown("# 🎬 Generador Automático de Videos con IA")
1276
  gr.Markdown("Genera videos cortos a partir de un tema o guion, usando imágenes de archivo de Pexels y voz generada.")
 
1277
  with gr.Row():
1278
  with gr.Column():
1279
  prompt_type = gr.Radio(
@@ -1281,6 +899,7 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
1281
  label="Método de Entrada",
1282
  value="Generar Guion con IA"
1283
  )
 
1284
  with gr.Column(visible=True) as ia_guion_column:
1285
  prompt_ia = gr.Textbox(
1286
  label="Tema para IA",
@@ -1289,6 +908,7 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
1289
  max_lines=4,
1290
  value=""
1291
  )
 
1292
  with gr.Column(visible=False) as manual_guion_column:
1293
  prompt_manual = gr.Textbox(
1294
  label="Tu Guion Completo",
@@ -1297,13 +917,16 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
1297
  max_lines=10,
1298
  value=""
1299
  )
 
1300
  musica_input = gr.Audio(
1301
  label="Música de fondo (opcional)",
1302
  type="filepath",
1303
  interactive=True,
1304
  value=None
1305
  )
 
1306
  generate_btn = gr.Button("✨ Generar Video", variant="primary")
 
1307
  with gr.Column():
1308
  video_output = gr.Video(
1309
  label="Previsualización del Video Generado",
@@ -1322,12 +945,14 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
1322
  placeholder="Esperando acción...",
1323
  value="Esperando entrada..."
1324
  )
 
1325
  prompt_type.change(
1326
  lambda x: (gr.update(visible=x == "Generar Guion con IA"),
1327
  gr.update(visible=x == "Usar Mi Guion")),
1328
  inputs=prompt_type,
1329
  outputs=[ia_guion_column, manual_guion_column]
1330
  )
 
1331
  generate_btn.click(
1332
  lambda: (None, None, gr.update(value="⏳ Procesando... Esto puede tomar varios minutos.", interactive=False)),
1333
  outputs=[video_output, file_output, status_output],
@@ -1337,9 +962,11 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
1337
  inputs=[prompt_type, prompt_ia, prompt_manual, musica_input],
1338
  outputs=[video_output, file_output, status_output]
1339
  )
 
1340
  gr.Markdown("### Instrucciones:")
1341
  gr.Markdown("""
1342
  1. **Clave API de Pexels:** Asegúrate de haber configurado la variable de entorno `PEXELS_API_KEY` con tu clave.
 
1343
  """)
1344
  gr.Markdown("---")
1345
  gr.Markdown("Desarrollado por [Tu Nombre/Empresa/Alias - Opcional]")
@@ -1354,22 +981,13 @@ if __name__ == "__main__":
1354
  logger.info("Clips base de MoviePy (como ColorClip) creados y cerrados exitosamente. FFmpeg parece accesible.")
1355
  except Exception as e:
1356
  logger.critical(f"Fallo al crear clip base de MoviePy. A menudo indica problemas con FFmpeg/ImageMagick. Error: {e}", exc_info=True)
 
1357
  except Exception as e:
1358
  logger.critical(f"Fallo al importar MoviePy. Asegúrate de que está instalado. Error: {e}", exc_info=True)
 
1359
  logger.info("Iniciando aplicación Gradio...")
1360
  try:
1361
- app.queue(
1362
- max_size=1,
1363
- api_open=False
1364
- ).launch(
1365
- server_name="0.0.0.0",
1366
- server_port=7860,
1367
- share=False,
1368
- prevent_thread_lock=True,
1369
- show_error=True,
1370
- max_threads=1,
1371
- timeout=10800 # 3 horas en segundos
1372
- )
1373
  except Exception as e:
1374
  logger.critical(f"No se pudo iniciar la app: {str(e)}", exc_info=True)
1375
  raise
 
764
 
765
  total_time = (datetime.now() - start_time).total_seconds()
766
  logger.info(f"PROCESO DE VIDEO FINALIZADO | Output: {output_path} | Tiempo total: {total_time:.2f}s")
767
+
768
  return output_path
769
 
770
  except ValueError as ve:
 
837
  logger.warning(f"No se pudo eliminar archivo temporal intermedio {path}: {str(e)}")
838
 
839
  logger.info(f"Directorio temporal intermedio {temp_dir_intermediate} persistirá para que Gradio lea el video final.")
840
+
841
  def run_app(prompt_type, prompt_ia, prompt_manual, musica_file):
842
  logger.info("="*80)
843
  logger.info("SOLICITUD RECIBIDA EN INTERFAZ")
844
+
845
  input_text = prompt_ia if prompt_type == "Generar Guion con IA" else prompt_manual
846
+
847
  output_video = None
848
  output_file = gr.update(value=None, visible=False)
849
  status_msg = gr.update(value="⏳ Procesando...", interactive=False)
850
+
851
  if not input_text or not input_text.strip():
852
  logger.warning("Texto de entrada vacío.")
853
  status_msg = gr.update(value="⚠️ Por favor, ingresa texto para el guion o el tema.", interactive=False)
854
  return output_video, output_file, status_msg
855
+
856
  logger.info(f"Tipo de entrada: {prompt_type}")
857
  logger.debug(f"Texto de entrada: '{input_text[:100]}...'")
858
  if musica_file:
859
  logger.info(f"Archivo de música recibido: {musica_file}")
860
  else:
861
  logger.info("No se proporcionó archivo de música.")
862
+
863
  try:
864
  logger.info("Llamando a crear_video...")
865
  video_path = crear_video(prompt_type, input_text, musica_file)
866
+
867
  if video_path and os.path.exists(video_path):
868
  logger.info(f"crear_video retornó path: {video_path}")
869
  logger.info(f"Tamaño del archivo de video retornado: {os.path.getsize(video_path)} bytes")
870
  output_video = video_path
871
  output_file = gr.update(value=video_path, visible=True)
872
  status_msg = gr.update(value="✅ Video generado exitosamente.", interactive=False)
 
873
  else:
874
  logger.error(f"crear_video no retornó un path válido o el archivo no existe: {video_path}")
875
  status_msg = gr.update(value="❌ Error: La generación del video falló o el archivo no se creó correctamente.", interactive=False)
876
+
877
  except ValueError as ve:
878
  logger.warning(f"Error de validación durante la creación del video: {str(ve)}")
879
  status_msg = gr.update(value=f"⚠️ Error de validación: {str(ve)}", interactive=False)
 
884
  logger.info("Fin del handler run_app.")
885
  return output_video, output_file, status_msg
886
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
887
  with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="""
888
  .gradio-container {max-width: 800px; margin: auto;}
889
  h1 {text-align: center;}
890
  """) as app:
891
+
892
  gr.Markdown("# 🎬 Generador Automático de Videos con IA")
893
  gr.Markdown("Genera videos cortos a partir de un tema o guion, usando imágenes de archivo de Pexels y voz generada.")
894
+
895
  with gr.Row():
896
  with gr.Column():
897
  prompt_type = gr.Radio(
 
899
  label="Método de Entrada",
900
  value="Generar Guion con IA"
901
  )
902
+
903
  with gr.Column(visible=True) as ia_guion_column:
904
  prompt_ia = gr.Textbox(
905
  label="Tema para IA",
 
908
  max_lines=4,
909
  value=""
910
  )
911
+
912
  with gr.Column(visible=False) as manual_guion_column:
913
  prompt_manual = gr.Textbox(
914
  label="Tu Guion Completo",
 
917
  max_lines=10,
918
  value=""
919
  )
920
+
921
  musica_input = gr.Audio(
922
  label="Música de fondo (opcional)",
923
  type="filepath",
924
  interactive=True,
925
  value=None
926
  )
927
+
928
  generate_btn = gr.Button("✨ Generar Video", variant="primary")
929
+
930
  with gr.Column():
931
  video_output = gr.Video(
932
  label="Previsualización del Video Generado",
 
945
  placeholder="Esperando acción...",
946
  value="Esperando entrada..."
947
  )
948
+
949
  prompt_type.change(
950
  lambda x: (gr.update(visible=x == "Generar Guion con IA"),
951
  gr.update(visible=x == "Usar Mi Guion")),
952
  inputs=prompt_type,
953
  outputs=[ia_guion_column, manual_guion_column]
954
  )
955
+
956
  generate_btn.click(
957
  lambda: (None, None, gr.update(value="⏳ Procesando... Esto puede tomar varios minutos.", interactive=False)),
958
  outputs=[video_output, file_output, status_output],
 
962
  inputs=[prompt_type, prompt_ia, prompt_manual, musica_input],
963
  outputs=[video_output, file_output, status_output]
964
  )
965
+
966
  gr.Markdown("### Instrucciones:")
967
  gr.Markdown("""
968
  1. **Clave API de Pexels:** Asegúrate de haber configurado la variable de entorno `PEXELS_API_KEY` con tu clave.
969
+
970
  """)
971
  gr.Markdown("---")
972
  gr.Markdown("Desarrollado por [Tu Nombre/Empresa/Alias - Opcional]")
 
981
  logger.info("Clips base de MoviePy (como ColorClip) creados y cerrados exitosamente. FFmpeg parece accesible.")
982
  except Exception as e:
983
  logger.critical(f"Fallo al crear clip base de MoviePy. A menudo indica problemas con FFmpeg/ImageMagick. Error: {e}", exc_info=True)
984
+
985
  except Exception as e:
986
  logger.critical(f"Fallo al importar MoviePy. Asegúrate de que está instalado. Error: {e}", exc_info=True)
987
+
988
  logger.info("Iniciando aplicación Gradio...")
989
  try:
990
+ app.launch(server_name="0.0.0.0", server_port=7860, share=False)
 
 
 
 
 
 
 
 
 
 
 
991
  except Exception as e:
992
  logger.critical(f"No se pudo iniciar la app: {str(e)}", exc_info=True)
993
  raise