freebg's picture
Update app.py
1ac3e89 verified
Raw
History Blame Contribute Delete
45.1 kB
"""
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"],
)
@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,
)