gnosticdev commited on
Commit
0e14a87
·
verified ·
1 Parent(s): e444709

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +105 -81
app.py CHANGED
@@ -9,7 +9,7 @@ import gradio as gr
9
  import torch
10
  from transformers import GPT2Tokenizer, GPT2LMHeadModel
11
  from keybert import KeyBERT
12
- # Importación correcta
13
  from moviepy.editor import VideoFileClip, concatenate_videoclips, AudioFileClip, CompositeAudioClip, concatenate_audioclips, AudioClip
14
  import re
15
  import math
@@ -35,7 +35,7 @@ logger.info("="*80)
35
  PEXELS_API_KEY = os.environ.get("PEXELS_API_KEY")
36
  if not PEXELS_API_KEY:
37
  logger.critical("NO SE ENCONTRÓ PEXELS_API_KEY EN VARIABLES DE ENTORNO")
38
- # raise ValueError("API key de Pexels no configurada")
39
 
40
  # Inicialización de modelos
41
  MODEL_NAME = "datificate/gpt2-small-spanish"
@@ -66,14 +66,16 @@ async def get_available_voices():
66
  logger.info("Obteniendo lista de voces disponibles de Edge TTS...")
67
  try:
68
  voices = await edge_tts.VoicesManager.create()
69
- # Filtrar solo voces en español si prefieres, o dejar todas
70
- # es_voices = [voice.Name for voice in voices.Voices if voice.Locale.startswith('es-')]
71
- # return es_voices if es_voices else [voice.Name for voice in voices.Voices]
72
-
73
- # O simplemente retornar todas las voces
74
- all_voices = [voice.Name for voice in voices.Voices]
75
- logger.info(f"Encontradas {len(all_voices)} voces de Edge TTS.")
76
- return all_voices
 
 
77
 
78
  except Exception as e:
79
  logger.error(f"Error obteniendo voces de Edge TTS: {str(e)}", exc_info=True)
@@ -157,46 +159,56 @@ def generate_script(prompt, max_length=150):
157
  text = tokenizer.decode(outputs[0], skip_special_tokens=True)
158
 
159
  cleaned_text = text.strip()
 
160
  try:
161
- instruction_end_idx = text.find(instruction_phrase)
162
- if instruction_end_idx != -1:
163
- cleaned_text = text[instruction_end_idx + len(instruction_phrase):].strip()
164
- logger.debug("Instrucción inicial encontrada y eliminada del guión generado.")
 
 
165
  else:
 
166
  instruction_start_idx = text.find(instruction_phrase_start)
167
  if instruction_start_idx != -1:
168
- prompt_in_output_idx = text.find(prompt, instruction_start_idx)
169
- if prompt_in_output_idx != -1:
170
- cleaned_text = text[prompt_in_output_idx + len(prompt):].strip()
171
- logger.debug("Instrucción base y prompt encontrados y eliminados del guión generado.")
172
- else:
173
- cleaned_text = text[instruction_start_idx + len(instruction_phrase_start):].strip()
174
- logger.debug("Instrucción base encontrada, eliminada del guión generado (sin prompt detectado).")
 
175
 
176
  except Exception as e:
177
  logger.warning(f"Error durante la limpieza heurística del guión de IA: {e}. Usando texto generado sin limpieza adicional.")
178
- cleaned_text = re.sub(r'<[^>]+>', '', text).strip()
179
 
180
- if not cleaned_text or len(cleaned_text) < 10:
181
- logger.warning("El guión generado parece muy corto o vacío después de la limpieza. Usando el texto generado original (sin limpieza heurística).")
182
- cleaned_text = re.sub(r'<[^>]+>', '', text).strip()
 
183
 
 
184
  cleaned_text = re.sub(r'<[^>]+>', '', cleaned_text).strip()
185
- cleaned_text = cleaned_text.lstrip(':').strip()
186
- cleaned_text = cleaned_text.lstrip('.').strip()
187
 
 
 
188
  sentences = cleaned_text.split('.')
189
  if sentences and sentences[0].strip():
190
  final_text = sentences[0].strip() + '.'
191
- if len(sentences) > 1 and sentences[1].strip() and len(final_text.split()) < max_length * 0.7:
 
192
  final_text += " " + sentences[1].strip() + "."
193
- final_text = final_text.replace("..", ".")
194
 
