MogensR commited on
Commit
c20eb35
Β·
1 Parent(s): 0ab958f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +43 -26
app.py CHANGED
@@ -2,8 +2,8 @@
2
  """
3
  Final Fixed Video Background Replacement
4
  Uses proper functions from utilities.py to avoid transparency issues
5
- NEW: Added GPU detection, model caching, batch processing support,
6
- and improved error handling
7
  """
8
  import sys
9
  import cv2
@@ -63,10 +63,27 @@
63
  two_stage_processor = None
64
  PROCESS_CANCELLED = False
65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  # ============================================================================ #
67
  # SAM2 LOADER WITH VALIDATION
68
  # ============================================================================ #
69
- def load_sam2_predictor_fixed(device: str = "cuda", progress_callback: Optional[callable] = None) -> Any:
70
  """Load SAM2 with proper error handling and validation"""
71
  def _prog(pct: float, desc: str):
72
  if progress_callback:
@@ -79,7 +96,6 @@ def _prog(pct: float, desc: str):
79
  time_info = parts[1].strip() if len(parts) > 1 else ""
80
  fps_info = parts[2].strip() if len(parts) > 2 else ""
81
  eta_info = parts[3].strip() if len(parts) > 3 else ""
82
-
83
  display_text = f"""πŸ“Š PROCESSING STATUS
84
  ━━━━━━━━━━━━━━━━━━━━━━━━━━
85
  🎬 {frame_info}
@@ -88,7 +104,6 @@ def _prog(pct: float, desc: str):
88
  🎯 {eta_info}
89
  ━━━━━━━━━━━━━━━━━━━━━━━━━━
90
  πŸ“ˆ Progress: {pct*100:.1f}%"""
91
-
92
  try:
93
  with open("/tmp/processing_info.txt", 'w') as f:
94
  f.write(display_text)
@@ -105,6 +120,7 @@ def _prog(pct: float, desc: str):
105
  cache_dir=str(CACHE_DIR / "sam2_checkpoint"),
106
  force_download=False
107
  )
 
108
  _prog(0.5, "SAM2 checkpoint downloaded, building model...")
109
 
110
  # Import and build
@@ -132,7 +148,7 @@ def _prog(pct: float, desc: str):
132
  raise Exception("SAM2 predictor test failed - no masks generated")
133
 
134
  _prog(1.0, "SAM2 loaded and validated successfully!")
135
- logger.info("SAM2 predictor loaded and tested successfully")
136
  return predictor
137
 
138
  except Exception as e:
@@ -171,7 +187,7 @@ def _prog(pct: float, desc: str):
171
  logger.warning(f"MatAnyone test failed: {test_e}, will use enhanced OpenCV")
172
 
173
  _prog(1.0, "MatAnyone loaded successfully!")
174
- logger.info("MatAnyone processor loaded successfully")
175
  return processor
176
 
177
  except Exception as e:
@@ -188,7 +204,8 @@ def get_model_status() -> Dict[str, str]:
188
  return {
189
  'sam2': 'Ready' if sam2_predictor is not None else 'Not loaded',
190
  'matanyone': 'Ready' if matanyone_model is not None else 'Not loaded',
191
- 'validated': models_loaded
 
192
  }
193
 
194
  def get_cache_status() -> Dict[str, Any]:
@@ -197,7 +214,8 @@ def get_cache_status() -> Dict[str, Any]:
197
  "sam2_loaded": sam2_predictor is not None,
198
  "matanyone_loaded": matanyone_model is not None,
199
  "models_validated": models_loaded,
200
- "two_stage_available": TWO_STAGE_AVAILABLE
 
201
  }
202
 
203
  def load_models_with_validation(progress_callback: Optional[callable] = None) -> str:
@@ -211,14 +229,13 @@ def load_models_with_validation(progress_callback: Optional[callable] = None) ->
211
  try:
212
  PROCESS_CANCELLED = False
213
  start_time = time.time()
