Update processing/video/video_processor.py
Browse files- processing/video/video_processor.py +105 -18
processing/video/video_processor.py
CHANGED
|
@@ -1,9 +1,101 @@
|
|
| 1 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
class CoreVideoProcessor:
|
| 4 |
"""
|
| 5 |
Minimal, safe implementation used by core/app.py.
|
| 6 |
-
|
|
|
|
| 7 |
"""
|
| 8 |
|
| 9 |
def __init__(self, config: Optional[ProcessorConfig] = None, models: Optional[Any] = None):
|
|
@@ -13,7 +105,6 @@ def __init__(self, config: Optional[ProcessorConfig] = None, models: Optional[An
|
|
| 13 |
if self.models is None:
|
| 14 |
self.log.warning("CoreVideoProcessor initialized without a models provider; will use fallbacks.")
|
| 15 |
self._ffmpeg = shutil.which("ffmpeg")
|
| 16 |
-
# (rest as before...)
|
| 17 |
|
| 18 |
# -------- Back-compat safe config flags (do not require attrs on user config)
|
| 19 |
self._use_windowed = _env_bool(
|
|
@@ -40,7 +131,6 @@ def prepare_background(self, background_choice: str, custom_background_path: Opt
|
|
| 40 |
If a valid custom background path is given, loads and resizes it. Otherwise, uses a preset.
|
| 41 |
Returns: np.ndarray RGB (H, W, 3) uint8
|
| 42 |
"""
|
| 43 |
-
import cv2
|
| 44 |
from utils.cv_processing import create_professional_background
|
| 45 |
|
| 46 |
if custom_background_path:
|
|
@@ -58,8 +148,6 @@ def prepare_background(self, background_choice: str, custom_background_path: Opt
|
|
| 58 |
# fallback to preset
|
| 59 |
return create_professional_background(background_choice, width, height)
|
| 60 |
|
| 61 |
-
# (rest of class unchanged...)
|
| 62 |
-
|
| 63 |
# ---------- mask post-processing (stability + crispness) ----------
|
| 64 |
def _iou(self, a: np.ndarray, b: np.ndarray, thr: float = 0.5) -> float:
|
| 65 |
a_bin = (a >= thr).astype(np.uint8)
|
|
@@ -189,7 +277,7 @@ def _prepare_background_from_config(
|
|
| 189 |
img_bgr = cv2.resize(img_bgr, (width, height), interpolation=cv2.INTER_LANCZOS4)
|
| 190 |
return cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
|
| 191 |
|
| 192 |
-
if bg_config and isinstance(bg_config.get("gradient"), dict):
|
| 193 |
try:
|
| 194 |
return _create_gradient_background_local(bg_config["gradient"], width, height)
|
| 195 |
except Exception as e:
|
|
@@ -295,13 +383,13 @@ def process_video(
|
|
| 295 |
|
| 296 |
self._prev_mask = None
|
| 297 |
|
| 298 |
-
ffmpeg_pipe: _FFmpegPipe | None = None
|
| 299 |
writer: cv2.VideoWriter | None = None
|
| 300 |
ffmpeg_failed_reason = None
|
| 301 |
|
| 302 |
-
if getattr(self.config, "use_nvenc", True) and shutil.which("ffmpeg"):
|
| 303 |
try:
|
| 304 |
-
ffmpeg_pipe = _FFmpegPipe(width, height, float(fps_out), output_path, self.config, log=self.log)
|
| 305 |
except Exception as e:
|
| 306 |
ffmpeg_failed_reason = str(e)
|
| 307 |
self.log.warning("FFmpeg NVENC pipeline unavailable. Falling back to OpenCV. Reason: %s", e)
|
|
@@ -347,11 +435,11 @@ def process_video(
|
|
| 347 |
|
| 348 |
if ffmpeg_pipe is not None:
|
| 349 |
try:
|
| 350 |
-
ffmpeg_pipe.write(out_bgr)
|
| 351 |
except Exception as e:
|
| 352 |
self.log.warning("Switching to OpenCV writer after FFmpeg error at frame %d: %s", frame_count, e)
|
| 353 |
try:
|
| 354 |
-
ffmpeg_pipe.close()
|
| 355 |
except Exception:
|
| 356 |
pass
|
| 357 |
ffmpeg_pipe = None
|
|
@@ -443,11 +531,11 @@ def process_video(
|
|
| 443 |
|
| 444 |
if ffmpeg_pipe is not None:
|
| 445 |
try:
|
| 446 |
-
ffmpeg_pipe.write(out_bgr)
|
| 447 |
except Exception as e:
|
| 448 |
self.log.warning("Switching to OpenCV writer after FFmpeg error at frame %d: %s", frame_count, e)
|
| 449 |
try:
|
| 450 |
-
ffmpeg_pipe.close()
|
| 451 |
except Exception:
|
| 452 |
pass
|
| 453 |
ffmpeg_pipe = None
|
|
@@ -481,10 +569,10 @@ def process_video(
|
|
| 481 |
|
| 482 |
if ffmpeg_pipe is not None:
|
| 483 |
try:
|
| 484 |
-
ffmpeg_pipe.write(np.ascontiguousarray(out_bgr_fb))
|
| 485 |
except Exception:
|
| 486 |
try:
|
| 487 |
-
ffmpeg_pipe.close()
|
| 488 |
except Exception:
|
| 489 |
pass
|
| 490 |
ffmpeg_pipe = None
|
|
@@ -520,7 +608,7 @@ def process_video(
|
|
| 520 |
writer.release()
|
| 521 |
if ffmpeg_pipe is not None:
|
| 522 |
try:
|
| 523 |
-
ffmpeg_pipe.close()
|
| 524 |
except Exception:
|
| 525 |
pass
|
| 526 |
|
|
@@ -535,4 +623,3 @@ def process_video(
|
|
| 535 |
"fps_out": float(fps_out),
|
| 536 |
"output_path": output_path,
|
| 537 |
}
|
| 538 |
-
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
"""
|
| 4 |
+
Core Video Processor for BackgroundFX Pro
|
| 5 |
+
|
| 6 |
+
- Minimal, safe implementation used by core/app.py
|
| 7 |
+
- Works with split/legacy loaders
|
| 8 |
+
- Keeps exact behavior you shared; only fixes typing imports + integrates
|
| 9 |
+
prepare_background() and related helpers.
|
| 10 |
+
|
| 11 |
+
NOTE:
|
| 12 |
+
- Requires utils.cv_processing helpers already present in your project:
|
| 13 |
+
segment_person_hq, refine_mask_hq, replace_background_hq,
|
| 14 |
+
create_professional_background, PROFESSIONAL_BACKGROUNDS,
|
| 15 |
+
_create_gradient_background_local, validate_video_file
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
from __future__ import annotations
|
| 19 |
+
|
| 20 |
+
import os
|
| 21 |
+
import time
|
| 22 |
+
import shutil
|
| 23 |
+
import logging
|
| 24 |
+
import threading
|
| 25 |
+
from typing import Optional, Any, Dict, Callable, Tuple, List
|
| 26 |
+
|
| 27 |
+
import numpy as np
|
| 28 |
+
import cv2
|
| 29 |
+
|
| 30 |
+
# ---------------------------------------------------------------------
|
| 31 |
+
# Project logger (non-fatal fallback to std logging)
|
| 32 |
+
# ---------------------------------------------------------------------
|
| 33 |
+
try:
|
| 34 |
+
from utils.logger import get_logger
|
| 35 |
+
_log = get_logger("processing.video.video_processor")
|
| 36 |
+
except Exception:
|
| 37 |
+
logging.basicConfig(level=logging.INFO)
|
| 38 |
+
_log = logging.getLogger("processing.video.video_processor")
|
| 39 |
+
|
| 40 |
+
# ---------------------------------------------------------------------
|
| 41 |
+
# Config type (import if available; otherwise annotations are postponed)
|
| 42 |
+
# ---------------------------------------------------------------------
|
| 43 |
+
try:
|
| 44 |
+
from config.processor_config import ProcessorConfig # your project config
|
| 45 |
+
except Exception: # keep runtime happy if only used for typing
|
| 46 |
+
ProcessorConfig = Any # type: ignore
|
| 47 |
+
|
| 48 |
+
# ---------------------------------------------------------------------
|
| 49 |
+
# Small env helpers (use project ones if you have them)
|
| 50 |
+
# ---------------------------------------------------------------------
|
| 51 |
+
try:
|
| 52 |
+
from utils.system.env_utils import env_bool as _env_bool # type: ignore
|
| 53 |
+
from utils.system.env_utils import env_int as _env_int # type: ignore
|
| 54 |
+
except Exception:
|
| 55 |
+
def _env_bool(name: str, default: bool = False) -> bool:
|
| 56 |
+
v = os.environ.get(name)
|
| 57 |
+
if v is None:
|
| 58 |
+
return bool(default)
|
| 59 |
+
return str(v).strip().lower() in ("1", "true", "yes", "y", "on")
|
| 60 |
+
|
| 61 |
+
def _env_int(name: str, default: int = 0) -> int:
|
| 62 |
+
try:
|
| 63 |
+
return int(os.environ.get(name, default))
|
| 64 |
+
except Exception:
|
| 65 |
+
return int(default)
|
| 66 |
+
|
| 67 |
+
# ---------------------------------------------------------------------
|
| 68 |
+
# CV helpers from your utils module
|
| 69 |
+
# ---------------------------------------------------------------------
|
| 70 |
+
from utils.cv_processing import (
|
| 71 |
+
segment_person_hq,
|
| 72 |
+
refine_mask_hq,
|
| 73 |
+
replace_background_hq,
|
| 74 |
+
create_professional_background,
|
| 75 |
+
PROFESSIONAL_BACKGROUNDS,
|
| 76 |
+
validate_video_file,
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
# Optional local gradient helper (present in some layouts)
|
| 80 |
+
try:
|
| 81 |
+
from utils.cv_processing import _create_gradient_background_local # type: ignore
|
| 82 |
+
except Exception:
|
| 83 |
+
_create_gradient_background_local = None # type: ignore
|
| 84 |
+
|
| 85 |
+
# ---------------------------------------------------------------------
|
| 86 |
+
# Optional FFmpeg pipe; code falls back to OpenCV if unavailable
|
| 87 |
+
# ---------------------------------------------------------------------
|
| 88 |
+
try:
|
| 89 |
+
from utils.video.ffmpeg_pipe import FFmpegPipe as _FFmpegPipe # type: ignore
|
| 90 |
+
except Exception:
|
| 91 |
+
_FFmpegPipe = None # type: ignore
|
| 92 |
+
|
| 93 |
|
| 94 |
class CoreVideoProcessor:
|
| 95 |
"""
|
| 96 |
Minimal, safe implementation used by core/app.py.
|
| 97 |
+
Orchestrates SAM2 → MatAnyone refinement → background compositing,
|
| 98 |
+
with robust fallbacks (OpenCV writer when FFmpeg/NVENC unavailable).
|
| 99 |
"""
|
| 100 |
|
| 101 |
def __init__(self, config: Optional[ProcessorConfig] = None, models: Optional[Any] = None):
|
|
|
|
| 105 |
if self.models is None:
|
| 106 |
self.log.warning("CoreVideoProcessor initialized without a models provider; will use fallbacks.")
|
| 107 |
self._ffmpeg = shutil.which("ffmpeg")
|
|
|
|
| 108 |
|
| 109 |
# -------- Back-compat safe config flags (do not require attrs on user config)
|
| 110 |
self._use_windowed = _env_bool(
|
|
|
|
| 131 |
If a valid custom background path is given, loads and resizes it. Otherwise, uses a preset.
|
| 132 |
Returns: np.ndarray RGB (H, W, 3) uint8
|
| 133 |
"""
|
|
|
|
| 134 |
from utils.cv_processing import create_professional_background
|
| 135 |
|
| 136 |
if custom_background_path:
|
|
|
|
| 148 |
# fallback to preset
|
| 149 |
return create_professional_background(background_choice, width, height)
|
| 150 |
|
|
|
|
|
|
|
| 151 |
# ---------- mask post-processing (stability + crispness) ----------
|
| 152 |
def _iou(self, a: np.ndarray, b: np.ndarray, thr: float = 0.5) -> float:
|
| 153 |
a_bin = (a >= thr).astype(np.uint8)
|
|
|
|
| 277 |
img_bgr = cv2.resize(img_bgr, (width, height), interpolation=cv2.INTER_LANCZOS4)
|
| 278 |
return cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
|
| 279 |
|
| 280 |
+
if bg_config and isinstance(bg_config.get("gradient"), dict) and _create_gradient_background_local:
|
| 281 |
try:
|
| 282 |
return _create_gradient_background_local(bg_config["gradient"], width, height)
|
| 283 |
except Exception as e:
|
|
|
|
| 383 |
|
| 384 |
self._prev_mask = None
|
| 385 |
|
| 386 |
+
ffmpeg_pipe: _FFmpegPipe | None = None # type: ignore
|
| 387 |
writer: cv2.VideoWriter | None = None
|
| 388 |
ffmpeg_failed_reason = None
|
| 389 |
|
| 390 |
+
if getattr(self.config, "use_nvenc", True) and shutil.which("ffmpeg") and _FFmpegPipe is not None:
|
| 391 |
try:
|
| 392 |
+
ffmpeg_pipe = _FFmpegPipe(width, height, float(fps_out), output_path, self.config, log=self.log) # type: ignore
|
| 393 |
except Exception as e:
|
| 394 |
ffmpeg_failed_reason = str(e)
|
| 395 |
self.log.warning("FFmpeg NVENC pipeline unavailable. Falling back to OpenCV. Reason: %s", e)
|
|
|
|
| 435 |
|
| 436 |
if ffmpeg_pipe is not None:
|
| 437 |
try:
|
| 438 |
+
ffmpeg_pipe.write(out_bgr) # type: ignore[attr-defined]
|
| 439 |
except Exception as e:
|
| 440 |
self.log.warning("Switching to OpenCV writer after FFmpeg error at frame %d: %s", frame_count, e)
|
| 441 |
try:
|
| 442 |
+
ffmpeg_pipe.close() # type: ignore[attr-defined]
|
| 443 |
except Exception:
|
| 444 |
pass
|
| 445 |
ffmpeg_pipe = None
|
|
|
|
| 531 |
|
| 532 |
if ffmpeg_pipe is not None:
|
| 533 |
try:
|
| 534 |
+
ffmpeg_pipe.write(out_bgr) # type: ignore[attr-defined]
|
| 535 |
except Exception as e:
|
| 536 |
self.log.warning("Switching to OpenCV writer after FFmpeg error at frame %d: %s", frame_count, e)
|
| 537 |
try:
|
| 538 |
+
ffmpeg_pipe.close() # type: ignore[attr-defined]
|
| 539 |
except Exception:
|
| 540 |
pass
|
| 541 |
ffmpeg_pipe = None
|
|
|
|
| 569 |
|
| 570 |
if ffmpeg_pipe is not None:
|
| 571 |
try:
|
| 572 |
+
ffmpeg_pipe.write(np.ascontiguousarray(out_bgr_fb)) # type: ignore[attr-defined]
|
| 573 |
except Exception:
|
| 574 |
try:
|
| 575 |
+
ffmpeg_pipe.close() # type: ignore[attr-defined]
|
| 576 |
except Exception:
|
| 577 |
pass
|
| 578 |
ffmpeg_pipe = None
|
|
|
|
| 608 |
writer.release()
|
| 609 |
if ffmpeg_pipe is not None:
|
| 610 |
try:
|
| 611 |
+
ffmpeg_pipe.close() # type: ignore[attr-defined]
|
| 612 |
except Exception:
|
| 613 |
pass
|
| 614 |
|
|
|
|
| 623 |
"fps_out": float(fps_out),
|
| 624 |
"output_path": output_path,
|
| 625 |
}
|
|
|