Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -34,6 +34,7 @@ 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 |
# raise ValueError("Pexels API key not configured")
|
| 38 |
|
| 39 |
# Model Initialization
|
|
@@ -618,7 +619,7 @@ def crear_video(prompt_type, input_text, musica_file=None):
|
|
| 618 |
# 6. Handle background music
|
| 619 |
logger.info("Processing audio...")
|
| 620 |
|
| 621 |
-
final_audio = audio_tts_original
|
| 622 |
|
| 623 |
musica_audio_looped = None
|
| 624 |
|
|
@@ -649,7 +650,6 @@ def crear_video(prompt_type, input_text, musica_file=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)
|
|
@@ -659,12 +659,11 @@ def crear_video(prompt_type, input_text, musica_file=None):
|
|
| 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 |
-
|
| 663 |
-
final_audio = audio_tts_original # Fallback
|
| 664 |
else:
|
| 665 |
logger.info("Audio mix completed (voice + music).")
|
| 666 |
-
final_audio = composite_audio
|
| 667 |
-
musica_audio = musica_audio_looped
|
| 668 |
|
| 669 |
except Exception as e:
|
| 670 |
logger.warning(f"Error processing background music: {str(e)}", exc_info=True)
|
|
@@ -692,13 +691,13 @@ def crear_video(prompt_type, input_text, musica_file=None):
|
|
| 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}")
|
|
@@ -728,33 +727,60 @@ def crear_video(prompt_type, input_text, musica_file=None):
|
|
| 728 |
logger.info("Starting cleanup of clips and intermediate temporary files...")
|
| 729 |
|
| 730 |
for clip in source_clips:
|
| 731 |
-
try:
|
| 732 |
-
|
|
|
|
|
|
|
| 733 |
|
| 734 |
for clip_segment in clips_to_concatenate:
|
| 735 |
-
|
| 736 |
-
|
|
|
|
|
|
|
| 737 |
|
| 738 |
-
|
| 739 |
-
|
| 740 |
-
|
| 741 |
-
|
| 742 |
-
|
| 743 |
-
|
| 744 |
-
|
| 745 |
-
|
| 746 |
-
|
| 747 |
-
|
| 748 |
-
|
| 749 |
-
|
| 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 |
-
|
| 756 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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,14 +801,12 @@ def run_app(prompt_type, prompt_ia, prompt_manual, musica_file):
|
|
| 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}")
|
|
@@ -799,25 +823,21 @@ def run_app(prompt_type, prompt_ia, prompt_manual, musica_file):
|
|
| 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
|
| 803 |
-
output_file = video_path
|
| 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 |
|
|
@@ -867,15 +887,14 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
|
|
| 867 |
|
| 868 |
with gr.Column():
|
| 869 |
video_output = gr.Video(
|
| 870 |
-
label="Generated Video Preview",
|
| 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,
|
| 878 |
-
visible=False
|
| 879 |
)
|
| 880 |
status_output = gr.Textbox(
|
| 881 |
label="Status",
|
|
@@ -892,22 +911,18 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
|
|
| 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]
|
| 906 |
).then(
|
| 907 |
-
|
| 908 |
-
|
| 909 |
-
|
| 910 |
-
outputs=[file_output] # Update visibility of file_output
|
| 911 |
)
|
| 912 |
|
| 913 |
|
|
|
|
| 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
|
|
|
|
| 619 |
# 6. Handle background music
|
| 620 |
logger.info("Processing audio...")
|
| 621 |
|
| 622 |
+
final_audio = audio_tts_original
|
| 623 |
|
| 624 |
musica_audio_looped = None
|
| 625 |
|
|
|
|
| 650 |
|
| 651 |
|
| 652 |
if musica_audio_looped:
|
|
|
|
| 653 |
composite_audio = CompositeAudioClip([
|
| 654 |
musica_audio_looped.volumex(0.2),
|
| 655 |
audio_tts_original.volumex(1.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 |
+
final_audio = audio_tts_original
|
|
|
|
| 663 |
else:
|
| 664 |
logger.info("Audio mix completed (voice + music).")
|
| 665 |
+
final_audio = composite_audio
|
| 666 |
+
musica_audio = musica_audio_looped
|
| 667 |
|
| 668 |
except Exception as e:
|
| 669 |
logger.warning(f"Error processing background music: {str(e)}", exc_info=True)
|
|
|
|
| 691 |
logger.warning(f"Error adjusting final audio duration: {str(e)}")
|
| 692 |
|
| 693 |
|
|
|
|
| 694 |
video_final = video_base.set_audio(final_audio)
|
| 695 |
|
| 696 |
if video_final is None or video_final.duration is None or video_final.duration <= 0:
|
| 697 |
logger.critical("Final video clip (with audio) is invalid before writing (None or zero duration).")
|
| 698 |
raise ValueError("Final video clip is invalid before writing.")
|
| 699 |
|
| 700 |
+
|
| 701 |
output_filename = "final_video.mp4"
|
| 702 |
output_path = os.path.join(temp_dir_intermediate, output_filename)
|
| 703 |
logger.info(f"Writing final video to: {output_path}")
|
|
|
|
| 727 |
logger.info("Starting cleanup of clips and intermediate temporary files...")
|
| 728 |
|
| 729 |
for clip in source_clips:
|
| 730 |
+
try:
|
| 731 |
+
clip.close()
|
| 732 |
+
except Exception as e:
|
| 733 |
+
logger.warning(f"Error closing source video clip in finally: {str(e)}")
|
| 734 |
|
| 735 |
for clip_segment in clips_to_concatenate:
|
| 736 |
+
try:
|
| 737 |
+
clip_segment.close()
|
| 738 |
+
except Exception as e:
|
| 739 |
+
logger.warning(f"Error closing video segment clip in finally: {str(e)}")
|
| 740 |
|
| 741 |
+
# Close audio clips: looped music, original music, then final audio (which might close its components)
|
| 742 |
+
if musica_audio is not None:
|
| 743 |
+
try:
|
| 744 |
+
musica_audio.close()
|
| 745 |
+
except Exception as e:
|
| 746 |
+
logger.warning(f"Error closing musica_audio (processed) in finally: {str(e)}")
|
| 747 |
+
|
| 748 |
+
if musica_audio_original is not None and musica_audio_original is not musica_audio:
|
| 749 |
+
try:
|
| 750 |
+
musica_audio_original.close()
|
| 751 |
+
except Exception as e:
|
| 752 |
+
logger.warning(f"Error closing musica_audio_original in finally: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 753 |
|
| 754 |
+
# Close TTS clips: potentially modified/trimmed TTS, then original TTS
|
| 755 |
+
# Note: audio_tts variable is only assigned audio_tts_original in this code, but keep structure for safety
|
| 756 |
+
if audio_tts is not None and audio_tts is not audio_tts_original:
|
| 757 |
+
try:
|
| 758 |
+
audio_tts.close()
|
| 759 |
+
except Exception as e:
|
| 760 |
+
logger.warning(f"Error closing audio_tts (processed) in finally: {str(e)}")
|
| 761 |
|
| 762 |
+
if audio_tts_original is not None:
|
| 763 |
+
try:
|
| 764 |
+
audio_tts_original.close()
|
| 765 |
+
except Exception as e:
|
| 766 |
+
logger.warning(f"Error closing audio_tts_original in finally: {str(e)}")
|
| 767 |
+
|
| 768 |
+
|
| 769 |
+
# Close video clips: final video (should cascade), then video base if it wasn't the final
|
| 770 |
+
if video_final is not None:
|
| 771 |
+
try:
|
| 772 |
+
video_final.close()
|
| 773 |
+
except Exception as e:
|
| 774 |
+
logger.warning(f"Error closing video_final in finally: {str(e)}")
|
| 775 |
+
# Note: video_base variable is reassigned to final_video_base. Close only if it exists and is different from video_final
|
| 776 |
+
elif video_base is not None and video_base is not video_final: # Check if video_base holds a different clip
|
| 777 |
+
try:
|
| 778 |
+
video_base.close()
|
| 779 |
+
except Exception as e:
|
| 780 |
+
logger.warning(f"Error closing video_base in finally: {str(e)}")
|
| 781 |
+
|
| 782 |
+
|
| 783 |
+
# Clean up intermediate files, but NOT the final video file
|
| 784 |
if temp_dir_intermediate and os.path.exists(temp_dir_intermediate):
|
| 785 |
final_output_in_temp = os.path.join(temp_dir_intermediate, "final_video.mp4")
|
| 786 |
|
|
|
|
| 801 |
|
| 802 |
input_text = prompt_ia if prompt_type == "Generar Guion con IA" else prompt_manual
|
| 803 |
|
|
|
|
| 804 |
output_video = None
|
| 805 |
output_file = None
|
| 806 |
status_msg = gr.update(value="⏳ Procesando...", interactive=False)
|
| 807 |
|
| 808 |
if not input_text or not input_text.strip():
|
| 809 |
logger.warning("Empty input text.")
|
|
|
|
| 810 |
return None, None, gr.update(value="⚠️ Por favor, ingresa texto para el guion o el tema.", interactive=False)
|
| 811 |
|
| 812 |
logger.info(f"Input Type: {prompt_type}")
|
|
|
|
| 823 |
if video_path and os.path.exists(video_path):
|
| 824 |
logger.info(f"crear_video returned path: {video_path}")
|
| 825 |
logger.info(f"Size of returned video file: {os.path.getsize(video_path)} bytes")
|
| 826 |
+
output_video = video_path
|
| 827 |
+
output_file = video_path
|
| 828 |
status_msg = gr.update(value="✅ Video generado exitosamente.", interactive=False)
|
| 829 |
else:
|
| 830 |
logger.error(f"crear_video did not return a valid path or file does not exist: {video_path}")
|
|
|
|
| 831 |
status_msg = gr.update(value="❌ Error: La generación del video falló o el archivo no se creó correctamente.", interactive=False)
|
| 832 |
|
| 833 |
except ValueError as ve:
|
| 834 |
logger.warning(f"Validation error during video creation: {str(ve)}")
|
|
|
|
| 835 |
status_msg = gr.update(value=f"⚠️ Error de validación: {str(ve)}", interactive=False)
|
| 836 |
except Exception as e:
|
| 837 |
logger.critical(f"Critical error during video creation: {str(e)}", exc_info=True)
|
|
|
|
| 838 |
status_msg = gr.update(value=f"❌ Error inesperado: {str(e)}", interactive=False)
|
| 839 |
finally:
|
| 840 |
logger.info("End of run_app handler.")
|
|
|
|
| 841 |
return output_video, output_file, status_msg
|
| 842 |
|
| 843 |
|
|
|
|
| 887 |
|
| 888 |
with gr.Column():
|
| 889 |
video_output = gr.Video(
|
| 890 |
+
label="Generated Video Preview",
|
| 891 |
interactive=False,
|
| 892 |
height=400
|
| 893 |
)
|
|
|
|
| 894 |
file_output = gr.File(
|
| 895 |
label="Download Video",
|
| 896 |
+
interactive=False,
|
| 897 |
+
visible=False
|
| 898 |
)
|
| 899 |
status_output = gr.Textbox(
|
| 900 |
label="Status",
|
|
|
|
| 911 |
outputs=[ia_guion_column, manual_guion_column]
|
| 912 |
)
|
| 913 |
|
|
|
|
| 914 |
generate_btn.click(
|
|
|
|
| 915 |
lambda: (None, None, gr.update(value="⏳ Procesando... Esto puede tomar 2-5 minutos.", interactive=False)),
|
| 916 |
outputs=[video_output, file_output, status_output],
|
| 917 |
queue=True,
|
| 918 |
).then(
|
|
|
|
| 919 |
run_app,
|
| 920 |
inputs=[prompt_type, prompt_ia, prompt_manual, musica_input],
|
| 921 |
+
outputs=[video_output, file_output, status_output]
|
| 922 |
).then(
|
| 923 |
+
lambda video_path, file_path: gr.update(visible=video_path is not None or file_path is not None), # Show download if either video or file path is returned
|
| 924 |
+
inputs=[video_output, file_output],
|
| 925 |
+
outputs=[file_output]
|
|
|
|
| 926 |
)
|
| 927 |
|
| 928 |
|