214
- device = "cuda" if torch.cuda.is_available() else "cpu"
215
- logger.info(f"Starting model loading on {device}")
216
 
217
  if progress_callback:
218
- progress_callback(0.0, "Starting model loading...")
219
 
220
  # Load SAM2 with validation
221
- sam2_predictor = load_sam2_predictor_fixed(device=device, progress_callback=progress_callback)
222
 
223
  if PROCESS_CANCELLED:
224
  return "Model loading cancelled by user"
@@ -237,7 +254,7 @@ def load_models_with_validation(progress_callback: Optional[callable] = None) ->
237
  logger.info("Two-stage processor initialized")
238
 
239
  load_time = time.time() - start_time
240
- message = f"SUCCESS: SAM2 + MatAnyone loaded and validated in {load_time:.1f}s"
241
  if TWO_STAGE_AVAILABLE:
242
  message += " (Two-stage mode available)"
243
  logger.info(message)
@@ -302,7 +319,6 @@ def _prog(pct: float, desc: str):
302
  🎯 {eta_info}
303
  ━━━━━━━━━━━━━━━━━━━━━━━━━━
304
  πŸ“ˆ Progress: {pct*100:.1f}%"""
305
-
306
  try:
307
  with open("/tmp/processing_info.txt", 'w') as f:
308
  f.write(display_text)
@@ -310,7 +326,7 @@ def _prog(pct: float, desc: str):
310
  logger.warning(f"Error writing processing info: {e}")
311
 
312
  try:
313
- _prog(0.0, f"Starting {'TWO-STAGE' if use_two_stage else 'SINGLE-STAGE'} processing...")
314
 
315
  # Check if two-stage mode is requested
316
  if use_two_stage:
@@ -337,7 +353,6 @@ def _prog(pct: float, desc: str):
337
  if background is None:
338
  return None, "Could not read custom background image."
339
  background_name = "Custom Image"
340
-
341
  else:
342
  if background_choice in PROFESSIONAL_BACKGROUNDS:
343
  bg_config = PROFESSIONAL_BACKGROUNDS[background_choice]
@@ -393,13 +408,14 @@ def _prog(pct: float, desc: str):
393
  f"Background: {background_name}\n"
394
  f"Method: Green Screen Chroma Key\n"
395
  f"Preset: {chroma_preset}\n"
396
- f"Quality: Professional cinema-grade"
 
397
  )
398
 
399
  return final_output, success_message
400
 
401
  # Single-stage processing
402
- _prog(0.05, "Starting SINGLE-STAGE processing...")
403
 
404
  cap = cv2.VideoCapture(video_path)
405
  if not cap.isOpened():
@@ -414,7 +430,7 @@ def _prog(pct: float, desc: str):
414
  return None, "Video appears to be empty."
415
 
416
  # Log video info
417
- logger.info(f"Video info: {frame_width}x{frame_height}, {fps}fps, {total_frames} frames")
418
 
419
  # Prepare background
420
  background = None
@@ -428,7 +444,6 @@ def _prog(pct: float, desc: str):
428
  if background is None:
429
  return None, "Could not read custom background image."
430
  background_name = "Custom Image"
431
-
432
  else:
433
  if background_choice in PROFESSIONAL_BACKGROUNDS:
434
  bg_config = PROFESSIONAL_BACKGROUNDS[background_choice]
@@ -443,7 +458,7 @@ def _prog(pct: float, desc: str):
443
  timestamp = int(time.time())
444
  fourcc = cv2.VideoWriter_fourcc(*'avc1') # H.264 for better compatibility
445
 
446
- _prog(0.1, f"Processing {total_frames} frames with {'TWO-STAGE' if use_two_stage else 'SINGLE-STAGE'} processing...")
447
 
448
  # Create temporary output for preview if needed
449
  if preview_mask or preview_greenscreen:
@@ -488,7 +503,7 @@ def _prog(pct: float, desc: str):
488
  eta_seconds = remaining_frames / current_fps if current_fps > 0 else 0
489
  eta_display = f"{int(eta_seconds//60)}m {int(eta_seconds%60)}s" if eta_seconds > 60 else f"{int(eta_seconds)}s"
490
 
491
- progress_msg = f"Frame {frame_count + 1}/{total_frames} | {elapsed_time:.1f}s | {current_fps:.1f} fps | ETA: {eta_display}"
492
 
493
  # Log and display progress
494
  logger.info(progress_msg)
@@ -510,7 +525,7 @@ def _prog(pct: float, desc: str):
510
  if (frame_count % KEYFRAME_INTERVAL == 0) or (last_refined_mask is None):
511
  refined_mask = refine_mask_hq(frame, mask, matanyone_model)
512
  last_refined_mask = refined_mask.copy()
513
- logger.info(f"Keyframe refinement at frame {frame_count}")
514
  else:
515
  # Blend SAM2 mask with last refined mask for temporal smoothness
516
  alpha = 0.7
@@ -548,7 +563,7 @@ def _prog(pct: float, desc: str):
548
  elapsed = time.time() - start_time
549
  fps_actual = frame_count / elapsed
550
  eta = (total_frames - frame_count) / fps_actual if fps_actual > 0 else 0
551
- logger.info(f"Progress: {frame_count}/{total_frames}, FPS: {fps_actual:.1f}, ETA: {eta:.0f}s")
552
 
553
  cap.release()
554
  final_writer.release()
@@ -606,7 +621,8 @@ def _prog(pct: float, desc: str):
606
  f"Processing time: {total_time:.1f}s\n"
607
  f"Average FPS: {avg_fps:.1f}\n"
608
  f"Keyframe interval: {KEYFRAME_INTERVAL}\n"
609
- f"Mode: {'TWO-STAGE' if use_two_stage else 'SINGLE-STAGE'}"
 
610
  )
611
 
612
  return final_output, success_message
@@ -624,6 +640,7 @@ def main():
624
  print(f"Keyframe interval: {KEYFRAME_INTERVAL} frames")
625
  print(f"Frame skip: {FRAME_SKIP} (1=all frames, 2=every other)")
626
  print(f"Two-stage mode: {'AVAILABLE' if TWO_STAGE_AVAILABLE else 'NOT AVAILABLE'}")
 
627
  print("Loading UI components...")
628
 
629
  # Import UI components
 
2
  """
