gnosticdev commited on
Commit
b0e58a6
·
verified ·
1 Parent(s): ebafac2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +16 -66
app.py CHANGED
@@ -9,7 +9,6 @@ import torch
9
  from transformers import GPT2Tokenizer, GPT2LMHeadModel
10
  from keybert import KeyBERT
11
  from TTS.api import TTS
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
@@ -133,9 +132,9 @@ def generate_script(prompt, max_length=150):
133
 
134
  cleaned_text = text.strip()
135
  try:
136
- instruction_end_idx = text.find(instruction_phrase)
137
  if instruction_end_idx != -1:
138
- cleaned_text = text[instruction_end_idx + len(instruction_phrase):].strip()
139
  logger.debug("Instrucción inicial encontrada y eliminada del guión generado.")
140
  else:
141
  instruction_start_idx = text.find(instruction_phrase_start)
@@ -178,8 +177,6 @@ def generate_script(prompt, max_length=150):
178
  logger.warning("Usando prompt original como guion debido al error de generación.")
179
  return prompt.strip()
180
 
181
- from TTS.api import TTS
182
-
183
  def text_to_speech(text, output_path, voice=None):
184
  logger.info(f"Convirtiendo texto a voz con Coqui TTS | Caracteres: {len(text)} | Salida: {output_path}")
185
  if not text or not text.strip():
@@ -191,6 +188,7 @@ def text_to_speech(text, output_path, voice=None):
191
  tts = TTS(model_name="tts_models/es/css10/vits", progress_bar=False, gpu=False)
192
 
193
  # Limpiar y truncar texto
 
194
  text = re.sub(r'[^\w\s.,!?áéíóúñÁÉÍÓÚÑ]', '', text)
195
  if len(text) > 500:
196
  logger.warning("Texto demasiado largo, truncando a 500 caracteres")
@@ -210,8 +208,6 @@ def text_to_speech(text, output_path, voice=None):
210
  except Exception as e:
211
  logger.error(f"Error TTS: {str(e)}", exc_info=True)
212
  return False
213
-
214
- #FIN DE ESTA MIERDA
215
 
216
  def download_video_file(url, temp_dir):
217
  if not url:
@@ -305,7 +301,6 @@ def loop_audio_to_length(audio_clip, target_duration):
305
  try: looped_audio.close()
306
  except: pass
307
 
308
-
309
  def extract_visual_keywords_from_script(script_text):
310
  logger.info("Extrayendo palabras clave del guion")
311
  if not script_text or not script_text.strip():
@@ -392,40 +387,23 @@ def crear_video(prompt_type, input_text, musica_file=None):
392
  logger.error("El guion resultante está vacío o solo contiene espacios.")
393
  raise ValueError("El guion está vacío.")
394
 
 
 
 
395
  temp_dir_intermediate = tempfile.mkdtemp(prefix="video_gen_intermediate_")
396
  logger.info(f"Directorio temporal intermedio creado: {temp_dir_intermediate}")
397
  temp_intermediate_files = []
398
 
399
- # 2. Generar audio de voz con reintentos y voz de respaldo
400
  logger.info("Generando audio de voz...")
401
  voz_path = os.path.join(temp_dir_intermediate, "voz.mp3")
402
 
403
- primary_voice = "es-ES-JuanNeural"
404
- fallback_voice = "es-ES-ElviraNeural" # Otra voz en español
405
- tts_success = False
406
- retries = 3
407
-
408
- for attempt in range(retries):
409
- current_voice = primary_voice if attempt == 0 else fallback_voice
410
- if attempt > 0: logger.warning(f"Reintentando TTS ({attempt+1}/{retries})...")
411
- logger.info(f"Intentando TTS con voz: {current_voice}")
412
- try:
413
- tts_success = asyncio.run(text_to_speech(guion, voz_path, voice=current_voice))
414
- if tts_success:
415
- logger.info(f"TTS exitoso en intento {attempt + 1} con voz {current_voice}.")
416
- break
417
- except Exception as e:
418
- pass
419
-
420
- if not tts_success and attempt == 0 and primary_voice != fallback_voice:
421
- logger.warning(f"Fallo con voz {primary_voice}, intentando voz de respaldo: {fallback_voice}")
422
- elif not tts_success and attempt < retries - 1:
423
- logger.warning(f"Fallo con voz {current_voice}, reintentando...")
424
-
425
 
