Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -757,12 +757,19 @@ class EnhancedVideoGenerator:
|
|
| 757 |
return f"{hours:02d}:{minutes:02d}:{secs:02d},{msecs:03d}"
|
| 758 |
|
| 759 |
def create_video(self, script: str, style: str, duration: int, output_path: str, selected_images: List[str]) -> str:
|
| 760 |
-
"""Create video with selected images"""
|
| 761 |
try:
|
| 762 |
# Progress bar
|
| 763 |
progress_bar = st.progress(0)
|
| 764 |
status_text = st.empty()
|
| 765 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 766 |
# Generate voice-over (20%)
|
| 767 |
status_text.text("Creating voice-over...")
|
| 768 |
audio = self.generate_voice_over(script)
|
|
@@ -771,60 +778,87 @@ class EnhancedVideoGenerator:
|
|
| 771 |
# Process selected images (40%)
|
| 772 |
status_text.text("Processing images...")
|
| 773 |
processed_images = []
|
|
|
|
| 774 |
for img_url in selected_images:
|
| 775 |
-
|
| 776 |
-
|
| 777 |
-
|
| 778 |
-
|
| 779 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 780 |
|
|
|
|
|
|
|
| 781 |
# Create frames with transitions
|
| 782 |
fps = 30
|
| 783 |
total_frames = int(duration * fps)
|
| 784 |
frames = []
|
| 785 |
-
|
| 786 |
status_text.text("Generating frames...")
|
| 787 |
frames_per_image = total_frames // len(processed_images)
|
| 788 |
-
|
| 789 |
for idx, img in enumerate(processed_images):
|
|
|
|
| 790 |
for _ in range(frames_per_image):
|
| 791 |
frames.append(img)
|
| 792 |
-
|
| 793 |
-
|
| 794 |
if idx < len(processed_images) - 1:
|
| 795 |
next_img = processed_images[idx + 1]
|
| 796 |
for alpha in np.linspace(0, 1, 15):
|
| 797 |
transition_frame = (1 - alpha) * img + alpha * next_img
|
| 798 |
frames.append(transition_frame.astype(np.uint8))
|
| 799 |
-
|
| 800 |
-
|
| 801 |
-
|
| 802 |
-
|
| 803 |
status_text.text("Compiling video...")
|
| 804 |
video = ImageSequenceClip(frames, fps=fps)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 805 |
video = video.set_audio(audio)
|
| 806 |
-
|
| 807 |
progress_bar.progress(90)
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
| 811 |
-
|
| 812 |
-
|
| 813 |
-
|
| 814 |
-
|
| 815 |
-
|
| 816 |
-
|
| 817 |
-
|
| 818 |
-
|
| 819 |
-
|
|
|
|
|
|
|
| 820 |
progress_bar.progress(100)
|
| 821 |
status_text.text("Video generation complete!")
|
| 822 |
-
|
| 823 |
return output_path
|
| 824 |
-
|
| 825 |
except Exception as e:
|
| 826 |
-
|
|
|
|
|
|
|
| 827 |
raise
|
|
|
|
| 828 |
|
| 829 |
|
| 830 |
def generate_visual_assets(self, script: str, style: str) -> List[Dict]:
|
|
|
|
| 757 |
return f"{hours:02d}:{minutes:02d}:{secs:02d},{msecs:03d}"
|
| 758 |
|
| 759 |
def create_video(self, script: str, style: str, duration: int, output_path: str, selected_images: List[str]) -> str:
|
| 760 |
+
"""Create video with selected images and improved error handling"""
|
| 761 |
try:
|
| 762 |
# Progress bar
|
| 763 |
progress_bar = st.progress(0)
|
| 764 |
status_text = st.empty()
|
| 765 |
|
| 766 |
+
# Validate inputs
|
| 767 |
+
if not selected_images:
|
| 768 |
+
raise ValueError("No images selected. Please select at least one image.")
|
| 769 |
+
|
| 770 |
+
if not script.strip():
|
| 771 |
+
raise ValueError("Script cannot be empty.")
|
| 772 |
+
|
| 773 |
# Generate voice-over (20%)
|
| 774 |
status_text.text("Creating voice-over...")
|
| 775 |
audio = self.generate_voice_over(script)
|
|
|
|
| 778 |
# Process selected images (40%)
|
| 779 |
status_text.text("Processing images...")
|
| 780 |
processed_images = []
|
| 781 |
+
|
| 782 |
for img_url in selected_images:
|
| 783 |
+
try:
|
| 784 |
+
response = requests.get(img_url, timeout=10)
|
| 785 |
+
response.raise_for_status() # Raise exception for bad status codes
|
| 786 |
+
img = Image.open(BytesIO(response.content))
|
| 787 |
+
img = img.convert('RGB') # Ensure RGB format
|
| 788 |
+
img = img.resize((1920, 1080), Image.Resampling.LANCZOS)
|
| 789 |
+
processed_images.append(np.array(img))
|
| 790 |
+
except Exception as e:
|
| 791 |
+
print(f"Error processing image {img_url}: {e}")
|
| 792 |
+
continue
|
| 793 |
+
|
| 794 |
+
if not processed_images:
|
| 795 |
+
raise ValueError("Failed to process any of the selected images.")
|
| 796 |
|
| 797 |
+
progress_bar.progress(40)
|
| 798 |
+
|
| 799 |
# Create frames with transitions
|
| 800 |
fps = 30
|
| 801 |
total_frames = int(duration * fps)
|
| 802 |
frames = []
|
| 803 |
+
|
| 804 |
status_text.text("Generating frames...")
|
| 805 |
frames_per_image = total_frames // len(processed_images)
|
| 806 |
+
|
| 807 |
for idx, img in enumerate(processed_images):
|
| 808 |
+
# Add main frames
|
| 809 |
for _ in range(frames_per_image):
|
| 810 |
frames.append(img)
|
| 811 |
+
|
| 812 |
+
# Add transition frames to next image
|
| 813 |
if idx < len(processed_images) - 1:
|
| 814 |
next_img = processed_images[idx + 1]
|
| 815 |
for alpha in np.linspace(0, 1, 15):
|
| 816 |
transition_frame = (1 - alpha) * img + alpha * next_img
|
| 817 |
frames.append(transition_frame.astype(np.uint8))
|
| 818 |
+
|
| 819 |
+
progress_bar.progress(70)
|
| 820 |
+
|
| 821 |
+
# Create video clip
|
| 822 |
status_text.text("Compiling video...")
|
| 823 |
video = ImageSequenceClip(frames, fps=fps)
|
| 824 |
+
|
| 825 |
+
# Ensure audio duration matches video duration
|
| 826 |
+
audio_duration = audio.duration
|
| 827 |
+
video_duration = len(frames) / fps
|
| 828 |
+
|
| 829 |
+
if audio_duration > video_duration:
|
| 830 |
+
audio = audio.subclip(0, video_duration)
|
| 831 |
+
elif audio_duration < video_duration:
|
| 832 |
+
video = video.subclip(0, audio_duration)
|
| 833 |
+
|
| 834 |
video = video.set_audio(audio)
|
| 835 |
+
|
| 836 |
progress_bar.progress(90)
|
| 837 |
+
|
| 838 |
+
# Write final video with error handling
|
| 839 |
+
try:
|
| 840 |
+
video.write_videofile(
|
| 841 |
+
output_path,
|
| 842 |
+
fps=fps,
|
| 843 |
+
codec='libx264',
|
| 844 |
+
audio_codec='aac',
|
| 845 |
+
threads=4,
|
| 846 |
+
preset='ultrafast'
|
| 847 |
+
)
|
| 848 |
+
except Exception as e:
|
| 849 |
+
raise RuntimeError(f"Failed to write video file: {str(e)}")
|
| 850 |
+
|
| 851 |
progress_bar.progress(100)
|
| 852 |
status_text.text("Video generation complete!")
|
| 853 |
+
|
| 854 |
return output_path
|
| 855 |
+
|
| 856 |
except Exception as e:
|
| 857 |
+
error_msg = f"Video creation failed: {str(e)}"
|
| 858 |
+
self.logger.error(error_msg)
|
| 859 |
+
st.error(error_msg)
|
| 860 |
raise
|
| 861 |
+
|
| 862 |
|
| 863 |
|
| 864 |
def generate_visual_assets(self, script: str, style: str) -> List[Dict]:
|