3
  Final Fixed Video Background Replacement
4
  Uses proper functions from utilities.py to avoid transparency issues
5
+ NEW: Added automatic device detection for Hugging Face Spaces compatibility,
6
+ improved error handling, and better resource management
7
  """
8
  import sys
9
  import cv2
 
63
  two_stage_processor = None
64
  PROCESS_CANCELLED = False
65
 
66
+ # ============================================================================ #
67
+ # DEVICE DETECTION FOR HUGGING FACE SPACES
68
+ # ============================================================================ #
69
+ def get_device():
70
+ """Automatically detect the best available device (CPU or GPU)"""
71
+ if torch.cuda.is_available():
72
+ # Get the current CUDA device name
73
+ device_name = torch.cuda.get_device_name(0)
74
+ logger.info(f"Using GPU: {device_name}")
75
+ return "cuda"
76
+ else:
77
+ logger.info("Using CPU (no GPU available)")
78
+ return "cpu"
79
+
80
+ # Set the device globally
81
+ DEVICE = get_device()
82
+
83
  # ============================================================================ #
84
  # SAM2 LOADER WITH VALIDATION
85
  # ============================================================================ #
86
+ def load_sam2_predictor_fixed(device: str = DEVICE, progress_callback: Optional[callable] = None) -> Any:
87
  """Load SAM2 with proper error handling and validation"""
88
  def _prog(pct: float, desc: str):
89
  if progress_callback:
 
96
  time_info = parts[1].strip() if len(parts) > 1 else ""
97
  fps_info = parts[2].strip() if len(parts) > 2 else ""
98
  eta_info = parts[3].strip() if len(parts) > 3 else ""
 
99
  display_text = f"""πŸ“Š PROCESSING STATUS
