Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -15,8 +15,7 @@ from moviepy.editor import (
|
|
15 |
TextClip,
|
16 |
CompositeVideoClip,
|
17 |
VideoClip,
|
18 |
-
ColorClip
|
19 |
-
ImageClip
|
20 |
)
|
21 |
import numpy as np
|
22 |
import json
|
@@ -292,72 +291,6 @@ def create_video(script_text: str, generate_script: bool, music_path: str | None
|
|
292 |
logger.error(f"Error normalizando clip: {e}")
|
293 |
return None
|
294 |
|
295 |
-
def create_visual_video(script: str, duration: float):
|
296 |
-
"""Crea un video visual con texto cuando no hay videos de Pexels"""
|
297 |
-
try:
|
298 |
-
logger.info("Creando video visual con texto...")
|
299 |
-
|
300 |
-
# Crear clips de texto para cada frase
|
301 |
-
sentences = [s.strip() for s in re.split(r"[.!?驴隆]", script) if s.strip()]
|
302 |
-
if not sentences:
|
303 |
-
sentences = [script]
|
304 |
-
|
305 |
-
clips = []
|
306 |
-
sentence_duration = duration / len(sentences)
|
307 |
-
|
308 |
-
for i, sentence in enumerate(sentences):
|
309 |
-
try:
|
310 |
-
# Crear fondo con color cambiante
|
311 |
-
color_intensity = int(255 * (i / len(sentences)))
|
312 |
-
bg_color = (color_intensity, 50, 150 - color_intensity//2)
|
313 |
-
|
314 |
-
bg_clip = ColorClip(
|
315 |
-
size=TARGET_RESOLUTION,
|
316 |
-
color=bg_color,
|
317 |
-
duration=sentence_duration
|
318 |
-
)
|
319 |
-
bg_clip.fps = TARGET_FPS
|
320 |
-
|
321 |
-
# Crear texto
|
322 |
-
txt_clip = TextClip(
|
323 |
-
sentence,
|
324 |
-
fontsize=60,
|
325 |
-
color="white",
|
326 |
-
stroke_color="black",
|
327 |
-
stroke_width=3,
|
328 |
-
method="caption",
|
329 |
-
size=(int(TARGET_RESOLUTION[0] * 0.9), None),
|
330 |
-
font="Arial-Bold",
|
331 |
-
align="center"
|
332 |
-
).set_position("center")
|
333 |
-
|
334 |
-
# Combinar fondo y texto
|
335 |
-
composite = CompositeVideoClip([bg_clip, txt_clip])
|
336 |
-
clips.append(composite)
|
337 |
-
|
338 |
-
except Exception as e:
|
339 |
-
logger.error(f"Error creando clip visual para '{sentence}': {e}")
|
340 |
-
continue
|
341 |
-
|
342 |
-
if clips:
|
343 |
-
return concatenate_videoclips(clips)
|
344 |
-
else:
|
345 |
-
# Si falla, crear un video simple con color
|
346 |
-
return ColorClip(
|
347 |
-
size=TARGET_RESOLUTION,
|
348 |
-
color=(50, 50, 150),
|
349 |
-
duration=duration
|
350 |
-
)
|
351 |
-
|
352 |
-
except Exception as e:
|
353 |
-
logger.error(f"Error creando video visual: {e}")
|
354 |
-
# 脷ltimo recurso: video de color s贸lido
|
355 |
-
return ColorClip(
|
356 |
-
size=TARGET_RESOLUTION,
|
357 |
-
color=(50, 50, 150),
|
358 |
-
duration=duration
|
359 |
-
)
|
360 |
-
|
361 |
try:
|
362 |
# Paso 1: Generar o usar gui贸n
|
363 |
update_task_progress(task_id, "Paso 1/7: Preparando gui贸n...")
|
@@ -390,14 +323,10 @@ def create_video(script_text: str, generate_script: bool, music_path: str | None
|
|
390 |
video_paths = []
|
391 |
keywords = extract_keywords(script)
|
392 |
|
393 |
-
logger.info(f"Palabras clave: {keywords}")
|
394 |
-
|
395 |
for i, keyword in enumerate(keywords[:3]):
|
396 |
update_task_progress(task_id, f"Paso 3/7: Buscando videos para '{keyword}' ({i+1}/{len(keywords[:3])})")
|
397 |
|
398 |
videos = search_pexels_videos(keyword, 2)
|
399 |
-
logger.info(f"Encontrados {len(videos)} videos para '{keyword}'")
|
400 |
-
|
401 |
for video_data in videos:
|
402 |
if len(video_paths) >= 6:
|
403 |
break
|
@@ -406,93 +335,51 @@ def create_video(script_text: str, generate_script: bool, music_path: str | None
|
|
406 |
if video_files:
|
407 |
best_file = max(video_files, key=lambda f: f.get("width", 0))
|
408 |
video_url = best_file.get("link")
|
409 |
-
video_quality = f"{best_file.get('width', 0)}x{best_file.get('height', 0)}"
|
410 |
-
|
411 |
-
logger.info(f"Intentando descargar video {video_quality}: {video_url}")
|
412 |
|
413 |
if video_url:
|
414 |
downloaded_path = download_video(video_url, temp_dir)
|
415 |
if downloaded_path:
|
416 |
video_paths.append(downloaded_path)
|
417 |
-
logger.info(f"Video descargado: {downloaded_path}")
|
418 |
|
419 |
-
|
|
|
420 |
|
421 |
-
# Paso 4: Procesar videos
|
422 |
update_task_progress(task_id, f"Paso 4/7: Procesando {len(video_paths)} videos...")
|
423 |
video_clips = []
|
424 |
|
425 |
-
|
426 |
-
|
427 |
-
clip = None
|
428 |
-
try:
|
429 |
-
logger.info(f"Procesando video {i+1}/{len(video_paths)}: {path}")
|
430 |
-
|
431 |
-
# Verificar archivo
|
432 |
-
if not os.path.exists(path):
|
433 |
-
logger.error(f"Archivo no existe: {path}")
|
434 |
-
continue
|
435 |
-
|
436 |
-
file_size = os.path.getsize(path)
|
437 |
-
logger.info(f"Tama帽o del archivo: {file_size} bytes")
|
438 |
-
|
439 |
-
if file_size < 1000:
|
440 |
-
logger.error(f"Archivo demasiado peque帽o: {path}")
|
441 |
-
continue
|
442 |
-
|
443 |
-
# Intentar cargar el video
|
444 |
-
clip = VideoFileClip(path)
|
445 |
-
if clip is None:
|
446 |
-
logger.error(f"No se pudo cargar el video: {path}")
|
447 |
-
continue
|
448 |
-
|
449 |
-
logger.info(f"Video cargado: duraci贸n={clip.duration}s, tama帽o={clip.size}")
|
450 |
-
|
451 |
-
# Tomar m谩ximo 8 segundos
|
452 |
-
duration = min(8, clip.duration)
|
453 |
-
processed_clip = clip.subclip(0, duration)
|
454 |
-
|
455 |
-
if processed_clip is None:
|
456 |
-
logger.error(f"No se pudo recortar el video: {path}")
|
457 |
-
clip.close()
|
458 |
-
continue
|
459 |
-
|
460 |
-
# Normalizar
|
461 |
-
processed_clip = normalize_clip(processed_clip)
|
462 |
-
|
463 |
-
if processed_clip is not None:
|
464 |
-
video_clips.append(processed_clip)
|
465 |
-
logger.info(f"Video {i+1} procesado exitosamente")
|
466 |
-
else:
|
467 |
-
logger.error(f"No se pudo normalizar el video {i+1}")
|
468 |
-
processed_clip.close()
|
469 |
-
clip.close()
|
470 |
-
|
471 |
-
except Exception as e:
|
472 |
-
logger.error(f"Error procesando video {i+1}: {str(e)}")
|
473 |
-
finally:
|
474 |
-
if clip is not None:
|
475 |
-
clip.close()
|
476 |
-
|
477 |
-
logger.info(f"Videos procesados exitosamente: {len(video_clips)} de {len(video_paths)}")
|
478 |
-
|
479 |
-
# Decidir qu茅 video usar
|
480 |
-
if video_clips:
|
481 |
-
# Usar videos de Pexels
|
482 |
try:
|
483 |
-
|
484 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
485 |
except Exception as e:
|
486 |
-
logger.error(f"Error
|
487 |
-
|
488 |
-
|
489 |
-
|
490 |
-
|
491 |
-
|
|
|
|
|
|
|
|
|
492 |
|
493 |
# Extender video si es m谩s corto que el audio
|
494 |
if base_video.duration < video_duration:
|
495 |
-
logger.info(f"Extendiendo video de {base_video.duration}s a {video_duration}s")
|
496 |
loops_needed = math.ceil(video_duration / base_video.duration)
|
497 |
base_video = concatenate_videoclips([base_video] * loops_needed)
|
498 |
|
@@ -542,8 +429,7 @@ def create_video(script_text: str, generate_script: bool, music_path: str | None
|
|
542 |
base_video.close()
|
543 |
final_video.close()
|
544 |
for clip in video_clips:
|
545 |
-
|
546 |
-
clip.close()
|
547 |
|
548 |
return output_path
|
549 |
|
|
|
15 |
TextClip,
|
16 |
CompositeVideoClip,
|
17 |
VideoClip,
|
18 |
+
ColorClip
|
|
|
19 |
)
|
20 |
import numpy as np
|
21 |
import json
|
|
|
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
|
|
|
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 - SOLO CORREGIR EL ERROR DE NoneType
|
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 |
|
|
|
429 |
base_video.close()
|
430 |
final_video.close()
|
431 |
for clip in video_clips:
|
432 |
+
clip.close()
|
|
|
433 |
|
434 |
return output_path
|
435 |
|