SerghejL/pro
0
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 