Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
@@ -34,7 +34,6 @@ logger.info("="*80)
|
|
34 |
PEXELS_API_KEY = os.environ.get("PEXELS_API_KEY")
|
35 |
if not PEXELS_API_KEY:
|
36 |
logger.critical("PEXELS_API_KEY environment variable not found.")
|
37 |
-
# Uncomment to force fail if not set:
|
38 |
# raise ValueError("Pexels API key not configured")
|
39 |
|
40 |
# Model Initialization
|
@@ -55,7 +54,7 @@ except Exception as e:
|
|
55 |
logger.info("Loading KeyBERT model...")
|
56 |
kw_model = None
|
57 |
try:
|
58 |
-
kw_model =
|
59 |
logger.info("KeyBERT initialized successfully")
|
60 |
except Exception as e:
|
61 |
logger.error(f"FAILURE loading KeyBERT: {str(e)}", exc_info=True)
|
@@ -205,22 +204,18 @@ def download_video_file(url, temp_dir):
|
|
205 |
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 |
|
208 |
-
# Handle cases where the input audio clip is invalid
|
209 |
if audio_clip is None or audio_clip.duration is None or audio_clip.duration <= 0:
|
210 |
logger.warning("Input audio clip is invalid (None or zero duration), cannot loop.")
|
211 |
-
# Return a silent clip of target duration as fallback
|
212 |
try:
|
213 |
-
|
214 |
-
sr = getattr(audio_clip, 'fps', 44100) if audio_clip else 44100 # Use fps for audio clips
|
215 |
return AudioClip(lambda t: 0, duration=target_duration, sr=sr)
|
216 |
except Exception as e:
|
217 |
logger.error(f"Could not create silence clip: {e}", exc_info=True)
|
218 |
-
return AudioFileClip(filename="")
|
219 |
|
220 |
if audio_clip.duration >= target_duration:
|
221 |
logger.debug("Audio clip already longer or equal to target. Trimming.")
|
222 |
trimmed_clip = audio_clip.subclip(0, target_duration)
|
223 |
-
# Check trimmed clip validity (should be ok, but good practice)
|
224 |
if trimmed_clip.duration is None or trimmed_clip.duration <= 0:
|
225 |
logger.error("Trimmed audio clip is invalid.")
|
226 |
try: trimmed_clip.close()
|
@@ -232,19 +227,17 @@ def loop_audio_to_length(audio_clip, target_duration):
|
|
232 |
logger.debug(f"Creating {loops} audio loops")
|
233 |
|
234 |
audio_segments = [audio_clip] * loops
|
235 |
-
looped_audio = None
|
236 |
-
final_looped_audio = None
|
237 |
try:
|
238 |
looped_audio = concatenate_audioclips(audio_segments)
|
239 |
|
240 |
-
# Verify the concatenated audio clip is valid
|
241 |
if looped_audio.duration is None or looped_audio.duration <= 0:
|
242 |
logger.error("Concatenated audio clip is invalid (None or zero duration).")
|
243 |
raise ValueError("Invalid concatenated audio.")
|
244 |
|
245 |
final_looped_audio = looped_audio.subclip(0, target_duration)
|
246 |
|
247 |
-
# Verify the final subclipped audio clip is valid
|
248 |
if final_looped_audio.duration is None or final_looped_audio.duration <= 0:
|
249 |
logger.error("Final subclipped audio clip is invalid (None or zero duration).")
|
250 |
raise ValueError("Invalid final subclipped audio.")
|
@@ -253,18 +246,16 @@ def loop_audio_to_length(audio_clip, target_duration):
|
|
253 |
|
254 |
except Exception as e:
|
255 |
logger.error(f"Error concatenating/subclipping audio clips for looping: {str(e)}", exc_info=True)
|
256 |
-
# Fallback: try returning the original clip trimmed if possible
|
257 |
try:
|
258 |
if audio_clip.duration is not None and audio_clip.duration > 0:
|
259 |
logger.warning("Returning original audio clip (may be too short).")
|
260 |
return audio_clip.subclip(0, min(audio_clip.duration, target_duration))
|
261 |
except:
|
262 |
-
pass
|
263 |
logger.error("Fallback to original audio clip failed.")
|
264 |
-
return AudioFileClip(filename="")
|
265 |
|
266 |
finally:
|
267 |
-
# Clean up the temporary concatenated clip if it was created but not returned
|
268 |
if looped_audio is not None and looped_audio is not final_looped_audio:
|
269 |
try: looped_audio.close()
|
270 |
except: pass
|
@@ -332,15 +323,14 @@ def crear_video(prompt_type, input_text, musica_file=None):
|
|
332 |
start_time = datetime.now()
|
333 |
temp_dir_intermediate = None
|
334 |
|
335 |
-
|
336 |
-
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
|
341 |
-
|
342 |
-
|
343 |
-
clips_to_concatenate = [] # Segments extracted from source_clips
|
344 |
|
345 |
try:
|
346 |
# 1. Generate or use script
|
@@ -369,16 +359,14 @@ def crear_video(prompt_type, input_text, musica_file=None):
|
|
369 |
|
370 |
audio_tts_original = AudioFileClip(voz_path)
|
371 |
|
372 |
-
# Verify initial TTS audio clip
|
373 |
if audio_tts_original.reader is None or audio_tts_original.duration is None or audio_tts_original.duration <= 0:
|
374 |
logger.critical("Initial TTS audio clip is invalid (reader is None or duration <= 0).")
|
375 |
-
# Try to close the invalid clip before raising
|
376 |
try: audio_tts_original.close()
|
377 |
except: pass
|
378 |
-
audio_tts_original = None
|
379 |
raise ValueError("Generated voice audio is invalid.")
|
380 |
|
381 |
-
audio_tts = audio_tts_original
|
382 |
audio_duration = audio_tts.duration
|
383 |
logger.info(f"Voice audio duration: {audio_duration:.2f} seconds")
|
384 |
|
@@ -386,7 +374,6 @@ def crear_video(prompt_type, input_text, musica_file=None):
|
|
386 |
logger.error(f"Voice audio duration ({audio_duration:.2f}s) is too short.")
|
387 |
raise ValueError("Generated voice audio is too short (min 1 second required).")
|
388 |
|
389 |
-
|
390 |
# 3. Extract keywords
|
391 |
logger.info("Extracting keywords...")
|
392 |
try:
|
@@ -428,7 +415,6 @@ def crear_video(prompt_type, input_text, musica_file=None):
|
|
428 |
except Exception as e:
|
429 |
logger.warning(f"Error searching generic videos for '{keyword}': {str(e)}")
|
430 |
|
431 |
-
|
432 |
if not videos_data:
|
433 |
logger.error("No videos found on Pexels for any keyword.")
|
434 |
raise ValueError("No suitable videos found on Pexels.")
|
@@ -470,26 +456,23 @@ def crear_video(prompt_type, input_text, musica_file=None):
|
|
470 |
logger.info("Processing and concatenating downloaded videos...")
|
471 |
current_duration = 0
|
472 |
min_clip_duration = 0.5
|
473 |
-
max_clip_segment = 10.0
|
474 |
|
475 |
for i, path in enumerate(video_paths):
|
476 |
-
# Stop if we have enough duration plus a buffer
|
477 |
if current_duration >= audio_duration + max_clip_segment:
|
478 |
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.")
|
479 |
break
|
480 |
|
481 |
-
clip = None
|
482 |
try:
|
483 |
logger.debug(f"[{i+1}/{len(video_paths)}] Opening clip: {path}")
|
484 |
clip = VideoFileClip(path)
|
485 |
-
source_clips.append(clip)
|
486 |
|
487 |
-
# Verify the source clip is valid
|
488 |
if clip.reader is None or clip.duration is None or clip.duration <= 0:
|
489 |
logger.warning(f"[{i+1}/{len(video_paths)}] Source clip {path} seems invalid (reader is None or duration <= 0). Skipping.")
|
490 |
continue
|
491 |
|
492 |
-
# Calculate how much to take from this clip
|
493 |
remaining_needed = audio_duration - current_duration
|
494 |
potential_use_duration = min(clip.duration, max_clip_segment)
|
495 |
|
@@ -500,12 +483,10 @@ def crear_video(prompt_type, input_text, musica_file=None):
|
|
500 |
|
501 |
if segment_duration >= min_clip_duration:
|
502 |
try:
|
503 |
-
# Create a subclip. This creates a *new* clip object.
|
504 |
sub = clip.subclip(0, segment_duration)
|
505 |
-
# Verify the subclip is valid (it should be a VideoFileClip still)
|
506 |
if sub.reader is None or sub.duration is None or sub.duration <= 0:
|
507 |
logger.warning(f"[{i+1}/{len(video_paths)}] Generated subclip from {path} is invalid. Skipping.")
|
508 |
-
try: sub.close()
|
509 |
except: pass
|
510 |
continue
|
511 |
|
@@ -524,7 +505,6 @@ def crear_video(prompt_type, input_text, musica_file=None):
|
|
524 |
except Exception as e:
|
525 |
logger.warning(f"[{i+1}/{len(video_paths)}] Error processing video {path}: {str(e)}", exc_info=True)
|
526 |
continue
|
527 |
-
# Source clips are closed in the main finally block
|
528 |
|
529 |
logger.info(f"Source clip processing finished. Obtained {len(clips_to_concatenate)} valid segments.")
|
530 |
|
@@ -533,13 +513,11 @@ def crear_video(prompt_type, input_text, musica_file=None):
|
|
533 |
raise ValueError("No valid video segments available to create the video.")
|
534 |
|
535 |
logger.info(f"Concatenating {len(clips_to_concatenate)} video segments.")
|
536 |
-
concatenated_base = None
|
537 |
try:
|
538 |
-
# Concatenate the collected valid segments
|
539 |
concatenated_base = concatenate_videoclips(clips_to_concatenate, method="chain")
|
540 |
logger.info(f"Base video duration after initial concatenation: {concatenated_base.duration:.2f}s")
|
541 |
|
542 |
-
# Verify the resulting concatenated clip is valid (CompositeVideoClip)
|
543 |
if concatenated_base is None or concatenated_base.duration is None or concatenated_base.duration <= 0:
|
544 |
logger.critical("Concatenated video base clip is invalid after first concatenation (None or zero duration).")
|
545 |
raise ValueError("Failed to create valid video base from segments.")
|
@@ -548,16 +526,14 @@ def crear_video(prompt_type, input_text, musica_file=None):
|
|
548 |
logger.critical(f"Error during initial concatenation: {str(e)}", exc_info=True)
|
549 |
raise ValueError("Failed during initial video concatenation.")
|
550 |
finally:
|
551 |
-
# IMPORTANT: Close all the individual segments that were concatenated *regardless of success*
|
552 |
for clip_segment in clips_to_concatenate:
|
553 |
try: clip_segment.close()
|
554 |
except: pass
|
555 |
-
clips_to_concatenate = []
|
556 |
|
557 |
-
video_base = concatenated_base
|
558 |
|
559 |
-
|
560 |
-
final_video_base = video_base # Start with the concatenated base
|
561 |
|
562 |
if final_video_base.duration < audio_duration:
|
563 |
logger.info(f"Base video ({final_video_base.duration:.2f}s) is shorter than audio ({audio_duration:.2f}s). Repeating...")
|
@@ -565,12 +541,11 @@ def crear_video(prompt_type, input_text, musica_file=None):
|
|
565 |
num_full_repeats = int(audio_duration // final_video_base.duration)
|
566 |
remaining_duration = audio_duration % final_video_base.duration
|
567 |
|
568 |
-
repeated_clips_list = [final_video_base] * num_full_repeats
|
569 |
|
570 |
if remaining_duration > 0:
|
571 |
try:
|
572 |
remaining_clip = final_video_base.subclip(0, remaining_duration)
|
573 |
-
# Verify remaining clip is valid (should be a CompositeVideoClip from subclip)
|
574 |
if remaining_clip is None or remaining_clip.duration is None or remaining_clip.duration <= 0:
|
575 |
logger.warning(f"Generated subclip for remaining duration {remaining_duration:.2f}s is invalid. Skipping.")
|
576 |
try: remaining_clip.close()
|
@@ -584,58 +559,45 @@ def crear_video(prompt_type, input_text, musica_file=None):
|
|
584 |
|
585 |
if repeated_clips_list:
|
586 |
logger.info(f"Concatenating {len(repeated_clips_list)} parts for repetition.")
|
587 |
-
video_base_repeated = None
|
588 |
try:
|
589 |
-
# Concatenate the repeated parts
|
590 |
-
# If repeated_clips_list contains duplicates of the same object, this is fine for concatenate_videoclips
|
591 |
video_base_repeated = concatenate_videoclips(repeated_clips_list, method="chain")
|
592 |
logger.info(f"Duration of repeated video base: {video_base_repeated.duration:.2f}s")
|
593 |
|
594 |
-
# Verify the repeated clip is valid
|
595 |
if video_base_repeated is None or video_base_repeated.duration is None or video_base_repeated.duration <= 0:
|
596 |
logger.critical("Concatenated repeated video base clip is invalid.")
|
597 |
raise ValueError("Failed to create valid repeated video base.")
|
598 |
|
599 |
-
# Close the old base clip *only if it's different from the new one*
|
600 |
if final_video_base is not video_base_repeated:
|
601 |
try: final_video_base.close()
|
602 |
except: pass
|
603 |
|
604 |
-
# Assign the new valid repeated clip
|
605 |
final_video_base = video_base_repeated
|
606 |
|
607 |
except Exception as e:
|
608 |
logger.critical(f"Error during repetition concatenation: {str(e)}", exc_info=True)
|
609 |
-
# If repetition fails, the error is raised. The original final_video_base will be closed in main finally.
|
610 |
raise ValueError("Failed during video repetition.")
|
611 |
finally:
|
612 |
-
# Close the clips in the repeated list, EXCEPT the one assigned to final_video_base
|
613 |
-
# This needs care as list items might be the same object
|
614 |
if 'repeated_clips_list' in locals():
|
615 |
for clip in repeated_clips_list:
|
616 |
-
# Only close if it's not the final clip and not already closed (MoviePy tracks this)
|
617 |
if clip is not final_video_base:
|
618 |
try: clip.close()
|
619 |
except: pass
|
620 |
|
621 |
|
622 |
-
# After repetition (or if no repetition happened), ensure duration matches audio exactly
|
623 |
if final_video_base.duration > audio_duration:
|
624 |
logger.info(f"Trimming video base ({final_video_base.duration:.2f}s) to match audio duration ({audio_duration:.2f}s).")
|
625 |
-
trimmed_video_base = None
|
626 |
try:
|
627 |
trimmed_video_base = final_video_base.subclip(0, audio_duration)
|
628 |
-
# Verify the trimmed clip is valid
|
629 |
if trimmed_video_base is None or trimmed_video_base.duration is None or trimmed_video_base.duration <= 0:
|
630 |
logger.critical("Trimmed video base clip is invalid.")
|
631 |
raise ValueError("Failed to create valid trimmed video base.")
|
632 |
|
633 |
-
# Close the old clip
|
634 |
if final_video_base is not trimmed_video_base:
|
635 |
try: final_video_base.close()
|
636 |
except: pass
|
637 |
|
638 |
-
# Assign the new valid trimmed clip
|
639 |
final_video_base = trimmed_video_base
|
640 |
|
641 |
except Exception as e:
|
@@ -643,27 +605,25 @@ def crear_video(prompt_type, input_text, musica_file=None):
|
|
643 |
raise ValueError("Failed during video trimming.")
|
644 |
|
645 |
|
646 |
-
# Final check on video_base before setting audio/writing
|
647 |
if final_video_base is None or final_video_base.duration is None or final_video_base.duration <= 0:
|
648 |
logger.critical("Final video base clip is invalid before audio/writing (None or zero duration).")
|
649 |
raise ValueError("Final video base clip is invalid.")
|
650 |
|
651 |
-
# Also check size, as MoviePy needs it for writing
|
652 |
if final_video_base.size is None or final_video_base.size[0] <= 0 or final_video_base.size[1] <= 0:
|
653 |
logger.critical(f"Final video base has invalid size: {final_video_base.size}. Cannot write video.")
|
654 |
raise ValueError("Final video base has invalid size before writing.")
|
655 |
|
656 |
-
video_base = final_video_base
|
657 |
|
658 |
# 6. Handle background music
|
659 |
logger.info("Processing audio...")
|
660 |
|
661 |
-
final_audio =
|
662 |
|
663 |
-
musica_audio_looped = None
|
664 |
|
665 |
if musica_file:
|
666 |
-
musica_audio_original = None
|
667 |
try:
|
668 |
music_path = os.path.join(temp_dir_intermediate, "musica_bg.mp3")
|
669 |
shutil.copyfile(musica_file, music_path)
|
@@ -672,101 +632,77 @@ def crear_video(prompt_type, input_text, musica_file=None):
|
|
672 |
|
673 |
musica_audio_original = AudioFileClip(music_path)
|
674 |
|
675 |
-
# Verify initial music audio clip
|
676 |
if musica_audio_original.reader is None or musica_audio_original.duration is None or musica_audio_original.duration <= 0:
|
677 |
logger.warning("Background music clip seems invalid or has zero duration. Skipping music.")
|
678 |
-
# Close the invalid clip before skipping
|
679 |
try: musica_audio_original.close()
|
680 |
except: pass
|
681 |
-
musica_audio_original = None
|
682 |
else:
|
683 |
musica_audio_looped = loop_audio_to_length(musica_audio_original, video_base.duration)
|
684 |
logger.debug(f"Music adjusted to video duration: {musica_audio_looped.duration:.2f}s")
|
685 |
|
686 |
-
# Verify the looped music clip is valid
|
687 |
if musica_audio_looped is None or musica_audio_looped.duration is None or musica_audio_looped.duration <= 0:
|
688 |
logger.warning("Looped background music clip is invalid. Skipping music.")
|
689 |
-
# Close the invalid looped clip
|
690 |
try: musica_audio_looped.close()
|
691 |
except: pass
|
692 |
-
musica_audio_looped = None
|
693 |
|
694 |
|
695 |
-
if musica_audio_looped:
|
696 |
-
#
|
697 |
-
|
698 |
-
musica_audio_looped.volumex(0.2),
|
699 |
-
|
700 |
])
|
701 |
-
|
702 |
-
if
|
703 |
logger.warning("Composite audio clip is invalid (None or zero duration). Using voice audio only.")
|
704 |
-
|
705 |
-
|
706 |
-
|
707 |
-
|
708 |
-
# Close the invalid composite audio
|
709 |
-
try: final_audio.close()
|
710 |
-
except: pass
|
711 |
-
except: pass # Ignore errors during cleanup
|
712 |
-
final_audio = audio_tts_original # Fallback to the original valid TTS
|
713 |
-
musica_audio = None # Ensure musica_audio variable is None
|
714 |
-
audio_tts = audio_tts_original # Ensure audio_tts variable points to the original valid TTS
|
715 |
-
|
716 |
else:
|
717 |
logger.info("Audio mix completed (voice + music).")
|
718 |
-
|
719 |
-
musica_audio = musica_audio_looped
|
720 |
-
# audio_tts variable already points to original which is handled in main finally
|
721 |
-
|
722 |
|
723 |
except Exception as e:
|
724 |
logger.warning(f"Error processing background music: {str(e)}", exc_info=True)
|
725 |
-
# Fallback to just TTS audio
|
726 |
final_audio = audio_tts_original
|
727 |
-
musica_audio = None
|
728 |
-
audio_tts = audio_tts_original # Ensure variable is original
|
729 |
logger.warning("Using voice audio only due to music processing error.")
|
730 |
|
731 |
|
732 |
-
# Ensure final_audio duration matches video_base duration if possible
|
733 |
-
# Check for significant duration mismatch allowing small floating point differences
|
734 |
if final_audio.duration is not None and abs(final_audio.duration - video_base.duration) > 0.2:
|
735 |
-
logger.warning(f"Final audio duration ({final_audio.duration:.2f}s) differs significantly from video base ({video_base.duration:.2f}s). Attempting trim
|
736 |
try:
|
737 |
-
# Need to create a *new* clip if trimming, and handle closing the old one
|
738 |
if final_audio.duration > video_base.duration:
|
739 |
trimmed_final_audio = final_audio.subclip(0, video_base.duration)
|
740 |
if trimmed_final_audio.duration is None or trimmed_final_audio.duration <= 0:
|
741 |
logger.warning("Trimmed final audio is invalid. Using original final_audio.")
|
742 |
try: trimmed_final_audio.close()
|
743 |
-
except: pass
|
744 |
else:
|
745 |
-
# Safely close the old final_audio if it's different
|
746 |
if final_audio is not trimmed_final_audio:
|
747 |
try: final_audio.close()
|
748 |
except: pass
|
749 |
-
final_audio = trimmed_final_audio
|
750 |
logger.warning("Trimmed final audio to match video duration.")
|
751 |
-
# MoviePy often extends audio automatically if it's too short, so we don't explicitly extend here.
|
752 |
except Exception as e:
|
753 |
logger.warning(f"Error adjusting final audio duration: {str(e)}")
|
754 |
|
755 |
|
756 |
# Final check on video_final before writing
|
757 |
-
# video_final is a composite of video_base and final_audio
|
758 |
video_final = video_base.set_audio(final_audio)
|
759 |
|
760 |
if video_final is None or video_final.duration is None or video_final.duration <= 0:
|
761 |
logger.critical("Final video clip (with audio) is invalid before writing (None or zero duration).")
|
762 |
raise ValueError("Final video clip is invalid before writing.")
|
763 |
|
764 |
-
|
765 |
output_filename = "final_video.mp4"
|
766 |
output_path = os.path.join(temp_dir_intermediate, output_filename)
|
767 |
logger.info(f"Writing final video to: {output_path}")
|
768 |
|
769 |
-
|
770 |
video_final.write_videofile(
|
771 |
output_path,
|
772 |
fps=24,
|
@@ -791,51 +727,34 @@ def crear_video(prompt_type, input_text, musica_file=None):
|
|
791 |
finally:
|
792 |
logger.info("Starting cleanup of clips and intermediate temporary files...")
|
793 |
|
794 |
-
# Close all initially opened source *video* clips
|
795 |
for clip in source_clips:
|
796 |
try: clip.close()
|
797 |
except Exception as e: logger.warning(f"Error closing source video clip in finally: {str(e)}")
|
798 |
|
799 |
-
# Close any video segments left in the list (should be empty if successful)
|
800 |
for clip_segment in clips_to_concatenate:
|
801 |
try: clip_segment.close()
|
802 |
except Exception as e: logger.warning(f"Error closing video segment clip in finally: {str(e)}")
|
803 |
|
804 |
-
# Close the main MoviePy objects if they were successfully created
|
805 |
try:
|
806 |
-
# Close
|
807 |
-
|
808 |
-
if
|
809 |
-
|
810 |
-
|
811 |
-
|
812 |
-
if
|
813 |
-
|
814 |
-
|
815 |
-
|
816 |
-
# Close the potentially modified/trimmed TTS clip if it exists and is different from original
|
817 |
-
if audio_tts is not None and audio_tts is not audio_tts_original:
|
818 |
-
try: audio_tts.close()
|
819 |
-
except Exception as e: logger.warning(f"Error closing audio_tts (modified) in finally: {str(e)}")
|
820 |
-
# Close the original TTS clip if it exists (it's the base)
|
821 |
-
if audio_tts_original is not None:
|
822 |
-
try: audio_tts_original.close()
|
823 |
-
except Exception as e: logger.warning(f"Error closing audio_tts_original in finally: {str(e)}")
|
824 |
-
|
825 |
-
# Close video_final first, which should cascade to video_base and final_audio (and their components)
|
826 |
if video_final is not None:
|
827 |
try: video_final.close()
|
828 |
except Exception as e: logger.warning(f"Error closing video_final in finally: {str(e)}")
|
829 |
-
# If video_final wasn't created but video_base was (due to error before set_audio), close video_base
|
830 |
elif video_base is not None:
|
831 |
try: video_base.close()
|
832 |
except Exception as e: logger.warning(f"Error closing video_base in finally: {str(e)}")
|
833 |
|
834 |
-
|
835 |
except Exception as e:
|
836 |
logger.warning(f"Error during final clip closing in finally: {str(e)}")
|
837 |
|
838 |
-
# Clean up intermediate files, but NOT the final video file
|
839 |
if temp_dir_intermediate and os.path.exists(temp_dir_intermediate):
|
840 |
final_output_in_temp = os.path.join(temp_dir_intermediate, "final_video.mp4")
|
841 |
|
@@ -856,9 +775,15 @@ def run_app(prompt_type, prompt_ia, prompt_manual, musica_file):
|
|
856 |
|
857 |
input_text = prompt_ia if prompt_type == "Generar Guion con IA" else prompt_manual
|
858 |
|
|
|
|
|
|
|
|
|
|
|
859 |
if not input_text or not input_text.strip():
|
860 |
logger.warning("Empty input text.")
|
861 |
-
|
|
|
862 |
|
863 |
logger.info(f"Input Type: {prompt_type}")
|
864 |
logger.debug(f"Input Text: '{input_text[:100]}...'")
|
@@ -874,19 +799,26 @@ def run_app(prompt_type, prompt_ia, prompt_manual, musica_file):
|
|
874 |
if video_path and os.path.exists(video_path):
|
875 |
logger.info(f"crear_video returned path: {video_path}")
|
876 |
logger.info(f"Size of returned video file: {os.path.getsize(video_path)} bytes")
|
877 |
-
|
|
|
|
|
878 |
else:
|
879 |
logger.error(f"crear_video did not return a valid path or file does not exist: {video_path}")
|
880 |
-
|
|
|
881 |
|
882 |
except ValueError as ve:
|
883 |
logger.warning(f"Validation error during video creation: {str(ve)}")
|
884 |
-
|
|
|
885 |
except Exception as e:
|
886 |
logger.critical(f"Critical error during video creation: {str(e)}", exc_info=True)
|
887 |
-
|
|
|
888 |
finally:
|
889 |
logger.info("End of run_app handler.")
|
|
|
|
|
890 |
|
891 |
|
892 |
# Gradio Interface
|
@@ -935,10 +867,16 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
|
|
935 |
|
936 |
with gr.Column():
|
937 |
video_output = gr.Video(
|
938 |
-
label="Generated Video",
|
939 |
interactive=False,
|
940 |
height=400
|
941 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
942 |
status_output = gr.Textbox(
|
943 |
label="Status",
|
944 |
interactive=False,
|
@@ -954,16 +892,25 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
|
|
954 |
outputs=[ia_guion_column, manual_guion_column]
|
955 |
)
|
956 |
|
|
|
957 |
generate_btn.click(
|
958 |
-
|
959 |
-
|
|
|
960 |
queue=True,
|
961 |
).then(
|
|
|
962 |
run_app,
|
963 |
inputs=[prompt_type, prompt_ia, prompt_manual, musica_input],
|
964 |
-
outputs=[video_output, status_output]
|
|
|
|
|
|
|
|
|
|
|
965 |
)
|
966 |
|
|
|
967 |
gr.Markdown("### Instructions:")
|
968 |
gr.Markdown("""
|
969 |
1. **Pexels API Key:** Ensure you have set the `PEXELS_API_KEY` environment variable.
|
@@ -973,17 +920,18 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
|
|
973 |
3. **Upload Music** (optional): Select an audio file (MP3, WAV, etc.) for background music.
|
974 |
4. **Click "✨ Generate Video"**.
|
975 |
5. Wait for the video to process. Processing time may vary. Check the status box.
|
976 |
-
6.
|
|
|
977 |
""")
|
978 |
gr.Markdown("---")
|
979 |
-
gr.Markdown("Developed by [Your Name/Company/Alias -
|
980 |
|
981 |
if __name__ == "__main__":
|
982 |
logger.info("Verifying critical dependencies...")
|
983 |
try:
|
984 |
from moviepy.editor import ColorClip
|
985 |
try:
|
986 |
-
temp_clip = ColorClip((100,100), color=(255,0,0), duration=0.1)
|
987 |
temp_clip.close()
|
988 |
logger.info("MoviePy base clips (like ColorClip) created and closed successfully. FFmpeg seems accessible.")
|
989 |
except Exception as e:
|
|
|
34 |
PEXELS_API_KEY = os.environ.get("PEXELS_API_KEY")
|
35 |
if not PEXELS_API_KEY:
|
36 |
logger.critical("PEXELS_API_KEY environment variable not found.")
|
|
|
37 |
# raise ValueError("Pexels API key not configured")
|
38 |
|
39 |
# Model Initialization
|
|
|
54 |
logger.info("Loading KeyBERT model...")
|
55 |
kw_model = None
|
56 |
try:
|
57 |
+
kw_model = KeyBERT('distilbert-base-multilingual-cased')
|
58 |
logger.info("KeyBERT initialized successfully")
|
59 |
except Exception as e:
|
60 |
logger.error(f"FAILURE loading KeyBERT: {str(e)}", exc_info=True)
|
|
|
204 |
def loop_audio_to_length(audio_clip, target_duration):
|
205 |
logger.debug(f"Adjusting audio | Current duration: {audio_clip.duration:.2f}s | Target: {target_duration:.2f}s")
|
206 |
|
|
|
207 |
if audio_clip is None or audio_clip.duration is None or audio_clip.duration <= 0:
|
208 |
logger.warning("Input audio clip is invalid (None or zero duration), cannot loop.")
|
|
|
209 |
try:
|
210 |
+
sr = getattr(audio_clip, 'fps', 44100) if audio_clip else 44100
|
|
|
211 |
return AudioClip(lambda t: 0, duration=target_duration, sr=sr)
|
212 |
except Exception as e:
|
213 |
logger.error(f"Could not create silence clip: {e}", exc_info=True)
|
214 |
+
return AudioFileClip(filename="")
|
215 |
|
216 |
if audio_clip.duration >= target_duration:
|
217 |
logger.debug("Audio clip already longer or equal to target. Trimming.")
|
218 |
trimmed_clip = audio_clip.subclip(0, target_duration)
|
|
|
219 |
if trimmed_clip.duration is None or trimmed_clip.duration <= 0:
|
220 |
logger.error("Trimmed audio clip is invalid.")
|
221 |
try: trimmed_clip.close()
|
|
|
227 |
logger.debug(f"Creating {loops} audio loops")
|
228 |
|
229 |
audio_segments = [audio_clip] * loops
|
230 |
+
looped_audio = None
|
231 |
+
final_looped_audio = None
|
232 |
try:
|
233 |
looped_audio = concatenate_audioclips(audio_segments)
|
234 |
|
|
|
235 |
if looped_audio.duration is None or looped_audio.duration <= 0:
|
236 |
logger.error("Concatenated audio clip is invalid (None or zero duration).")
|
237 |
raise ValueError("Invalid concatenated audio.")
|
238 |
|
239 |
final_looped_audio = looped_audio.subclip(0, target_duration)
|
240 |
|
|
|
241 |
if final_looped_audio.duration is None or final_looped_audio.duration <= 0:
|
242 |
logger.error("Final subclipped audio clip is invalid (None or zero duration).")
|
243 |
raise ValueError("Invalid final subclipped audio.")
|
|
|
246 |
|
247 |
except Exception as e:
|
248 |
logger.error(f"Error concatenating/subclipping audio clips for looping: {str(e)}", exc_info=True)
|
|
|
249 |
try:
|
250 |
if audio_clip.duration is not None and audio_clip.duration > 0:
|
251 |
logger.warning("Returning original audio clip (may be too short).")
|
252 |
return audio_clip.subclip(0, min(audio_clip.duration, target_duration))
|
253 |
except:
|
254 |
+
pass
|
255 |
logger.error("Fallback to original audio clip failed.")
|
256 |
+
return AudioFileClip(filename="")
|
257 |
|
258 |
finally:
|
|
|
259 |
if looped_audio is not None and looped_audio is not final_looped_audio:
|
260 |
try: looped_audio.close()
|
261 |
except: pass
|
|
|
323 |
start_time = datetime.now()
|
324 |
temp_dir_intermediate = None
|
325 |
|
326 |
+
audio_tts_original = None
|
327 |
+
musica_audio_original = None
|
328 |
+
audio_tts = None
|
329 |
+
musica_audio = None
|
330 |
+
video_base = None
|
331 |
+
video_final = None
|
332 |
+
source_clips = []
|
333 |
+
clips_to_concatenate = []
|
|
|
334 |
|
335 |
try:
|
336 |
# 1. Generate or use script
|
|
|
359 |
|
360 |
audio_tts_original = AudioFileClip(voz_path)
|
361 |
|
|
|
362 |
if audio_tts_original.reader is None or audio_tts_original.duration is None or audio_tts_original.duration <= 0:
|
363 |
logger.critical("Initial TTS audio clip is invalid (reader is None or duration <= 0).")
|
|
|
364 |
try: audio_tts_original.close()
|
365 |
except: pass
|
366 |
+
audio_tts_original = None
|
367 |
raise ValueError("Generated voice audio is invalid.")
|
368 |
|
369 |
+
audio_tts = audio_tts_original
|
370 |
audio_duration = audio_tts.duration
|
371 |
logger.info(f"Voice audio duration: {audio_duration:.2f} seconds")
|
372 |
|
|
|
374 |
logger.error(f"Voice audio duration ({audio_duration:.2f}s) is too short.")
|
375 |
raise ValueError("Generated voice audio is too short (min 1 second required).")
|
376 |
|
|
|
377 |
# 3. Extract keywords
|
378 |
logger.info("Extracting keywords...")
|
379 |
try:
|
|
|
415 |
except Exception as e:
|
416 |
logger.warning(f"Error searching generic videos for '{keyword}': {str(e)}")
|
417 |
|
|
|
418 |
if not videos_data:
|
419 |
logger.error("No videos found on Pexels for any keyword.")
|
420 |
raise ValueError("No suitable videos found on Pexels.")
|
|
|
456 |
logger.info("Processing and concatenating downloaded videos...")
|
457 |
current_duration = 0
|
458 |
min_clip_duration = 0.5
|
459 |
+
max_clip_segment = 10.0
|
460 |
|
461 |
for i, path in enumerate(video_paths):
|
|
|
462 |
if current_duration >= audio_duration + max_clip_segment:
|
463 |
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.")
|
464 |
break
|
465 |
|
466 |
+
clip = None
|
467 |
try:
|
468 |
logger.debug(f"[{i+1}/{len(video_paths)}] Opening clip: {path}")
|
469 |
clip = VideoFileClip(path)
|
470 |
+
source_clips.append(clip)
|
471 |
|
|
|
472 |
if clip.reader is None or clip.duration is None or clip.duration <= 0:
|
473 |
logger.warning(f"[{i+1}/{len(video_paths)}] Source clip {path} seems invalid (reader is None or duration <= 0). Skipping.")
|
474 |
continue
|
475 |
|
|
|
476 |
remaining_needed = audio_duration - current_duration
|
477 |
potential_use_duration = min(clip.duration, max_clip_segment)
|
478 |
|
|
|
483 |
|
484 |
if segment_duration >= min_clip_duration:
|
485 |
try:
|
|
|
486 |
sub = clip.subclip(0, segment_duration)
|
|
|
487 |
if sub.reader is None or sub.duration is None or sub.duration <= 0:
|
488 |
logger.warning(f"[{i+1}/{len(video_paths)}] Generated subclip from {path} is invalid. Skipping.")
|
489 |
+
try: sub.close()
|
490 |
except: pass
|
491 |
continue
|
492 |
|
|
|
505 |
except Exception as e:
|
506 |
logger.warning(f"[{i+1}/{len(video_paths)}] Error processing video {path}: {str(e)}", exc_info=True)
|
507 |
continue
|
|
|
508 |
|
509 |
logger.info(f"Source clip processing finished. Obtained {len(clips_to_concatenate)} valid segments.")
|
510 |
|
|
|
513 |
raise ValueError("No valid video segments available to create the video.")
|
514 |
|
515 |
logger.info(f"Concatenating {len(clips_to_concatenate)} video segments.")
|
516 |
+
concatenated_base = None
|
517 |
try:
|
|
|
518 |
concatenated_base = concatenate_videoclips(clips_to_concatenate, method="chain")
|
519 |
logger.info(f"Base video duration after initial concatenation: {concatenated_base.duration:.2f}s")
|
520 |
|
|
|
521 |
if concatenated_base is None or concatenated_base.duration is None or concatenated_base.duration <= 0:
|
522 |
logger.critical("Concatenated video base clip is invalid after first concatenation (None or zero duration).")
|
523 |
raise ValueError("Failed to create valid video base from segments.")
|
|
|
526 |
logger.critical(f"Error during initial concatenation: {str(e)}", exc_info=True)
|
527 |
raise ValueError("Failed during initial video concatenation.")
|
528 |
finally:
|
|
|
529 |
for clip_segment in clips_to_concatenate:
|
530 |
try: clip_segment.close()
|
531 |
except: pass
|
532 |
+
clips_to_concatenate = []
|
533 |
|
534 |
+
video_base = concatenated_base
|
535 |
|
536 |
+
final_video_base = video_base
|
|
|
537 |
|
538 |
if final_video_base.duration < audio_duration:
|
539 |
logger.info(f"Base video ({final_video_base.duration:.2f}s) is shorter than audio ({audio_duration:.2f}s). Repeating...")
|
|
|
541 |
num_full_repeats = int(audio_duration // final_video_base.duration)
|
542 |
remaining_duration = audio_duration % final_video_base.duration
|
543 |
|
544 |
+
repeated_clips_list = [final_video_base] * num_full_repeats
|
545 |
|
546 |
if remaining_duration > 0:
|
547 |
try:
|
548 |
remaining_clip = final_video_base.subclip(0, remaining_duration)
|
|
|
549 |
if remaining_clip is None or remaining_clip.duration is None or remaining_clip.duration <= 0:
|
550 |
logger.warning(f"Generated subclip for remaining duration {remaining_duration:.2f}s is invalid. Skipping.")
|
551 |
try: remaining_clip.close()
|
|
|
559 |
|
560 |
if repeated_clips_list:
|
561 |
logger.info(f"Concatenating {len(repeated_clips_list)} parts for repetition.")
|
562 |
+
video_base_repeated = None
|
563 |
try:
|
|
|
|
|
564 |
video_base_repeated = concatenate_videoclips(repeated_clips_list, method="chain")
|
565 |
logger.info(f"Duration of repeated video base: {video_base_repeated.duration:.2f}s")
|
566 |
|
|
|
567 |
if video_base_repeated is None or video_base_repeated.duration is None or video_base_repeated.duration <= 0:
|
568 |
logger.critical("Concatenated repeated video base clip is invalid.")
|
569 |
raise ValueError("Failed to create valid repeated video base.")
|
570 |
|
|
|
571 |
if final_video_base is not video_base_repeated:
|
572 |
try: final_video_base.close()
|
573 |
except: pass
|
574 |
|
|
|
575 |
final_video_base = video_base_repeated
|
576 |
|
577 |
except Exception as e:
|
578 |
logger.critical(f"Error during repetition concatenation: {str(e)}", exc_info=True)
|
|
|
579 |
raise ValueError("Failed during video repetition.")
|
580 |
finally:
|
|
|
|
|
581 |
if 'repeated_clips_list' in locals():
|
582 |
for clip in repeated_clips_list:
|
|
|
583 |
if clip is not final_video_base:
|
584 |
try: clip.close()
|
585 |
except: pass
|
586 |
|
587 |
|
|
|
588 |
if final_video_base.duration > audio_duration:
|
589 |
logger.info(f"Trimming video base ({final_video_base.duration:.2f}s) to match audio duration ({audio_duration:.2f}s).")
|
590 |
+
trimmed_video_base = None
|
591 |
try:
|
592 |
trimmed_video_base = final_video_base.subclip(0, audio_duration)
|
|
|
593 |
if trimmed_video_base is None or trimmed_video_base.duration is None or trimmed_video_base.duration <= 0:
|
594 |
logger.critical("Trimmed video base clip is invalid.")
|
595 |
raise ValueError("Failed to create valid trimmed video base.")
|
596 |
|
|
|
597 |
if final_video_base is not trimmed_video_base:
|
598 |
try: final_video_base.close()
|
599 |
except: pass
|
600 |
|
|
|
601 |
final_video_base = trimmed_video_base
|
602 |
|
603 |
except Exception as e:
|
|
|
605 |
raise ValueError("Failed during video trimming.")
|
606 |
|
607 |
|
|
|
608 |
if final_video_base is None or final_video_base.duration is None or final_video_base.duration <= 0:
|
609 |
logger.critical("Final video base clip is invalid before audio/writing (None or zero duration).")
|
610 |
raise ValueError("Final video base clip is invalid.")
|
611 |
|
|
|
612 |
if final_video_base.size is None or final_video_base.size[0] <= 0 or final_video_base.size[1] <= 0:
|
613 |
logger.critical(f"Final video base has invalid size: {final_video_base.size}. Cannot write video.")
|
614 |
raise ValueError("Final video base has invalid size before writing.")
|
615 |
|
616 |
+
video_base = final_video_base
|
617 |
|
618 |
# 6. Handle background music
|
619 |
logger.info("Processing audio...")
|
620 |
|
621 |
+
final_audio = audio_tts_original # Start with the original valid TTS audio
|
622 |
|
623 |
+
musica_audio_looped = None
|
624 |
|
625 |
if musica_file:
|
626 |
+
musica_audio_original = None
|
627 |
try:
|
628 |
music_path = os.path.join(temp_dir_intermediate, "musica_bg.mp3")
|
629 |
shutil.copyfile(musica_file, music_path)
|
|
|
632 |
|
633 |
musica_audio_original = AudioFileClip(music_path)
|
634 |
|
|
|
635 |
if musica_audio_original.reader is None or musica_audio_original.duration is None or musica_audio_original.duration <= 0:
|
636 |
logger.warning("Background music clip seems invalid or has zero duration. Skipping music.")
|
|
|
637 |
try: musica_audio_original.close()
|
638 |
except: pass
|
639 |
+
musica_audio_original = None
|
640 |
else:
|
641 |
musica_audio_looped = loop_audio_to_length(musica_audio_original, video_base.duration)
|
642 |
logger.debug(f"Music adjusted to video duration: {musica_audio_looped.duration:.2f}s")
|
643 |
|
|
|
644 |
if musica_audio_looped is None or musica_audio_looped.duration is None or musica_audio_looped.duration <= 0:
|
645 |
logger.warning("Looped background music clip is invalid. Skipping music.")
|
|
|
646 |
try: musica_audio_looped.close()
|
647 |
except: pass
|
648 |
+
musica_audio_looped = None
|
649 |
|
650 |
|
651 |
+
if musica_audio_looped:
|
652 |
+
# Use the looped music and the current audio_tts (which is the original)
|
653 |
+
composite_audio = CompositeAudioClip([
|
654 |
+
musica_audio_looped.volumex(0.2),
|
655 |
+
audio_tts_original.volumex(1.0)
|
656 |
])
|
657 |
+
|
658 |
+
if composite_audio.duration is None or composite_audio.duration <= 0:
|
659 |
logger.warning("Composite audio clip is invalid (None or zero duration). Using voice audio only.")
|
660 |
+
try: composite_audio.close()
|
661 |
+
except: pass
|
662 |
+
# Components were likely closed by composite_audio.close() or will be in main finally
|
663 |
+
final_audio = audio_tts_original # Fallback
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
664 |
else:
|
665 |
logger.info("Audio mix completed (voice + music).")
|
666 |
+
final_audio = composite_audio # Use the valid composite audio
|
667 |
+
musica_audio = musica_audio_looped # Assign for cleanup
|
|
|
|
|
668 |
|
669 |
except Exception as e:
|
670 |
logger.warning(f"Error processing background music: {str(e)}", exc_info=True)
|
|
|
671 |
final_audio = audio_tts_original
|
672 |
+
musica_audio = None
|
|
|
673 |
logger.warning("Using voice audio only due to music processing error.")
|
674 |
|
675 |
|
|
|
|
|
676 |
if final_audio.duration is not None and abs(final_audio.duration - video_base.duration) > 0.2:
|
677 |
+
logger.warning(f"Final audio duration ({final_audio.duration:.2f}s) differs significantly from video base ({video_base.duration:.2f}s). Attempting trim.")
|
678 |
try:
|
|
|
679 |
if final_audio.duration > video_base.duration:
|
680 |
trimmed_final_audio = final_audio.subclip(0, video_base.duration)
|
681 |
if trimmed_final_audio.duration is None or trimmed_final_audio.duration <= 0:
|
682 |
logger.warning("Trimmed final audio is invalid. Using original final_audio.")
|
683 |
try: trimmed_final_audio.close()
|
684 |
+
except: pass
|
685 |
else:
|
|
|
686 |
if final_audio is not trimmed_final_audio:
|
687 |
try: final_audio.close()
|
688 |
except: pass
|
689 |
+
final_audio = trimmed_final_audio
|
690 |
logger.warning("Trimmed final audio to match video duration.")
|
|
|
691 |
except Exception as e:
|
692 |
logger.warning(f"Error adjusting final audio duration: {str(e)}")
|
693 |
|
694 |
|
695 |
# Final check on video_final before writing
|
|
|
696 |
video_final = video_base.set_audio(final_audio)
|
697 |
|
698 |
if video_final is None or video_final.duration is None or video_final.duration <= 0:
|
699 |
logger.critical("Final video clip (with audio) is invalid before writing (None or zero duration).")
|
700 |
raise ValueError("Final video clip is invalid before writing.")
|
701 |
|
|
|
702 |
output_filename = "final_video.mp4"
|
703 |
output_path = os.path.join(temp_dir_intermediate, output_filename)
|
704 |
logger.info(f"Writing final video to: {output_path}")
|
705 |
|
|
|
706 |
video_final.write_videofile(
|
707 |
output_path,
|
708 |
fps=24,
|
|
|
727 |
finally:
|
728 |
logger.info("Starting cleanup of clips and intermediate temporary files...")
|
729 |
|
|
|
730 |
for clip in source_clips:
|
731 |
try: clip.close()
|
732 |
except Exception as e: logger.warning(f"Error closing source video clip in finally: {str(e)}")
|
733 |
|
|
|
734 |
for clip_segment in clips_to_concatenate:
|
735 |
try: clip_segment.close()
|
736 |
except Exception as e: logger.warning(f"Error closing video segment clip in finally: {str(e)}")
|
737 |
|
|
|
738 |
try:
|
739 |
+
# Close audio clips: looped music, original music, then final audio (which might close its components)
|
740 |
+
if musica_audio is not None: try: musica_audio.close() except Exception as e: logger.warning(f"Error closing musica_audio in finally: {str(e)}")
|
741 |
+
if musica_audio_original is not None and musica_audio_original is not musica_audio: try: musica_audio_original.close() except Exception as e: logger.warning(f"Error closing musica_audio_original in finally: {str(e)}")
|
742 |
+
|
743 |
+
# Close TTS clips: potentially modified/trimmed TTS, then original TTS
|
744 |
+
if audio_tts is not None and audio_tts is not audio_tts_original: try: audio_tts.close() except Exception as e: logger.warning(f"Error closing audio_tts (modified) in finally: {str(e)}")
|
745 |
+
if audio_tts_original is not None: try: audio_tts_original.close() except Exception as e: logger.warning(f"Error closing audio_tts_original in finally: {str(e)}")
|
746 |
+
|
747 |
+
# Close video clips: final video (should cascade), then video base if it wasn't the final
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
748 |
if video_final is not None:
|
749 |
try: video_final.close()
|
750 |
except Exception as e: logger.warning(f"Error closing video_final in finally: {str(e)}")
|
|
|
751 |
elif video_base is not None:
|
752 |
try: video_base.close()
|
753 |
except Exception as e: logger.warning(f"Error closing video_base in finally: {str(e)}")
|
754 |
|
|
|
755 |
except Exception as e:
|
756 |
logger.warning(f"Error during final clip closing in finally: {str(e)}")
|
757 |
|
|
|
758 |
if temp_dir_intermediate and os.path.exists(temp_dir_intermediate):
|
759 |
final_output_in_temp = os.path.join(temp_dir_intermediate, "final_video.mp4")
|
760 |
|
|
|
775 |
|
776 |
input_text = prompt_ia if prompt_type == "Generar Guion con IA" else prompt_manual
|
777 |
|
778 |
+
# Initialize outputs to None and default status
|
779 |
+
output_video = None
|
780 |
+
output_file = None
|
781 |
+
status_msg = gr.update(value="⏳ Procesando...", interactive=False)
|
782 |
+
|
783 |
if not input_text or not input_text.strip():
|
784 |
logger.warning("Empty input text.")
|
785 |
+
# Return None for video and file, update status
|
786 |
+
return None, None, gr.update(value="⚠️ Por favor, ingresa texto para el guion o el tema.", interactive=False)
|
787 |
|
788 |
logger.info(f"Input Type: {prompt_type}")
|
789 |
logger.debug(f"Input Text: '{input_text[:100]}...'")
|
|
|
799 |
if video_path and os.path.exists(video_path):
|
800 |
logger.info(f"crear_video returned path: {video_path}")
|
801 |
logger.info(f"Size of returned video file: {os.path.getsize(video_path)} bytes")
|
802 |
+
output_video = video_path # Set video component value
|
803 |
+
output_file = video_path # Set file component value for download
|
804 |
+
status_msg = gr.update(value="✅ Video generado exitosamente.", interactive=False)
|
805 |
else:
|
806 |
logger.error(f"crear_video did not return a valid path or file does not exist: {video_path}")
|
807 |
+
# Leave video and file outputs as None
|
808 |
+
status_msg = gr.update(value="❌ Error: La generación del video falló o el archivo no se creó correctamente.", interactive=False)
|
809 |
|
810 |
except ValueError as ve:
|
811 |
logger.warning(f"Validation error during video creation: {str(ve)}")
|
812 |
+
# Leave video and file outputs as None
|
813 |
+
status_msg = gr.update(value=f"⚠️ Error de validación: {str(ve)}", interactive=False)
|
814 |
except Exception as e:
|
815 |
logger.critical(f"Critical error during video creation: {str(e)}", exc_info=True)
|
816 |
+
# Leave video and file outputs as None
|
817 |
+
status_msg = gr.update(value=f"❌ Error inesperado: {str(e)}", interactive=False)
|
818 |
finally:
|
819 |
logger.info("End of run_app handler.")
|
820 |
+
# Return all three outputs
|
821 |
+
return output_video, output_file, status_msg
|
822 |
|
823 |
|
824 |
# Gradio Interface
|
|
|
867 |
|
868 |
with gr.Column():
|
869 |
video_output = gr.Video(
|
870 |
+
label="Generated Video Preview", # Changed label
|
871 |
interactive=False,
|
872 |
height=400
|
873 |
)
|
874 |
+
# Add the File component for download
|
875 |
+
file_output = gr.File(
|
876 |
+
label="Download Video",
|
877 |
+
interactive=False, # Not interactive for user upload
|
878 |
+
visible=False # Hide initially
|
879 |
+
)
|
880 |
status_output = gr.Textbox(
|
881 |
label="Status",
|
882 |
interactive=False,
|
|
|
892 |
outputs=[ia_guion_column, manual_guion_column]
|
893 |
)
|
894 |
|
895 |
+
# Modify the click event to return 3 outputs
|
896 |
generate_btn.click(
|
897 |
+
# Action 1: Reset outputs and set status to processing
|
898 |
+
lambda: (None, None, gr.update(value="⏳ Procesando... Esto puede tomar 2-5 minutos.", interactive=False)),
|
899 |
+
outputs=[video_output, file_output, status_output],
|
900 |
queue=True,
|
901 |
).then(
|
902 |
+
# Action 2: Call the main processing function
|
903 |
run_app,
|
904 |
inputs=[prompt_type, prompt_ia, prompt_manual, musica_input],
|
905 |
+
outputs=[video_output, file_output, status_output] # Match the 3 outputs
|
906 |
+
).then(
|
907 |
+
# Action 3: Make the download link visible if a file was returned
|
908 |
+
lambda video_path: gr.update(visible=video_path is not None), # Check if video_output has a value
|
909 |
+
inputs=[video_output], # Use video_output as input to check if generation was successful
|
910 |
+
outputs=[file_output] # Update visibility of file_output
|
911 |
)
|
912 |
|
913 |
+
|
914 |
gr.Markdown("### Instructions:")
|
915 |
gr.Markdown("""
|
916 |
1. **Pexels API Key:** Ensure you have set the `PEXELS_API_KEY` environment variable.
|
|
|
920 |
3. **Upload Music** (optional): Select an audio file (MP3, WAV, etc.) for background music.
|
921 |
4. **Click "✨ Generate Video"**.
|
922 |
5. Wait for the video to process. Processing time may vary. Check the status box.
|
923 |
+
6. The generated video will appear above, and a download link will show if successful.
|
924 |
+
7. If there are errors, check the `video_generator_full.log` file for details.
|
925 |
""")
|
926 |
gr.Markdown("---")
|
927 |
+
gr.Markdown("Developed by [Your Name/Company/Alias - Opcional]")
|
928 |
|
929 |
if __name__ == "__main__":
|
930 |
logger.info("Verifying critical dependencies...")
|
931 |
try:
|
932 |
from moviepy.editor import ColorClip
|
933 |
try:
|
934 |
+
temp_clip = ColorClip((100,100), color=(255,0,0), duration=0.1)
|
935 |
temp_clip.close()
|
936 |
logger.info("MoviePy base clips (like ColorClip) created and closed successfully. FFmpeg seems accessible.")
|
937 |
except Exception as e:
|