100
  ━━━━━━━━━━━━━━━━━━━━━━━━━━
101
  🎬 {frame_info}
 
104
  🎯 {eta_info}
105
  ━━━━━━━━━━━━━━━━━━━━━━━━━━
106
  πŸ“ˆ Progress: {pct*100:.1f}%"""
 
107
  try:
108
  with open("/tmp/processing_info.txt", 'w') as f:
109
  f.write(display_text)
 
120
  cache_dir=str(CACHE_DIR / "sam2_checkpoint"),
121
  force_download=False
122
  )
123
+
124
  _prog(0.5, "SAM2 checkpoint downloaded, building model...")
125
 
126
  # Import and build
 
148
  raise Exception("SAM2 predictor test failed - no masks generated")
149
 
150
  _prog(1.0, "SAM2 loaded and validated successfully!")
151
+ logger.info(f"SAM2 predictor loaded and tested successfully on {device}")
152
  return predictor
153
 
154
  except Exception as e:
 
187
  logger.warning(f"MatAnyone test failed: {test_e}, will use enhanced OpenCV")
188
 
189
  _prog(1.0, "MatAnyone loaded successfully!")
190
+ logger.info(f"MatAnyone processor loaded successfully on {DEVICE}")
191
  return processor
192
 
193
  except Exception as e:
 
204
  return {
205
  'sam2': 'Ready' if sam2_predictor is not None else 'Not loaded',
206
  'matanyone': 'Ready' if matanyone_model is not None else 'Not loaded',
207
+ 'validated': models_loaded,
208
+ 'device': DEVICE
209
  }
210
 
211
  def get_cache_status() -> Dict[str, Any]:
 
214
  "sam2_loaded": sam2_predictor is not None,
215
  "matanyone_loaded": matanyone_model is not None,
216
  "models_validated": models_loaded,
217
+ "two_stage_available": TWO_STAGE_AVAILABLE,
218
+ "device": DEVICE
219
  }
220
 
221
  def load_models_with_validation(progress_callback: Optional[callable] = None) -> str:
 
229
  try:
230
  PROCESS_CANCELLED = False
231
  start_time = time.time()
232
+ logger.info(f"Starting model loading on {DEVICE}")
 
233
 
234
  if progress_callback:
235
+ progress_callback(0.0, f"Starting model loading on {DEVICE}...")
236
 
237
  # Load SAM2 with validation
238
+ sam2_predictor = load_sam2_predictor_fixed(device=DEVICE, progress_callback=progress_callback)
239
 
240
  if PROCESS_CANCELLED:
241
  return "Model loading cancelled by user"
 
254
  logger.info("Two-stage processor initialized")
255
 
256
  load_time = time.time() - start_time
257
+ message = f"SUCCESS: SAM2 + MatAnyone loaded and validated in {load_time:.1f}s on {DEVICE}"
258
  if TWO_STAGE_AVAILABLE:
259
  message += " (Two-stage mode available)"
260
  logger.info(message)
 
319
  🎯 {eta_info}
320
  ━━━━━━━━━━━━━━━━━━━━━━━━━━
321
  πŸ“ˆ Progress: {pct*100:.1f}%"""
 
322
  try:
323
  with open("/tmp/processing_info.txt", 'w') as f:
324
  f.write(display_text)
 
326
  logger.warning(f"Error writing processing info: {e}")
327
 
328
  try:
329
+ _prog(0.0, f"Starting {'TWO-STAGE' if use_two_stage else 'SINGLE-STAGE'} processing on {DEVICE}...")
330
 
331
  # Check if two-stage mode is requested
332
  if use_two_stage:
 
353
  if background is None:
354
  return None, "Could not read custom background image."
355
  background_name = "Custom Image"
 
356
  else:
357
  if background_choice in PROFESSIONAL_BACKGROUNDS:
