Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -796,107 +796,142 @@ class EnhancedVideoGenerator:
|
|
| 796 |
return f"{hours:02d}:{minutes:02d}:{secs:02d},{msecs:03d}"
|
| 797 |
|
| 798 |
def create_video(self, script: str, style: str, duration: int, output_path: str, selected_images: List[str]) -> str:
|
| 799 |
-
|
| 800 |
-
|
| 801 |
-
|
| 802 |
-
|
| 803 |
-
|
| 804 |
-
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
| 811 |
-
|
| 812 |
-
|
| 813 |
-
|
| 814 |
-
|
| 815 |
-
|
| 816 |
-
|
| 817 |
-
|
| 818 |
-
|
| 819 |
-
|
| 820 |
-
|
| 821 |
-
|
| 822 |
-
try:
|
| 823 |
-
response = requests.get(img_url, timeout=10)
|
| 824 |
-
response.raise_for_status() # Raise exception for bad status codes
|
| 825 |
-
img = Image.open(BytesIO(response.content))
|
| 826 |
-
img = img.convert('RGB') # Ensure RGB format
|
| 827 |
-
img = img.resize((1920, 1080), Image.Resampling.LANCZOS)
|
| 828 |
-
processed_images.append(np.array(img))
|
| 829 |
-
except Exception as e:
|
| 830 |
-
print(f"Error processing image {img_url}: {e}")
|
| 831 |
-
continue
|
| 832 |
-
|
| 833 |
-
if not processed_images:
|
| 834 |
-
raise ValueError("Failed to process any of the selected images.")
|
| 835 |
-
|
| 836 |
-
progress_bar.progress(40)
|
| 837 |
-
|
| 838 |
-
# Create frames with transitions
|
| 839 |
-
fps = 30
|
| 840 |
-
total_frames = int(duration * fps)
|
| 841 |
-
frames = []
|
| 842 |
-
|
| 843 |
-
status_text.text("Generating frames...")
|
| 844 |
-
frames_per_image = total_frames // len(processed_images)
|
| 845 |
-
|
| 846 |
-
for idx, img in enumerate(processed_images):
|
| 847 |
-
# Add main frames
|
| 848 |
-
for _ in range(frames_per_image):
|
| 849 |
-
frames.append(img)
|
| 850 |
-
|
| 851 |
-
# Add transition frames to next image
|
| 852 |
-
if idx < len(processed_images) - 1:
|
| 853 |
-
next_img = processed_images[idx + 1]
|
| 854 |
-
for alpha in np.linspace(0, 1, 15):
|
| 855 |
-
transition_frame = (1 - alpha) * img + alpha * next_img
|
| 856 |
-
frames.append(transition_frame.astype(np.uint8))
|
| 857 |
-
|
| 858 |
-
progress_bar.progress(70)
|
| 859 |
-
|
| 860 |
-
# Create video clip
|
| 861 |
-
status_text.text("Compiling video...")
|
| 862 |
-
video = ImageSequenceClip(frames, fps=fps)
|
| 863 |
-
|
| 864 |
-
# Ensure audio duration matches video duration
|
| 865 |
-
audio_duration = audio.duration
|
| 866 |
-
video_duration = len(frames) / fps
|
| 867 |
-
|
| 868 |
-
if audio_duration > video_duration:
|
| 869 |
-
audio = audio.subclip(0, video_duration)
|
| 870 |
-
elif audio_duration < video_duration:
|
| 871 |
-
video = video.subclip(0, audio_duration)
|
| 872 |
-
|
| 873 |
-
video = video.set_audio(audio)
|
| 874 |
-
|
| 875 |
-
progress_bar.progress(90)
|
| 876 |
-
|
| 877 |
-
# Write final video with error handling
|
| 878 |
try:
|
| 879 |
-
|
| 880 |
-
|
| 881 |
-
|
| 882 |
-
|
| 883 |
-
|
| 884 |
-
|
| 885 |
-
preset='ultrafast'
|
| 886 |
-
)
|
| 887 |
except Exception as e:
|
| 888 |
-
|
| 889 |
-
|
| 890 |
-
|
| 891 |
-
|
| 892 |
-
|
| 893 |
-
|
| 894 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 895 |
except Exception as e:
|
| 896 |
-
|
| 897 |
-
self.logger.error(error_msg)
|
| 898 |
-
st.error(error_msg)
|
| 899 |
-
raise
|
| 900 |
|
| 901 |
|
| 902 |
|
|
|
|
| 796 |
return f"{hours:02d}:{minutes:02d}:{secs:02d},{msecs:03d}"
|
| 797 |
|
| 798 |
def create_video(self, script: str, style: str, duration: int, output_path: str, selected_images: List[str]) -> str:
|
| 799 |
+
"""Create video with selected images and improved error handling"""
|
| 800 |
+
try:
|
| 801 |
+
# Initialize progress tracking
|
| 802 |
+
progress_bar = st.progress(0)
|
| 803 |
+
status_text = st.empty()
|
| 804 |
+
|
| 805 |
+
# Validate inputs
|
| 806 |
+
if not selected_images:
|
| 807 |
+
raise ValueError("No images selected. Please select at least one image.")
|
| 808 |
+
|
| 809 |
+
if not script.strip():
|
| 810 |
+
raise ValueError("Script cannot be empty.")
|
| 811 |
+
|
| 812 |
+
# Generate voice-over
|
| 813 |
+
status_text.text("Creating voice-over...")
|
| 814 |
+
audio = self.generate_fallback_audio(script) # Using fallback audio for reliability
|
| 815 |
+
progress_bar.progress(20)
|
| 816 |
+
|
| 817 |
+
# Process images
|
| 818 |
+
status_text.text("Processing images...")
|
| 819 |
+
processed_images = []
|
| 820 |
+
|
| 821 |
+
for img_url in selected_images:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 822 |
try:
|
| 823 |
+
response = requests.get(img_url, timeout=10)
|
| 824 |
+
response.raise_for_status()
|
| 825 |
+
img = Image.open(BytesIO(response.content))
|
| 826 |
+
img = img.convert('RGB')
|
| 827 |
+
img = img.resize((1920, 1080), Image.Resampling.LANCZOS)
|
| 828 |
+
processed_images.append(img)
|
|
|
|
|
|
|
| 829 |
except Exception as e:
|
| 830 |
+
print(f"Error processing image {img_url}: {e}")
|
| 831 |
+
continue
|
| 832 |
+
|
| 833 |
+
if not processed_images:
|
| 834 |
+
raise ValueError("Failed to process any of the selected images.")
|
| 835 |
+
|
| 836 |
+
progress_bar.progress(40)
|
| 837 |
+
|
| 838 |
+
# Create frames
|
| 839 |
+
status_text.text("Generating frames...")
|
| 840 |
+
frames = []
|
| 841 |
+
fps = 30
|
| 842 |
+
total_frames = int(duration * fps)
|
| 843 |
+
frames_per_image = total_frames // len(processed_images)
|
| 844 |
+
|
| 845 |
+
# Convert images to numpy arrays
|
| 846 |
+
image_arrays = [np.array(img) for img in processed_images]
|
| 847 |
+
|
| 848 |
+
# Generate frames with transitions
|
| 849 |
+
frame_count = 0
|
| 850 |
+
for idx, img_array in enumerate(image_arrays):
|
| 851 |
+
# Calculate how many frames this image should appear
|
| 852 |
+
if idx == len(image_arrays) - 1:
|
| 853 |
+
n_frames = total_frames - frame_count
|
| 854 |
+
else:
|
| 855 |
+
n_frames = min(frames_per_image, total_frames - frame_count)
|
| 856 |
+
|
| 857 |
+
# Add main frames
|
| 858 |
+
for _ in range(n_frames):
|
| 859 |
+
frames.append(img_array)
|
| 860 |
+
frame_count += 1
|
| 861 |
+
|
| 862 |
+
# Add transition frames to next image
|
| 863 |
+
if idx < len(image_arrays) - 1:
|
| 864 |
+
next_img_array = image_arrays[idx + 1]
|
| 865 |
+
transition_frames = 15 # Number of transition frames
|
| 866 |
+
for t in range(transition_frames):
|
| 867 |
+
if frame_count < total_frames:
|
| 868 |
+
alpha = t / transition_frames
|
| 869 |
+
transition_frame = cv2.addWeighted(
|
| 870 |
+
img_array, 1 - alpha,
|
| 871 |
+
next_img_array, alpha, 0
|
| 872 |
+
)
|
| 873 |
+
frames.append(transition_frame)
|
| 874 |
+
frame_count += 1
|
| 875 |
+
|
| 876 |
+
progress_bar.progress(70)
|
| 877 |
+
|
| 878 |
+
# Create video with frames
|
| 879 |
+
status_text.text("Compiling video...")
|
| 880 |
+
clip = ImageSequenceClip(frames, fps=fps)
|
| 881 |
+
|
| 882 |
+
# Add audio
|
| 883 |
+
audio_duration = audio.duration
|
| 884 |
+
video_duration = len(frames) / fps
|
| 885 |
+
|
| 886 |
+
if audio_duration > video_duration:
|
| 887 |
+
audio = audio.subclip(0, video_duration)
|
| 888 |
+
elif audio_duration < video_duration:
|
| 889 |
+
clip = clip.subclip(0, audio_duration)
|
| 890 |
+
|
| 891 |
+
final_clip = clip.set_audio(audio)
|
| 892 |
+
|
| 893 |
+
# Ensure output directory exists
|
| 894 |
+
output_dir = os.path.dirname(output_path)
|
| 895 |
+
if output_dir and not os.path.exists(output_dir):
|
| 896 |
+
os.makedirs(output_dir)
|
| 897 |
+
|
| 898 |
+
progress_bar.progress(90)
|
| 899 |
+
|
| 900 |
+
# Write video file
|
| 901 |
+
status_text.text("Saving video...")
|
| 902 |
+
try:
|
| 903 |
+
final_clip.write_videofile(
|
| 904 |
+
output_path,
|
| 905 |
+
fps=fps,
|
| 906 |
+
codec='libx264',
|
| 907 |
+
audio_codec='aac',
|
| 908 |
+
ffmpeg_params=['-pix_fmt', 'yuv420p'], # Ensure compatibility
|
| 909 |
+
verbose=False,
|
| 910 |
+
logger=None
|
| 911 |
+
)
|
| 912 |
+
except Exception as e:
|
| 913 |
+
raise RuntimeError(f"Failed to write video file: {str(e)}")
|
| 914 |
+
|
| 915 |
+
progress_bar.progress(100)
|
| 916 |
+
status_text.text("Video generation complete!")
|
| 917 |
+
|
| 918 |
+
return output_path
|
| 919 |
+
|
| 920 |
+
except Exception as e:
|
| 921 |
+
error_msg = f"Video creation failed: {str(e)}"
|
| 922 |
+
print(error_msg) # For debugging
|
| 923 |
+
raise RuntimeError(error_msg)
|
| 924 |
+
finally:
|
| 925 |
+
# Cleanup
|
| 926 |
+
try:
|
| 927 |
+
if 'clip' in locals():
|
| 928 |
+
clip.close()
|
| 929 |
+
if 'final_clip' in locals():
|
| 930 |
+
final_clip.close()
|
| 931 |
+
if 'audio' in locals():
|
| 932 |
+
audio.close()
|
| 933 |
except Exception as e:
|
| 934 |
+
print(f"Cleanup error: {e}")
|
|
|
|
|
|
|
|
|
|
| 935 |
|
| 936 |
|
| 937 |
|