195
  logger.info(f"Guion generado final (Truncado a 100 chars): '{final_text[:100]}...'")
196
  return final_text.strip()
197
 
198
  logger.info(f"Guion generado final (sin oraciones completas detectadas - Truncado): '{cleaned_text[:100]}...'")
199
- return cleaned_text.strip()
200
 
201
  except Exception as e:
202
  logger.error(f"Error generando guion con GPT-2 (fuera del bloque de limpieza): {str(e)}", exc_info=True)
@@ -374,11 +386,12 @@ def extract_visual_keywords_from_script(script_text):
374
  logger.info(f"Palabras clave finales: {top_keywords}")
375
  return top_keywords
376
 
377
- def crear_video(prompt_type, input_text, selected_voice, musica_file=None): # <-- AHORA RECIBE selected_voice
 
378
  logger.info("="*80)
379
  logger.info(f"INICIANDO CREACIÓN DE VIDEO | Tipo: {prompt_type}")
380
  logger.debug(f"Input: '{input_text[:100]}...'")
381
- logger.info(f"Voz seleccionada para TTS: {selected_voice}") # <-- LOGUEAR la voz seleccionada
382
 
383
  start_time = datetime.now()
384
  temp_dir_intermediate = None
@@ -409,31 +422,37 @@ def crear_video(prompt_type, input_text, selected_voice, musica_file=None): # <-
409
  logger.info(f"Directorio temporal intermedio creado: {temp_dir_intermediate}")
410
  temp_intermediate_files = []
411
 
412
- # 2. Generar audio de voz usando la voz seleccionada
413
  logger.info("Generando audio de voz...")
414
  voz_path = os.path.join(temp_dir_intermediate, "voz.mp3")
415
 
416
- # Ya no necesitamos reintentos/fallback aquí, la voz viene seleccionada
417
- tts_success = asyncio.run(text_to_speech(guion, voz_path, voice=selected_voice))
418
-
419
- # Si falla la generación con la voz seleccionada, intentar con una voz de respaldo
420
- if not tts_success:
421
- logger.warning(f"La generación de TTS falló con la voz seleccionada '{selected_voice}'. Intentando con voz de respaldo 'es-ES-ElviraNeural'.")
422
- fallback_voice = "es-ES-ElviraNeural"
423
- if selected_voice == fallback_voice: # Evitar reintentar con la misma voz fallida
424
- fallback_voice = "en-US-AriaNeural" # O alguna otra conocida
425
- logger.warning(f"La voz de respaldo era la misma que falló. Intentando otra voz de respaldo: {fallback_voice}")
426
-
427
- tts_success = asyncio.run(text_to_speech(guion, voz_path, voice=fallback_voice))
428
- if tts_success:
429
- logger.info(f"TTS exitoso con voz de respaldo: {fallback_voice}.")
430
- else:
431
- logger.error(f"La generación de TTS falló también con la voz de respaldo.")
432
-
 
 
 
 
 
 
433
 
434
- # Verificar si el archivo fue creado después de los intentos
435
  if not tts_success or not os.path.exists(voz_path) or os.path.getsize(voz_path) <= 100:
436
- logger.error(f"Fallo en la generación de voz. Archivo de audio no creado o es muy pequeño.")
437
  raise ValueError("Error generando voz a partir del guion (fallo de TTS).")
438
 
439
  temp_intermediate_files.append(voz_path)
@@ -869,10 +888,11 @@ def crear_video(prompt_type, input_text, selected_voice, musica_file=None): # <-
869
 
870
 
871
  # run_app ahora recibe todos los inputs, incluyendo la voz seleccionada
872
- def run_app(prompt_type, prompt_ia, prompt_manual, musica_file, selected_voice): # <-- AHORA RECIBE selected_voice
873
  logger.info("="*80)
874
  logger.info("SOLICITUD RECIBIDA EN INTERFAZ")
875
 
 
876
  input_text = prompt_ia if prompt_type == "Generar Guion con IA" else prompt_manual
877
 
878
  output_video = None
@@ -883,9 +903,14 @@ def run_app(prompt_type, prompt_ia, prompt_manual, musica_file, selected_voice):
883
  logger.warning("Texto de entrada vacío.")
884
  return None, None, gr.update(value="⚠️ Por favor, ingresa texto para el guion o el tema.", interactive=False)
