gnosticdev commited on
Commit
c8581d9
·
verified ·
1 Parent(s): a889be2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +142 -62
app.py CHANGED
@@ -219,12 +219,13 @@ def loop_audio_to_length(audio_clip, target_duration):
219
  try:
220
  looped_audio = concatenate_audioclips(audio_segments)
221
  final_looped_audio = looped_audio.subclip(0, target_duration)
222
- # Close the temporary concatenated clip
223
  try: looped_audio.close()
224
  except: pass
225
  return final_looped_audio
226
  except Exception as e:
227
  logger.error(f"Error concatenating audio clips for looping: {str(e)}", exc_info=True)
 
228
  return audio_clip.subclip(0, min(audio_clip.duration, target_duration))
229
 
230
  def extract_visual_keywords_from_script(script_text):
@@ -289,10 +290,12 @@ def crear_video(prompt_type, input_text, musica_file=None):
289
  start_time = datetime.now()
290
  temp_dir_intermediate = None
291
 
 
292
  audio_tts = None
293
  musica_audio = None
294
  video_base = None
295
  video_final = None
 
296
 
297
  try:
298
  # 1. Generate or use script
@@ -323,9 +326,9 @@ def crear_video(prompt_type, input_text, musica_file=None):
323
  audio_duration = audio_tts.duration
324
  logger.info(f"Voice audio duration: {audio_duration:.2f} seconds")
325
 
326
- if audio_duration < 0.5:
327
  logger.error(f"Voice audio duration ({audio_duration:.2f}s) is too short.")
328
- raise ValueError("Generated voice audio is too short.")
329
 
330
 
331
  # 3. Extract keywords
@@ -365,6 +368,7 @@ def crear_video(prompt_type, input_text, musica_file=None):
365
  videos = buscar_videos_pexels(keyword, PEXELS_API_KEY, per_page=2)
366
  if videos:
367
  videos_data.extend(videos)
 
368
  logger.info(f"Found {len(videos)} videos for '{keyword}' (generic). Total data: {len(videos_data)}")
369
  except Exception as e:
370
  logger.warning(f"Error searching generic videos for '{keyword}': {str(e)}")
@@ -409,7 +413,8 @@ def crear_video(prompt_type, input_text, musica_file=None):
409
 
410
  # 5. Process and concatenate video clips
411
  logger.info("Processing and concatenating downloaded videos...")
412
- clips = []
 
413
  current_duration = 0
414
  min_clip_duration = 0.5
415
  max_clip_segment = 10.0
@@ -423,6 +428,7 @@ def crear_video(prompt_type, input_text, musica_file=None):
423
  try:
424
  logger.debug(f"[{i+1}/{len(video_paths)}] Opening clip: {path}")
425
  clip = VideoFileClip(path)
 
426
 
427
  if clip.reader is None or clip.duration is None or clip.duration <= 0:
428
  logger.warning(f"[{i+1}/{len(video_paths)}] Clip {path} seems invalid (reader is None or duration <= 0). Skipping.")
@@ -438,11 +444,19 @@ def crear_video(prompt_type, input_text, musica_file=None):
438
 
439
  if segment_duration >= min_clip_duration:
440
  try:
 
441
  sub = clip.subclip(0, segment_duration)
442
- clips.append(sub)
 
 
 
 
 
 
 
443
  current_duration += sub.duration
444
  logger.debug(f"[{i+1}/{len(video_paths)}] Segment added: {sub.duration:.1f}s (total video: {current_duration:.1f}/{audio_duration:.1f}s)")
445
- # sub.close() # Decided against closing subclips explicitly here
446
  except Exception as sub_e:
447
  logger.warning(f"[{i+1}/{len(video_paths)}] Error creating subclip from {path} ({segment_duration:.1f}s): {str(sub_e)}")
448
  continue
@@ -454,39 +468,51 @@ def crear_video(prompt_type, input_text, musica_file=None):
454
  except Exception as e:
455
  logger.warning(f"[{i+1}/{len(video_paths)}] Error processing video {path}: {str(e)}", exc_info=True)
456
  continue
 
 
457
 
458
- finally:
459
- if clip is not None:
460
- try:
461
- clip.close()
462
- logger.debug(f"[{i+1}/{len(video_paths)}] Clip {path} closed.")
463
- except Exception as close_e:
464
- logger.warning(f"[{i+1}/{len(video_paths)}] Error closing clip {path}: {str(close_e)}")
465
 
 
 
 
 
 
 
 
 
 
466
 
