""" FreeBG.site — Complete SaaS Background Removal HuggingFace Space | Gradio 4.x Tabs: Tool | Admin (password protected) | API Docs | My Usage Admin: Add/Edit/Delete customers + Auto-update HF Secret """ import os, io, time, json, logging, random, string from datetime import datetime, timezone, timedelta from typing import Optional import numpy as np from PIL import Image logging.basicConfig(level=logging.INFO, format="%(levelname)s:%(name)s:%(message)s") logger = logging.getLogger("freebg") def utcnow(): return datetime.now(timezone.utc) # ── Config ──────────────────────────────────────────────────────────────────── HF_TOKEN = os.environ.get("HF_TOKEN", "") MASTER_KEY = os.environ.get("MASTER_API_KEY", "freebg-master-key-change-me") ADMIN_PASS = os.environ.get("ADMIN_PASSWORD", "freebg-admin-2026") HF_REPO_ID = os.environ.get("HF_REPO_ID", "freebg/background-remover") # ── HF Login ────────────────────────────────────────────────────────────────── if HF_TOKEN: try: from huggingface_hub import login login(token=HF_TOKEN, add_to_git_credential=False) logger.info("✅ HF login OK") except Exception as e: logger.warning(f"HF login: {e}") # ── API Keys ────────────────────────────────────────────────────────────────── API_KEYS: dict = {} _raw = os.environ.get("API_KEYS_JSON", "") if _raw: try: API_KEYS = json.loads(_raw) logger.info(f"Loaded {len(API_KEYS)} customer keys") except Exception as e: logger.warning(f"API_KEYS_JSON error: {e}") API_KEYS.setdefault(MASTER_KEY, { "plan": "master", "calls_today": 0, "reset_at": (utcnow() + timedelta(days=1)).timestamp(), "owner": "freebg.site", "created_at": utcnow().isoformat() }) PLAN_LIMITS = { "free": {"daily": 10, "models": ["fast"], "price": "$0"}, "starter": {"daily": 100, "models": ["fast", "quality"], "price": "$9/mo"}, "pro": {"daily": 500, "models": ["fast", "quality", "best"],"price": "$29/mo"}, "master": {"daily": 999999, "models": ["fast", "quality", "best"],"price": "Custom"}, } # ── HF Secret Auto-Update ───────────────────────────────────────────────────── def push_keys_to_hf_secret() -> tuple: if not HF_TOKEN: return False, "HF_TOKEN not set" keys_json = json.dumps(API_KEYS) try: from huggingface_hub import add_space_secret add_space_secret(HF_REPO_ID, "API_KEYS_JSON", keys_json, token=HF_TOKEN) logger.info("✅ HF Secret updated") return True, "✅ **Saved!** Space restarts in ~30s. New key will be active." except Exception as e1: logger.warning(f"add_space_secret failed: {e1}") try: import requests as req headers = {"Authorization": f"Bearer {HF_TOKEN}"} url = f"https://huggingface.co/api/spaces/{HF_REPO_ID}/secrets" for method in [req.put, req.post]: r = method(url, headers=headers, json={"key": "API_KEYS_JSON", "value": keys_json}, timeout=15) if r.status_code in (200, 201, 204): return True, "✅ **Saved!** Space restarts in ~30s." except Exception as e2: logger.error(f"API failed: {e2}") manual = ( "⚠️ **Auto-save failed** — HF_TOKEN needs **write** permission.\n\n" "**Fix:** Go to https://huggingface.co/settings/tokens\n" "→ Create new token → Type: **Write** → Replace HF_TOKEN secret with it.\n\n" "**Paste this JSON manually in HF Secret `API_KEYS_JSON`:**\n\n" + keys_json ) return False, manual # ── Key Generator ───────────────────────────────────────────────────────────── def gen_key(plan: str) -> str: chars = string.ascii_lowercase + string.digits r = lambda n: ''.join(random.choices(chars, k=n)) return f"freebg-{plan[:2]}-{r(8)}-{r(8)}" # ── Model Cache ─────────────────────────────────────────────────────────────── _cache: dict = {} def load_u2net(): if "u2net" in _cache: return _cache["u2net"] try: from rembg import new_session _cache["u2net"] = new_session("u2net") logger.info("✅ U2-Net ready"); return _cache["u2net"] except Exception as e: logger.error(f"U2-Net: {e}"); return None def load_birefnet(): if "birefnet" in _cache: return _cache["birefnet"] try: from transformers import AutoModelForImageSegmentation from torchvision import transforms m = AutoModelForImageSegmentation.from_pretrained("ZhengPeng7/BiRefNet", trust_remote_code=True) m = m.float(); m.eval() _cache["birefnet"] = m _cache["birefnet_tf"] = transforms.Compose([ transforms.Resize((1024,1024)), transforms.ToTensor(), transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])]) logger.info("✅ BiRefNet ready"); return m except Exception as e: logger.error(f"BiRefNet: {e}"); return None def load_rmbg(): if "rmbg" in _cache: return _cache["rmbg"] try: from transformers import AutoModelForImageSegmentation from torchvision import transforms m = AutoModelForImageSegmentation.from_pretrained( "briaai/RMBG-2.0", trust_remote_code=True, token=HF_TOKEN or None) m = m.float(); m.eval() _cache["rmbg"] = m _cache["rmbg_tf"] = transforms.Compose([ transforms.Resize((1024,1024)), transforms.ToTensor(), transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])]) logger.info("✅ RMBG-2.0 ready"); return m except Exception as e: logger.error(f"RMBG-2.0: {e}"); return None def _seg(key, img): import torch m = _cache[key]; tf = _cache[key+"_tf"]; orig = img.size inp = tf(img.convert("RGB")).unsqueeze(0).float() with torch.no_grad(): out = m(inp) pred = (out[-1] if isinstance(out,(list,tuple)) else out).sigmoid() mask = Image.fromarray((pred[0].squeeze().cpu().numpy()*255).astype(np.uint8)).resize(orig, Image.LANCZOS) r = img.convert("RGBA"); r.putalpha(mask); return r def infer_fast(img): from rembg import remove if not load_u2net(): raise RuntimeError("U2-Net not available") buf = io.BytesIO(); img.save(buf,"PNG"); buf.seek(0) return Image.open(io.BytesIO(remove(buf.read(), session=_cache["u2net"]))).convert("RGBA") def infer_quality(img): if not load_birefnet(): raise RuntimeError("BiRefNet not available") return _seg("birefnet", img) def infer_best(img): if not load_rmbg(): raise RuntimeError("BRIA RMBG-2.0 not available — check HF_TOKEN") return _seg("rmbg", img) MODEL_FN = {"fast": infer_fast, "quality": infer_quality, "best": infer_best} MODEL_INFO = { "fast": {"name":"U2-Net", "desc":"Fastest (~1-5s)"}, "quality": {"name":"BiRefNet", "desc":"High quality (~2-4min CPU)"}, "best": {"name":"BRIA RMBG-2.0", "desc":"Best quality (~2-4min CPU)"}, } # ── Auth ────────────────────────────────────────────────────────────────────── def validate_key(api_key, model): if not api_key: return False, "API key required" if api_key not in API_KEYS: return False, "Invalid API key" kd = API_KEYS[api_key]; plan = kd.get("plan","free") cfg = PLAN_LIMITS.get(plan, PLAN_LIMITS["free"]) if utcnow().timestamp() > kd.get("reset_at",0): kd["calls_today"] = 0 kd["reset_at"] = (utcnow() + timedelta(days=1)).timestamp() if model not in cfg["models"]: return False, f"Model '{model}' not in {plan} plan. Upgrade at freebg.site" if kd.get("calls_today",0) >= cfg["daily"]: return False, f"Daily limit {cfg['daily']} reached." return True, "" def inc_usage(k): if k in API_KEYS: API_KEYS[k]["calls_today"] = API_KEYS[k].get("calls_today",0)+1 def get_usage(k): if k not in API_KEYS: return {"error":"Invalid key"} kd = API_KEYS[k]; plan = kd.get("plan","free") cfg = PLAN_LIMITS.get(plan, PLAN_LIMITS["free"]) return {"plan":plan,"calls_today":kd.get("calls_today",0), "daily_limit":cfg["daily"],"available_models":cfg["models"],"owner":kd.get("owner","")} def run_removal(api_key, img_data, model="fast", max_size=0): t0 = time.time() ok, err = validate_key(api_key, model) if not ok: return None, {}, err try: img = (Image.fromarray(img_data) if isinstance(img_data, np.ndarray) else img_data if isinstance(img_data, Image.Image) else Image.open(img_data)).convert("RGB") ow, oh = img.size if max_size>0 and max(ow,oh)>max_size: r=max_size/max(ow,oh); img=img.resize((int(ow*r),int(oh*r)),Image.LANCZOS) result = MODEL_FN[model](img) inc_usage(api_key) elapsed = round(time.time()-t0, 2) meta = {"model":MODEL_INFO[model]["name"],"model_key":model, "original_size":f"{ow}x{oh}","output_size":f"{result.size[0]}x{result.size[1]}", "processing_time_sec":elapsed,"calls_today":API_KEYS[api_key]["calls_today"]} logger.info(f"✅ {model} {ow}x{oh} {elapsed}s key=...{api_key[-6:]}") return result, meta, "" except Exception as e: logger.error(f"run_removal: {e}", exc_info=True) return None, {}, str(e) # ── Gradio Callbacks ────────────────────────────────────────────────────────── import gradio as gr _CHOICES = ["🚀 Fast (U2-Net)","⚡ High Quality (BiRefNet)","🏆 Best (BRIA RMBG-2.0)"] _CMAP = {"🚀 Fast (U2-Net)":"fast","⚡ High Quality (BiRefNet)":"quality","🏆 Best (BRIA RMBG-2.0)":"best"} def ui_remove(api_key, image, choice): m = _CMAP.get(choice,"fast") result, meta, err = run_removal(api_key, image, m) if err: return None, f"❌ **Error:** {err}" return result, (f"✅ **Done** | `{meta['model']}` | `{meta['processing_time_sec']}s` | " f"`{meta['original_size']}` → `{meta['output_size']}` | calls: `{meta['calls_today']}`") def ui_usage(api_key): info = get_usage(api_key) if "error" in info: return f"❌ {info['error']}" return (f"**Plan:** {info['plan'].upper()} \n" f"**Calls today:** {info['calls_today']} / {info['daily_limit']} \n" f"**Models:** {', '.join(info['available_models'])} \n" f"**Owner:** {info.get('owner','—')}") # ── Admin Functions ─────────────────────────────────────────────────────────── def admin_login(password): if password == ADMIN_PASS: return (gr.update(visible=False), gr.update(visible=True), "", admin_list_customers(), admin_stats()) return gr.update(visible=True), gr.update(visible=False), "❌ Wrong password", [], "" def admin_list_customers(): rows = [] for key, d in API_KEYS.items(): if key == MASTER_KEY: continue rows.append([ key, d.get("owner",""), d.get("plan","free"), d.get("calls_today",0), d.get("limit", PLAN_LIMITS.get(d.get("plan","free"),{}).get("daily",10)), d.get("created_at","")[:10] if d.get("created_at") else "" ]) return rows def admin_add_customer(email, plan, custom_limit): if not email or "@" not in email: return "❌ Valid email required", admin_list_customers(), "" if plan not in PLAN_LIMITS: return "❌ Invalid plan", admin_list_customers(), "" key = gen_key(plan) limit = int(custom_limit) if str(custom_limit).strip().isdigit() else PLAN_LIMITS[plan]["daily"] API_KEYS[key] = { "plan": plan, "owner": email, "calls_today": 0, "reset_at": 0, "limit": limit, "models": PLAN_LIMITS[plan]["models"], "created_at": utcnow().isoformat() } ok, msg = push_keys_to_hf_secret() status = f"✅ Key created for **{email}** ({plan} plan)\n\n🔑 `{key}`\n\n{msg}" return status, admin_list_customers(), key def admin_delete_customer(key): if not key or key.strip() not in API_KEYS: return "❌ Key not found", admin_list_customers() key = key.strip() if key == MASTER_KEY: return "❌ Cannot delete master key", admin_list_customers() owner = API_KEYS[key].get("owner","") del API_KEYS[key] ok, msg = push_keys_to_hf_secret() return f"✅ Deleted key for **{owner}**\n\n{msg}", admin_list_customers() def admin_upgrade_plan(key, new_plan): if not key or key.strip() not in API_KEYS: return "❌ Key not found", admin_list_customers() if new_plan not in PLAN_LIMITS: return "❌ Invalid plan", admin_list_customers() key = key.strip() API_KEYS[key]["plan"] = new_plan API_KEYS[key]["limit"] = PLAN_LIMITS[new_plan]["daily"] API_KEYS[key]["models"] = PLAN_LIMITS[new_plan]["models"] owner = API_KEYS[key].get("owner","") ok, msg = push_keys_to_hf_secret() return f"✅ **{owner}** upgraded to **{new_plan}**\n\n{msg}", admin_list_customers() def admin_refresh(): return admin_list_customers() def admin_export_json(): return json.dumps(API_KEYS, indent=2) def admin_stats(): vals = list(API_KEYS.values()) total = len([v for v in vals if v.get("owner") != "freebg.site"]) free_c = len([v for v in vals if v.get("plan")=="free"]) start = len([v for v in vals if v.get("plan")=="starter"]) pro_c = len([v for v in vals if v.get("plan") in ("pro","master") and v.get("owner")!="freebg.site"]) calls = sum(v.get("calls_today",0) for v in vals) rev = start*9 + pro_c*29 return (f"👥 **Total Customers:** {total} \n" f"🆓 **Free:** {free_c} \n" f"⭐ **Starter ($9):** {start} \n" f"🏆 **Pro ($29):** {pro_c} \n" f"📊 **Total Calls Today:** {calls} \n" f"💰 **Est. Monthly Revenue:** ${rev}") # ═══════════════════════════════════════════════════════════════════════════════ # ── NEW FEATURE FUNCTIONS (existing code untouched above) ───────────────────── # ═══════════════════════════════════════════════════════════════════════════════ import zipfile import tempfile # ── Session History Store ───────────────────────────────────────────────────── _session_history: list = [] # list of dicts: {thumb, result, name, time, model, size} def _pil_to_thumb(img: Image.Image, size=(120, 120)) -> Image.Image: """Create a small thumbnail for history display.""" thumb = img.copy() thumb.thumbnail(size, Image.LANCZOS) return thumb def _apply_background(result_rgba: Image.Image, bg_choice: str, bg_color: str, bg_image=None) -> Image.Image: """ Apply a background to a transparent RGBA image. bg_choice: 'transparent' | 'color' | 'custom_image' bg_color: hex string like '#ffffff' bg_image: PIL Image or None """ if bg_choice == "transparent": return result_rgba w, h = result_rgba.size if bg_choice == "color": try: from PIL import ImageColor rgb = ImageColor.getrgb(bg_color) except Exception: rgb = (255, 255, 255) canvas = Image.new("RGBA", (w, h), rgb + (255,)) canvas.paste(result_rgba, mask=result_rgba.split()[3]) return canvas.convert("RGB") if bg_choice == "custom_image" and bg_image is not None: bg = (Image.fromarray(bg_image) if isinstance(bg_image, np.ndarray) else bg_image).convert("RGBA") bg = bg.resize((w, h), Image.LANCZOS) bg.paste(result_rgba, mask=result_rgba.split()[3]) return bg.convert("RGB") return result_rgba # ── Batch Processing ────────────────────────────────────────────────────────── def ui_batch_remove(api_key, images, choice, bg_choice, bg_color, bg_image, progress=gr.Progress()): """ Process multiple images in a queue. Returns: gallery of results, status markdown, zip_file_path """ if not images: return [], "⚠️ No images uploaded.", None model = _CMAP.get(choice, "fast") results_gallery = [] status_lines = [] processed_pils = [] progress(0, desc="Starting batch…") for i, img_input in enumerate(images): frac = i / len(images) fname = f"image_{i+1}" # img_input from gr.File is a filepath string when type="filepath" try: if isinstance(img_input, str): pil_in = Image.open(img_input).convert("RGB") fname = os.path.basename(img_input) elif isinstance(img_input, np.ndarray): pil_in = Image.fromarray(img_input).convert("RGB") elif isinstance(img_input, Image.Image): pil_in = img_input.convert("RGB") else: status_lines.append(f"❌ `{fname}` — unsupported format") continue except Exception as e: status_lines.append(f"❌ `{fname}` — read error: {e}") progress((i + 1) / len(images), desc=f"Error on {fname}") continue progress(frac, desc=f"Processing {i+1}/{len(images)}: {fname}") result, meta, err = run_removal(api_key, pil_in, model) if err: status_lines.append(f"❌ `{fname}` — {err}") progress((i + 1) / len(images), desc=f"Failed: {fname}") continue # Apply background final = _apply_background(result, bg_choice, bg_color, bg_image) # Save to history _session_history.append({ "name": fname, "time": utcnow().strftime("%H:%M:%S"), "model": meta["model"], "size": meta["output_size"], "result": final, }) results_gallery.append(final) processed_pils.append((fname, final)) status_lines.append(f"✅ `{fname}` — {meta['processing_time_sec']}s | {meta['output_size']}") progress((i + 1) / len(images), desc=f"Done: {fname}") # Build ZIP zip_path = None if processed_pils: tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".zip") with zipfile.ZipFile(tmp.name, "w", zipfile.ZIP_DEFLATED) as zf: for fname, pil in processed_pils: buf = io.BytesIO() ext = "png" if bg_choice == "transparent" else "jpg" fmt = "PNG" if bg_choice == "transparent" else "JPEG" pil.save(buf, fmt, optimize=True) buf.seek(0) out_name = os.path.splitext(fname)[0] + f"_nobg.{ext}" zf.writestr(out_name, buf.read()) zip_path = tmp.name total = len(processed_pils) failed = len(images) - total summary = (f"### Batch Complete\n" f"✅ **{total}** processed | ❌ **{failed}** failed\n\n" + "\n".join(status_lines)) return results_gallery, summary, zip_path # ── History ─────────────────────────────────────────────────────────────────── def ui_get_history(): """Return history gallery and summary.""" if not _session_history: return [], "No images processed yet this session." gallery = [item["result"] for item in _session_history] lines = [f"| `{it['name']}` | {it['model']} | {it['size']} | {it['time']} |" for it in _session_history] table = ("| File | Model | Size | Time |\n" "|------|-------|------|------|\n" + "\n".join(lines)) return gallery, f"### Session History ({len(_session_history)} images)\n\n" + table def ui_clear_history(): _session_history.clear() return [], "🗑️ History cleared." # ── Single image with background replacement ────────────────────────────────── def ui_remove_with_bg(api_key, image, choice, bg_choice, bg_color, bg_image): """Enhanced single removal with background replacement support.""" m = _CMAP.get(choice, "fast") result, meta, err = run_removal(api_key, image, m) if err: return None, f"❌ **Error:** {err}" final = _apply_background(result, bg_choice, bg_color, bg_image) # Save to history _session_history.append({ "name": "single_image", "time": utcnow().strftime("%H:%M:%S"), "model": meta["model"], "size": meta["output_size"], "result": final, }) return final, (f"✅ **Done** | `{meta['model']}` | `{meta['processing_time_sec']}s` | " f"`{meta['original_size']}` → `{meta['output_size']}` | calls: `{meta['calls_today']}`") # ═══════════════════════════════════════════════════════════════════════════════ # ── Gradio UI ───────────────────────────────────────────────────────────────── # ═══════════════════════════════════════════════════════════════════════════════ with gr.Blocks(title="FreeBG — Background Removal API") as demo: gr.HTML("""

