MogensR commited on
Commit
239315b
ยท
1 Parent(s): e2cdff9

Update ui_components.py

Browse files
Files changed (1) hide show
  1. ui_components.py +318 -219
ui_components.py CHANGED
@@ -1,231 +1,330 @@
1
  #!/usr/bin/env python3
2
  """
3
- UI Components for BackgroundFX Pro - with Start/Stop, frame counter, and live FPS
 
 
 
 
 
 
 
4
  """
5
 
6
- import gradio as gr
 
 
 
7
  import time
8
- import threading
9
-
10
- # --- Import backend logic ---
11
- try:
12
- from core.app import (
13
- processor,
14
- load_models_with_validation,
15
- process_video_fixed,
16
- get_model_status,
17
- get_cache_status,
18
- )
19
- CORE_FUNCTIONS_AVAILABLE = True
20
- except Exception as e:
21
- print(f"[UI] Core functions import failed: {e}")
22
- CORE_FUNCTIONS_AVAILABLE = False
23
-
24
- try:
25
- from utils.backgrounds import PROFESSIONAL_BACKGROUNDS
26
- UTILITIES_AVAILABLE = True
27
- except Exception as e:
28
- PROFESSIONAL_BACKGROUNDS = {"office_modern": {"name": "Modern Office", "description": "Default office background"}}
29
- UTILITIES_AVAILABLE = False
30
-
31
- # --- UI state management ---
32
- stop_event = threading.Event()
33
-
34
- def create_interface():
35
- is_processing = gr.State(False)
36
- frame_progress = gr.State({"current": 0, "total": 0, "fps": 0.0})
37
-
38
- def enhanced_process_video(
39
- video_path, bg_method, custom_img, prof_choice,
40
- use_two_stage, chroma_preset, quality_preset,
41
- is_processing_state, frame_progress_state, progress=gr.Progress()
42
- ):
43
- """
44
- Handles video processing (start/stop) and live frame/fps reporting.
45
- """
46
- # If already processing: user wants to STOP
47
- if is_processing_state:
48
- stop_event.set()
49
- return None, "Processing stopped by user.", "Processing stopped.", False, {"current": 0, "total": 0, "fps": 0.0}
50
-
51
- # Otherwise: START processing
52
- stop_event.clear()
53
- is_processing_state = True
54
- frame_progress_state = {"current": 0, "total": 0, "fps": 0.0}
55
- last_update = time.time()
56
- last_frame = 0
57
-
58
- if not CORE_FUNCTIONS_AVAILABLE:
59
- return None, "Core backend not available.", "System error.", False, frame_progress_state
60
- if not processor.models_loaded:
61
- return None, "Models not loaded.", "Load models first.", False, frame_progress_state
62
- if not video_path:
63
- return None, "No video uploaded.", "Upload a video.", False, frame_progress_state
64
-
65
- # Choose background
66
- if bg_method == "professional" and not prof_choice:
67
- return None, "No professional background selected.", "Choose a background.", False, frame_progress_state
68
- if bg_method == "upload" and not custom_img:
69
- return None, "No custom background image.", "Upload a background image.", False, frame_progress_state
70
-
71
- try:
72
- def progress_callback(pct, desc, current_frame=None, total_frames=None):
73
- # User cancellation check
74
- if stop_event.is_set():
75
- raise Exception("Processing stopped by user.")
76
-
77
- # Standard Gradio progress
78
- if progress:
79
- progress(pct, desc)
80
-
81
- # Update frame count/fps
82
- nonlocal last_update, last_frame, frame_progress_state
83
- now = time.time()
84
- fps = 0.0
85
- if current_frame is not None and total_frames is not None:
86
- dt = now - last_update if last_update else 0.01
87
- frames_done = current_frame - last_frame if last_frame is not None else 0
88
- fps = frames_done / dt if dt > 0 else 0.0
89
- last_update = now
90
- last_frame = current_frame
91
- frame_progress_state = {
92
- "current": int(current_frame),
93
- "total": int(total_frames),
94
- "fps": round(fps, 2)
95
- }
96
- return desc
97
-
98
- result_path, result_message = process_video_fixed(
99
- video_path=video_path,
100
- background_choice=prof_choice if bg_method == "professional" else "custom",
101
- custom_background_path=custom_img if bg_method == "upload" else None,
102
- progress_callback=lambda pct, desc: progress_callback(pct, desc), # You may want to pass current_frame/total_frames
103
- use_two_stage=bool(use_two_stage),
104
- chroma_preset=chroma_preset or "standard",
105
- preview_mask=False,
106
- preview_greenscreen=False,
107
- stop_event=stop_event, # <-- Backend must support this argument!
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  )
109
- is_processing_state = False
110
- return result_path, result_message, "Processing completed.", False, frame_progress_state
111
-
112
- except Exception as e:
113
- is_processing_state = False
114
- return None, f"Error: {str(e)}", "Error during processing.", False, frame_progress_state
115
-
116
- def handle_model_loading(progress=gr.Progress()):
117
- """
118
- Wrapper function for model loading with proper progress handling
119
- """
120
- try:
121
- # Call the model loading function with progress callback
122
- result = load_models_with_validation(progress)
123
- return result
124
- except Exception as e:
125
- return f"Model loading failed: {str(e)}"
126
-
127
- # --- UI setup ---
128
- with gr.Blocks(
129
- title="BackgroundFX Pro",
130
- css="""
131
- .main-header { text-align: center; margin-bottom: 20px; }
132
- .status-box { background: #f8f9fa; padding: 15px; border-radius: 8px; margin: 10px 0; }
133
- .live-progress { font-size: 1.1em; font-weight: bold; color: #004085; }
134
- """
135
- ) as demo:
136
- with gr.Row():
137
- gr.Markdown("# BackgroundFX Pro - Video Background Replacement", elem_classes=["main-header"])
138
-
139
- # --- Main UI fields ---
140
- with gr.Row():
141
- with gr.Column():
142
- video_input = gr.Video(label="Upload Video", height=280)
143
- background_method = gr.Radio(
144
- ["professional", "upload"], value="professional", label="Background Method"
145
- )
146
- professional_choice = gr.Dropdown(
147
- choices=[(bg['name'], key) for key, bg in PROFESSIONAL_BACKGROUNDS.items()],
148
- value=list(PROFESSIONAL_BACKGROUNDS.keys())[0],
149
- label="Professional Background"
150
- )
151
- custom_background = gr.Image(label="Upload Background", type="filepath", visible=False)
152
- quality_preset = gr.Dropdown(
153
- choices=[
154
- ("Fast", "fast"),
155
- ("Balanced", "balanced"),
156
- ("High Quality", "high")
157
- ],
158
- value="balanced",
159
- label="Quality Preset"
160
- )
161
- use_two_stage = gr.Checkbox("Enable Two-Stage Mode", value=False)
162
- chroma_preset = gr.Dropdown(
163
- choices=[("Standard", "standard"), ("Studio", "studio"), ("Outdoor", "outdoor")],
164
- value="standard",
165
- label="Chroma Key Preset"
166
- )
167
- # Dynamic show/hide
168
- def update_background_visibility(method):
169
- return (
170
- gr.update(visible=(method == "professional")),
171
- gr.update(visible=(method == "upload"))
172
  )