358
  bg_config = PROFESSIONAL_BACKGROUNDS[background_choice]
 
408
  f"Background: {background_name}\n"
409
  f"Method: Green Screen Chroma Key\n"
410
  f"Preset: {chroma_preset}\n"
411
+ f"Quality: Professional cinema-grade\n"
412
+ f"Device: {DEVICE}"
413
  )
414
 
415
  return final_output, success_message
416
 
417
  # Single-stage processing
418
+ _prog(0.05, f"Starting SINGLE-STAGE processing on {DEVICE}...")
419
 
420
  cap = cv2.VideoCapture(video_path)
421
  if not cap.isOpened():
 
430
  return None, "Video appears to be empty."
431
 
432
  # Log video info
433
+ logger.info(f"Video info: {frame_width}x{frame_height}, {fps}fps, {total_frames} frames, processing on {DEVICE}")
434
 
435
  # Prepare background
436
  background = None
 
444
  if background is None:
445
  return None, "Could not read custom background image."
446
  background_name = "Custom Image"
 
447
  else:
448
  if background_choice in PROFESSIONAL_BACKGROUNDS:
449
  bg_config = PROFESSIONAL_BACKGROUNDS[background_choice]
 
458
  timestamp = int(time.time())
459
  fourcc = cv2.VideoWriter_fourcc(*'avc1') # H.264 for better compatibility
460
 
461
+ _prog(0.1, f"Processing {total_frames} frames with {'TWO-STAGE' if use_two_stage else 'SINGLE-STAGE'} processing on {DEVICE}...")
462
 
463
  # Create temporary output for preview if needed
464
  if preview_mask or preview_greenscreen:
 
503
  eta_seconds = remaining_frames / current_fps if current_fps > 0 else 0
504
  eta_display = f"{int(eta_seconds//60)}m {int(eta_seconds%60)}s" if eta_seconds > 60 else f"{int(eta_seconds)}s"
505
 
506
+ progress_msg = f"Frame {frame_count + 1}/{total_frames} | {elapsed_time:.1f}s | {current_fps:.1f} fps | ETA: {eta_display} | Device: {DEVICE}"
507
 
508
  # Log and display progress
509
  logger.info(progress_msg)
 
525
  if (frame_count % KEYFRAME_INTERVAL == 0) or (last_refined_mask is None):
526
  refined_mask = refine_mask_hq(frame, mask, matanyone_model)
527
  last_refined_mask = refined_mask.copy()
528
+ logger.info(f"Keyframe refinement at frame {frame_count} on {DEVICE}")
529
  else:
530
  # Blend SAM2 mask with last refined mask for temporal smoothness
531
  alpha = 0.7
 
563
  elapsed = time.time() - start_time
564
  fps_actual = frame_count / elapsed
565
  eta = (total_frames - frame_count) / fps_actual if fps_actual > 0 else 0
566
+ logger.info(f"Progress: {frame_count}/{total_frames}, FPS: {fps_actual:.1f}, ETA: {eta:.0f}s, Device: {DEVICE}")
567
 
568
  cap.release()
569
  final_writer.release()
 
621
  f"Processing time: {total_time:.1f}s\n"
622
  f"Average FPS: {avg_fps:.1f}\n"
623
  f"Keyframe interval: {KEYFRAME_INTERVAL}\n"
624
+ f"Mode: {'TWO-STAGE' if use_two_stage else 'SINGLE-STAGE'}\n"
625
+ f"Device: {DEVICE}"
626
  )
627
 
628
  return final_output, success_message
 
640
  print(f"Keyframe interval: {KEYFRAME_INTERVAL} frames")
641
  print(f"Frame skip: {FRAME_SKIP} (1=all frames, 2=every other)")
642
  print(f"Two-stage mode: {'AVAILABLE' if TWO_STAGE_AVAILABLE else 'NOT AVAILABLE'}")
643
+ print(f"Device: {DEVICE}")
644
  print("Loading UI components...")
645
 
646
  # Import UI components