MogensR commited on
Commit
2eef9e8
Β·
1 Parent(s): 23796fb

Update core/app.py

Browse files
Files changed (1) hide show
  1. core/app.py +100 -39
core/app.py CHANGED
@@ -38,6 +38,10 @@
38
  if str(ROOT) not in sys.path:
39
  sys.path.insert(0, str(ROOT))
40
 
 
 
 
 
41
  # ── Gradio schema patch (HF quirk) ───────────────────────────────────────────
42
  try:
43
  import gradio_client.utils as gc_utils
@@ -59,28 +63,50 @@ def _patched_get_type(schema):
59
  from core.exceptions import ModelLoadingError, VideoProcessingError
60
  from utils.hardware.device_manager import DeviceManager
61
  from utils.system.memory_manager import MemoryManager
62
- from models.loaders.model_loader import ModelLoader
 
 
 
 
 
 
 
 
 
63
  from processing.video.video_processor import CoreVideoProcessor
64
  from processing.audio.audio_processor import AudioProcessor
65
  from utils.monitoring.progress_tracker import ProgressTracker
66
  from utils.cv_processing import validate_video_file
67
 
68
- # ── Optional Two-Stage import (exact module path) ────────────────────────────
69
  TWO_STAGE_AVAILABLE = False
70
  TWO_STAGE_IMPORT_ORIGIN = ""
71
  TWO_STAGE_IMPORT_ERROR = ""
72
  CHROMA_PRESETS: Dict[str, Dict[str, Any]] = {"standard": {}}
73
  TwoStageProcessor = None # type: ignore
74
 
75
- try:
76
- from processing.two_stage.two_stage_processor import TwoStageProcessor, CHROMA_PRESETS # type: ignore
77
- TWO_STAGE_AVAILABLE = True
78
- TWO_STAGE_IMPORT_ORIGIN = "processing.two_stage.two_stage_processor"
79
- logger.info("Two-stage import OK (%s)", TWO_STAGE_IMPORT_ORIGIN)
80
- except Exception as e:
81
- TWO_STAGE_AVAILABLE = False
82
- TWO_STAGE_IMPORT_ERROR = f"{repr(e)}\n{traceback.format_exc()}"
83
- logger.warning("Two-stage import FAILED: %s", TWO_STAGE_IMPORT_ERROR.strip().splitlines()[-1])
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
  # ╔══════════════════════════════════════════════════════════════════════════╗
86
  # β•‘ VideoProcessor class β•‘
@@ -99,13 +125,13 @@ def __init__(self):
99
  self.model_loader = ModelLoader(self.device_manager, self.memory_manager)
100
 
101
  self.audio_processor = AudioProcessor()
102
- self.core_processor: CoreVideoProcessor | None = None
103
- self.two_stage_processor: Any | None = None
104
 
105
  self.models_loaded = False
106
  self.loading_lock = threading.Lock()
107
  self.cancel_event = threading.Event()
108
- self.progress_tracker: ProgressTracker | None = None
109
 
110
  logger.info(f"VideoProcessor on device: {self.device_manager.get_optimal_device()}")
111
 
@@ -124,6 +150,9 @@ def _patch_config_defaults(cfg: Any) -> None:
124
  "max_model_size_bytes": 0,
125
  # housekeeping
126
  "output_dir": str((Path(__file__).resolve().parent.parent) / "outputs"),
 
 
 
127
  }
128
  for k, v in defaults.items():
129
  if not hasattr(cfg, k):
@@ -161,28 +190,39 @@ def load_models(self, progress_callback: Optional[Callable] = None) -> str:
161
  if self.cancel_event.is_set():
162
  return "Model loading cancelled"
163
 
164
- # unwrap
165
- sam2_predictor = getattr(sam2_loaded, "model", None) if sam2_loaded else None
166
- mat_model = getattr(mat_loaded, "model", None) if mat_loaded else None
167
 
168
- # single-stage
169
  self.core_processor = CoreVideoProcessor(config=self.config, models=self.model_loader)
170
 
