"""4K Upscaler Pro — Premium AI-powered upscaler with Lumina Synth design.""" # ── Monkey-patch gradio_client to fix bool schema crash ───────────── # Gradio 5.x generates JSON schemas with `additionalProperties: False` # (a boolean). The gradio_client parser tries `"const" in schema` on it, # which crashes with TypeError because you can't do `"x" in False`. import gradio_client.utils as _gc_utils _original_json_schema_to_python_type = _gc_utils._json_schema_to_python_type def _patched_json_schema_to_python_type(schema, defs=None): """Safe wrapper that handles bool schemas.""" if isinstance(schema, bool): return "Any" # Guard against additionalProperties being bool if isinstance(schema, dict) and "additionalProperties" in schema: if isinstance(schema["additionalProperties"], bool): schema = {k: v for k, v in schema.items() if k != "additionalProperties"} return _original_json_schema_to_python_type(schema, defs) _gc_utils._json_schema_to_python_type = _patched_json_schema_to_python_type _original_get_type = _gc_utils.get_type def _patched_get_type(schema): """Safe wrapper that handles bool schemas in get_type.""" if isinstance(schema, bool): return "Any" if not isinstance(schema, dict): return "Any" return _original_get_type(schema) _gc_utils.get_type = _patched_get_type # ── End monkey-patch ──────────────────────────────────────────────── import gradio as gr from core import upscale_image, upscale_video # ───────────────────────────────────────────────────────────────────── # Lumina Synth Design System — Generated by Stitch # ───────────────────────────────────────────────────────────────────── CSS = """ @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Inter:wght@300;400;500;600;700&display=swap'); /* ── Design Tokens ── */ :root { --bg: #0e0e13; --surface: #131319; --surface-high: #1f1f26; --surface-highest: #25252d; --surface-bright: #2c2b33; --primary: #ba9eff; --primary-dim: #8455ef; --secondary: #53ddfc; --secondary-dim: #40ceed; --tertiary: #ec63ff; --on-surface: #f9f5fd; --on-surface-variant: #acaab1; --outline: #76747b; --outline-variant: #48474d; --error: #ff6e84; --gradient-primary: linear-gradient(135deg, #8455ef, #53ddfc); --gradient-glow: linear-gradient(135deg, rgba(132,85,239,0.4), rgba(83,221,252,0.4)); --glass-bg: rgba(19, 19, 25, 0.75); --glass-border: rgba(72, 71, 77, 0.25); --radius-md: 16px; --radius-lg: 24px; --radius-full: 9999px; --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } /* ── Global Reset ── */ *, *::before, *::after { box-sizing: border-box; } body, .gradio-container { background: var(--bg) !important; font-family: 'Inter', -apple-system, sans-serif !important; color: var(--on-surface) !important; min-height: 100vh; } .gradio-container { max-width: 1400px !important; margin: 0 auto !important; padding: 0 24px !important; } /* ── Ambient Background Glow ── */ .gradio-container::before { content: ''; position: fixed; top: -50%; left: -50%; width: 200%; height: 200%; background: radial-gradient(ellipse at 30% 20%, rgba(132,85,239,0.08) 0%, transparent 50%), radial-gradient(ellipse at 70% 80%, rgba(83,221,252,0.05) 0%, transparent 50%); pointer-events: none; z-index: 0; } /* ── Hero Title ── */ .hero-title { text-align: center; padding: 48px 0 8px; } .hero-title h1 { font-family: 'Space Grotesk', sans-serif !important; font-size: 3.2rem !important; font-weight: 700 !important; letter-spacing: -0.03em !important; background: var(--gradient-primary) !important; -webkit-background-clip: text !important; -webkit-text-fill-color: transparent !important; background-clip: text !important; line-height: 1.1 !important; margin: 0 !important; padding: 0 !important; } .hero-subtitle p { text-align: center !important; color: var(--on-surface-variant) !important; font-size: 1.05rem !important; font-weight: 400 !important; letter-spacing: 0.01em !important; margin-top: 8px !important; padding-bottom: 24px !important; } /* ── Badge ── */ .status-badge p { display: inline-block !important; background: rgba(132, 85, 239, 0.15) !important; border: 1px solid rgba(186, 158, 255, 0.2) !important; color: var(--primary) !important; font-family: 'Inter', sans-serif !important; font-size: 0.7rem !important; font-weight: 600 !important; text-transform: uppercase !important; letter-spacing: 0.08em !important; padding: 4px 14px !important; border-radius: var(--radius-full) !important; text-align: center !important; width: auto !important; margin: 0 auto !important; } /* ── Tabs — Glowing Indicators ── */ .tab-nav { display: flex !important; justify-content: center !important; gap: 4px !important; padding: 6px !important; background: var(--surface) !important; border-radius: var(--radius-full) !important; border: 1px solid var(--glass-border) !important; margin-bottom: 32px !important; width: fit-content !important; margin-left: auto !important; margin-right: auto !important; } .tab-nav button { font-family: 'Inter', sans-serif !important; font-size: 0.875rem !important; font-weight: 500 !important; color: var(--on-surface-variant) !important; background: transparent !important; border: none !important; padding: 10px 28px !important; border-radius: var(--radius-full) !important; cursor: pointer !important; transition: var(--transition) !important; position: relative !important; } .tab-nav button:hover { color: var(--on-surface) !important; background: rgba(132, 85, 239, 0.08) !important; } .tab-nav button.selected { background: var(--gradient-primary) !important; color: #fff !important; font-weight: 600 !important; box-shadow: 0 0 20px rgba(132, 85, 239, 0.4), 0 0 60px rgba(83, 221, 252, 0.15) !important; } /* ── Glass Panels ── */ .glass-panel { background: var(--glass-bg) !important; backdrop-filter: blur(24px) !important; -webkit-backdrop-filter: blur(24px) !important; border-radius: var(--radius-lg) !important; border: 1px solid var(--glass-border) !important; padding: 28px !important; transition: var(--transition) !important; position: relative !important; overflow: hidden !important; } .glass-panel::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 1px; background: linear-gradient(90deg, transparent, rgba(186,158,255,0.3), transparent); pointer-events: none; } .glass-panel:hover { border-color: rgba(132, 85, 239, 0.2) !important; box-shadow: 0 0 40px rgba(132, 85, 239, 0.06) !important; } /* ── Section Labels ── */ .section-label p { font-family: 'Inter', sans-serif !important; font-size: 0.7rem !important; font-weight: 600 !important; text-transform: uppercase !important; letter-spacing: 0.1em !important; color: var(--on-surface-variant) !important; margin-bottom: 12px !important; } /* ── Input Components ── */ .gradio-image, .gradio-video { border-radius: var(--radius-md) !important; overflow: hidden !important; } .gradio-image .image-container, .gradio-image .upload-container { background: var(--surface) !important; border: 1.5px dashed var(--outline-variant) !important; border-radius: var(--radius-md) !important; min-height: 280px !important; transition: var(--transition) !important; } .gradio-image .upload-container:hover { border-color: var(--secondary) !important; background: var(--surface-high) !important; box-shadow: 0 0 30px rgba(83, 221, 252, 0.08) !important; } /* ── Radio Buttons — Pill Style ── */ .gradio-radio label { background: var(--surface-high) !important; border: 1px solid transparent !important; border-radius: var(--radius-full) !important; padding: 8px 20px !important; color: var(--on-surface-variant) !important; font-size: 0.85rem !important; font-weight: 500 !important; cursor: pointer !important; transition: var(--transition) !important; } .gradio-radio label:hover { background: var(--surface-bright) !important; color: var(--on-surface) !important; } .gradio-radio label.selected { background: var(--primary-dim) !important; color: #fff !important; border-color: var(--primary) !important; box-shadow: 0 0 15px rgba(132, 85, 239, 0.3) !important; } /* ── Dropdown ── */ .gradio-dropdown { border-radius: var(--radius-md) !important; } .gradio-dropdown select, .gradio-dropdown input { background: var(--surface-high) !important; border: 1px solid var(--outline-variant) !important; border-radius: var(--radius-md) !important; color: var(--on-surface) !important; padding: 10px 16px !important; font-size: 0.875rem !important; transition: var(--transition) !important; } .gradio-dropdown select:focus, .gradio-dropdown input:focus { border-color: var(--primary) !important; box-shadow: 0 0 0 3px rgba(132, 85, 239, 0.15) !important; } /* ── Primary Button — Gradient with Glow ── */ .gr-button-primary, button.primary { background: var(--gradient-primary) !important; border: none !important; border-radius: var(--radius-full) !important; color: #fff !important; font-family: 'Inter', sans-serif !important; font-weight: 600 !important; font-size: 0.95rem !important; letter-spacing: 0.02em !important; padding: 14px 32px !important; cursor: pointer !important; transition: var(--transition) !important; position: relative !important; overflow: hidden !important; text-shadow: 0 1px 2px rgba(0,0,0,0.3) !important; } .gr-button-primary::before, button.primary::before { content: ''; position: absolute; inset: 0; background: linear-gradient(135deg, rgba(255,255,255,0.15), transparent); pointer-events: none; } .gr-button-primary:hover, button.primary:hover { transform: translateY(-1px) !important; box-shadow: 0 8px 30px rgba(132, 85, 239, 0.4), 0 0 60px rgba(83, 221, 252, 0.2) !important; } .gr-button-primary:active, button.primary:active { transform: translateY(0) !important; } /* ── Secondary Button ── */ .gr-button-secondary, button.secondary { background: transparent !important; border: 1px solid var(--outline-variant) !important; border-radius: var(--radius-full) !important; color: var(--secondary) !important; font-weight: 500 !important; padding: 12px 24px !important; transition: var(--transition) !important; } .gr-button-secondary:hover, button.secondary:hover { border-color: var(--secondary) !important; background: rgba(83, 221, 252, 0.08) !important; box-shadow: 0 0 20px rgba(83, 221, 252, 0.15) !important; } /* ── Info Textbox ── */ .gradio-textbox textarea, .gradio-textbox input { background: var(--surface) !important; border: 1px solid var(--outline-variant) !important; border-radius: var(--radius-md) !important; color: var(--on-surface) !important; font-family: 'Inter', monospace !important; font-size: 0.85rem !important; line-height: 1.6 !important; } /* ── File Download ── */ .gradio-file { border-radius: var(--radius-md) !important; } /* ── Progress Bar — Neon Gradient ── */ .progress-bar { background: var(--surface) !important; border-radius: var(--radius-full) !important; overflow: hidden !important; } .progress-bar > div { background: var(--gradient-primary) !important; box-shadow: 0 0 15px rgba(132, 85, 239, 0.5) !important; transition: width 0.4s ease !important; } /* Gradio's built-in progress */ .progress-level { background: var(--gradient-primary) !important; } .progress-level span { color: #fff !important; font-family: 'Inter', sans-serif !important; font-weight: 500 !important; } .meta-text span { color: var(--on-surface-variant) !important; font-size: 0.85rem !important; } /* ── Loading Shimmer Animation ── */ @keyframes shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } } @keyframes pulse-glow { 0%, 100% { box-shadow: 0 0 20px rgba(132, 85, 239, 0.2); } 50% { box-shadow: 0 0 40px rgba(132, 85, 239, 0.5), 0 0 80px rgba(83, 221, 252, 0.2); } } @keyframes spin { to { transform: rotate(360deg); } } .generating { animation: pulse-glow 2s ease-in-out infinite !important; } /* ── Scrollbar ── */ ::-webkit-scrollbar { width: 6px; } ::-webkit-scrollbar-track { background: var(--bg); } ::-webkit-scrollbar-thumb { background: var(--outline-variant); border-radius: 3px; } ::-webkit-scrollbar-thumb:hover { background: var(--outline); } /* ── Labels ── */ label, .label-wrap { color: var(--on-surface-variant) !important; font-weight: 500 !important; font-size: 0.85rem !important; } /* ── Markdown in API tab ── */ .prose { color: var(--on-surface) !important; } .prose h2, .prose h3 { font-family: 'Space Grotesk', sans-serif !important; color: var(--on-surface) !important; } .prose code { background: var(--surface-high) !important; color: var(--secondary) !important; padding: 2px 6px !important; border-radius: 6px !important; font-size: 0.85em !important; } .prose pre { background: var(--surface) !important; border: 1px solid var(--outline-variant) !important; border-radius: var(--radius-md) !important; padding: 16px !important; } .prose pre code { background: transparent !important; color: var(--on-surface) !important; } .prose hr { border-color: var(--outline-variant) !important; opacity: 0.3 !important; } .prose strong { color: var(--primary) !important; } /* ── Footer Hide ── */ footer { display: none !important; } /* ── Result Card Styling ── */ .result-card { background: var(--surface) !important; border: 1px solid var(--glass-border) !important; border-radius: var(--radius-lg) !important; padding: 20px !important; position: relative !important; } .result-card::after { content: ''; position: absolute; bottom: 0; left: 0; right: 0; height: 1px; background: linear-gradient(90deg, transparent, rgba(83, 221, 252, 0.3), transparent); pointer-events: none; } """ # ───────────────────────────────────────────────────────────────────── # Build Gradio UI # ───────────────────────────────────────────────────────────────────── RESOLUTIONS = [ "4K (3840×2160)", "2K (2560×1440)", "1080p (1920×1080)", "2x Original", ] METHODS = ["Real-ESRGAN (AI)", "Lanczos (Fast)"] def process_image(image_path, method, target, progress=gr.Progress()): """Wrapper that bridges Gradio progress to core function.""" return upscale_image(image_path, method, target, progress=progress) def process_video(video_path, method, target, progress=gr.Progress()): """Wrapper that bridges Gradio progress to core function.""" return upscale_video(video_path, method, target, progress=progress) with gr.Blocks( css=CSS, title="4K Upscaler Pro — AI Image & Video Enhancement", theme=gr.themes.Base( primary_hue=gr.themes.colors.purple, secondary_hue=gr.themes.colors.cyan, neutral_hue=gr.themes.colors.gray, font=gr.themes.GoogleFont("Inter"), font_mono=gr.themes.GoogleFont("Space Mono"), ), ) as demo: # ── Hero Section ── gr.Markdown("# ⬆ 4K Upscaler Pro", elem_classes=["hero-title"]) gr.Markdown( "AI-powered image & video enhancement • Up to 4K resolution • REST API ready", elem_classes=["hero-subtitle"], ) gr.Markdown("✦ POWERED BY REAL-ESRGAN", elem_classes=["status-badge"]) with gr.Tabs(): # ━━━━━━━━━━━━ IMAGE TAB ━━━━━━━━━━━━ with gr.TabItem("🖼 Image Upscaler", id="image-tab"): with gr.Row(equal_height=True): with gr.Column(scale=1, elem_classes=["glass-panel"]): gr.Markdown("**INPUT**", elem_classes=["section-label"]) img_input = gr.Image( type="filepath", label="Upload Image", height=300, sources=["upload", "clipboard"], ) img_method = gr.Radio( METHODS, value="Lanczos (Fast)", label="Upscaling Method", ) img_target = gr.Dropdown( RESOLUTIONS, value="4K (3840×2160)", label="Target Resolution", ) img_btn = gr.Button( "⬆ Upscale Image", variant="primary", size="lg", ) with gr.Column(scale=1, elem_classes=["glass-panel"]): gr.Markdown("**RESULT**", elem_classes=["section-label"]) img_output = gr.Image( label="Upscaled Result", height=300, interactive=False, ) img_info = gr.Textbox( label="Processing Details", interactive=False, lines=3, ) img_download = gr.File(label="⬇ Download PNG") img_btn.click( process_image, inputs=[img_input, img_method, img_target], outputs=[img_output, img_info, img_download], ) # ━━━━━━━━━━━━ VIDEO TAB ━━━━━━━━━━━━ with gr.TabItem("🎬 Video Upscaler", id="video-tab"): with gr.Row(equal_height=True): with gr.Column(scale=1, elem_classes=["glass-panel"]): gr.Markdown("**INPUT**", elem_classes=["section-label"]) vid_input = gr.Video(label="Upload Video", height=300) vid_method = gr.Radio( METHODS, value="Lanczos (Fast)", label="Upscaling Method", ) vid_target = gr.Dropdown( RESOLUTIONS, value="4K (3840×2160)", label="Target Resolution", ) vid_btn = gr.Button( "⬆ Upscale Video", variant="primary", size="lg", ) with gr.Column(scale=1, elem_classes=["glass-panel"]): gr.Markdown("**RESULT**", elem_classes=["section-label"]) vid_output = gr.Video( label="Upscaled Result", height=300, interactive=False, ) vid_info = gr.Textbox( label="Processing Details", interactive=False, lines=3, ) vid_btn.click( process_video, inputs=[vid_input, vid_method, vid_target], outputs=[vid_output, vid_info], ) # ━━━━━━━━━━━━ API DOCS TAB ━━━━━━━━━━━━ with gr.TabItem("📡 API Docs", id="api-tab"): gr.Markdown(""" ## REST API — Use from n8n, curl, or any HTTP client Hugging Face Spaces expose a built-in API automatically. --- ### 🖼 Image Upscaling **POST** `https://areebsa-upsclr.hf.space/api/predict` ```json { "data": [ "", "Lanczos (Fast)", "4K (3840×2160)" ] } ``` **Response:** ```json { "data": [ "", "✅ Upscaled: 1280×720 → 3840×2160", "" ] } ``` --- ### 🎬 Video Upscaling **POST** `https://areebsa-upsclr.hf.space/api/predict` Use `fn_index: 1` for the video endpoint. ```json { "fn_index": 1, "data": [ "", "Lanczos (Fast)", "4K (3840×2160)" ] } ``` --- ### 🔗 n8n / Zapier Integration Use the **HTTP Request** node: - **Method:** POST - **URL:** `https://areebsa-upsclr.hf.space/api/predict` - **Body:** JSON with `data` array (see above) - **Headers:** `Content-Type: application/json` For private spaces, add: - **Header:** `Authorization: Bearer ` """) # ── Launch ── if __name__ == "__main__": demo.queue(max_size=5).launch( ssr_mode=False, server_name="0.0.0.0", server_port=7860, )