File size: 7,456 Bytes
58835e7
 
6426727
 
 
58835e7
 
2449cff
 
6426727
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52c1994
6426727
 
 
 
 
 
58835e7
6426727
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58835e7
6426727
 
 
 
 
 
 
2449cff
6426727
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#!/usr/bin/env python3
"""
BackgroundFX Pro - CSP-Safe Application Entry Point
Built for Hugging Face Spaces (strict Content Security Policy)
No inline JavaScript, CSP-compliant Gradio, safe environment vars, fallback AI models
"""

import early_env  # <<< must be FIRST to sanitize threading/env before anything else

import os
import logging
from pathlib import Path
from typing import Optional, Tuple, Dict, Any, Callable

# 1️⃣ Set CSP-safe environment variables BEFORE any imports (Gradio will see these)
os.environ['GRADIO_ALLOW_FLAGGING'] = 'never'
os.environ['GRADIO_ANALYTICS_ENABLED'] = 'False'
os.environ['GRADIO_SERVER_NAME'] = '0.0.0.0'
os.environ['GRADIO_SERVER_PORT'] = '7860'

# 2️⃣ Patch Gradio schema early for Hugging Face bug compatibility
try:
    import gradio_client.utils as gc_utils
    orig_get_type = gc_utils.get_type
    def patched_get_type(schema):
        if not isinstance(schema, dict):
            if isinstance(schema, bool):
                return "boolean"
            if isinstance(schema, str):
                return "string"
            if isinstance(schema, (int, float)):
                return "number"
            return "string"
        return orig_get_type(schema)
    gc_utils.get_type = patched_get_type
except Exception:
    pass  # No fatal error if Gradio patch fails

# 3️⃣ Set up logging early for debugging
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(name)s - %(message)s"
)
logger = logging.getLogger("BackgroundFX")

# 4️⃣ Import your modular code (assuming your project structure)
from core.exceptions import ModelLoadingError, VideoProcessingError
from config.app_config import get_config
from utils.hardware.device_manager import DeviceManager
from utils.system.memory_manager import MemoryManager
from models.loaders.model_loader import ModelLoader
from processing.video.video_processor import CoreVideoProcessor, ProcessorConfig
from processing.audio.audio_processor import AudioProcessor
from utils.cv_processing import PROFESSIONAL_BACKGROUNDS, validate_video_file
# If you have TwoStage: from processing.two_stage.two_stage_processor import TwoStageProcessor, CHROMA_PRESETS

# 5️⃣ CSP-safe fallback model stubs
class CSPSafeSAM2:
    def set_image(self, image):
        self.shape = getattr(image, 'shape', (512, 512, 3))
    def predict(self, point_coords=None, point_labels=None, box=None, multimask_output=True, **kwargs):
        import numpy as np
        h, w = self.shape[:2] if hasattr(self, 'shape') else (512, 512)
        n = 3 if multimask_output else 1
        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)

class CSPSafeMatAnyone:
    def step(self, image_tensor, mask_tensor=None, objects=None, first_frame_pred=False):
        import torch
        shape = getattr(image_tensor, 'shape', (1, 3, 256, 256))
        return torch.ones((shape[0], 1, shape[2], shape[3]))
    def output_prob_to_mask(self, output_prob):
        return (output_prob > 0.5).float()
    def process(self, image, mask):
        return mask

# 6️⃣ Application main processor object
class VideoBackgroundApp:
    def __init__(self):
        self.config = get_config()
        self.device_mgr = DeviceManager()
        self.memory_mgr = MemoryManager(self.device_mgr.get_optimal_device())
        self.model_loader = ModelLoader(self.device_mgr, self.memory_mgr)
        self.audio_proc = AudioProcessor()
        self.models_loaded = False
        self.core_processor = None

    def load_models(self, progress_callback: Optional[Callable]=None) -> str:
        logger.info("Loading models (CSP-safe)...")
        try:
            sam2, matanyone = self.model_loader.load_all_models(progress_callback=progress_callback)
        except Exception as e:
            logger.warning(f"Model loading failed ({e}) - Using CSP-safe fallbacks")
            sam2, matanyone = None, None
        sam2_model = getattr(sam2, "model", sam2) if sam2 else CSPSafeSAM2()
        matanyone_model = getattr(matanyone, "model", matanyone) if matanyone else CSPSafeMatAnyone()
        self.core_processor = CoreVideoProcessor(config=ProcessorConfig(), models=None)
        self.core_processor.models = type('FakeModelManager', (), {
            'get_sam2': lambda self: sam2_model,
            'get_matanyone': lambda self: matanyone_model
        })()
        self.models_loaded = True
        return "Models loaded (CSP-safe, fallback mode if no AI models loaded)."

    def process_video(self, video, bg_style, custom_bg_file):
        if not self.models_loaded:
            return None, "Models not loaded yet"
        import time
        output_path = f"/tmp/output_{int(time.time())}.mp4"
        cfg = PROFESSIONAL_BACKGROUNDS.get(bg_style, PROFESSIONAL_BACKGROUNDS["minimalist"])
        if custom_bg_file:
            cfg = {"custom_path": custom_bg_file.name}
        ok, msg = validate_video_file(video)
        if not ok:
            return None, f"Invalid video: {msg}"
        try:
            result = self.core_processor.process_video(
                input_path=video,
                output_path=output_path,
                bg_config=cfg
            )
            output_with_audio = self.audio_proc.add_audio_to_video(video, output_path)
            return output_with_audio, f"Processing complete ({result.get('frames', 'n/a')} frames, {bg_style})"
        except Exception as e:
            return None, f"Processing failed: {e}"

# 7️⃣ Gradio interface CSP-safe
def create_csp_safe_gradio():
    import gradio as gr
    app = VideoBackgroundApp()
    with gr.Blocks(
        title="BackgroundFX Pro - CSP Safe",
        analytics_enabled=False,
        css="""
        .gradio-container { max-width: 1000px; margin: auto; }
        """
    ) as demo:
        gr.Markdown("# 🎬 BackgroundFX Pro (CSP-Safe)")
        gr.Markdown("Replace your video background with cinema-quality AI matting. Built for Hugging Face Spaces CSP.")

        with gr.Row():
            with gr.Column():
                video = gr.Video(label="Upload Video")
                bg_style = gr.Dropdown(
                    choices=list(PROFESSIONAL_BACKGROUNDS.keys()),
                    value="minimalist",
                    label="Background Style"
                )
                custom_bg = gr.File(label="Custom Background (Optional)", file_types=["image"])
                btn_load = gr.Button("🔄 Load Models", variant="secondary")
                btn_run = gr.Button("🎬 Process Video", variant="primary")

            with gr.Column():
                status = gr.Textbox(label="Status", lines=4)
                out_video = gr.Video(label="Processed Video")

        def safe_load():
            return app.load_models()
        def safe_process(vid, style, custom_bg_file):
            return app.process_video(vid, style, custom_bg_file)
        btn_load.click(fn=safe_load, outputs=[status])
        btn_run.click(fn=safe_process, inputs=[video, bg_style, custom_bg], outputs=[out_video, status])

    return demo

# 8️⃣ Main entry point (for Hugging Face Spaces)
if __name__ == "__main__":
    logger.info("Launching CSP-safe Gradio interface for Hugging Face Spaces")
    demo = create_csp_safe_gradio()
    demo.queue().launch(
        server_name="0.0.0.0",
        server_port=7860,
        show_error=True,
        debug=False,
        inbrowser=False
    )