885
 
886
- if not selected_voice or selected_voice not in AVAILABLE_VOICES:
887
- logger.warning(f"Voz seleccionada inválida o vacía: '{selected_voice}'. Usando voz por defecto: {DEFAULT_VOICE}.")
888
- selected_voice = DEFAULT_VOICE # Usar voz por defecto si la seleccionada es inválida
 
 
 
 
 
889
 
890
  logger.info(f"Tipo de entrada: {prompt_type}")
891
  logger.debug(f"Texto de entrada: '{input_text[:100]}...'")
@@ -893,18 +918,18 @@ def run_app(prompt_type, prompt_ia, prompt_manual, musica_file, selected_voice):
893
  logger.info(f"Archivo de música recibido: {musica_file}")
894
  else:
895
  logger.info("No se proporcionó archivo de música.")
896
- logger.info(f"Voz seleccionada (validada): {selected_voice}") # Loguear la voz validada
897
 
898
  try:
899
  logger.info("Llamando a crear_video...")
900
- # Pasar la voz seleccionada a crear_video
901
- video_path = crear_video(prompt_type, input_text, selected_voice, musica_file) # <-- PASAR selected_voice
902
 
903
  if video_path and os.path.exists(video_path):
904
  logger.info(f"crear_video retornó path: {video_path}")
905
  logger.info(f"Tamaño del archivo de video retornado: {os.path.getsize(video_path)} bytes")
906
- output_video = video_path
907
- output_file = video_path
908
  status_msg = gr.update(value="✅ Video generado exitosamente.", interactive=False)
909
  else:
910
  logger.error(f"crear_video no retornó un path válido o el archivo no existe: {video_path}")
@@ -943,7 +968,7 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
943
  prompt_ia = gr.Textbox(
944
  label="Tema para IA",
945
  lines=2,
946
- placeholder="Ej: Un paisaje natural con montañas y ríos al amanecer...",
947
  max_lines=4,
948
  value=""
949
  )
@@ -963,15 +988,15 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
963
  interactive=True,
964
  value=None
965
  )
966
-
967
- # --- NUEVO COMPONENTE: Selección de Voz ---
968
  voice_dropdown = gr.Dropdown(
969
  label="Seleccionar Voz para Guion",
970
  choices=AVAILABLE_VOICES, # Usar la lista obtenida al inicio
971
  value=DEFAULT_VOICE, # Usar la voz por defecto calculada
972
  interactive=True
973
  )
974
- # --- FIN NUEVO COMPONENTE ---
975
 
976
 
977
  generate_btn = gr.Button("✨ Generar Video", variant="primary")
@@ -985,7 +1010,7 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
985
  file_output = gr.File(
986
  label="Descargar Archivo de Video",
987
  interactive=False,
988
- visible=False
989
  )
990
  status_output = gr.Textbox(
991
  label="Estado",
@@ -1000,7 +1025,7 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
1000
  lambda x: (gr.update(visible=x == "Generar Guion con IA"),
1001
  gr.update(visible=x == "Usar Mi Guion")),
1002
  inputs=prompt_type,
1003
- outputs=[ia_guion_column, manual_guion_column]
1004
  )
1005
 
1006
  # Evento click del botón de generar video
@@ -1008,16 +1033,17 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
1008
  # Acción 1 (síncrona): Resetear salidas y establecer estado
1009
  lambda: (None, None, gr.update(value="⏳ Procesando... Esto puede tomar varios minutos.", interactive=False)),
1010
  outputs=[video_output, file_output, status_output],
