CoolFace
Apppublic

SerghejL/pro

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
app.py475 linesDownload Raw Back to root
1# === Pixweave Pro — app.py (Lanczos only, no quotas/limits) ===2# Изменения:3# • Убраны квоты/персистентные лимиты, файл-блокировки и связанная логика.4# • Удалены баннеры и проверки «Limit reached», UI больше не блокируется.5# • Сохранён функционал: апскейл (Lanczos+Unsharp), экспорт для печати (CMYK/ICC/300DPI), очистка превью и поля загрузки после скачивания.6 7import os, io, base648import gradio as gr9 10# -------- Soft Gate (client-only): noindex + UI hide on direct open --------11HEAD_HTML = r"""12<meta name="robots" content="noindex, nofollow, noarchive">13<link rel="canonical" href="https://pixweave.net/">14<script>15(function(){16  // Hide UI if opened directly (not embedded at pixweave.net)17  var fromPix = document.referrer && document.referrer.indexOf('https://pixweave.net/') === 0;18  if (window.top === window.self && !fromPix) {19    document.addEventListener('DOMContentLoaded', function(){20      document.body.innerHTML = '<div style="padding:24px;font:16px/1.4 system-ui">Access via <a href=\"https://pixweave.net/\">pixweave.net</a> only.</div>';21      document.title = 'Access blocked';22    });23  }24  // Optional: accept postMessage handshake from parent25  window.addEventListener('message', function(e){26    if (e.origin === 'https://pixweave.net' && e.data === 'PIX_OK') {27      // no-op: keeping soft gate purely client-side28    }29  });30})();31</script>32"""33 34from PIL import Image, ImageFilter, ImageOps, ImageCms35 36# Отключаем телеметрию Gradio37os.environ.setdefault("GRADIO_ANALYTICS_ENABLED", "False")38 39# ----------------- UI утилиты -----------------40def _to_data_uri(img: Image.Image) -> str:41    """Вернёт предпросмотр как <div> с background-image (без правого клика)."""42    w, h = img.size43    max_side = 192044    k = min(1.0, float(max_side) / float(max(w, h)))45    if k < 1.0:46        img = img.resize((max(1, int(w * k)), max(1, int(h * k))), Image.LANCZOS)47        w, h = img.size48    buf = io.BytesIO()49    try:50        img.save(buf, format='WEBP', quality=82, method=6)51        mime = 'image/webp'52    except Exception:53        img.save(buf, format='JPEG', quality=88, optimize=True)54        mime = 'image/jpeg'55    b64 = base64.b64encode(buf.getvalue()).decode('ascii')56    return (57        '<div class="pvw" oncontextmenu="return false" draggable="false" '58        f'style="--w:{w};--h:{h};background-image:url(data:{mime};base64,{b64});"></div>'59    )60 61 62def _parse_scale(val):63    try:64        if isinstance(val, (int, float)):65            return int(val)66        s = ''.join(ch for ch in str(val) if ch.isdigit())67        return int(s) if s else 468    except Exception:69        return 470 71 72def _ip(width):73    w = max(0, min(int(width), 100))74    return f'<div id="inline-prog" class="ip running"><div class="bar" style="width:{w}%"></div></div>'75 76 77# ----------------- Апскейл (только LANCZOS) -----------------78def upscale_lanczos(img: Image.Image, scale: int):79    if img is None:80        return None, None, "Please upload an image."81    s = _parse_scale(scale)82    if s not in (1, 2, 4):83        s = 484    if s == 1:85        out = img.convert("RGB") if img.mode != "RGB" else img86        return _to_data_uri(out), out, "✅ Ready."87    out = img.resize((img.width * s, img.height * s), Image.LANCZOS)88    try:89        out = out.filter(ImageFilter.UnsharpMask(radius=1.2, percent=160, threshold=3))90    except Exception:91        pass92    return _to_data_uri(out), out, "✅ Upscale complete."93 94 95# ----------------- Печать (CMYK/ICC/300 DPI) -----------------96FOGRA_PATH = "icc/FOGRA39.icc"             # EU97SWOP_PATH  = "icc/USWebCoatedSWOP.icc"     # US98PRINT_PRESETS = {99    ("A4", "0"): (2480, 3508),100    ("A4", "3"): (2551, 3579),101    ("A4", "5"): (2598, 3626),102}103 104 105def _load_icc_bytes(path: str) -> bytes:106    with open(path, "rb") as f:107        return f.read()108 109 110def _find_icc_path_for(region: str) -> str:111    want = 'fogra' if region == 'EU' else 'swop'112    preferred = [FOGRA_PATH] if region == 'EU' else [SWOP_PATH]113    aliases = (114        [115            "icc/ISOcoated_v2_300_eci.icc",116            "icc/CoatedFOGRA39.icc",117            "icc/Coated_Fogra39L_VIGC_300.icc",118            "ISOcoated_v2_300_eci.icc",119            "CoatedFOGRA39.icc",120            "Coated_Fogra39L_VIGC_300.icc",121        ] if region == 'EU' else [122            "icc/USWebCoatedSWOP.icc",123            "icc/USWebCoatedSWOPv2.icc",124            "USWebCoatedSWOP.icc",125            "USWebCoatedSWOPv2.icc",126        ]127    )128    candidates = preferred + aliases129    for root in ("icc", "."):130        try:131            for fn in os.listdir(root):132                if fn.lower().endswith('.icc') and want in fn.lower():133                    candidates.append(os.path.join(root, fn))134        except Exception:135            pass136    for p in candidates:137        try:138            if os.path.exists(p):139                try:140                    if os.path.getsize(p) < 1024:141                        continue142                except Exception:143                    pass144                return p145        except Exception:146            pass147    raise FileNotFoundError(f"No ICC found for region {region}. Looked for files with '{want}'.")148 149 150def _to_cmyk_with_profile(img_rgb: Image.Image, region: str):151    if img_rgb.mode != 'RGB':152        img_rgb = img_rgb.convert('RGB')153    dst_path = _find_icc_path_for(region)154    icc_bytes = _load_icc_bytes(dst_path)155    src_prof = ImageCms.createProfile("sRGB")156    # Попытка 1157    try:158        dst_prof = ImageCms.getOpenProfile(dst_path)159        out = ImageCms.profileToProfile(img_rgb, src_prof, dst_prof, renderingIntent=0, outputMode="CMYK")160        return out, icc_bytes161    except Exception as e:162        print("[print] p2p(open) failed:", e)163    # Попытка 2 (BPC)164    try:165        dst_prof = ImageCms.getOpenProfile(dst_path)166        tfm = ImageCms.buildTransformFromOpenProfiles(167            src_prof, dst_prof, "RGB", "CMYK",168            renderingIntent=0, flags=ImageCms.FLAGS_BLACKPOINTCOMPENSATION169        )170        out = ImageCms.applyTransform(img_rgb, tfm)171        return out, icc_bytes172    except Exception as e:173        print("[print] build(BPC) failed:", e)174    # Попытка 3 (bytes)175    try:176        dst_prof = ImageCms.ImageCmsProfile(io.BytesIO(icc_bytes))177        out = ImageCms.profileToProfile(img_rgb, src_prof, dst_prof, renderingIntent=0, outputMode="CMYK")178        return out, icc_bytes179    except Exception as e:180        print("[print] p2p(bytes) failed:", e)181    # Фолбэк: CMYK без ICC182    try:183        out = img_rgb.convert('CMYK')184        return out, b""185    except Exception as e:186        print("[print] plain CMYK failed:", e)187    raise gr.Error("CMYK conversion failed. Check /icc profiles.")188 189 190def _flatten_to_rgb(img: Image.Image, bg=(255,255,255)) -> Image.Image:191    try:192        if img.mode in ("RGBA", "LA") or (img.mode == "P" and "transparency" in img.info):193            base = Image.new("RGB", img.size, bg)194            if img.mode != "RGBA":195                img = img.convert("RGBA")196            base.paste(img, mask=img.split()[-1])197            return base198        return img.convert("RGB") if img.mode != "RGB" else img199    except Exception:200        return img.convert("RGB")201 202 203def _get_target_dims(paper: str, region: str, bleed_mm: str):204    inc = {"0": 0, "3": 71, "5": 118}.get(str(bleed_mm), 0)205    if paper == "A4":206        base = PRINT_PRESETS.get(("A4", "0"), (2480, 3508))207        return base[0] + inc, base[1] + inc208    if paper == "Postcard":209        bw, bh = (1240, 1748) if region == "EU" else (1200, 1800)210        return bw + inc, bh + inc211    tgt = PRINT_PRESETS.get((paper, str(bleed_mm)))212    return tgt if tgt else PRINT_PRESETS.get(("A4", "0"))213 214 215def _resize_for_print(img: Image.Image, region: str, paper: str, bleed_mm: str, fit_mode: str) -> Image.Image:216    tw, th = _get_target_dims(paper, region, bleed_mm)217    if fit_mode == "Fit":218        try:219            resized = ImageOps.contain(img, (tw, th), method=Image.Resampling.LANCZOS)220        except Exception:221            resized = ImageOps.contain(img, (tw, th))222        canvas = Image.new("RGB", (tw, th), (255, 255, 255))223        x = (tw - resized.width) // 2224        y = (th - resized.height) // 2225        if resized.mode in ("RGBA", "LA"):226            canvas.paste(resized.convert("RGBA"), (x, y), resized.convert("RGBA"))227        else:228            canvas.paste(_flatten_to_rgb(resized), (x, y))229        return canvas230    else:231        work = _flatten_to_rgb(img)232        try:233            return ImageOps.fit(work, (tw, th), method=Image.Resampling.LANCZOS, centering=(0.5, 0.5))234        except Exception:235            return ImageOps.fit(work, (tw, th), centering=(0.5, 0.5))236 237 238def _export_ready_for_print(img_rgb: Image.Image, region: str, paper: str, bleed_mm: str,239                            fit_mode: str, out_fmt: str, base_name: str):240    os.makedirs("out", exist_ok=True)241    sized = _resize_for_print(img_rgb, region, paper, bleed_mm, fit_mode)242    img_cmyk, icc_bytes = _to_cmyk_with_profile(sized, region)243    has_icc = bool(icc_bytes)244    if out_fmt == "TIFF":245        path = os.path.join("out", f"{base_name}_{paper}_{bleed_mm}mm_{region}.tiff")246        if has_icc:247            img_cmyk.save(path, format="TIFF", compression="tiff_lzw", dpi=(300, 300), icc_profile=icc_bytes)248        else:249            img_cmyk.save(path, format="TIFF", compression="tiff_lzw", dpi=(300, 300))250    else:251        path = os.path.join("out", f"{base_name}_{paper}_{bleed_mm}mm_{region}.jpg")252        if has_icc:253            img_cmyk.save(path, format="JPEG", quality=95, dpi=(300, 300), icc_profile=icc_bytes)254        else:255            img_cmyk.save(path, format="JPEG", quality=95, dpi=(300, 300))256    return path, has_icc257 258 259# ----------------- Тема / CSS -----------------260CUSTOM_CSS = """261:root { --pl-bg:#0f172a; --pl-card:#0b1226; --pl-text:#e5e7eb; --pl-muted:#94a3b8; --pl-grad: linear-gradient(90deg, #f472b6, #60a5fa); }262html, body, .gradio-container { background: var(--pl-bg) !important; color:var(--pl-text) !important; }263footer { display:none !important; }264.gr-box, .gr-panel, .block, .form, .tabs, .tabitem, .wrap, .container {265  background: linear-gradient(180deg, rgba(255,255,255,.04), rgba(255,255,255,.01)) !important;266  border: 1px solid rgba(255,255,255,.06) !important; border-radius: 12px !important;267}268input, textarea, .gr-input, .gradio-textbox, .gradio-file, .gradio-image {269  background:#0b1226 !important; color:#e5e7eb !important; border-color:rgba(255,255,255,.08) !important;270}271.gradio-container .gr-button { background: var(--pl-grad) !important; color:#fff !important; border:none !important; font-weight:800 !important; letter-spacing:.2px; padding:12px 18px !important; border-radius:999px !important; box-shadow: 0 6px 24px rgba(0,0,0,.28); transition: transform .15s ease, box-shadow .15s ease; }272.gradio-container .gr-button:hover { transform: translateY(-1px); box-shadow: 0 10px 28px rgba(0,0,0,.35); }273#btn-download, #btn-download button, #btn-download a { background: var(--pl-grad) !important; color:#fff !important; border:none !important; border-radius:999px !important; font-weight:800 !important; letter-spacing:.2px; padding:12px 18px !important; box-shadow: 0 6px 24px rgba(0,0,0,.28); }274#btn-download a { display:inline-flex; align-items:center; justify-content:center; }275#btn-upscale, #btn-upscale button { background: var(--pl-grad) !important; color:#fff !important; border:none !important; border-radius:999px !important; font-weight:800 !important; letter-spacing:.2px; padding:12px 18px !important; box-shadow: 0 6px 24px rgba(0,0,0,.28); }276#btn-upscale button:hover { transform: translateY(-1px); box-shadow: 0 10px 28px rgba(0,0,0,.35); }277.pl-pill { display:inline-flex; align-items:center; gap:.45rem; padding:8px 12px; margin:6px 0 8px; border-radius:999px; font-weight:800; letter-spacing:.2px; background: var(--pl-grad); color:#fff !important; box-shadow: 0 6px 24px rgba(0,0,0,.28); }278#status { color:#fff !important; background: rgba(255,255,255,.22); border: 1px solid rgba(255,255,255,.32); padding:12px 14px; border-radius:12px; font-weight:900; font-size:16px; line-height:1.25; text-shadow: 0 1px 1px rgba(0,0,0,.35); }279.pvw{ width:100%; aspect-ratio: var(--w) / var(--h); background-size:contain; background-repeat:no-repeat; background-position:center; border-radius:12px; user-select:none; -webkit-user-select:none; -webkit-user-drag:none; position:relative; min-height:180px; }280.pvw-badge{ position:absolute; right:8px; bottom:8px; padding:6px 10px; border-radius:999px; background: rgba(0,0,0,.55); color:#fff; font-weight:800; font-size:12px; letter-spacing:.2px; backdrop-filter: blur(2px); }281/* toggle / chips */282#toggle-print { margin:6px 0 10px; }283#toggle-print label { display:flex; align-items:center; gap:.6rem; padding:10px 12px; border-radius:12px; font-weight:800; letter-spacing:.2px; color:#fff !important; background: rgba(255,255,255,.06); border:1px solid rgba(255,255,255,.14); }284#toggle-print input { position:absolute; opacity:0; width:0; height:0; pointer-events:none; }285#toggle-print label::before { content:""; width:12px; height:14px; border-radius:3px; background:#0a0f1f; box-shadow: inset 0 0 0 1px rgba(255,255,255,.15); }286#toggle-print label:has(input:checked)::before { background:#22c55e; box-shadow: 0 0 10px rgba(34,197,94,.55); }287#scale-radio label, #region-radio label, #paper-radio label, #bleed-radio label, #fit-radio label, #format-radio label { display:flex; align-items:center; gap:.5rem; }288#scale-radio label::before, #region-radio label::before, #paper-radio label::before, #bleed-radio label::before, #fit-radio label::before, #format-radio label::before { content:""; width:10px; height:14px; border-radius:3px; background: #0a0f1f; box-shadow: inset 0 0 0 1px rgba(255,255,255,.15); }289#scale-radio label:has(input:checked)::before, #region-radio label:has(input:checked)::before, #paper-radio label:has(input:checked)::before, #bleed-radio label:has(input:checked)::before, #fit-radio label:has(input:checked)::before, #format-radio label:has(input:checked)::before { background:#22c55e; box-shadow: 0 0 10px rgba(34,197,94,.55); }290#scale-radio input, #region-radio input, #paper-radio input, #bleed-radio input, #fit-radio input, #format-radio input { position:absolute; opacity:0; width:0; height:0; pointer-events:none; }291#scale-radio label:has(input:checked), #region-radio label:has(input:checked), #paper-radio label:has(input:checked), #bleed-radio label:has(input:checked), #fit-radio label:has(input:checked), #format-radio label:has(input:checked) {292  background: linear-gradient(180deg, rgba(34,197,94,.12), rgba(34,197,94,.06));293  outline: 1px solid rgba(34,197,94,.45);294  box-shadow: 0 0 0 2px rgba(34,197,94,.18) inset, 0 0 18px rgba(34,197,94,.18);295  border-radius: 10px;296}297/* inline progress */298.ip{ height:4px; background:rgba(255,255,255,.12); border-radius:999px; overflow:hidden; margin:8px 0 0; opacity:0; transition: opacity .2s ease; }299.ip .bar{ height:100%; width:0%; background: linear-gradient(90deg, #22c55e, #86efac); box-shadow: 0 0 12px rgba(34,197,94,.45); transition: width .35s ease; }300.ip.running{ opacity:1; }301[data-testid="progress-bar"], div[class*="progress"][class*="top"], div[aria-live="polite"]{ display:none !important; }302"""303 304try:305    THEME = gr.themes.Soft(primary_hue="fuchsia", neutral_hue="slate")306except Exception:307    THEME = None308 309with gr.Blocks(title="Pixweave — Image Upscaler (Lanczos)", css=CUSTOM_CSS, theme=THEME) as demo:310    # UI311    with gr.Row():312        with gr.Column():313            gr.HTML('<div class="pl-pill">Upload image</div>')314            in_img = gr.Image(type="pil", show_label=False)315 316            gr.HTML('<div class="pl-pill">Scale</div>')317            scale = gr.Radio(choices=["×1", "×2", "×4"], value="×4", show_label=False, elem_id="scale-radio")318 319            gr.HTML('<div class="pl-pill">Ready for print (CMYK, 300 DPI)</div>')320            ready = gr.Checkbox(value=False, label="Ready for print", elem_id="toggle-print")321            print_state = gr.HTML('<div id="print-state" style="margin:6px 0 4px; font-weight:800; color:#e5e7eb;">Status: OFF ⚪</div>')322            with gr.Row():323                region = gr.Radio(choices=["EU", "US"], value="EU", label="Region (ICC)", interactive=True, elem_id="region-radio")324                paper  = gr.Radio(choices=["A4", "Postcard"], value="A4", label="Paper", interactive=True, elem_id="paper-radio")325            with gr.Row():326                bleed  = gr.Radio(choices=["0", "3", "5"], value="3", label="Bleed (mm)", interactive=True, elem_id="bleed-radio")327                fit    = gr.Radio(choices=["Fit", "Fill"], value="Fit", label="Fit mode", interactive=True, elem_id="fit-radio")328 329            gr.HTML('<div class="pl-pill">Format</div>')330            fmt = gr.Radio(choices=["PNG", "JPEG"], value="PNG", show_label=False, elem_id="format-radio")331 332            def _toggle_formats(ready_flag, scale_value):333                s = _parse_scale(scale_value)334                if ready_flag:335                    btn_label = "Export for print" if s == 1 else "Upscale & Export for print"336                    return (337                        gr.update(choices=["TIFF", "JPEG"], value="TIFF"),338                        gr.update(value=btn_label),339                        gr.update(value='<div id="print-state" style="margin:6px 0 4px; font-weight:800; color:#e5e7eb;">Status: ON 🟢</div>'),340                    )341                else:342                    return (343                        gr.update(choices=["PNG", "JPEG"], value="PNG"),344                        gr.update(value="Upscale"),345                        gr.update(value='<div id="print-state" style="margin:6px 0 4px; font-weight:800; color:#e5e7eb;">Status: OFF ⚪</div>'),346                    )347 348            btn = gr.Button("Upscale", variant="primary", elem_id="btn-upscale")349            inline_prog = gr.HTML('<div id="inline-prog" class="ip"><div class="bar"></div></div>', elem_id="inline-prog-wrap")350 351            ready.change(_toggle_formats, inputs=[ready, scale], outputs=[fmt, btn, print_state])352 353            def _update_btn_label_on_scale(scale_value, ready_flag):354                s = _parse_scale(scale_value)355                if ready_flag:356                    label = "Export for print" if s == 1 else "Upscale & Export for print"357                else:358                    label = "Upscale"359                return gr.update(value=label)360 361            scale.change(_update_btn_label_on_scale, inputs=[scale, ready], outputs=[btn])362 363        with gr.Column():364            gr.HTML('<div class="pl-pill">Upscaled preview</div>')365            out_html = gr.HTML("", elem_id="out-preview")366 367            gr.HTML('<div class="pl-pill">Download</div>')368            download_btn = gr.DownloadButton(label="Press to Download", value=None, elem_id="btn-download")369 370            status = gr.Markdown("", visible=True, elem_id="status")371 372    # ----------------- Логика -----------------373    def ui_pre(ready_flag, scale_value, request: gr.Request):374        _q = getattr(request, 'query_params', None) or {}375        _via = _q.get('via')376        _ref = (request.headers.get('referer') or '').lower() if getattr(request, 'headers', None) else ''377        if _via != 'pix' and not _ref.startswith('https://pixweave.net/'):378            raise gr.Error('Access via pixweave.net only.')379        s = _parse_scale(scale_value)380        label = "Export for print" if (ready_flag and s == 1) else ("Upscale & Export for print" if ready_flag else "Upscale")381        return gr.update(value=label + " …", interactive=False), "⏳ Working…", gr.update(value=_ip(8))382 383    def run_and_report(img, scale, ready, region, paper, bleed, fit, fmt, request: gr.Request):384        _q = getattr(request, 'query_params', None) or {}385        _via = _q.get('via')386        _ref = (request.headers.get('referer') or '').lower() if getattr(request, 'headers', None) else ''387        if _via != 'pix' and not _ref.startswith('https://pixweave.net/'):388            raise gr.Error('Access via pixweave.net only.')389        s_val = _parse_scale(scale)390        # 1) апскейл391        yield "", gr.update(value=None), gr.update(), gr.update(value=_ip(8))392        html, upscaled_rgb, msg = upscale_lanczos(img, scale)393        if html is None or upscaled_rgb is None:394            yield "", gr.update(value=None), (msg or "❌ Upscale failed."), gr.update(value=_ip(100))395            return396        yield html, gr.update(value=None), gr.update(), gr.update(value=_ip(55))397 398        # 2) экспорт399        os.makedirs("out", exist_ok=True)400        base = f"upscaled_{s_val}x"401        if not ready:402            yield html, gr.update(value=None), gr.update(), gr.update(value=_ip(85))403            if fmt == "PNG":404                out_path = os.path.join("out", base + ".png")405                upscaled_rgb.save(out_path, "PNG")406                label = "Press to Download PNG"407            else:408                out_path = os.path.join("out", base + ".jpg")409                upscaled_rgb.save(out_path, "JPEG", quality=95)410                label = "Press to Download JPEG"411            yield html, gr.update(value=out_path, label=label), "✅ Upscale complete.", gr.update(value=_ip(100))412            return413 414        pr_fmt = "TIFF" if fmt == "TIFF" else "JPEG"415        yield html, gr.update(value=None), gr.update(), gr.update(value=_ip(65))416        out_path, has_icc = _export_ready_for_print(upscaled_rgb, region, paper, bleed, fit, pr_fmt, base_name=f"print_{base}")417        region_tag = "FOGRA39" if region == "EU" else "US SWOP"418        label = f"Press to Download {pr_fmt} (CMYK)"419        embed = "with ICC" if has_icc else "(no ICC embedded)"420        paper_label = ("Postcard (A6)" if (paper == "Postcard" and region == "EU") else ("Postcard (4×6\")" if paper == "Postcard" else paper))421        msg2 = f"✅ Upscale complete. Print-ready: {pr_fmt} (CMYK, {region_tag}, {embed}), {paper_label}, {bleed} mm, 300 DPI."422        yield html, gr.update(value=out_path, label=label), msg2, gr.update(value=_ip(100))423 424    def ui_post(ready_flag, scale_value, request: gr.Request):425        _q = getattr(request, 'query_params', None) or {}426        _via = _q.get('via')427        _ref = (request.headers.get('referer') or '').lower() if getattr(request, 'headers', None) else ''428        if _via != 'pix' and not _ref.startswith('https://pixweave.net/'):429            raise gr.Error('Access via pixweave.net only.')430        s = _parse_scale(scale_value)431        label = "Export for print" if (ready_flag and s == 1) else ("Upscale & Export for print" if ready_flag else "Upscale")432        return gr.update(value=label, interactive=True)433 434    def clear_after_download(request: gr.Request):435        _q = getattr(request, 'query_params', None) or {}436        _via = _q.get('via')437        _ref = (request.headers.get('referer') or '').lower() if getattr(request, 'headers', None) else ''438        if _via != 'pix' and not _ref.startswith('https://pixweave.net/'):439            raise gr.Error('Access via pixweave.net only.')440        # После клика: очистить превью, кнопку скачивания и окно загрузки441        return "", gr.update(value=None), gr.update(value=None)442 443    # ----------------- Привязки -----------------444    btn.click(fn=ui_pre, inputs=[ready, scale], outputs=[btn, status, inline_prog]) \445       .then(fn=run_and_report,446             inputs=[in_img, scale, ready, region, paper, bleed, fit, fmt],447             outputs=[out_html, download_btn, status, inline_prog]) \448       .then(fn=ui_post, inputs=[ready, scale], outputs=[btn])449 450    download_btn.click(fn=clear_after_download, inputs=None, outputs=[out_html, download_btn, in_img])451 452    # Небольшая «пружинка» на кнопках453    gr.HTML("""454<script>455(function(){456  function spring(el){ if(!el) return; el.classList.remove('spring'); void el.offsetWidth; el.classList.add('spring'); setTimeout(function(){ el.classList.remove('spring'); }, 260); }457  function bind(sel){ var el = document.querySelector(sel); if(!el) return; el.addEventListener('click', function(){ spring(el); }, true); }458  bind('#btn-upscale button');459  bind('#btn-download a'); bind('#btn-download button');460})();461</script>462    """)463 464# Запуск465_app = demo466try:467    _app = demo.queue(concurrency_count=1, max_size=12)468except TypeError:469    try:470        _app = demo.queue(max_size=24)471    except TypeError:472        _app = demo.queue()473 474_app.launch(show_api=False)475