gnosticdev commited on
Commit
9b60d0a
verified
1 Parent(s): 4ae4f20

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +45 -245
app.py CHANGED
@@ -175,11 +175,9 @@ def extract_keywords(text: str) -> list[str]:
175
 
176
  def search_pexels_videos(query: str, count: int = 3) -> list[dict]:
177
  if not PEXELS_API_KEY:
178
- logger.warning("PEXELS_API_KEY no est谩 definida")
179
  return []
180
 
181
  try:
182
- logger.info(f"Buscando videos en Pexels para: {query}")
183
  response = requests.get(
184
  "https://api.pexels.com/videos/search",
185
  headers={"Authorization": PEXELS_API_KEY},
@@ -187,10 +185,7 @@ def search_pexels_videos(query: str, count: int = 3) -> list[dict]:
187
  timeout=20
188
  )
189
  response.raise_for_status()
190
- data = response.json()
191
- videos = data.get("videos", [])
192
- logger.info(f"Se encontraron {len(videos)} videos para '{query}'")
193
- return videos
194
  except Exception as e:
195
  logger.error(f"Error buscando videos en Pexels: {e}")
196
  return []
@@ -200,7 +195,6 @@ def download_video(url: str, folder: str) -> str | None:
200
  filename = f"{uuid.uuid4().hex}.mp4"
201
  filepath = os.path.join(folder, filename)
202
 
203
- logger.info(f"Descargando video desde: {url}")
204
  with requests.get(url, stream=True, timeout=60) as response:
205
  response.raise_for_status()
206
  with open(filepath, "wb") as f:
@@ -208,7 +202,6 @@ def download_video(url: str, folder: str) -> str | None:
208
  f.write(chunk)
209
 
210
  if os.path.exists(filepath) and os.path.getsize(filepath) > 1000:
211
- logger.info(f"Video descargado correctamente: {filepath} ({os.path.getsize(filepath)} bytes)")
212
  return filepath
213
  else:
214
  logger.error(f"Archivo descargado inv谩lido: {filepath}")
