gnosticdev commited on
Commit
a45d148
·
verified ·
1 Parent(s): e0350e4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +54 -58
app.py CHANGED
@@ -15,8 +15,10 @@ import shutil
15
  import json
16
  from collections import Counter
17
 
 
18
  tts_model = None
19
 
 
20
  logging.basicConfig(
21
  level=logging.DEBUG,
22
  format='%(asctime)s - %(levelname)s - %(message)s',
@@ -30,10 +32,12 @@ logger.info("="*80)
30
  logger.info("INICIO DE EJECUCIÓN - GENERADOR DE VIDEOS")
31
  logger.info("="*80)
32
 
 
33
  PEXELS_API_KEY = os.environ.get("PEXELS_API_KEY")
34
  if not PEXELS_API_KEY:
35
  logger.critical("NO SE ENCONTRÓ PEXELS_API_KEY EN VARIABLES DE ENTORNO")
36
 
 
37
  MODEL_NAME = "datificate/gpt2-small-spanish"
38
  logger.info(f"Inicializando modelo GPT-2: {MODEL_NAME}")
39
  tokenizer = None
@@ -178,13 +182,20 @@ def text_to_speech(text, output_path, voice=None):
178
  return False
179
 
180
  try:
 
181
  tts = TTS(model_name="tts_models/es/css10/vits", progress_bar=False, gpu=False)
 
 
182
  text = text.replace("na hora", "A la hora")
183
  text = re.sub(r'[^\w\s.,!?áéíóúñÁÉÍÓÚÑ]', '', text)
184
  if len(text) > 500:
185
  logger.warning("Texto demasiado largo, truncando a 500 caracteres")
186
  text = text[:500]
 
 
187
  tts.tts_to_file(text=text, file_path=output_path)
 
 
188
  if os.path.exists(output_path) and os.path.getsize(output_path) > 1000:
189
  logger.info(f"Audio creado: {output_path} | Tamaño: {os.path.getsize(output_path)} bytes")
190
  return True
@@ -362,6 +373,7 @@ def crear_video(prompt_type, input_text, musica_file=None):
362
  clips_to_concatenate = []
363
 
364
  try:
 
365
  if prompt_type == "Generar Guion con IA":
366
  guion = generate_script(input_text)
367
  else:
@@ -373,14 +385,18 @@ def crear_video(prompt_type, input_text, musica_file=None):
373
  logger.error("El guion resultante está vacío o solo contiene espacios.")
374
  raise ValueError("El guion está vacío.")
375
 
 
376
  guion = guion.replace("na hora", "A la hora")
377
 
378
  temp_dir_intermediate = tempfile.mkdtemp(prefix="video_gen_intermediate_")
379
  logger.info(f"Directorio temporal intermedio creado: {temp_dir_intermediate}")
380
  temp_intermediate_files = []
381
 
 
382
  logger.info("Generando audio de voz...")
383
  voz_path = os.path.join(temp_dir_intermediate, "voz.mp3")
 
 
384
  tts_success = text_to_speech(guion, voz_path)
385
 
386
  if not tts_success or not os.path.exists(voz_path) or os.path.getsize(voz_path) <= 1000:
@@ -388,6 +404,7 @@ def crear_video(prompt_type, input_text, musica_file=None):
388
  raise ValueError("Error generando voz a partir del guion (fallo de TTS).")
389
 
390
  temp_intermediate_files.append(voz_path)
 
391
  audio_tts_original = AudioFileClip(voz_path)
392
 
393
  if audio_tts_original.reader is None or audio_tts_original.duration is None or audio_tts_original.duration <= 0:
@@ -405,6 +422,7 @@ def crear_video(prompt_type, input_text, musica_file=None):
405
  logger.error(f"Duración audio voz ({audio_duration:.2f}s) es muy corta.")
406
  raise ValueError("Generated voice audio is too short (min 1 second required).")
407
 
 
408
  logger.info("Extrayendo palabras clave...")
409
  try:
410
  keywords = extract_visual_keywords_from_script(guion)
@@ -416,6 +434,7 @@ def crear_video(prompt_type, input_text, musica_file=None):
416
  if not keywords:
417
  keywords = ["video", "background"]
418
 
 
419
  logger.info("Buscando videos en Pexels...")
420
  videos_data = []
421
  total_desired_videos = 10
@@ -481,6 +500,7 @@ def crear_video(prompt_type, input_text, musica_file=None):
481
  logger.error("No se pudo descargar ningún archivo de video utilizable.")
482
  raise ValueError("No se pudo descargar ningún video utilizable de Pexels.")
483
 
 
484
  logger.info("Procesando y concatenando videos descargados...")
485
  current_duration = 0
486
  min_clip_duration = 0.5
@@ -547,7 +567,7 @@ def crear_video(prompt_type, input_text, musica_file=None):
547
  logger.info(f"Duración video base después de concatenación inicial: {concatenated_base.duration:.2f}s")
548
 
549
  if concatenated_base is None or concatenated_base.duration is None or concatenated_base.duration <= 0:
550
- logger.critical("Video base concatenado es inválido después de la primera concatenación (None or duración cero).")
551
  raise ValueError("Fallo al crear video base válido a partir de segmentos.")
552
 
553
  except Exception as e:
@@ -631,7 +651,7 @@ def crear_video(prompt_type, input_text, musica_file=None):
631
  raise ValueError("Fallo durante el recorte de video.")
632
 
633
  if final_video_base is None or final_video_base.duration is None or final_video_base.duration <= 0:
634
- logger.critical("Video base final es inválido antes de audio/escritura (None or duración cero).")
635
  raise ValueError("Video base final es inválido.")
636
 
637
  if final_video_base.size is None or final_video_base.size[0] <= 0 or final_video_base.size[1] <= 0:
@@ -640,8 +660,11 @@ def crear_video(prompt_type, input_text, musica_file=None):
640
 
641
  video_base = final_video_base
642
 
 
643
  logger.info("Procesando audio...")
 
644
  final_audio = audio_tts_original
 
645
  musica_audio_looped = None
646
 
647
  if musica_file:
@@ -671,19 +694,19 @@ def crear_video(prompt_type, input_text, musica_file=None):
671
 
672
  if musica_audio_looped:
673
  composite_audio = CompositeAudioClip([
674
- musica_audio_looped.volumex(0.2),
675
- audio_tts_original.volumex(1.0)
676
  ])
677
 
678
  if composite_audio.duration is None or composite_audio.duration <= 0:
679
- logger.warning("Clip de audio compuesto es inválido (None or duración cero). Usando solo audio de voz.")
680
  try: composite_audio.close()
681
  except: pass
682
  final_audio = audio_tts_original
683
  else:
684
  logger.info("Mezcla de audio completada (voz + música).")
685
  final_audio = composite_audio
686
- musica_audio = musica_audio_looped
687
 
688
  except Exception as e:
689
  logger.warning(f"Error procesando música de fondo: {str(e)}", exc_info=True)
@@ -709,26 +732,30 @@ def crear_video(prompt_type, input_text, musica_file=None):
709
  except Exception as e:
710
  logger.warning(f"Error ajustando duración del audio final: {str(e)}")
711
 
 
712
  logger.info("Renderizando video final...")
713
  video_final = video_base.set_audio(final_audio)
714
 
715
  if video_final is None or video_final.duration is None or video_final.duration <= 0:
716
- logger.critical("Clip de video final (con audio) es inválido antes de escribir (None or duración cero).")
717
  raise ValueError("Clip de video final es inválido antes de escribir.")
718
 
719
- output_filename = f"final_video_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4"
720
- persistent_output = os.path.join(os.getcwd(), output_filename)
721
- logger.info(f"Escribiendo video persistente en: {persistent_output}")
 
 
 
 
722
 
723
  try:
724
  video_final.write_videofile(
725
- filename=persistent_output,
726
  fps=24,
727
  threads=4,
728
  codec="libx264",
729
  audio_codec="aac",
730
  preset="medium",
731
- ffmpeg_params=['-movflags', 'frag_keyframe+empty_moov'],
732
  logger='bar'
733
  )
734
  except Exception as e:
@@ -736,9 +763,9 @@ def crear_video(prompt_type, input_text, musica_file=None):
736
  raise ValueError(f"Fallo al escribir el video final: {str(e)}")
737
 
738
  total_time = (datetime.now() - start_time).total_seconds()
739
- logger.info(f"PROCESO DE VIDEO FINALIZADO | Output: {persistent_output} | Tamaño: {os.path.getsize(persistent_output)} bytes | Tiempo total: {total_time:.2f}s")
740
 
741
- return persistent_output
742
 
743
  except ValueError as ve:
744
  logger.error(f"ERROR CONTROLADO en crear_video: {str(ve)}")
@@ -797,18 +824,19 @@ def crear_video(prompt_type, input_text, musica_file=None):
797
  logger.warning(f"Error cerrando video_base en finally: {str(e)}")
798
 
799
  if temp_dir_intermediate and os.path.exists(temp_dir_intermediate):
 
 
800
  for path in temp_intermediate_files:
801
  try:
802
- if os.path.isfile(path):
803
  logger.debug(f"Eliminando archivo temporal intermedio: {path}")
804
  os.remove(path)
 
 
805
  except Exception as e:
806
  logger.warning(f"No se pudo eliminar archivo temporal intermedio {path}: {str(e)}")
807
- try:
808
- os.rmdir(temp_dir_intermediate)
809
- logger.info(f"Directorio temporal eliminado: {temp_dir_intermediate}")
810
- except Exception as e:
811
- logger.warning(f"No se pudo eliminar directorio temporal {temp_dir_intermediate}: {str(e)}")
812
 
813
  def run_app(prompt_type, prompt_ia, prompt_manual, musica_file):
814
  logger.info("="*80)
@@ -856,11 +884,9 @@ def run_app(prompt_type, prompt_ia, prompt_manual, musica_file):
856
  logger.info("Fin del handler run_app.")
857
  return output_video, output_file, status_msg
858
 
859
- # AQUÍ COMIENZA LA PARTE DE LA INTERFAZ GRADIO
860
  with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="""
861
  .gradio-container {max-width: 800px; margin: auto;}
862
  h1 {text-align: center;}
863
- .progress-bar {height: 20px!important;}
864
  """) as app:
