Spaces:
Runtime error
Runtime error
import streamlit as st | |
import os | |
import tempfile | |
import numpy as np | |
import cv2 | |
from PIL import Image | |
import subprocess | |
import random | |
import time | |
# Set page config | |
st.set_page_config(page_title="Audio + Image to Video Converter", layout="wide") | |
def add_snow_effect(frame, density=0.01): | |
"""Add snow effect to a frame""" | |
h, w = frame.shape[:2] | |
snow_points = np.random.rand(int(h*w*density), 2) | |
snow_points[:, 0] *= h | |
snow_points[:, 1] *= w | |
for y, x in snow_points.astype(int): | |
if 0 <= y < h and 0 <= x < w: | |
frame[y, x] = [255, 255, 255] # White snow | |
return frame | |
def add_heart_effect(frame, density=0.002): | |
"""Add heart effect to a frame""" | |
h, w = frame.shape[:2] | |
heart_points = np.random.rand(int(h*w*density), 2) | |
heart_points[:, 0] *= h | |
heart_points[:, 1] *= w | |
heart_colors = [ | |
[255, 0, 0], # Red | |
[255, 20, 147], # Pink | |
[255, 105, 180] # Hot Pink | |
] | |
for y, x in heart_points.astype(int): | |
if 0 <= y < h and 0 <= x < w: | |
color = random.choice(heart_colors) | |
cv2.drawMarker(frame, (int(x), int(y)), color, | |
markerType=cv2.MARKER_STAR, markerSize=10, thickness=2) | |
return frame | |
def add_wave_effect(frame, time_val, amplitude=20, frequency=0.1): | |
"""Add wave effect to a frame""" | |
h, w = frame.shape[:2] | |
for x in range(w): | |
offset = int(amplitude * np.sin(frequency * x + time_val)) | |
for y in range(h): | |
src_y = y + offset | |
if 0 <= src_y < h: | |
frame[y, x] = frame[src_y, x] | |
return frame | |
def add_sparkle_effect(frame, density=0.001): | |
"""Add sparkle effect to a frame""" | |
h, w = frame.shape[:2] | |
sparkle_points = np.random.rand(int(h*w*density), 2) | |
sparkle_points[:, 0] *= h | |
sparkle_points[:, 1] *= w | |
sparkle_colors = [ | |
[255, 215, 0], # Gold | |
[255, 255, 255], # White | |
[173, 216, 230] # Light Blue | |
] | |
for y, x in sparkle_points.astype(int): | |
if 0 <= y < h and 0 <= x < w: | |
size = random.randint(1, 3) | |
color = random.choice(sparkle_colors) | |
cv2.circle(frame, (int(x), int(y)), size, color, -1) | |
return frame | |
def get_audio_duration(audio_path): | |
"""Get the duration of an audio file using ffprobe""" | |
cmd = [ | |
'ffprobe', | |
'-v', 'error', | |
'-show_entries', 'format=duration', | |
'-of', 'default=noprint_wrappers=1:nokey=1', | |
audio_path | |
] | |
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) | |
return float(result.stdout.strip()) | |
def create_video(image_path, audio_path, effect_type, output_path, fps=24): | |
"""Create a video from an image and audio with the selected effect""" | |
# Get audio duration | |
try: | |
duration = get_audio_duration(audio_path) | |
except Exception as e: | |
st.error(f"Error getting audio duration: {e}") | |
# Fallback to a default duration if ffprobe fails | |
duration = 30.0 | |
# Load image | |
img = cv2.imread(image_path) | |
h, w = img.shape[:2] | |
# Define output video parameters | |
fourcc = cv2.VideoWriter_fourcc(*'mp4v') | |
temp_video_path = tempfile.NamedTemporaryFile(suffix='.mp4', delete=False).name | |
out = cv2.VideoWriter(temp_video_path, fourcc, fps, (w, h)) | |
# Calculate total frames | |
total_frames = int(duration * fps) | |
progress_bar = st.progress(0) | |
# Generate video frames with effect | |
for i in range(total_frames): | |
progress = i / total_frames | |
progress_bar.progress(progress) | |
# Create a copy of the original image | |
frame = img.copy() | |
time_val = i / fps | |
# Apply selected effect | |
if effect_type == 'Snow': | |
frame = add_snow_effect(frame) | |
elif effect_type == 'Hearts': | |
frame = add_heart_effect(frame) | |
elif effect_type == 'Waves': | |
frame = add_wave_effect(frame, time_val) | |
elif effect_type == 'Sparkles': | |
frame = add_sparkle_effect(frame) | |
elif effect_type == 'Random': | |
effect = random.choice(['Snow', 'Hearts', 'Waves', 'Sparkles']) | |
if effect == 'Snow': | |
frame = add_snow_effect(frame) | |
elif effect == 'Hearts': | |
frame = add_heart_effect(frame) | |
elif effect == 'Waves': | |
frame = add_wave_effect(frame, time_val) | |
elif effect == 'Sparkles': | |
frame = add_sparkle_effect(frame) | |
# Write the frame | |
out.write(frame) | |
# Release the video writer | |
out.release() | |
# Combine the silent video with audio using ffmpeg directly | |
cmd = [ | |
'ffmpeg', | |
'-i', temp_video_path, # Input video file | |
'-i', audio_path, # Input audio file | |
'-map', '0:v', # Use video from first input | |
'-map', '1:a', # Use audio from second input | |
'-c:v', 'copy', # Copy the video codec | |
'-c:a', 'aac', # Use AAC for audio | |
'-shortest', # End when the shortest input ends | |
output_path # Output file | |
] | |
# Run ffmpeg command | |
subprocess.run(cmd, check=True) | |
# Clean up temporary file | |
os.remove(temp_video_path) | |
progress_bar.progress(1.0) | |
return output_path | |
def main(): | |
st.title("🎵 Audio + Image to Video Creator 🖼️") | |
st.markdown(""" | |
Upload an audio file and an image to create a video! | |
The image will be static throughout the video, and you can add visual effects. | |
""") | |
# Sidebar for effects selection | |
st.sidebar.title("Effect Options") | |
effect_type = st.sidebar.selectbox( | |
"Choose Visual Effect", | |
["None", "Snow", "Hearts", "Waves", "Sparkles", "Random"] | |
) | |
# File uploaders | |
col1, col2 = st.columns(2) | |
with col1: | |
st.subheader("Upload Audio File") | |
audio_file = st.file_uploader("Choose an audio file", type=["mp3", "wav", "ogg"]) | |
if audio_file is not None: | |
# Save the audio file temporarily | |
audio_path = tempfile.NamedTemporaryFile(delete=False, suffix='.mp3').name | |
with open(audio_path, "wb") as f: | |
f.write(audio_file.getbuffer()) | |
st.audio(audio_file) | |
with col2: | |
st.subheader("Upload Image") | |
image_file = st.file_uploader("Choose an image", type=["jpg", "jpeg", "png"]) | |
if image_file is not None: | |
# Save the image file temporarily | |
image_path = tempfile.NamedTemporaryFile(delete=False, suffix='.jpg').name | |
with open(image_path, "wb") as f: | |
f.write(image_file.getbuffer()) | |
st.image(image_file, caption="Uploaded Image", use_column_width=True) | |
# Generate video when both files are uploaded | |
if st.button("Create Video", disabled=(audio_file is None or image_file is None)): | |
if audio_file is not None and image_file is not None: | |
st.subheader("Creating Video...") | |
# Create a temporary output file | |
output_path = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4').name | |
with st.spinner("Processing..."): | |
try: | |
video_path = create_video(image_path, audio_path, effect_type, output_path) | |
# Display the output video | |
st.subheader("Generated Video") | |
st.video(video_path) | |
# Provide download link | |
with open(video_path, "rb") as file: | |
btn = st.download_button( | |
label="Download Video", | |
data=file, | |
file_name="output_video.mp4", | |
mime="video/mp4" | |
) | |
except Exception as e: | |
st.error(f"Error creating video: {e}") | |
finally: | |
# Clean up temporary files | |
if 'audio_path' in locals(): | |
os.remove(audio_path) | |
if 'image_path' in locals(): | |
os.remove(image_path) | |
if 'output_path' in locals() and os.path.exists(output_path): | |
os.remove(output_path) | |
if __name__ == "__main__": | |
main() |