|
|
|
|
|
""" |
|
|
UI Components for BackgroundFX Pro - with Start/Stop, frame counter, and live FPS |
|
|
""" |
|
|
|
|
|
import gradio as gr |
|
|
import time |
|
|
import threading |
|
|
|
|
|
|
|
|
try: |
|
|
from core.app import ( |
|
|
processor, |
|
|
load_models_with_validation, |
|
|
process_video_fixed, |
|
|
get_model_status, |
|
|
get_cache_status, |
|
|
) |
|
|
CORE_FUNCTIONS_AVAILABLE = True |
|
|
except Exception as e: |
|
|
print(f"[UI] Core functions import failed: {e}") |
|
|
CORE_FUNCTIONS_AVAILABLE = False |
|
|
|
|
|
try: |
|
|
from utils.backgrounds import PROFESSIONAL_BACKGROUNDS |
|
|
UTILITIES_AVAILABLE = True |
|
|
except Exception as e: |
|
|
PROFESSIONAL_BACKGROUNDS = {"office_modern": {"name": "Modern Office", "description": "Default office background"}} |
|
|
UTILITIES_AVAILABLE = False |
|
|
|
|
|
|
|
|
stop_event = threading.Event() |
|
|
|
|
|
def create_interface(): |
|
|
is_processing = gr.State(False) |
|
|
frame_progress = gr.State({"current": 0, "total": 0, "fps": 0.0}) |
|
|
|
|
|
def enhanced_process_video( |
|
|
video_path, bg_method, custom_img, prof_choice, |
|
|
use_two_stage, chroma_preset, quality_preset, |
|
|
is_processing_state, frame_progress_state, progress=gr.Progress() |
|
|
): |
|
|
""" |
|
|
Handles video processing (start/stop) and live frame/fps reporting. |
|
|
""" |
|
|
|
|
|
if is_processing_state: |
|
|
stop_event.set() |
|
|
return None, "Processing stopped by user.", "Processing stopped.", False, {"current": 0, "total": 0, "fps": 0.0} |
|
|
|
|
|
|
|
|
stop_event.clear() |
|
|
is_processing_state = True |
|
|
frame_progress_state = {"current": 0, "total": 0, "fps": 0.0} |
|
|
last_update = time.time() |
|
|
last_frame = 0 |
|
|
|
|
|
if not CORE_FUNCTIONS_AVAILABLE: |
|
|
return None, "Core backend not available.", "System error.", False, frame_progress_state |
|
|
if not processor.models_loaded: |
|
|
return None, "Models not loaded.", "Load models first.", False, frame_progress_state |
|
|
if not video_path: |
|
|
return None, "No video uploaded.", "Upload a video.", False, frame_progress_state |
|
|
|
|
|
|
|
|
if bg_method == "professional" and not prof_choice: |
|
|
return None, "No professional background selected.", "Choose a background.", False, frame_progress_state |
|
|
if bg_method == "upload" and not custom_img: |
|
|
return None, "No custom background image.", "Upload a background image.", False, frame_progress_state |
|
|
|
|
|
try: |
|
|
def progress_callback(pct, desc, current_frame=None, total_frames=None): |
|
|
|
|
|
if stop_event.is_set(): |
|
|
raise Exception("Processing stopped by user.") |
|
|
|
|
|
|
|
|
if progress: |
|
|
progress(pct, desc) |
|
|
|
|
|
|
|
|
nonlocal last_update, last_frame, frame_progress_state |
|
|
now = time.time() |
|
|
fps = 0.0 |
|
|
if current_frame is not None and total_frames is not None: |
|
|
dt = now - last_update if last_update else 0.01 |
|
|
frames_done = current_frame - last_frame if last_frame is not None else 0 |
|
|
fps = frames_done / dt if dt > 0 else 0.0 |
|
|
last_update = now |
|
|
last_frame = current_frame |
|
|
frame_progress_state = { |
|
|
"current": int(current_frame), |
|
|
"total": int(total_frames), |
|
|
"fps": round(fps, 2) |
|
|
} |
|
|
return desc |
|
|
|
|
|
result_path, result_message = process_video_fixed( |
|
|
video_path=video_path, |
|
|
background_choice=prof_choice if bg_method == "professional" else "custom", |
|
|
custom_background_path=custom_img if bg_method == "upload" else None, |
|
|
progress_callback=lambda pct, desc: progress_callback(pct, desc), |
|
|
use_two_stage=bool(use_two_stage), |
|
|
chroma_preset=chroma_preset or "standard", |
|
|
preview_mask=False, |
|
|
preview_greenscreen=False, |
|
|
stop_event=stop_event, |
|
|
) |
|
|
is_processing_state = False |
|
|
return result_path, result_message, "Processing completed.", False, frame_progress_state |
|
|
|
|
|
except Exception as e: |
|
|
is_processing_state = False |
|
|
return None, f"Error: {str(e)}", "Error during processing.", False, frame_progress_state |
|
|
|
|
|
def handle_model_loading(progress=gr.Progress()): |
|
|
""" |
|
|
Wrapper function for model loading with proper progress handling |
|
|
""" |
|
|
try: |
|
|
|
|
|
result = load_models_with_validation(progress) |
|
|
return result |
|
|
except Exception as e: |
|
|
return f"Model loading failed: {str(e)}" |
|
|
|
|
|
|
|
|
with gr.Blocks( |
|
|
title="BackgroundFX Pro", |
|
|
css=""" |
|
|
.main-header { text-align: center; margin-bottom: 20px; } |
|
|
.status-box { background: #f8f9fa; padding: 15px; border-radius: 8px; margin: 10px 0; } |
|
|
.live-progress { font-size: 1.1em; font-weight: bold; color: #004085; } |
|
|
""" |
|
|
) as demo: |
|
|
with gr.Row(): |
|
|
gr.Markdown("# BackgroundFX Pro - Video Background Replacement", elem_classes=["main-header"]) |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(): |
|
|
video_input = gr.Video(label="Upload Video", height=280) |
|
|
background_method = gr.Radio( |
|
|
["professional", "upload"], value="professional", label="Background Method" |
|
|
) |
|
|
professional_choice = gr.Dropdown( |
|
|
choices=[(bg['name'], key) for key, bg in PROFESSIONAL_BACKGROUNDS.items()], |
|
|
value=list(PROFESSIONAL_BACKGROUNDS.keys())[0], |
|
|
label="Professional Background" |
|
|
) |
|
|
custom_background = gr.Image(label="Upload Background", type="filepath", visible=False) |
|
|
quality_preset = gr.Dropdown( |
|
|
choices=[ |
|
|
("Fast", "fast"), |
|
|
("Balanced", "balanced"), |
|
|
("High Quality", "high") |
|
|
], |
|
|
value="balanced", |
|
|
label="Quality Preset" |
|
|
) |
|
|
use_two_stage = gr.Checkbox("Enable Two-Stage Mode", value=False) |
|
|
chroma_preset = gr.Dropdown( |
|
|
choices=[("Standard", "standard"), ("Studio", "studio"), ("Outdoor", "outdoor")], |
|
|
value="standard", |
|
|
label="Chroma Key Preset" |
|
|
) |
|
|
|
|
|
def update_background_visibility(method): |
|
|
return ( |
|
|
gr.update(visible=(method == "professional")), |
|
|
gr.update(visible=(method == "upload")) |
|
|
) |
|
|
background_method.change( |
|
|
fn=update_background_visibility, |
|
|
inputs=background_method, |
|
|
outputs=[professional_choice, custom_background] |
|
|
) |
|
|
with gr.Row(): |
|
|
load_models_btn = gr.Button("Load Models", variant="secondary") |
|
|
process_btn = gr.Button("Process Video", variant="primary") |
|
|
|
|
|
status_text = gr.Textbox(label="Status", value="Ready", interactive=False) |
|
|
frame_info = gr.Textbox(label="Frame Progress", value="", interactive=False) |
|
|
fps_info = gr.Textbox(label="Frames/sec", value="", interactive=False) |
|
|
|
|
|
with gr.Column(): |
|
|
video_output = gr.Video(label="Processed Video", height=360) |
|
|
result_info = gr.Textbox(label="Processing Info", lines=8, interactive=False) |
|
|
debug_info = gr.Textbox(label="Debug Log", lines=6, interactive=False, visible=False) |
|
|
|
|
|
|
|
|
def update_process_btn(is_processing): |
|
|
return gr.update(value="Stop Processing" if is_processing else "Process Video") |
|
|
is_processing.change(update_process_btn, inputs=is_processing, outputs=process_btn) |
|
|
|
|
|
def update_frame_fields(frame_progress): |
|
|
total = frame_progress.get("total", 0) |
|
|
current = frame_progress.get("current", 0) |
|
|
fps = frame_progress.get("fps", 0.0) |
|
|
txt = f"{current}/{total}" if total else "" |
|
|
return txt, f"{fps:.2f} FPS" if fps else "" |
|
|
frame_progress.change(update_frame_fields, inputs=frame_progress, outputs=[frame_info, fps_info]) |
|
|
|
|
|
process_btn.click( |
|
|
fn=enhanced_process_video, |
|
|
inputs=[ |
|
|
video_input, |
|
|
background_method, |
|
|
custom_background, |
|
|
professional_choice, |
|
|
use_two_stage, |
|
|
chroma_preset, |
|
|
quality_preset, |
|
|
is_processing, |
|
|
frame_progress |
|
|
], |
|
|
outputs=[video_output, result_info, debug_info, is_processing, frame_progress], |
|
|
show_progress=True |
|
|
) |
|
|
|
|
|
|
|
|
load_models_btn.click( |
|
|
fn=handle_model_loading, |
|
|
outputs=[status_text], |
|
|
show_progress=True |
|
|
) |
|
|
|
|
|
return demo |
|
|
|
|
|
def create_ui(): |
|
|
return create_interface() |