Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -36,7 +36,6 @@ os.environ["GRADIO_SERVER_TIMEOUT"] = "3800" # 30 minutos en segundos
|
|
| 36 |
# ------------------- Configuraci贸n & Globals -------------------
|
| 37 |
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
|
| 38 |
logger = logging.getLogger(__name__)
|
| 39 |
-
|
| 40 |
PEXELS_API_KEY = os.getenv("PEXELS_API_KEY")
|
| 41 |
if not PEXELS_API_KEY:
|
| 42 |
logger.warning("PEXELS_API_KEY no definido. Los videos no funcionar谩n.")
|
|
@@ -271,8 +270,18 @@ def loop_audio_to_duration(audio_clip: AudioFileClip, target_duration: float) ->
|
|
| 271 |
return audio_clip
|
| 272 |
|
| 273 |
def create_video(script_text: str, generate_script: bool, music_path: str | None, task_id: str) -> str:
|
| 274 |
-
"""Funci贸n principal para crear el video"""
|
| 275 |
temp_dir = tempfile.mkdtemp()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
|
| 277 |
try:
|
| 278 |
# Paso 1: Generar o usar gui贸n
|
|
@@ -330,14 +339,20 @@ def create_video(script_text: str, generate_script: bool, music_path: str | None
|
|
| 330 |
video_clips = []
|
| 331 |
|
| 332 |
for path in video_paths:
|
|
|
|
| 333 |
try:
|
| 334 |
clip = VideoFileClip(path)
|
| 335 |
# Tomar m谩ximo 8 segundos de cada clip
|
| 336 |
duration = min(8, clip.duration)
|
| 337 |
-
|
|
|
|
|
|
|
|
|
|
| 338 |
except Exception as e:
|
| 339 |
logger.error(f"Error procesando video {path}: {e}")
|
| 340 |
-
|
|
|
|
|
|
|
| 341 |
|
| 342 |
if not video_clips:
|
| 343 |
raise RuntimeError("No se pudieron procesar los videos")
|
|
@@ -345,12 +360,22 @@ def create_video(script_text: str, generate_script: bool, music_path: str | None
|
|
| 345 |
# Concatenar videos
|
| 346 |
base_video = concatenate_videoclips(video_clips, method="chain")
|
| 347 |
|
| 348 |
-
# Extender video si es m谩s corto que el audio
|
| 349 |
if base_video.duration < video_duration:
|
|
|
|
| 350 |
loops_needed = math.ceil(video_duration / base_video.duration)
|
| 351 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 352 |
|
| 353 |
-
#
|
| 354 |
base_video = base_video.subclip(0, video_duration)
|
| 355 |
|
| 356 |
# Paso 5: Componer audio final
|
|
@@ -366,6 +391,11 @@ def create_video(script_text: str, generate_script: bool, music_path: str | None
|
|
| 366 |
else:
|
| 367 |
final_audio = voice_clip
|
| 368 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 369 |
|
| 370 |
# Paso 7: Renderizar video final
|
| 371 |
update_task_progress(task_id, "Paso 7/7: Renderizando video final...")
|
|
@@ -374,10 +404,12 @@ def create_video(script_text: str, generate_script: bool, music_path: str | None
|
|
| 374 |
output_path = os.path.join(RESULTS_DIR, f"video_{task_id}.mp4")
|
| 375 |
final_video.write_videofile(
|
| 376 |
output_path,
|
| 377 |
-
fps=
|
| 378 |
codec="libx264",
|
| 379 |
-
audio_codec="
|
| 380 |
-
|
|
|
|
|
|
|
| 381 |
logger=None,
|
| 382 |
verbose=False
|
| 383 |
)
|
|
|
|
| 36 |
# ------------------- Configuraci贸n & Globals -------------------
|
| 37 |
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
|
| 38 |
logger = logging.getLogger(__name__)
|
|
|
|
| 39 |
PEXELS_API_KEY = os.getenv("PEXELS_API_KEY")
|
| 40 |
if not PEXELS_API_KEY:
|
| 41 |
logger.warning("PEXELS_API_KEY no definido. Los videos no funcionar谩n.")
|
|
|
|
| 270 |
return audio_clip
|
| 271 |
|
| 272 |
def create_video(script_text: str, generate_script: bool, music_path: str | None, task_id: str) -> str:
|
|
|
|
| 273 |
temp_dir = tempfile.mkdtemp()
|
| 274 |
+
# Constantes para normalizaci贸n
|
| 275 |
+
TARGET_FPS = 24
|
| 276 |
+
TARGET_RESOLUTION = (1280, 720) # (ancho, alto)
|
| 277 |
+
|
| 278 |
+
def normalize_clip(clip):
|
| 279 |
+
"""Normaliza un clip de video a resoluci贸n y FPS est谩ndar"""
|
| 280 |
+
if clip.size != TARGET_RESOLUTION:
|
| 281 |
+
clip = clip.resize(TARGET_RESOLUTION)
|
| 282 |
+
if clip.fps != TARGET_FPS:
|
| 283 |
+
clip = clip.set_fps(TARGET_FPS)
|
| 284 |
+
return clip
|
| 285 |
|
| 286 |
try:
|
| 287 |
# Paso 1: Generar o usar gui贸n
|
|
|
|
| 339 |
video_clips = []
|
| 340 |
|
| 341 |
for path in video_paths:
|
| 342 |
+
clip = None
|
| 343 |
try:
|
| 344 |
clip = VideoFileClip(path)
|
| 345 |
# Tomar m谩ximo 8 segundos de cada clip
|
| 346 |
duration = min(8, clip.duration)
|
| 347 |
+
processed_clip = clip.subclip(0, duration)
|
| 348 |
+
# Normalizar el clip (resoluci贸n y FPS)
|
| 349 |
+
processed_clip = normalize_clip(processed_clip)
|
| 350 |
+
video_clips.append(processed_clip)
|
| 351 |
except Exception as e:
|
| 352 |
logger.error(f"Error procesando video {path}: {e}")
|
| 353 |
+
finally:
|
| 354 |
+
if clip is not None:
|
| 355 |
+
clip.close()
|
| 356 |
|
| 357 |
if not video_clips:
|
| 358 |
raise RuntimeError("No se pudieron procesar los videos")
|
|
|
|
| 360 |
# Concatenar videos
|
| 361 |
base_video = concatenate_videoclips(video_clips, method="chain")
|
| 362 |
|
| 363 |
+
# Extender video si es m谩s corto que el audio con transiciones suaves
|
| 364 |
if base_video.duration < video_duration:
|
| 365 |
+
fade_duration = 0.5 # segundos de fundido
|
| 366 |
loops_needed = math.ceil(video_duration / base_video.duration)
|
| 367 |
+
|
| 368 |
+
# Crear una lista de clips para el loop
|
| 369 |
+
looped_clips = [base_video]
|
| 370 |
+
for _ in range(loops_needed - 1):
|
| 371 |
+
# Crear un clip con fundido de entrada para la transici贸n
|
| 372 |
+
fade_in_clip = base_video.crossfadein(fade_duration)
|
| 373 |
+
looped_clips.append(fade_in_clip)
|
| 374 |
+
looped_clips.append(base_video)
|
| 375 |
+
|
| 376 |
+
base_video = concatenate_videoclips(looped_clips)
|
| 377 |
|
| 378 |
+
# Asegurar que el video tenga la duraci贸n exacta del audio
|
| 379 |
base_video = base_video.subclip(0, video_duration)
|
| 380 |
|
| 381 |
# Paso 5: Componer audio final
|
|
|
|
| 391 |
else:
|
| 392 |
final_audio = voice_clip
|
| 393 |
|
| 394 |
+
# Paso 6: Agregar subt铆tulos
|
| 395 |
+
update_task_progress(task_id, "Paso 6/7: Agregando subt铆tulos...")
|
| 396 |
+
subtitle_clips = create_subtitle_clips(script, base_video.w, base_video.h, video_duration)
|
| 397 |
+
if subtitle_clips:
|
| 398 |
+
base_video = CompositeVideoClip([base_video] + subtitle_clips)
|
| 399 |
|
| 400 |
# Paso 7: Renderizar video final
|
| 401 |
update_task_progress(task_id, "Paso 7/7: Renderizando video final...")
|
|
|
|
| 404 |
output_path = os.path.join(RESULTS_DIR, f"video_{task_id}.mp4")
|
| 405 |
final_video.write_videofile(
|
| 406 |
output_path,
|
| 407 |
+
fps=TARGET_FPS,
|
| 408 |
codec="libx264",
|
| 409 |
+
audio_codec="aac", # Mejor calidad de audio
|
| 410 |
+
bitrate="8000k", # Controlar calidad de video
|
| 411 |
+
threads=4, # Mejor uso de CPU
|
| 412 |
+
preset="slow", # Mejor compresi贸n
|
| 413 |
logger=None,
|
| 414 |
verbose=False
|
| 415 |
)
|