File size: 6,267 Bytes
8ad08ed aa315a3 8ad08ed aa315a3 8ad08ed 2bfe6e5 8ad08ed aa315a3 8ad08ed 2bfe6e5 8ad08ed 2bfe6e5 161521f 8ad08ed 161521f 8ad08ed 2bfe6e5 161521f 2bfe6e5 8ad08ed 161521f 8ad08ed aa315a3 8ad08ed aa315a3 8ad08ed aa315a3 8ad08ed aa315a3 8ad08ed aa315a3 8ad08ed aa315a3 8ad08ed aa315a3 8ad08ed 2bfe6e5 aa315a3 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
#!/usr/bin/env python3
"""
Compatibility shim: CoreVideoProcessor
Bridges the legacy import `from processing.video.video_processor import CoreVideoProcessor`
to the modern pipeline functions living in `utils.cv_processing` and models in `core.models`.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Optional, Dict, Any, Tuple, Callable
import cv2
import numpy as np
import time
import threading
from utils.logger import get_logger
from core.models import ModelManager
# ← these funcs are the ones you showed (in utils/cv_processing.py)
from utils.cv_processing import (
segment_person_hq,
refine_mask_hq,
replace_background_hq,
create_professional_background,
validate_video_file,
)
@dataclass
class ProcessorConfig:
background_preset: str = "minimalist" # key in PROFESSIONAL_BACKGROUNDS
write_fps: Optional[float] = None # None -> keep source fps
class CoreVideoProcessor:
"""
Minimal, safe implementation used by core/app.py.
It relies on ModelManager (SAM2 + MatAnyone) and your cv_processing helpers.
Now supports live progress + cancel/stop.
"""
def __init__(self, config: Optional[ProcessorConfig] = None, models: Optional[ModelManager] = None):
self.log = get_logger(f"{__name__}.CoreVideoProcessor")
self.config = config or ProcessorConfig()
self.models = models or ModelManager()
try:
self.models.load_all()
except Exception as e:
self.log.warning(f"Model load issue (will use fallbacks if needed): {e}")
# --- single-frame API (useful for images or per-frame video loop) ---
def process_frame(self, frame: np.ndarray, background: np.ndarray) -> Dict[str, Any]:
"""Return dict with composited frame + mask; always succeeds with fallbacks."""
predictor = None
try:
sam2_model = self.models.get_sam2()
if sam2_model is not None:
if hasattr(sam2_model, 'predictor'):
predictor = sam2_model.predictor
elif hasattr(sam2_model, 'set_image'):
predictor = sam2_model
elif isinstance(sam2_model, dict) and 'model' in sam2_model:
self.log.warning("SAM2 loaded as dict format, not directly usable")
predictor = None
if predictor is None:
self.log.debug("SAM2 predictor not available, will use fallback")
except Exception as e:
self.log.warning(f"SAM2 predictor unavailable: {e}")
# 1) segment
mask = segment_person_hq(frame, predictor, fallback_enabled=True)
# 2) refine
matanyone = None
try:
matanyone_model = self.models.get_matanyone()
if matanyone_model is not None:
matanyone = matanyone_model
except Exception as e:
self.log.warning(f"MatAnyone unavailable: {e}")
mask_refined = refine_mask_hq(frame, mask, matanyone, fallback_enabled=True)
# 3) composite
out = replace_background_hq(frame, mask_refined, background, fallback_enabled=True)
return {"frame": out, "mask": mask_refined}
# --- simple video API (covers typical usage in older core/app.py code) ---
def process_video(
self,
input_path: str,
output_path: str,
bg_config: Optional[Dict[str, Any]] = None,
progress_callback: Optional[Callable[[int, int, float], None]] = None, # <-- ADDED
stop_event: Optional[threading.Event] = None # <-- ADDED
) -> Dict[str, Any]:
"""
Process a full video with live progress and optional stop.
progress_callback: function(current_frame, total_frames, fps)
stop_event: threading.Event() - if set(), abort processing.
Returns: dict with stats.
"""
ok, msg = validate_video_file(input_path)
if not ok:
raise ValueError(f"Invalid video: {msg}")
self.log.info(f"Video validation: {msg}")
cap = cv2.VideoCapture(input_path)
if not cap.isOpened():
raise RuntimeError(f"Could not open video: {input_path}")
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv2.CAP_PROP_FPS)
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
fps_out = self.config.write_fps or (fps if fps and fps > 0 else 25.0)
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
writer = cv2.VideoWriter(output_path, fourcc, fps_out, (width, height))
# Build background (once)
from utils.cv_processing import PROFESSIONAL_BACKGROUNDS
preset = self.config.background_preset
cfg = bg_config or PROFESSIONAL_BACKGROUNDS.get(preset, PROFESSIONAL_BACKGROUNDS["minimalist"])
background = create_professional_background(cfg, width, height)
frame_count = 0
start_time = time.time()
try:
while True:
ret, frame = cap.read()
if not ret:
break
# --- CANCEL SUPPORT ---
if stop_event is not None and stop_event.is_set():
self.log.info("Processing stopped by user request") # <-- CHANGED
break
res = self.process_frame(frame, background)
writer.write(res["frame"])
frame_count += 1
# --- LIVE PROGRESS ---
if progress_callback:
elapsed = time.time() - start_time
fps_live = frame_count / elapsed if elapsed > 0 else 0
progress_callback(
frame_count,
total_frames,
fps_live
)
finally:
cap.release()
writer.release()
self.log.info(f"Processed {frame_count} frames → {output_path}")
return {
"frames": frame_count,
"width": width,
"height": height,
"fps_out": fps_out
}
# Backward-compat export name
VideoProcessor = CoreVideoProcessor
|