🎨 FreeBG — Background Removal API

3 AI Models · API Key Auth · freebg.site

""") with gr.Tabs(): # ── TAB 1: Remove Background ────────────────────────────────────────── with gr.TabItem("🖼️ Remove Background"): with gr.Row(): with gr.Column(scale=1): t_key = gr.Textbox(label="🔑 API Key", placeholder="freebg-xxxx-xxxx (get one free below)", type="password") t_model = gr.Dropdown(label="Model — Quality vs Speed", choices=_CHOICES, value=_CHOICES[0]) t_img = gr.Image(label="Upload Image", type="pil") t_btn = gr.Button("✨ Remove Background", variant="primary", size="lg") with gr.Column(scale=1): t_out = gr.Image(label="Result — Transparent PNG", type="pil") t_info = gr.Markdown("*Upload an image and click Remove Background*") t_btn.click(ui_remove, [t_key, t_img, t_model], [t_out, t_info]) gr.Markdown(""" --- ### Get Your Free API Key | Plan | Daily Limit | Models | Price | |------|-------------|--------|-------| | **Free** | 10 images/day | fast only | **$0** | | **Starter** | 100 images/day | fast + quality | **$9/mo** | | **Pro** | 500 images/day | All 3 models | **$29/mo** | 📧 Email **support@freebg.site** to get your API key, or use the master key for testing. """) # ── TAB 2: My Usage ─────────────────────────────────────────────────── with gr.TabItem("📊 My Usage"): gr.Markdown("### Check Your API Key Usage") u_key = gr.Textbox(label="🔑 Your API Key", type="password", placeholder="freebg-xxxx-xxxx") u_btn = gr.Button("Check Usage", variant="secondary") u_out = gr.Markdown() u_btn.click(ui_usage, [u_key], [u_out]) # ── TAB 3: API Docs ─────────────────────────────────────────────────── with gr.TabItem("📚 API Docs"): gr.Markdown(f""" ## FreeBG REST API **Base URL:** `https://freebg-background-remover.hf.space` --- ### `POST /api/remove-bg` — Remove Background **Headers:** ``` X-API-Key: your-key-here ``` **Body (multipart/form-data):** | Field | Type | Default | Description | |-------|------|---------|-------------| | `image` | file | required | JPG/PNG/WEBP up to 20MB | | `model` | string | `fast` | `fast` · `quality` · `best` | | `max_size` | int | `0` | Resize longest edge (0=original) | **Returns:** `image/png` with transparent background **Response Headers:** ``` X-Model BRIA RMBG-2.0 X-Processing-Time 1.56 X-Original-Size 1200x896 X-Calls-Today 5 ``` --- ### `GET /api/usage` — Check Usage ``` Header: X-API-Key: your-key ``` ### `GET /api/models` — List Models ### `GET /health` — Health Check --- ### Code Examples **JavaScript:** ```javascript const fd = new FormData(); fd.append('image', imageFile); fd.append('model', 'fast'); // fast | quality | best const res = await fetch( 'https://freebg-background-remover.hf.space/api/remove-bg', {{ method: 'POST', headers: {{'X-API-Key': 'your-key'}}, body: fd }} ); const blob = await res.blob(); // transparent PNG const url = URL.createObjectURL(blob); ``` **Python:** ```python import requests r = requests.post( 'https://freebg-background-remover.hf.space/api/remove-bg', headers={{'X-API-Key': 'your-key'}}, files={{'image': open('photo.jpg', 'rb')}}, data={{'model': 'fast'}} ) open('result.png', 'wb').write(r.content) ``` **cURL:** ```bash curl -X POST https://freebg-background-remover.hf.space/api/remove-bg \\ -H "X-API-Key: your-key" \\ -F "image=@photo.jpg" \\ -F "model=fast" \\ --output result.png ``` """) # ── TAB 4: Admin Panel ──────────────────────────────────────────────── with gr.TabItem("🔐 Admin"): # Login section with gr.Column(visible=True) as admin_login_col: gr.Markdown("### 🔐 Admin Login") a_pwd = gr.Textbox(label="Admin Password", type="password", placeholder="Enter admin password") a_lbtn = gr.Button("Login", variant="primary") a_lerr = gr.Markdown() # Admin panel (hidden until login) with gr.Column(visible=False) as admin_panel_col: gr.Markdown("## 👑 FreeBG Admin Panel") with gr.Tabs(): # ── Add Customer ───────────────────────────────────────── with gr.TabItem("➕ Add Customer"): gr.Markdown("### Add New Customer\nFill in details → Generate Key → Key is automatically saved to HF Secret") with gr.Row(): a_email = gr.Textbox(label="Customer Email", placeholder="customer@gmail.com", scale=2) a_plan = gr.Dropdown(label="Plan", choices=["free","starter","pro","master"], value="starter", scale=1) a_limit = gr.Textbox(label="Custom Limit (optional)", placeholder="leave empty = plan default", scale=1) a_add_btn = gr.Button("✨ Generate API Key + Save to HF Secret", variant="primary", size="lg") a_add_out = gr.Markdown() a_new_key = gr.Textbox(label="🔑 Generated Key (copy & send to customer)", interactive=True) gr.HTML("""
⚠️ Key is automatically saved to HF Secret. Space restarts in ~30 seconds to apply the new key.
""") # ── Customer List ───────────────────────────────────────── with gr.TabItem("👥 Customers"): a_refresh_btn = gr.Button("🔄 Refresh List", size="sm") a_table = gr.Dataframe( headers=["API Key","Owner","Plan","Calls Today","Daily Limit","Created"], datatype=["str","str","str","number","number","str"], interactive=False, wrap=True ) gr.Markdown("---\n### ✏️ Edit / Upgrade Plan") with gr.Row(): a_edit_key = gr.Textbox(label="API Key to Edit", placeholder="freebg-xx-xxxxxxxx-xxxxxxxx") a_edit_plan = gr.Dropdown(label="New Plan", choices=["free","starter","pro","master"], value="pro") a_upgrade_btn = gr.Button("⬆️ Upgrade Plan + Save to HF Secret", variant="secondary") a_upgrade_out = gr.Markdown() gr.Markdown("---\n### 🗑️ Delete Key") with gr.Row(): a_del_key = gr.Textbox(label="API Key to Delete", placeholder="freebg-xx-xxxxxxxx-xxxxxxxx") a_del_btn = gr.Button("🗑️ Delete Key + Save to HF Secret", variant="stop") a_del_out = gr.Markdown() # ── Stats ────────────────────────────────────────────────── with gr.TabItem("📊 Stats"): a_stats_btn = gr.Button("🔄 Refresh Stats") a_stats_out = gr.Markdown() a_stats_btn.click(admin_stats, [], [a_stats_out]) # ── Export JSON ──────────────────────────────────────────── with gr.TabItem("📋 Export JSON"): gr.Markdown(""" ### Export API Keys JSON Use this if you want to manually paste into HF Secret. **Auto-update already happens** when you Add/Edit/Delete customers. """) a_export_btn = gr.Button("📋 Get Current JSON") a_export_out = gr.Code(language="json", interactive=False) a_export_btn.click(admin_export_json, [], [a_export_out]) # ── Login wiring ────────────────────────────────────────────────── a_lbtn.click( admin_login, inputs=[a_pwd], outputs=[admin_login_col, admin_panel_col, a_lerr, a_table, a_stats_out] ) a_pwd.submit( admin_login, inputs=[a_pwd], outputs=[admin_login_col, admin_panel_col, a_lerr, a_table, a_stats_out] ) # ── Admin actions wiring ────────────────────────────────────────── a_add_btn.click( admin_add_customer, inputs=[a_email, a_plan, a_limit], outputs=[a_add_out, a_table, a_new_key] ) a_refresh_btn.click(admin_refresh, [], [a_table]) a_upgrade_btn.click( admin_upgrade_plan, inputs=[a_edit_key, a_edit_plan], outputs=[a_upgrade_out, a_table] ) a_del_btn.click( admin_delete_customer, inputs=[a_del_key], outputs=[a_del_out, a_table] ) # ══════════════════════════════════════════════════════════════════════ # ── NEW TAB: Batch Processing ───────────────────────────────────────── # ══════════════════════════════════════════════════════════════════════ with gr.TabItem("📦 Batch Processing"): gr.Markdown(""" ### 📦 Batch Background Removal Upload multiple images — all processed in a queue with progress tracking. Results downloadable as a single ZIP file. """) with gr.Row(): with gr.Column(scale=1): b_key = gr.Textbox(label="🔑 API Key", type="password", placeholder="freebg-xxxx-xxxx") b_model = gr.Dropdown(label="Model", choices=_CHOICES, value=_CHOICES[0]) b_files = gr.File(label="📁 Upload Images (JPG/PNG/WEBP)", file_count="multiple", file_types=["image"]) # ── Background Options ───────────────────────────────────── gr.Markdown("#### 🎨 Background Option") b_bg_choice = gr.Radio( label="Output Background", choices=["transparent", "color", "custom_image"], value="transparent", info="transparent=PNG, color=solid fill, custom_image=paste your bg" ) b_bg_color = gr.ColorPicker(label="Background Color", value="#ffffff", visible=False) b_bg_image = gr.Image(label="Custom Background Image", type="pil", visible=False) b_btn = gr.Button("🚀 Start Batch Processing", variant="primary", size="lg") with gr.Column(scale=1): b_gallery = gr.Gallery(label="✅ Results", columns=3, object_fit="contain", height=360) b_status = gr.Markdown("*Upload images and click Start Batch*") b_zip = gr.File(label="⬇️ Download All as ZIP") # Show/hide color picker and custom bg image based on bg choice def _toggle_bg(choice): return (gr.update(visible=(choice == "color")), gr.update(visible=(choice == "custom_image"))) b_bg_choice.change(_toggle_bg, [b_bg_choice], [b_bg_color, b_bg_image]) b_btn.click( ui_batch_remove, inputs=[b_key, b_files, b_model, b_bg_choice, b_bg_color, b_bg_image], outputs=[b_gallery, b_status, b_zip] ) # ══════════════════════════════════════════════════════════════════════ # ── NEW TAB: Background Replacement (Single Image) ──────────────────── # ══════════════════════════════════════════════════════════════════════ with gr.TabItem("🎨 BG Replace"): gr.Markdown(""" ### 🎨 Background Replacement Remove background and instantly replace with a solid color or your own image. """) with gr.Row(): with gr.Column(scale=1): br_key = gr.Textbox(label="🔑 API Key", type="password", placeholder="freebg-xxxx-xxxx") br_model = gr.Dropdown(label="Model", choices=_CHOICES, value=_CHOICES[0]) br_img = gr.Image(label="Upload Image", type="pil") gr.Markdown("#### 🎨 New Background") br_bg_choice = gr.Radio( label="Background Type", choices=["transparent", "color", "custom_image"], value="color" ) br_bg_color = gr.ColorPicker(label="Color", value="#ffffff") br_bg_image = gr.Image(label="Your Background Image", type="pil", visible=False) br_btn = gr.Button("✨ Remove & Replace Background", variant="primary", size="lg") with gr.Column(scale=1): br_out = gr.Image(label="Result", type="pil") br_info = gr.Markdown("*Upload an image and choose a background*") def _br_toggle(choice): return (gr.update(visible=(choice == "color")), gr.update(visible=(choice == "custom_image"))) br_bg_choice.change(_br_toggle, [br_bg_choice], [br_bg_color, br_bg_image]) br_btn.click( ui_remove_with_bg, inputs=[br_key, br_img, br_model, br_bg_choice, br_bg_color, br_bg_image], outputs=[br_out, br_info] ) # ══════════════════════════════════════════════════════════════════════ # ── NEW TAB: Session History ──────────────────────────────────════════ # ══════════════════════════════════════════════════════════════════════ with gr.TabItem("🕒 History"): gr.Markdown(""" ### 🕒 Session History All images processed during this session — from any tab. """) with gr.Row(): h_refresh_btn = gr.Button("🔄 Refresh History", variant="secondary") h_clear_btn = gr.Button("🗑️ Clear History", variant="stop") h_gallery = gr.Gallery(label="Processed Images", columns=4, object_fit="contain", height=400) h_status = gr.Markdown("*Click Refresh to load history*") h_refresh_btn.click(ui_get_history, [], [h_gallery, h_status]) h_clear_btn.click(ui_clear_history, [], [h_gallery, h_status]) gr.HTML("""
FreeBG.site · U2-Net · BiRefNet · BRIA RMBG-2.0 · freebg.site
""") # ── FastAPI Routes ──────────────────────────────────────────────────────────── from fastapi import Request from fastapi.responses import Response, JSONResponse from fastapi.middleware.cors import CORSMiddleware demo.app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["GET","POST","OPTIONS"], allow_headers=["*"], expose_headers=["X-Model","X-Processing-Time","X-Original-Size","X-Output-Size","X-Calls-Today"], ) @demo.app.post("/api/remove-bg") async def api_remove_bg(request: Request): x_api_key = request.headers.get("x-api-key") or request.headers.get("X-API-Key") if not x_api_key: return JSONResponse({"success":False,"error":"X-API-Key header required"}, status_code=401) try: form = await request.form() model = str(form.get("model","fast")) max_size = int(form.get("max_size",0)) img_file = form.get("image") if not img_file: return JSONResponse({"success":False,"error":"'image' field required"}, status_code=400) pil = Image.open(io.BytesIO(await img_file.read())).convert("RGB") except Exception as e: return JSONResponse({"success":False,"error":f"Bad request: {e}"}, status_code=400) if model not in MODEL_FN: return JSONResponse({"success":False,"error":f"Invalid model. Choose: {list(MODEL_FN)}"}, status_code=400) result, meta, err = run_removal(x_api_key, pil, model, max_size) if err: code = 401 if any(w in err.lower() for w in ("key","limit","plan","upgrade")) else 500 return JSONResponse({"success":False,"error":err}, status_code=code) buf = io.BytesIO(); result.save(buf,"PNG",optimize=True); buf.seek(0) return Response(buf.read(), media_type="image/png", headers={ "X-Model": meta["model"], "X-Processing-Time": str(meta["processing_time_sec"]), "X-Original-Size": meta["original_size"], "X-Output-Size": meta["output_size"], "X-Calls-Today": str(meta["calls_today"]), }) @demo.app.get("/api/usage") async def api_usage(request: Request): key = request.headers.get("x-api-key") or request.headers.get("X-API-Key") if not key: return JSONResponse({"error":"X-API-Key required"}, status_code=401) info = get_usage(key) return JSONResponse({"success":"error" not in info, **info}, status_code=200 if "error" not in info else 401) @demo.app.get("/api/models") async def api_models(): return {"success":True,"models":{k:v for k,v in MODEL_INFO.items()}} @demo.app.get("/health") async def health(): return {"status":"ok","hf_token":"set" if HF_TOKEN else "missing", "models_ready":[k for k in _cache if not k.endswith("_tf")], "timestamp":utcnow().isoformat()} # ── Launch ──────────────────────────────────────────────────────────────────── if __name__ == "__main__": logger.info("Preloading U2-Net...") load_u2net() demo.launch( server_name="0.0.0.0", server_port=7860, show_error=True, ssr_mode=False, )