@@ -298,48 +291,6 @@ def create_video(script_text: str, generate_script: bool, music_path: str | None
298
  logger.error(f"Error normalizando clip: {e}")
299
  return None
300
 
301
- def validate_clip(clip, path="unknown"):
302
- """Funci贸n para validar que un clip sea usable - versi贸n menos estricta"""
303
- if clip is None:
304
- logger.error(f"Clip es None: {path}")
305
- return False
306
-
307
- try:
308
- # Verificar duraci贸n
309
- if clip.duration <= 0:
310
- logger.error(f"Clip con duraci贸n inv谩lida: {path}")
311
- return False
312
-
313
- # Verificar que podemos obtener un frame - MENOS ESTRICTO
314
- try:
315
- test_frame = clip.get_frame(0)
316
- if test_frame is None:
317
- logger.error(f"No se pudo obtener frame del clip: {path}")
318
- return False
319
- except Exception as frame_error:
320
- logger.warning(f"Error al obtener frame de {path}: {frame_error}")
321
- # En lugar de rechazar, intentamos continuar
322
- return True
323
-
324
- return True
325
- except Exception as e:
326
- logger.error(f"Error validando clip {path}: {e}")
327
- return False
328
-
329
- def create_fallback_video(duration):
330
- """Crea un video de respaldo"""
331
- try:
332
- fallback = ColorClip(
333
- size=TARGET_RESOLUTION,
334
- color=(0, 0, 0),
335
- duration=duration
336
- )
337
- fallback.fps = TARGET_FPS
338
- return fallback
339
- except Exception as e:
340
- logger.error(f"Error creando video de respaldo: {e}")
341
- return None
342
-
343
  try:
344
  # Paso 1: Generar o usar gui贸n
345
  update_task_progress(task_id, "Paso 1/7: Preparando gui贸n...")
@@ -372,239 +323,87 @@ def create_video(script_text: str, generate_script: bool, music_path: str | None
372
  video_paths = []
373
  keywords = extract_keywords(script)
374
 
375
- logger.info(f"Palabras clave extra铆das: {keywords}")
376
-
377
  for i, keyword in enumerate(keywords[:3]):
378
  update_task_progress(task_id, f"Paso 3/7: Buscando videos para '{keyword}' ({i+1}/{len(keywords[:3])})")
379
 
380
  videos = search_pexels_videos(keyword, 2)
381
- logger.info(f"Se encontraron {len(videos)} videos para '{keyword}'")
382
-
383
  for video_data in videos:
384
  if len(video_paths) >= 6:
385
  break
386
 
387
  video_files = video_data.get("video_files", [])
388
  if video_files:
389
- # Tomar el video de mejor calidad
390
  best_file = max(video_files, key=lambda f: f.get("width", 0))
391
  video_url = best_file.get("link")
392
- video_quality = f"{best_file.get('width', 0)}x{best_file.get('height', 0)}"
393
-
394
- logger.info(f"Intentando descargar video: {video_quality} - {video_url}")
395
 
396
  if video_url:
397
  downloaded_path = download_video(video_url, temp_dir)
398
  if downloaded_path:
399
  video_paths.append(downloaded_path)
400
- logger.info(f"Video descargado exitosamente: {downloaded_path}")
401
-
402
- logger.info(f"Total de videos descargados: {len(video_paths)}")
403
 
404
  if not video_paths:
405
- logger.warning("No se pudieron descargar videos de Pexels, creando video de respaldo...")
406
- base_video = create_fallback_video(video_duration)
407
- if base_video is None:
408
- raise RuntimeError("No se pudo crear video de respaldo")
409
- else:
410
- # Paso 4: Procesar videos
411
- update_task_progress(task_id, f"Paso 4/7: Procesando {len(video_paths)} videos...")
412
- video_clips = []
413
-
414
- for i, path in enumerate(video_paths):
415
- clip = None
416
- try:
417
- logger.info(f"Procesando video {i+1}/{len(video_paths)}: {path}")
418
-
419
- # Verificar que el archivo exista y tenga tama帽o
420
- if not os.path.exists(path) or os.path.getsize(path) < 1024:
421
- logger.error(f"Archivo inv谩lido: {path}")
422
- continue
423
-
424
- # Cargar el video
425
- clip = VideoFileClip(path)
426
- if clip is None:
427
- logger.error(f"No se pudo cargar el video: {path}")
428
- continue
429
-
430
- logger.info(f"Video cargado: {path} - Duraci贸n: {clip.duration}s - Tama帽o: {clip.size}")
431
-
432
- # Validar el clip original - MENOS ESTRICTO
433
- if not validate_clip(clip, path):
434
- logger.warning(f"Clip inv谩lido, pero intentando usarlo igualmente: {path}")
435
- # No cerramos el clip, lo intentamos usar igualmente
436
-
437
- # Recortar el video
438
- duration = min(8, clip.duration)
439
- processed_clip = clip.subclip(0, duration)
440
-
441
- if processed_clip is None:
442
- logger.error(f"Error al recortar video: {path}")
443
- clip.close()
444
- continue
445
-
446
- # Validar el clip recortado - MENOS ESTRICTO
447
- if not validate_clip(processed_clip, f"{path} (recortado)"):
448
- logger.warning(f"Clip recortado inv谩lido, pero intentando usarlo igualmente: {path}")
449
- # No cerramos el clip, lo intentamos usar igualmente
450
-
451
- # Normalizar
452
- processed_clip = normalize_clip(processed_clip)
453
-
454
- if processed_clip is not None:
455
- # Validaci贸n final del clip procesado - MENOS ESTRICTO
456
- if validate_clip(processed_clip, f"{path} (normalizado)"):
457
- video_clips.append(processed_clip)
458
- logger.info(f"Video procesado exitosamente: {path}")
459
- else:
460
- logger.warning(f"Clip normalizado inv谩lido, pero intentando usarlo igualmente: {path}")
461
- video_clips.append(processed_clip) # Lo usamos igualmente
462
- else:
463
- logger.error(f"Error normalizando video: {path}")
464
- clip.close()
465
-
466
- except Exception as e:
467
- logger.error(f"Error procesando video {path}: {e}")
468
- finally:
469
- if clip is not None:
470
- clip.close()
471
-
472
- logger.info(f"Total de videos procesados exitosamente: {len(video_clips)}")
473
-
474
- # Verificar si tenemos clips v谩lidos
475
- if not video_clips:
476
- logger.warning("No se procesaron videos v谩lidos, creando video de respaldo...")
477
- base_video = create_fallback_video(video_duration)
478
- if base_video is None:
479
- raise RuntimeError("No se pudo crear video de respaldo")
480
- else:
481
- # Verificar que todos los clips sean v谩lidos antes de concatenar
482
- valid_clips = []
483
- for i, clip in enumerate(video_clips):
484
- try:
485
- # Verificaci贸n final de cada clip - MENOS ESTRICTA
486
- if validate_clip(clip, f"clip_{i}"):
487
- valid_clips.append(clip)
488
- else:
489
- logger.warning(f"Clip inv谩lido en posici贸n {i}, pero intentando usarlo igualmente")
490
- valid_clips.append(clip) # Lo usamos igualmente
491
- except Exception as e:
492
- logger.error(f"Clip inv谩lido en posici贸n {i}: {e}")
493
- if clip is not None:
494
- clip.close()
495
-
496
- if not valid_clips:
497
- logger.warning("Todos los clips son inv谩lidos, creando video de respaldo...")
498
- base_video = create_fallback_video(video_duration)
499
- if base_video is None:
500
- raise RuntimeError("No se pudo crear video de respaldo")
501
- else:
502
- # Concatenar solo clips v谩lidos
503
- update_task_progress(task_id, "Paso 4/7: Concatenando videos v谩lidos...")
504
- try:
505
- base_video = concatenate_videoclips(valid_clips, method="chain")
506
-
507
- # Verificar que la concatenaci贸n funcion贸
508
- if base_video is None:
509
- raise RuntimeError("La concatenaci贸n devolvi贸 None")
510
-
511
- # Validar el video concatenado - MENOS ESTRICTO
512
- if not validate_clip(base_video, "video_concatenado"):
513
- logger.warning("Video concatenado inv谩lido, pero intentando usarlo igualmente")
514
-
515
- logger.info(f"Video concatenado exitosamente. Duraci贸n: {base_video.duration}s")
516
-
517
- except Exception as e:
518
- logger.error(f"Error concatenando videos: {e}")
519
- # Liberar clips
520
- for clip in valid_clips:
521
- if clip is not None:
522
- clip.close()
523
- # Crear video de respaldo
524
- base_video = create_fallback_video(video_duration)
525
- if base_video is None:
526
- raise RuntimeError("No se pudo crear video de respaldo")
527
 
528
- # Extender video si es m谩s corto que el audio
529
- if base_video.duration < video_duration:
530
- update_task_progress(task_id, "Paso 4/7: Extendiendo video...")
 
 
 
531
  try:
532
- fade_duration = 0.5
533
- loops_needed = math.ceil(video_duration / base_video.duration)
 
 
534
 
535
- looped_clips = [base_video]
536
- for _ in range(loops_needed - 1):
537
- fade_in_clip = base_video.crossfadein(fade_duration)
538
- if fade_in_clip is not None:
539
- looped_clips.append(fade_in_clip)
540
- looped_clips.append(base_video)
541
 
542
- # Guardar referencia al video original para liberarlo despu茅s
543
- original_video = base_video
544
- base_video = concatenate_videoclips(looped_clips)
545
-
546
- # Verificar el video extendido
547
- if base_video is None or not validate_clip(base_video, "video_extendido"):
548
- logger.error("Error al extender video, usando original")
549
- base_video = original_video
550
- else:
551
- # Liberar el video original
552
- original_video.close()
553
 
 
 
 
554
  except Exception as e:
555
- logger.error(f"Error extendiendo video: {e}")
556
- # No hacemos nada, seguimos con el video original
 
 
 
 
 
 
 
 
 
 
 
 
 
557
 
558
  # Asegurar duraci贸n exacta
559
- try:
560
- original_video = base_video
561
- base_video = base_video.subclip(0, video_duration)
562
-
563
- if base_video is None or not validate_clip(base_video, "video_recortado"):
564
- logger.error("Error al recortar video final, usando original")
565
- base_video = original_video
566
- else:
567
- original_video.close()
568
-
569
- except Exception as e:
570
- logger.error(f"Error al recortar video final: {e}")
571
- # No hacemos nada, seguimos con el video original
572
 
573
  # Paso 5: Componer audio final
574
  update_task_progress(task_id, "Paso 5/7: Componiendo audio...")
575
- final_audio = voice_clip
576
-
577
  if music_path and os.path.exists(music_path):
578
- music_clip = None
579
  try:
580
  music_clip = AudioFileClip(music_path)
581
- if music_clip is not None:
582
- music_clip = loop_audio_to_duration(music_clip, video_duration)
583
- if music_clip is not None:
584
- music_clip = music_clip.volumex(0.2)
585
- final_audio = CompositeAudioClip([music_clip, voice_clip])
586
  except Exception as e:
587
  logger.error(f"Error con m煤sica: {e}")
588
- finally:
589
- if music_clip is not None:
590
- music_clip.close()
591
 
592
  # Paso 6: Agregar subt铆tulos
593
  update_task_progress(task_id, "Paso 6/7: Agregando subt铆tulos...")
594
  subtitle_clips = create_subtitle_clips(script, base_video.w, base_video.h, video_duration)
595
  if subtitle_clips:
596
- try:
597
- original_video = base_video
598
- base_video = CompositeVideoClip([base_video] + subtitle_clips)
599
-
600
- if base_video is None or not validate_clip(base_video, "video_con_subtitulos"):
601
- logger.error("Error al agregar subt铆tulos, usando video original")
602
- base_video = original_video
603
- else:
604
- original_video.close()
605
-
606
- except Exception as e:
607
- logger.error(f"Error creando video con subt铆tulos: {e}")
608
 
609
  # Paso 7: Renderizar video final
610
  update_task_progress(task_id, "Paso 7/7: Renderizando video final...")
@@ -625,11 +424,12 @@ def create_video(script_text: str, generate_script: bool, music_path: str | None
625
 
626
  # Limpiar clips
627
  voice_clip.close()
 
 
628
  base_video.close()
629
  final_video.close()
630
  for clip in video_clips:
631
- if clip is not None:
632
- clip.close()
633
 
634
  return output_path
635
 
 
175
 
176
  def search_pexels_videos(query: str, count: int = 3) -> list[dict]:
177
  if not PEXELS_API_KEY:
 
178
  return []
179
 
180
  try:
 
181
  response = requests.get(
182
  "https://api.pexels.com/videos/search",
183
  headers={"Authorization": PEXELS_API_KEY},
 
185
  timeout=20
186
  )
187
  response.raise_for_status()
188
+ return response.json().get("videos", [])
 
 
 
189
  except Exception as e:
190
  logger.error(f"Error buscando videos en Pexels: {e}")
191
  return []
 
195
  filename = f"{uuid.uuid4().hex}.mp4"
196
  filepath = os.path.join(folder, filename)
197
 
 
198
  with requests.get(url, stream=True, timeout=60) as response:
199
  response.raise_for_status()
200
  with open(filepath, "wb") as f:
 
202
  f.write(chunk)
203
 
204
  if os.path.exists(filepath) and os.path.getsize(filepath) > 1000:
 
205
  return filepath
206
  else:
207
  logger.error(f"Archivo descargado inv谩lido: {filepath}")
 
291
  logger.error(f"Error normalizando clip: {e}")
292
  return None
293
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294
  try:
295
  # Paso 1: Generar o usar gui贸n
296
  update_task_progress(task_id, "Paso 1/7: Preparando gui贸n...")
 
323
  video_paths = []
324
  keywords = extract_keywords(script)
325
 
 
 
326
  for i, keyword in enumerate(keywords[:3]):
327
  update_task_progress(task_id, f"Paso 3/7: Buscando videos para '{keyword}' ({i+1}/{len(keywords[:3])})")
328
 
329
  videos = search_pexels_videos(keyword, 2)
 
 
330
  for video_data in videos:
331
  if len(video_paths) >= 6:
332
  break
333
 
334
  video_files = video_data.get("video_files", [])
335
  if video_files:
 
336
  best_file = max(video_files, key=lambda f: f.get("width", 0))
337
  video_url = best_file.get("link")
 
 
 
338
 
339
  if video_url:
340
  downloaded_path = download_video(video_url, temp_dir)
341
  if downloaded_path:
342
  video_paths.append(downloaded_path)
 
 
 
343
 
344
  if not video_paths:
345
+ raise RuntimeError("No se pudieron descargar videos de Pexels")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
346
 
347
+ # Paso 4: Procesar videos - VERSI脫N SIMPLIFICADA
348
+ update_task_progress(task_id, f"Paso 4/7: Procesando {len(video_paths)} videos...")
349
+ video_clips = []
350
+
351
+ for path in video_paths:
352
+ clip = None
353
  try:
354
+ # Cargar el video
355
+ clip = VideoFileClip(path)
356
+ if clip is None:
357
+ continue
358
 
359
+ # Tomar m谩ximo 8 segundos de cada clip
360
+ duration = min(8, clip.duration)
361
+ processed_clip = clip.subclip(0, duration)
 
 
 
362
 
363
+ # Normalizar el clip
364
+ processed_clip = normalize_clip(processed_clip)
 
 
 
 
 
 
 
 
 
365
 
366
+ if processed_clip is not None:
367
+ video_clips.append(processed_clip)
368
+
369
  except Exception as e:
370
+ logger.error(f"Error procesando video {path}: {e}")
371
+ finally:
372
+ if clip is not None:
373
+ clip.close()
374
+
375
+ if not video_clips:
376
+ raise RuntimeError("No se pudieron procesar los videos")
377
+
378
+ # Concatenar videos
379
+ base_video = concatenate_videoclips(video_clips, method="chain")
380
+
381
+ # Extender video si es m谩s corto que el audio
382
+ if base_video.duration < video_duration:
383
+ loops_needed = math.ceil(video_duration / base_video.duration)
384
+ base_video = concatenate_videoclips([base_video] * loops_needed)
385
 
386
  # Asegurar duraci贸n exacta
387
+ base_video = base_video.subclip(0, video_duration)
 
 
 
 
 
 
 
 
 
 
 
 
388
 
389
  # Paso 5: Componer audio final
390
  update_task_progress(task_id, "Paso 5/7: Componiendo audio...")
 
 
391
  if music_path and os.path.exists(music_path):
 
392
  try:
393
  music_clip = AudioFileClip(music_path)
394
+ music_clip = loop_audio_to_duration(music_clip, video_duration).volumex(0.2)
395
+ final_audio = CompositeAudioClip([music_clip, voice_clip])
 
 
 
396
  except Exception as e:
397
  logger.error(f"Error con m煤sica: {e}")
398
+ final_audio = voice_clip
399
+ else:
400
+ final_audio = voice_clip
401
 
402
  # Paso 6: Agregar subt铆tulos
403
  update_task_progress(task_id, "Paso 6/7: Agregando subt铆tulos...")
404
  subtitle_clips = create_subtitle_clips(script, base_video.w, base_video.h, video_duration)
405
  if subtitle_clips:
406
+ base_video = CompositeVideoClip([base_video] + subtitle_clips)
 
 
 
 
 
 
 
 
 
 
 
407
 
408
  # Paso 7: Renderizar video final
409
  update_task_progress(task_id, "Paso 7/7: Renderizando video final...")
 
424
 
425
  # Limpiar clips
426
  voice_clip.close()
427
+ if 'music_clip' in locals():
428
+ music_clip.close()
429
  base_video.close()
430
  final_video.close()
431
  for clip in video_clips:
432
+ clip.close()
 
433
 
434
  return output_path
435