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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +126 -67
app.py CHANGED
@@ -206,7 +206,13 @@ def loop_audio_to_length(audio_clip, target_duration):
206
  logger.debug(f"Adjusting audio | Current duration: {audio_clip.duration:.2f}s | Target: {target_duration:.2f}s")
207
  if audio_clip.duration is None or audio_clip.duration <= 0:
208
  logger.warning("Audio clip has zero or negative duration, cannot loop.")
209
- return AudioFileClip(filename="")
 
 
 
 
 
 
210
 
211
  if audio_clip.duration >= target_duration:
212
  logger.debug("Audio clip already longer or equal to target.")
@@ -216,17 +222,21 @@ def loop_audio_to_length(audio_clip, target_duration):
216
  logger.debug(f"Creating {loops} audio loops")
217
 
218
  audio_segments = [audio_clip] * loops
 
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):
232
  logger.info("Extracting keywords from script")
@@ -293,9 +303,10 @@ def crear_video(prompt_type, input_text, musica_file=None):
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,7 +337,7 @@ def crear_video(prompt_type, input_text, musica_file=None):
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
 
@@ -368,7 +379,6 @@ def crear_video(prompt_type, input_text, musica_file=None):
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,27 +423,28 @@ def crear_video(prompt_type, input_text, musica_file=None):
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
421
 
422
  for i, path in enumerate(video_paths):
 
423
  if current_duration >= audio_duration + max_clip_segment:
424
  logger.debug(f"Video base sufficient ({current_duration:.1f}s >= {audio_duration:.1f}s + {max_clip_segment:.1f}s buffer). Stopping processing remaining source clips.")
425
  break
426
 
427
- clip = None
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.")
435
  continue
436
 
 
437
  remaining_needed = audio_duration - current_duration
438
  potential_use_duration = min(clip.duration, max_clip_segment)
439
 
@@ -446,7 +457,7 @@ def crear_video(prompt_type, input_text, musica_file=None):
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
@@ -468,8 +479,7 @@ def crear_video(prompt_type, input_text, musica_file=None):
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
 