173
- background_method.change(
174
- fn=update_background_visibility,
175
- inputs=background_method,
176
- outputs=[professional_choice, custom_background]
177
- )
178
- with gr.Row():
179
- load_models_btn = gr.Button("Load Models", variant="secondary")
180
- process_btn = gr.Button("Process Video", variant="primary")
181
-
182
- status_text = gr.Textbox(label="Status", value="Ready", interactive=False)
183
- frame_info = gr.Textbox(label="Frame Progress", value="", interactive=False)
184
- fps_info = gr.Textbox(label="Frames/sec", value="", interactive=False)
185
-
186
- with gr.Column():
187
- video_output = gr.Video(label="Processed Video", height=360)
188
- result_info = gr.Textbox(label="Processing Info", lines=8, interactive=False)
189
- debug_info = gr.Textbox(label="Debug Log", lines=6, interactive=False, visible=False)
190
-
191
- # --- Button actions ---
192
- def update_process_btn(is_processing):
193
- return gr.update(value="Stop Processing" if is_processing else "Process Video")
194
- is_processing.change(update_process_btn, inputs=is_processing, outputs=process_btn)
195
-
196
- def update_frame_fields(frame_progress):
197
- total = frame_progress.get("total", 0)
198
- current = frame_progress.get("current", 0)
199
- fps = frame_progress.get("fps", 0.0)
200
- txt = f"{current}/{total}" if total else ""
201
- return txt, f"{fps:.2f} FPS" if fps else ""
202
- frame_progress.change(update_frame_fields, inputs=frame_progress, outputs=[frame_info, fps_info])
203
-
204
- process_btn.click(
205
- fn=enhanced_process_video,
206
- inputs=[
207
- video_input,
208
- background_method,
209
- custom_background,
210
- professional_choice,
211
- use_two_stage,
212
- chroma_preset,
213
- quality_preset,
214
- is_processing,
215
- frame_progress
216
- ],
217
- outputs=[video_output, result_info, debug_info, is_processing, frame_progress],
218
- show_progress=True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  )
 
 
 
