Update app.py
Browse files
app.py
CHANGED
|
@@ -8,7 +8,6 @@
|
|
| 8 |
import early_env # <<< must be FIRST to sanitize threading/env before anything else
|
| 9 |
|
| 10 |
import os
|
| 11 |
-
import logging
|
| 12 |
from pathlib import Path
|
| 13 |
from typing import Optional, Tuple, Dict, Any, Callable
|
| 14 |
|
|
@@ -36,12 +35,11 @@ def patched_get_type(schema):
|
|
| 36 |
except Exception:
|
| 37 |
pass # No fatal error if Gradio patch fails
|
| 38 |
|
| 39 |
-
# 3️⃣
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
)
|
| 44 |
-
logger = logging.getLogger("BackgroundFX")
|
| 45 |
|
| 46 |
# 4️⃣ Import your modular code (assuming your project structure)
|
| 47 |
from core.exceptions import ModelLoadingError, VideoProcessingError
|
|
@@ -51,8 +49,10 @@ def patched_get_type(schema):
|
|
| 51 |
from models.loaders.model_loader import ModelLoader
|
| 52 |
from processing.video.video_processor import CoreVideoProcessor, ProcessorConfig
|
| 53 |
from processing.audio.audio_processor import AudioProcessor
|
| 54 |
-
|
| 55 |
-
#
|
|
|
|
|
|
|
| 56 |
|
| 57 |
# 5️⃣ CSP-safe fallback model stubs
|
| 58 |
class CSPSafeSAM2:
|
|
@@ -65,13 +65,22 @@ def predict(self, point_coords=None, point_labels=None, box=None, multimask_outp
|
|
| 65 |
return np.ones((n, h, w), dtype=bool), np.array([0.9, 0.8, 0.7][:n]), np.ones((n, h, w), dtype=np.float32)
|
| 66 |
|
| 67 |
class CSPSafeMatAnyone:
|
| 68 |
-
def step(self, image_tensor, mask_tensor=None, objects=None, first_frame_pred=False):
|
| 69 |
import torch
|
| 70 |
-
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
def output_prob_to_mask(self, output_prob):
|
| 73 |
return (output_prob > 0.5).float()
|
| 74 |
-
def process(self, image, mask):
|
| 75 |
return mask
|
| 76 |
|
| 77 |
# 6️⃣ Application main processor object
|
|
@@ -84,50 +93,78 @@ def __init__(self):
|
|
| 84 |
self.audio_proc = AudioProcessor()
|
| 85 |
self.models_loaded = False
|
| 86 |
self.core_processor = None
|
|
|
|
| 87 |
|
| 88 |
def load_models(self, progress_callback: Optional[Callable]=None) -> str:
|
| 89 |
-
logger.info("Loading models (CSP-safe)
|
| 90 |
try:
|
| 91 |
sam2, matanyone = self.model_loader.load_all_models(progress_callback=progress_callback)
|
| 92 |
except Exception as e:
|
| 93 |
-
logger.warning(
|
| 94 |
sam2, matanyone = None, None
|
|
|
|
| 95 |
sam2_model = getattr(sam2, "model", sam2) if sam2 else CSPSafeSAM2()
|
| 96 |
matanyone_model = getattr(matanyone, "model", matanyone) if matanyone else CSPSafeMatAnyone()
|
|
|
|
|
|
|
| 97 |
self.core_processor = CoreVideoProcessor(config=ProcessorConfig(), models=None)
|
| 98 |
self.core_processor.models = type('FakeModelManager', (), {
|
| 99 |
-
'get_sam2': lambda
|
| 100 |
-
'get_matanyone': lambda
|
| 101 |
})()
|
|
|
|
| 102 |
self.models_loaded = True
|
| 103 |
-
|
|
|
|
|
|
|
| 104 |
|
| 105 |
def process_video(self, video, bg_style, custom_bg_file):
|
| 106 |
if not self.models_loaded:
|
| 107 |
return None, "Models not loaded yet"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
import time
|
| 109 |
output_path = f"/tmp/output_{int(time.time())}.mp4"
|
| 110 |
-
|
|
|
|
| 111 |
if custom_bg_file:
|
| 112 |
cfg = {"custom_path": custom_bg_file.name}
|
| 113 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
if not ok:
|
| 115 |
-
|
|
|
|
|
|
|
| 116 |
try:
|
| 117 |
result = self.core_processor.process_video(
|
| 118 |
input_path=video,
|
| 119 |
output_path=output_path,
|
| 120 |
bg_config=cfg
|
| 121 |
)
|
|
|
|
|
|
|
| 122 |
output_with_audio = self.audio_proc.add_audio_to_video(video, output_path)
|
| 123 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
except Exception as e:
|
|
|
|
| 125 |
return None, f"Processing failed: {e}"
|
| 126 |
|
| 127 |
# 7️⃣ Gradio interface CSP-safe
|
| 128 |
def create_csp_safe_gradio():
|
| 129 |
import gradio as gr
|
| 130 |
app = VideoBackgroundApp()
|
|
|
|
| 131 |
with gr.Blocks(
|
| 132 |
title="BackgroundFX Pro - CSP Safe",
|
| 133 |
analytics_enabled=False,
|
|
@@ -141,9 +178,11 @@ def create_csp_safe_gradio():
|
|
| 141 |
with gr.Row():
|
| 142 |
with gr.Column():
|
| 143 |
video = gr.Video(label="Upload Video")
|
|
|
|
|
|
|
| 144 |
bg_style = gr.Dropdown(
|
| 145 |
-
choices=
|
| 146 |
-
value="
|
| 147 |
label="Background Style"
|
| 148 |
)
|
| 149 |
custom_bg = gr.File(label="Custom Background (Optional)", file_types=["image"])
|
|
@@ -155,9 +194,14 @@ def create_csp_safe_gradio():
|
|
| 155 |
out_video = gr.Video(label="Processed Video")
|
| 156 |
|
| 157 |
def safe_load():
|
| 158 |
-
|
|
|
|
|
|
|
|
|
|
| 159 |
def safe_process(vid, style, custom_bg_file):
|
|
|
|
| 160 |
return app.process_video(vid, style, custom_bg_file)
|
|
|
|
| 161 |
btn_load.click(fn=safe_load, outputs=[status])
|
| 162 |
btn_run.click(fn=safe_process, inputs=[video, bg_style, custom_bg], outputs=[out_video, status])
|
| 163 |
|
|
|
|
| 8 |
import early_env # <<< must be FIRST to sanitize threading/env before anything else
|
| 9 |
|
| 10 |
import os
|
|
|
|
| 11 |
from pathlib import Path
|
| 12 |
from typing import Optional, Tuple, Dict, Any, Callable
|
| 13 |
|
|
|
|
| 35 |
except Exception:
|
| 36 |
pass # No fatal error if Gradio patch fails
|
| 37 |
|
| 38 |
+
# 3️⃣ Initialize logging EARLY (before importing modules that emit logs)
|
| 39 |
+
from utils.logging_setup import setup_logging, make_logger
|
| 40 |
+
setup_logging(app_name="backgroundfx") # LOG_LEVEL env var respected; defaults to DEBUG in dev
|
| 41 |
+
logger = make_logger("entrypoint")
|
| 42 |
+
logger.info("Entrypoint starting…")
|
|
|
|
| 43 |
|
| 44 |
# 4️⃣ Import your modular code (assuming your project structure)
|
| 45 |
from core.exceptions import ModelLoadingError, VideoProcessingError
|
|
|
|
| 49 |
from models.loaders.model_loader import ModelLoader
|
| 50 |
from processing.video.video_processor import CoreVideoProcessor, ProcessorConfig
|
| 51 |
from processing.audio.audio_processor import AudioProcessor
|
| 52 |
+
|
| 53 |
+
# NOTE: You previously imported from utils.cv_processing; adjust if needed.
|
| 54 |
+
# Here we import from your unified utils (the file you showed me earlier).
|
| 55 |
+
from utils import PROFESSIONAL_BACKGROUNDS, validate_video_file
|
| 56 |
|
| 57 |
# 5️⃣ CSP-safe fallback model stubs
|
| 58 |
class CSPSafeSAM2:
|
|
|
|
| 65 |
return np.ones((n, h, w), dtype=bool), np.array([0.9, 0.8, 0.7][:n]), np.ones((n, h, w), dtype=np.float32)
|
| 66 |
|
| 67 |
class CSPSafeMatAnyone:
|
| 68 |
+
def step(self, image_tensor, mask_tensor=None, objects=None, first_frame_pred=False, **kwargs):
|
| 69 |
import torch
|
| 70 |
+
# image_tensor is CHW or NCHW (guard in ModelLoader handles it)
|
| 71 |
+
if hasattr(image_tensor, "shape"):
|
| 72 |
+
if len(image_tensor.shape) == 3:
|
| 73 |
+
_, H, W = image_tensor.shape
|
| 74 |
+
elif len(image_tensor.shape) == 4:
|
| 75 |
+
_, _, H, W = image_tensor.shape
|
| 76 |
+
else:
|
| 77 |
+
H, W = 256, 256
|
| 78 |
+
else:
|
| 79 |
+
H, W = 256, 256
|
| 80 |
+
return torch.ones((1, 1, H, W))
|
| 81 |
def output_prob_to_mask(self, output_prob):
|
| 82 |
return (output_prob > 0.5).float()
|
| 83 |
+
def process(self, image, mask, **kwargs):
|
| 84 |
return mask
|
| 85 |
|
| 86 |
# 6️⃣ Application main processor object
|
|
|
|
| 93 |
self.audio_proc = AudioProcessor()
|
| 94 |
self.models_loaded = False
|
| 95 |
self.core_processor = None
|
| 96 |
+
logger.info("VideoBackgroundApp initialized (device=%s)", self.device_mgr.get_optimal_device())
|
| 97 |
|
| 98 |
def load_models(self, progress_callback: Optional[Callable]=None) -> str:
|
| 99 |
+
logger.info("Loading models (CSP-safe)…")
|
| 100 |
try:
|
| 101 |
sam2, matanyone = self.model_loader.load_all_models(progress_callback=progress_callback)
|
| 102 |
except Exception as e:
|
| 103 |
+
logger.warning("Model loading failed (%s) - Using CSP-safe fallbacks", e)
|
| 104 |
sam2, matanyone = None, None
|
| 105 |
+
|
| 106 |
sam2_model = getattr(sam2, "model", sam2) if sam2 else CSPSafeSAM2()
|
| 107 |
matanyone_model = getattr(matanyone, "model", matanyone) if matanyone else CSPSafeMatAnyone()
|
| 108 |
+
|
| 109 |
+
# CoreVideoProcessor expects a models manager; provide minimal adapter
|
| 110 |
self.core_processor = CoreVideoProcessor(config=ProcessorConfig(), models=None)
|
| 111 |
self.core_processor.models = type('FakeModelManager', (), {
|
| 112 |
+
'get_sam2': lambda self_: sam2_model,
|
| 113 |
+
'get_matanyone': lambda self_: matanyone_model
|
| 114 |
})()
|
| 115 |
+
|
| 116 |
self.models_loaded = True
|
| 117 |
+
logger.info("Models ready (SAM2=%s, MatAnyOne=%s)",
|
| 118 |
+
type(sam2_model).__name__, type(matanyone_model).__name__)
|
| 119 |
+
return "Models loaded (CSP-safe; fallbacks in use if actual AI models failed)."
|
| 120 |
|
| 121 |
def process_video(self, video, bg_style, custom_bg_file):
|
| 122 |
if not self.models_loaded:
|
| 123 |
return None, "Models not loaded yet"
|
| 124 |
+
|
| 125 |
+
logger.info("process_video called (video=%s, bg_style=%s, custom_bg=%s)",
|
| 126 |
+
video, bg_style, getattr(custom_bg_file, "name", None))
|
| 127 |
+
|
| 128 |
import time
|
| 129 |
output_path = f"/tmp/output_{int(time.time())}.mp4"
|
| 130 |
+
|
| 131 |
+
# Background config
|
| 132 |
if custom_bg_file:
|
| 133 |
cfg = {"custom_path": custom_bg_file.name}
|
| 134 |
+
else:
|
| 135 |
+
# Safe default: ensure value exists in dict
|
| 136 |
+
style = bg_style if bg_style in PROFESSIONAL_BACKGROUNDS else "office"
|
| 137 |
+
cfg = PROFESSIONAL_BACKGROUNDS.get(style, PROFESSIONAL_BACKGROUNDS["office"])
|
| 138 |
+
|
| 139 |
+
# Validate input video (your `validate_video_file` returns bool)
|
| 140 |
+
ok = validate_video_file(video)
|
| 141 |
if not ok:
|
| 142 |
+
logger.warning("Invalid/unreadable video: %s", video)
|
| 143 |
+
return None, "Invalid or unreadable video file"
|
| 144 |
+
|
| 145 |
try:
|
| 146 |
result = self.core_processor.process_video(
|
| 147 |
input_path=video,
|
| 148 |
output_path=output_path,
|
| 149 |
bg_config=cfg
|
| 150 |
)
|
| 151 |
+
logger.info("Core processing done → %s", output_path)
|
| 152 |
+
|
| 153 |
output_with_audio = self.audio_proc.add_audio_to_video(video, output_path)
|
| 154 |
+
logger.info("Audio merged → %s", output_with_audio)
|
| 155 |
+
|
| 156 |
+
frames = (result.get('frames') if isinstance(result, dict) else None) or "n/a"
|
| 157 |
+
return output_with_audio, f"Processing complete ({frames} frames, style={bg_style})"
|
| 158 |
+
|
| 159 |
except Exception as e:
|
| 160 |
+
logger.exception("Processing failed")
|
| 161 |
return None, f"Processing failed: {e}"
|
| 162 |
|
| 163 |
# 7️⃣ Gradio interface CSP-safe
|
| 164 |
def create_csp_safe_gradio():
|
| 165 |
import gradio as gr
|
| 166 |
app = VideoBackgroundApp()
|
| 167 |
+
|
| 168 |
with gr.Blocks(
|
| 169 |
title="BackgroundFX Pro - CSP Safe",
|
| 170 |
analytics_enabled=False,
|
|
|
|
| 178 |
with gr.Row():
|
| 179 |
with gr.Column():
|
| 180 |
video = gr.Video(label="Upload Video")
|
| 181 |
+
# Ensure default choice exists in PROFESSIONAL_BACKGROUNDS (use 'office')
|
| 182 |
+
choices = list(PROFESSIONAL_BACKGROUNDS.keys())
|
| 183 |
bg_style = gr.Dropdown(
|
| 184 |
+
choices=choices,
|
| 185 |
+
value="office",
|
| 186 |
label="Background Style"
|
| 187 |
)
|
| 188 |
custom_bg = gr.File(label="Custom Background (Optional)", file_types=["image"])
|
|
|
|
| 194 |
out_video = gr.Video(label="Processed Video")
|
| 195 |
|
| 196 |
def safe_load():
|
| 197 |
+
msg = app.load_models()
|
| 198 |
+
logger.info("UI: models loaded")
|
| 199 |
+
return msg
|
| 200 |
+
|
| 201 |
def safe_process(vid, style, custom_bg_file):
|
| 202 |
+
logger.info("UI: starting processing (style=%s)", style)
|
| 203 |
return app.process_video(vid, style, custom_bg_file)
|
| 204 |
+
|
| 205 |
btn_load.click(fn=safe_load, outputs=[status])
|
| 206 |
btn_run.click(fn=safe_process, inputs=[video, bg_style, custom_bg], outputs=[out_video, status])
|
| 207 |
|