import os import io import json import requests import tempfile from datetime import date from fastapi import FastAPI, UploadFile, File, HTTPException, Header from fastapi.responses import StreamingResponse from fastapi.middleware.cors import CORSMiddleware from gradio_client import Client, handle_file # --- KONFIGURASI (Silakan sesuaikan) --- # 1. API Key dari remove.bg (dari screenshot Anda) # SANGAT DISARANKAN untuk mengatur ini sebagai environment variable di platform deploy Anda. REMOVEBG_API_KEY = os.getenv("REMOVEBG_API_KEY") # 2. Nama Gradio Space untuk fallback BRIA_SPACE = "briaai/BRIA-RMBG-2.0" # Ganti jika Anda punya nama Space Bria yang lain MYSTYC_SPACE = "Mystyc/remove-bg-rmbg" # Space yang Anda gunakan sekarang # 3. Pengaturan Jatah Harian untuk remove.bg DAILY_QUOTA_LIMIT = 2 QUOTA_FILE = os.path.join(tempfile.gettempdir(), "quota.json") # --- Inisialisasi Aplikasi FastAPI --- app = FastAPI(title="Dynamic Fallback RMBG Proxy") app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]) # --- FUNGSI-FUNGSI PEMBANTU --- def _check_and_update_quota(): """Membaca file quota, memeriksa apakah jatah masih ada, dan mengupdate jika perlu.""" today = str(date.today()) quota_data = {"date": today, "count": 0} if os.path.exists(QUOTA_FILE): try: with open(QUOTA_FILE, "r") as f: data = json.load(f) # Jika tanggal sudah berbeda, reset jatah if data.get("date") == today: quota_data = data else: print(f"Tanggal baru terdeteksi. Mereset jatah harian untuk {today}.") except (json.JSONDecodeError, FileNotFoundError): print("File quota rusak atau tidak ditemukan, membuat yang baru.") with open(QUOTA_FILE, "w") as f: json.dump(quota_data, f) if quota_data["count"] < DAILY_QUOTA_LIMIT: return True else: print(f"Jatah harian remove.bg ({DAILY_QUOTA_LIMIT}) untuk tanggal {today} sudah habis.") return False def _increment_quota_count(): """Menambah hitungan jatah setelah pemanggilan API remove.bg berhasil.""" try: with open(QUOTA_FILE, "r+") as f: data = json.load(f) data["count"] += 1 f.seek(0) json.dump(data, f) f.truncate() print(f"Jatah remove.bg digunakan: {data['count']}/{DAILY_QUOTA_LIMIT}") except Exception as e: print(f"Gagal menambah hitungan jatah: {e}") def _predict_from_removebg_api(image_blob: bytes): """Memanggil API resmi remove.bg.""" print("Mencoba Prioritas 1: remove.bg API...") response = requests.post( 'https://api.remove.bg/v1/removebg', files={'image_file': image_blob}, headers={'X-Api-Key': REMOVEBG_API_KEY}, timeout=180 ) response.raise_for_status() # Akan error jika status bukan 2xx _increment_quota_count() # Tambah hitungan jika berhasil print("✅ Sukses menggunakan remove.bg API.") return StreamingResponse(io.BytesIO(response.content), media_type="image/png") # GANTI SELURUH FUNGSI LAMA DENGAN YANG INI def _predict_from_gradio_space(local_path: str, space_name: str, api_name: str): """Fungsi yang direfaktor untuk memanggil Gradio Space manapun dengan api_name dinamis.""" print(f"Mencoba Prioritas Fallback: Gradio Space '{space_name}' dengan API '{api_name}'...") client = Client(src=space_name) result = client.predict(image=handle_file(local_path), api_name=api_name) # --- LOGIKA BARU UNTUK MEMBACA HASIL --- image_path = None # Jika hasilnya adalah list atau tuple (seperti di Mystyc Space yang baru) if isinstance(result, (list, tuple)) and len(result) > 0: # Ambil elemen PERTAMA (indeks 0) sebagai path gambar image_path = result[0] # Jika hasilnya hanya string (seperti di Gradio Space yang lama) elif isinstance(result, str): image_path = result # Lanjutkan proses jika path gambar valid if image_path and isinstance(image_path, str) and os.path.exists(image_path): with open(image_path, "rb") as f: print(f"✅ Sukses menggunakan Gradio Space '{space_name}'.") return StreamingResponse(io.BytesIO(f.read()), media_type="image/png") # Jika format tidak dikenali sama sekali raise ValueError(f"Output dari Gradio Space '{space_name}' tidak dikenali: {repr(result)}") # --- ENDPOINTS --- @app.get("/health") def health(): return {"ok": True, "services": {"primary": "remove.bg", "fallback_1": BRIA_SPACE, "fallback_2": MYSTYC_SPACE}} @app.post("/remove_bg", response_class=StreamingResponse) async def remove_bg(file: UploadFile = File(...)): blob = await file.read() if not blob: raise HTTPException(400, "File kosong") # --- LOGIKA FALLBACK DIMULAI DI SINI --- # 1. Coba remove.bg API jika jatah masih ada if REMOVEBG_API_KEY != "MASUKKAN_API_KEY_REMOVEBG_ANDA_DI_SINI" and _check_and_update_quota(): try: return _predict_from_removebg_api(blob) except Exception as e: print(f"⚠️ Gagal dengan remove.bg API: {e}. Mencoba fallback berikutnya.") # Jika remove.bg gagal atau jatah habis, buat file sementara untuk Gradio suffix = os.path.splitext(file.filename or ".png")[1] tmp_path = None try: with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp: tmp.write(blob) tmp_path = tmp.name # 2. Coba Bria RM-BG try: return _predict_from_gradio_space(tmp_path, BRIA_SPACE, api_name="/image") except Exception as e: print(f"⚠️ Gagal dengan Gradio Space '{BRIA_SPACE}': {e}. Mencoba fallback terakhir.") # 3. Coba Mystyc RM-BG try: return _predict_from_gradio_space(tmp_path, MYSTYC_SPACE, api_name="/predict") except Exception as e: print(f"⚠️ Gagal dengan Gradio Space '{MYSTYC_SPACE}': {e}. Semua layanan gagal.") raise HTTPException(status_code=503, detail=f"Semua layanan penghapus background tidak tersedia. Error terakhir: {e}") finally: # Selalu hapus file sementara if tmp_path and os.path.exists(tmp_path): os.remove(tmp_path)