Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -833,180 +833,180 @@ class EnhancedVideoGenerator:
|
|
| 833 |
return f"{hours:02d}:{minutes:02d}:{secs:02d},{msecs:03d}"
|
| 834 |
|
| 835 |
def create_video(self, script: str, style: str, duration: int, output_path: str, selected_images: List[str]) -> str:
|
| 836 |
-
|
| 837 |
-
|
| 838 |
-
|
| 839 |
-
|
| 840 |
-
|
| 841 |
-
|
| 842 |
-
|
| 843 |
-
|
| 844 |
-
|
| 845 |
-
|
| 846 |
-
|
| 847 |
-
|
| 848 |
-
|
| 849 |
-
|
| 850 |
-
|
| 851 |
-
# Generate voice-over with progress tracking
|
| 852 |
-
status_text.text("Creating voice-over...")
|
| 853 |
-
audio = self.generate_voice_over(script)
|
| 854 |
-
progress_bar.progress(20)
|
| 855 |
-
|
| 856 |
-
# Process images with effects
|
| 857 |
-
status_text.text("Processing images with effects...")
|
| 858 |
-
processed_images = []
|
| 859 |
-
|
| 860 |
-
for img_url in selected_images:
|
| 861 |
-
try:
|
| 862 |
-
response = requests.get(img_url, timeout=10)
|
| 863 |
-
response.raise_for_status()
|
| 864 |
-
img = Image.open(BytesIO(response.content))
|
| 865 |
-
img = img.convert('RGB')
|
| 866 |
-
|
| 867 |
-
# Apply image effects based on style
|
| 868 |
-
if style == "Creative":
|
| 869 |
-
# Add creative effects
|
| 870 |
-
enhancer = ImageEnhance.Contrast(img)
|
| 871 |
-
img = enhancer.enhance(1.2)
|
| 872 |
-
enhancer = ImageEnhance.Brightness(img)
|
| 873 |
-
img = enhancer.enhance(1.1)
|
| 874 |
-
elif style == "Professional":
|
| 875 |
-
# Add professional effects
|
| 876 |
-
enhancer = ImageEnhance.Sharpness(img)
|
| 877 |
-
img = enhancer.enhance(1.3)
|
| 878 |
-
|
| 879 |
-
img = img.resize((1920, 1080), Image.Resampling.LANCZOS)
|
| 880 |
-
processed_images.append(img)
|
| 881 |
|
| 882 |
-
|
| 883 |
-
|
| 884 |
-
|
| 885 |
-
|
| 886 |
-
|
| 887 |
-
|
| 888 |
-
|
| 889 |
-
|
| 890 |
-
|
| 891 |
-
|
| 892 |
-
|
| 893 |
-
|
| 894 |
-
|
| 895 |
-
|
| 896 |
-
|
| 897 |
-
|
| 898 |
-
|
| 899 |
-
|
| 900 |
-
|
| 901 |
-
|
| 902 |
-
|
| 903 |
-
|
| 904 |
-
|
| 905 |
-
|
| 906 |
-
|
| 907 |
-
|
| 908 |
-
|
| 909 |
-
|
| 910 |
-
|
| 911 |
-
|
| 912 |
-
|
| 913 |
-
|
| 914 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 915 |
|
| 916 |
-
|
| 917 |
-
|
| 918 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 919 |
|
| 920 |
-
#
|
| 921 |
-
|
| 922 |
-
|
| 923 |
-
|
| 924 |
-
|
| 925 |
-
|
| 926 |
-
|
| 927 |
-
|
| 928 |
-
|
| 929 |
-
|
| 930 |
-
|
| 931 |
-
|
| 932 |
-
|
| 933 |
-
|
| 934 |
-
|
| 935 |
-
|
| 936 |
-
|
| 937 |
-
|
| 938 |
-
|
| 939 |
-
|
| 940 |
-
|
| 941 |
-
|
| 942 |
-
|
| 943 |
-
|
| 944 |
-
|
| 945 |
-
|
| 946 |
-
|
| 947 |
-
|
| 948 |
-
|
| 949 |
-
|
| 950 |
-
|
| 951 |
-
|
| 952 |
-
|
| 953 |
-
|
| 954 |
-
|
| 955 |
-
|
| 956 |
-
status_text.text("Saving video...")
|
| 957 |
-
cache_dir = os.path.join(os.path.dirname(output_path), ".cache")
|
| 958 |
-
os.makedirs(cache_dir, exist_ok=True)
|
| 959 |
-
|
| 960 |
-
try:
|
| 961 |
-
final_clip.write_videofile(
|
| 962 |
-
output_path,
|
| 963 |
-
fps=fps,
|
| 964 |
-
codec='libx264',
|
| 965 |
-
audio_codec='aac',
|
| 966 |
-
ffmpeg_params=['-pix_fmt', 'yuv420p'],
|
| 967 |
-
temp_audiofile=os.path.join(cache_dir, "temp-audio.m4a"),
|
| 968 |
-
verbose=False,
|
| 969 |
-
logger=None
|
| 970 |
-
)
|
| 971 |
-
except Exception as e:
|
| 972 |
-
# Attempt error recovery
|
| 973 |
-
status_text.text("Attempting error recovery...")
|
| 974 |
try:
|
| 975 |
-
# Try alternative codec settings
|
| 976 |
final_clip.write_videofile(
|
| 977 |
output_path,
|
| 978 |
fps=fps,
|
| 979 |
codec='libx264',
|
| 980 |
-
audio_codec='
|
|
|
|
|
|
|
| 981 |
verbose=False,
|
| 982 |
logger=None
|
| 983 |
)
|
| 984 |
-
except Exception as
|
| 985 |
-
|
| 986 |
-
|
| 987 |
-
|
| 988 |
-
|
| 989 |
-
|
| 990 |
-
|
| 991 |
-
|
| 992 |
-
|
| 993 |
-
|
| 994 |
-
|
| 995 |
-
|
| 996 |
-
|
| 997 |
-
|
| 998 |
-
|
| 999 |
-
|
| 1000 |
-
|
| 1001 |
-
|
| 1002 |
-
|
| 1003 |
-
|
| 1004 |
-
|
| 1005 |
except Exception as e:
|
| 1006 |
-
|
| 1007 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1008 |
|
| 1009 |
|
|
|
|
| 1010 |
def generate_visual_assets(self, script: str, style: str) -> List[Dict]:
|
| 1011 |
"""Generate relevant visual assets based on script content"""
|
| 1012 |
try:
|
|
|
|
| 833 |
return f"{hours:02d}:{minutes:02d}:{secs:02d},{msecs:03d}"
|
| 834 |
|
| 835 |
def create_video(self, script: str, style: str, duration: int, output_path: str, selected_images: List[str]) -> str:
|
| 836 |
+
"""Create video with enhanced features and proper error handling"""
|
| 837 |
+
try:
|
| 838 |
+
# Initialize progress tracking
|
| 839 |
+
progress_bar = st.progress(0)
|
| 840 |
+
status_text = st.empty()
|
| 841 |
+
|
| 842 |
+
# Create output directory if it doesn't exist
|
| 843 |
+
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
| 844 |
+
|
| 845 |
+
# Validate inputs and paths
|
| 846 |
+
if not output_path:
|
| 847 |
+
raise ValueError("Output path cannot be empty")
|
| 848 |
+
if not selected_images:
|
| 849 |
+
raise ValueError("No images selected")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 850 |
|
| 851 |
+
# Generate voice-over with progress tracking
|
| 852 |
+
status_text.text("Creating voice-over...")
|
| 853 |
+
audio = self.generate_voice_over(script)
|
| 854 |
+
progress_bar.progress(20)
|
| 855 |
+
|
| 856 |
+
# Process images with effects
|
| 857 |
+
status_text.text("Processing images with effects...")
|
| 858 |
+
processed_images = []
|
| 859 |
+
|
| 860 |
+
for img_url in selected_images:
|
| 861 |
+
try:
|
| 862 |
+
response = requests.get(img_url, timeout=10)
|
| 863 |
+
response.raise_for_status()
|
| 864 |
+
img = Image.open(BytesIO(response.content))
|
| 865 |
+
img = img.convert('RGB')
|
| 866 |
+
|
| 867 |
+
# Apply image effects based on style
|
| 868 |
+
if style == "Creative":
|
| 869 |
+
# Add creative effects
|
| 870 |
+
enhancer = ImageEnhance.Contrast(img)
|
| 871 |
+
img = enhancer.enhance(1.2)
|
| 872 |
+
enhancer = ImageEnhance.Brightness(img)
|
| 873 |
+
img = enhancer.enhance(1.1)
|
| 874 |
+
elif style == "Professional":
|
| 875 |
+
# Add professional effects
|
| 876 |
+
enhancer = ImageEnhance.Sharpness(img)
|
| 877 |
+
img = enhancer.enhance(1.3)
|
| 878 |
+
|
| 879 |
+
img = img.resize((1920, 1080), Image.Resampling.LANCZOS)
|
| 880 |
+
processed_images.append(img)
|
| 881 |
+
|
| 882 |
+
except Exception as e:
|
| 883 |
+
print(f"Error processing image {img_url}: {e}")
|
| 884 |
+
continue
|
| 885 |
+
|
| 886 |
+
progress_bar.progress(40)
|
| 887 |
+
|
| 888 |
+
# Generate frames with transitions
|
| 889 |
+
status_text.text("Creating frames with transitions...")
|
| 890 |
+
frames = []
|
| 891 |
+
fps = 30
|
| 892 |
+
total_frames = int(duration * fps)
|
| 893 |
+
frames_per_image = total_frames // len(processed_images)
|
| 894 |
+
|
| 895 |
+
# Convert images to numpy arrays
|
| 896 |
+
image_arrays = [np.array(img) for img in processed_images]
|
| 897 |
+
|
| 898 |
+
# Add transition effects
|
| 899 |
+
frame_count = 0
|
| 900 |
+
for idx, img_array in enumerate(image_arrays):
|
| 901 |
+
# Calculate frames for this image
|
| 902 |
+
if idx == len(image_arrays) - 1:
|
| 903 |
+
n_frames = total_frames - frame_count
|
| 904 |
+
else:
|
| 905 |
+
n_frames = min(frames_per_image, total_frames - frame_count)
|
| 906 |
|
| 907 |
+
# Add effects and transitions
|
| 908 |
+
for frame_idx in range(n_frames):
|
| 909 |
+
# Apply fade in/out effect
|
| 910 |
+
alpha = 1.0
|
| 911 |
+
if frame_idx < 15: # Fade in
|
| 912 |
+
alpha = frame_idx / 15
|
| 913 |
+
elif frame_idx > n_frames - 15: # Fade out
|
| 914 |
+
alpha = (n_frames - frame_idx) / 15
|
| 915 |
+
|
| 916 |
+
frame = img_array * alpha
|
| 917 |
+
frames.append(frame.astype(np.uint8))
|
| 918 |
+
frame_count += 1
|
| 919 |
+
|
| 920 |
+
# Update progress
|
| 921 |
+
progress = int(40 + (frame_count / total_frames * 30))
|
| 922 |
+
progress_bar.progress(progress)
|
| 923 |
|
| 924 |
+
# Add transition to next image
|
| 925 |
+
if idx < len(image_arrays) - 1:
|
| 926 |
+
next_img_array = image_arrays[idx + 1]
|
| 927 |
+
transition_frames = 15
|
| 928 |
+
for t in range(transition_frames):
|
| 929 |
+
if frame_count < total_frames:
|
| 930 |
+
alpha = t / transition_frames
|
| 931 |
+
transition_frame = cv2.addWeighted(
|
| 932 |
+
img_array, 1 - alpha,
|
| 933 |
+
next_img_array, alpha, 0
|
| 934 |
+
)
|
| 935 |
+
frames.append(transition_frame)
|
| 936 |
+
frame_count += 1
|
| 937 |
+
|
| 938 |
+
progress_bar.progress(70)
|
| 939 |
+
|
| 940 |
+
# Create video with frames
|
| 941 |
+
status_text.text("Compiling video...")
|
| 942 |
+
clip = ImageSequenceClip(frames, fps=fps)
|
| 943 |
+
|
| 944 |
+
# Add audio with proper synchronization
|
| 945 |
+
audio_duration = audio.duration
|
| 946 |
+
video_duration = len(frames) / fps
|
| 947 |
+
|
| 948 |
+
if audio_duration > video_duration:
|
| 949 |
+
audio = audio.subclip(0, video_duration)
|
| 950 |
+
elif audio_duration < video_duration:
|
| 951 |
+
clip = clip.subclip(0, audio_duration)
|
| 952 |
+
|
| 953 |
+
final_clip = clip.set_audio(audio)
|
| 954 |
+
|
| 955 |
+
# Write video with progress caching
|
| 956 |
+
status_text.text("Saving video...")
|
| 957 |
+
cache_dir = os.path.join(os.path.dirname(output_path), ".cache")
|
| 958 |
+
os.makedirs(cache_dir, exist_ok=True)
|
| 959 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 960 |
try:
|
|
|
|
| 961 |
final_clip.write_videofile(
|
| 962 |
output_path,
|
| 963 |
fps=fps,
|
| 964 |
codec='libx264',
|
| 965 |
+
audio_codec='aac',
|
| 966 |
+
ffmpeg_params=['-pix_fmt', 'yuv420p'],
|
| 967 |
+
temp_audiofile=os.path.join(cache_dir, "temp-audio.m4a"),
|
| 968 |
verbose=False,
|
| 969 |
logger=None
|
| 970 |
)
|
| 971 |
+
except Exception as e:
|
| 972 |
+
# Attempt error recovery
|
| 973 |
+
status_text.text("Attempting error recovery...")
|
| 974 |
+
try:
|
| 975 |
+
# Try alternative codec settings
|
| 976 |
+
final_clip.write_videofile(
|
| 977 |
+
output_path,
|
| 978 |
+
fps=fps,
|
| 979 |
+
codec='libx264',
|
| 980 |
+
audio_codec='mp3',
|
| 981 |
+
verbose=False,
|
| 982 |
+
logger=None
|
| 983 |
+
)
|
| 984 |
+
except Exception as recovery_e:
|
| 985 |
+
raise RuntimeError(f"Video creation failed even with recovery attempt: {str(recovery_e)}")
|
| 986 |
+
|
| 987 |
+
progress_bar.progress(100)
|
| 988 |
+
status_text.text("Video generation complete!")
|
| 989 |
+
|
| 990 |
+
return output_path
|
| 991 |
+
|
| 992 |
except Exception as e:
|
| 993 |
+
error_msg = f"Video creation failed: {str(e)}"
|
| 994 |
+
print(error_msg)
|
| 995 |
+
raise RuntimeError(error_msg)
|
| 996 |
+
finally:
|
| 997 |
+
# Cleanup
|
| 998 |
+
try:
|
| 999 |
+
if 'clip' in locals():
|
| 1000 |
+
clip.close()
|
| 1001 |
+
if 'final_clip' in locals():
|
| 1002 |
+
final_clip.close()
|
| 1003 |
+
if 'audio' in locals():
|
| 1004 |
+
audio.close()
|
| 1005 |
+
except Exception as e:
|
| 1006 |
+
print(f"Cleanup error: {e}")
|
| 1007 |
|
| 1008 |
|
| 1009 |
+
|
| 1010 |
def generate_visual_assets(self, script: str, style: str) -> List[Dict]:
|
| 1011 |
"""Generate relevant visual assets based on script content"""
|
| 1012 |
try:
|