171
- # two-stage (optional)
172
  self.two_stage_processor = None
173
- if TWO_STAGE_AVAILABLE and (TwoStageProcessor is not None) and (sam2_predictor or mat_model):
174
  try:
175
  self.two_stage_processor = TwoStageProcessor(
176
  sam2_predictor=sam2_predictor, matanyone_model=mat_model
177
  )
178
  logger.info("Two-stage processor initialised")
179
  except Exception as e:
180
- logger.warning("Two-stage init failed: %r", e)
181
  self.two_stage_processor = None
182
 
183
  self.models_loaded = True
184
  msg = self.model_loader.get_load_summary()
185
- msg += ("\nβœ… Two-stage processor ready" if self.two_stage_processor else "\n⚠️ Two-stage processor not available")
 
 
 
 
 
 
 
 
 
 
 
186
  logger.info(msg)
187
  return msg
188
 
@@ -194,8 +234,7 @@ def load_models(self, progress_callback: Optional[Callable] = None) -> str:
194
  except Exception as e:
195
  self.models_loaded = False
196
  err = f"Unexpected error during model loading: {e}"
197
- logger.error(err)
198
- logger.debug("Traceback:\n%s", traceback.format_exc())
199
  return err
200
 
201
  # ── Public entry – process video ─────────────────────────────────────────
@@ -212,7 +251,7 @@ def process_video(
212
  preview_greenscreen: bool = False,
213
  ) -> Tuple[Optional[str], str]:
214
  if not self.models_loaded or not self.core_processor:
215
- return None, "Models not loaded. Please click β€œLoad Models” first."
216
  if self.cancel_event.is_set():
217
  return None, "Processing cancelled"
218
 