865
 
866
  gr.Markdown("# 🎬 Generador Automático de Videos con IA")
@@ -887,7 +913,7 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
887
  prompt_manual = gr.Textbox(
888
  label="Tu Guion Completo",
889
  lines=5,
890
- placeholder="Ej: En este video exploraremos los misterios del océano. Veremos la vida marina fascinante y los arrecifes de coral vibrantes. ¡Acompáñamos en esta aventura subacuática!",
891
  max_lines=10,
892
  value=""
893
  )
@@ -901,7 +927,6 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
901
 
902
  generate_btn = gr.Button("✨ Generar Video", variant="primary")
903
 
904
- # COLUMNA DE SALIDA
905
  with gr.Column():
906
  video_output = gr.Video(
907
  label="Previsualización del Video Generado",
@@ -920,15 +945,7 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
920
  placeholder="Esperando acción...",
921
  value="Esperando entrada..."
922
  )
923
- progress_bar = gr.Slider(
924
- visible=False,
925
- interactive=False,
926
- label="Progreso",
927
- minimum=0,
928
- maximum=100
929
- )
930
 
931
- # MANEJADORES DE EVENTOS
932
  prompt_type.change(
933
  lambda x: (gr.update(visible=x == "Generar Guion con IA"),
934
  gr.update(visible=x == "Usar Mi Guion")),
@@ -937,36 +954,23 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
937
  )
