MogensR commited on
Commit
aa315a3
·
1 Parent(s): f0f27f4

Update processing/video/video_processor.py

Browse files
Files changed (1) hide show
  1. processing/video/video_processor.py +44 -15
processing/video/video_processor.py CHANGED
@@ -9,10 +9,12 @@
9
  from __future__ import annotations
10
 
11
  from dataclasses import dataclass
12
- from typing import Optional, Dict, Any, Tuple
13
 
14
  import cv2
15
  import numpy as np
 
 
16
 
17
  from utils.logger import get_logger
18
  from core.models import ModelManager
@@ -31,11 +33,11 @@ class ProcessorConfig:
31
  background_preset: str = "minimalist" # key in PROFESSIONAL_BACKGROUNDS
32
  write_fps: Optional[float] = None # None -> keep source fps
33
 
34
-
35
  class CoreVideoProcessor:
36
  """
37
  Minimal, safe implementation used by core/app.py.
38
  It relies on ModelManager (SAM2 + MatAnyone) and your cv_processing helpers.
 
39
  """
40
 
41
  def __init__(self, config: Optional[ProcessorConfig] = None, models: Optional[ModelManager] = None):
@@ -53,21 +55,14 @@ def process_frame(self, frame: np.ndarray, background: np.ndarray) -> Dict[str,
53
  predictor = None
54
  try:
55
  sam2_model = self.models.get_sam2()
56
- # Check if we have a working SAM2 predictor
57
- # SAM2ImagePredictor has set_image and predict methods
58
  if sam2_model is not None:
59
- # Check if it's wrapped (has .predictor attribute)
60
  if hasattr(sam2_model, 'predictor'):
61
  predictor = sam2_model.predictor
62
- # Or if it IS the predictor (has set_image method)
63
  elif hasattr(sam2_model, 'set_image'):
64
  predictor = sam2_model
65
- # Or if it's a dict with model and processor (from transformers)
66
  elif isinstance(sam2_model, dict) and 'model' in sam2_model:
67
- # For now, we can't use this format easily
68
  self.log.warning("SAM2 loaded as dict format, not directly usable")
69
  predictor = None
70
-
71
  if predictor is None:
72
  self.log.debug("SAM2 predictor not available, will use fallback")
73
  except Exception as e:
@@ -80,7 +75,6 @@ def process_frame(self, frame: np.ndarray, background: np.ndarray) -> Dict[str,
80
  matanyone = None
81
  try:
82
  matanyone_model = self.models.get_matanyone()
83
- # Just check if we have a MatAnyone model at all
84
  if matanyone_model is not None:
85
  matanyone = matanyone_model
86
  except Exception as e:
@@ -94,8 +88,20 @@ def process_frame(self, frame: np.ndarray, background: np.ndarray) -> Dict[str,
94
  return {"frame": out, "mask": mask_refined}
95
 
96
  # --- simple video API (covers typical usage in older core/app.py code) ---
97
- def process_video(self, input_path: str, output_path: str, bg_config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
98
- """Process a full video; returns basic stats."""
 
 
 
 
 
 
 
 
 
 
 
 
99
  ok, msg = validate_video_file(input_path)
100
  if not ok:
101
  raise ValueError(f"Invalid video: {msg}")
@@ -108,32 +114,55 @@ def process_video(self, input_path: str, output_path: str, bg_config: Optional[D
108
  width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
109
  height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
110
  fps = cap.get(cv2.CAP_PROP_FPS)
 
111
  fps_out = self.config.write_fps or (fps if fps and fps > 0 else 25.0)
112
 
113
  fourcc = cv2.VideoWriter_fourcc(*"mp4v")
114
  writer = cv2.VideoWriter(output_path, fourcc, fps_out, (width, height))
115
 
116
  # Build background (once)
117
- from utils.cv_processing import PROFESSIONAL_BACKGROUNDS # local import to avoid circulars
118
  preset = self.config.background_preset
119
  cfg = bg_config or PROFESSIONAL_BACKGROUNDS.get(preset, PROFESSIONAL_BACKGROUNDS["minimalist"])
120
  background = create_professional_background(cfg, width, height)
121
 
122
  frame_count = 0
 
123
  try:
124
  while True:
125
  ret, frame = cap.read()
126
  if not ret:
127
  break
 
 
 
 
 
 
128
  res = self.process_frame(frame, background)
129
  writer.write(res["frame"])
130
  frame_count += 1
 
 
 
 
 
 
 
 
 
 
131
  finally:
132
  cap.release()
133
  writer.release()
134
 
135
  self.log.info(f"Processed {frame_count} frames → {output_path}")
136
- return {"frames": frame_count, "width": width, "height": height, "fps_out": fps_out}
 
 
 
 
 
137
 
138
  # Backward-compat export name
139
- VideoProcessor = CoreVideoProcessor
 
9
  from __future__ import annotations
10
 
11
  from dataclasses import dataclass
12
+ from typing import Optional, Dict, Any, Tuple, Callable
13
 
14
  import cv2
15
  import numpy as np
16
+ import time
17
+ import threading
18
 
19
  from utils.logger import get_logger
20
  from core.models import ModelManager
 
33
  background_preset: str = "minimalist" # key in PROFESSIONAL_BACKGROUNDS
34
  write_fps: Optional[float] = None # None -> keep source fps
35
 
 
36
  class CoreVideoProcessor:
37
  """
38
  Minimal, safe implementation used by core/app.py.
39
  It relies on ModelManager (SAM2 + MatAnyone) and your cv_processing helpers.
40
+ Now supports live progress + cancel/stop.
41
  """
42
 
43
  def __init__(self, config: Optional[ProcessorConfig] = None, models: Optional[ModelManager] = None):
 
55
  predictor = None
56
  try:
57
  sam2_model = self.models.get_sam2()
 
 
58
  if sam2_model is not None:
 
59
  if hasattr(sam2_model, 'predictor'):
60
  predictor = sam2_model.predictor
 
61
  elif hasattr(sam2_model, 'set_image'):
62
  predictor = sam2_model
 
63
  elif isinstance(sam2_model, dict) and 'model' in sam2_model:
 
64
  self.log.warning("SAM2 loaded as dict format, not directly usable")
65
  predictor = None
 
66
  if predictor is None:
67
  self.log.debug("SAM2 predictor not available, will use fallback")
68
  except Exception as e:
 
75
  matanyone = None
76
  try:
77
  matanyone_model = self.models.get_matanyone()
 
78
  if matanyone_model is not None:
79
  matanyone = matanyone_model
80
  except Exception as e:
 
88
  return {"frame": out, "mask": mask_refined}
89
 
90
  # --- simple video API (covers typical usage in older core/app.py code) ---
91
+ def process_video(
92
+ self,
93
+ input_path: str,
94
+ output_path: str,
95
+ bg_config: Optional[Dict[str, Any]] = None,
96
+ progress_callback: Optional[Callable[[int, int, float], None]] = None, # <-- ADDED
97
+ stop_event: Optional[threading.Event] = None # <-- ADDED
98
+ ) -> Dict[str, Any]:
99
+ """
100
+ Process a full video with live progress and optional stop.
101
+ progress_callback: function(current_frame, total_frames, fps)
102
+ stop_event: threading.Event() - if set(), abort processing.
103
+ Returns: dict with stats.
104
+ """
105
  ok, msg = validate_video_file(input_path)
106
  if not ok:
107
  raise ValueError(f"Invalid video: {msg}")
 
114
  width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
115
  height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
116
  fps = cap.get(cv2.CAP_PROP_FPS)
117
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
118
  fps_out = self.config.write_fps or (fps if fps and fps > 0 else 25.0)
119
 
120
  fourcc = cv2.VideoWriter_fourcc(*"mp4v")
121
  writer = cv2.VideoWriter(output_path, fourcc, fps_out, (width, height))
122
 
123
  # Build background (once)
124
+ from utils.cv_processing import PROFESSIONAL_BACKGROUNDS
125
  preset = self.config.background_preset
126
  cfg = bg_config or PROFESSIONAL_BACKGROUNDS.get(preset, PROFESSIONAL_BACKGROUNDS["minimalist"])
127
  background = create_professional_background(cfg, width, height)
128
 
129
  frame_count = 0
130
+ start_time = time.time()
131
  try:
132
  while True:
133
  ret, frame = cap.read()
134
  if not ret:
135
  break
136
+
137
+ # --- CANCEL SUPPORT ---
138
+ if stop_event is not None and stop_event.is_set():
139
+ self.log.info("Processing stopped by user request") # <-- CHANGED
140
+ break
141
+
142
  res = self.process_frame(frame, background)
143
  writer.write(res["frame"])
144
  frame_count += 1
145
+
146
+ # --- LIVE PROGRESS ---
147
+ if progress_callback:
148
+ elapsed = time.time() - start_time
149
+ fps_live = frame_count / elapsed if elapsed > 0 else 0
150
+ progress_callback(
151
+ frame_count,
152
+ total_frames,
153
+ fps_live
154
+ )
155
  finally:
156
  cap.release()
157
  writer.release()
158
 
159
  self.log.info(f"Processed {frame_count} frames → {output_path}")
160
+ return {
161
+ "frames": frame_count,
162
+ "width": width,
163
+ "height": height,
164
+ "fps_out": fps_out
165
+ }
166
 
167
  # Backward-compat export name
168
+ VideoProcessor = CoreVideoProcessor