1011
- queue=True,
1012
  ).then(
1013
- # Acción 2 (asíncrona): Llamar a la función principal de procesamiento
1014
  run_app,
1015
- # PASAR TODOS LOS INPUTS DE LA INTERFAZ a run_app
1016
- inputs=[prompt_type, prompt_ia, prompt_manual, musica_input, voice_dropdown], # <-- AHORA PASAMOS voice_dropdown
1017
  # run_app retornará los 3 outputs esperados
1018
  outputs=[video_output, file_output, status_output]
1019
  ).then(
1020
- # Acción 3 (síncrona): Hacer visible el enlace de descarga si se retornó un archivo
 
1021
  lambda video_path, file_path, status_msg: gr.update(visible=file_path is not None),
1022
  inputs=[video_output, file_output, status_output],
1023
  outputs=[file_output]
@@ -1027,15 +1053,13 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
1027
  gr.Markdown("### Instrucciones:")
1028
  gr.Markdown("""
1029
  1. **Clave API de Pexels:** Asegúrate de haber configurado la variable de entorno `PEXELS_API_KEY` con tu clave.
1030
- 2. **Selecciona el tipo de entrada**:
1031
- - "Generar Guion con IA": Describe brevemente un tema (ej. "La belleza de las montañas"). La IA generará un guion corto.
1032
- - "Usar Mi Guion": Escribe el guion completo que quieres para el video.
1033
- 3. **Sube música** (opcional): Selecciona un archivo de audio (MP3, WAV, etc.) para usar como música de fondo.
1034
- 4. **Selecciona la voz** para el guion.
1035
  5. **Haz clic en "✨ Generar Video"**.
1036
- 6. Espera a que se procese el video. El tiempo de espera puede variar. Verás el estado en el cuadro de texto.
1037
- 7. La previsualización del video aparecerá arriba (puede fallar para archivos grandes), y un enlace **Descargar Archivo de Video** se mostrará si la generación fue exitosa.
1038
- 8. Si hay errores, revisa el log `video_generator_full.log` para más detalles.
1039
  """)
1040
  gr.Markdown("---")
1041
  gr.Markdown("Desarrollado por [Tu Nombre/Empresa/Alias - Opcional]")
@@ -1047,7 +1071,7 @@ if __name__ == "__main__":
1047
  try:
1048
  temp_clip = ColorClip((100,100), color=(255,0,0), duration=0.1)
1049
  temp_clip.close()
1050
- logger.info("Clips base de MoviePy (como ColorClip) creados y cerrados exitosamente. FFmpeg parece accesible.")
1051
  except Exception as e:
1052
  logger.critical(f"Fallo al crear clip base de MoviePy. A menudo indica problemas con FFmpeg/ImageMagick. Error: {e}", exc_info=True)
1053
 
 
9
  import torch
10
  from transformers import GPT2Tokenizer, GPT2LMHeadModel
11
  from keybert import KeyBERT
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
 
35
  PEXELS_API_KEY = os.environ.get("PEXELS_API_KEY")
36
  if not PEXELS_API_KEY:
37
  logger.critical("NO SE ENCONTRÓ PEXELS_API_KEY EN VARIABLES DE ENTORNO")
38
+ # raise ValueError("API key de Pexels no configurada") # Descomentar para forzar fallo si no está
39
 
40
  # Inicialización de modelos
41
  MODEL_NAME = "datificate/gpt2-small-spanish"
 
66
  logger.info("Obteniendo lista de voces disponibles de Edge TTS...")
67
  try:
68
  voices = await edge_tts.VoicesManager.create()
69
+ # Retornar solo voces en español si prefieres
70
+ es_voices = [voice.Name for voice in voices.Voices if voice.Locale.startswith('es-')]
71
+ if es_voices:
72
+ logger.info(f"Encontradas {len(es_voices)} voces en español.")
73
+ return es_voices
74
+ else:
75
+ # Si no hay español, retornar todas las voces
76
+ all_voices = [voice.Name for voice in voices.Voices]
77
+ logger.warning(f"No se encontraron voces en español. Retornando {len(all_voices)} voces en todos los idiomas.")
78
+ return all_voices if all_voices else ["en-US-AriaNeural"] # Fallback si no hay ninguna
79
 
80
  except Exception as e:
81
  logger.error(f"Error obteniendo voces de Edge TTS: {str(e)}", exc_info=True)
 
159
  text = tokenizer.decode(outputs[0], skip_special_tokens=True)
160
 
161
  cleaned_text = text.strip()
162
+ # Limpieza mejorada de la frase de instrucción
163
  try:
164
+ # Buscar el índice de inicio del prompt original dentro del texto generado
165
+ prompt_in_output_idx = text.lower().find(prompt.lower())
166
+ if prompt_in_output_idx != -1:
167
+ # Tomar todo el texto DESPUÉS del prompt original
168
+ cleaned_text = text[prompt_in_output_idx + len(prompt):].strip()
169
+ logger.debug("Texto limpiado tomando parte después del prompt original.")
170
  else:
171
+ # Fallback si el prompt original no está exacto en la salida: buscar la frase de instrucción base
172
  instruction_start_idx = text.find(instruction_phrase_start)
173
  if instruction_start_idx != -1:
174
+ # Tomar texto después de la frase base (puede incluir el prompt)
175
+ cleaned_text = text[instruction_start_idx + len(instruction_phrase_start):].strip()
176
+ logger.debug("Texto limpiado tomando parte después de la frase de instrucción base.")
177
+ else:
178
+ # Si ni la frase de instrucción ni el prompt se encuentran, usar el texto original
179
+ logger.warning("No se pudo identificar el inicio del guión generado. Usando texto generado completo.")
180
+ cleaned_text = text.strip() # Limpieza básica
181
+
182
 
183
  except Exception as e:
184
  logger.warning(f"Error durante la limpieza heurística del guión de IA: {e}. Usando texto generado sin limpieza adicional.")
185
+ cleaned_text = re.sub(r'<[^>]+>', '', text).strip() # Limpieza básica como fallback
186
 
187
+ # Asegurarse de que el texto resultante no sea solo la instrucción o vacío
188
+ if not cleaned_text or len(cleaned_text) < 10: # Umbral de longitud mínima
189
+ logger.warning("El guión generado parece muy corto o vacío después de la limpieza heurística. Usando el texto generado original (sin limpieza adicional).")
190
+ cleaned_text = re.sub(r'<[^>]+>', '', text).strip() # Fallback al texto original limpio
191
 
192
+ # Limpieza final de caracteres especiales y espacios sobrantes
193
  cleaned_text = re.sub(r'<[^>]+>', '', cleaned_text).strip()
194
+ cleaned_text = cleaned_text.lstrip(':').strip() # Quitar posibles ':' al inicio
195
+ cleaned_text = cleaned_text.lstrip('.').strip() # Quitar posibles '.' al inicio
196
 
197
+
198
+ # Intentar obtener al menos una oración completa si es posible para un inicio más limpio
199
  sentences = cleaned_text.split('.')
200
  if sentences and sentences[0].strip():
201
  final_text = sentences[0].strip() + '.'
202
+ # Añadir la segunda oración si existe y es razonable
203
+ if len(sentences) > 1 and sentences[1].strip() and len(final_text.split()) < max_length * 0.7: # Usar un 70% de max_length como umbral
204
  final_text += " " + sentences[1].strip() + "."
205
+ final_text = final_text.replace("..", ".") # Limpiar doble punto
206
 
207
  logger.info(f"Guion generado final (Truncado a 100 chars): '{final_text[:100]}...'")
208
  return final_text.strip()
209
 
210
  logger.info(f"Guion generado final (sin oraciones completas detectadas - Truncado): '{cleaned_text[:100]}...'")
211
+ return cleaned_text.strip() # Si no se puede formar una oración, devolver el texto limpio tal cual
212
 
213
  except Exception as e:
214
  logger.error(f"Error generando guion con GPT-2 (fuera del bloque de limpieza): {str(e)}", exc_info=True)
 
386
  logger.info(f"Palabras clave finales: {top_keywords}")
387
  return top_keywords
388
 
389
+ # crear_video ahora recibe la voz seleccionada
390
+ def crear_video(prompt_type, input_text, selected_voice, musica_file=None):
391
  logger.info("="*80)
392
  logger.info(f"INICIANDO CREACIÓN DE VIDEO | Tipo: {prompt_type}")
393
  logger.debug(f"Input: '{input_text[:100]}...'")
394
+ logger.info(f"Voz seleccionada para TTS: {selected_voice}")
395
 
396
  start_time = datetime.now()
397
  temp_dir_intermediate = None
 
422
  logger.info(f"Directorio temporal intermedio creado: {temp_dir_intermediate}")
423
  temp_intermediate_files = []
424
 
425
+ # 2. Generar audio de voz usando la voz seleccionada, con reintentos si falla
426
  logger.info("Generando audio de voz...")
427
  voz_path = os.path.join(temp_dir_intermediate, "voz.mp3")
428
 
429
+ tts_voices_to_try = [selected_voice] # Intentar primero la voz seleccionada
430
+ # Añadir voces de respaldo si son diferentes a la seleccionada
431
+ if "es-ES-JuanNeural" not in tts_voices_to_try: tts_voices_to_try.append("es-ES-JuanNeural")
432
+ if "es-ES-ElviraNeural" not in tts_voices_to_try: tts_voices_to_try.append("es-ES-ElviraNeural")
433
+ # Si la lista de voces disponibles es conocida y contiene otras voces españolas, añadirlas también
434
+ # Opcional: si AVAILABLE_VOICES es fiable, podrías usar un subconjunto ordenado para reintentos
435
+ # Ejemplo: for voice_id in [selected_voice] + [v for v in AVAILABLE_VOICES if v != selected_voice][:2]:
436
+
437
+ tts_success = False
438
+ tried_voices = []
439
+
440
+ for current_voice in tts_voices_to_try:
441
+ if current_voice in tried_voices: continue # Evitar intentar la misma voz dos veces si está duplicada
442
+ tried_voices.append(current_voice)
443
+ logger.info(f"Intentando TTS con voz: {current_voice}...")
444
+ try:
445
+ tts_success = asyncio.run(text_to_speech(guion, voz_path, voice=current_voice))
446
+ if tts_success:
447
+ logger.info(f"TTS exitoso con voz '{current_voice}'.")
448
+ break # Salir del bucle de reintentos si tiene éxito
449
+ except Exception as e:
450
+ logger.warning(f"Fallo al generar TTS con voz '{current_voice}': {str(e)}", exc_info=True)
451
+ pass # Continuar al siguiente intento
452
 
453
+ # Verificar si el archivo fue creado después de todos los intentos
454
  if not tts_success or not os.path.exists(voz_path) or os.path.getsize(voz_path) <= 100:
455
+ logger.error("Fallo en la generación de voz después de todos los intentos. Archivo de audio no creado o es muy pequeño.")
456
  raise ValueError("Error generando voz a partir del guion (fallo de TTS).")
457
 
458
  temp_intermediate_files.append(voz_path)
 
888
 
889
 
890
  # run_app ahora recibe todos los inputs, incluyendo la voz seleccionada
891
+ def run_app(prompt_type, prompt_ia, prompt_manual, musica_file, selected_voice): # <-- Recibe el valor del Dropdown
892
  logger.info("="*80)
893
  logger.info("SOLICITUD RECIBIDA EN INTERFAZ")
894
 
895
+ # Elegir el texto de entrada basado en el prompt_type
896
  input_text = prompt_ia if prompt_type == "Generar Guion con IA" else prompt_manual
897
 
898
  output_video = None
 
903
  logger.warning("Texto de entrada vacío.")
904
  return None, None, gr.update(value="⚠️ Por favor, ingresa texto para el guion o el tema.", interactive=False)
905
 
906
+ # Validar la voz seleccionada. Si no es válida, usar la por defecto.
907
+ # AVAILABLE_VOICES se obtiene al inicio.
908
+ if selected_voice not in AVAILABLE_VOICES:
909
+ logger.warning(f"Voz seleccionada inválida o no encontrada en la lista: '{selected_voice}'. Usando voz por defecto: {DEFAULT_VOICE}.")
910
+ selected_voice = DEFAULT_VOICE
911
+ else:
912
+ logger.info(f"Voz seleccionada validada: {selected_voice}")
913
+
914
 
915
  logger.info(f"Tipo de entrada: {prompt_type}")
916
  logger.debug(f"Texto de entrada: '{input_text[:100]}...'")
 
918
  logger.info(f"Archivo de música recibido: {musica_file}")
919
  else:
920
  logger.info("No se proporcionó archivo de música.")
921
+ logger.info(f"Voz final a usar: {selected_voice}") # Loguear la voz final que se usará
922
 
923
  try:
924
  logger.info("Llamando a crear_video...")
925
+ # Pasar el input_text elegido, la voz seleccionada y el archivo de música
926
+ video_path = crear_video(prompt_type, input_text, selected_voice, musica_file) # <-- PASAR selected_voice a crear_video
927
 
928
  if video_path and os.path.exists(video_path):
929
  logger.info(f"crear_video retornó path: {video_path}")
930
  logger.info(f"Tamaño del archivo de video retornado: {os.path.getsize(video_path)} bytes")
931
+ output_video = video_path # Establecer valor del componente de video
932
+ output_file = video_path # Establecer valor del componente de archivo para descarga
933
  status_msg = gr.update(value="✅ Video generado exitosamente.", interactive=False)
934
  else:
935
  logger.error(f"crear_video no retornó un path válido o el archivo no existe: {video_path}")
 
968
  prompt_ia = gr.Textbox(
969
  label="Tema para IA",
970
  lines=2,
971
+ placeholder="Ej: Un paisaje natural con montañas y ríos al amanecer, mostrando la belleza de la naturaleza...",
972
  max_lines=4,
973
  value=""
974
  )
 
988
  interactive=True,
989
  value=None
990
  )
