Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
@@ -31,6 +31,77 @@ logger.info("="*80)
|
|
31 |
logger.info("INICIO DE EJECUCI脫N - GENERADOR DE VIDEOS")
|
32 |
logger.info("="*80)
|
33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
# Clave API de Pexels
|
35 |
PEXELS_API_KEY = os.environ.get("PEXELS_API_KEY")
|
36 |
if not PEXELS_API_KEY:
|
@@ -61,36 +132,6 @@ except Exception as e:
|
|
61 |
logger.error(f"FALLA al cargar KeyBERT: {str(e)}", exc_info=True)
|
62 |
kw_model = None
|
63 |
|
64 |
-
# --- Obtener voces de Edge TTS al inicio ---
|
65 |
-
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 |
-
# Retornar 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 |
-
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)
|
82 |
-
# Retornar una lista de voces por defecto si falla la API de Edge TTS
|
83 |
-
logger.warning("No se pudieron obtener voces de Edge TTS. Usando lista de voces por defecto.")
|
84 |
-
return ["es-ES-JuanNeural", "es-ES-ElviraNeural", "en-US-AriaNeural"]
|
85 |
-
|
86 |
-
# Obtener las voces al inicio del script (esto puede tardar un poco)
|
87 |
-
logger.info("Inicializando lista de voces disponibles...")
|
88 |
-
AVAILABLE_VOICES = asyncio.run(get_available_voices())
|
89 |
-
# Establecer una voz por defecto inicial
|
90 |
-
DEFAULT_VOICE = "es-ES-JuanNeural" if "es-ES-JuanNeural" in AVAILABLE_VOICES else (AVAILABLE_VOICES[0] if AVAILABLE_VOICES else "en-US-AriaNeural")
|
91 |
-
logger.info(f"Voz por defecto seleccionada: {DEFAULT_VOICE}")
|
92 |
-
|
93 |
-
|
94 |
def buscar_videos_pexels(query, api_key, per_page=5):
|
95 |
if not api_key:
|
96 |
logger.warning("No se puede buscar en Pexels: API Key no configurada.")
|
@@ -159,63 +200,53 @@ def generate_script(prompt, max_length=150):
|
|
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 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
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 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
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()
|
186 |
|
187 |
-
|
188 |
-
|
189 |
-
|
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()
|
195 |
-
cleaned_text = cleaned_text.lstrip('.').strip()
|
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 |
-
|
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("..", ".")
|
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()
|
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)
|
215 |
logger.warning("Usando prompt original como guion debido al error de generaci贸n.")
|
216 |
return prompt.strip()
|
217 |
|
218 |
-
# Funci贸n TTS
|
219 |
async def text_to_speech(text, output_path, voice):
|
220 |
logger.info(f"Convirtiendo texto a voz | Caracteres: {len(text)} | Voz: {voice} | Salida: {output_path}")
|
221 |
if not text or not text.strip():
|
@@ -386,12 +417,11 @@ def extract_visual_keywords_from_script(script_text):
|
|
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
|
395 |
|
396 |
start_time = datetime.now()
|
397 |
temp_dir_intermediate = None
|
@@ -422,39 +452,35 @@ def crear_video(prompt_type, input_text, selected_voice, musica_file=None):
|
|
422 |
logger.info(f"Directorio temporal intermedio creado: {temp_dir_intermediate}")
|
423 |
temp_intermediate_files = []
|
424 |
|
425 |
-
# 2. Generar audio de voz
|
426 |
logger.info("Generando audio de voz...")
|
427 |
voz_path = os.path.join(temp_dir_intermediate, "voz.mp3")
|
428 |
|
429 |
-
|
430 |
-
|
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 fiable, podr铆as usar un subconjunto ordenado para reintentos m谩s amplios
|
434 |
-
# Ejemplo: for voice_id in [selected_voice] + sorted([v for v in AVAILABLE_VOICES if v.startswith('es-') and v != selected_voice]) + sorted([v for v in AVAILABLE_VOICES if not v.startswith('es-') and v != selected_voice]):
|
435 |
-
|
436 |
-
|
437 |
tts_success = False
|
438 |
-
|
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
|
442 |
-
tried_voices.add(current_voice)
|
443 |
|
444 |
-
|
|
|
|
|
|
|
445 |
try:
|
446 |
tts_success = asyncio.run(text_to_speech(guion, voz_path, voice=current_voice))
|
447 |
if tts_success:
|
448 |
-
logger.info(f"TTS exitoso con voz
|
449 |
-
break
|
450 |
except Exception as e:
|
451 |
-
|
452 |
-
|
|
|
|
|
|
|
|
|
453 |
|
454 |
|
455 |
-
# Verificar si el archivo fue creado despu茅s de todos los intentos
|
456 |
if not tts_success or not os.path.exists(voz_path) or os.path.getsize(voz_path) <= 100:
|
457 |
-
logger.error("Fallo en la generaci贸n de voz despu茅s de
|
458 |
raise ValueError("Error generando voz a partir del guion (fallo de TTS).")
|
459 |
|
460 |
temp_intermediate_files.append(voz_path)
|
@@ -504,6 +530,19 @@ def crear_video(prompt_type, input_text, selected_voice, musica_file=None):
|
|
504 |
except Exception as e:
|
505 |
logger.warning(f"Error buscando videos para '{keyword}': {str(e)}")
|
506 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
507 |
if len(videos_data) < total_desired_videos / 2:
|
508 |
logger.warning(f"Pocos videos encontrados ({len(videos_data)}). Intentando con palabras clave gen茅ricas.")
|
509 |
generic_keywords = ["nature", "city", "background", "abstract"]
|
|
|
31 |
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",
|
38 |
+
"es-ES-ElviraNeural": "Elvira (Espa帽a) - Femenino",
|
39 |
+
"es-ES-AlvaroNeural": "脕lvaro (Espa帽a) - Masculino",
|
40 |
+
"es-ES-AbrilNeural": "Abril (Espa帽a) - Femenino",
|
41 |
+
"es-ES-ArnauNeural": "Arnau (Espa帽a) - Masculino",
|
42 |
+
"es-ES-DarioNeural": "Dar铆o (Espa帽a) - Masculino",
|
43 |
+
"es-ES-EliasNeural": "El铆as (Espa帽a) - Masculino",
|
44 |
+
"es-ES-EstrellaNeural": "Estrella (Espa帽a) - Femenino",
|
45 |
+
"es-ES-IreneNeural": "Irene (Espa帽a) - Femenino",
|
46 |
+
"es-ES-LaiaNeural": "Laia (Espa帽a) - Femenino",
|
47 |
+
"es-ES-LiaNeural": "L铆a (Espa帽a) - Femenino",
|
48 |
+
"es-ES-NilNeural": "Nil (Espa帽a) - Masculino",
|
49 |
+
"es-ES-SaulNeural": "Sa煤l (Espa帽a) - Masculino",
|
50 |
+
"es-ES-TeoNeural": "Teo (Espa帽a) - Masculino",
|
51 |
+
"es-ES-TrianaNeural": "Triana (Espa帽a) - Femenino",
|
52 |
+
"es-ES-VeraNeural": "Vera (Espa帽a) - Femenino"
|
53 |
+
},
|
54 |
+
"Espa帽ol (M茅xico)": {
|
55 |
+
"es-MX-JorgeNeural": "Jorge (M茅xico) - Masculino",
|
56 |
+
"es-MX-DaliaNeural": "Dalia (M茅xico) - Femenino",
|
57 |
+
"es-MX-BeatrizNeural": "Beatriz (M茅xico) - Femenino",
|
58 |
+
"es-MX-CandelaNeural": "Candela (M茅xico) - Femenino",
|
59 |
+
"es-MX-CarlotaNeural": "Carlota (M茅xico) - Femenino",
|
60 |
+
"es-MX-CecilioNeural": "Cecilio (M茅xico) - Masculino",
|
61 |
+
"es-MX-GerardoNeural": "Gerardo (M茅xico) - Masculino",
|
62 |
+
"es-MX-LarissaNeural": "Larissa (M茅xico) - Femenino",
|
63 |
+
"es-MX-LibertoNeural": "Liberto (M茅xico) - Masculino",
|
64 |
+
"es-MX-LucianoNeural": "Luciano (M茅xico) - Masculino",
|
65 |
+
"es-MX-MarinaNeural": "Marina (M茅xico) - Femenino",
|
66 |
+
"es-MX-NuriaNeural": "Nuria (M茅xico) - Femenino",
|
67 |
+
"es-MX-PelayoNeural": "Pelayo (M茅xico) - Masculino",
|
68 |
+
"es-MX-RenataNeural": "Renata (M茅xico) - Femenino",
|
69 |
+
"es-MX-YagoNeural": "Yago (M茅xico) - Masculino"
|
70 |
+
},
|
71 |
+
"Espa帽ol (Argentina)": {
|
72 |
+
"es-AR-TomasNeural": "Tom谩s (Argentina) - Masculino",
|
73 |
+
"es-AR-ElenaNeural": "Elena (Argentina) - Femenino"
|
74 |
+
},
|
75 |
+
"Espa帽ol (Colombia)": {
|
76 |
+
"es-CO-GonzaloNeural": "Gonzalo (Colombia) - Masculino",
|
77 |
+
"es-CO-SalomeNeural": "Salom茅 (Colombia) - Femenino"
|
78 |
+
},
|
79 |
+
"Espa帽ol (Chile)": {
|
80 |
+
"es-CL-LorenzoNeural": "Lorenzo (Chile) - Masculino",
|
81 |
+
"es-CL-CatalinaNeural": "Catalina (Chile) - Femenino"
|
82 |
+
},
|
83 |
+
"Espa帽ol (Per煤)": {
|
84 |
+
"es-PE-AlexNeural": "Alex (Per煤) - Masculino",
|
85 |
+
"es-PE-CamilaNeural": "Camila (Per煤) - Femenino"
|
86 |
+
},
|
87 |
+
"Espa帽ol (Venezuela)": {
|
88 |
+
"es-VE-PaolaNeural": "Paola (Venezuela) - Femenino",
|
89 |
+
"es-VE-SebastianNeural": "Sebasti谩n (Venezuela) - Masculino"
|
90 |
+
},
|
91 |
+
"Espa帽ol (Estados Unidos)": {
|
92 |
+
"es-US-AlonsoNeural": "Alonso (Estados Unidos) - Masculino",
|
93 |
+
"es-US-PalomaNeural": "Paloma (Estados Unidos) - Femenino"
|
94 |
+
}
|
95 |
+
}
|
96 |
+
|
97 |
+
# Funci贸n para obtener lista plana de voces para el dropdown
|
98 |
+
def get_voice_choices():
|
99 |
+
choices = []
|
100 |
+
for region, voices in VOCES_DISPONIBLES.items():
|
101 |
+
for voice_id, voice_name in voices.items():
|
102 |
+
choices.append((voice_name, voice_id))
|
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:
|
|
|
132 |
logger.error(f"FALLA al cargar KeyBERT: {str(e)}", exc_info=True)
|
133 |
kw_model = None
|
134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
135 |
def buscar_videos_pexels(query, api_key, per_page=5):
|
136 |
if not api_key:
|
137 |
logger.warning("No se puede buscar en Pexels: API Key no configurada.")
|
|
|
200 |
text = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
201 |
|
202 |
cleaned_text = text.strip()
|
|
|
203 |
try:
|
204 |
+
instruction_end_idx = text.find(instruction_phrase)
|
205 |
+
if instruction_end_idx != -1:
|
206 |
+
cleaned_text = text[instruction_end_idx + len(instruction_phrase):].strip()
|
207 |
+
logger.debug("Instrucci贸n inicial encontrada y eliminada del gui贸n generado.")
|
|
|
|
|
208 |
else:
|
|
|
209 |
instruction_start_idx = text.find(instruction_phrase_start)
|
210 |
if instruction_start_idx != -1:
|
211 |
+
prompt_in_output_idx = text.find(prompt, instruction_start_idx)
|
212 |
+
if prompt_in_output_idx != -1:
|
213 |
+
cleaned_text = text[prompt_in_output_idx + len(prompt):].strip()
|
214 |
+
logger.debug("Instrucci贸n base y prompt encontrados y eliminados del gui贸n generado.")
|
215 |
+
else:
|
216 |
+
cleaned_text = text[instruction_start_idx + len(instruction_phrase_start):].strip()
|
217 |
+
logger.debug("Instrucci贸n base encontrada, eliminada del gui贸n generado (sin prompt detectado).")
|
|
|
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 |
+
if not cleaned_text or len(cleaned_text) < 10:
|
224 |
+
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).")
|
225 |
+
cleaned_text = re.sub(r'<[^>]+>', '', text).strip()
|
|
|
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 |
+
if len(sentences) > 1 and sentences[1].strip() and len(final_text.split()) < max_length * 0.7:
|
|
|
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 con voz especificada
|
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 |
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}")
|
423 |
logger.debug(f"Input: '{input_text[:100]}...'")
|
424 |
+
logger.info(f"Voz seleccionada: {selected_voice}")
|
425 |
|
426 |
start_time = datetime.now()
|
427 |
temp_dir_intermediate = 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 y voz de respaldo
|
456 |
logger.info("Generando audio de voz...")
|
457 |
voz_path = os.path.join(temp_dir_intermediate, "voz.mp3")
|
458 |
|
459 |
+
primary_voice = selected_voice
|
460 |
+
fallback_voice = "es-ES-ElviraNeural" if selected_voice != "es-ES-ElviraNeural" else "es-ES-JuanNeural"
|
|
|
|
|
|
|
|
|
|
|
|
|
461 |
tts_success = False
|
462 |
+
retries = 3
|
|
|
|
|
|
|
|
|
463 |
|
464 |
+
for attempt in range(retries):
|
465 |
+
current_voice = primary_voice if attempt == 0 else fallback_voice
|
466 |
+
if attempt > 0: logger.warning(f"Reintentando TTS ({attempt+1}/{retries})...")
|
467 |
+
logger.info(f"Intentando TTS con voz: {current_voice}")
|
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 en intento {attempt + 1} con voz {current_voice}.")
|
472 |
+
break
|
473 |
except Exception as e:
|
474 |
+
pass
|
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(f"Fallo en la generaci贸n de voz despu茅s de {retries} intentos. Archivo de audio no creado o es muy peque帽o.")
|
484 |
raise ValueError("Error generando voz a partir del guion (fallo de TTS).")
|
485 |
|
486 |
temp_intermediate_files.append(voz_path)
|
|
|
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: break
|
538 |
+
try:
|
539 |
+
videos = buscar_videos_pexels(keyword, PEXELS_API_KEY, per_page=2)
|
540 |
+
if videos:
|
541 |
+
videos_data.extend(videos)
|
542 |
+
logger.info(f"Encontrados {len(videos)} videos para '{keyword}' (gen茅rico). Total data: {len {len(videos)} videos para '{keyword}'. Total data: {len(videos_data)}")
|
543 |
+
except Exception as e:
|
544 |
+
logger.warning(f"Error buscando videos para '{keyword}': {str(e)}")
|
545 |
+
|
546 |
if len(videos_data) < total_desired_videos / 2:
|
547 |
logger.warning(f"Pocos videos encontrados ({len(videos_data)}). Intentando con palabras clave gen茅ricas.")
|
548 |
generic_keywords = ["nature", "city", "background", "abstract"]
|