MogensR commited on
Commit
c075655
·
verified ·
1 Parent(s): 7acb710

Delete app_old.py

Browse files
Files changed (1) hide show
  1. app_old.py +0 -491
app_old.py DELETED
@@ -1,491 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- BackgroundFX Pro - CSP-Safe Application Entry Point
4
- Now with: live background preview + sources: Preset / Upload / Gradient / AI Generate
5
- (uses utils.cv_processing to avoid circular imports)
6
- """
7
-
8
- import early_env # <<< must be FIRST
9
-
10
- import os, time
11
- from typing import Optional, Dict, Any, Callable, Tuple
12
-
13
- # 1) CSP-safe Gradio env
14
- os.environ['GRADIO_ALLOW_FLAGGING'] = 'never'
15
- os.environ['GRADIO_ANALYTICS_ENABLED'] = 'False'
16
- os.environ['GRADIO_SERVER_NAME'] = '0.0.0.0'
17
- os.environ['GRADIO_SERVER_PORT'] = '7860'
18
-
19
- # 2) Gradio schema patch
20
- try:
21
- import gradio_client.utils as gc_utils
22
- _orig_get_type = gc_utils.get_type
23
- def _patched_get_type(schema):
24
- if not isinstance(schema, dict):
25
- if isinstance(schema, bool): return "boolean"
26
- if isinstance(schema, str): return "string"
27
- if isinstance(schema, (int, float)): return "number"
28
- return "string"
29
- return _orig_get_type(schema)
30
- gc_utils.get_type = _patched_get_type
31
- except Exception:
32
- pass
33
-
34
- # 3) Logging early
35
- from utils.logging_setup import setup_logging, make_logger
36
- setup_logging(app_name="backgroundfx")
37
- logger = make_logger("entrypoint")
38
- logger.info("Entrypoint starting…")
39
-
40
- # 4) Imports
41
- from config.app_config import get_config
42
- from utils.hardware.device_manager import DeviceManager
43
- from utils.system.memory_manager import MemoryManager
44
- from models.loaders.model_loader import ModelLoader
45
- from processing.video.video_processor import CoreVideoProcessor, ProcessorConfig
46
- from processing.audio.audio_processor import AudioProcessor
47
-
48
- # ⛑️ Bring helpers from the slim, self-contained cv_processing (no circular imports)
49
- from utils.cv_processing import (
50
- PROFESSIONAL_BACKGROUNDS, # dict of presets
51
- validate_video_file, # returns (ok, reason)
52
- create_professional_background, # used for preview defaults
53
- )
54
-
55
- # 5) CSP-safe fallbacks for models
56
- class CSPSafeSAM2:
57
- def set_image(self, image):
58
- self.shape = getattr(image, 'shape', (512, 512, 3))
59
- def predict(self, point_coords=None, point_labels=None, box=None, multimask_output=True, **kwargs):
60
- import numpy as np
61
- h, w = self.shape[:2] if hasattr(self, 'shape') else (512, 512)
62
- n = 3 if multimask_output else 1
63
- 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)
64
-
65
- class CSPSafeMatAnyone:
66
- def step(self, image_tensor, mask_tensor=None, objects=None, first_frame_pred=False, **kwargs):
67
- import torch
68
- if hasattr(image_tensor, "shape"):
69
- if len(image_tensor.shape) == 3:
70
- _, H, W = image_tensor.shape
71
- elif len(image_tensor.shape) == 4:
72
- _, _, H, W = image_tensor.shape
73
- else:
74
- H, W = 256, 256
75
- else:
76
- H, W = 256, 256
77
- return torch.ones((1, 1, H, W))
78
- def output_prob_to_mask(self, output_prob):
79
- return (output_prob > 0.5).float()
80
- def process(self, image, mask, **kwargs):
81
- return mask
82
-
83
- # ---------- helpers for UI ----------
84
- import numpy as np
85
- import cv2
86
- from PIL import Image
87
-
88
- PREVIEW_W, PREVIEW_H = 640, 360 # 16:9
89
-
90
- def _hex_to_rgb(x: str) -> Tuple[int, int, int]:
91
- x = (x or "").strip()
92
- if x.startswith("#") and len(x) == 7:
93
- return tuple(int(x[i:i+2], 16) for i in (1, 3, 5))
94
- return (255, 255, 255)
95
-
96
- def _np_to_pil(arr: np.ndarray) -> Image.Image:
97
- if arr.dtype != np.uint8:
98
- arr = arr.clip(0, 255).astype(np.uint8)
99
- return Image.fromarray(arr)
100
-
101
- def _create_gradient_preview(spec: Dict[str, Any], width: int, height: int) -> np.ndarray:
102
- """Lightweight linear gradient (with rotation) for previews."""
103
- def _to_rgb(c):
104
- if isinstance(c, (list, tuple)) and len(c) == 3:
105
- return tuple(int(x) for x in c)
106
- if isinstance(c, str) and c.startswith("#") and len(c) == 7:
107
- return tuple(int(c[i:i+2], 16) for i in (1,3,5))
108
- return (255, 255, 255)
109
- start = _to_rgb(spec.get("start", "#222222"))
110
- end = _to_rgb(spec.get("end", "#888888"))
111
- angle = float(spec.get("angle_deg", 0))
112
-
113
- bg = np.zeros((height, width, 3), np.uint8)
114
- for y in range(height):
115
- t = y / max(1, height - 1)
116
- r = int(start[0] * (1 - t) + end[0] * t)
117
- g = int(start[1] * (1 - t) + end[1] * t)
118
- b = int(start[2] * (1 - t) + end[2] * t)
119
- bg[y, :] = (r, g, b)
120
- if abs(angle) % 360 < 1e-6:
121
- return bg
122
- center = (width / 2, height / 2)
123
- rot = cv2.getRotationMatrix2D(center, angle, 1.0)
124
- return cv2.warpAffine(bg, rot, (width, height), flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT_101)
125
-
126
- # ---------- main app ----------
127
- class VideoBackgroundApp:
128
- def __init__(self):
129
- self.config = get_config()
130
- self.device_mgr = DeviceManager()
131
- self.memory_mgr = MemoryManager(self.device_mgr.get_optimal_device())
132
- self.model_loader = ModelLoader(self.device_mgr, self.memory_mgr)
133
- self.audio_proc = AudioProcessor()
134
- self.models_loaded = False
135
- self.core_processor: Optional[CoreVideoProcessor] = None
136
- logger.info("VideoBackgroundApp initialized (device=%s)", self.device_mgr.get_optimal_device())
137
-
138
- def _build_processor_config_safely(self) -> ProcessorConfig:
139
- """
140
- Build ProcessorConfig including stability knobs if supported by your installed CoreVideoProcessor.
141
- If your version doesn't have those fields, we auto-filter them out to avoid TypeError.
142
- """
143
- # Desired config (includes stability + encoding)
144
- desired: Dict[str, Any] = dict(
145
- background_preset="office",
146
- write_fps=None,
147
- max_model_size=1280,
148
- # --- stability knobs (only used if supported in your CoreVideoProcessor) ---
149
- temporal_ema_alpha=0.75, # 0.6–0.85 typical
150
- min_iou_to_accept=0.05, # reject sudden mask jumps
151
- dilate_px=6, # pad edges for hair/ears
152
- edge_blur_px=2, # calm shimmering edges
153
- # --- encoding (NVENC + fallbacks used inside the processor you installed) ---
154
- use_nvenc=True,
155
- nvenc_codec="h264",
156
- nvenc_preset="p5",
157
- nvenc_cq=18,
158
- nvenc_tune_hq=True,
159
- nvenc_pix_fmt="yuv420p",
160
- )
161
-
162
- # Filter against dataclass fields if present
163
- fields = getattr(ProcessorConfig, "__dataclass_fields__", None)
164
- if isinstance(fields, dict):
165
- filtered = {k: v for k, v in desired.items() if k in fields}
166
- else:
167
- # very old ProcessorConfig: just pass the common ones
168
- filtered = {
169
- "background_preset": desired["background_preset"],
170
- "write_fps": desired["write_fps"],
171
- "max_model_size": desired["max_model_size"],
172
- "use_nvenc": desired["use_nvenc"],
173
- "nvenc_codec": desired["nvenc_codec"],
174
- "nvenc_preset": desired["nvenc_preset"],
175
- "nvenc_cq": desired["nvenc_cq"],
176
- "nvenc_tune_hq": desired["nvenc_tune_hq"],
177
- "nvenc_pix_fmt": desired["nvenc_pix_fmt"],
178
- }
179
-
180
- try:
181
- return ProcessorConfig(**filtered)
182
- except TypeError:
183
- # final safety: pass minimal args
184
- return ProcessorConfig(
185
- background_preset="office",
186
- write_fps=None,
187
- max_model_size=1280,
188
- use_nvenc=True,
189
- nvenc_codec="h264",
190
- nvenc_preset="p5",
191
- nvenc_cq=18,
192
- nvenc_tune_hq=True,
193
- nvenc_pix_fmt="yuv420p",
194
- )
195
-
196
- def load_models(self, progress_callback: Optional[Callable] = None) -> str:
197
- logger.info("Loading models (CSP-safe)…")
198
- try:
199
- sam2, matanyone = self.model_loader.load_all_models(progress_callback=progress_callback)
200
- except Exception as e:
201
- logger.warning("Model loading failed (%s) - Using CSP-safe fallbacks", e)
202
- sam2, matanyone = None, None
203
-
204
- sam2_model = getattr(sam2, "model", sam2) if sam2 else CSPSafeSAM2()
205
- matanyone_model = getattr(matanyone, "model", matanyone) if matanyone else CSPSafeMatAnyone()
206
-
207
- cfg = self._build_processor_config_safely()
208
-
209
- self.core_processor = CoreVideoProcessor(config=cfg, models=None)
210
- self.core_processor.models = type('FakeModelManager', (), {
211
- 'get_sam2': lambda self_: sam2_model,
212
- 'get_matanyone': lambda self_: matanyone_model
213
- })()
214
-
215
- self.models_loaded = True
216
- logger.info("Models ready (SAM2=%s, MatAnyOne=%s)",
217
- type(sam2_model).__name__, type(matanyone_model).__name__)
218
- return "Models loaded (CSP-safe; fallbacks in use if actual AI models failed)."
219
-
220
- # ---- PREVIEWS ----
221
- def preview_preset(self, preset_key: str) -> Image.Image:
222
- key = preset_key if preset_key in PROFESSIONAL_BACKGROUNDS else "office"
223
- bg = create_professional_background(key, PREVIEW_W, PREVIEW_H) # RGB
224
- return _np_to_pil(bg)
225
-
226
- def preview_upload(self, file) -> Optional[Image.Image]:
227
- if file is None:
228
- return None
229
- try:
230
- img = Image.open(file.name).convert("RGB")
231
- img = img.resize((PREVIEW_W, PREVIEW_H), Image.LANCZOS)
232
- return img
233
- except Exception as e:
234
- logger.warning("Upload preview failed: %s", e)
235
- return None
236
-
237
- def preview_gradient(self, gtype: str, color1: str, color2: str, angle: int) -> Image.Image:
238
- spec = {
239
- "type": (gtype or "linear").lower(), # "linear" or "radial" (preview uses linear with rotation)
240
- "start": _hex_to_rgb(color1 or "#222222"),
241
- "end": _hex_to_rgb(color2 or "#888888"),
242
- "angle_deg": float(angle or 0),
243
- }
244
- bg = _create_gradient_preview(spec, PREVIEW_W, PREVIEW_H)
245
- return _np_to_pil(bg)
246
-
247
- def ai_generate_background(self, prompt: str, seed: int, width: int, height: int) -> Tuple[Optional[Image.Image], Optional[str], str]:
248
- """
249
- Try generating a background with diffusers; save to /tmp and return (img, path, status).
250
- """
251
- try:
252
- from diffusers import StableDiffusionPipeline
253
- import torch
254
- model_id = os.environ.get("BGFX_T2I_MODEL", "stabilityai/stable-diffusion-2-1")
255
- dtype = torch.float16 if torch.cuda.is_available() else torch.float32
256
- device = "cuda" if torch.cuda.is_available() else "cpu"
257
- pipe = StableDiffusionPipeline.from_pretrained(model_id, torch_dtype=dtype).to(device)
258
- g = torch.Generator(device=device).manual_seed(int(seed)) if seed is not None else None
259
- if device == "cuda":
260
- with torch.autocast("cuda"):
261
- img = pipe(prompt, height=height, width=width, guidance_scale=7.0, num_inference_steps=25, generator=g).images[0]
262
- else:
263
- img = pipe(prompt, height=height, width=width, guidance_scale=7.0, num_inference_steps=25, generator=g).images[0]
264
- tmp_path = f"/tmp/ai_bg_{int(time.time())}.png"
265
- img.save(tmp_path)
266
- return img.resize((PREVIEW_W, PREVIEW_H), Image.LANCZOS), tmp_path, f"AI background generated ✓ ({os.path.basename(tmp_path)})"
267
- except Exception as e:
268
- logger.warning("AI generation unavailable: %s", e)
269
- return None, None, f"AI generation unavailable: {e}"
270
-
271
- # ---- PROCESS VIDEO ----
272
- def process_video(
273
- self,
274
- video: str,
275
- bg_source: str,
276
- preset_key: str,
277
- custom_bg_file,
278
- grad_type: str,
279
- grad_color1: str,
280
- grad_color2: str,
281
- grad_angle: int,
282
- ai_bg_path: Optional[str],
283
- ):
284
- if not self.models_loaded:
285
- return None, "Models not loaded yet"
286
-
287
- if not video:
288
- return None, "Please upload a video first."
289
-
290
- logger.info("process_video called (video=%s, source=%s, preset=%s, file=%s, grad=%s, ai=%s)",
291
- video, bg_source, preset_key, getattr(custom_bg_file, "name", None) if custom_bg_file else None,
292
- {"type": grad_type, "c1": grad_color1, "c2": grad_color2, "angle": grad_angle},
293
- ai_bg_path)
294
-
295
- output_path = f"/tmp/output_{int(time.time())}.mp4"
296
-
297
- # ✅ Validate input video (tuple: ok, reason)
298
- ok, reason = validate_video_file(video)
299
- if not ok:
300
- logger.warning("Invalid/unreadable video: %s (%s)", video, reason)
301
- return None, f"Invalid or unreadable video file: {reason}"
302
-
303
- # Build bg_config based on source
304
- src = (bg_source or "Preset").lower()
305
- if src == "upload" and custom_bg_file is not None:
306
- bg_cfg: Dict[str, Any] = {"custom_path": custom_bg_file.name}
307
- elif src == "gradient":
308
- bg_cfg = {
309
- "gradient": {
310
- "type": (grad_type or "linear").lower(),
311
- "start": _hex_to_rgb(grad_color1 or "#222222"),
312
- "end": _hex_to_rgb(grad_color2 or "#888888"),
313
- "angle_deg": float(grad_angle or 0),
314
- }
315
- }
316
- elif src == "ai generate" and ai_bg_path:
317
- bg_cfg = {"custom_path": ai_bg_path}
318
- else:
319
- key = preset_key if preset_key in PROFESSIONAL_BACKGROUNDS else "office"
320
- bg_cfg = {"background_choice": key}
321
-
322
- try:
323
- result = self.core_processor.process_video(
324
- input_path=video,
325
- output_path=output_path,
326
- bg_config=bg_cfg
327
- )
328
- logger.info("Core processing done → %s", output_path)
329
-
330
- output_with_audio = self.audio_proc.add_audio_to_video(video, output_path)
331
- logger.info("Audio merged → %s", output_with_audio)
332
-
333
- frames = (result.get('frames') if isinstance(result, dict) else None) or "n/a"
334
- return output_with_audio, f"Processing complete ({frames} frames, background={bg_source})"
335
-
336
- except Exception as e:
337
- logger.exception("Processing failed")
338
- return None, f"Processing failed: {e}"
339
-
340
- # 7) Gradio UI
341
- def create_csp_safe_gradio():
342
- import gradio as gr
343
- app = VideoBackgroundApp()
344
-
345
- with gr.Blocks(
346
- title="BackgroundFX Pro - CSP Safe",
347
- analytics_enabled=False,
348
- css="""
349
- .gradio-container { max-width: 1100px; margin: auto; }
350
- """
351
- ) as demo:
352
- gr.Markdown("# 🎬 BackgroundFX Pro (CSP-Safe)")
353
- gr.Markdown("Replace your video background with cinema-quality AI matting. Now with live background preview.")
354
-
355
- with gr.Row():
356
- with gr.Column(scale=1):
357
- video = gr.Video(label="Upload Video")
358
- bg_source = gr.Radio(
359
- ["Preset", "Upload", "Gradient", "AI Generate"],
360
- value="Preset",
361
- label="Background Source",
362
- interactive=True,
363
- )
364
-
365
- # PRESET
366
- preset_choices = list(PROFESSIONAL_BACKGROUNDS.keys())
367
- default_preset = "office" if "office" in preset_choices else (preset_choices[0] if preset_choices else "office")
368
- preset_key = gr.Dropdown(choices=preset_choices, value=default_preset, label="Preset")
369
-
370
- # UPLOAD
371
- custom_bg = gr.File(label="Custom Background (Image)", file_types=["image"], visible=False)
372
-
373
- # GRADIENT
374
- grad_type = gr.Dropdown(choices=["Linear", "Radial"], value="Linear", label="Gradient Type", visible=False)
375
- grad_color1 = gr.ColorPicker(value="#222222", label="Start Color", visible=False)
376
- grad_color2 = gr.ColorPicker(value="#888888", label="End Color", visible=False)
377
- grad_angle = gr.Slider(0, 360, value=0, step=1, label="Angle (degrees)", visible=False)
378
-
379
- # AI
380
- ai_prompt = gr.Textbox(label="AI Prompt", placeholder="e.g., sunlit modern office, soft bokeh, neutral palette", visible=False)
381
- ai_seed = gr.Slider(0, 2**31-1, step=1, value=42, label="Seed", visible=False)
382
- ai_size = gr.Dropdown(choices=["640x360","960x540","1280x720"], value="640x360", label="AI Image Size", visible=False)
383
- ai_go = gr.Button("✨ Generate Background", visible=False, variant="secondary")
384
- ai_status = gr.Markdown(visible=False)
385
- ai_bg_path_state = gr.State(value=None) # store /tmp path
386
-
387
- btn_load = gr.Button("🔄 Load Models", variant="secondary")
388
- btn_run = gr.Button("🎬 Process Video", variant="primary")
389
-
390
- with gr.Column(scale=1):
391
- status = gr.Textbox(label="Status", lines=4)
392
- bg_preview = gr.Image(label="Background Preview", width=PREVIEW_W, height=PREVIEW_H, interactive=False)
393
- out_video = gr.Video(label="Processed Video")
394
-
395
- # ---------- UI wiring ----------
396
-
397
- # background source → show/hide controls
398
- def on_source_toggle(src):
399
- src = (src or "Preset").lower()
400
- return (
401
- gr.update(visible=(src == "preset")),
402
- gr.update(visible=(src == "upload")),
403
- gr.update(visible=(src == "gradient")),
404
- gr.update(visible=(src == "gradient")),
405
- gr.update(visible=(src == "gradient")),
406
- gr.update(visible=(src == "gradient")),
407
- gr.update(visible=(src == "ai generate")),
408
- gr.update(visible=(src == "ai generate")),
409
- gr.update(visible=(src == "ai generate")),
410
- gr.update(visible=(src == "ai generate")),
411
- gr.update(visible=(src == "ai generate")),
412
- )
413
- bg_source.change(
414
- fn=on_source_toggle,
415
- inputs=[bg_source],
416
- outputs=[preset_key, custom_bg, grad_type, grad_color1, grad_color2, grad_angle, ai_prompt, ai_seed, ai_size, ai_go, ai_status],
417
- )
418
-
419
- # ✅ Clear any previous AI image path when switching source (avoids stale AI background)
420
- def _clear_ai_state(_):
421
- return None
422
- bg_source.change(fn=_clear_ai_state, inputs=[bg_source], outputs=[ai_bg_path_state])
423
-
424
- # When source changes, also refresh preview based on visible controls
425
- def on_source_preview(src, pkey, gt, c1, c2, ang):
426
- src_l = (src or "Preset").lower()
427
- if src_l == "preset":
428
- return app.preview_preset(pkey)
429
- elif src_l == "gradient":
430
- return app.preview_gradient(gt, c1, c2, ang)
431
- # For upload/AI we keep whatever the component change handler sets (don’t overwrite)
432
- return gr.update() # no-op
433
- bg_source.change(
434
- fn=on_source_preview,
435
- inputs=[bg_source, preset_key, grad_type, grad_color1, grad_color2, grad_angle],
436
- outputs=[bg_preview]
437
- )
438
-
439
- # live previews
440
- preset_key.change(fn=lambda k: app.preview_preset(k), inputs=[preset_key], outputs=[bg_preview])
441
- custom_bg.change(fn=lambda f: app.preview_upload(f), inputs=[custom_bg], outputs=[bg_preview])
442
- for comp in (grad_type, grad_color1, grad_color2, grad_angle):
443
- comp.change(
444
- fn=lambda gt, c1, c2, ang: app.preview_gradient(gt, c1, c2, ang),
445
- inputs=[grad_type, grad_color1, grad_color2, grad_angle],
446
- outputs=[bg_preview],
447
- )
448
-
449
- # AI generate
450
- def ai_generate(prompt, seed, size):
451
- try:
452
- w, h = map(int, size.split("x"))
453
- except Exception:
454
- w, h = PREVIEW_W, PREVIEW_H
455
- img, path, msg = app.ai_generate_background(
456
- prompt or "professional modern office background, neutral colors, depth of field",
457
- int(seed), w, h
458
- )
459
- return img, (path or None), msg
460
- ai_go.click(fn=ai_generate, inputs=[ai_prompt, ai_seed, ai_size], outputs=[bg_preview, ai_bg_path_state, ai_status])
461
-
462
- # model load / run
463
- def safe_load():
464
- msg = app.load_models()
465
- logger.info("UI: models loaded")
466
- # Set initial preview (preset default)
467
- default_key = preset_key.value if hasattr(preset_key, "value") else "office"
468
- return msg, app.preview_preset(default_key)
469
- btn_load.click(fn=safe_load, outputs=[status, bg_preview])
470
-
471
- def safe_process(vid, src, pkey, file, gtype, c1, c2, ang, ai_path):
472
- return app.process_video(vid, src, pkey, file, gtype, c1, c2, ang, ai_path)
473
- btn_run.click(
474
- fn=safe_process,
475
- inputs=[video, bg_source, preset_key, custom_bg, grad_type, grad_color1, grad_color2, grad_angle, ai_bg_path_state],
476
- outputs=[out_video, status]
477
- )
478
-
479
- return demo
480
-
481
- # 8) Launch
482
- if __name__ == "__main__":
483
- logger.info("Launching CSP-safe Gradio interface for Hugging Face Spaces")
484
- demo = create_csp_safe_gradio()
485
- demo.queue().launch(
486
- server_name="0.0.0.0",
487
- server_port=7860,
488
- show_error=True,
489
- debug=False,
490
- inbrowser=False
491
- )