gnosticdev commited on
Commit
cc81e3b
verified
1 Parent(s): 91bd4f8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +135 -62
app.py CHANGED
@@ -243,22 +243,28 @@ def create_subtitle_clips(script: str, video_width: int, video_height: int, dura
243
  if sentence_duration < 0.5:
244
  continue
245
 
246
- txt_clip = (
247
- TextClip(
248
- sentence,
249
- fontsize=max(20, int(video_height * 0.05)),
250
- color="white",
251
- stroke_color="black",
252
- stroke_width=2,
253
- method="caption",
254
- size=(int(video_width * 0.9), None),
255
- font="Arial-Bold"
 
 
 
 
 
256
  )
257
- .set_start(current_time)
258
- .set_duration(sentence_duration)
259
- .set_position(("center", "bottom"))
260
- )
261
- clips.append(txt_clip)
 
262
  current_time += sentence_duration
263
 
264
  return clips
@@ -268,6 +274,8 @@ def create_subtitle_clips(script: str, video_width: int, video_height: int, dura
268
 
269
  def loop_audio_to_duration(audio_clip: AudioFileClip, target_duration: float) -> AudioFileClip:
270
  """Hace loop del audio hasta alcanzar la duraci贸n objetivo"""
 
 
271
  try:
272
  if audio_clip.duration >= target_duration:
273
  return audio_clip.subclip(0, target_duration)
@@ -287,11 +295,17 @@ def create_video(script_text: str, generate_script: bool, music_path: str | None
287
 
288
  def normalize_clip(clip):
289
  """Normaliza un clip de video a resoluci贸n y FPS est谩ndar"""
290
- if clip.size != TARGET_RESOLUTION:
291
- clip = clip.resize(TARGET_RESOLUTION)
292
- if clip.fps != TARGET_FPS:
293
- clip = clip.set_fps(TARGET_FPS)
294
- return clip
 
 
 
 
 
 
295
 
296
  try:
297
  # Paso 1: Generar o usar gui贸n
@@ -311,11 +325,20 @@ def create_video(script_text: str, generate_script: bool, music_path: str | None
311
  if not generate_tts_audio(script, audio_path):
312
  raise RuntimeError("Error generando el audio TTS")
313
 
314
- voice_clip = AudioFileClip(audio_path)
315
- video_duration = voice_clip.duration
316
-
317
- if video_duration < 1:
318
- raise ValueError("El audio generado es demasiado corto")
 
 
 
 
 
 
 
 
 
319
 
320
  # Paso 3: Buscar y descargar videos
321
  update_task_progress(task_id, "Paso 3/7: Buscando videos en Pexels...")
@@ -352,12 +375,19 @@ def create_video(script_text: str, generate_script: bool, music_path: str | None
352
  clip = None
353
  try:
354
  clip = VideoFileClip(path)
 
 
 
355
  # Tomar m谩ximo 8 segundos de cada clip
356
  duration = min(8, clip.duration)
357
  processed_clip = clip.subclip(0, duration)
 
358
  # Normalizar el clip (resoluci贸n y FPS)
359
  processed_clip = normalize_clip(processed_clip)
360
- video_clips.append(processed_clip)
 
 
 
361
  except Exception as e:
362
  logger.error(f"Error procesando video {path}: {e}")
363
  finally:
@@ -368,7 +398,15 @@ def create_video(script_text: str, generate_script: bool, music_path: str | None
368
  raise RuntimeError("No se pudieron procesar los videos")
369
 
370
  # Concatenar videos
371
- base_video = concatenate_videoclips(video_clips, method="chain")
 
 
 
 
 
 
 
 
372
 
373
  # Extender video si es m谩s corto que el audio con transiciones suaves
374
  if base_video.duration < video_duration:
@@ -380,60 +418,95 @@ def create_video(script_text: str, generate_script: bool, music_path: str | None
380
  for _ in range(loops_needed - 1):
381
  # Crear un clip con fundido de entrada para la transici贸n
382
  fade_in_clip = base_video.crossfadein(fade_duration)
383
- looped_clips.append(fade_in_clip)
384
- looped_clips.append(base_video)
 
385
 
386
- base_video = concatenate_videoclips(looped_clips)
 
 
 
 
 
 
 
387
 
388
  # Asegurar que el video tenga la duraci贸n exacta del audio
389
- base_video = base_video.subclip(0, video_duration)
 
 
 
 
 
 
 
390
 
391
  # Paso 5: Componer audio final
392
  update_task_progress(task_id, "Paso 5/7: Componiendo audio...")
 
 
393
  if music_path and os.path.exists(music_path):
 
394
  try:
395
  music_clip = AudioFileClip(music_path)
396
- music_clip = loop_audio_to_duration(music_clip, video_duration).volumex(0.2)
397
- final_audio = CompositeAudioClip([music_clip, voice_clip])
 
 
 
398
  except Exception as e:
399
  logger.error(f"Error con m煤sica: {e}")
400
- final_audio = voice_clip
401
- else:
402
- final_audio = voice_clip
403
 
404
  # Paso 6: Agregar subt铆tulos
405
  update_task_progress(task_id, "Paso 6/7: Agregando subt铆tulos...")
406
  subtitle_clips = create_subtitle_clips(script, base_video.w, base_video.h, video_duration)
407
  if subtitle_clips:
408
- base_video = CompositeVideoClip([base_video] + subtitle_clips)
 
 
 
 
 
 
409
 
410
  # Paso 7: Renderizar video final
411
  update_task_progress(task_id, "Paso 7/7: Renderizando video final...")
412
- final_video = base_video.set_audio(final_audio)
413
-
414
- output_path = os.path.join(RESULTS_DIR, f"video_{task_id}.mp4")
415
- final_video.write_videofile(
416
- output_path,
417
- fps=TARGET_FPS,
418
- codec="libx264",
419
- audio_codec="aac", # Mejor calidad de audio
420
- bitrate="8000k", # Controlar calidad de video
421
- threads=4, # Mejor uso de CPU
422
- preset="slow", # Mejor compresi贸n
423
- logger=None,
424
- verbose=False
425
- )
426
-
427
- # Limpiar clips
428
- voice_clip.close()
429
- if 'music_clip' in locals():
430
- music_clip.close()
431
- base_video.close()
432
- final_video.close()
433
- for clip in video_clips:
434
- clip.close()
435
-
436
- return output_path
 
 
 
 
 
 
 
 
437
 
438
  except Exception as e:
439
  logger.error(f"Error creando video: {e}")
 
243
  if sentence_duration < 0.5:
244
  continue
245
 
246
+ try:
247
+ txt_clip = (
248
+ TextClip(
249
+ sentence,
250
+ fontsize=max(20, int(video_height * 0.05)),
251
+ color="white",
252
+ stroke_color="black",
253
+ stroke_width=2,
254
+ method="caption",
255
+ size=(int(video_width * 0.9), None),
256
+ font="Arial-Bold"
257
+ )
258
+ .set_start(current_time)
259
+ .set_duration(sentence_duration)
260
+ .set_position(("center", "bottom"))
261
  )
262
+ if txt_clip is not None:
263
+ clips.append(txt_clip)
264
+ except Exception as e:
265
+ logger.error(f"Error creando subt铆tulo para '{sentence}': {e}")
266
+ continue
267
+
268
  current_time += sentence_duration
269
 
270
  return clips
 
274
 
275
  def loop_audio_to_duration(audio_clip: AudioFileClip, target_duration: float) -> AudioFileClip:
276
  """Hace loop del audio hasta alcanzar la duraci贸n objetivo"""
277
+ if audio_clip is None:
278
+ return None
279
  try:
280
  if audio_clip.duration >= target_duration:
281
  return audio_clip.subclip(0, target_duration)
 
295
 
296
  def normalize_clip(clip):
297
  """Normaliza un clip de video a resoluci贸n y FPS est谩ndar"""
298
+ if clip is None:
299
+ return None
300
+ try:
301
+ if clip.size != TARGET_RESOLUTION:
302
+ clip = clip.resize(TARGET_RESOLUTION)
303
+ if clip.fps != TARGET_FPS:
304
+ clip = clip.set_fps(TARGET_FPS)
305
+ return clip
306
+ except Exception as e:
307
+ logger.error(f"Error normalizando clip: {e}")
308
+ return None
309
 
310
  try:
311
  # Paso 1: Generar o usar gui贸n
 
325
  if not generate_tts_audio(script, audio_path):
326
  raise RuntimeError("Error generando el audio TTS")
327
 
328
+ voice_clip = None
329
+ try:
330
+ voice_clip = AudioFileClip(audio_path)
331
+ if voice_clip is None:
332
+ raise RuntimeError("No se pudo cargar el clip de audio")
333
+
334
+ video_duration = voice_clip.duration
335
+
336
+ if video_duration < 1:
337
+ raise ValueError("El audio generado es demasiado corto")
338
+ except Exception as e:
339
+ if voice_clip is not None:
340
+ voice_clip.close()
341
+ raise e
342
 
343
  # Paso 3: Buscar y descargar videos
344
  update_task_progress(task_id, "Paso 3/7: Buscando videos en Pexels...")
 
375
  clip = None
376
  try:
377
  clip = VideoFileClip(path)
378
+ if clip is None:
379
+ continue
380
+
381
  # Tomar m谩ximo 8 segundos de cada clip
382
  duration = min(8, clip.duration)
383
  processed_clip = clip.subclip(0, duration)
384
+
385
  # Normalizar el clip (resoluci贸n y FPS)
386
  processed_clip = normalize_clip(processed_clip)
387
+
388
+ if processed_clip is not None:
389
+ video_clips.append(processed_clip)
390
+
391
  except Exception as e:
392
  logger.error(f"Error procesando video {path}: {e}")
393
  finally:
 
398
  raise RuntimeError("No se pudieron procesar los videos")
399
 
400
  # Concatenar videos
401
+ base_video = None
402
+ try:
403
+ base_video = concatenate_videoclips(video_clips, method="chain")
404
+ if base_video is None:
405
+ raise RuntimeError("No se pudo concatenar los videos")
406
+ except Exception as e:
407
+ if base_video is not None:
408
+ base_video.close()
409
+ raise e
410
 
411
  # Extender video si es m谩s corto que el audio con transiciones suaves
412
  if base_video.duration < video_duration:
 
418
  for _ in range(loops_needed - 1):
419
  # Crear un clip con fundido de entrada para la transici贸n
420
  fade_in_clip = base_video.crossfadein(fade_duration)
421
+ if fade_in_clip is not None:
422
+ looped_clips.append(fade_in_clip)
423
+ looped_clips.append(base_video)
424
 
425
+ try:
426
+ base_video = concatenate_videoclips(looped_clips)
427
+ if base_video is None:
428
+ raise RuntimeError("No se pudo extender el video")
429
+ except Exception as e:
430
+ if base_video is not None:
431
+ base_video.close()
432
+ raise e
433
 
434
  # Asegurar que el video tenga la duraci贸n exacta del audio
435
+ try:
436
+ base_video = base_video.subclip(0, video_duration)
437
+ if base_video is None:
438
+ raise RuntimeError("No se pudo recortar el video")
439
+ except Exception as e:
440
+ if base_video is not None:
441
+ base_video.close()
442
+ raise e
443
 
444
  # Paso 5: Componer audio final
445
  update_task_progress(task_id, "Paso 5/7: Componiendo audio...")
446
+ final_audio = voice_clip
447
+
448
  if music_path and os.path.exists(music_path):
449
+ music_clip = None
450
  try:
451
  music_clip = AudioFileClip(music_path)
452
+ if music_clip is not None:
453
+ music_clip = loop_audio_to_duration(music_clip, video_duration)
454
+ if music_clip is not None:
455
+ music_clip = music_clip.volumex(0.2)
456
+ final_audio = CompositeAudioClip([music_clip, voice_clip])
457
  except Exception as e:
458
  logger.error(f"Error con m煤sica: {e}")
459
+ finally:
460
+ if music_clip is not None:
461
+ music_clip.close()
462
 
463
  # Paso 6: Agregar subt铆tulos
464
  update_task_progress(task_id, "Paso 6/7: Agregando subt铆tulos...")
465
  subtitle_clips = create_subtitle_clips(script, base_video.w, base_video.h, video_duration)
466
  if subtitle_clips:
467
+ try:
468
+ base_video = CompositeVideoClip([base_video] + subtitle_clips)
469
+ if base_video is None:
470
+ raise RuntimeError("No se pudo agregar subt铆tulos")
471
+ except Exception as e:
472
+ logger.error(f"Error creando video con subt铆tulos: {e}")
473
+ # Continuar sin subt铆tulos si falla
474
 
475
  # Paso 7: Renderizar video final
476
  update_task_progress(task_id, "Paso 7/7: Renderizando video final...")
477
+ final_video = None
478
+ try:
479
+ final_video = base_video.set_audio(final_audio)
480
+ if final_video is None:
481
+ raise RuntimeError("No se pudo combinar video y audio")
482
+
483
+ output_path = os.path.join(RESULTS_DIR, f"video_{task_id}.mp4")
484
+ final_video.write_videofile(
485
+ output_path,
486
+ fps=TARGET_FPS,
487
+ codec="libx264",
488
+ audio_codec="aac", # Mejor calidad de audio
489
+ bitrate="8000k", # Controlar calidad de video
490
+ threads=4, # Mejor uso de CPU
491
+ preset="slow", # Mejor compresi贸n
492
+ logger=None,
493
+ verbose=False
494
+ )
495
+
496
+ return output_path
497
+ except Exception as e:
498
+ raise e
499
+ finally:
500
+ # Limpiar clips
501
+ if voice_clip is not None:
502
+ voice_clip.close()
503
+ if base_video is not None:
504
+ base_video.close()
505
+ if final_video is not None:
506
+ final_video.close()
507
+ for clip in video_clips:
508
+ if clip is not None:
509
+ clip.close()
510
 
511
  except Exception as e:
512
  logger.error(f"Error creando video: {e}")