@@ -478,26 +488,29 @@ def crear_video(prompt_type, input_text, musica_file=None):
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
@@ -513,20 +526,29 @@ def crear_video(prompt_type, input_text, musica_file=None):
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:
519
  logger.warning(f"Error creating subclip for remaining duration {remaining_duration:.2f}s: {str(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
 
@@ -538,21 +560,24 @@ def crear_video(prompt_type, input_text, musica_file=None):
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
 
@@ -564,16 +589,15 @@ def crear_video(prompt_type, input_text, musica_file=None):
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
@@ -582,6 +606,7 @@ def crear_video(prompt_type, input_text, musica_file=None):
582
  final_audio = audio_tts
583
 
584
  if musica_file:
 
585
  try:
586
  music_path = os.path.join(temp_dir_intermediate, "musica_bg.mp3")
587
  shutil.copyfile(musica_file, music_path)
@@ -589,32 +614,47 @@ def crear_video(prompt_type, input_text, musica_file=None):
589
  logger.info(f"Background music copied to: {music_path}")
590
 
591
  musica_audio = AudioFileClip(music_path)
592
- logger.debug(f"Original music duration: {musica_audio.duration:.2f}s")
593
 
594
- musica_audio_looped = loop_audio_to_length(musica_audio, video_base.duration)
595
- logger.debug(f"Music adjusted to video duration: {musica_audio_looped.duration:.2f}s")
596
-
597
- try: musica_audio.close()
598
- except: pass
599
- musica_audio = musica_audio_looped
600
 
 
 
 
601
 
602
- final_audio = CompositeAudioClip([
603
- musica_audio.volumex(0.2),
604
- audio_tts.volumex(1.0)
605
- ])
606
- logger.info("Audio mix completed (voice + music).")
 
 
 
 
 
 
 
 
 
 
607
 
608
  except Exception as e:
609
  logger.warning(f"Error processing background music: {str(e)}", exc_info=True)
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
@@ -622,7 +662,8 @@ def crear_video(prompt_type, input_text, musica_file=None):
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
 
@@ -630,6 +671,12 @@ def crear_video(prompt_type, input_text, musica_file=None):
630
  output_path = os.path.join(temp_dir_intermediate, output_filename)
631
  logger.info(f"Writing final video to: {output_path}")
632
 
 
 
 
 
 
 
633
  video_final.write_videofile(
634
  output_path,
635
  fps=24,
@@ -668,13 +715,12 @@ def crear_video(prompt_type, input_text, musica_file=None):
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
 
@@ -827,10 +873,23 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
827
  if __name__ == "__main__":
828
  logger.info("Verifying critical dependencies...")
829
  try:
830
- from moviepy.editor import VideoFileClip
831
- logger.info("MoviePy imported correctly. FFmpeg seems accessible.")
 
 
 
 
 
 
 
 
 
 
832
  except Exception as e:
833
- logger.critical(f"Failed to import MoviePy, often indicates FFmpeg issues. Ensure it is installed and in PATH. Error: {e}")
 
 
 
834
 
835
  logger.info("Starting Gradio app...")
836
  try:
 
206
  logger.debug(f"Adjusting audio | Current duration: {audio_clip.duration:.2f}s | Target: {target_duration:.2f}s")
207
  if audio_clip.duration is None or audio_clip.duration <= 0:
208
  logger.warning("Audio clip has zero or negative duration, cannot loop.")
209
+ # Return a small silent clip or similar if duration is invalid
210
+ try:
211
+ from moviepy.editor import AudioClip
212
+ return AudioClip(lambda t: 0, duration=target_duration, sr=44100)
213
+ except Exception as e:
214
+ logger.error(f"Could not create silence clip: {e}")
215
+ return AudioFileClip(filename="")
216
 
217
  if audio_clip.duration >= target_duration:
218
  logger.debug("Audio clip already longer or equal to target.")
 
222
  logger.debug(f"Creating {loops} audio loops")
223
 
224
  audio_segments = [audio_clip] * loops
225
+ looped_audio = None # Initialize for finally block
226
  try:
227
  looped_audio = concatenate_audioclips(audio_segments)
228
  final_looped_audio = looped_audio.subclip(0, target_duration)
 
 
 
229
  return final_looped_audio
230
  except Exception as e:
231
  logger.error(f"Error concatenating audio clips for looping: {str(e)}", exc_info=True)
232
  # Fallback: return original clip if looping fails
233
  return audio_clip.subclip(0, min(audio_clip.duration, target_duration))
234
+ finally:
235
+ # Clean up the temporary concatenated clip
236
+ if looped_audio is not None:
237
+ try: looped_audio.close()
238
+ except: pass
239
+
240
 
241
  def extract_visual_keywords_from_script(script_text):
242
  logger.info("Extracting keywords from script")
 
303
  # Initialize clips and audio objects to None for cleanup in finally
304
  audio_tts = None
305
  musica_audio = None
306
+ video_base = None # This will hold the final video base clip before adding audio
307
+ video_final = None # This will hold the final clip with video and audio
308
+ source_clips = [] # Clips loaded from downloaded files for proper closing
309
+ clips_to_concatenate = [] # Segments extracted from source_clips
310
 
311
  try:
312
  # 1. Generate or use script
 
337
  audio_duration = audio_tts.duration
338
  logger.info(f"Voice audio duration: {audio_duration:.2f} seconds")
339
 
340
+ if audio_duration < 1.0:
341
  logger.error(f"Voice audio duration ({audio_duration:.2f}s) is too short.")
342
  raise ValueError("Generated voice audio is too short (min 1 second required).")
343
 
 
379
  videos = buscar_videos_pexels(keyword, PEXELS_API_KEY, per_page=2)
380
  if videos:
381
  videos_data.extend(videos)
 
382
  logger.info(f"Found {len(videos)} videos for '{keyword}' (generic). Total data: {len(videos_data)}")
383
  except Exception as e:
384
  logger.warning(f"Error searching generic videos for '{keyword}': {str(e)}")
 
423
 
424
  # 5. Process and concatenate video clips
425
  logger.info("Processing and concatenating downloaded videos...")
 
 
426
  current_duration = 0
427
  min_clip_duration = 0.5
428
+ max_clip_segment = 10.0 # Max segment length from one source clip
429
 
430
  for i, path in enumerate(video_paths):
431
+ # Stop if we have enough duration plus a buffer
432
  if current_duration >= audio_duration + max_clip_segment:
433
  logger.debug(f"Video base sufficient ({current_duration:.1f}s >= {audio_duration:.1f}s + {max_clip_segment:.1f}s buffer). Stopping processing remaining source clips.")
434
  break
435
 
436
+ clip = None # Initialize for finally block
437
  try:
438
  logger.debug(f"[{i+1}/{len(video_paths)}] Opening clip: {path}")
439
  clip = VideoFileClip(path)
440
  source_clips.append(clip) # Add to list for later cleanup
441
 
442
+ # Verify the source clip is valid
443
  if clip.reader is None or clip.duration is None or clip.duration <= 0:
444
+ logger.warning(f"[{i+1}/{len(video_paths)}] Source clip {path} seems invalid (reader is None or duration <= 0). Skipping.")
445
  continue
446
 
447
+ # Calculate how much to take from this clip
448
  remaining_needed = audio_duration - current_duration
449
  potential_use_duration = min(clip.duration, max_clip_segment)
450
 
 
457
  try:
458
  # Create a subclip. This creates a *new* clip object.
459
  sub = clip.subclip(0, segment_duration)
460
+ # Verify the subclip is valid (it should be a VideoFileClip still)
461
  if sub.reader is None or sub.duration is None or sub.duration <= 0:
462
  logger.warning(f"[{i+1}/{len(video_paths)}] Generated subclip from {path} is invalid. Skipping.")
463
  try: sub.close() # Close the invalid subclip
 
479
  except Exception as e:
480
  logger.warning(f"[{i+1}/{len(video_paths)}] Error processing video {path}: {str(e)}", exc_info=True)
481
  continue
482
+ # Source clips are closed in the main finally block
 
483
 
484
  logger.info(f"Source clip processing finished. Obtained {len(clips_to_concatenate)} valid segments.")
485
 
 
488
  raise ValueError("No valid video segments available to create the video.")
489
 
490
  logger.info(f"Concatenating {len(clips_to_concatenate)} video segments.")
491
+ concatenated_base = None # Hold the result temporarily
492
  try:
493
  # Concatenate the collected valid segments
494
+ concatenated_base = concatenate_videoclips(clips_to_concatenate, method="chain")
495
+ logger.info(f"Base video duration after initial concatenation: {concatenated_base.duration:.2f}s")
 
 
 
 
 
 
496
 
497
  # Verify the resulting concatenated clip is valid
498
+ # CORRECTED CHECK for CompositeVideoClip: Check existence and duration
499
+ if concatenated_base is None or concatenated_base.duration is None or concatenated_base.duration <= 0:
500
+ logger.critical("Concatenated video base clip is invalid after first concatenation (None or zero duration).")
501
  raise ValueError("Failed to create valid video base from segments.")
502
 
503
  except Exception as e:
504
  logger.critical(f"Error during initial concatenation: {str(e)}", exc_info=True)
505
  raise ValueError("Failed during initial video concatenation.")
506
+ finally:
507
+ # IMPORTANT: Close all the individual segments that were concatenated *regardless of success*
508
+ for clip_segment in clips_to_concatenate:
509
+ try: clip_segment.close()
510
+ except: pass
511
+ clips_to_concatenate = [] # Clear the list
512
 
513
+ video_base = concatenated_base # Assign the valid concatenated clip
514
 
515
  # --- REVISED REPETITION AND TRIMMING LOGIC ---
516
  final_video_base = video_base # Start with the concatenated base
 
526
  if remaining_duration > 0:
527
  try:
528
  remaining_clip = final_video_base.subclip(0, remaining_duration)
529
+ # Verify remaining clip is valid (should be a CompositeVideoClip from subclip)
530
+ if remaining_clip is None or remaining_clip.duration is None or remaining_clip.duration <= 0:
531
+ logger.warning(f"Generated subclip for remaining duration {remaining_duration:.2f}s is invalid. Skipping.")
532
+ try: remaining_clip.close()
533
+ except: pass
534
+ else:
535
+ repeated_clips_list.append(remaining_clip)
536
+ logger.debug(f"Adding remaining segment: {remaining_duration:.2f}s")
537
+
538
  except Exception as e:
539
  logger.warning(f"Error creating subclip for remaining duration {remaining_duration:.2f}s: {str(e)}")
540
 
541
  if repeated_clips_list:
542
  logger.info(f"Concatenating {len(repeated_clips_list)} parts for repetition.")
543
+ video_base_repeated = None # Hold result temporarily
544
  try:
545
  # Concatenate the repeated parts
546
  video_base_repeated = concatenate_videoclips(repeated_clips_list, method="chain")
547
  logger.info(f"Duration of repeated video base: {video_base_repeated.duration:.2f}s")
548
 
549
  # Verify the repeated clip is valid
550
+ # CORRECTED CHECK: Check existence and duration
551
+ if video_base_repeated is None or video_base_repeated.duration is None or video_base_repeated.duration <= 0:
552
  logger.critical("Concatenated repeated video base clip is invalid.")
553
  raise ValueError("Failed to create valid repeated video base.")
554
 
 
560
 
561
  except Exception as e:
562
  logger.critical(f"Error during repetition concatenation: {str(e)}", exc_info=True)
563
+ raise ValueError("Failed during video repetition.")
564
+ finally:
565
+ # Close the clips in the repeated list, EXCEPT the one assigned to final_video_base
566
+ for clip in repeated_clips_list:
567
+ if clip is not final_video_base:
568
+ try: clip.close()
569
+ except: pass
570
 
 
 
 
571
 
572
  # After repetition (or if no repetition happened), ensure duration matches audio exactly
573
  if final_video_base.duration > audio_duration:
574
  logger.info(f"Trimming video base ({final_video_base.duration:.2f}s) to match audio duration ({audio_duration:.2f}s).")
575
+ trimmed_video_base = None # Hold result temporarily
576
  try:
577
  trimmed_video_base = final_video_base.subclip(0, audio_duration)
578
  # Verify the trimmed clip is valid
579
+ # CORRECTED CHECK: Check existence and duration
580
+ if trimmed_video_base is None or trimmed_video_base.duration is None or trimmed_video_base.duration <= 0:
581
  logger.critical("Trimmed video base clip is invalid.")
582
  raise ValueError("Failed to create valid trimmed video base.")
583
 
 
589
 
590
  except Exception as e:
591
  logger.critical(f"Error during trimming: {str(e)}", exc_info=True)
592
+ raise ValueError("Failed during video trimming.")
593
+
 
594
 
595
  # Final check on video_base before setting audio/writing
596
+ # CORRECTED CHECK: Check existence and duration
597
+ if final_video_base is None or final_video_base.duration is None or final_video_base.duration <= 0:
598
  logger.critical("Final video base clip is invalid before audio/writing.")
599
  raise ValueError("Final video base clip is invalid.")
600
 
 
601
  video_base = final_video_base # Use the final adjusted video_base for subsequent steps
602
 
603
  # 6. Handle background music
 
606
  final_audio = audio_tts
607
 
608
  if musica_file:
609
+ musica_audio = None # Initialize for finally block
610
  try:
611
  music_path = os.path.join(temp_dir_intermediate, "musica_bg.mp3")
612
  shutil.copyfile(musica_file, music_path)
 
614
  logger.info(f"Background music copied to: {music_path}")
615
 
616
  musica_audio = AudioFileClip(music_path)
 
617
 
618
+ if musica_audio.duration is None or musica_audio.duration <= 0:
619
+ logger.warning("Background music clip seems invalid or has zero duration. Skipping music.")
620
+ musica_audio = None # Invalidate the clip
 
 
 
621
 
622
+ if musica_audio:
623
+ musica_audio_looped = loop_audio_to_length(musica_audio, video_base.duration)
624
+ logger.debug(f"Music adjusted to video duration: {musica_audio_looped.duration:.2f}s")
625
 
626
+ # Close the original music audio clip
627
+ try: musica_audio.close()
628
+ except: pass
629
+ musica_audio = musica_audio_looped # Assign the looped/trimmed clip
630
+
631
+ final_audio = CompositeAudioClip([
632
+ musica_audio.volumex(0.2),
633
+ audio_tts.volumex(1.0)
634
+ ])
635
+ # Verify the resulting composite audio
636
+ if final_audio.duration is None or final_audio.duration <= 0:
637
+ logger.warning("Composite audio clip is invalid (None or zero duration). Using voice audio only.")
638
+ final_audio = audio_tts # Fallback
639
+ else:
640
+ logger.info("Audio mix completed (voice + music).")
641
 
642
  except Exception as e:
643
  logger.warning(f"Error processing background music: {str(e)}", exc_info=True)
644
  final_audio = audio_tts
645
  logger.warning("Using voice audio only due to music processing error.")
646
+ # No finally here for musica_audio, it's handled in the main finally block
647
 
648
+ # Ensure final_audio duration matches video_base duration if possible (MoviePy is lenient but best to match)
649
+ if final_audio.duration is not None and abs(final_audio.duration - video_base.duration) > 0.2: # Allow small floating point differences
650
+ logger.warning(f"Final audio duration ({final_audio.duration:.2f}s) differs significantly from video base ({video_base.duration:.2f}s). Attempting trim/extend.")
651
+ try:
652
+ if final_audio.duration > video_base.duration:
653
+ final_audio = final_audio.subclip(0, video_base.duration)
654
+ logger.warning("Trimmed final audio to match video duration.")
655
+ # MoviePy often extends audio automatically if it's too short, so we don't explicitly extend here.
656
+ except Exception as e:
657
+ logger.warning(f"Error adjusting final audio duration: {str(e)}")
658
 
659
 
660
  # 7. Create final video
 
662
  video_final = video_base.set_audio(final_audio)
663
 
664
  # Final check on the combined video+audio clip
665
+ # CORRECTED CHECK: Check existence and duration
666
+ if video_final is None or video_final.duration is None or video_final.duration <= 0:
667
  logger.critical("Final video clip (with audio) is invalid before writing.")
668
  raise ValueError("Final video clip is invalid before writing.")
669
 
 
671
  output_path = os.path.join(temp_dir_intermediate, output_filename)
672
  logger.info(f"Writing final video to: {output_path}")
673
 
674
+ # Check if video_base has a valid size before writing
675
+ if video_base.size is None or video_base.size[0] <= 0 or video_base.size[1] <= 0:
676
+ logger.critical(f"Video base has invalid size: {video_base.size}. Cannot write video.")
677
+ raise ValueError("Video base has invalid size before writing.")
678
+
679
+
680
  video_final.write_videofile(
681
  output_path,
682
  fps=24,
 
715
  try:
716
  if audio_tts is not None: audio_tts.close()
717
  if musica_audio is not None: musica_audio.close()
718
+ # Close video_final first, which should cascade to components like video_base
 
 
719
  if video_final is not None:
720
  try: video_final.close()
721
  except Exception as e: logger.warning(f"Error closing video_final in finally: {str(e)}")
722
+ # If video_final wasn't created but video_base was (due to error before set_audio), close video_base
723
+ elif video_base is not None:
724
  try: video_base.close()
725
  except Exception as e: logger.warning(f"Error closing video_base in finally: {str(e)}")
726
 
 
873
  if __name__ == "__main__":
874
  logger.info("Verifying critical dependencies...")
875
  try:
876
+ # Try importing something that requires FFmpeg/ImageMagick backend
877
+ from moviepy.editor import ColorClip
878
+ # Check if basic clip creation works
879
+ try:
880
+ temp_clip = ColorClip((100,100), color=(255,0,0), duration=1)
881
+ temp_clip.close()
882
+ logger.info("MoviePy base clips (like ColorClip) created successfully. FFmpeg seems accessible.")
883
+ except Exception as e:
884
+ logger.critical(f"Failed to create basic MoviePy clip. Often indicates FFmpeg/ImageMagick issues. Error: {e}")
885
+ # Decide whether to raise or just log
886
+ # raise RuntimeError(f"MoviePy/FFmpeg dependency issue: {e}") # Uncomment to force exit
887
+
888
  except Exception as e:
889
+ logger.critical(f"Failed to import MoviePy. Ensure it is installed. Error: {e}", exc_info=True)
890
+ # Decide whether to raise or just log
891
+ # raise RuntimeError(f"MoviePy import failed: {e}") # Uncomment to force exit
892
+
893
 
894
  logger.info("Starting Gradio app...")
895
  try: