Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,15 +1,104 @@
|
|
| 1 |
-
# Import necessary libraries (assuming all your imports remain the same)
|
| 2 |
import gradio as gr
|
| 3 |
import os
|
| 4 |
import tempfile
|
| 5 |
import shutil
|
| 6 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
-
#
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
MAX_CLIPS = 10
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
def process_script(topic, script_input):
|
| 15 |
"""Process the topic or script and return updates for the UI."""
|
|
@@ -24,7 +113,6 @@ def process_script(topic, script_input):
|
|
| 24 |
paired_elements = [(elements[i], elements[i + 1]) for i in range(0, len(elements) - 1, 2)]
|
| 25 |
num_clips = min(len(paired_elements), MAX_CLIPS)
|
| 26 |
|
| 27 |
-
# Prepare updates for clip editor
|
| 28 |
accordion_updates = []
|
| 29 |
prompt_updates = []
|
| 30 |
narration_updates = []
|
|
@@ -35,7 +123,7 @@ def process_script(topic, script_input):
|
|
| 35 |
accordion_updates.append(gr.update(visible=True, label=f"Clip {i+1}: {media_elem['prompt'][:20]}..."))
|
| 36 |
prompt_updates.append(gr.update(value=media_elem['prompt']))
|
| 37 |
narration_updates.append(gr.update(value=tts_elem['text']))
|
| 38 |
-
media_updates.append(gr.update(value=None))
|
| 39 |
else:
|
| 40 |
accordion_updates.append(gr.update(visible=False))
|
| 41 |
prompt_updates.append(gr.update(value=""))
|
|
@@ -45,73 +133,41 @@ def process_script(topic, script_input):
|
|
| 45 |
return raw_script, num_clips, accordion_updates, prompt_updates, narration_updates, media_updates
|
| 46 |
|
| 47 |
def generate_video_full(resolution, render_speed, video_clip_percent, zoom_pan_effect,
|
| 48 |
-
bgm_upload, bgm_volume, subtitles_enabled,
|
| 49 |
-
outline_width, font_color, outline_color, position, num_clips,
|
| 50 |
-
*clip_inputs):
|
| 51 |
"""Generate the video using all settings and edited clip data."""
|
| 52 |
global TARGET_RESOLUTION, CAPTION_COLOR, TEMP_FOLDER
|
| 53 |
|
| 54 |
-
# Set resolution
|
| 55 |
TARGET_RESOLUTION = (1080, 1920) if resolution == "Short (1080x1920)" else (1920, 1080)
|
| 56 |
-
|
| 57 |
-
# Set caption settings
|
| 58 |
-
CAPTION_COLOR = font_color if subtitles_enabled else "transparent"
|
| 59 |
-
|
| 60 |
-
# Create temporary folder
|
| 61 |
TEMP_FOLDER = tempfile.mkdtemp()
|
| 62 |
|
| 63 |
-
# Parse clip inputs (visual_prompt, narration, custom_media for each clip)
|
| 64 |
clips_data = []
|
| 65 |
for i in range(num_clips):
|
| 66 |
idx = i * 3
|
| 67 |
-
visual_prompt = clip_inputs[idx]
|
| 68 |
-
narration = clip_inputs[idx + 1]
|
| 69 |
-
custom_media = clip_inputs[idx + 2]
|
| 70 |
clips_data.append({
|
| 71 |
-
'visual_prompt':
|
| 72 |
-
'narration':
|
| 73 |
-
'custom_media':
|
| 74 |
})
|
| 75 |
|
| 76 |
-
# Generate clips
|
| 77 |
clips = []
|
| 78 |
for idx, clip_data in enumerate(clips_data):
|
| 79 |
-
# Use custom media if provided, otherwise generate media
|
| 80 |
if clip_data['custom_media']:
|
| 81 |
media_path = clip_data['custom_media']
|
| 82 |
asset_type = 'video' if media_path.endswith(('.mp4', '.avi', '.mov')) else 'image'
|
| 83 |
else:
|
| 84 |
-
media_asset = generate_media(clip_data['visual_prompt'],
|
| 85 |
-
if not media_asset:
|
| 86 |
-
continue
|
| 87 |
media_path = media_asset['path']
|
| 88 |
asset_type = media_asset['asset_type']
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
media_path = media_asset['path']
|
| 97 |
-
asset_type = 'video'
|
| 98 |
-
|
| 99 |
-
# Generate TTS
|
| 100 |
-
tts_path = generate_tts(clip_data['narration'], 'en')
|
| 101 |
-
if not tts_path:
|
| 102 |
-
continue
|
| 103 |
-
|
| 104 |
-
# Create clip
|
| 105 |
duration = max(3, len(clip_data['narration'].split()) * 0.5)
|
| 106 |
-
clip = create_clip(
|
| 107 |
-
media_path=media_path,
|
| 108 |
-
asset_type=asset_type,
|
| 109 |
-
tts_path=tts_path,
|
| 110 |
-
duration=duration,
|
| 111 |
-
effects='fade-in',
|
| 112 |
-
narration_text=clip_data['narration'],
|
| 113 |
-
segment_index=idx
|
| 114 |
-
)
|
| 115 |
if clip and zoom_pan_effect and asset_type == 'image':
|
| 116 |
clip = apply_kenburns_effect(clip, TARGET_RESOLUTION)
|
| 117 |
if clip:
|
|
@@ -121,10 +177,8 @@ def generate_video_full(resolution, render_speed, video_clip_percent, zoom_pan_e
|
|
| 121 |
shutil.rmtree(TEMP_FOLDER)
|
| 122 |
return None, None
|
| 123 |
|
| 124 |
-
# Concatenate clips
|
| 125 |
final_video = concatenate_videoclips(clips, method="compose")
|
| 126 |
-
|
| 127 |
-
# Add background music if uploaded
|
| 128 |
if bgm_upload:
|
| 129 |
bg_music = AudioFileClip(bgm_upload).volumex(bgm_volume)
|
| 130 |
if bg_music.duration < final_video.duration:
|
|
@@ -133,19 +187,16 @@ def generate_video_full(resolution, render_speed, video_clip_percent, zoom_pan_e
|
|
| 133 |
bg_music = bg_music.subclip(0, final_video.duration)
|
| 134 |
final_video = final_video.set_audio(CompositeVideoClip([final_video.audio, bg_music]))
|
| 135 |
|
| 136 |
-
|
| 137 |
-
output_path = "final_video.mp4"
|
| 138 |
final_video.write_videofile(output_path, codec='libx264', fps=24, preset=render_speed)
|
| 139 |
-
|
| 140 |
-
# Clean up
|
| 141 |
shutil.rmtree(TEMP_FOLDER)
|
| 142 |
|
| 143 |
return output_path, output_path
|
| 144 |
|
| 145 |
-
# Gradio
|
| 146 |
-
with gr.Blocks(title="
|
| 147 |
-
gr.Markdown("#
|
| 148 |
-
gr.Markdown("Create
|
| 149 |
|
| 150 |
with gr.Row():
|
| 151 |
# Column 1: Content Input & Script Generation
|
|
@@ -153,59 +204,43 @@ with gr.Blocks(title="🚀 Orbit Video Engine") as demo:
|
|
| 153 |
gr.Markdown("### 1. Content Input")
|
| 154 |
topic_input = gr.Textbox(label="Topic", placeholder="e.g., Funny Cat Facts")
|
| 155 |
script_input = gr.Textbox(label="Or Paste Full Script", lines=10, placeholder="[Title]\nNarration...")
|
| 156 |
-
generate_button = gr.Button("
|
| 157 |
script_display = gr.Textbox(label="Generated Script", interactive=False, visible=False)
|
| 158 |
|
| 159 |
# Column 2: Clip Editor
|
| 160 |
with gr.Column(scale=2):
|
| 161 |
gr.Markdown("### 2. Edit Clips")
|
| 162 |
-
gr.
|
| 163 |
-
with gr.Column() as clip_editor:
|
| 164 |
clip_accordions = []
|
| 165 |
for i in range(MAX_CLIPS):
|
| 166 |
with gr.Accordion(f"Clip {i+1}", visible=False) as acc:
|
| 167 |
visual_prompt = gr.Textbox(label="Visual Prompt")
|
| 168 |
narration = gr.Textbox(label="Narration", lines=3)
|
| 169 |
-
custom_media = gr.File(label="Upload Custom Media
|
| 170 |
clip_accordions.append((acc, visual_prompt, narration, custom_media))
|
| 171 |
|
| 172 |
# Column 3: Settings & Output
|
| 173 |
with gr.Column(scale=1):
|
| 174 |
gr.Markdown("### 3. Video Settings")
|
| 175 |
resolution = gr.Radio(["Short (1080x1920)", "Full HD (1920x1080)"], label="Resolution", value="Full HD (1920x1080)")
|
| 176 |
-
render_speed = gr.Dropdown(["ultrafast", "
|
| 177 |
video_clip_percent = gr.Slider(0, 100, value=25, label="Video Clip Percentage")
|
| 178 |
-
zoom_pan_effect = gr.Checkbox(label="Add Zoom/Pan Effect
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
with gr.Accordion("Subtitle Settings", open=True):
|
| 185 |
-
subtitles_enabled = gr.Checkbox(label="Enable Subtitles", value=True)
|
| 186 |
-
font = gr.Dropdown(["Impact", "Arial", "Times New Roman"], label="Font", value="Arial")
|
| 187 |
-
font_size = gr.Number(label="Font Size", value=45)
|
| 188 |
-
outline_width = gr.Number(label="Outline Width", value=2)
|
| 189 |
-
font_color = gr.ColorPicker(label="Font Color", value="#FFFFFF")
|
| 190 |
-
outline_color = gr.ColorPicker(label="Outline Color", value="#000000")
|
| 191 |
-
position = gr.Radio(["center", "bottom", "top"], label="Position", value="bottom")
|
| 192 |
-
|
| 193 |
-
generate_video_button = gr.Button("🎬 Generate Video")
|
| 194 |
-
|
| 195 |
gr.Markdown("### 4. Output")
|
| 196 |
output_video = gr.Video(label="Generated Video")
|
| 197 |
download_button = gr.File(label="Download Video")
|
| 198 |
|
| 199 |
-
# State to track number of clips
|
| 200 |
num_clips_state = gr.State(value=0)
|
| 201 |
|
| 202 |
-
# Event handlers
|
| 203 |
generate_button.click(
|
| 204 |
fn=process_script,
|
| 205 |
inputs=[topic_input, script_input],
|
| 206 |
outputs=[script_display, num_clips_state] +
|
| 207 |
-
[comp for acc in clip_accordions for comp in [acc[0], acc[1], acc[2], acc[3]]]
|
| 208 |
-
_js="() => {return [document.querySelector('#topic_input textarea').value, document.querySelector('#script_input textarea').value]}"
|
| 209 |
).then(
|
| 210 |
fn=lambda x: gr.update(visible=True),
|
| 211 |
inputs=[script_display],
|
|
@@ -215,11 +250,9 @@ with gr.Blocks(title="🚀 Orbit Video Engine") as demo:
|
|
| 215 |
generate_video_button.click(
|
| 216 |
fn=generate_video_full,
|
| 217 |
inputs=[resolution, render_speed, video_clip_percent, zoom_pan_effect,
|
| 218 |
-
bgm_upload, bgm_volume, subtitles_enabled,
|
| 219 |
-
|
| 220 |
-
[comp for acc in clip_accordions for comp in acc[1:]], # visual_prompt, narration, custom_media
|
| 221 |
outputs=[output_video, download_button]
|
| 222 |
)
|
| 223 |
|
| 224 |
-
|
| 225 |
-
demo.launch(share=True)
|
|
|
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
import os
|
| 3 |
import tempfile
|
| 4 |
import shutil
|
| 5 |
+
import random
|
| 6 |
+
import requests
|
| 7 |
+
from moviepy.editor import VideoFileClip, concatenate_videoclips, AudioFileClip, ImageClip, TextClip
|
| 8 |
+
import numpy as np
|
| 9 |
+
from gtts import gTTS
|
| 10 |
+
import cv2
|
| 11 |
|
| 12 |
+
# Global configurations
|
| 13 |
+
PEXELS_API_KEY = 'BhJqbcdm9Vi90KqzXKAhnEHGsuFNv4irXuOjWtT761U49lRzo03qBGna' # Replace with your Pexels API key
|
| 14 |
+
OUTPUT_VIDEO_FILENAME = "final_video.mp4"
|
| 15 |
+
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
| 16 |
MAX_CLIPS = 10
|
| 17 |
+
TARGET_RESOLUTION = (1920, 1080) # Default resolution
|
| 18 |
+
TEMP_FOLDER = None
|
| 19 |
+
CAPTION_COLOR = "#FFFFFF"
|
| 20 |
+
|
| 21 |
+
# Helper Functions
|
| 22 |
+
def generate_script(topic):
|
| 23 |
+
"""Generate a simple script based on the topic."""
|
| 24 |
+
return f"[Title]\n{topic}\n[Narration]\nThis is a sample narration about {topic}."
|
| 25 |
+
|
| 26 |
+
def parse_script(script):
|
| 27 |
+
"""Parse the script into elements."""
|
| 28 |
+
elements = []
|
| 29 |
+
lines = script.strip().split('\n')
|
| 30 |
+
for line in lines:
|
| 31 |
+
if line.startswith('[Title]'):
|
| 32 |
+
elements.append({'type': 'media', 'prompt': line[7:].strip()})
|
| 33 |
+
elif line.startswith('[Narration]'):
|
| 34 |
+
elements.append({'type': 'tts', 'text': line[11:].strip()})
|
| 35 |
+
return elements
|
| 36 |
+
|
| 37 |
+
def search_pexels_videos(query, api_key):
|
| 38 |
+
"""Search for videos on Pexels."""
|
| 39 |
+
url = f"https://api.pexels.com/videos/search?query={quote(query)}&per_page=1"
|
| 40 |
+
headers = {"Authorization": api_key}
|
| 41 |
+
response = requests.get(url, headers=headers)
|
| 42 |
+
if response.status_code == 200:
|
| 43 |
+
data = response.json()
|
| 44 |
+
if data['videos']:
|
| 45 |
+
return data['videos'][0]['video_files'][0]['link']
|
| 46 |
+
return None
|
| 47 |
+
|
| 48 |
+
def download_video(url, output_path):
|
| 49 |
+
"""Download a video from a URL."""
|
| 50 |
+
response = requests.get(url, headers={"User-Agent": USER_AGENT}, stream=True)
|
| 51 |
+
if response.status_code == 200:
|
| 52 |
+
with open(output_path, 'wb') as f:
|
| 53 |
+
shutil.copyfileobj(response.raw, f)
|
| 54 |
+
return output_path
|
| 55 |
+
return None
|
| 56 |
+
|
| 57 |
+
def generate_tts(text, lang='en'):
|
| 58 |
+
"""Generate TTS audio using gTTS."""
|
| 59 |
+
tts = gTTS(text=text, lang=lang, slow=False)
|
| 60 |
+
tts_path = os.path.join(TEMP_FOLDER, f"tts_{random.randint(0, 10000)}.mp3")
|
| 61 |
+
tts.save(tts_path)
|
| 62 |
+
return tts_path
|
| 63 |
+
|
| 64 |
+
def resize_media(media_path, target_resolution):
|
| 65 |
+
"""Resize media to match target resolution."""
|
| 66 |
+
clip = VideoFileClip(media_path) if media_path.endswith(('.mp4', '.avi', '.mov')) else ImageClip(media_path)
|
| 67 |
+
return clip.resize(target_resolution)
|
| 68 |
+
|
| 69 |
+
def apply_kenburns_effect(clip, target_resolution):
|
| 70 |
+
"""Apply a zoom/pan (Ken Burns) effect to an image clip."""
|
| 71 |
+
w, h = target_resolution
|
| 72 |
+
clip = clip.resize(height=h * 1.2).crop(x_center=w/2, y_center=h/2, width=w, height=h)
|
| 73 |
+
return clip.fx(vfx.zoom_in, 0.1)
|
| 74 |
+
|
| 75 |
+
def create_clip(media_path, asset_type, tts_path, duration, effects, narration_text, segment_index):
|
| 76 |
+
"""Create a video clip with media and narration."""
|
| 77 |
+
if asset_type == 'video':
|
| 78 |
+
clip = VideoFileClip(media_path).subclip(0, min(duration, VideoFileClip(media_path).duration))
|
| 79 |
+
else:
|
| 80 |
+
clip = ImageClip(media_path, duration=duration)
|
| 81 |
+
|
| 82 |
+
clip = resize_media(media_path, TARGET_RESOLUTION)
|
| 83 |
+
if effects == 'fade-in':
|
| 84 |
+
clip = clip.crossfadein(1.0)
|
| 85 |
+
|
| 86 |
+
audio = AudioFileClip(tts_path)
|
| 87 |
+
clip = clip.set_audio(audio.set_duration(duration))
|
| 88 |
+
|
| 89 |
+
if CAPTION_COLOR != "transparent":
|
| 90 |
+
txt_clip = TextClip(narration_text, fontsize=45, color=CAPTION_COLOR, font="Arial", stroke_color="#000000", stroke_width=2)
|
| 91 |
+
txt_clip = txt_clip.set_position('bottom').set_duration(duration)
|
| 92 |
+
clip = CompositeVideoClip([clip, txt_clip])
|
| 93 |
+
|
| 94 |
+
return clip
|
| 95 |
+
|
| 96 |
+
def generate_media(prompt, current_index, total_segments):
|
| 97 |
+
"""Generate media (placeholder for actual generation)."""
|
| 98 |
+
media_path = os.path.join(TEMP_FOLDER, f"media_{current_index}.jpg")
|
| 99 |
+
with open(media_path, 'wb') as f:
|
| 100 |
+
f.write(requests.get("https://via.placeholder.com/1920x1080").content) # Placeholder image
|
| 101 |
+
return {'path': media_path, 'asset_type': 'image'}
|
| 102 |
|
| 103 |
def process_script(topic, script_input):
|
| 104 |
"""Process the topic or script and return updates for the UI."""
|
|
|
|
| 113 |
paired_elements = [(elements[i], elements[i + 1]) for i in range(0, len(elements) - 1, 2)]
|
| 114 |
num_clips = min(len(paired_elements), MAX_CLIPS)
|
| 115 |
|
|
|
|
| 116 |
accordion_updates = []
|
| 117 |
prompt_updates = []
|
| 118 |
narration_updates = []
|
|
|
|
| 123 |
accordion_updates.append(gr.update(visible=True, label=f"Clip {i+1}: {media_elem['prompt'][:20]}..."))
|
| 124 |
prompt_updates.append(gr.update(value=media_elem['prompt']))
|
| 125 |
narration_updates.append(gr.update(value=tts_elem['text']))
|
| 126 |
+
media_updates.append(gr.update(value=None))
|
| 127 |
else:
|
| 128 |
accordion_updates.append(gr.update(visible=False))
|
| 129 |
prompt_updates.append(gr.update(value=""))
|
|
|
|
| 133 |
return raw_script, num_clips, accordion_updates, prompt_updates, narration_updates, media_updates
|
| 134 |
|
| 135 |
def generate_video_full(resolution, render_speed, video_clip_percent, zoom_pan_effect,
|
| 136 |
+
bgm_upload, bgm_volume, subtitles_enabled, num_clips, *clip_inputs):
|
|
|
|
|
|
|
| 137 |
"""Generate the video using all settings and edited clip data."""
|
| 138 |
global TARGET_RESOLUTION, CAPTION_COLOR, TEMP_FOLDER
|
| 139 |
|
|
|
|
| 140 |
TARGET_RESOLUTION = (1080, 1920) if resolution == "Short (1080x1920)" else (1920, 1080)
|
| 141 |
+
CAPTION_COLOR = "#FFFFFF" if subtitles_enabled else "transparent"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
TEMP_FOLDER = tempfile.mkdtemp()
|
| 143 |
|
|
|
|
| 144 |
clips_data = []
|
| 145 |
for i in range(num_clips):
|
| 146 |
idx = i * 3
|
|
|
|
|
|
|
|
|
|
| 147 |
clips_data.append({
|
| 148 |
+
'visual_prompt': clip_inputs[idx],
|
| 149 |
+
'narration': clip_inputs[idx + 1],
|
| 150 |
+
'custom_media': clip_inputs[idx + 2]
|
| 151 |
})
|
| 152 |
|
|
|
|
| 153 |
clips = []
|
| 154 |
for idx, clip_data in enumerate(clips_data):
|
|
|
|
| 155 |
if clip_data['custom_media']:
|
| 156 |
media_path = clip_data['custom_media']
|
| 157 |
asset_type = 'video' if media_path.endswith(('.mp4', '.avi', '.mov')) else 'image'
|
| 158 |
else:
|
| 159 |
+
media_asset = generate_media(clip_data['visual_prompt'], idx, num_clips)
|
|
|
|
|
|
|
| 160 |
media_path = media_asset['path']
|
| 161 |
asset_type = media_asset['asset_type']
|
| 162 |
+
if random.random() < (video_clip_percent / 100):
|
| 163 |
+
video_url = search_pexels_videos(clip_data['visual_prompt'], PEXELS_API_KEY)
|
| 164 |
+
if video_url:
|
| 165 |
+
media_path = download_video(video_url, os.path.join(TEMP_FOLDER, f"video_{idx}.mp4"))
|
| 166 |
+
asset_type = 'video'
|
| 167 |
+
|
| 168 |
+
tts_path = generate_tts(clip_data['narration'])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
duration = max(3, len(clip_data['narration'].split()) * 0.5)
|
| 170 |
+
clip = create_clip(media_path, asset_type, tts_path, duration, 'fade-in', clip_data['narration'], idx)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
if clip and zoom_pan_effect and asset_type == 'image':
|
| 172 |
clip = apply_kenburns_effect(clip, TARGET_RESOLUTION)
|
| 173 |
if clip:
|
|
|
|
| 177 |
shutil.rmtree(TEMP_FOLDER)
|
| 178 |
return None, None
|
| 179 |
|
|
|
|
| 180 |
final_video = concatenate_videoclips(clips, method="compose")
|
| 181 |
+
|
|
|
|
| 182 |
if bgm_upload:
|
| 183 |
bg_music = AudioFileClip(bgm_upload).volumex(bgm_volume)
|
| 184 |
if bg_music.duration < final_video.duration:
|
|
|
|
| 187 |
bg_music = bg_music.subclip(0, final_video.duration)
|
| 188 |
final_video = final_video.set_audio(CompositeVideoClip([final_video.audio, bg_music]))
|
| 189 |
|
| 190 |
+
output_path = OUTPUT_VIDEO_FILENAME
|
|
|
|
| 191 |
final_video.write_videofile(output_path, codec='libx264', fps=24, preset=render_speed)
|
|
|
|
|
|
|
| 192 |
shutil.rmtree(TEMP_FOLDER)
|
| 193 |
|
| 194 |
return output_path, output_path
|
| 195 |
|
| 196 |
+
# Gradio Interface
|
| 197 |
+
with gr.Blocks(title="Video Generator") as demo:
|
| 198 |
+
gr.Markdown("# Video Generator")
|
| 199 |
+
gr.Markdown("Create custom videos with script, clips, and settings!")
|
| 200 |
|
| 201 |
with gr.Row():
|
| 202 |
# Column 1: Content Input & Script Generation
|
|
|
|
| 204 |
gr.Markdown("### 1. Content Input")
|
| 205 |
topic_input = gr.Textbox(label="Topic", placeholder="e.g., Funny Cat Facts")
|
| 206 |
script_input = gr.Textbox(label="Or Paste Full Script", lines=10, placeholder="[Title]\nNarration...")
|
| 207 |
+
generate_button = gr.Button("Generate Script & Load Clips")
|
| 208 |
script_display = gr.Textbox(label="Generated Script", interactive=False, visible=False)
|
| 209 |
|
| 210 |
# Column 2: Clip Editor
|
| 211 |
with gr.Column(scale=2):
|
| 212 |
gr.Markdown("### 2. Edit Clips")
|
| 213 |
+
with gr.Column():
|
|
|
|
| 214 |
clip_accordions = []
|
| 215 |
for i in range(MAX_CLIPS):
|
| 216 |
with gr.Accordion(f"Clip {i+1}", visible=False) as acc:
|
| 217 |
visual_prompt = gr.Textbox(label="Visual Prompt")
|
| 218 |
narration = gr.Textbox(label="Narration", lines=3)
|
| 219 |
+
custom_media = gr.File(label="Upload Custom Media")
|
| 220 |
clip_accordions.append((acc, visual_prompt, narration, custom_media))
|
| 221 |
|
| 222 |
# Column 3: Settings & Output
|
| 223 |
with gr.Column(scale=1):
|
| 224 |
gr.Markdown("### 3. Video Settings")
|
| 225 |
resolution = gr.Radio(["Short (1080x1920)", "Full HD (1920x1080)"], label="Resolution", value="Full HD (1920x1080)")
|
| 226 |
+
render_speed = gr.Dropdown(["ultrafast", "fast", "medium", "slow"], label="Render Speed", value="fast")
|
| 227 |
video_clip_percent = gr.Slider(0, 100, value=25, label="Video Clip Percentage")
|
| 228 |
+
zoom_pan_effect = gr.Checkbox(label="Add Zoom/Pan Effect", value=True)
|
| 229 |
+
bgm_upload = gr.Audio(label="Upload Background Music", type="filepath")
|
| 230 |
+
bgm_volume = gr.Slider(0.0, 1.0, value=0.15, label="BGM Volume")
|
| 231 |
+
subtitles_enabled = gr.Checkbox(label="Enable Subtitles", value=True)
|
| 232 |
+
generate_video_button = gr.Button("Generate Video")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
gr.Markdown("### 4. Output")
|
| 234 |
output_video = gr.Video(label="Generated Video")
|
| 235 |
download_button = gr.File(label="Download Video")
|
| 236 |
|
|
|
|
| 237 |
num_clips_state = gr.State(value=0)
|
| 238 |
|
|
|
|
| 239 |
generate_button.click(
|
| 240 |
fn=process_script,
|
| 241 |
inputs=[topic_input, script_input],
|
| 242 |
outputs=[script_display, num_clips_state] +
|
| 243 |
+
[comp for acc in clip_accordions for comp in [acc[0], acc[1], acc[2], acc[3]]]
|
|
|
|
| 244 |
).then(
|
| 245 |
fn=lambda x: gr.update(visible=True),
|
| 246 |
inputs=[script_display],
|
|
|
|
| 250 |
generate_video_button.click(
|
| 251 |
fn=generate_video_full,
|
| 252 |
inputs=[resolution, render_speed, video_clip_percent, zoom_pan_effect,
|
| 253 |
+
bgm_upload, bgm_volume, subtitles_enabled, num_clips_state] +
|
| 254 |
+
[comp for acc in clip_accordions for comp in acc[1:]],
|
|
|
|
| 255 |
outputs=[output_video, download_button]
|
| 256 |
)
|
| 257 |
|
| 258 |
+
demo.launch()
|
|
|