Spaces:
Runtime error
Runtime error
Update app.py
Browse files
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
|
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 |
-
|
|
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
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 |
-
|
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 |
-
|
|
|
|
|
|
|
|
|
468 |
|
469 |
-
|
470 |
-
|
471 |
-
|
472 |
-
|
473 |
-
|
474 |
-
|
475 |
-
|
476 |
-
|
477 |
-
|
478 |
-
|
479 |
-
|
480 |
-
|
|
|
|
|
|
|
481 |
|
482 |
-
num_full_repeats = int(audio_duration //
|
483 |
-
remaining_duration = audio_duration %
|
484 |
|
485 |
-
repeated_clips_list = [
|
486 |
|
487 |
if remaining_duration > 0:
|
488 |
try:
|
489 |
-
remaining_clip =
|
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 |
-
|
498 |
-
|
499 |
-
|
500 |
-
|
501 |
-
|
502 |
-
|
503 |
-
|
504 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
505 |
else:
|
506 |
logger.error("Failed to create repeated video clips list.")
|
507 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
508 |
|
509 |
-
|
510 |
-
|
511 |
-
|
512 |
-
|
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
|
553 |
-
|
554 |
-
|
555 |
-
|
|
|
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 |
-
|
592 |
-
|
593 |
-
|
594 |
-
|
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:
|
|
|
|
|
|
|
|
|
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 |
|