| import gradio as gr |
| import librosa |
| import numpy as np |
| import tempfile |
| import os |
| import random |
| from moviepy.editor import ( |
| VideoFileClip, |
| ImageClip, |
| AudioFileClip, |
| concatenate_videoclips, |
| vfx |
| ) |
|
|
| |
| |
| |
| def analyze_audio(audio_path): |
| y, sr = librosa.load(audio_path) |
| tempo, beats = librosa.beat.beat_track(y=y, sr=sr) |
| beat_times = librosa.frames_to_time(beats, sr=sr) |
|
|
| onset_env = librosa.onset.onset_strength(y=y, sr=sr) |
| energy = onset_env / np.max(onset_env) |
|
|
| return beat_times, energy |
|
|
| |
| |
| |
| TEMPLATES = { |
| "Auto": { |
| "min_clip": 0.3, |
| "max_clip": 0.8, |
| "zoom_prob": 0.4, |
| "shake_prob": 0.2 |
| }, |
| "Hype": { |
| "min_clip": 0.2, |
| "max_clip": 0.5, |
| "zoom_prob": 0.6, |
| "shake_prob": 0.4 |
| }, |
| "Sad": { |
| "min_clip": 0.6, |
| "max_clip": 1.2, |
| "zoom_prob": 0.2, |
| "shake_prob": 0.0 |
| } |
| } |
|
|
| |
| |
| |
| def generate_edit(song, media_files, template): |
| if song is None or not media_files: |
| return None |
|
|
| rules = TEMPLATES[template] |
|
|
| with tempfile.TemporaryDirectory() as tmp: |
| song_path = os.path.join(tmp, "song.mp3") |
| with open(song_path, "wb") as f: |
| f.write(song) |
|
|
| beats, energy = analyze_audio(song_path) |
| audio = AudioFileClip(song_path) |
|
|
| clips = [] |
| media_index = 0 |
| t = 0 |
|
|
| while t < audio.duration: |
| duration = random.uniform( |
| rules["min_clip"], rules["max_clip"] |
| ) |
|
|
| media = media_files[media_index % len(media_files)] |
| media_index += 1 |
|
|
| media_path = os.path.join(tmp, os.path.basename(media.name)) |
| with open(media_path, "wb") as f: |
| f.write(media.read()) |
|
|
| if media_path.lower().endswith((".mp4", ".mov", ".webm")): |
| base = VideoFileClip(media_path) |
| clip = base.subclip(0, min(duration, base.duration)) |
| else: |
| clip = ImageClip(media_path).set_duration(duration) |
|
|
| |
| if random.random() < rules["zoom_prob"]: |
| clip = clip.fx(vfx.resize, lambda t: 1 + 0.05 * t) |
|
|
| if random.random() < rules["shake_prob"]: |
| clip = clip.fx(vfx.crop, x1=5, y1=5, x2=5, y2=5) |
|
|
| clip = clip.resize(height=1080).set_position("center") |
| clips.append(clip) |
|
|
| t += duration |
|
|
| final = concatenate_videoclips( |
| clips, method="compose", padding=-0.05 |
| ) |
|
|
| final = final.set_audio(audio) |
| final = final.resize((1080, 1920)) |
|
|
| output = os.path.join(tmp, "edit.mp4") |
| final.write_videofile( |
| output, |
| fps=30, |
| codec="libx264", |
| audio_codec="aac", |
| threads=2, |
| logger=None |
| ) |
|
|
| return output |
|
|
| |
| |
| |
| with gr.Blocks(title="EditForge AI Advanced") as demo: |
| gr.Markdown("## 🎬 EditForge AI (Advanced)") |
|
|
| song = gr.File(label="Upload Song", type="binary") |
| media = gr.Files(label="Upload Images / Videos") |
|
|
| template = gr.Dropdown( |
| choices=["Auto", "Hype", "Sad"], |
| value="Auto", |
| label="Edit Style" |
| ) |
|
|
| output = gr.Video(label="Final Edit") |
|
|
| btn = gr.Button("🚀 Generate Edit") |
|
|
| btn.click( |
| fn=generate_edit, |
| inputs=[song, media, template], |
| outputs=output |
| ) |
|
|
| demo.launch(server_name="0.0.0.0", server_port=7860) |
|
|