@@ -223,9 +262,14 @@ def process_video(
223
  return None, f"Invalid video: {why}"
224
 
225
  try:
 
 
 
 
 
226
  if use_two_stage:
227
  if not TWO_STAGE_AVAILABLE or self.two_stage_processor is None:
228
- return None, "Two-stage processing not available on this build"
229
  return self._process_two_stage(
230
  video_path,
231
  background_choice,
@@ -248,8 +292,7 @@ def process_video(
248
  logger.error(f"Processing failed: {e}")
249
  return None, f"Processing failed: {e}"
250
  except Exception as e:
251
- logger.error(f"Unexpected processing error: {e}")
252
- logger.debug("Traceback:\n%s", traceback.format_exc())
253
  return None, f"Unexpected error: {e}"
254
 
255
  # ── Private – single-stage ───────────────────────────────────────────────
@@ -268,6 +311,7 @@ def _process_single_stage(
268
  out_dir.mkdir(parents=True, exist_ok=True)
269
  out_path = str(out_dir / f"processed_{ts}.mp4")
270
 
 
271
  result = self.core_processor.process_video(
272
  input_path=video_path,
273
  output_path=out_path,
@@ -275,7 +319,9 @@ def _process_single_stage(
275
  "background_choice": background_choice,
276
  "custom_path": custom_background_path,
277
  },
 
278
  )
 
279
  if not result:
280
  return None, "Video processing failed"
281
 
@@ -286,16 +332,19 @@ def _process_single_stage(
286
  original_video=video_path, processed_video=out_path
287
  )
288
  except Exception as e:
289
- logger.warning("Audio mux failed, returning video without audio: %r", e)
290
  final_path = out_path
291
  else:
292
  final_path = out_path
293
 
 
 
294
  msg = (
295
  "Processing completed.\n"
296
  f"Frames: {result.get('frames', 'unknown')}\n"
297
  f"Background: {background_choice}\n"
298
  f"Mode: Single-stage\n"
 
299
  f"Device: {self.device_manager.get_optimal_device()}"
300
  )
301
  return final_path, msg
@@ -321,13 +370,13 @@ def _process_two_stage(
321
  h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) or 720
322
  cap.release()
323
 
324
- # Prepare background via core processor
325
  try:
326
  background = self.core_processor.prepare_background(
327
  background_choice, custom_background_path, w, h
328
  )
329
  except Exception as e:
330
- logger.error("Background preparation failed: %r", e)
331
  return None, f"Failed to prepare background: {e}"
332
  if background is None:
333
  return None, "Failed to prepare background"
@@ -338,7 +387,7 @@ def _process_two_stage(
338
  final_out = str(out_dir / f"final_{ts}.mp4")
339
 
340
  chroma_cfg = CHROMA_PRESETS.get(chroma_preset, CHROMA_PRESETS.get("standard", {}))
341
- logger.info("Two-stage with preset: %s | key_color_mode=%s", chroma_preset, key_color_mode)
342
 
343
  result, message = self.two_stage_processor.process_full_pipeline(
344
  video_path,
@@ -351,19 +400,21 @@ def _process_two_stage(
351
  if result is None:
352
  return None, message
353
 
354
- # Mux audio from original (same logic as single-stage)
355
  try:
356
  final_path = self.audio_processor.add_audio_to_video(
357
  original_video=video_path, processed_video=result
358
  )
359
  except Exception as e:
360
- logger.warning("Audio mux failed for two-stage; returning video without audio: %r", e)
361
  final_path = result
362
 
 
363
  msg = (
364
  "Two-stage processing completed.\n"
365
  f"Background: {background_choice}\n"
366
  f"Chroma Preset: {chroma_preset}\n"
 
367
  f"Device: {self.device_manager.get_optimal_device()}"
368
  )
369
  return final_path, msg
@@ -372,23 +423,25 @@ def _process_two_stage(
372
  def get_status(self) -> Dict[str, Any]:
373
  status = {
374
  "models_loaded": self.models_loaded,
375
- "two_stage_available": bool(TWO_STAGE_AVAILABLE and (self.two_stage_processor is not None)),
376
  "two_stage_origin": TWO_STAGE_IMPORT_ORIGIN or "",
377
- "two_stage_error": TWO_STAGE_IMPORT_ERROR[:2000] if TWO_STAGE_IMPORT_ERROR else "",
378
  "device": str(self.device_manager.get_optimal_device()),
379
  "core_processor_loaded": self.core_processor is not None,
380
  "config": self._safe_config_dict(),
381
  "memory_usage": self._safe_memory_usage(),
382
  }
 
383
  try:
384
  status["sam2_loaded"] = self.model_loader.get_sam2() is not None
385
  status["matanyone_loaded"] = self.model_loader.get_matanyone() is not None
 
386
  except Exception:
387
  status["sam2_loaded"] = False
388
  status["matanyone_loaded"] = False
389
 
390
  if self.progress_tracker:
391
  status["progress"] = self.progress_tracker.get_all_progress()
 
392
  return status
393
 
394
  def _safe_config_dict(self) -> Dict[str, Any]:
@@ -396,7 +449,8 @@ def _safe_config_dict(self) -> Dict[str, Any]:
396
  return self.config.to_dict()
397
  except Exception:
398
  keys = ["use_nvenc", "prefer_mp4", "video_codec", "audio_copy",
399
- "ffmpeg_path", "max_model_size", "max_model_size_bytes", "output_dir"]
 
400
  return {k: getattr(self.config, k, None) for k in keys}
401
 
402
  def _safe_memory_usage(self) -> Dict[str, Any]:
@@ -464,7 +518,14 @@ def main():
464
  try:
465
  logger.info("Starting BackgroundFX Pro")
466
  logger.info(f"Device: {processor.device_manager.get_optimal_device()}")
467
- logger.info("Two-stage available (import): %s", TWO_STAGE_AVAILABLE)
 
 
 
 
 
 
 
468
 
469
  from ui.ui_components import create_interface
470
  demo = create_interface()
@@ -479,4 +540,4 @@ def main():
479
 
480
 
481
  if __name__ == "__main__":
482
- main()
 
38
  if str(ROOT) not in sys.path:
39
  sys.path.insert(0, str(ROOT))
40
 
41
+ # Create loader directories if they don't exist
42
+ loaders_dir = ROOT / "models" / "loaders"
43
+ loaders_dir.mkdir(parents=True, exist_ok=True)
44
+
45
  # ── Gradio schema patch (HF quirk) ───────────────────────────────────────────
46
  try:
47
  import gradio_client.utils as gc_utils
 
63
  from core.exceptions import ModelLoadingError, VideoProcessingError
64
  from utils.hardware.device_manager import DeviceManager
65
  from utils.system.memory_manager import MemoryManager
66
+
67
+ # Try to import the new split loaders first, fall back to old if needed
68
+ try:
69
+ from models.loaders.model_loader import ModelLoader
70
+ logger.info("Using split loader architecture")
71
+ except ImportError:
72
+ logger.warning("Split loaders not found, using legacy loader")
73
+ # Fall back to old loader if split architecture isn't available yet
74
+ from models.model_loader import ModelLoader # type: ignore
75
+
76
  from processing.video.video_processor import CoreVideoProcessor
77
  from processing.audio.audio_processor import AudioProcessor
78
  from utils.monitoring.progress_tracker import ProgressTracker
79
  from utils.cv_processing import validate_video_file
80
 
81
+ # ── Optional Two-Stage import ────────────────────────────────────────────────
82
  TWO_STAGE_AVAILABLE = False
83
  TWO_STAGE_IMPORT_ORIGIN = ""
84
  TWO_STAGE_IMPORT_ERROR = ""
85
  CHROMA_PRESETS: Dict[str, Dict[str, Any]] = {"standard": {}}
86
  TwoStageProcessor = None # type: ignore
87
 
88
+ # Try multiple import paths for two-stage processor
89
+ two_stage_paths = [
90
+ "processors.two_stage", # Your fixed version
91
+ "processing.two_stage.two_stage_processor",
92
+ "processing.two_stage",
93
+ ]
94
+
95
+ for import_path in two_stage_paths:
96
+ try:
97
+ if "processors" in import_path:
98
+ from processors.two_stage import TwoStageProcessor, CHROMA_PRESETS # type: ignore
99
+ else:
100
+ from processing.two_stage.two_stage_processor import TwoStageProcessor, CHROMA_PRESETS # type: ignore
101
+ TWO_STAGE_AVAILABLE = True
102
+ TWO_STAGE_IMPORT_ORIGIN = import_path
103
+ logger.info(f"Two-stage import OK ({import_path})")
104
+ break
105
+ except Exception as e:
106
+ continue
107
+
108
+ if not TWO_STAGE_AVAILABLE:
109
+ logger.warning("Two-stage import FAILED from all paths")
110
 
111
  # ╔══════════════════════════════════════════════════════════════════════════╗
112
  # β•‘ VideoProcessor class β•‘
 
125
  self.model_loader = ModelLoader(self.device_manager, self.memory_manager)
126
 
127
  self.audio_processor = AudioProcessor()
128
+ self.core_processor: Optional[CoreVideoProcessor] = None
129
+ self.two_stage_processor: Optional[Any] = None
130
 
131
  self.models_loaded = False
132
  self.loading_lock = threading.Lock()
133
  self.cancel_event = threading.Event()
134
+ self.progress_tracker: Optional[ProgressTracker] = None
135
 
136
  logger.info(f"VideoProcessor on device: {self.device_manager.get_optimal_device()}")
137
 
 
150
  "max_model_size_bytes": 0,
151
  # housekeeping
152
  "output_dir": str((Path(__file__).resolve().parent.parent) / "outputs"),
153
+ # MatAnyone settings to ensure it's enabled
154
+ "matanyone_enabled": True,
155
+ "use_matanyone": True,
156
  }
157
  for k, v in defaults.items():
158
  if not hasattr(cfg, k):
 
190
  if self.cancel_event.is_set():
191
  return "Model loading cancelled"
192
 
193
+ # Get the actual models
194
+ sam2_predictor = sam2_loaded.model if sam2_loaded else None
195
+ mat_model = mat_loaded.model if mat_loaded else None
196
 
197
+ # Initialize core processor
198
  self.core_processor = CoreVideoProcessor(config=self.config, models=self.model_loader)
199
 
200
+ # Initialize two-stage processor if available
201
  self.two_stage_processor = None
202
+ if TWO_STAGE_AVAILABLE and TwoStageProcessor and (sam2_predictor or mat_model):
203
  try:
204
  self.two_stage_processor = TwoStageProcessor(
205
  sam2_predictor=sam2_predictor, matanyone_model=mat_model
206
  )
207
  logger.info("Two-stage processor initialised")
208
  except Exception as e:
209
+ logger.warning(f"Two-stage init failed: {e}")
210
  self.two_stage_processor = None
211
 
212
  self.models_loaded = True
213
  msg = self.model_loader.get_load_summary()
214
+
215
+ # Add status about processors
216
+ if self.two_stage_processor:
217
+ msg += "\nβœ… Two-stage processor ready"
218
+ else:
219
+ msg += "\n⚠️ Two-stage processor not available"
220
+
221
+ if mat_model:
222
+ msg += "\nβœ… MatAnyone refinement active"
223
+ else:
224
+ msg += "\n⚠️ MatAnyone not loaded (edges may be rough)"
225
+
226
  logger.info(msg)
227
  return msg
228
 
 
234
  except Exception as e:
235
  self.models_loaded = False
236
  err = f"Unexpected error during model loading: {e}"
237
+ logger.error(f"{err}\n{traceback.format_exc()}")
 
238
  return err
239
 
240
  # ── Public entry – process video ─────────────────────────────────────────
 
251
  preview_greenscreen: bool = False,
252
  ) -> Tuple[Optional[str], str]:
253
  if not self.models_loaded or not self.core_processor:
254
+ return None, "Models not loaded. Please click 'Load Models' first."
255
  if self.cancel_event.is_set():
256
  return None, "Processing cancelled"
257
 
 
262
  return None, f"Invalid video: {why}"
263
 
264
  try:
265
+ # Log which mode we're using
266
+ mode = "two-stage" if use_two_stage else "single-stage"
267
+ matanyone_status = "enabled" if self.model_loader.get_matanyone() else "disabled"
268
+ logger.info(f"Processing video in {mode} mode, MatAnyone: {matanyone_status}")
269
+
270
  if use_two_stage:
271
  if not TWO_STAGE_AVAILABLE or self.two_stage_processor is None:
272
+ return None, "Two-stage processing not available"
273
  return self._process_two_stage(
274
  video_path,
275
  background_choice,
 
292
  logger.error(f"Processing failed: {e}")
293
  return None, f"Processing failed: {e}"
294
  except Exception as e:
295
+ logger.error(f"Unexpected processing error: {e}\n{traceback.format_exc()}")
 
296
  return None, f"Unexpected error: {e}"
297
 
298
  # ── Private – single-stage ───────────────────────────────────────────────
 
311
  out_dir.mkdir(parents=True, exist_ok=True)
312
  out_path = str(out_dir / f"processed_{ts}.mp4")
313
 
314
+ # Process video
315
  result = self.core_processor.process_video(
316
  input_path=video_path,
317
  output_path=out_path,
 
319
  "background_choice": background_choice,
320
  "custom_path": custom_background_path,
321
  },
322
+ progress_callback=progress_callback,
323
  )
324
+
325
  if not result:
326
  return None, "Video processing failed"
327
 
 
332
  original_video=video_path, processed_video=out_path
333
  )
334
  except Exception as e:
335
+ logger.warning(f"Audio mux failed, returning video without audio: {e}")
336
  final_path = out_path
337
  else:
338
  final_path = out_path
339
 
340
+ # Build status message
341
+ matanyone_status = "βœ“" if self.model_loader.get_matanyone() else "βœ—"
342
  msg = (
343
  "Processing completed.\n"
344
  f"Frames: {result.get('frames', 'unknown')}\n"
345
  f"Background: {background_choice}\n"
346
  f"Mode: Single-stage\n"
347
+ f"MatAnyone: {matanyone_status}\n"
348
  f"Device: {self.device_manager.get_optimal_device()}"
349
  )
350
  return final_path, msg
 
370
  h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) or 720
371
  cap.release()
372
 
373
+ # Prepare background
374
  try:
375
  background = self.core_processor.prepare_background(
376
  background_choice, custom_background_path, w, h
377
  )
378
  except Exception as e:
379
+ logger.error(f"Background preparation failed: {e}")
380
  return None, f"Failed to prepare background: {e}"
381
  if background is None:
382
  return None, "Failed to prepare background"
 
387
  final_out = str(out_dir / f"final_{ts}.mp4")
388
 
389
  chroma_cfg = CHROMA_PRESETS.get(chroma_preset, CHROMA_PRESETS.get("standard", {}))
390
+ logger.info(f"Two-stage with preset: {chroma_preset} | key_color: {key_color_mode}")
391
 
392
  result, message = self.two_stage_processor.process_full_pipeline(
393
  video_path,
 
400
  if result is None:
401
  return None, message
402
 
403
+ # Mux audio
404
  try:
405
  final_path = self.audio_processor.add_audio_to_video(
406
  original_video=video_path, processed_video=result
407
  )
408
  except Exception as e:
409
+ logger.warning(f"Audio mux failed: {e}")
410
  final_path = result
411
 
412
+ matanyone_status = "βœ“" if self.model_loader.get_matanyone() else "βœ—"
413
  msg = (
414
  "Two-stage processing completed.\n"
415
  f"Background: {background_choice}\n"
416
  f"Chroma Preset: {chroma_preset}\n"
417
+ f"MatAnyone: {matanyone_status}\n"
418
  f"Device: {self.device_manager.get_optimal_device()}"
419
  )
420
  return final_path, msg
 
423
  def get_status(self) -> Dict[str, Any]:
424
  status = {
425
  "models_loaded": self.models_loaded,
426
+ "two_stage_available": bool(TWO_STAGE_AVAILABLE and self.two_stage_processor),
427
  "two_stage_origin": TWO_STAGE_IMPORT_ORIGIN or "",
 
428
  "device": str(self.device_manager.get_optimal_device()),
429
  "core_processor_loaded": self.core_processor is not None,
430
  "config": self._safe_config_dict(),
431
  "memory_usage": self._safe_memory_usage(),
432
  }
433
+
434
  try:
435
  status["sam2_loaded"] = self.model_loader.get_sam2() is not None
436
  status["matanyone_loaded"] = self.model_loader.get_matanyone() is not None
437
+ status["model_info"] = self.model_loader.get_model_info()
438
  except Exception:
439
  status["sam2_loaded"] = False
440
  status["matanyone_loaded"] = False
441
 
442
  if self.progress_tracker:
443
  status["progress"] = self.progress_tracker.get_all_progress()
444
+
445
  return status
446
 
447
  def _safe_config_dict(self) -> Dict[str, Any]:
 
449
  return self.config.to_dict()
450
  except Exception:
451
  keys = ["use_nvenc", "prefer_mp4", "video_codec", "audio_copy",
452
+ "ffmpeg_path", "max_model_size", "max_model_size_bytes",
453
+ "output_dir", "matanyone_enabled"]
454
  return {k: getattr(self.config, k, None) for k in keys}
455
 
456
  def _safe_memory_usage(self) -> Dict[str, Any]:
 
518
  try:
519
  logger.info("Starting BackgroundFX Pro")
520
  logger.info(f"Device: {processor.device_manager.get_optimal_device()}")
521
+ logger.info(f"Two-stage available: {TWO_STAGE_AVAILABLE}")
522
+
523
+ # Log model loader type
524
+ try:
525
+ from models.loaders.model_loader import ModelLoader
526
+ logger.info("Using split loader architecture")
527
+ except:
528
+ logger.info("Using legacy loader")
529
 
530
  from ui.ui_components import create_interface
531
  demo = create_interface()
 
540
 
541
 
542
  if __name__ == "__main__":
543
+ main()