Spaces:
Running
Running
| """ | |
| 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(""" | |
| <style> | |
| .warn-box {background:#1a1008; border:1px solid #ffc850; border-radius:8px; | |
| padding:10px; font-size:13px; color:#ffc850; margin-top:8px} | |
| </style> | |
| <div style="text-align:center;padding:18px 0 8px"> | |
| <h1 style="font-size:26px;font-weight:900;margin:0;letter-spacing:-1px"> | |
| π¨ FreeBG β Background Removal API | |
| </h1> | |
| <p style="color:#888;font-size:13px;margin:6px 0 0"> | |
| 3 AI Models Β· API Key Auth Β· | |
| <a href="https://freebg.site" target="_blank" style="color:#00e5a0">freebg.site</a> | |
| </p> | |
| </div>""") | |
| 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("""<div class="warn-box"> | |
| β οΈ Key is automatically saved to HF Secret. | |
| Space restarts in ~30 seconds to apply the new key. | |
| </div>""") | |
| # ββ 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(""" | |
| <div style="text-align:center;padding:10px;font-size:11px;color:#555;margin-top:8px"> | |
| FreeBG.site Β· U2-Net Β· BiRefNet Β· BRIA RMBG-2.0 Β· | |
| <a href="https://freebg.site" style="color:#555">freebg.site</a> | |
| </div>""") | |
| # ββ 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"], | |
| ) | |
| 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"]), | |
| }) | |
| 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) | |
| async def api_models(): | |
| return {"success":True,"models":{k:v for k,v in MODEL_INFO.items()}} | |
| 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, | |
| ) |