467
- logger.info(f"Source clip processing finished. Obtained {len(clips)} valid clips.")
 
 
 
 
468
 
469
- if not clips:
470
- logger.error("No valid video clips available to create the sequence.")
471
- raise ValueError("No valid video clips available to create the video.")
472
-
473
- logger.info(f"Concatenating {len(clips)} video clips.")
474
- # Use the default "chain" method for simple concatenation
475
- video_base = concatenate_videoclips(clips) # Removed method="compose"
476
- logger.info(f"Base video duration after initial concatenation: {video_base.duration:.2f}s")
477
-
478
- # --- REVISED REPETITION LOGIC ---
479
- if video_base.duration < audio_duration:
480
- logger.info(f"Base video ({video_base.duration:.2f}s) is shorter than audio ({audio_duration:.2f}s). Repeating...")
 
 
 
481
 
482
- num_full_repeats = int(audio_duration // video_base.duration)
483
- remaining_duration = audio_duration % video_base.duration
484
 
485
- repeated_clips_list = [video_base] * num_full_repeats
486
 
487
  if remaining_duration > 0:
488
  try:
489
- remaining_clip = video_base.subclip(0, remaining_duration)
490
  repeated_clips_list.append(remaining_clip)
491
  logger.debug(f"Adding remaining segment: {remaining_duration:.2f}s")
492
  except Exception as e:
@@ -494,27 +520,62 @@ def crear_video(prompt_type, input_text, musica_file=None):
494
 
495
  if repeated_clips_list:
496
  logger.info(f"Concatenating {len(repeated_clips_list)} parts for repetition.")
497
- video_base_repeated = concatenate_videoclips(repeated_clips_list)
498
- logger.info(f"Duration of repeated video base: {video_base_repeated.duration:.2f}s")
499
-
500
- try: video_base.close()
501
- except: pass
502
-
503
- video_base = video_base_repeated
504
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
505
  else:
506
  logger.error("Failed to create repeated video clips list.")
507
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
508
 
509
- if video_base.duration > audio_duration:
510
- logger.info(f"Trimming video base ({video_base.duration:.2f}s) to match audio duration ({audio_duration:.2f}s).")
511
- trimmed_video_base = video_base.subclip(0, audio_duration)
512
- try: video_base.close()
513
- except: pass
514
- video_base = trimmed_video_base
515
- logger.info(f"Final base video duration: {video_base.duration:.2f}s")
516
 
517
 
 
 
518
  # 6. Handle background music
519
  logger.info("Processing audio...")
520
 
@@ -549,16 +610,22 @@ def crear_video(prompt_type, input_text, musica_file=None):
549
  final_audio = audio_tts
550
  logger.warning("Using voice audio only due to music processing error.")
551
 
552
- if final_audio.duration is not None and final_audio.duration > video_base.duration:
553
- final_audio = final_audio.subclip(0, video_base.duration)
554
- elif final_audio.duration is not None and final_audio.duration < video_base.duration:
555
- logger.warning(f"Final audio duration ({final_audio.duration:.2f}s) is less than video base ({video_base.duration:.2f}s).")
 
556
 
557
 
558
  # 7. Create final video
559
  logger.info("Rendering final video...")
560
  video_final = video_base.set_audio(final_audio)
561
-
 
 
 
 
 
562
  output_filename = "final_video.mp4"
563
  output_path = os.path.join(temp_dir_intermediate, output_filename)
564
  logger.info(f"Writing final video to: {output_path}")
@@ -585,23 +652,36 @@ def crear_video(prompt_type, input_text, musica_file=None):
585
  logger.critical(f"CRITICAL UNHANDLED ERROR in crear_video: {str(e)}", exc_info=True)
586
  raise e
587
  finally:
588
- logger.info("Starting cleanup of intermediate temporary files...")
 
 
 
 
 
 
 
 
 
 
 
 
589
  try:
590
- if audio_tts is not None:
591
- try: audio_tts.close()
592
- except: pass
593
- if musica_audio is not None:
594
- try: musica_audio.close()
595
- except: pass
596
- if video_base is not None:
597
- try: video_base.close()
598
- except: pass
599
  if video_final is not None:
600
  try: video_final.close()
601
- except: pass
 
 
 
 
602
  except Exception as e:
603
  logger.warning(f"Error during final clip closing in finally: {str(e)}")
604
 
 
605
  if temp_dir_intermediate and os.path.exists(temp_dir_intermediate):
606
  final_output_in_temp = os.path.join(temp_dir_intermediate, "final_video.mp4")
607
 
 
219
  try:
220
  looped_audio = concatenate_audioclips(audio_segments)
221
  final_looped_audio = looped_audio.subclip(0, target_duration)
222
+ # Close the temporary concatenated clip to free resources
223
  try: looped_audio.close()
224
  except: pass
225
  return final_looped_audio
226
  except Exception as e:
227
  logger.error(f"Error concatenating audio clips for looping: {str(e)}", exc_info=True)
228
+ # Fallback: return original clip if looping fails
229
  return audio_clip.subclip(0, min(audio_clip.duration, target_duration))
230
 
231
  def extract_visual_keywords_from_script(script_text):
 
290
  start_time = datetime.now()
291
  temp_dir_intermediate = None
292
 
293
+ # Initialize clips and audio objects to None for cleanup in finally
294
  audio_tts = None
295
  musica_audio = None
296
  video_base = None
297
  video_final = None
298
+ concatenated_clips = [] # Keep track of main video clips for closing
299
 
300
  try:
301
  # 1. Generate or use script
 
326
  audio_duration = audio_tts.duration
327
  logger.info(f"Voice audio duration: {audio_duration:.2f} seconds")
328
 
329
+ if audio_duration < 1.0: # Require at least 1 second of audio
330
  logger.error(f"Voice audio duration ({audio_duration:.2f}s) is too short.")
331
+ raise ValueError("Generated voice audio is too short (min 1 second required).")
332
 
333
 
334
  # 3. Extract keywords
 
368
  videos = buscar_videos_pexels(keyword, PEXELS_API_KEY, per_page=2)
369
  if videos:
370
  videos_data.extend(videos)
371
+ logger.extend(videos)
372
  logger.info(f"Found {len(videos)} videos for '{keyword}' (generic). Total data: {len(videos_data)}")
373
  except Exception as e:
374
  logger.warning(f"Error searching generic videos for '{keyword}': {str(e)}")
 
413
 
414
  # 5. Process and concatenate video clips
415
  logger.info("Processing and concatenating downloaded videos...")
416
+ source_clips = [] # Clips loaded from downloaded files
417
+ clips_to_concatenate = [] # Segments extracted from source_clips
418
  current_duration = 0
419
  min_clip_duration = 0.5
420
  max_clip_segment = 10.0
 
428
  try:
429
  logger.debug(f"[{i+1}/{len(video_paths)}] Opening clip: {path}")
430
  clip = VideoFileClip(path)
431
+ source_clips.append(clip) # Add to list for later cleanup
432
 
433
  if clip.reader is None or clip.duration is None or clip.duration <= 0:
434
  logger.warning(f"[{i+1}/{len(video_paths)}] Clip {path} seems invalid (reader is None or duration <= 0). Skipping.")
 
444
 
445
  if segment_duration >= min_clip_duration:
446
  try:
447
+ # Create a subclip. This creates a *new* clip object.
448
  sub = clip.subclip(0, segment_duration)
449
+ # Verify the subclip is valid
450
+ if sub.reader is None or sub.duration is None or sub.duration <= 0:
451
+ logger.warning(f"[{i+1}/{len(video_paths)}] Generated subclip from {path} is invalid. Skipping.")
452
+ try: sub.close() # Close the invalid subclip
453
+ except: pass
454
+ continue
455
+
456
+ clips_to_concatenate.append(sub)
457
  current_duration += sub.duration
458
  logger.debug(f"[{i+1}/{len(video_paths)}] Segment added: {sub.duration:.1f}s (total video: {current_duration:.1f}/{audio_duration:.1f}s)")
459
+
460
  except Exception as sub_e:
461
  logger.warning(f"[{i+1}/{len(video_paths)}] Error creating subclip from {path} ({segment_duration:.1f}s): {str(sub_e)}")
462
  continue
 
468
  except Exception as e:
469
  logger.warning(f"[{i+1}/{len(video_paths)}] Error processing video {path}: {str(e)}", exc_info=True)
470
  continue
471
+ # No 'finally' here to close 'clip' explicitly, because we add valid clips to source_clips
472
+ # and close them all together later.
473
 
474
+ logger.info(f"Source clip processing finished. Obtained {len(clips_to_concatenate)} valid segments.")
 
 
 
 
 
 
475
 
476
+ if not clips_to_concatenate:
477
+ logger.error("No valid video segments available to create the sequence.")
478
+ raise ValueError("No valid video segments available to create the video.")
479
+
480
+ logger.info(f"Concatenating {len(clips_to_concatenate)} video segments.")
481
+ try:
482
+ # Concatenate the collected valid segments
483
+ video_base = concatenate_videoclips(clips_to_concatenate, method="chain") # Explicitly use "chain"
484
+ logger.info(f"Base video duration after initial concatenation: {video_base.duration:.2f}s")
485
 
486
+ # IMPORTANT: Close all the individual segments that were concatenated
487
+ for clip_segment in clips_to_concatenate:
488
+ try: clip_segment.close()
489
+ except: pass
490
+ clips_to_concatenate = [] # Clear the list
491
 
492
+ # Verify the resulting concatenated clip is valid
493
+ if video_base.reader is None or video_base.duration is None or video_base.duration <= 0:
494
+ logger.critical("Concatenated video base clip is invalid after first concatenation.")
495
+ raise ValueError("Failed to create valid video base from segments.")
496
+
497
+ except Exception as e:
498
+ logger.critical(f"Error during initial concatenation: {str(e)}", exc_info=True)
499
+ raise ValueError("Failed during initial video concatenation.")
500
+
501
+
502
+ # --- REVISED REPETITION AND TRIMMING LOGIC ---
503
+ final_video_base = video_base # Start with the concatenated base
504
+
505
+ if final_video_base.duration < audio_duration:
506
+ logger.info(f"Base video ({final_video_base.duration:.2f}s) is shorter than audio ({audio_duration:.2f}s). Repeating...")
507
 
508
+ num_full_repeats = int(audio_duration // final_video_base.duration)
509
+ remaining_duration = audio_duration % final_video_base.duration
510
 
511
+ repeated_clips_list = [final_video_base] * num_full_repeats
512
 
513
  if remaining_duration > 0:
514
  try:
515
+ remaining_clip = final_video_base.subclip(0, remaining_duration)
516
  repeated_clips_list.append(remaining_clip)
517
  logger.debug(f"Adding remaining segment: {remaining_duration:.2f}s")
518
  except Exception as e:
 
520
 
521
  if repeated_clips_list:
522
  logger.info(f"Concatenating {len(repeated_clips_list)} parts for repetition.")
523
+ try:
524
+ # Concatenate the repeated parts
525
+ video_base_repeated = concatenate_videoclips(repeated_clips_list, method="chain")
526
+ logger.info(f"Duration of repeated video base: {video_base_repeated.duration:.2f}s")
527
+
528
+ # Verify the repeated clip is valid
529
+ if video_base_repeated.reader is None or video_base_repeated.duration is None or video_base_repeated.duration <= 0:
530
+ logger.critical("Concatenated repeated video base clip is invalid.")
531
+ raise ValueError("Failed to create valid repeated video base.")
532
+
533
+ # Close the old base clip
534
+ try: final_video_base.close()
535
+ except: pass
536
+ # Assign the new valid repeated clip
537
+ final_video_base = video_base_repeated
538
+
539
+ except Exception as e:
540
+ logger.critical(f"Error during repetition concatenation: {str(e)}", exc_info=True)
541
+ # If repetition fails, maybe just use the original base and accept it's too short
542
+ logger.warning("Repetition failed. Using original video base (may be too short).")
543
+ # No need to change final_video_base if concatenation failed
544
+
545
  else:
546
  logger.error("Failed to create repeated video clips list.")
547
+ # Use original video_base if repetition list is empty
548
+
549
+ # After repetition (or if no repetition happened), ensure duration matches audio exactly
550
+ if final_video_base.duration > audio_duration:
551
+ logger.info(f"Trimming video base ({final_video_base.duration:.2f}s) to match audio duration ({audio_duration:.2f}s).")
552
+ try:
553
+ trimmed_video_base = final_video_base.subclip(0, audio_duration)
554
+ # Verify the trimmed clip is valid
555
+ if trimmed_video_base.reader is None or trimmed_video_base.duration is None or trimmed_video_base.duration <= 0:
556
+ logger.critical("Trimmed video base clip is invalid.")
557
+ raise ValueError("Failed to create valid trimmed video base.")
558
+
559
+ # Close the old clip
560
+ try: final_video_base.close()
561
+ except: pass
562
+ # Assign the new valid trimmed clip
563
+ final_video_base = trimmed_video_base
564
+
565
+ except Exception as e:
566
+ logger.critical(f"Error during trimming: {str(e)}", exc_info=True)
567
+ # If trimming fails, use the original longer clip (might cause audio/video sync issues)
568
+ logger.warning("Trimming failed. Using potentially longer video base.")
569
+ pass # final_video_base remains the longer one
570
 
571
+ # Final check on video_base before setting audio/writing
572
+ if final_video_base.reader is None or final_video_base.duration is None or final_video_base.duration <= 0:
573
+ logger.critical("Final video base clip is invalid before audio/writing.")
574
+ raise ValueError("Final video base clip is invalid.")
 
 
 
575
 
576
 
577
+ video_base = final_video_base # Use the final adjusted video_base for subsequent steps
578
+
579
  # 6. Handle background music
580
  logger.info("Processing audio...")
581
 
 
610
  final_audio = audio_tts
611
  logger.warning("Using voice audio only due to music processing error.")
612
 
613
+ if final_audio.duration is not None and abs(final_audio.duration - video_base.duration) > 0.1: # Check for significant duration mismatch
614
+ logger.warning(f"Final audio duration ({final_audio.duration:.2f}s) differs from video base ({video_base.duration:.2f}s). This might cause sync issues.")
615
+ # MoviePy write_videofile often handles slight differences, but large ones can fail or sync poorly.
616
+ # We could try to adjust audio length here, but loop_audio_to_length should have handled it.
617
+ # Let's just log the warning for now.
618
 
619
 
620
  # 7. Create final video
621
  logger.info("Rendering final video...")
622
  video_final = video_base.set_audio(final_audio)
623
+
624
+ # Final check on the combined video+audio clip
625
+ if video_final.reader is None or video_final.duration is None or video_final.duration <= 0:
626
+ logger.critical("Final video clip (with audio) is invalid before writing.")
627
+ raise ValueError("Final video clip is invalid before writing.")
628
+
629
  output_filename = "final_video.mp4"
630
  output_path = os.path.join(temp_dir_intermediate, output_filename)
631
  logger.info(f"Writing final video to: {output_path}")
 
652
  logger.critical(f"CRITICAL UNHANDLED ERROR in crear_video: {str(e)}", exc_info=True)
653
  raise e
654
  finally:
655
+ logger.info("Starting cleanup of clips and intermediate temporary files...")
656
+
657
+ # Close all initially opened source clips
658
+ for clip in source_clips:
659
+ try: clip.close()
660
+ except Exception as e: logger.warning(f"Error closing source clip in finally: {str(e)}")
661
+
662
+ # Close any segments left in the list (should be empty if successful)
663
+ for clip_segment in clips_to_concatenate:
664
+ try: clip_segment.close()
665
+ except Exception as e: logger.warning(f"Error closing segment clip in finally: {str(e)}")
666
+
667
+ # Close the main MoviePy objects if they were successfully created
668
  try:
669
+ if audio_tts is not None: audio_tts.close()
670
+ if musica_audio is not None: musica_audio.close()
671
+ # The video_base and video_final clips are often intertwined or subclips of each other.
672
+ # Closing video_final should ideally close everything it's composed of.
673
+ # Explicitly closing video_base if it's different might help, but be careful not to double close.
 
 
 
 
674
  if video_final is not None:
675
  try: video_final.close()
676
+ except Exception as e: logger.warning(f"Error closing video_final in finally: {str(e)}")
677
+ elif video_base is not None: # If video_final wasn't created, close video_base
678
+ try: video_base.close()
679
+ except Exception as e: logger.warning(f"Error closing video_base in finally: {str(e)}")
680
+
681
  except Exception as e:
682
  logger.warning(f"Error during final clip closing in finally: {str(e)}")
683
 
684
+ # Clean up intermediate files, but NOT the final video file
685
  if temp_dir_intermediate and os.path.exists(temp_dir_intermediate):
686
  final_output_in_temp = os.path.join(temp_dir_intermediate, "final_video.mp4")
687