Spaces:
Runtime error
Runtime error
Update app.py
Browse files
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
|
@@ -32,6 +32,7 @@ logger.info("INICIO DE EJECUCIÓN - GENERADOR DE VIDEOS")
|
|
32 |
logger.info("="*80)
|
33 |
|
34 |
# Diccionario de voces TTS disponibles organizadas por idioma
|
|
|
35 |
VOCES_DISPONIBLES = {
|
36 |
"Español (España)": {
|
37 |
"es-ES-JuanNeural": "Juan (España) - Masculino",
|
@@ -99,9 +100,32 @@ def get_voice_choices():
|
|
99 |
choices = []
|
100 |
for region, voices in VOCES_DISPONIBLES.items():
|
101 |
for voice_id, voice_name in voices.items():
|
102 |
-
|
|
|
103 |
return choices
|
104 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
105 |
# Clave API de Pexels
|
106 |
PEXELS_API_KEY = os.environ.get("PEXELS_API_KEY")
|
107 |
if not PEXELS_API_KEY:
|
@@ -200,53 +224,63 @@ def generate_script(prompt, max_length=150):
|
|
200 |
text = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
201 |
|
202 |
cleaned_text = text.strip()
|
|
|
203 |
try:
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
|
|
|
|
208 |
else:
|
|
|
209 |
instruction_start_idx = text.find(instruction_phrase_start)
|
210 |
if instruction_start_idx != -1:
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
|
|
218 |
|
219 |
except Exception as e:
|
220 |
logger.warning(f"Error durante la limpieza heurística del guión de IA: {e}. Usando texto generado sin limpieza adicional.")
|
221 |
-
cleaned_text = re.sub(r'<[^>]+>', '', text).strip()
|
222 |
|
223 |
-
|
224 |
-
|
225 |
-
|
|
|
226 |
|
|
|
227 |
cleaned_text = re.sub(r'<[^>]+>', '', cleaned_text).strip()
|
228 |
-
cleaned_text = cleaned_text.lstrip(':').strip()
|
229 |
-
cleaned_text = cleaned_text.lstrip('.').strip()
|
|
|
230 |
|
|
|
231 |
sentences = cleaned_text.split('.')
|
232 |
if sentences and sentences[0].strip():
|
233 |
final_text = sentences[0].strip() + '.'
|
234 |
-
|
|
|
235 |
final_text += " " + sentences[1].strip() + "."
|
236 |
-
final_text = final_text.replace("..", ".")
|
237 |
|
238 |
logger.info(f"Guion generado final (Truncado a 100 chars): '{final_text[:100]}...'")
|
239 |
return final_text.strip()
|
240 |
|
241 |
logger.info(f"Guion generado final (sin oraciones completas detectadas - Truncado): '{cleaned_text[:100]}...'")
|
242 |
-
return cleaned_text.strip()
|
243 |
|
244 |
except Exception as e:
|
245 |
logger.error(f"Error generando guion con GPT-2 (fuera del bloque de limpieza): {str(e)}", exc_info=True)
|
246 |
logger.warning("Usando prompt original como guion debido al error de generación.")
|
247 |
return prompt.strip()
|
248 |
|
249 |
-
# Función TTS
|
250 |
async def text_to_speech(text, output_path, voice):
|
251 |
logger.info(f"Convirtiendo texto a voz | Caracteres: {len(text)} | Voz: {voice} | Salida: {output_path}")
|
252 |
if not text or not text.strip():
|
@@ -417,6 +451,7 @@ def extract_visual_keywords_from_script(script_text):
|
|
417 |
logger.info(f"Palabras clave finales: {top_keywords}")
|
418 |
return top_keywords
|
419 |
|
|
|
420 |
def crear_video(prompt_type, input_text, selected_voice, musica_file=None):
|
421 |
logger.info("="*80)
|
422 |
logger.info(f"INICIANDO CREACIÓN DE VIDEO | Tipo: {prompt_type}")
|
@@ -452,35 +487,40 @@ def crear_video(prompt_type, input_text, selected_voice, musica_file=None):
|
|
452 |
logger.info(f"Directorio temporal intermedio creado: {temp_dir_intermediate}")
|
453 |
temp_intermediate_files = []
|
454 |
|
455 |
-
# 2. Generar audio de voz con reintentos
|
456 |
logger.info("Generando audio de voz...")
|
457 |
voz_path = os.path.join(temp_dir_intermediate, "voz.mp3")
|
458 |
|
459 |
-
|
460 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
461 |
tts_success = False
|
462 |
-
|
463 |
|
464 |
-
for
|
465 |
-
current_voice
|
466 |
-
|
467 |
-
|
|
|
468 |
try:
|
469 |
tts_success = asyncio.run(text_to_speech(guion, voz_path, voice=current_voice))
|
470 |
if tts_success:
|
471 |
-
logger.info(f"TTS exitoso
|
472 |
-
break
|
473 |
except Exception as e:
|
474 |
-
|
475 |
-
|
476 |
-
if not tts_success and attempt == 0 and primary_voice != fallback_voice:
|
477 |
-
logger.warning(f"Fallo con voz {primary_voice}, intentando voz de respaldo: {fallback_voice}")
|
478 |
-
elif not tts_success and attempt < retries - 1:
|
479 |
-
logger.warning(f"Fallo con voz {current_voice}, reintentando...")
|
480 |
-
|
481 |
|
|
|
482 |
if not tts_success or not os.path.exists(voz_path) or os.path.getsize(voz_path) <= 100:
|
483 |
-
logger.error(
|
484 |
raise ValueError("Error generando voz a partir del guion (fallo de TTS).")
|
485 |
|
486 |
temp_intermediate_files.append(voz_path)
|
@@ -530,20 +570,6 @@ def crear_video(prompt_type, input_text, selected_voice, musica_file=None):
|
|
530 |
except Exception as e:
|
531 |
logger.warning(f"Error buscando videos para '{keyword}': {str(e)}")
|
532 |
|
533 |
-
if len(videos_data) < total_desired_videos / 2:
|
534 |
-
logger.warning(f"Pocos videos encontrados ({len(videos_data)}). Intentando con palabras clave genéricas.")
|
535 |
-
generic_keywords = ["nature", "city", "background", "abstract"]
|
536 |
-
for keyword in generic_keywords:
|
537 |
-
if len(videos_data) >= total_desired_videos:
|
538 |
-
break
|
539 |
-
try:
|
540 |
-
videos = buscar_videos_pexels(keyword, PEXELS_API_KEY, per_page=2)
|
541 |
-
if videos:
|
542 |
-
videos_data.extend(videos)
|
543 |
-
logger.info(f"Encontrados {len(videos)} videos para '{keyword}' (genérico). Total data: {len(videos_data)}")
|
544 |
-
except Exception as e:
|
545 |
-
logger.warning(f"Error buscando videos para '{keyword}': {str(e)}")
|
546 |
-
|
547 |
if len(videos_data) < total_desired_videos / 2:
|
548 |
logger.warning(f"Pocos videos encontrados ({len(videos_data)}). Intentando con palabras clave genéricas.")
|
549 |
generic_keywords = ["nature", "city", "background", "abstract"]
|
@@ -929,7 +955,7 @@ def crear_video(prompt_type, input_text, selected_voice, musica_file=None):
|
|
929 |
logger.info(f"Directorio temporal intermedio {temp_dir_intermediate} persistirá para que Gradio lea el video final.")
|
930 |
|
931 |
|
932 |
-
#
|
933 |
def run_app(prompt_type, prompt_ia, prompt_manual, musica_file, selected_voice): # <-- Recibe el valor del Dropdown
|
934 |
logger.info("="*80)
|
935 |
logger.info("SOLICITUD RECIBIDA EN INTERFAZ")
|
@@ -947,10 +973,11 @@ def run_app(prompt_type, prompt_ia, prompt_manual, musica_file, selected_voice):
|
|
947 |
return None, None, gr.update(value="⚠️ Por favor, ingresa texto para el guion o el tema.", interactive=False)
|
948 |
|
949 |
# Validar la voz seleccionada. Si no es válida, usar la por defecto.
|
950 |
-
# AVAILABLE_VOICES se obtiene al inicio.
|
951 |
-
|
952 |
-
|
953 |
-
selected_voice
|
|
|
954 |
else:
|
955 |
logger.info(f"Voz seleccionada validada: {selected_voice}")
|
956 |
|
@@ -961,12 +988,12 @@ def run_app(prompt_type, prompt_ia, prompt_manual, musica_file, selected_voice):
|
|
961 |
logger.info(f"Archivo de música recibido: {musica_file}")
|
962 |
else:
|
963 |
logger.info("No se proporcionó archivo de música.")
|
964 |
-
logger.info(f"Voz final a usar: {selected_voice}") # Loguear la voz final
|
965 |
|
966 |
try:
|
967 |
logger.info("Llamando a crear_video...")
|
968 |
-
# Pasar el input_text elegido, la voz seleccionada y el archivo de música a crear_video
|
969 |
-
video_path = crear_video(prompt_type, input_text, selected_voice, musica_file) # <-- PASAR selected_voice a crear_video
|
970 |
|
971 |
if video_path and os.path.exists(video_path):
|
972 |
logger.info(f"crear_video retornó path: {video_path}")
|
@@ -1038,8 +1065,8 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
|
|
1038 |
# --- COMPONENTE: Selección de Voz ---
|
1039 |
voice_dropdown = gr.Dropdown(
|
1040 |
label="Seleccionar Voz para Guion",
|
1041 |
-
choices=AVAILABLE_VOICES,
|
1042 |
-
value=
|
1043 |
interactive=True
|
1044 |
# visible=... <-- ¡NO DEBE ESTAR AQUÍ!
|
1045 |
)
|
@@ -1058,7 +1085,7 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
|
|
1058 |
file_output = gr.File(
|
1059 |
label="Descargar Archivo de Video",
|
1060 |
interactive=False,
|
1061 |
-
visible=False # <-- ESTÁ BIEN AQUÍ
|
1062 |
# visible=... <-- ¡NO DEBE ESTAR AQUÍ si ya está visible=False arriba!
|
1063 |
)
|
1064 |
status_output = gr.Textbox(
|
@@ -1093,11 +1120,8 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
|
|
1093 |
outputs=[video_output, file_output, status_output]
|
1094 |
).then(
|
1095 |
# Acción 3 (síncrona): Hacer visible el enlace de descarga
|
1096 |
-
# Recibe las salidas de la Acción 2
|
1097 |
lambda video_path, file_path, status_msg: gr.update(visible=file_path is not None),
|
1098 |
-
# Inputs para esta lambda son los outputs del .then() anterior
|
1099 |
inputs=[video_output, file_output, status_output],
|
1100 |
-
# Actualizamos la visibilidad del componente file_output
|
1101 |
outputs=[file_output]
|
1102 |
)
|
1103 |
|
|
|
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
|
|
|
32 |
logger.info("="*80)
|
33 |
|
34 |
# Diccionario de voces TTS disponibles organizadas por idioma
|
35 |
+
# Puedes expandir esta lista si conoces otros IDs de voz de Edge TTS
|
36 |
VOCES_DISPONIBLES = {
|
37 |
"Español (España)": {
|
38 |
"es-ES-JuanNeural": "Juan (España) - Masculino",
|
|
|
100 |
choices = []
|
101 |
for region, voices in VOCES_DISPONIBLES.items():
|
102 |
for voice_id, voice_name in voices.items():
|
103 |
+
# Formato: (Texto a mostrar en el dropdown, Valor que se pasa)
|
104 |
+
choices.append((f"{voice_name} ({region})", voice_id))
|
105 |
return choices
|
106 |
|
107 |
+
# Obtener las voces al inicio del script
|
108 |
+
# Usamos la lista predefinida por ahora para evitar el error de inicio con la API
|
109 |
+
# Si deseas obtenerlas dinámicamente, descomenta la siguiente línea y comenta la que usa get_voice_choices()
|
110 |
+
# AVAILABLE_VOICES = asyncio.run(get_available_voices())
|
111 |
+
AVAILABLE_VOICES = get_voice_choices() # <-- Usamos la lista predefinida y aplanada
|
112 |
+
# Establecer una voz por defecto inicial
|
113 |
+
DEFAULT_VOICE_ID = "es-ES-JuanNeural" # ID de Juan
|
114 |
+
|
115 |
+
# Buscar el nombre amigable para la voz por defecto si existe
|
116 |
+
DEFAULT_VOICE_NAME = DEFAULT_VOICE_ID
|
117 |
+
for text, voice_id in AVAILABLE_VOICES:
|
118 |
+
if voice_id == DEFAULT_VOICE_ID:
|
119 |
+
DEFAULT_VOICE_NAME = text
|
120 |
+
break
|
121 |
+
# Si Juan no está en la lista (ej. lista de fallback), usar la primera voz disponible
|
122 |
+
if DEFAULT_VOICE_ID not in [v[1] for v in AVAILABLE_VOICES]:
|
123 |
+
DEFAULT_VOICE_ID = AVAILABLE_VOICES[0][1] if AVAILABLE_VOICES else "en-US-AriaNeural"
|
124 |
+
DEFAULT_VOICE_NAME = AVAILABLE_VOICES[0][0] if AVAILABLE_VOICES else "Aria (United States) - Female" # Fallback name
|
125 |
+
|
126 |
+
logger.info(f"Voz por defecto seleccionada (ID): {DEFAULT_VOICE_ID}")
|
127 |
+
|
128 |
+
|
129 |
# Clave API de Pexels
|
130 |
PEXELS_API_KEY = os.environ.get("PEXELS_API_KEY")
|
131 |
if not PEXELS_API_KEY:
|
|
|
224 |
text = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
225 |
|
226 |
cleaned_text = text.strip()
|
227 |
+
# Limpieza mejorada de la frase de instrucción
|
228 |
try:
|
229 |
+
# Buscar el índice de inicio del prompt original dentro del texto generado
|
230 |
+
prompt_in_output_idx = text.lower().find(prompt.lower())
|
231 |
+
if prompt_in_output_idx != -1:
|
232 |
+
# Tomar todo el texto DESPUÉS del prompt original
|
233 |
+
cleaned_text = text[prompt_in_output_idx + len(prompt):].strip()
|
234 |
+
logger.debug("Texto limpiado tomando parte después del prompt original.")
|
235 |
else:
|
236 |
+
# Fallback si el prompt original no está exacto en la salida: buscar la frase de instrucción base
|
237 |
instruction_start_idx = text.find(instruction_phrase_start)
|
238 |
if instruction_start_idx != -1:
|
239 |
+
# Tomar texto después de la frase base (puede incluir el prompt)
|
240 |
+
cleaned_text = text[instruction_start_idx + len(instruction_phrase_start):].strip()
|
241 |
+
logger.debug("Texto limpiado tomando parte después de la frase de instrucción base.")
|
242 |
+
else:
|
243 |
+
# Si ni la frase de instrucción ni el prompt se encuentran, usar el texto original
|
244 |
+
logger.warning("No se pudo identificar el inicio del guión generado. Usando texto generado completo.")
|
245 |
+
cleaned_text = text.strip() # Limpieza básica
|
246 |
+
|
247 |
|
248 |
except Exception as e:
|
249 |
logger.warning(f"Error durante la limpieza heurística del guión de IA: {e}. Usando texto generado sin limpieza adicional.")
|
250 |
+
cleaned_text = re.sub(r'<[^>]+>', '', text).strip() # Limpieza básica como fallback
|
251 |
|
252 |
+
# Asegurarse de que el texto resultante no sea solo la instrucción o vacío
|
253 |
+
if not cleaned_text or len(cleaned_text) < 10: # Umbral de longitud mínima
|
254 |
+
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).")
|
255 |
+
cleaned_text = re.sub(r'<[^>]+>', '', text).strip() # Fallback al texto original limpio
|
256 |
|
257 |
+
# Limpieza final de caracteres especiales y espacios sobrantes
|
258 |
cleaned_text = re.sub(r'<[^>]+>', '', cleaned_text).strip()
|
259 |
+
cleaned_text = cleaned_text.lstrip(':').strip() # Quitar posibles ':' al inicio
|
260 |
+
cleaned_text = cleaned_text.lstrip('.').strip() # Quitar posibles '.' al inicio
|
261 |
+
|
262 |
|
263 |
+
# Intentar obtener al menos una oración completa si es posible para un inicio más limpio
|
264 |
sentences = cleaned_text.split('.')
|
265 |
if sentences and sentences[0].strip():
|
266 |
final_text = sentences[0].strip() + '.'
|
267 |
+
# Añadir la segunda oración si existe y es razonable
|
268 |
+
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
|
269 |
final_text += " " + sentences[1].strip() + "."
|
270 |
+
final_text = final_text.replace("..", ".") # Limpiar doble punto
|
271 |
|
272 |
logger.info(f"Guion generado final (Truncado a 100 chars): '{final_text[:100]}...'")
|
273 |
return final_text.strip()
|
274 |
|
275 |
logger.info(f"Guion generado final (sin oraciones completas detectadas - Truncado): '{cleaned_text[:100]}...'")
|
276 |
+
return cleaned_text.strip() # Si no se puede formar una oración, devolver el texto limpio tal cual
|
277 |
|
278 |
except Exception as e:
|
279 |
logger.error(f"Error generando guion con GPT-2 (fuera del bloque de limpieza): {str(e)}", exc_info=True)
|
280 |
logger.warning("Usando prompt original como guion debido al error de generación.")
|
281 |
return prompt.strip()
|
282 |
|
283 |
+
# Función TTS ahora recibe la voz a usar
|
284 |
async def text_to_speech(text, output_path, voice):
|
285 |
logger.info(f"Convirtiendo texto a voz | Caracteres: {len(text)} | Voz: {voice} | Salida: {output_path}")
|
286 |
if not text or not text.strip():
|
|
|
451 |
logger.info(f"Palabras clave finales: {top_keywords}")
|
452 |
return top_keywords
|
453 |
|
454 |
+
# crear_video ahora recibe la voz seleccionada
|
455 |
def crear_video(prompt_type, input_text, selected_voice, musica_file=None):
|
456 |
logger.info("="*80)
|
457 |
logger.info(f"INICIANDO CREACIÓN DE VIDEO | Tipo: {prompt_type}")
|
|
|
487 |
logger.info(f"Directorio temporal intermedio creado: {temp_dir_intermediate}")
|
488 |
temp_intermediate_files = []
|
489 |
|
490 |
+
# 2. Generar audio de voz usando la voz seleccionada, con reintentos si falla
|
491 |
logger.info("Generando audio de voz...")
|
492 |
voz_path = os.path.join(temp_dir_intermediate, "voz.mp3")
|
493 |
|
494 |
+
tts_voices_to_try = [selected_voice] # Intentar primero la voz seleccionada
|
495 |
+
# Añadir voces de respaldo si no están ya en la lista y son diferentes a la seleccionada
|
496 |
+
# Nos aseguramos de no añadir None o IDs vacíos a la lista de reintento
|
497 |
+
if "es-ES-JuanNeural" not in tts_voices_to_try and "es-ES-JuanNeural" is not None: tts_voices_to_try.append("es-ES-JuanNeural")
|
498 |
+
if "es-ES-ElviraNeural" not in tts_voices_to_try and "es-ES-ElviraNeural" is not None: tts_voices_to_try.append("es-ES-ElviraNeural")
|
499 |
+
# Si la lista de voces disponibles es fiable, podrías usar un subconjunto ordenado para reintentos más amplios
|
500 |
+
# Opcional: si AVAILABLE_VOICES es fiable, podrías usar un subconjunto ordenado para reintentos
|
501 |
+
# Ejemplo: for voice_id in [selected_voice] + sorted([v[1] for v in AVAILABLE_VOICES if v[1].startswith('es-') and v[1] != selected_voice]) + sorted([v[1] for v in AVAILABLE_VOICES if not v[1].startswith('es-') and v[1] != selected_voice]):
|
502 |
+
|
503 |
+
|
504 |
tts_success = False
|
505 |
+
tried_voices = set() # Usar un set para rastrear voces intentadas de forma eficiente
|
506 |
|
507 |
+
for current_voice in tts_voices_to_try:
|
508 |
+
if not current_voice or current_voice in tried_voices: continue # Evitar intentar IDs None/vacíos o duplicados
|
509 |
+
tried_voices.add(current_voice)
|
510 |
+
|
511 |
+
logger.info(f"Intentando TTS con voz: {current_voice}...")
|
512 |
try:
|
513 |
tts_success = asyncio.run(text_to_speech(guion, voz_path, voice=current_voice))
|
514 |
if tts_success:
|
515 |
+
logger.info(f"TTS exitoso con voz '{current_voice}'.")
|
516 |
+
break # Salir del bucle de reintento si tiene éxito
|
517 |
except Exception as e:
|
518 |
+
logger.warning(f"Fallo al generar TTS con voz '{current_voice}': {str(e)}", exc_info=True)
|
519 |
+
pass # Continuar al siguiente intento
|
|
|
|
|
|
|
|
|
|
|
520 |
|
521 |
+
# Verificar si el archivo fue creado después de todos los intentos
|
522 |
if not tts_success or not os.path.exists(voz_path) or os.path.getsize(voz_path) <= 100:
|
523 |
+
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.")
|
524 |
raise ValueError("Error generando voz a partir del guion (fallo de TTS).")
|
525 |
|
526 |
temp_intermediate_files.append(voz_path)
|
|
|
570 |
except Exception as e:
|
571 |
logger.warning(f"Error buscando videos para '{keyword}': {str(e)}")
|
572 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
573 |
if len(videos_data) < total_desired_videos / 2:
|
574 |
logger.warning(f"Pocos videos encontrados ({len(videos_data)}). Intentando con palabras clave genéricas.")
|
575 |
generic_keywords = ["nature", "city", "background", "abstract"]
|
|
|
955 |
logger.info(f"Directorio temporal intermedio {temp_dir_intermediate} persistirá para que Gradio lea el video final.")
|
956 |
|
957 |
|
958 |
+
# run_app ahora recibe todos los inputs, incluyendo la voz seleccionada
|
959 |
def run_app(prompt_type, prompt_ia, prompt_manual, musica_file, selected_voice): # <-- Recibe el valor del Dropdown
|
960 |
logger.info("="*80)
|
961 |
logger.info("SOLICITUD RECIBIDA EN INTERFAZ")
|
|
|
973 |
return None, None, gr.update(value="⚠️ Por favor, ingresa texto para el guion o el tema.", interactive=False)
|
974 |
|
975 |
# Validar la voz seleccionada. Si no es válida, usar la por defecto.
|
976 |
+
# AVAILABLE_VOICES se obtiene al inicio. Hay que buscar si el voice_id existe en la lista de pares (nombre, id)
|
977 |
+
voice_ids_disponibles = [v[1] for v in AVAILABLE_VOICES]
|
978 |
+
if selected_voice not in voice_ids_disponibles:
|
979 |
+
logger.warning(f"Voz seleccionada inválida o no encontrada en la lista: '{selected_voice}'. Usando voz por defecto: {DEFAULT_VOICE_ID}.")
|
980 |
+
selected_voice = DEFAULT_VOICE_ID # <-- Usar el ID de la voz por defecto
|
981 |
else:
|
982 |
logger.info(f"Voz seleccionada validada: {selected_voice}")
|
983 |
|
|
|
988 |
logger.info(f"Archivo de música recibido: {musica_file}")
|
989 |
else:
|
990 |
logger.info("No se proporcionó archivo de música.")
|
991 |
+
logger.info(f"Voz final a usar (ID): {selected_voice}") # Loguear el ID de la voz final
|
992 |
|
993 |
try:
|
994 |
logger.info("Llamando a crear_video...")
|
995 |
+
# Pasar el input_text elegido, la voz seleccionada (el ID) y el archivo de música a crear_video
|
996 |
+
video_path = crear_video(prompt_type, input_text, selected_voice, musica_file) # <-- PASAR selected_voice (ID) a crear_video
|
997 |
|
998 |
if video_path and os.path.exists(video_path):
|
999 |
logger.info(f"crear_video retornó path: {video_path}")
|
|
|
1065 |
# --- COMPONENTE: Selección de Voz ---
|
1066 |
voice_dropdown = gr.Dropdown(
|
1067 |
label="Seleccionar Voz para Guion",
|
1068 |
+
choices=AVAILABLE_VOICES, # Usar la lista obtenida al inicio
|
1069 |
+
value=DEFAULT_VOICE_ID, # Usar el ID de la voz por defecto calculada
|
1070 |
interactive=True
|
1071 |
# visible=... <-- ¡NO DEBE ESTAR AQUÍ!
|
1072 |
)
|
|
|
1085 |
file_output = gr.File(
|
1086 |
label="Descargar Archivo de Video",
|
1087 |
interactive=False,
|
1088 |
+
visible=False # <-- ESTÁ BIEN AQUÍ
|
1089 |
# visible=... <-- ¡NO DEBE ESTAR AQUÍ si ya está visible=False arriba!
|
1090 |
)
|
1091 |
status_output = gr.Textbox(
|
|
|
1120 |
outputs=[video_output, file_output, status_output]
|
1121 |
).then(
|
1122 |
# Acción 3 (síncrona): Hacer visible el enlace de descarga
|
|
|
1123 |
lambda video_path, file_path, status_msg: gr.update(visible=file_path is not None),
|
|
|
1124 |
inputs=[video_output, file_output, status_output],
|
|
|
1125 |
outputs=[file_output]
|
1126 |
)
|
1127 |
|