938
 
939
  generate_btn.click(
940
- lambda: (
941
- None,
942
- None,
943
- gr.update(value="⏳ Procesando... Esto puede tomar varios minutos.", interactive=False),
944
- gr.update(visible=True, value=0)
945
- ),
946
- outputs=[video_output, file_output, status_output, progress_bar],
947
  queue=True,
948
  ).then(
949
  run_app,
950
  inputs=[prompt_type, prompt_ia, prompt_manual, musica_input],
951
  outputs=[video_output, file_output, status_output]
952
- ).then(
953
- lambda: gr.update(visible=False),
954
- outputs=[progress_bar]
955
  )
956
 
957
- # INSTRUCCIONES
958
  gr.Markdown("### Instrucciones:")
959
  gr.Markdown("""
960
  1. **Clave API de Pexels:** Asegúrate de haber configurado la variable de entorno `PEXELS_API_KEY` con tu clave.
961
- 2. Para usar tu propio guion: Selecciona "Usar Mi Guion" y escribe tu texto completo
962
- 3. Para generar guion automático: Selecciona "Generar Guion con IA" y escribe un tema breve
963
- 4. Opcional: Sube un archivo de audio para música de fondo
964
- 5. Haz clic en "Generar Video" y espera el resultado (puede tardar varios minutos)
965
  """)
966
  gr.Markdown("---")
967
- gr.Markdown("Desarrollado con ❤️ usando Python, Gradio, MoviePy y modelos de IA")
968
 
969
- # BLOQUE PRINCIPAL DE EJECUCIÓN
970
  if __name__ == "__main__":
971
  logger.info("Verificando dependencias críticas...")
972
  try:
@@ -983,15 +987,7 @@ if __name__ == "__main__":
983
 
984
  logger.info("Iniciando aplicación Gradio...")
985
  try:
986
- # Versión simplificada para Gradio >=4.13.0
987
- app.queue(concurrency_count=1).launch(
988
- debug=True,
989
- share=True,
990
- server_name="0.0.0.0",
991
- server_port=7860,
992
- show_error=True,
993
- inline=False
994
- )
995
  except Exception as e:
