Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -1,28 +1,35 @@
|
|
1 |
# Import necessary libraries
|
2 |
-
from kokoro import KPipeline
|
3 |
-
import soundfile as sf
|
4 |
import os
|
5 |
-
|
6 |
-
VideoFileClip, concatenate_videoclips, AudioFileClip, ImageClip,
|
7 |
-
CompositeVideoClip, TextClip
|
8 |
-
)
|
9 |
-
import moviepy.video.fx.all as vfx
|
10 |
-
import moviepy.config as mpy_config
|
11 |
-
from pydub import AudioSegment
|
12 |
-
from PIL import Image
|
13 |
-
import tempfile
|
14 |
import random
|
15 |
-
import cv2
|
16 |
import math
|
|
|
|
|
17 |
import requests
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
from gtts import gTTS
|
19 |
import gradio as gr
|
20 |
-
import
|
21 |
-
import
|
|
|
|
|
|
|
|
|
|
|
22 |
|
23 |
-
# Initialize Kokoro TTS pipeline
|
24 |
-
|
25 |
-
|
|
|
|
|
|
|
|
|
|
|
26 |
mpy_config.change_settings({"IMAGEMAGICK_BINARY": "/usr/bin/convert"})
|
27 |
|
28 |
# Global Configuration
|
@@ -36,7 +43,9 @@ MAX_CLIPS = 10 # Maximum number of editable clips
|
|
36 |
# Temporary folder setup
|
37 |
TEMP_FOLDER = tempfile.mkdtemp()
|
38 |
|
39 |
-
#
|
|
|
|
|
40 |
def generate_script(user_input):
|
41 |
headers = {
|
42 |
'Authorization': f'Bearer {OPENROUTER_API_KEY}',
|
@@ -95,10 +104,10 @@ Now here is the Topic/scrip: {user_input}
|
|
95 |
return response.json()['choices'][0]['message']['content']
|
96 |
else:
|
97 |
print(f"API Error {response.status_code}: {response.text}")
|
98 |
-
return
|
99 |
except Exception as e:
|
100 |
print(f"Request failed: {str(e)}")
|
101 |
-
return
|
102 |
|
103 |
def parse_script(script_text):
|
104 |
sections = {}
|
@@ -110,7 +119,7 @@ def parse_script(script_text):
|
|
110 |
bracket_start = line.find("[")
|
111 |
bracket_end = line.find("]", bracket_start)
|
112 |
if bracket_start != -1 and bracket_end != -1:
|
113 |
-
if current_title
|
114 |
sections[current_title] = current_text.strip()
|
115 |
current_title = line[bracket_start+1:bracket_end]
|
116 |
current_text = line[bracket_end+1:].strip()
|
@@ -121,6 +130,7 @@ def parse_script(script_text):
|
|
121 |
clips = [{"title": title, "narration": narration} for title, narration in sections.items()]
|
122 |
return clips
|
123 |
|
|
|
124 |
def search_pexels_videos(query, pexels_api_key):
|
125 |
headers = {'Authorization': pexels_api_key}
|
126 |
url = "https://api.pexels.com/videos/search"
|
@@ -131,9 +141,10 @@ def search_pexels_videos(query, pexels_api_key):
|
|
131 |
videos = response.json().get("videos", [])
|
132 |
hd_videos = [v["video_files"][0]["link"] for v in videos if v["video_files"] and v["video_files"][0]["quality"] == "hd"]
|
133 |
return random.choice(hd_videos) if hd_videos else None
|
|
|
134 |
except Exception as e:
|
135 |
print(f"Video search error: {e}")
|
136 |
-
|
137 |
|
138 |
def search_pexels_images(query, pexels_api_key):
|
139 |
headers = {'Authorization': pexels_api_key}
|
@@ -144,9 +155,10 @@ def search_pexels_images(query, pexels_api_key):
|
|
144 |
if response.status_code == 200:
|
145 |
photos = response.json().get("photos", [])
|
146 |
return random.choice(photos)["src"]["original"] if photos else None
|
|
|
147 |
except Exception as e:
|
148 |
print(f"Image search error: {e}")
|
149 |
-
|
150 |
|
151 |
def search_google_images(query):
|
152 |
search_url = f"https://www.google.com/search?q={quote(query)}&tbm=isch"
|
@@ -188,9 +200,10 @@ def download_video(video_url, filename):
|
|
188 |
print(f"Video download error: {e}")
|
189 |
return None
|
190 |
|
|
|
191 |
def generate_media(prompt, custom_media=None, video_prob=0.25):
|
192 |
-
if custom_media:
|
193 |
-
asset_type = "video" if custom_media.endswith(('.mp4', '.avi', '.mov')) else "image"
|
194 |
return {"path": custom_media, "asset_type": asset_type}
|
195 |
safe_prompt = re.sub(r'[^\w\s-]', '', prompt).strip().replace(' ', '_')
|
196 |
if "news" in prompt.lower():
|
@@ -207,26 +220,33 @@ def generate_media(prompt, custom_media=None, video_prob=0.25):
|
|
207 |
image_url = search_pexels_images(prompt, PEXELS_API_KEY)
|
208 |
if image_url and download_image(image_url, image_file):
|
209 |
return {"path": image_file, "asset_type": "image"}
|
|
|
210 |
return None
|
211 |
|
212 |
-
def generate_tts(text, voice):
|
|
|
|
|
213 |
safe_text = re.sub(r'[^\w\s-]', '', text[:10]).strip().replace(' ', '_')
|
214 |
file_path = os.path.join(TEMP_FOLDER, f"tts_{safe_text}.wav")
|
215 |
if os.path.exists(file_path):
|
216 |
return file_path
|
217 |
try:
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
return file_path
|
|
|
|
|
|
|
229 |
|
|
|
230 |
def apply_kenburns_effect(clip, target_resolution):
|
231 |
target_w, target_h = target_resolution
|
232 |
clip_aspect = clip.w / clip.h
|
@@ -266,8 +286,11 @@ def add_background_music(final_video, bgm_path=None, bgm_volume=0.15):
|
|
266 |
return final_video
|
267 |
|
268 |
def create_clip(media_path, asset_type, tts_path, narration_text, target_resolution, subtitles_enabled, font, font_size, outline_width, font_color, outline_color, position, zoom_pan_effect):
|
269 |
-
|
270 |
-
|
|
|
|
|
|
|
271 |
if asset_type == "video":
|
272 |
clip = VideoFileClip(media_path)
|
273 |
clip = resize_to_fill(clip, target_resolution)
|
@@ -277,10 +300,10 @@ def create_clip(media_path, asset_type, tts_path, narration_text, target_resolut
|
|
277 |
if zoom_pan_effect:
|
278 |
clip = apply_kenburns_effect(clip, target_resolution)
|
279 |
clip = resize_to_fill(clip, target_resolution)
|
280 |
-
if subtitles_enabled and narration_text:
|
281 |
words = narration_text.split()
|
282 |
chunks = [' '.join(words[i:i+5]) for i in range(0, len(words), 5)]
|
283 |
-
chunk_duration = audio_clip.duration / len(chunks)
|
284 |
subtitle_clips = []
|
285 |
y_position = target_resolution[1] * (0.1 if position == "top" else 0.8 if position == "bottom" else 0.5)
|
286 |
for i, chunk in enumerate(chunks):
|
@@ -297,9 +320,11 @@ def create_clip(media_path, asset_type, tts_path, narration_text, target_resolut
|
|
297 |
).set_start(i * chunk_duration).set_end((i + 1) * chunk_duration).set_position(('center', y_position))
|
298 |
subtitle_clips.append(txt_clip)
|
299 |
clip = CompositeVideoClip([clip] + subtitle_clips)
|
300 |
-
|
|
|
|
|
301 |
|
302 |
-
|
303 |
def generate_video(resolution, render_speed, video_clip_percent, zoom_pan_effect, bgm_upload, bgm_volume, subtitles_enabled, font, font_size, outline_width, font_color, outline_color, position, *clip_data):
|
304 |
target_resolution = (1080, 1920) if resolution == "Short (1080x1920)" else (1920, 1080)
|
305 |
clips = []
|
@@ -309,21 +334,23 @@ def generate_video(resolution, render_speed, video_clip_percent, zoom_pan_effect
|
|
309 |
media_asset = generate_media(prompt, custom_media, video_clip_percent / 100.0)
|
310 |
if not media_asset:
|
311 |
continue
|
312 |
-
tts_path = generate_tts(narration
|
313 |
clip = create_clip(
|
314 |
media_asset['path'], media_asset['asset_type'], tts_path, narration, target_resolution,
|
315 |
subtitles_enabled, font, font_size, outline_width, font_color, outline_color, position, zoom_pan_effect
|
316 |
)
|
317 |
-
|
|
|
318 |
if not clips:
|
|
|
319 |
return None
|
320 |
final_video = concatenate_videoclips(clips, method="compose")
|
321 |
final_video = add_background_music(final_video, bgm_upload, bgm_volume)
|
322 |
-
final_video.write_videofile(OUTPUT_VIDEO_FILENAME, codec='libx264', fps=24, preset=render_speed)
|
323 |
shutil.rmtree(TEMP_FOLDER)
|
324 |
return OUTPUT_VIDEO_FILENAME
|
325 |
|
326 |
-
|
327 |
def load_clips(topic, script):
|
328 |
raw_script = script.strip() if script.strip() else generate_script(topic)
|
329 |
clips = parse_script(raw_script)[:MAX_CLIPS]
|
@@ -333,11 +360,14 @@ def load_clips(topic, script):
|
|
333 |
updates.extend([
|
334 |
gr.update(value=clips[i]["title"]),
|
335 |
gr.update(value=clips[i]["narration"]),
|
336 |
-
gr.update(value=None)
|
337 |
-
gr.update(visible=True)
|
338 |
])
|
339 |
else:
|
340 |
-
updates.extend([
|
|
|
|
|
|
|
|
|
341 |
return updates
|
342 |
|
343 |
# Gradio Interface
|
@@ -349,19 +379,18 @@ with gr.Blocks(title="🚀 Orbit Video Engine") as app:
|
|
349 |
topic_input = gr.Textbox(label="Video Topic", placeholder="e.g., Funny Cat Facts")
|
350 |
script_input = gr.Textbox(label="Or Paste Full Script", lines=10, placeholder="[Title]\nNarration...")
|
351 |
generate_script_btn = gr.Button("📝 Generate Script & Load Clips")
|
352 |
-
generated_script_display = gr.Textbox(label="Generated Script", interactive=False)
|
353 |
|
354 |
# Column 2: Clip Editor
|
355 |
with gr.Column():
|
356 |
gr.Markdown("### 2. Edit Clips")
|
357 |
gr.Markdown("Modify prompts, narration, and upload custom media for each clip.")
|
358 |
-
|
359 |
for i in range(MAX_CLIPS):
|
360 |
-
with gr.Row(
|
361 |
-
prompt = gr.Textbox(label="Visual Prompt")
|
362 |
-
narration = gr.Textbox(label="Narration", lines=3)
|
363 |
-
custom_media = gr.File(label="Upload Custom Media (Image/Video)", file_types=["image", "video"])
|
364 |
-
clip_rows.append(row)
|
365 |
prompts.append(prompt)
|
366 |
narrations.append(narration)
|
367 |
custom_medias.append(custom_media)
|
@@ -390,14 +419,15 @@ with gr.Blocks(title="🚀 Orbit Video Engine") as app:
|
|
390 |
|
391 |
# Event Handlers
|
392 |
generate_script_btn.click(
|
393 |
-
load_clips,
|
394 |
inputs=[topic_input, script_input],
|
395 |
-
outputs=[generated_script_display] + prompts + narrations + custom_medias
|
396 |
)
|
397 |
generate_video_btn.click(
|
398 |
-
generate_video,
|
399 |
inputs=[resolution, render_speed, video_clip_percent, zoom_pan_effect, bgm_upload, bgm_volume, subtitles_enabled, font, font_size, outline_width, font_color, outline_color, position] + prompts + narrations + custom_medias,
|
400 |
outputs=video_output
|
401 |
)
|
402 |
|
|
|
403 |
app.launch(share=True)
|
|
|
1 |
# Import necessary libraries
|
|
|
|
|
2 |
import os
|
3 |
+
import re
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
import random
|
|
|
5 |
import math
|
6 |
+
import shutil
|
7 |
+
import tempfile
|
8 |
import requests
|
9 |
+
from urllib.parse import quote
|
10 |
+
from bs4 import BeautifulSoup
|
11 |
+
import numpy as np
|
12 |
+
from PIL import Image
|
13 |
+
import cv2
|
14 |
+
from pydub import AudioSegment
|
15 |
from gtts import gTTS
|
16 |
import gradio as gr
|
17 |
+
import soundfile as sf
|
18 |
+
from moviepy.editor import (
|
19 |
+
VideoFileClip, concatenate_videoclips, AudioFileClip, ImageClip,
|
20 |
+
CompositeVideoClip, TextClip, CompositeAudioClip
|
21 |
+
)
|
22 |
+
import moviepy.video.fx.all as vfx
|
23 |
+
import moviepy.config as mpy_config
|
24 |
|
25 |
+
# Initialize Kokoro TTS pipeline (assuming it's available; replace with dummy if not)
|
26 |
+
try:
|
27 |
+
from kokoro import KPipeline
|
28 |
+
pipeline = KPipeline(lang_code='a')
|
29 |
+
except ImportError:
|
30 |
+
pipeline = None # Fallback to gTTS if Kokoro is unavailable
|
31 |
+
|
32 |
+
# Ensure ImageMagick binary is set (adjust path as needed for your system)
|
33 |
mpy_config.change_settings({"IMAGEMAGICK_BINARY": "/usr/bin/convert"})
|
34 |
|
35 |
# Global Configuration
|
|
|
43 |
# Temporary folder setup
|
44 |
TEMP_FOLDER = tempfile.mkdtemp()
|
45 |
|
46 |
+
# Helper Functions
|
47 |
+
|
48 |
+
## Script Generation
|
49 |
def generate_script(user_input):
|
50 |
headers = {
|
51 |
'Authorization': f'Bearer {OPENROUTER_API_KEY}',
|
|
|
104 |
return response.json()['choices'][0]['message']['content']
|
105 |
else:
|
106 |
print(f"API Error {response.status_code}: {response.text}")
|
107 |
+
return "Failed to generate script due to API error."
|
108 |
except Exception as e:
|
109 |
print(f"Request failed: {str(e)}")
|
110 |
+
return "Oops, script generation broke. Blame the internet!"
|
111 |
|
112 |
def parse_script(script_text):
|
113 |
sections = {}
|
|
|
119 |
bracket_start = line.find("[")
|
120 |
bracket_end = line.find("]", bracket_start)
|
121 |
if bracket_start != -1 and bracket_end != -1:
|
122 |
+
if current_title:
|
123 |
sections[current_title] = current_text.strip()
|
124 |
current_title = line[bracket_start+1:bracket_end]
|
125 |
current_text = line[bracket_end+1:].strip()
|
|
|
130 |
clips = [{"title": title, "narration": narration} for title, narration in sections.items()]
|
131 |
return clips
|
132 |
|
133 |
+
## Media Fetching
|
134 |
def search_pexels_videos(query, pexels_api_key):
|
135 |
headers = {'Authorization': pexels_api_key}
|
136 |
url = "https://api.pexels.com/videos/search"
|
|
|
141 |
videos = response.json().get("videos", [])
|
142 |
hd_videos = [v["video_files"][0]["link"] for v in videos if v["video_files"] and v["video_files"][0]["quality"] == "hd"]
|
143 |
return random.choice(hd_videos) if hd_videos else None
|
144 |
+
return None
|
145 |
except Exception as e:
|
146 |
print(f"Video search error: {e}")
|
147 |
+
return None
|
148 |
|
149 |
def search_pexels_images(query, pexels_api_key):
|
150 |
headers = {'Authorization': pexels_api_key}
|
|
|
155 |
if response.status_code == 200:
|
156 |
photos = response.json().get("photos", [])
|
157 |
return random.choice(photos)["src"]["original"] if photos else None
|
158 |
+
return None
|
159 |
except Exception as e:
|
160 |
print(f"Image search error: {e}")
|
161 |
+
return None
|
162 |
|
163 |
def search_google_images(query):
|
164 |
search_url = f"https://www.google.com/search?q={quote(query)}&tbm=isch"
|
|
|
200 |
print(f"Video download error: {e}")
|
201 |
return None
|
202 |
|
203 |
+
## Media and TTS Generation
|
204 |
def generate_media(prompt, custom_media=None, video_prob=0.25):
|
205 |
+
if isinstance(custom_media, str) and os.path.exists(custom_media):
|
206 |
+
asset_type = "video" if custom_media.lower().endswith(('.mp4', '.avi', '.mov')) else "image"
|
207 |
return {"path": custom_media, "asset_type": asset_type}
|
208 |
safe_prompt = re.sub(r'[^\w\s-]', '', prompt).strip().replace(' ', '_')
|
209 |
if "news" in prompt.lower():
|
|
|
220 |
image_url = search_pexels_images(prompt, PEXELS_API_KEY)
|
221 |
if image_url and download_image(image_url, image_file):
|
222 |
return {"path": image_file, "asset_type": "image"}
|
223 |
+
print(f"No media generated for prompt: {prompt}")
|
224 |
return None
|
225 |
|
226 |
+
def generate_tts(text, voice='en'):
|
227 |
+
if not text.strip():
|
228 |
+
return None
|
229 |
safe_text = re.sub(r'[^\w\s-]', '', text[:10]).strip().replace(' ', '_')
|
230 |
file_path = os.path.join(TEMP_FOLDER, f"tts_{safe_text}.wav")
|
231 |
if os.path.exists(file_path):
|
232 |
return file_path
|
233 |
try:
|
234 |
+
if pipeline:
|
235 |
+
audio_segments = [audio for _, _, audio in pipeline(text, voice='af_heart', speed=0.9, split_pattern=r'\n+')]
|
236 |
+
full_audio = np.concatenate(audio_segments) if len(audio_segments) > 1 else audio_segments[0]
|
237 |
+
sf.write(file_path, full_audio, 24000)
|
238 |
+
else:
|
239 |
+
tts = gTTS(text=text, lang=voice)
|
240 |
+
mp3_path = os.path.join(TEMP_FOLDER, f"tts_{safe_text}.mp3")
|
241 |
+
tts.save(mp3_path)
|
242 |
+
AudioSegment.from_mp3(mp3_path).export(file_path, format="wav")
|
243 |
+
os.remove(mp3_path)
|
244 |
return file_path
|
245 |
+
except Exception as e:
|
246 |
+
print(f"TTS generation failed: {e}")
|
247 |
+
return None
|
248 |
|
249 |
+
## Video Processing
|
250 |
def apply_kenburns_effect(clip, target_resolution):
|
251 |
target_w, target_h = target_resolution
|
252 |
clip_aspect = clip.w / clip.h
|
|
|
286 |
return final_video
|
287 |
|
288 |
def create_clip(media_path, asset_type, tts_path, narration_text, target_resolution, subtitles_enabled, font, font_size, outline_width, font_color, outline_color, position, zoom_pan_effect):
|
289 |
+
if not media_path or not os.path.exists(media_path) or (tts_path and not os.path.exists(tts_path)):
|
290 |
+
print("Missing media or TTS file")
|
291 |
+
return None
|
292 |
+
audio_clip = AudioFileClip(tts_path).audio_fadeout(0.2) if tts_path else None
|
293 |
+
target_duration = audio_clip.duration + 0.2 if audio_clip else 5.0
|
294 |
if asset_type == "video":
|
295 |
clip = VideoFileClip(media_path)
|
296 |
clip = resize_to_fill(clip, target_resolution)
|
|
|
300 |
if zoom_pan_effect:
|
301 |
clip = apply_kenburns_effect(clip, target_resolution)
|
302 |
clip = resize_to_fill(clip, target_resolution)
|
303 |
+
if subtitles_enabled and narration_text and audio_clip:
|
304 |
words = narration_text.split()
|
305 |
chunks = [' '.join(words[i:i+5]) for i in range(0, len(words), 5)]
|
306 |
+
chunk_duration = audio_clip.duration / max(len(chunks), 1)
|
307 |
subtitle_clips = []
|
308 |
y_position = target_resolution[1] * (0.1 if position == "top" else 0.8 if position == "bottom" else 0.5)
|
309 |
for i, chunk in enumerate(chunks):
|
|
|
320 |
).set_start(i * chunk_duration).set_end((i + 1) * chunk_duration).set_position(('center', y_position))
|
321 |
subtitle_clips.append(txt_clip)
|
322 |
clip = CompositeVideoClip([clip] + subtitle_clips)
|
323 |
+
if audio_clip:
|
324 |
+
clip = clip.set_audio(audio_clip)
|
325 |
+
return clip
|
326 |
|
327 |
+
## Main Video Generation Function
|
328 |
def generate_video(resolution, render_speed, video_clip_percent, zoom_pan_effect, bgm_upload, bgm_volume, subtitles_enabled, font, font_size, outline_width, font_color, outline_color, position, *clip_data):
|
329 |
target_resolution = (1080, 1920) if resolution == "Short (1080x1920)" else (1920, 1080)
|
330 |
clips = []
|
|
|
334 |
media_asset = generate_media(prompt, custom_media, video_clip_percent / 100.0)
|
335 |
if not media_asset:
|
336 |
continue
|
337 |
+
tts_path = generate_tts(narration) if narration.strip() else None
|
338 |
clip = create_clip(
|
339 |
media_asset['path'], media_asset['asset_type'], tts_path, narration, target_resolution,
|
340 |
subtitles_enabled, font, font_size, outline_width, font_color, outline_color, position, zoom_pan_effect
|
341 |
)
|
342 |
+
if clip:
|
343 |
+
clips.append(clip)
|
344 |
if not clips:
|
345 |
+
print("No clips generated.")
|
346 |
return None
|
347 |
final_video = concatenate_videoclips(clips, method="compose")
|
348 |
final_video = add_background_music(final_video, bgm_upload, bgm_volume)
|
349 |
+
final_video.write_videofile(OUTPUT_VIDEO_FILENAME, codec='libx264', fps=24, preset=render_speed, logger=None)
|
350 |
shutil.rmtree(TEMP_FOLDER)
|
351 |
return OUTPUT_VIDEO_FILENAME
|
352 |
|
353 |
+
## Load Clips Function
|
354 |
def load_clips(topic, script):
|
355 |
raw_script = script.strip() if script.strip() else generate_script(topic)
|
356 |
clips = parse_script(raw_script)[:MAX_CLIPS]
|
|
|
360 |
updates.extend([
|
361 |
gr.update(value=clips[i]["title"]),
|
362 |
gr.update(value=clips[i]["narration"]),
|
363 |
+
gr.update(value=None) # Clear custom media
|
|
|
364 |
])
|
365 |
else:
|
366 |
+
updates.extend([
|
367 |
+
gr.update(value=""),
|
368 |
+
gr.update(value=""),
|
369 |
+
gr.update(value=None)
|
370 |
+
])
|
371 |
return updates
|
372 |
|
373 |
# Gradio Interface
|
|
|
379 |
topic_input = gr.Textbox(label="Video Topic", placeholder="e.g., Funny Cat Facts")
|
380 |
script_input = gr.Textbox(label="Or Paste Full Script", lines=10, placeholder="[Title]\nNarration...")
|
381 |
generate_script_btn = gr.Button("📝 Generate Script & Load Clips")
|
382 |
+
generated_script_display = gr.Textbox(label="Generated Script", interactive=False, lines=10)
|
383 |
|
384 |
# Column 2: Clip Editor
|
385 |
with gr.Column():
|
386 |
gr.Markdown("### 2. Edit Clips")
|
387 |
gr.Markdown("Modify prompts, narration, and upload custom media for each clip.")
|
388 |
+
prompts, narrations, custom_medias = [], [], []
|
389 |
for i in range(MAX_CLIPS):
|
390 |
+
with gr.Row(): # Always visible
|
391 |
+
prompt = gr.Textbox(label=f"Clip {i+1} Visual Prompt")
|
392 |
+
narration = gr.Textbox(label=f"Clip {i+1} Narration", lines=3)
|
393 |
+
custom_media = gr.File(label=f"Clip {i+1} Upload Custom Media (Image/Video)", file_types=["image", "video"], value=None)
|
|
|
394 |
prompts.append(prompt)
|
395 |
narrations.append(narration)
|
396 |
custom_medias.append(custom_media)
|
|
|
419 |
|
420 |
# Event Handlers
|
421 |
generate_script_btn.click(
|
422 |
+
fn=load_clips,
|
423 |
inputs=[topic_input, script_input],
|
424 |
+
outputs=[generated_script_display] + prompts + narrations + custom_medias
|
425 |
)
|
426 |
generate_video_btn.click(
|
427 |
+
fn=generate_video,
|
428 |
inputs=[resolution, render_speed, video_clip_percent, zoom_pan_effect, bgm_upload, bgm_volume, subtitles_enabled, font, font_size, outline_width, font_color, outline_color, position] + prompts + narrations + custom_medias,
|
429 |
outputs=video_output
|
430 |
)
|
431 |
|
432 |
+
# Launch the app
|
433 |
app.launch(share=True)
|