MogensR commited on
Commit
7c2f655
Β·
1 Parent(s): 9a9dc8f

Update ui/ui_components.py

Browse files
Files changed (1) hide show
  1. ui/ui_components.py +346 -0
ui/ui_components.py CHANGED
@@ -0,0 +1,346 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ UI Components for BackgroundFX Pro (forced Two-Stage)
4
+ -----------------------------------------------------
5
+ * Pure layout (tiny wrapper to set env for quality)
6
+ * All heavy logic stays in ui/callbacks.py
7
+ * Two-stage mode is always active (checkbox removed)
8
+ """
9
+
10
+ from __future__ import annotations
11
+ import os
12
+ import gradio as gr
13
+
14
+ from ui.callbacks import (
15
+ cb_load_models,
16
+ cb_process_video,
17
+ cb_cancel,
18
+ cb_status,
19
+ cb_clear,
20
+ cb_generate_bg,
21
+ cb_use_gen_bg,
22
+ cb_preset_bg_preview,
23
+ )
24
+
25
+ # Typography & UI polish: sharper text + cleaner cards
26
+ CSS = """
27
+ :root {
28
+ --radius: 16px;
29
+ --font-sans: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto,
30
+ 'Helvetica Neue', Arial, sans-serif;
31
+ }
32
+
33
+ /* Global crisp text */
34
+ html, body, .gradio-container, .gradio-container * {
35
+ font-family: var(--font-sans) !important;
36
+ -webkit-font-smoothing: antialiased !important;
37
+ -moz-osx-font-smoothing: grayscale !important;
38
+ text-rendering: optimizeLegibility !important;
39
+ font-synthesis-weight: none;
40
+ }
41
+
42
+ /* Headings tighter & bolder */
43
+ .gradio-container h1, .gradio-container h2, .gradio-container h3 {
44
+ letter-spacing: -0.01em;
45
+ font-weight: 700;
46
+ }
47
+
48
+ /* Body copy slightly tighter */
49
+ #hero .prose, .gr-markdown, .gr-text {
50
+ letter-spacing: -0.003em;
51
+ }
52
+
53
+ /* Card look */
54
+ .card {
55
+ border-radius: var(--radius);
56
+ border: 1px solid rgba(0,0,0,.08);
57
+ padding: 16px;
58
+ background: linear-gradient(180deg, rgba(255,255,255,.94), rgba(248,250,252,.94));
59
+ box-shadow: 0 10px 30px rgba(0,0,0,.06);
60
+ }
61
+
62
+ .footer-note { opacity: 0.7; font-size: 12px; }
63
+ .sm { font-size: 13px; opacity: 0.85; }
64
+ #statusbox { min-height: 120px; }
65
+ .preview-img { border-radius: var(--radius); border: 1px solid rgba(0,0,0,.08); }
66
+
67
+ /* Buttons get a tiny weight bump for clarity */
68
+ button, .gr-button { font-weight: 600; }
69
+
70
+ /* Inline Quality select between buttons */
71
+ .inline-quality .wrap-inner { min-width: 170px; }
72
+ """
73
+
74
+ # Keep in sync with utils/cv_processing.PROFESSIONAL_BACKGROUNDS
75
+ _BG_CHOICES = [
76
+ "minimalist",
77
+ "office_modern",
78
+ "studio_blue",
79
+ "studio_green",
80
+ "warm_gradient",
81
+ "tech_dark",
82
+ ]
83
+ PRO_IMAGE_CHOICES = ["minimalist", "office_modern", "studio_blue", "studio_green"]
84
+ GRADIENT_COLOR_CHOICES = ["warm_gradient", "tech_dark"]
85
+
86
+
87
+ def create_interface() -> gr.Blocks:
88
+ with gr.Blocks(
89
+ title="🎬 BackgroundFX Pro",
90
+ css=CSS,
91
+ analytics_enabled=False,
92
+ theme=gr.themes.Soft()
93
+ ) as demo:
94
+
95
+ # ------------------------------------------------------------------
96
+ # HERO
97
+ # ------------------------------------------------------------------
98
+ with gr.Row(elem_id="hero"):
99
+ gr.Markdown(
100
+ "## 🎬 BackgroundFX Pro (CSP-Safe)\n"
101
+ "Replace your video background with cinema-quality AI matting. "
102
+ "Built for Hugging Face Spaces CSP.\n\n"
103
+ "_Tip: press **Load Models** once after the Space spins up._"
104
+ )
105
+
106
+ # ------------------------------------------------------------------
107
+ # TAB – Quick Start
108
+ # ------------------------------------------------------------------
109
+ with gr.Tab("🏁 Quick Start"):
110
+
111
+ with gr.Row():
112
+ # ── Left column ────────────────────────────────────────────
113
+ with gr.Column(scale=1):
114
+ video = gr.Video(label="Upload Video", interactive=True)
115
+
116
+ # Hidden: effective preset key (still used by callbacks / defaults)
117
+ bg_style = gr.Dropdown(
118
+ label="Background Style (hidden)",
119
+ choices=_BG_CHOICES,
120
+ value="minimalist",
121
+ visible=False,
122
+ )
123
+
124
+ # =======================
125
+ # Background Source block
126
+ # =======================
127
+ gr.Markdown("### πŸ–ΌοΈ Background Source")
128
+
129
+ bg_method = gr.Radio(
130
+ label="Choose method",
131
+ choices=[
132
+ "Upload file",
133
+ "Pre-loaded professional images",
134
+ "Pre-loaded Gradients / Colors",
135
+ "AI generated background",
136
+ ],
137
+ value="Upload file",
138
+ )
139
+
140
+ # a) Upload file option
141
+ with gr.Group(visible=True) as grp_upload:
142
+ custom_bg = gr.Image(
143
+ label="Upload Background Image",
144
+ interactive=True,
145
+ type="filepath", # returns file path
146
+ elem_classes=["preview-img"]
147
+ )
148
+
149
+ # b) Pre-loaded professional images
150
+ with gr.Group(visible=False) as grp_pro_images:
151
+ pro_image_dd = gr.Dropdown(
152
+ label="Professional Images",
153
+ choices=PRO_IMAGE_CHOICES,
154
+ value=PRO_IMAGE_CHOICES[0],
155
+ info="Pre-defined photo-like backgrounds",
156
+ )
157
+ gr.Markdown(
158
+ "<span class='sm'>Selecting a preset updates the preview below.</span>"
159
+ )
160
+
161
+ # c) Pre-loaded gradients & full colors
162
+ with gr.Group(visible=False) as grp_gradients:
163
+ gradient_dd = gr.Dropdown(
164
+ label="Gradients & Full Colors",
165
+ choices=GRADIENT_COLOR_CHOICES,
166
+ value=GRADIENT_COLOR_CHOICES[0],
167
+ info="Clean gradients or solid color styles",
168
+ )
169
+ gr.Markdown(
170
+ "<span class='sm'>Selecting a preset updates the preview below.</span>"
171
+ )
172
+
173
+ # d) AI-generated background (inline, lightweight)
174
+ with gr.Group(visible=False) as grp_ai:
175
+ prompt = gr.Textbox(
176
+ label="Describe vibe",
177
+ value="modern office",
178
+ info="e.g. 'soft sunset studio', 'cool tech dark', 'forest ambience'"
179
+ )
180
+ with gr.Row():
181
+ gen_width = gr.Slider(640, 1920, 1280, step=10, label="Width")
182
+ gen_height = gr.Slider(360, 1080, 720, step=10, label="Height")
183
+ with gr.Row():
184
+ bokeh = gr.Slider(0, 30, 8, step=1, label="Bokeh Blur")
185
+ vignette = gr.Slider(0, 0.6, 0.15, step=0.01, label="Vignette")
186
+ contrast = gr.Slider(0.8, 1.4, 1.05, step=0.01, label="Contrast")
187
+ with gr.Row():
188
+ btn_gen_bg_inline = gr.Button("✨ Generate Background", variant="primary")
189
+ use_gen_as_custom_inline = gr.Button("πŸ“Œ Use as Custom Background", variant="secondary")
190
+ gen_preview = gr.Image(
191
+ label="Generated Background",
192
+ interactive=False,
193
+ elem_classes=["preview-img"]
194
+ )
195
+ gen_path = gr.Textbox(label="Saved Path", interactive=False)
196
+
197
+ # ── Advanced options accordion ───────────────────────
198
+ with gr.Accordion("Advanced", open=False):
199
+ chroma_preset = gr.Dropdown(
200
+ label="Chroma Preset",
201
+ choices=["standard"], # can add 'studio', 'outdoor' later
202
+ value="standard"
203
+ )
204
+ key_color_mode = gr.Dropdown(
205
+ label="Key-Colour Mode",
206
+ choices=["auto", "green", "blue", "cyan", "magenta"],
207
+ value="auto",
208
+ info="Auto picks a colour far from your clothes; override if needed."
209
+ )
210
+ preview_mask = gr.Checkbox(
211
+ label="Preview Mask only (mute audio)",
212
+ value=False
213
+ )
214
+ preview_greenscreen = gr.Checkbox(
215
+ label="Preview Green-screen only (mute audio)",
216
+ value=False
217
+ )
218
+
219
+ # ── Controls row: Load β†’ Quality β†’ Process / Cancel ──
220
+ with gr.Row():
221
+ btn_load = gr.Button("πŸ”„ Load Models", variant="secondary")
222
+
223
+ quality = gr.Dropdown(
224
+ label="Quality",
225
+ choices=["speed", "balanced", "max"],
226
+ value=os.getenv("BFX_QUALITY", "balanced"),
227
+ info="Speed = fastest; Max = best edges & spill control.",
228
+ elem_classes=["inline-quality"],
229
+ )
230
+
231
+ btn_run = gr.Button("🎬 Process Video", variant="primary")
232
+ btn_cancel = gr.Button("⏹️ Cancel", variant="secondary")
233
+
234
+ # ── Right column ──────────────────────────────────────────
235
+ with gr.Column(scale=1):
236
+ out_video = gr.Video(label="Processed Output", interactive=False)
237
+ statusbox = gr.Textbox(label="Status", lines=8, elem_id="statusbox")
238
+ with gr.Row():
239
+ btn_refresh = gr.Button("πŸ” Refresh Status", variant="secondary")
240
+ btn_clear = gr.Button("🧹 Clear", variant="secondary")
241
+
242
+ # ------------------------------------------------------------------
243
+ # TAB – Status & settings
244
+ # ------------------------------------------------------------------
245
+ with gr.Tab("πŸ“ˆ Status & Settings"):
246
+ with gr.Row():
247
+ with gr.Column(scale=1, elem_classes=["card"]):
248
+ model_status = gr.JSON(label="Model Status")
249
+ with gr.Column(scale=1, elem_classes=["card"]):
250
+ cache_status = gr.JSON(label="Cache / System Status")
251
+
252
+ gr.Markdown(
253
+ "<div class='footer-note'>If models fail to load, fallbacks keep the UI responsive. "
254
+ "Check the runtime log for details.</div>"
255
+ )
256
+
257
+ # ------------------------------------------------------------------
258
+ # Callback wiring
259
+ # ------------------------------------------------------------------
260
+
261
+ # Toggle which background sub-section is visible
262
+ def _toggle_bg_sections(choice: str):
263
+ return (
264
+ gr.update(visible=(choice == "Upload file")),
265
+ gr.update(visible=(choice == "Pre-loaded professional images")),
266
+ gr.update(visible=(choice == "Pre-loaded Gradients / Colors")),
267
+ gr.update(visible=(choice == "AI generated background")),
268
+ )
269
+
270
+ bg_method.change(
271
+ _toggle_bg_sections,
272
+ inputs=[bg_method],
273
+ outputs=[grp_upload, grp_pro_images, grp_gradients, grp_ai],
274
+ )
275
+
276
+ # Load models
277
+ btn_load.click(cb_load_models, outputs=statusbox)
278
+
279
+ # Tiny wrapper to set env for quality before calling the existing callback
280
+ def _run_with_quality(video_pth, bg_style_val, custom_bg_pth,
281
+ use_two_stage_state, chroma_p, key_mode,
282
+ prev_mask, prev_gs, quality_val):
283
+ os.environ["BFX_QUALITY"] = (quality_val or "balanced")
284
+ return cb_process_video(
285
+ video_pth, bg_style_val, custom_bg_pth,
286
+ use_two_stage_state, chroma_p, key_mode, prev_mask, prev_gs
287
+ )
288
+
289
+ # Always two-stage: pass use_two_stage=True to callback via State
290
+ btn_run.click(
291
+ _run_with_quality,
292
+ inputs=[
293
+ video,
294
+ bg_style,
295
+ custom_bg,
296
+ gr.State(value=True), # Always two-stage
297
+ chroma_preset,
298
+ key_color_mode,
299
+ preview_mask,
300
+ preview_greenscreen,
301
+ quality, # <-- the new control
302
+ ],
303
+ outputs=[out_video, statusbox],
304
+ )
305
+
306
+ # Cancel / Status / Clear
307
+ btn_cancel.click(cb_cancel, outputs=statusbox)
308
+ btn_refresh.click(cb_status, outputs=[model_status, cache_status])
309
+
310
+ btn_clear.click(
311
+ cb_clear,
312
+ outputs=[out_video, statusbox, gen_preview, gen_path, custom_bg]
313
+ )
314
+
315
+ # Preloaded presets β†’ update preview (write into custom_bg)
316
+ pro_image_dd.change(
317
+ cb_preset_bg_preview,
318
+ inputs=[pro_image_dd],
319
+ outputs=[custom_bg],
320
+ )
321
+ gradient_dd.change(
322
+ cb_preset_bg_preview,
323
+ inputs=[gradient_dd],
324
+ outputs=[custom_bg],
325
+ )
326
+
327
+ # AI background generation (inline)
328
+ btn_gen_bg_inline.click(
329
+ cb_generate_bg,
330
+ inputs=[prompt, gen_width, gen_height, bokeh, vignette, contrast],
331
+ outputs=[gen_preview, gen_path],
332
+ )
333
+ use_gen_as_custom_inline.click(
334
+ cb_use_gen_bg,
335
+ inputs=[gen_path],
336
+ outputs=[custom_bg],
337
+ )
338
+
339
+ # Initialize with a default preset preview on load
340
+ demo.load(
341
+ cb_preset_bg_preview,
342
+ inputs=[bg_style],
343
+ outputs=[custom_bg]
344
+ )
345
+
346
+ return demo