Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -796,145 +796,145 @@ 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 |
-
|
| 824 |
-
|
| 825 |
-
|
| 826 |
-
|
| 827 |
-
|
| 828 |
-
|
|
|
|
|
|
|
|
|
|
| 829 |
except Exception as e:
|
| 830 |
-
|
| 831 |
-
|
| 832 |
-
|
| 833 |
-
|
| 834 |
-
|
| 835 |
-
|
| 836 |
-
|
| 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 |
-
|
| 935 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 936 |
|
| 937 |
|
|
|
|
| 938 |
def generate_visual_assets(self, script: str, style: str) -> List[Dict]:
|
| 939 |
"""Generate relevant visual assets based on script content"""
|
| 940 |
try:
|
|
@@ -1178,65 +1178,65 @@ class VideoGeneratorUI:
|
|
| 1178 |
print(f"Error displaying image: {e}")
|
| 1179 |
|
| 1180 |
def show_video_settings(self, prompt: str, selected_images: List[str]):
|
| 1181 |
-
|
| 1182 |
-
|
| 1183 |
-
|
| 1184 |
-
|
| 1185 |
-
|
| 1186 |
-
|
| 1187 |
-
|
| 1188 |
-
|
| 1189 |
-
|
| 1190 |
-
)
|
| 1191 |
-
with col2:
|
| 1192 |
-
duration = st.slider(
|
| 1193 |
-
"Video duration (seconds)",
|
| 1194 |
-
min_value=30,
|
| 1195 |
-
max_value=180,
|
| 1196 |
-
value=60,
|
| 1197 |
-
step=30
|
| 1198 |
-
)
|
| 1199 |
-
|
| 1200 |
-
if st.button("🎬 Generate Video", type="primary"):
|
| 1201 |
-
if not selected_images:
|
| 1202 |
-
st.error("Please select at least one image before generating the video.")
|
| 1203 |
-
return
|
| 1204 |
-
|
| 1205 |
-
try:
|
| 1206 |
-
output_dir = "temp_videos"
|
| 1207 |
-
os.makedirs(output_dir, exist_ok=True)
|
| 1208 |
-
output_path = os.path.join(output_dir, f"vaultgenix_video_{int(time.time())}.mp4")
|
| 1209 |
-
|
| 1210 |
-
video_path = self.generator.create_video(
|
| 1211 |
-
prompt,
|
| 1212 |
-
style,
|
| 1213 |
-
duration,
|
| 1214 |
-
output_path,
|
| 1215 |
-
selected_images
|
| 1216 |
)
|
| 1217 |
-
|
| 1218 |
-
|
| 1219 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1220 |
|
| 1221 |
-
|
| 1222 |
-
|
| 1223 |
-
|
| 1224 |
-
|
| 1225 |
-
|
| 1226 |
-
# Download button
|
| 1227 |
-
st.download_button(
|
| 1228 |
-
label="⬇️ Download Video",
|
| 1229 |
-
data=video_bytes,
|
| 1230 |
-
file_name=os.path.basename(video_path),
|
| 1231 |
-
mime="video/mp4"
|
| 1232 |
-
)
|
| 1233 |
-
else:
|
| 1234 |
-
st.error("Video generation failed. Please try again.")
|
| 1235 |
|
| 1236 |
-
|
| 1237 |
-
|
| 1238 |
-
|
| 1239 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1240 |
def generate_video(self, prompt: str, style: str, duration: int, selected_images: List[str]):
|
| 1241 |
"""Handle video generation with improved error handling"""
|
| 1242 |
if not selected_images:
|
|
|
|
| 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 |
+
|
| 938 |
def generate_visual_assets(self, script: str, style: str) -> List[Dict]:
|
| 939 |
"""Generate relevant visual assets based on script content"""
|
| 940 |
try:
|
|
|
|
| 1178 |
print(f"Error displaying image: {e}")
|
| 1179 |
|
| 1180 |
def show_video_settings(self, prompt: str, selected_images: List[str]):
|
| 1181 |
+
"""Show video generation settings and controls"""
|
| 1182 |
+
st.subheader("Video Settings")
|
| 1183 |
+
|
| 1184 |
+
col1, col2 = st.columns(2)
|
| 1185 |
+
with col1:
|
| 1186 |
+
style = st.selectbox(
|
| 1187 |
+
"Choose style",
|
| 1188 |
+
options=["Professional", "Creative", "Educational"],
|
| 1189 |
+
index=0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1190 |
)
|
| 1191 |
+
with col2:
|
| 1192 |
+
duration = st.slider(
|
| 1193 |
+
"Video duration (seconds)",
|
| 1194 |
+
min_value=30,
|
| 1195 |
+
max_value=180,
|
| 1196 |
+
value=60,
|
| 1197 |
+
step=30
|
| 1198 |
+
)
|
| 1199 |
+
|
| 1200 |
+
if st.button("🎬 Generate Video", type="primary"):
|
| 1201 |
+
if not selected_images:
|
| 1202 |
+
st.error("Please select at least one image before generating the video.")
|
| 1203 |
+
return
|
| 1204 |
|
| 1205 |
+
try:
|
| 1206 |
+
output_dir = "temp_videos"
|
| 1207 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 1208 |
+
output_path = os.path.join(output_dir, f"vaultgenix_video_{int(time.time())}.mp4")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1209 |
|
| 1210 |
+
video_path = self.generator.create_video(
|
| 1211 |
+
prompt,
|
| 1212 |
+
style,
|
| 1213 |
+
duration,
|
| 1214 |
+
output_path,
|
| 1215 |
+
selected_images
|
| 1216 |
+
)
|
| 1217 |
+
|
| 1218 |
+
if os.path.exists(video_path):
|
| 1219 |
+
st.success("✨ Video generated successfully!")
|
| 1220 |
+
|
| 1221 |
+
# Display video
|
| 1222 |
+
with open(video_path, 'rb') as video_file:
|
| 1223 |
+
video_bytes = video_file.read()
|
| 1224 |
+
st.video(video_bytes)
|
| 1225 |
+
|
| 1226 |
+
# Download button
|
| 1227 |
+
st.download_button(
|
| 1228 |
+
label="⬇️ Download Video",
|
| 1229 |
+
data=video_bytes,
|
| 1230 |
+
file_name=os.path.basename(video_path),
|
| 1231 |
+
mime="video/mp4"
|
| 1232 |
+
)
|
| 1233 |
+
else:
|
| 1234 |
+
st.error("Video generation failed. Please try again.")
|
| 1235 |
+
|
| 1236 |
+
except Exception as e:
|
| 1237 |
+
st.error(f"Error generating video: {str(e)}")
|
| 1238 |
+
print(f"Video generation error: {str(e)}") # For debugging
|
| 1239 |
+
|
| 1240 |
def generate_video(self, prompt: str, style: str, duration: int, selected_images: List[str]):
|
| 1241 |
"""Handle video generation with improved error handling"""
|
| 1242 |
if not selected_images:
|