991
+
992
+ # --- COMPONENTE: Selección de Voz ---
993
  voice_dropdown = gr.Dropdown(
994
  label="Seleccionar Voz para Guion",
995
  choices=AVAILABLE_VOICES, # Usar la lista obtenida al inicio
996
  value=DEFAULT_VOICE, # Usar la voz por defecto calculada
997
  interactive=True
998
  )
999
+ # --- FIN COMPONENTE ---
1000
 
1001
 
1002
  generate_btn = gr.Button("✨ Generar Video", variant="primary")
 
1010
  file_output = gr.File(
1011
  label="Descargar Archivo de Video",
1012
  interactive=False,
1013
+ visible=False # Ocultar inicialmente
1014
  )
1015
  status_output = gr.Textbox(
1016
  label="Estado",
 
1025
  lambda x: (gr.update(visible=x == "Generar Guion con IA"),
1026
  gr.update(visible=x == "Usar Mi Guion")),
1027
  inputs=prompt_type,
1028
+ outputs=[ia_guion_column, manual_guion_column] # Apuntar a las Columnas
1029
  )
1030
 
1031
  # Evento click del botón de generar video
 
1033
  # Acción 1 (síncrona): Resetear salidas y establecer estado
1034
  lambda: (None, None, gr.update(value="⏳ Procesando... Esto puede tomar varios minutos.", interactive=False)),
