Spaces:
Running on Zero
Running on Zero
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import spaces
|
| 2 |
import os
|
| 3 |
import shutil
|
|
@@ -7,6 +15,7 @@ import importlib.util
|
|
| 7 |
import sys
|
| 8 |
import numpy as np
|
| 9 |
import gradio as gr
|
|
|
|
| 10 |
|
| 11 |
# --- Constants ---
|
| 12 |
BASE_DIR = os.getcwd()
|
|
@@ -20,6 +29,33 @@ if not hasattr(np, 'int'):
|
|
| 20 |
np.int = int
|
| 21 |
print("Applied global NumPy compatibility patch for scikit-video.")
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
# --- Setup Functions ---
|
| 24 |
def run_command(command):
|
| 25 |
subprocess.run(command, shell=True, check=True)
|
|
@@ -28,7 +64,6 @@ def patch_skvideo_fully():
|
|
| 28 |
"""Full recursive patch for ALL skvideo files (fixes np.int / np.float everywhere)"""
|
| 29 |
print("Applying FULL NumPy compatibility patch to entire skvideo package...")
|
| 30 |
try:
|
| 31 |
-
# Find skvideo install location
|
| 32 |
skvideo_dir = subprocess.check_output(
|
| 33 |
[sys.executable, "-c", "import skvideo; print(skvideo.__path__[0])"],
|
| 34 |
text=True
|
|
@@ -52,24 +87,30 @@ def patch_skvideo_fully():
|
|
| 52 |
print(f"β οΈ skvideo patch failed: {e}")
|
| 53 |
|
| 54 |
def setup_environment():
|
| 55 |
-
"""Full setup with complete skvideo fix"""
|
| 56 |
print("--- Starting Environment Setup ---")
|
| 57 |
|
| 58 |
-
# 1.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
print("Installing scikit-video, imageio[ffmpeg], and RIFE requirements...")
|
| 60 |
subprocess.check_call([sys.executable, "-m", "pip", "install", "-q",
|
| 61 |
"scikit-video", "imageio[ffmpeg]", "tqdm", "opencv-python>=4.1.2",
|
| 62 |
"moviepy>=1.0.3", "torchvision"])
|
| 63 |
|
| 64 |
-
#
|
| 65 |
patch_skvideo_fully()
|
| 66 |
|
| 67 |
-
#
|
| 68 |
if not os.path.exists(RIFE_DIR):
|
| 69 |
print("Cloning Practical-RIFE...")
|
| 70 |
run_command(f"git clone https://github.com/hzwer/Practical-RIFE {RIFE_DIR}")
|
| 71 |
|
| 72 |
-
#
|
| 73 |
hdv3_path = os.path.join(RIFE_DIR, "HDv3")
|
| 74 |
if not os.path.exists(hdv3_path) or not os.path.exists(os.path.join(RIFE_DIR, "train_log", "RIFE_HDv3.py")):
|
| 75 |
print("Downloading RIFE v4.26 model weights...")
|
|
@@ -97,7 +138,7 @@ def setup_environment():
|
|
| 97 |
if os.path.exists(zip_path):
|
| 98 |
os.remove(zip_path)
|
| 99 |
|
| 100 |
-
#
|
| 101 |
inference_script = os.path.join(RIFE_DIR, "inference_video.py")
|
| 102 |
if os.path.exists(inference_script):
|
| 103 |
with open(inference_script, 'r') as f:
|
|
@@ -111,7 +152,7 @@ def setup_environment():
|
|
| 111 |
f.write(new_content)
|
| 112 |
print("β
RIFE patched for libx264")
|
| 113 |
|
| 114 |
-
#
|
| 115 |
if os.path.exists(os.path.join(RIFE_DIR, "train_log", "RIFE_HDv3.py")) and os.path.exists(os.path.join(hdv3_path, "flownet.pkl")):
|
| 116 |
print("β
Model files verified")
|
| 117 |
else:
|
|
@@ -152,7 +193,7 @@ def create_boomerang_loop(input_path, output_path, fps):
|
|
| 152 |
print(f"β Boomerang Failed: {e}")
|
| 153 |
return False
|
| 154 |
|
| 155 |
-
# --- Processing Function (
|
| 156 |
@spaces.GPU(required=True)
|
| 157 |
def interpolate_video(input_video_path, multi_factor, create_boomerang=False):
|
| 158 |
if input_video_path is None:
|
|
@@ -163,13 +204,11 @@ def interpolate_video(input_video_path, multi_factor, create_boomerang=False):
|
|
| 163 |
final_output_path = os.path.join(BASE_DIR, "final_interpolated.mp4")
|
| 164 |
boomerang_path = os.path.join(BASE_DIR, "final_boomerang.mp4")
|
| 165 |
|
| 166 |
-
# Clean previous files
|
| 167 |
for p in [output_path, final_output_path, boomerang_path]:
|
| 168 |
if os.path.exists(p): os.remove(p)
|
| 169 |
expected_no_audio = output_path.replace(".mp4", "_noaudio.mp4")
|
| 170 |
if os.path.exists(expected_no_audio): os.remove(expected_no_audio)
|
| 171 |
|
| 172 |
-
# Torch diagnostics
|
| 173 |
import torch
|
| 174 |
print(f"π₯ CUDA available: {torch.cuda.is_available()}")
|
| 175 |
if torch.cuda.is_available():
|
|
@@ -217,7 +256,7 @@ def interpolate_video(input_video_path, multi_factor, create_boomerang=False):
|
|
| 217 |
finally:
|
| 218 |
os.chdir(BASE_DIR)
|
| 219 |
|
| 220 |
-
# --- Stitch function (unchanged) ---
|
| 221 |
def stitch_videos(video_files, resolution_choice):
|
| 222 |
if not video_files: return None
|
| 223 |
try:
|
|
@@ -226,7 +265,7 @@ def stitch_videos(video_files, resolution_choice):
|
|
| 226 |
except:
|
| 227 |
target_w, target_h = "1920", "1080"
|
| 228 |
|
| 229 |
-
print(f"Stitching {len(video_files)} videos into {target_w}x{target_h}...")
|
| 230 |
stitch_list_path = os.path.join(BASE_DIR, "stitch_list.txt")
|
| 231 |
output_stitched = os.path.join(BASE_DIR, "final_stitched.mp4")
|
| 232 |
temp_dir = os.path.join(BASE_DIR, "temp_stitch")
|
|
@@ -249,10 +288,55 @@ def stitch_videos(video_files, resolution_choice):
|
|
| 249 |
shutil.rmtree(temp_dir)
|
| 250 |
return output_stitched
|
| 251 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
# --- Gradio UI ---
|
| 253 |
-
with gr.Blocks(title="RIFE + Boomerang") as demo:
|
| 254 |
-
gr.Markdown("# ποΈ RIFE: Interpolate & Boomerang
|
| 255 |
-
gr.Markdown("**β
Fixed skvideo np.int error** β now works on latest NumPy!")
|
| 256 |
|
| 257 |
with gr.Tabs():
|
| 258 |
with gr.TabItem("1. Smooth Video + Boomerang"):
|
|
@@ -268,14 +352,79 @@ with gr.Blocks(title="RIFE + Boomerang") as demo:
|
|
| 268 |
interp_btn.click(interpolate_video, inputs=[video_input, multi_select, boomerang_check], outputs=video_output)
|
| 269 |
|
| 270 |
with gr.TabItem("2. Stitch Videos"):
|
| 271 |
-
gr.Markdown("Upload
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 272 |
with gr.Row():
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
|
| 281 |
-
demo.launch()
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!-- FULL UPDATED CODE - Copy and replace your entire script with this -->
|
| 2 |
+
<!DOCTYPE html>
|
| 3 |
+
<html>
|
| 4 |
+
<head>
|
| 5 |
+
<title>RIFE + Boomerang + Enhanced Stitch</title>
|
| 6 |
+
</head>
|
| 7 |
+
<body>
|
| 8 |
+
<pre><code>
|
| 9 |
import spaces
|
| 10 |
import os
|
| 11 |
import shutil
|
|
|
|
| 15 |
import sys
|
| 16 |
import numpy as np
|
| 17 |
import gradio as gr
|
| 18 |
+
import time # for any fallback timing
|
| 19 |
|
| 20 |
# --- Constants ---
|
| 21 |
BASE_DIR = os.getcwd()
|
|
|
|
| 29 |
np.int = int
|
| 30 |
print("Applied global NumPy compatibility patch for scikit-video.")
|
| 31 |
|
| 32 |
+
# --- NEW: Thumbnail helper (used by stitching tab) ---
|
| 33 |
+
def create_thumbnails(video_paths):
|
| 34 |
+
"""Extract first frame thumbnail for each video + show position number"""
|
| 35 |
+
if not video_paths:
|
| 36 |
+
return []
|
| 37 |
+
temp_dir = os.path.join(BASE_DIR, "temp_thumbs")
|
| 38 |
+
os.makedirs(temp_dir, exist_ok=True)
|
| 39 |
+
|
| 40 |
+
gallery_items = []
|
| 41 |
+
for i, vid_path in enumerate(video_paths):
|
| 42 |
+
thumb_name = f"thumb_{i:03d}_{os.path.basename(vid_path)}.jpg"
|
| 43 |
+
thumb_path = os.path.join(temp_dir, thumb_name)
|
| 44 |
+
|
| 45 |
+
cmd = ['ffmpeg', '-i', vid_path, '-ss', '00:00:00.5', '-vframes', '1',
|
| 46 |
+
'-vf', 'scale=280:-1', '-y', thumb_path]
|
| 47 |
+
try:
|
| 48 |
+
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=8)
|
| 49 |
+
if os.path.exists(thumb_path):
|
| 50 |
+
display_text = f"{i+1}. {os.path.basename(vid_path)}"
|
| 51 |
+
gallery_items.append((thumb_path, display_text))
|
| 52 |
+
else:
|
| 53 |
+
gallery_items.append((None, f"{i+1}. {os.path.basename(vid_path)}"))
|
| 54 |
+
except Exception as e:
|
| 55 |
+
print(f"Thumbnail failed for {vid_path}: {e}")
|
| 56 |
+
gallery_items.append((None, f"{i+1}. {os.path.basename(vid_path)}"))
|
| 57 |
+
return gallery_items
|
| 58 |
+
|
| 59 |
# --- Setup Functions ---
|
| 60 |
def run_command(command):
|
| 61 |
subprocess.run(command, shell=True, check=True)
|
|
|
|
| 64 |
"""Full recursive patch for ALL skvideo files (fixes np.int / np.float everywhere)"""
|
| 65 |
print("Applying FULL NumPy compatibility patch to entire skvideo package...")
|
| 66 |
try:
|
|
|
|
| 67 |
skvideo_dir = subprocess.check_output(
|
| 68 |
[sys.executable, "-c", "import skvideo; print(skvideo.__path__[0])"],
|
| 69 |
text=True
|
|
|
|
| 87 |
print(f"β οΈ skvideo patch failed: {e}")
|
| 88 |
|
| 89 |
def setup_environment():
|
| 90 |
+
"""Full setup with complete skvideo fix + thumbnail cleanup"""
|
| 91 |
print("--- Starting Environment Setup ---")
|
| 92 |
|
| 93 |
+
# 1. Clean old thumbnails (keeps disk clean)
|
| 94 |
+
thumb_dir = os.path.join(BASE_DIR, "temp_thumbs")
|
| 95 |
+
if os.path.exists(thumb_dir):
|
| 96 |
+
shutil.rmtree(thumb_dir, ignore_errors=True)
|
| 97 |
+
os.makedirs(thumb_dir, exist_ok=True)
|
| 98 |
+
|
| 99 |
+
# 2. Install dependencies
|
| 100 |
print("Installing scikit-video, imageio[ffmpeg], and RIFE requirements...")
|
| 101 |
subprocess.check_call([sys.executable, "-m", "pip", "install", "-q",
|
| 102 |
"scikit-video", "imageio[ffmpeg]", "tqdm", "opencv-python>=4.1.2",
|
| 103 |
"moviepy>=1.0.3", "torchvision"])
|
| 104 |
|
| 105 |
+
# 3. FULL skvideo patch
|
| 106 |
patch_skvideo_fully()
|
| 107 |
|
| 108 |
+
# 4. Clone RIFE
|
| 109 |
if not os.path.exists(RIFE_DIR):
|
| 110 |
print("Cloning Practical-RIFE...")
|
| 111 |
run_command(f"git clone https://github.com/hzwer/Practical-RIFE {RIFE_DIR}")
|
| 112 |
|
| 113 |
+
# 5. Download + fix model
|
| 114 |
hdv3_path = os.path.join(RIFE_DIR, "HDv3")
|
| 115 |
if not os.path.exists(hdv3_path) or not os.path.exists(os.path.join(RIFE_DIR, "train_log", "RIFE_HDv3.py")):
|
| 116 |
print("Downloading RIFE v4.26 model weights...")
|
|
|
|
| 138 |
if os.path.exists(zip_path):
|
| 139 |
os.remove(zip_path)
|
| 140 |
|
| 141 |
+
# 6. Patch RIFE for high-quality output
|
| 142 |
inference_script = os.path.join(RIFE_DIR, "inference_video.py")
|
| 143 |
if os.path.exists(inference_script):
|
| 144 |
with open(inference_script, 'r') as f:
|
|
|
|
| 152 |
f.write(new_content)
|
| 153 |
print("β
RIFE patched for libx264")
|
| 154 |
|
| 155 |
+
# 7. Verification
|
| 156 |
if os.path.exists(os.path.join(RIFE_DIR, "train_log", "RIFE_HDv3.py")) and os.path.exists(os.path.join(hdv3_path, "flownet.pkl")):
|
| 157 |
print("β
Model files verified")
|
| 158 |
else:
|
|
|
|
| 193 |
print(f"β Boomerang Failed: {e}")
|
| 194 |
return False
|
| 195 |
|
| 196 |
+
# --- Processing Function (unchanged) ---
|
| 197 |
@spaces.GPU(required=True)
|
| 198 |
def interpolate_video(input_video_path, multi_factor, create_boomerang=False):
|
| 199 |
if input_video_path is None:
|
|
|
|
| 204 |
final_output_path = os.path.join(BASE_DIR, "final_interpolated.mp4")
|
| 205 |
boomerang_path = os.path.join(BASE_DIR, "final_boomerang.mp4")
|
| 206 |
|
|
|
|
| 207 |
for p in [output_path, final_output_path, boomerang_path]:
|
| 208 |
if os.path.exists(p): os.remove(p)
|
| 209 |
expected_no_audio = output_path.replace(".mp4", "_noaudio.mp4")
|
| 210 |
if os.path.exists(expected_no_audio): os.remove(expected_no_audio)
|
| 211 |
|
|
|
|
| 212 |
import torch
|
| 213 |
print(f"π₯ CUDA available: {torch.cuda.is_available()}")
|
| 214 |
if torch.cuda.is_available():
|
|
|
|
| 256 |
finally:
|
| 257 |
os.chdir(BASE_DIR)
|
| 258 |
|
| 259 |
+
# --- Stitch function (unchanged - now receives ordered list of paths) ---
|
| 260 |
def stitch_videos(video_files, resolution_choice):
|
| 261 |
if not video_files: return None
|
| 262 |
try:
|
|
|
|
| 265 |
except:
|
| 266 |
target_w, target_h = "1920", "1080"
|
| 267 |
|
| 268 |
+
print(f"Stitching {len(video_files)} videos (in your chosen order) into {target_w}x{target_h}...")
|
| 269 |
stitch_list_path = os.path.join(BASE_DIR, "stitch_list.txt")
|
| 270 |
output_stitched = os.path.join(BASE_DIR, "final_stitched.mp4")
|
| 271 |
temp_dir = os.path.join(BASE_DIR, "temp_stitch")
|
|
|
|
| 288 |
shutil.rmtree(temp_dir)
|
| 289 |
return output_stitched
|
| 290 |
|
| 291 |
+
# --- Reordering helper functions (NEW) ---
|
| 292 |
+
def handle_upload(files):
|
| 293 |
+
"""Convert uploaded files to ordered paths + generate thumbnails"""
|
| 294 |
+
if not files:
|
| 295 |
+
return [], [], 0
|
| 296 |
+
# Gradio File objects have .name = temp path
|
| 297 |
+
paths = [f.name if hasattr(f, "name") else str(f) for f in files]
|
| 298 |
+
thumbs = create_thumbnails(paths)
|
| 299 |
+
return paths, thumbs, 0
|
| 300 |
+
|
| 301 |
+
def reorder_clips(paths, idx, direction):
|
| 302 |
+
"""Move clip up or down"""
|
| 303 |
+
if not paths:
|
| 304 |
+
return paths, [], 0
|
| 305 |
+
idx = int(idx)
|
| 306 |
+
if idx < 0 or idx >= len(paths):
|
| 307 |
+
return paths, create_thumbnails(paths), idx
|
| 308 |
+
|
| 309 |
+
new_paths = paths.copy()
|
| 310 |
+
if direction == "up" and idx > 0:
|
| 311 |
+
new_paths[idx], new_paths[idx-1] = new_paths[idx-1], new_paths[idx]
|
| 312 |
+
new_idx = idx - 1
|
| 313 |
+
elif direction == "down" and idx < len(new_paths)-1:
|
| 314 |
+
new_paths[idx], new_paths[idx+1] = new_paths[idx+1], new_paths[idx]
|
| 315 |
+
new_idx = idx + 1
|
| 316 |
+
else:
|
| 317 |
+
new_idx = idx
|
| 318 |
+
|
| 319 |
+
return new_paths, create_thumbnails(new_paths), new_idx
|
| 320 |
+
|
| 321 |
+
def remove_selected(paths, idx):
|
| 322 |
+
"""Remove selected clip"""
|
| 323 |
+
if not paths:
|
| 324 |
+
return [], [], 0
|
| 325 |
+
idx = int(idx)
|
| 326 |
+
if 0 <= idx < len(paths):
|
| 327 |
+
new_paths = paths.copy()
|
| 328 |
+
del new_paths[idx]
|
| 329 |
+
new_idx = max(0, min(idx, len(new_paths)-1))
|
| 330 |
+
return new_paths, create_thumbnails(new_paths), new_idx
|
| 331 |
+
return paths, create_thumbnails(paths), idx
|
| 332 |
+
|
| 333 |
+
def clear_all():
|
| 334 |
+
return [], [], 0
|
| 335 |
+
|
| 336 |
# --- Gradio UI ---
|
| 337 |
+
with gr.Blocks(title="RIFE + Boomerang + Enhanced Stitch") as demo:
|
| 338 |
+
gr.Markdown("# ποΈ RIFE: Interpolate & Boomerang + Smart Stitch")
|
| 339 |
+
gr.Markdown("**β
Fixed skvideo np.int error** β now works on latest NumPy! \n**New:** Stitch tab now shows **thumbnails** + **reorder with arrows**!")
|
| 340 |
|
| 341 |
with gr.Tabs():
|
| 342 |
with gr.TabItem("1. Smooth Video + Boomerang"):
|
|
|
|
| 352 |
interp_btn.click(interpolate_video, inputs=[video_input, multi_select, boomerang_check], outputs=video_output)
|
| 353 |
|
| 354 |
with gr.TabItem("2. Stitch Videos"):
|
| 355 |
+
gr.Markdown("### π Upload clips β see thumbnails β **reorder** β stitch")
|
| 356 |
+
|
| 357 |
+
stitch_inputs = gr.File(
|
| 358 |
+
label="Upload Video Clips (multiple)",
|
| 359 |
+
file_count="multiple",
|
| 360 |
+
file_types=["video"],
|
| 361 |
+
height=100
|
| 362 |
+
)
|
| 363 |
+
|
| 364 |
+
clip_paths_state = gr.State([]) # Ordered list of full video paths
|
| 365 |
+
|
| 366 |
+
gr.Markdown("**Current Order (thumbnails show position)**")
|
| 367 |
+
clip_gallery = gr.Gallery(
|
| 368 |
+
label="Thumbnails - Click one then use arrow buttons below",
|
| 369 |
+
columns=4,
|
| 370 |
+
height=320,
|
| 371 |
+
object_fit="contain"
|
| 372 |
+
)
|
| 373 |
+
|
| 374 |
with gr.Row():
|
| 375 |
+
selected_index = gr.Number(label="Selected Index (0 = first clip)", value=0, precision=0, minimum=0)
|
| 376 |
+
|
| 377 |
+
with gr.Row():
|
| 378 |
+
up_btn = gr.Button("β Move Up", size="small")
|
| 379 |
+
down_btn = gr.Button("β Move Down", size="small")
|
| 380 |
+
remove_btn = gr.Button("ποΈ Remove", variant="stop", size="small")
|
| 381 |
+
clear_btn = gr.Button("Clear All", variant="stop", size="small")
|
| 382 |
+
|
| 383 |
+
res_select = gr.Dropdown(
|
| 384 |
+
choices=["1920x1080", "1280x1280", "1024x1024"],
|
| 385 |
+
value="1920x1080",
|
| 386 |
+
label="Output Resolution"
|
| 387 |
+
)
|
| 388 |
+
stitch_btn = gr.Button("π¬ Stitch Videos in Shown Order", variant="primary")
|
| 389 |
+
stitch_output = gr.Video(label="Stitched Result")
|
| 390 |
+
|
| 391 |
+
# --- Connect all stitching events (NEW) ---
|
| 392 |
+
stitch_inputs.change(
|
| 393 |
+
fn=handle_upload,
|
| 394 |
+
inputs=stitch_inputs,
|
| 395 |
+
outputs=[clip_paths_state, clip_gallery, selected_index]
|
| 396 |
+
)
|
| 397 |
+
|
| 398 |
+
up_btn.click(
|
| 399 |
+
fn=lambda p, i: reorder_clips(p, i, "up"),
|
| 400 |
+
inputs=[clip_paths_state, selected_index],
|
| 401 |
+
outputs=[clip_paths_state, clip_gallery, selected_index]
|
| 402 |
+
)
|
| 403 |
+
|
| 404 |
+
down_btn.click(
|
| 405 |
+
fn=lambda p, i: reorder_clips(p, i, "down"),
|
| 406 |
+
inputs=[clip_paths_state, selected_index],
|
| 407 |
+
outputs=[clip_paths_state, clip_gallery, selected_index]
|
| 408 |
+
)
|
| 409 |
+
|
| 410 |
+
remove_btn.click(
|
| 411 |
+
fn=remove_selected,
|
| 412 |
+
inputs=[clip_paths_state, selected_index],
|
| 413 |
+
outputs=[clip_paths_state, clip_gallery, selected_index]
|
| 414 |
+
)
|
| 415 |
+
|
| 416 |
+
clear_btn.click(
|
| 417 |
+
fn=clear_all,
|
| 418 |
+
outputs=[clip_paths_state, clip_gallery, selected_index]
|
| 419 |
+
)
|
| 420 |
+
|
| 421 |
+
stitch_btn.click(
|
| 422 |
+
fn=stitch_videos,
|
| 423 |
+
inputs=[clip_paths_state, res_select],
|
| 424 |
+
outputs=stitch_output
|
| 425 |
+
)
|
| 426 |
|
| 427 |
+
demo.launch()
|
| 428 |
+
</code></pre>
|
| 429 |
+
</body>
|
| 430 |
+
</html>
|