996
  logger.critical(f"No se pudo iniciar la app: {str(e)}", exc_info=True)
997
  raise
 
15
  import json
16
  from collections import Counter
17
 
18
+ # Variable global para TTS
19
  tts_model = None
20
 
21
+ # Configuración de logging
22
  logging.basicConfig(
23
  level=logging.DEBUG,
24
  format='%(asctime)s - %(levelname)s - %(message)s',
 
32
  logger.info("INICIO DE EJECUCIÓN - GENERADOR DE VIDEOS")
33
  logger.info("="*80)
34
 
35
+ # Clave API de Pexels
36
  PEXELS_API_KEY = os.environ.get("PEXELS_API_KEY")
37
  if not PEXELS_API_KEY:
38
  logger.critical("NO SE ENCONTRÓ PEXELS_API_KEY EN VARIABLES DE ENTORNO")
39
 
40
+ # Inicialización de modelos
41
  MODEL_NAME = "datificate/gpt2-small-spanish"
42
  logger.info(f"Inicializando modelo GPT-2: {MODEL_NAME}")
43
  tokenizer = None
 
182
  return False
183
 
184
  try:
185
+ # Usar modelo específico para español, sin GPU
186
  tts = TTS(model_name="tts_models/es/css10/vits", progress_bar=False, gpu=False)
187
+
188
+ # Limpiar y truncar texto
189
  text = text.replace("na hora", "A la hora")
190
  text = re.sub(r'[^\w\s.,!?áéíóúñÁÉÍÓÚÑ]', '', text)
191
  if len(text) > 500:
192
  logger.warning("Texto demasiado largo, truncando a 500 caracteres")
193
  text = text[:500]
194
+
195
+ # Generar audio sin especificar idioma
196
  tts.tts_to_file(text=text, file_path=output_path)
197
+
198
+ # Verificar archivo generado
199
  if os.path.exists(output_path) and os.path.getsize(output_path) > 1000:
200
  logger.info(f"Audio creado: {output_path} | Tamaño: {os.path.getsize(output_path)} bytes")
201
  return True
 
373
  clips_to_concatenate = []
374
 
375
  try:
376
+ # 1. Generar o usar guion
377
  if prompt_type == "Generar Guion con IA":
378
  guion = generate_script(input_text)
379
  else:
 
385
  logger.error("El guion resultante está vacío o solo contiene espacios.")
386
  raise ValueError("El guion está vacío.")
387
 
388
+ # Corregir error tipográfico en el guion
389
  guion = guion.replace("na hora", "A la hora")
390
 
391
  temp_dir_intermediate = tempfile.mkdtemp(prefix="video_gen_intermediate_")
392
  logger.info(f"Directorio temporal intermedio creado: {temp_dir_intermediate}")
393
  temp_intermediate_files = []
394
 
395
+ # 2. Generar audio de voz
396
  logger.info("Generando audio de voz...")
397
  voz_path = os.path.join(temp_dir_intermediate, "voz.mp3")
398
+
399
+ # Llamar a text_to_speech directamente
400
  tts_success = text_to_speech(guion, voz_path)
401
 
402
  if not tts_success or not os.path.exists(voz_path) or os.path.getsize(voz_path) <= 1000:
 
404
  raise ValueError("Error generando voz a partir del guion (fallo de TTS).")
405
 
406
  temp_intermediate_files.append(voz_path)
407
+
408
  audio_tts_original = AudioFileClip(voz_path)
409
 
410
  if audio_tts_original.reader is None or audio_tts_original.duration is None or audio_tts_original.duration <= 0:
 
422
  logger.error(f"Duración audio voz ({audio_duration:.2f}s) es muy corta.")
423
  raise ValueError("Generated voice audio is too short (min 1 second required).")
424
 
425
+ # 3. Extraer palabras clave
426
  logger.info("Extrayendo palabras clave...")
427
  try:
428
  keywords = extract_visual_keywords_from_script(guion)
 
434
  if not keywords:
435
  keywords = ["video", "background"]
436
 
437
+ # 4. Buscar y descargar videos
438
  logger.info("Buscando videos en Pexels...")
439
  videos_data = []
440
  total_desired_videos = 10
 
500
  logger.error("No se pudo descargar ningún archivo de video utilizable.")
501
  raise ValueError("No se pudo descargar ningún video utilizable de Pexels.")
502
 
503
+ # 5. Procesar y concatenar clips de video
504
  logger.info("Procesando y concatenando videos descargados...")
505
  current_duration = 0
506
  min_clip_duration = 0.5
 
567
  logger.info(f"Duración video base después de concatenación inicial: {concatenated_base.duration:.2f}s")
568
 
569
  if concatenated_base is None or concatenated_base.duration is None or concatenated_base.duration <= 0:
570
+ logger.critical("Video base concatenado es inválido después de la primera concatenación (None o duración cero).")
571
  raise ValueError("Fallo al crear video base válido a partir de segmentos.")
572
 
573
  except Exception as e:
 
651
  raise ValueError("Fallo durante el recorte de video.")
652
 
653
  if final_video_base is None or final_video_base.duration is None or final_video_base.duration <= 0:
654
+ logger.critical("Video base final es inválido antes de audio/escritura (None o duración cero).")
655
  raise ValueError("Video base final es inválido.")
656
 
657
  if final_video_base.size is None or final_video_base.size[0] <= 0 or final_video_base.size[1] <= 0:
 
660
 
661
  video_base = final_video_base
662
 
663
+ # 6. Manejar música de fondo
664
  logger.info("Procesando audio...")
665
+
666
  final_audio = audio_tts_original
667
+
668
  musica_audio_looped = None
669
 
670
  if musica_file:
 
694
 
695
  if musica_audio_looped:
696
  composite_audio = CompositeAudioClip([
697
+ musica_audio_looped.volumex(0.2), # Volumen 20% para música
698
+ audio_tts_original.volumex(1.0) # Volumen 100% para voz
699
  ])
700
 
701
  if composite_audio.duration is None or composite_audio.duration <= 0:
702
+ logger.warning("Clip de audio compuesto es inválido (None o duración cero). Usando solo audio de voz.")
703
  try: composite_audio.close()
704
  except: pass
705
  final_audio = audio_tts_original
706
  else:
707
  logger.info("Mezcla de audio completada (voz + música).")
708
  final_audio = composite_audio
709
+ musica_audio = musica_audio_looped # Asignar para limpieza
710
 
711
  except Exception as e:
712
  logger.warning(f"Error procesando música de fondo: {str(e)}", exc_info=True)
 
732
  except Exception as e:
733
  logger.warning(f"Error ajustando duración del audio final: {str(e)}")
734
 
735
+ # 7. Crear video final
736
  logger.info("Renderizando video final...")
737
  video_final = video_base.set_audio(final_audio)
738
 
739
  if video_final is None or video_final.duration is None or video_final.duration <= 0:
740
+ logger.critical("Clip de video final (con audio) es inválido antes de escribir (None o duración cero).")
741
  raise ValueError("Clip de video final es inválido antes de escribir.")
742
 
743
+ output_filename = "final_video.mp4"
744
+ output_path = os.path.join(temp_dir_intermediate, output_filename)
745
+ logger.info(f"Escribiendo video final a: {output_path}")
746
+
747
+ if not output_path or not isinstance(output_path, str):
748
+ logger.critical(f"output_path no es válido: {output_path}")
749
+ raise ValueError("El nombre del archivo de salida no es válido.")
750
 
751
  try:
752
  video_final.write_videofile(
753
+ filename=output_path,
754
  fps=24,
755
  threads=4,
756
  codec="libx264",
757
  audio_codec="aac",
758
  preset="medium",
 
759
  logger='bar'
760
  )
761
  except Exception as e:
 
763
  raise ValueError(f"Fallo al escribir el video final: {str(e)}")
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:
771
  logger.error(f"ERROR CONTROLADO en crear_video: {str(ve)}")
 
824
  logger.warning(f"Error cerrando video_base en finally: {str(e)}")
825
 
826
  if temp_dir_intermediate and os.path.exists(temp_dir_intermediate):
827
+ final_output_in_temp = os.path.join(temp_dir_intermediate, "final_video.mp4")
828
+
829
  for path in temp_intermediate_files:
830
  try:
831
+ if os.path.isfile(path) and path != final_output_in_temp:
832
  logger.debug(f"Eliminando archivo temporal intermedio: {path}")
833
  os.remove(path)
834
+ elif os.path.isfile(path) and path == final_output_in_temp:
835
+ logger.debug(f"Saltando eliminación del archivo de video final: {path}")
836
  except Exception as e:
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)
 
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")
 
913
  prompt_manual = gr.Textbox(
914
  label="Tu Guion Completo",
915
  lines=5,
916
+ 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!",
917
  max_lines=10,
918
  value=""
919
  )
 
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")),
 
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],
 
 
 
 
 
959
  queue=True,
960
  ).then(
961
  run_app,
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]")
973
 
 
974
  if __name__ == "__main__":
975
  logger.info("Verificando dependencias críticas...")
976
  try:
 
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