1inkusFace commited on
Commit
485beec
Β·
verified Β·
1 Parent(s): e532647

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +174 -25
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. Install dependencies
 
 
 
 
 
 
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
- # 2. FULL skvideo patch (this is the key fix for your error)
65
  patch_skvideo_fully()
66
 
67
- # 3. Clone RIFE
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
- # 4. Download + fix model (unchanged)
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
- # 5. Patch RIFE for high-quality output
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
- # 6. Verification
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 (with full error logging) ---
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 Loop")
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 multiple videos β†’ normalized to 60fps")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  with gr.Row():
273
- with gr.Column():
274
- stitch_inputs = gr.File(label="Upload Clips", file_count="multiple")
275
- res_select = gr.Dropdown(choices=["1920x1080", "1280x1280", "1024x1024"], value="1920x1080", label="Resolution")
276
- stitch_btn = gr.Button("Stitch Videos", variant="primary")
277
- with gr.Column():
278
- stitch_output = gr.Video(label="Stitched Result")
279
- stitch_btn.click(stitch_videos, inputs=[stitch_inputs, res_select], outputs=stitch_output)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>