426
- if not tts_success or not os.path.exists(voz_path) or os.path.getsize(voz_path) <= 100:
427
- 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.")
428
- raise ValueError("Error generando voz a partir del guion (fallo de TTS).")
429
 
430
  temp_intermediate_files.append(voz_path)
431
 
@@ -655,7 +633,6 @@ def crear_video(prompt_type, input_text, musica_file=None):
655
  try: clip.close()
656
  except: pass
657
 
658
-
659
  if final_video_base.duration > audio_duration:
660
  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).")
661
  trimmed_video_base = None
@@ -675,7 +652,6 @@ def crear_video(prompt_type, input_text, musica_file=None):
675
  logger.critical(f"Error durante el recorte: {str(e)}", exc_info=True)
676
  raise ValueError("Fallo durante el recorte de video.")
677
 
678
-
679
  if final_video_base is None or final_video_base.duration is None or final_video_base.duration <= 0:
680
  logger.critical("Video base final es inválido antes de audio/escritura (None o duración cero).")
681
  raise ValueError("Video base final es inválido.")
@@ -718,7 +694,6 @@ def crear_video(prompt_type, input_text, musica_file=None):
718
  except: pass
719
  musica_audio_looped = None
720
 
721
-
722
  if musica_audio_looped:
723
  composite_audio = CompositeAudioClip([
724
  musica_audio_looped.volumex(0.2), # Volumen 20% para música
@@ -741,7 +716,6 @@ def crear_video(prompt_type, input_text, musica_file=None):
741
  musica_audio = None
742
  logger.warning("Usando solo audio de voz debido a un error con la música.")
743
 
744
-
745
  if final_audio.duration is not None and abs(final_audio.duration - video_base.duration) > 0.2:
746
  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.")
747
  try:
@@ -773,7 +747,6 @@ def crear_video(prompt_type, input_text, musica_file=None):
773
  logger.info(f"Escribiendo video final a: {output_path}")
774
 
775
  video_final.write_videofile(
776
- output_path,
777
  fps=24,
778
  threads=4,
779
  codec="libx264",
@@ -858,8 +831,6 @@ def crear_video(prompt_type, input_text, musica_file=None):
858
 
859
  logger.info(f"Directorio temporal intermedio {temp_dir_intermediate} persistirá para que Gradio lea el video final.")
860
 
861
-
862
- # La función run_app ahora recibe todos los inputs de texto y el archivo de música
863
  def run_app(prompt_type, prompt_ia, prompt_manual, musica_file):
864
  logger.info("="*80)
865
  logger.info("SOLICITUD RECIBIDA EN INTERFAZ")
@@ -873,7 +844,6 @@ def run_app(prompt_type, prompt_ia, prompt_manual, musica_file):
873
 
874
  if not input_text or not input_text.strip():
875
  logger.warning("Texto de entrada vacío.")
876
- # Retornar None para video y archivo, actualizar estado con mensaje de error
877
  return None, None, gr.update(value="⚠️ Por favor, ingresa texto para el guion o el tema.", interactive=False)
878
 
879
  logger.info(f"Tipo de entrada: {prompt_type}")
@@ -885,14 +855,13 @@ def run_app(prompt_type, prompt_ia, prompt_manual, musica_file):
885
 
886
  try:
887
  logger.info("Llamando a crear_video...")
888
- # Pasar el input_text elegido y el archivo de música a crear_video
889
  video_path = crear_video(prompt_type, input_text, musica_file)
890
 
891
  if video_path and os.path.exists(video_path):
892
  logger.info(f"crear_video retornó path: {video_path}")
893
  logger.info(f"Tamaño del archivo de video retornado: {os.path.getsize(video_path)} bytes")
894
- output_video = video_path # Establecer valor del componente de video
895
- output_file = video_path # Establecer valor del componente de archivo para descarga
896
  status_msg = gr.update(value="✅ Video generado exitosamente.", interactive=False)
897
  else:
898
  logger.error(f"crear_video no retornó un path válido o el archivo no existe: {video_path}")
@@ -906,10 +875,8 @@ def run_app(prompt_type, prompt_ia, prompt_manual, musica_file):
906
  status_msg = gr.update(value=f"❌ Error inesperado: {str(e)}", interactive=False)
907
  finally:
908
  logger.info("Fin del handler run_app.")
909
- # Retornar las tres salidas esperadas por el evento click
910
  return output_video, output_file, status_msg
911
 
912
-
913
  # Interfaz de Gradio
914
  with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="""
915
  .gradio-container {max-width: 800px; margin: auto;}
@@ -927,8 +894,6 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
927
  value="Generar Guion con IA"
928
  )
929
 
930
- # Contenedores para los campos de texto para controlar la visibilidad
931
- # Nombrados para que coincidan con los outputs del evento change
932
  with gr.Column(visible=True) as ia_guion_column:
933
  prompt_ia = gr.Textbox(
934
  label="Tema para IA",
@@ -965,7 +930,7 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
965
  file_output = gr.File(
966
  label="Descargar Archivo de Video",
967
  interactive=False,
968
- visible=False # Ocultar inicialmente
969
  )
970
  status_output = gr.Textbox(
971
  label="Estado",
@@ -975,42 +940,27 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
975
  value="Esperando entrada..."
976
  )
977
 
978
- # Evento para mostrar/ocultar los campos de texto según el tipo de prompt
979
- # Apuntar a los componentes Column padre para controlar la visibilidad
980
  prompt_type.change(
981
  lambda x: (gr.update(visible=x == "Generar Guion con IA"),
982
  gr.update(visible=x == "Usar Mi Guion")),
983
  inputs=prompt_type,
984
- # Pasar los componentes Column
985
  outputs=[ia_guion_column, manual_guion_column]
986
  )
987
 
988
- # Evento click del botón de generar video
989
  generate_btn.click(
990
- # Acción 1 (síncrona): Resetear salidas y establecer estado a procesando
991
- # Retorna None para los 3 outputs iniciales
992
  lambda: (None, None, gr.update(value="⏳ Procesando... Esto puede tomar varios minutos.", interactive=False)),
993
  outputs=[video_output, file_output, status_output],
994
- queue=True, # Usar la cola de Gradio para tareas largas
995
  ).then(
996
- # Acción 2 (asíncrona): Llamar a la función principal de procesamiento
997
  run_app,
998
- # PASAR TODOS LOS INPUTS DE LA INTERFAZ que run_app espera
999
  inputs=[prompt_type, prompt_ia, prompt_manual, musica_input],
1000
- # run_app retornará los 3 outputs esperados aquí
1001
  outputs=[video_output, file_output, status_output]
1002
  ).then(
1003
- # Acción 3 (síncrona): Hacer visible el enlace de descarga si se retornó un archivo
1004
- # Esta función recibe las salidas de la Acción 2 (video_path, file_path, status_msg)
1005
- # Solo necesitamos video_path o file_path para decidir si mostrar el enlace
1006
  lambda video_path, file_path, status_msg: gr.update(visible=file_path is not None),
1007
- # Inputs son las salidas de la función .then() anterior
1008
  inputs=[video_output, file_output, status_output],
1009
- # Actualizamos la visibilidad del componente file_output
1010
  outputs=[file_output]
1011
  )
1012
 
1013
-
1014
  gr.Markdown("### Instrucciones:")
1015
  gr.Markdown("""
1016
  1. **Clave API de Pexels:** Asegúrate de haber configurado la variable de entorno `PEXELS_API_KEY` con tu clave.
 
9
  from transformers import GPT2Tokenizer, GPT2LMHeadModel
10
  from keybert import KeyBERT
11
  from TTS.api import TTS
 
12
  from moviepy.editor import VideoFileClip, concatenate_videoclips, AudioFileClip, CompositeAudioClip, concatenate_audioclips, AudioClip
13
  import re
14
  import math
 
132
 
133
  cleaned_text = text.strip()
134
  try:
135
+ instruction_end_idx = text.find(instruction_phrase_start)
136
  if instruction_end_idx != -1:
137
+ cleaned_text = text[instruction_end_idx + len(instruction_phrase_start):].strip()
138
  logger.debug("Instrucción inicial encontrada y eliminada del guión generado.")
139
  else:
140
  instruction_start_idx = text.find(instruction_phrase_start)
 
177
  logger.warning("Usando prompt original como guion debido al error de generación.")
178
  return prompt.strip()
179
 
 
 
180
  def text_to_speech(text, output_path, voice=None):
181
  logger.info(f"Convirtiendo texto a voz con Coqui TTS | Caracteres: {len(text)} | Salida: {output_path}")
182
  if not text or not text.strip():
 
188
  tts = TTS(model_name="tts_models/es/css10/vits", progress_bar=False, gpu=False)
189
 
190
  # Limpiar y truncar texto
191
+ text = text.replace("na hora", "A la hora")
192
  text = re.sub(r'[^\w\s.,!?áéíóúñÁÉÍÓÚÑ]', '', text)
193
  if len(text) > 500:
194
  logger.warning("Texto demasiado largo, truncando a 500 caracteres")
 
208
  except Exception as e:
209
  logger.error(f"Error TTS: {str(e)}", exc_info=True)
210
  return False
 
 
211
 
212
  def download_video_file(url, temp_dir):
213
  if not url:
 
301
  try: looped_audio.close()
302
  except: pass
303
 
 
304
  def extract_visual_keywords_from_script(script_text):
305
  logger.info("Extrayendo palabras clave del guion")
306
  if not script_text or not script_text.strip():
 
387
  logger.error("El guion resultante está vacío o solo contiene espacios.")
388
  raise ValueError("El guion está vacío.")
389
 
390
+ # Corregir error tipográfico en el guion
391
+ guion = guion.replace("na hora", "A la hora")
392
+
393
  temp_dir_intermediate = tempfile.mkdtemp(prefix="video_gen_intermediate_")
394
  logger.info(f"Directorio temporal intermedio creado: {temp_dir_intermediate}")
395
  temp_intermediate_files = []
396
 
397
+ # 2. Generar audio de voz
398
  logger.info("Generando audio de voz...")
399
  voz_path = os.path.join(temp_dir_intermediate, "voz.mp3")
400
 
401
+ # Llamar a text_to_speech directamente
402
+ tts_success = text_to_speech(guion, voz_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
403
 
404
+ if not tts_success or not os.path.exists(voz_path) or os.path.getsize(voz_path) <= 1000:
405
+ logger.error(f"Fallo en la generación de voz. Archivo de audio no creado o es muy pequeño: {voz_path}")
406
+ raise ValueError("Error generando voz a partir del guion (fallo de TTS).")
407
 
408
  temp_intermediate_files.append(voz_path)
409
 
 
633
  try: clip.close()
634
  except: pass
635
 
 
636
  if final_video_base.duration > audio_duration:
637
  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).")
638
  trimmed_video_base = None
 
652
  logger.critical(f"Error durante el recorte: {str(e)}", exc_info=True)
653
  raise ValueError("Fallo durante el recorte de video.")
654
 
 
655
  if final_video_base is None or final_video_base.duration is None or final_video_base.duration <= 0:
656
  logger.critical("Video base final es inválido antes de audio/escritura (None o duración cero).")
657
  raise ValueError("Video base final es inválido.")
 
694
  except: pass
695
  musica_audio_looped = None
696
 
 
697
  if musica_audio_looped:
698
  composite_audio = CompositeAudioClip([
699
  musica_audio_looped.volumex(0.2), # Volumen 20% para música
 
716
  musica_audio = None
717
  logger.warning("Usando solo audio de voz debido a un error con la música.")
718
 
 
719
  if final_audio.duration is not None and abs(final_audio.duration - video_base.duration) > 0.2:
720
  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.")
721
  try:
 
747
  logger.info(f"Escribiendo video final a: {output_path}")
748
 
749
  video_final.write_videofile(
 
750
  fps=24,
751
  threads=4,
752
  codec="libx264",
 
831
 
832
  logger.info(f"Directorio temporal intermedio {temp_dir_intermediate} persistirá para que Gradio lea el video final.")
833
 
 
 
834
  def run_app(prompt_type, prompt_ia, prompt_manual, musica_file):
835
  logger.info("="*80)
836
  logger.info("SOLICITUD RECIBIDA EN INTERFAZ")
 
844
 
845
  if not input_text or not input_text.strip():
846
  logger.warning("Texto de entrada vacío.")
 
847
  return None, None, gr.update(value="⚠️ Por favor, ingresa texto para el guion o el tema.", interactive=False)
848
 
849
  logger.info(f"Tipo de entrada: {prompt_type}")
 
855
 
856
  try:
857
  logger.info("Llamando a crear_video...")
 
858
  video_path = crear_video(prompt_type, input_text, musica_file)
859
 
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 = video_path
865
  status_msg = gr.update(value="✅ Video generado exitosamente.", interactive=False)
866
  else:
867
  logger.error(f"crear_video no retornó un path válido o el archivo no existe: {video_path}")
 
875
  status_msg = gr.update(value=f"❌ Error inesperado: {str(e)}", interactive=False)
876
  finally:
877
  logger.info("Fin del handler run_app.")
 
878
  return output_video, output_file, status_msg
879
 
 
880
  # Interfaz de Gradio
881
  with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="""
882
  .gradio-container {max-width: 800px; margin: auto;}
 
894
  value="Generar Guion con IA"
895
  )
896
 
 
 
897
  with gr.Column(visible=True) as ia_guion_column:
898
  prompt_ia = gr.Textbox(
899
  label="Tema para IA",
 
930
  file_output = gr.File(
931
  label="Descargar Archivo de Video",
932
  interactive=False,
933
+ visible=False
934
  )
935
  status_output = gr.Textbox(
936
  label="Estado",
 
940
  value="Esperando entrada..."
941
  )
942
 
 
 
943
  prompt_type.change(
944
  lambda x: (gr.update(visible=x == "Generar Guion con IA"),
945
  gr.update(visible=x == "Usar Mi Guion")),
946
  inputs=prompt_type,
 
947
  outputs=[ia_guion_column, manual_guion_column]
948
  )
949
 
 
950
  generate_btn.click(
 
 
951
  lambda: (None, None, gr.update(value="⏳ Procesando... Esto puede tomar varios minutos.", interactive=False)),
952
  outputs=[video_output, file_output, status_output],
953
+ queue=True,
954
  ).then(
 
955
  run_app,
 
956
  inputs=[prompt_type, prompt_ia, prompt_manual, musica_input],
 
957
  outputs=[video_output, file_output, status_output]
958
  ).then(
 
 
 
959
  lambda video_path, file_path, status_msg: gr.update(visible=file_path is not None),
 
960
  inputs=[video_output, file_output, status_output],
 
961
  outputs=[file_output]
962
  )
963
 
 
964
  gr.Markdown("### Instrucciones:")
965
  gr.Markdown("""
966
  1. **Clave API de Pexels:** Asegúrate de haber configurado la variable de entorno `PEXELS_API_KEY` con tu clave.