1035
  outputs=[video_output, file_output, status_output],
1036
+ queue=True, # Usar la cola de Gradio
1037
  ).then(
1038
+ # Acción 2 (asíncrona): Llamar a la función principal
1039
  run_app,
1040
+ # PASAR TODOS LOS INPUTS RELEVANTES
1041
+ inputs=[prompt_type, prompt_ia, prompt_manual, musica_input, voice_dropdown], # <-- PASAR el dropdown de voz
1042
  # run_app retornará los 3 outputs esperados
1043
  outputs=[video_output, file_output, status_output]
1044
  ).then(
1045
+ # Acción 3 (síncrona): Hacer visible el enlace de descarga
1046
+ # Recibe las salidas de la Acción 2
1047
  lambda video_path, file_path, status_msg: gr.update(visible=file_path is not None),
1048
  inputs=[video_output, file_output, status_output],
1049
  outputs=[file_output]
 
1053
  gr.Markdown("### Instrucciones:")
1054
  gr.Markdown("""
1055
  1. **Clave API de Pexels:** Asegúrate de haber configurado la variable de entorno `PEXELS_API_KEY` con tu clave.
1056
+ 2. **Selecciona el tipo de entrada**: "Generar Guion con IA" o "Usar Mi Guion".
1057
+ 3. **Sube música** (opcional): Selecciona un archivo de audio (MP3, WAV, etc.).
1058
+ 4. **Selecciona la voz** deseada del desplegable.
 
 
1059
  5. **Haz clic en "✨ Generar Video"**.
1060
+ 6. Espera a que se procese el video. Verás el estado.
1061
+ 7. La previsualización aparecerá si es posible, y siempre un enlace **Descargar Archivo de Video** si la generación fue exitosa.
1062
+ 8. Revisa `video_generator_full.log` para detalles si hay errores.
1063
  """)
1064
  gr.Markdown("---")
1065
  gr.Markdown("Desarrollado por [Tu Nombre/Empresa/Alias - Opcional]")
 
1071
  try:
1072
  temp_clip = ColorClip((100,100), color=(255,0,0), duration=0.1)
1073
  temp_clip.close()
1074
+ logger.info("Clips base de MoviePy creados y cerrados exitosamente. FFmpeg parece accesible.")
1075
  except Exception as e:
1076
  logger.critical(f"Fallo al crear clip base de MoviePy. A menudo indica problemas con FFmpeg/ImageMagick. Error: {e}", exc_info=True)
1077