Update app.py
Browse files
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
|
| 6 |
-
|
| 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 =
|
| 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 |
-
|
| 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=
|
| 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
|