220
 
221
- # Fixed model loading button - let Gradio handle progress automatically
222
- load_models_btn.click(
223
- fn=handle_model_loading,
224
- outputs=[status_text],
225
- show_progress=True
 
 
 
 
226
  )
227
 
228
  return demo
229
-
230
- def create_ui():
231
- return create_interface()
 
1
  #!/usr/bin/env python3
2
  """
3
+ UI Components for BackgroundFX Pro (Hugging Face Spaces, CSP-safe)
4
+
5
+ - Clean, modern layout with tabs
6
+ - Keeps existing functionality:
7
+ * Load models
8
+ * Process video (single-stage / two-stage switch, previews, etc.)
9
+ * Status panel
10
+ - Adds lightweight "AI Background" generator (procedural, no heavy deps)
11
  """
12
 
13
+ from __future__ import annotations
14
+
15
+ import os
16
+ import io
17
  import time
18
+ import math
19
+ import random
20
+ from pathlib import Path
21
+ from typing import Optional, Tuple, Dict, Any, List
22
+
23
+ import gradio as gr
24
+ from PIL import Image, ImageFilter, ImageOps
25
+ import numpy as np
26
+
27
+ # Import core wrappers (these are defined in core/app.py)
28
+ # NOTE: core/app.py imports ui_components only *inside* main(), so this wonโ€™t create a circular import.
29
+ from core.app import (
30
+ load_models_with_validation,
31
+ process_video_fixed,
32
+ get_model_status,
33
+ get_cache_status,
34
+ PROCESS_CANCELLED,
35
+ )
36
+
37
+ # --------------------------
38
+ # Helpers: file paths, io
39
+ # --------------------------
40
+
41
+ TMP_DIR = Path("/tmp/bgfx")
42
+ TMP_DIR.mkdir(parents=True, exist_ok=True)
43
+
44
+
45
+ def _save_pil(img: Image.Image, stem: str = "gen_bg", ext: str = "png") -> str:
46
+ """Save a PIL image into /tmp and return its path."""
47
+ ts = int(time.time() * 1000)
48
+ p = TMP_DIR / f"{stem}_{ts}.{ext}"
49
+ img.save(p)
50
+ return str(p)
51
+
52
+
53
+ def _image_to_np(img: Image.Image) -> np.ndarray:
54
+ return np.array(img)
55
+
56
+
57
+ # --------------------------
58
+ # Lightweight "AI" background generator
59
+ # (procedural: palette from prompt + Perlin-ish noise + bokeh blur)
60
+ # --------------------------
61
+
62
+ _PALETTES = {
63
+ # very light keyword mapping; expand anytime
64
+ "office": [(240, 245, 250), (210, 220, 230), (180, 190, 200)],
65
+ "studio": [(18, 18, 20), (32, 32, 36), (58, 60, 64)],
66
+ "sunset": [(255, 183, 77), (255, 138, 101), (244, 143, 177)],
67
+ "forest": [(46, 125, 50), (102, 187, 106), (165, 214, 167)],
68
+ "ocean": [(33, 150, 243), (3, 169, 244), (0, 188, 212)],
69
+ "minimal": [(245, 246, 248), (230, 232, 236), (214, 218, 224)],
70
+ "warm": [(255, 224, 178), (255, 204, 128), (255, 171, 145)],
71
+ "cool": [(197, 202, 233), (179, 229, 252), (178, 235, 242)],
72
+ "royal": [(63, 81, 181), (121, 134, 203), (159, 168, 218)],
73
+ }
74
+
75
+ def _palette_from_prompt(prompt: str) -> List[tuple]:
76
+ p = (prompt or "").lower()
77
+ for key, pal in _PALETTES.items():
78
+ if key in p:
79
+ return pal
80
+ # fallback: hash to palette
81
+ random.seed(hash(p) % (2**32 - 1))
82
+ return [tuple(random.randint(90, 200) for _ in range(3)) for _ in range(3)]
83
+
84
+
85
+ def _perlin_like_noise(h: int, w: int, octaves: int = 4) -> np.ndarray:
86
+ """Fast fake-perlin using summed blurred noise."""
87
+ acc = np.zeros((h, w), dtype=np.float32)
88
+ for o in range(octaves):
89
+ scale = 2 ** o
90
+ noise = np.random.rand(h // scale + 1, w // scale + 1).astype(np.float32)
91
+ noise = Image.fromarray((noise * 255).astype(np.uint8)).resize((w, h), Image.BILINEAR)
92
+ noise = np.array(noise).astype(np.float32) / 255.0
93
+ acc += noise / (o + 1)
94
+ acc = acc / acc.max()
95
+ return acc
96
+
97
+
98
+ def _blend_palette(noise: np.ndarray, palette: List[tuple]) -> Image.Image:
99
+ """Map grayscale noise to a 3-color gradient."""
100
+ h, w = noise.shape
101
+ img = np.zeros((h, w, 3), dtype=np.float32)
102
+ # Tri-color mapping
103
+ thresholds = [0.33, 0.66]
104
+ c0, c1, c2 = [np.array(c, dtype=np.float32) for c in palette]
105
+ mask0 = noise < thresholds[0]
106
+ mask1 = (noise >= thresholds[0]) & (noise < thresholds[1])
107
+ mask2 = noise >= thresholds[1]
108
+ img[mask0] = c0
109
+ img[mask1] = c1
110
+ img[mask2] = c2
111
+ img = np.clip(img, 0, 255).astype(np.uint8)
112
+ return Image.fromarray(img)
113
+
114
+
115
+ def generate_ai_background(
116
+ prompt: str,
117
+ width: int = 1280,
118
+ height: int = 720,
119
+ bokeh: float = 0.0,
120
+ vignette: float = 0.15,
121
+ contrast: float = 1.05,
122
+ ) -> Tuple[Image.Image, str]:
123
+ """Procedural 'AI-ish' background for fast, dependency-free generation."""
124
+ palette = _palette_from_prompt(prompt)
125
+ noise = _perlin_like_noise(height, width, octaves=4)
126
+ img = _blend_palette(noise, palette)
127
+
128
+ # Subtle blur / bokeh
129
+ if bokeh > 0:
130
+ radius = max(0, min(50, bokeh))
131
+ img = img.filter(ImageFilter.GaussianBlur(radius=radius))
132
+
133
+ # Vignette
134
+ if vignette > 0:
135
+ y, x = np.ogrid[:height, :width]
136
+ cx, cy = width / 2, height / 2
137
+ r = np.sqrt((x - cx) ** 2 + (y - cy) ** 2)
138
+ mask = 1 - np.clip(r / (max(width, height) / 1.2), 0, 1)
139
+ mask = mask ** 2
140
+ mask = (mask * (1 - vignette) + (1 - (1 - vignette))).astype(np.float32)
141
+ base = np.array(img).astype(np.float32) / 255.0
142
+ out = np.empty_like(base)
143
+ for c in range(3):
144
+ out[..., c] = base[..., c] * mask
145
+ img = Image.fromarray(np.clip(out * 255, 0, 255).astype(np.uint8))
146
+
147
+ # Simple contrast
148
+ if contrast != 1.0:
149
+ img = ImageOps.autocontrast(img, cutoff=1)
150
+ arr = np.array(img).astype(np.float32)
151
+ mean = arr.mean(axis=(0, 1), keepdims=True)
152
+ arr = (arr - mean) * contrast + mean
153
+ img = Image.fromarray(np.clip(arr, 0, 255).astype(np.uint8))
154
+
155
+ path = _save_pil(img, stem="ai_bg", ext="png")
156
+ return img, path
157
+
158
+
159
+ # --------------------------
160
+ # Gradio UI
161
+ # --------------------------
162
+
163
+ CSS = """
164
+ :root { --radius: 16px; }
165
+ .gradio-container { max-width: 1080px !important; margin: auto !important; }
166
+ #hero .prose { font-size: 15px; }
167
+ .card { border-radius: var(--radius); border: 1px solid rgba(0,0,0,.08); padding: 16px; background: linear-gradient(180deg, rgba(255,255,255,.9), rgba(248,250,252,.9)); box-shadow: 0 10px 30px rgba(0,0,0,.06); }
168
+ .footer-note { opacity: 0.7; font-size: 12px; }
169
+ .sm { font-size: 13px; opacity: 0.85; }
170
+ #statusbox { min-height: 120px; }
171
+ """
172
+
173
+ def create_interface() -> gr.Blocks:
174
+ with gr.Blocks(title="๐ŸŽฌ BackgroundFX Pro", css=CSS, analytics_enabled=False, theme=gr.themes.Soft()) as demo:
175
+ # ---------- HERO ----------
176
+ with gr.Row(elem_id="hero"):
177
+ gr.Markdown(
178
+ "## ๐ŸŽฌ BackgroundFX Pro\n"
179
+ "Polished matting & background replacement for video. Runs on Hugging Face Spaces.\n"
180
+ "Tip: **Load models** before processing for best results."
181
  )
182
+
183
+ with gr.Tab("๐Ÿ Quick Start"):
184
+ with gr.Row():
185
+ with gr.Column(scale=1):
186
+ video = gr.Video(label="Upload Video")
187
+ bg_style = gr.Dropdown(
188
+ label="Background Style",
189
+ choices=[
190
+ "minimalist", "office", "studio", "ocean", "forest", "sunset",
191
+ "royal", "warm", "cool"
192
+ ],
193
+ value="minimalist",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  )
195
+ custom_bg = gr.File(label="Custom Background (Optional)", file_types=["image"])
196
+
197
+ with gr.Accordion("Advanced", open=False):
198
+ use_two_stage = gr.Checkbox(label="Use Two-Stage Pipeline", value=False)
199
+ chroma_preset = gr.Dropdown(
200
+ label="Chroma Preset", choices=["standard"], value="standard"
201
+ )
202
+ preview_mask = gr.Checkbox(label="Preview Mask (no audio remix)", value=False)
203
+ preview_greenscreen = gr.Checkbox(label="Preview Greenscreen (no audio remix)", value=False)
204
+
205
+ with gr.Row():
206
+ btn_load = gr.Button("๐Ÿ”„ Load Models", variant="secondary")
207
+ btn_run = gr.Button("๐ŸŽฌ Process Video", variant="primary")
208
+ btn_cancel = gr.Button("โน๏ธ Cancel", variant="secondary")
209
+
210
+ with gr.Column(scale=1):
211
+ out_video = gr.Video(label="Processed Output", interactive=False)
212
+ statusbox = gr.Textbox(label="Status", lines=8, elem_id="statusbox")
213
+ with gr.Row():
214
+ btn_refresh = gr.Button("๐Ÿ” Refresh Status", variant="secondary")
215
+ btn_clear = gr.Button("๐Ÿงน Clear", variant="secondary")
216
+
217
+ # ---------- AI BACKGROUND ----------
218
+ with gr.Tab("๐Ÿง  AI Background (Lightweight)"):
219
+ with gr.Row():
220
+ with gr.Column(scale=1):
221
+ prompt = gr.Textbox(label="Describe the vibe (e.g., 'modern office', 'soft sunset studio')", value="modern office")
222
+ with gr.Row():
223
+ gen_width = gr.Slider(640, 1920, value=1280, step=10, label="Width")
224
+ gen_height = gr.Slider(360, 1080, value=720, step=10, label="Height")
225
+ with gr.Row():
226
+ bokeh = gr.Slider(0, 30, value=8, step=1, label="Bokeh Blur")
227
+ vignette = gr.Slider(0.0, 0.6, value=0.15, step=0.01, label="Vignette")
228
+ contrast = gr.Slider(0.8, 1.4, value=1.05, step=0.01, label="Contrast")
229
+
230
+ btn_gen_bg = gr.Button("โœจ Generate Background", variant="primary")
231
+
232
+ with gr.Column(scale=1):
233
+ gen_preview = gr.Image(label="Generated Background", interactive=False)
234
+ gen_path = gr.Textbox(label="Saved Path", interactive=False)
235
+ use_gen_as_custom = gr.Button("๐Ÿ“Œ Use As Custom Background", variant="secondary")
236
+
237
+ # ---------- STATUS ----------
238
+ with gr.Tab("๐Ÿ“ˆ Status & Settings"):
239
+ with gr.Row():
240
+ with gr.Column(scale=1, elem_classes=["card"]):
241
+ model_status = gr.JSON(label="Model Status")
242
+ with gr.Column(scale=1, elem_classes=["card"]):
243
+ cache_status = gr.JSON(label="Cache / System Status")
244
+ gr.Markdown("<div class='footer-note'>If models fail to load, fallbacks keep the UI responsive. Check logs for details.</div>")
245
+
246
+ # ---------- CALLBACKS ----------
247
+ # Load Models
248
+ def _cb_load_models() -> str:
249
+ return load_models_with_validation()
250
+
251
+ # Process
252
+ def _cb_process(
253
+ vid: str,
254
+ style: str,
255
+ custom_file: dict | None,
256
+ use_two: bool,
257
+ chroma: str,
258
+ prev_mask: bool,
259
+ prev_green: bool,
260
+ ):
261
+ if PROCESS_CANCELLED.is_set():
262
+ # if user cancelled previously, reset it so a new run can proceed
263
+ PROCESS_CANCELLED.clear()
264
+ custom_path = None
265
+ if isinstance(custom_file, dict) and custom_file.get("name"):
266
+ custom_path = custom_file["name"]
267
+ return process_video_fixed(
268
+ video_path=vid,
269
+ background_choice=style,
270
+ custom_background_path=custom_path,
271
+ progress_callback=None,
272
+ use_two_stage=use_two,
273
+ chroma_preset=chroma,
274
+ preview_mask=prev_mask,
275
+ preview_greenscreen=prev_green,
276
+ )
277
+
278
+ # Cancel processing
279
+ def _cb_cancel() -> str:
280
+ try:
281
+ PROCESS_CANCELLED.set()
282
+ return "Cancellation requested."
283
+ except Exception as e:
284
+ return f"Cancel failed: {e}"
285
+
286
+ # Refresh status
287
+ def _cb_status() -> Tuple[Dict[str, Any], Dict[str, Any]]:
288
+ try:
289
+ return get_model_status(), get_cache_status()
290
+ except Exception as e:
291
+ return {"error": str(e)}, {"error": str(e)}
292
+
293
+ # Clear
294
+ def _cb_clear():
295
+ return None, "", None, ""
296
+
297
+ # AI background generation
298
+ def _cb_generate_bg(prompt_text: str, w: int, h: int, b: float, v: float, c: float):
299
+ img, path = generate_ai_background(prompt_text, width=int(w), height=int(h), bokeh=b, vignette=v, contrast=c)
300
+ return img, path
301
+
302
+ # Use AI gen as custom
303
+ def _cb_use_gen_bg(path_text: str):
304
+ # The Quick Start tab expects a "file" object. We can simply echo the path
305
+ # and let the "Process" callback read it if provided.
306
+ return {"name": path_text, "size": os.path.getsize(path_text)} if path_text and os.path.exists(path_text) else None
307
+
308
+ # Wire events
309
+ btn_load.click(_cb_load_models, outputs=statusbox)
310
+ btn_run.click(
311
+ _cb_process,
312
+ inputs=[video, bg_style, custom_bg, use_two_stage, chroma_preset, preview_mask, preview_greenscreen],
313
+ outputs=[out_video, statusbox],
314
  )
315
+ btn_cancel.click(_cb_cancel, outputs=statusbox)
316
+ btn_refresh.click(_cb_status, outputs=[model_status, cache_status])
317
+ btn_clear.click(_cb_clear, outputs=[out_video, statusbox, gen_preview, gen_path])
318
 
319
+ btn_gen_bg.click(
320
+ _cb_generate_bg,
321
+ inputs=[prompt, gen_width, gen_height, bokeh, vignette, contrast],
322
+ outputs=[gen_preview, gen_path],
323
+ )
324
+ use_gen_as_custom.click(
325
+ _cb_use_gen_bg,
326
+ inputs=[gen_path],
327
+ outputs=[custom_bg],
328
  )
329
 
330
  return demo