Spaces:
Sleeping
Sleeping
Refactor for Headshort and Scene Generation using Instant-ID model hosted in Replicate
Browse files- .env.example +16 -0
- .gitignore +4 -1
- README.md +153 -65
- app.py +241 -227
- app.py.wip_avatar +0 -423
- config.yaml +130 -0
- env.example +13 -0
- requirements.txt +8 -46
- src/rateLimiter.py +0 -0
- src/replicateHandler.py +90 -0
.env.example
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ==========================================
|
| 2 |
+
# FaceForge AI - Environment Configuration
|
| 3 |
+
# Copy this file to .env and update values
|
| 4 |
+
# ==========================================
|
| 5 |
+
|
| 6 |
+
# Dev Mode (optional)
|
| 7 |
+
# false = 5 generations/day (default)
|
| 8 |
+
# true = 10 generations/day (for development/testing)
|
| 9 |
+
DEV_MODE=false
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
# Replicate API Token (required)
|
| 14 |
+
# Get yours at: https://replicate.com/account/api-tokens
|
| 15 |
+
REPLICATE_API_TOKEN=your_token_here
|
| 16 |
+
|
.gitignore
CHANGED
|
@@ -1 +1,4 @@
|
|
| 1 |
-
roadmap/
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
roadmap/
|
| 2 |
+
output/
|
| 3 |
+
prompts
|
| 4 |
+
*.bak
|
README.md
CHANGED
|
@@ -1,67 +1,61 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
emoji: 🐨
|
| 4 |
-
colorFrom: pink
|
| 5 |
-
colorTo: pink
|
| 6 |
-
sdk: gradio
|
| 7 |
-
sdk_version: 5.49.1
|
| 8 |
-
app_file: app.py
|
| 9 |
-
pinned: false
|
| 10 |
-
license: mit
|
| 11 |
-
short_description: FaceForgeAI_ZeroGPU
|
| 12 |
-
---
|
| 13 |
|
| 14 |
-
|
| 15 |
-
|
| 16 |
|
| 17 |
-
**Author:** Vijay S. Chaudhari
|
| 18 |
-
**Runtime:** Hugging Face Spaces (ZeroGPU) 🚀
|
| 19 |
-
|
| 20 |
---
|
| 21 |
|
| 22 |
-
## 🧠 Overview
|
| 23 |
-
**FaceForge AI** transforms your uploaded photo into professional-quality images powered by
|
| 24 |
-
-
|
| 25 |
-
-
|
| 26 |
-
-
|
| 27 |
|
| 28 |
-
This edition
|
| 29 |
|
| 30 |
---
|
| 31 |
|
| 32 |
-
##
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
- **
|
| 36 |
-
- **
|
|
|
|
| 37 |
|
| 38 |
-
✅ **User
|
| 39 |
-
- Preset prompt styles
|
| 40 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
---
|
| 43 |
|
| 44 |
-
## 🧩 Tech Stack
|
| 45 |
|
| 46 |
| Component | Purpose |
|
| 47 |
-
|
| 48 |
| **Python 3.10+** | Core runtime |
|
| 49 |
| **Gradio 4.x** | Web UI framework |
|
| 50 |
-
| **
|
| 51 |
-
| **
|
| 52 |
-
| **Real-ESRGAN 0.3.0** | Super-resolution |
|
| 53 |
| **Rembg 2.x** | Background removal |
|
| 54 |
-
| **
|
| 55 |
-
|
| 56 |
|
| 57 |
---
|
| 58 |
|
| 59 |
-
## 🧰 Installation
|
| 60 |
|
| 61 |
### 1️⃣ Clone Repository
|
| 62 |
```bash
|
| 63 |
git clone https://github.com/agentofAI/FaceForgeAI.git
|
| 64 |
-
cd
|
| 65 |
```
|
| 66 |
|
| 67 |
### 2️⃣ Install Dependencies
|
|
@@ -69,7 +63,15 @@ cd FaceForgeAI_ZeroGPU
|
|
| 69 |
pip install -r requirements.txt
|
| 70 |
```
|
| 71 |
|
| 72 |
-
### 3️⃣
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
```bash
|
| 74 |
python app.py
|
| 75 |
```
|
|
@@ -77,46 +79,132 @@ Open the local Gradio URL (typically http://127.0.0.1:7860) in your browser.
|
|
| 77 |
|
| 78 |
---
|
| 79 |
|
| 80 |
-
## 🎨
|
| 81 |
|
| 82 |
-
|
| 83 |
-
|--------------|--------|
|
| 84 |
-
| 🎬 **Cinematic Portrait** | highly detailed, digital portrait, professional lighting, cinematic style, artistic AI avatar |
|
| 85 |
-
| 🎨 **Stylized Realism** | stylized yet realistic portrait, balanced lighting, subtle gradient background, sharp focus on face |
|
| 86 |
-
| 🏢 **Studio Professional** | studio portrait, even lighting, neutral background, realistic skin, confident pose |
|
| 87 |
-
| 🤵 **Natural Headshot** | realistic professional headshot, soft studio lighting, neutral background, crisp details, natural skin tone |
|
| 88 |
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
|
| 94 |
-
|
| 95 |
-
| **
|
| 96 |
-
| **
|
| 97 |
-
| **
|
| 98 |
-
| **Gradio** | [gradio-app/gradio](https://github.com/gradio-app/gradio) |
|
| 99 |
|
| 100 |
---
|
| 101 |
|
| 102 |
-
##
|
|
|
|
| 103 |
```
|
| 104 |
faceforge-ai/
|
| 105 |
│
|
| 106 |
-
├── app.py
|
| 107 |
-
├──
|
| 108 |
-
├──
|
| 109 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
```
|
| 111 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
---
|
| 113 |
|
| 114 |
## 📜 License
|
| 115 |
|
| 116 |
-
This project is for educational and demonstration purposes.
|
| 117 |
Each model used retains its original open-source license.
|
| 118 |
|
| 119 |
---
|
| 120 |
-
## 👨💻 Author
|
| 121 |
|
| 122 |
-
Vijay S. Chaudhari
|
|
|
|
|
|
| 1 |
+
# 🎨 FaceForge AI
|
| 2 |
+
[](https://huggingface.co/spaces/VcRlAgent/FaceForgeAI)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
+
**Author:** Vijay S. Chaudhari
|
| 5 |
+
**Runtime:** Gradio + Replicate API 🚀
|
| 6 |
|
|
|
|
|
|
|
|
|
|
| 7 |
---
|
| 8 |
|
| 9 |
+
## 🧠 Overview
|
| 10 |
+
**FaceForge AI** transforms your uploaded photo into professional-quality images powered by AI:
|
| 11 |
+
- 🎭 **Background Remover** – clean background removal for any image
|
| 12 |
+
- 💼 **Professional Headshots** – studio-quality portraits in multiple styles
|
| 13 |
+
- 🌍 **Scene Changer** – place yourself in different environments with identity preservation
|
| 14 |
|
| 15 |
+
This edition uses **Replicate's InstantID** for high-quality identity-preserving transformations with configurable rate limiting for cost control.
|
| 16 |
|
| 17 |
---
|
| 18 |
|
| 19 |
+
## ✨ Key Features
|
| 20 |
+
|
| 21 |
+
✅ **Three Generation Modes**
|
| 22 |
+
- **Background Remover** – removes background using Rembg
|
| 23 |
+
- **Professional Headshots** – Studio, Office, Premium Editorial styles
|
| 24 |
+
- **Scene Changer** – Coastal Run, Urban Evening, Forest Trail environments
|
| 25 |
|
| 26 |
+
✅ **User Controls**
|
| 27 |
+
- Preset prompt styles via dropdown
|
| 28 |
+
- Optional advanced settings (CFG, Steps, Denoise)
|
| 29 |
+
- Real-time usage tracking
|
| 30 |
+
|
| 31 |
+
✅ **Rate Limiting**
|
| 32 |
+
- 5 generations/day (standard mode)
|
| 33 |
+
- 10 generations/day (dev mode)
|
| 34 |
+
- Midnight UTC reset
|
| 35 |
+
- Device-based tracking
|
| 36 |
|
| 37 |
---
|
| 38 |
|
| 39 |
+
## 🧩 Tech Stack
|
| 40 |
|
| 41 |
| Component | Purpose |
|
| 42 |
+
|-----------|---------|
|
| 43 |
| **Python 3.10+** | Core runtime |
|
| 44 |
| **Gradio 4.x** | Web UI framework |
|
| 45 |
+
| **Replicate API** | InstantID model hosting |
|
| 46 |
+
| **InstantID** | Identity-preserving image generation |
|
|
|
|
| 47 |
| **Rembg 2.x** | Background removal |
|
| 48 |
+
| **PyYAML** | Configuration management |
|
| 49 |
+
| **Pillow** | Image processing |
|
| 50 |
|
| 51 |
---
|
| 52 |
|
| 53 |
+
## 🧰 Installation
|
| 54 |
|
| 55 |
### 1️⃣ Clone Repository
|
| 56 |
```bash
|
| 57 |
git clone https://github.com/agentofAI/FaceForgeAI.git
|
| 58 |
+
cd FaceForgeAI
|
| 59 |
```
|
| 60 |
|
| 61 |
### 2️⃣ Install Dependencies
|
|
|
|
| 63 |
pip install -r requirements.txt
|
| 64 |
```
|
| 65 |
|
| 66 |
+
### 3️⃣ Configure Environment
|
| 67 |
+
```bash
|
| 68 |
+
cp .env.example .env
|
| 69 |
+
# Edit .env and add your REPLICATE_API_TOKEN
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
Get your Replicate API token at: https://replicate.com/account/api-tokens
|
| 73 |
+
|
| 74 |
+
### 4️⃣ Run Locally
|
| 75 |
```bash
|
| 76 |
python app.py
|
| 77 |
```
|
|
|
|
| 79 |
|
| 80 |
---
|
| 81 |
|
| 82 |
+
## 🎨 Style Presets
|
| 83 |
|
| 84 |
+
### Professional Headshots
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
+
| Style | Description |
|
| 87 |
+
|-------|-------------|
|
| 88 |
+
| 📸 **Studio Headshot** | Professional editorial with neutral background, magazine quality |
|
| 89 |
+
| 🏢 **Office Setting** | Subtle office background, premium business attire |
|
| 90 |
+
| ✨ **Premium Editorial** | High-end minimal studio, composed presence |
|
| 91 |
|
| 92 |
+
### Scene Changer
|
| 93 |
+
|
| 94 |
+
| Scene | Description |
|
| 95 |
+
|-------|-------------|
|
| 96 |
+
| 🏖️ **Coastal Run** | Ocean and horizon background, warm sunrise lighting |
|
| 97 |
+
| 🌃 **Urban Evening** | City lights, soft ambient night lighting |
|
| 98 |
+
| 🌲 **Forest Trail** | Trees and dirt path, diffused outdoor light |
|
|
|
|
| 99 |
|
| 100 |
---
|
| 101 |
|
| 102 |
+
## 📁 Project Structure
|
| 103 |
+
|
| 104 |
```
|
| 105 |
faceforge-ai/
|
| 106 |
│
|
| 107 |
+
├── app.py # Main Gradio application
|
| 108 |
+
├── config.yaml # Prompts and settings configuration
|
| 109 |
+
├── rate_limiter.py # Rate limiting logic
|
| 110 |
+
├── replicate_handler.py # Replicate API wrapper
|
| 111 |
+
├── requirements.txt # Python dependencies
|
| 112 |
+
├── .env.example # Environment variables template
|
| 113 |
+
├── rate_limits.json # Auto-generated usage tracking
|
| 114 |
+
└── README.md # Documentation
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
---
|
| 118 |
+
|
| 119 |
+
## ⚙️ Configuration
|
| 120 |
+
|
| 121 |
+
### Rate Limits (config.yaml)
|
| 122 |
+
```yaml
|
| 123 |
+
rate_limit:
|
| 124 |
+
default_daily_limit: 5 # Standard mode
|
| 125 |
+
dev_daily_limit: 10 # Dev mode
|
| 126 |
+
reset_timezone: "UTC" # Reset at midnight UTC
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
### Dev Mode
|
| 130 |
+
Enable higher rate limits in `.env`:
|
| 131 |
+
```bash
|
| 132 |
+
DEV_MODE=true
|
| 133 |
```
|
| 134 |
|
| 135 |
+
### Add Custom Styles
|
| 136 |
+
Edit `config.yaml` to add new headshot or scene styles:
|
| 137 |
+
|
| 138 |
+
```yaml
|
| 139 |
+
headshots:
|
| 140 |
+
"🎯 Your Style":
|
| 141 |
+
prompt: |
|
| 142 |
+
Your custom prompt here...
|
| 143 |
+
multiple lines supported
|
| 144 |
+
negative: "Things to avoid..."
|
| 145 |
+
|
| 146 |
+
scenes:
|
| 147 |
+
"🎪 Your Scene":
|
| 148 |
+
prompt: "Scene description..."
|
| 149 |
+
negative: "Things to avoid..."
|
| 150 |
+
```
|
| 151 |
+
|
| 152 |
+
### Advanced Settings
|
| 153 |
+
Toggle "Enable Advanced Settings" in UI to control:
|
| 154 |
+
- **CFG Scale**: Prompt adherence (1.0-10.0)
|
| 155 |
+
- **Steps**: Generation quality (20-50)
|
| 156 |
+
- **Denoise**: Transformation strength (0.5-1.0)
|
| 157 |
+
|
| 158 |
+
---
|
| 159 |
+
|
| 160 |
+
## 📊 Rate Limiting
|
| 161 |
+
|
| 162 |
+
- **Tracking**: Server-side session file per device
|
| 163 |
+
- **Reset**: Midnight UTC daily
|
| 164 |
+
- **Scope**: Shared across all generation functions
|
| 165 |
+
- **Override**: Dev mode doubles the limit
|
| 166 |
+
|
| 167 |
+
Device fingerprinting uses IP + User-Agent for consistent tracking.
|
| 168 |
+
|
| 169 |
+
---
|
| 170 |
+
|
| 171 |
+
## 🧾 Model Credits
|
| 172 |
+
|
| 173 |
+
| Model | Source / License |
|
| 174 |
+
|-------|------------------|
|
| 175 |
+
| **InstantID** | [zsxkib/instant-id-basic](https://replicate.com/zsxkib/instant-id-basic) via Replicate |
|
| 176 |
+
| **Rembg** | [danielgatis/rembg](https://github.com/danielgatis/rembg) (MIT License) |
|
| 177 |
+
| **Gradio** | [gradio-app/gradio](https://github.com/gradio-app/gradio) (Apache 2.0) |
|
| 178 |
+
|
| 179 |
+
---
|
| 180 |
+
|
| 181 |
+
## 🐛 Troubleshooting
|
| 182 |
+
|
| 183 |
+
**"REPLICATE_API_TOKEN not found"**
|
| 184 |
+
- Verify `.env` file exists with valid token
|
| 185 |
+
- Check token at https://replicate.com/account/api-tokens
|
| 186 |
+
|
| 187 |
+
**"Daily limit reached"**
|
| 188 |
+
- Wait for midnight UTC reset
|
| 189 |
+
- Enable `DEV_MODE=true` for higher limits
|
| 190 |
+
|
| 191 |
+
**Generation fails**
|
| 192 |
+
- Verify image uploaded successfully
|
| 193 |
+
- Check Replicate API status
|
| 194 |
+
- Ensure internet connectivity
|
| 195 |
+
|
| 196 |
+
**Rate limits not working**
|
| 197 |
+
- Delete `rate_limits.json` to reset tracking
|
| 198 |
+
- Verify server-side file permissions
|
| 199 |
+
|
| 200 |
---
|
| 201 |
|
| 202 |
## 📜 License
|
| 203 |
|
| 204 |
+
This project is for educational and demonstration purposes.
|
| 205 |
Each model used retains its original open-source license.
|
| 206 |
|
| 207 |
---
|
|
|
|
| 208 |
|
| 209 |
+
Author: Vijay S. Chaudhari
|
| 210 |
+
© 2025 Vijay S. Chaudhari
|
app.py
CHANGED
|
@@ -1,180 +1,149 @@
|
|
| 1 |
-
# ==========================================
|
| 2 |
-
# FaceForge AI – ZeroGPU Gradio Version
|
| 3 |
-
# Author: Vijay S. Chaudhari | 2025
|
| 4 |
-
# ==========================================
|
| 5 |
-
|
| 6 |
import gradio as gr
|
| 7 |
-
import
|
| 8 |
import torch
|
| 9 |
-
import
|
| 10 |
-
import numpy as np
|
| 11 |
-
from PIL import Image, ImageEnhance, ImageOps
|
| 12 |
from rembg import remove
|
| 13 |
-
from
|
| 14 |
-
from
|
| 15 |
-
import io
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
import torchvision
|
| 19 |
-
print("Printing Torch and TorchVision versions:")
|
| 20 |
-
print(torch.__version__)
|
| 21 |
-
print(torchvision.__version__)
|
| 22 |
-
|
| 23 |
-
# GPU libraries
|
| 24 |
-
from gfpgan import GFPGANer
|
| 25 |
-
from basicsr.archs.rrdbnet_arch import RRDBNet
|
| 26 |
-
from realesrgan import RealESRGANer
|
| 27 |
|
| 28 |
-
accelerator = Accelerator()
|
| 29 |
|
| 30 |
# ------------------------------------------
|
| 31 |
-
#
|
| 32 |
# ------------------------------------------
|
| 33 |
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 37 |
-
|
| 38 |
-
# RealESRGAN upsampler
|
| 39 |
-
model = RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=23, num_grow_ch=32, scale=2)
|
| 40 |
-
upsampler = RealESRGANer(
|
| 41 |
-
scale=2,
|
| 42 |
-
model_path='https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.1/RealESRGAN_x2plus.pth',
|
| 43 |
-
model=model,
|
| 44 |
-
tile=400,
|
| 45 |
-
tile_pad=10,
|
| 46 |
-
pre_pad=0,
|
| 47 |
-
half=True,
|
| 48 |
-
device=device
|
| 49 |
-
)
|
| 50 |
-
|
| 51 |
-
# GFPGAN enhancer
|
| 52 |
-
face_enhancer = GFPGANer(
|
| 53 |
-
model_path='https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.3.pth',
|
| 54 |
-
upscale=2,
|
| 55 |
-
arch='clean',
|
| 56 |
-
channel_multiplier=2,
|
| 57 |
-
bg_upsampler=upsampler,
|
| 58 |
-
device=device
|
| 59 |
-
)
|
| 60 |
-
|
| 61 |
-
# Stable Diffusion Img2Img pipeline (public model)
|
| 62 |
-
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 63 |
-
if device == "cuda":
|
| 64 |
-
sd_pipe = StableDiffusionImg2ImgPipeline.from_pretrained(
|
| 65 |
-
"runwayml/stable-diffusion-v1-5",
|
| 66 |
-
torch_dtype=torch.float16
|
| 67 |
-
).to(device)
|
| 68 |
-
|
| 69 |
-
# Optimize for ZeroGPU memory
|
| 70 |
-
sd_pipe.enable_attention_slicing()
|
| 71 |
-
sd_pipe.enable_model_cpu_offload()
|
| 72 |
-
|
| 73 |
-
else:
|
| 74 |
-
sd_pipe = StableDiffusionImg2ImgPipeline.from_pretrained(
|
| 75 |
-
"runwayml/stable-diffusion-v1-5").to(device)
|
| 76 |
-
sd_pipe.to(device)
|
| 77 |
|
| 78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
|
| 80 |
-
|
| 81 |
-
|
|
|
|
|
|
|
| 82 |
|
| 83 |
# ------------------------------------------
|
| 84 |
-
#
|
| 85 |
# ------------------------------------------
|
| 86 |
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
only_center_face=False,
|
| 97 |
-
paste_back=True,
|
| 98 |
-
weight=0.5
|
| 99 |
-
)
|
| 100 |
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
# ------------------------------------------
|
| 105 |
-
# Image Processing Functions
|
| 106 |
-
# ------------------------------------------
|
| 107 |
-
|
| 108 |
-
def enhance_image(img: Image.Image) -> Image.Image:
|
| 109 |
-
"""Basic enhancement"""
|
| 110 |
-
img = ImageEnhance.Contrast(img).enhance(1.15)
|
| 111 |
-
img = ImageEnhance.Sharpness(img).enhance(1.1)
|
| 112 |
-
return img
|
| 113 |
-
|
| 114 |
-
@spaces.GPU
|
| 115 |
-
def create_headshot(img: Image.Image) -> Image.Image:
|
| 116 |
-
"""Professional headshot with gradient background"""
|
| 117 |
-
# Enhance face
|
| 118 |
-
img_enhanced = enhance_face(img)
|
| 119 |
|
| 120 |
-
#
|
| 121 |
-
|
|
|
|
|
|
|
| 122 |
|
| 123 |
-
#
|
| 124 |
-
|
| 125 |
-
if
|
| 126 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
|
| 136 |
-
#
|
| 137 |
-
|
|
|
|
|
|
|
| 138 |
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
img_no_bg.thumbnail((550, 550), Image.Resampling.LANCZOS)
|
| 142 |
-
offset = ((600 - img_no_bg.width) // 2, (600 - img_no_bg.height) // 2)
|
| 143 |
|
| 144 |
-
|
| 145 |
-
|
|
|
|
|
|
|
| 146 |
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
|
|
|
| 154 |
|
| 155 |
-
#
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
|
| 164 |
-
with torch.autocast("cuda"):
|
| 165 |
-
result = sd_pipe(prompt=prompt, image=img_resized, strength=strength, guidance_scale=guidance_scale)
|
| 166 |
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
|
|
|
| 170 |
|
| 171 |
-
@spaces.GPU
|
| 172 |
-
def process_all(img: Image.Image):
|
| 173 |
-
"""Process all three types at once"""
|
| 174 |
-
headshot = create_headshot(img)
|
| 175 |
-
passport = create_passport(img)
|
| 176 |
-
avatar = create_avatar(img)
|
| 177 |
-
return headshot, passport, avatar
|
| 178 |
|
| 179 |
# ------------------------------------------
|
| 180 |
# Gradio Interface
|
|
@@ -184,98 +153,143 @@ with gr.Blocks(theme=gr.themes.Soft(), title="FaceForge AI") as demo:
|
|
| 184 |
gr.Markdown(
|
| 185 |
"""
|
| 186 |
# 🎨 FaceForge AI
|
| 187 |
-
###
|
| 188 |
-
Upload your photo and
|
| 189 |
"""
|
| 190 |
)
|
| 191 |
-
|
| 192 |
-
#
|
| 193 |
-
|
| 194 |
-
"
|
| 195 |
-
|
| 196 |
-
"
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
with gr.Row():
|
|
|
|
| 201 |
with gr.Column(scale=1):
|
| 202 |
input_image = gr.Image(type="pil", label="📷 Upload Your Photo")
|
| 203 |
-
|
| 204 |
-
gr.Markdown("### ⚙️
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
choices=list(PROMPT_MAP.keys()),
|
| 210 |
-
value="🤵 Natural Headshot"
|
| 211 |
)
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
)
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
)
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
)
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
def run_avatar(img, preset_label, custom, strength, guidance):
|
| 257 |
-
final_prompt = custom.strip() if custom and custom.strip() != "" else PROMPT_MAP[preset_label]
|
| 258 |
-
return create_avatar(img, final_prompt, strength, guidance)
|
| 259 |
-
|
| 260 |
-
# --- Button actions ---
|
| 261 |
-
btn_headshot.click(fn=run_headshot, inputs=[input_image], outputs=[output_headshot])
|
| 262 |
-
btn_passport.click(fn=run_passport, inputs=[input_image], outputs=[output_passport])
|
| 263 |
-
btn_avatar.click(
|
| 264 |
-
fn=run_avatar,
|
| 265 |
-
inputs=[input_image, preset_prompt, custom_prompt, strength_slider, guidance_slider],
|
| 266 |
-
outputs=[output_avatar]
|
| 267 |
)
|
| 268 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
gr.Markdown(
|
| 270 |
"""
|
| 271 |
---
|
| 272 |
-
### Features
|
| 273 |
-
-
|
| 274 |
-
-
|
| 275 |
-
-
|
| 276 |
-
- ⚡ **
|
|
|
|
| 277 |
|
| 278 |
-
© 2025 Vijay S. Chaudhari | Powered by
|
| 279 |
"""
|
| 280 |
)
|
| 281 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
+
import yaml
|
| 3 |
import torch
|
| 4 |
+
from PIL import Image
|
|
|
|
|
|
|
| 5 |
from rembg import remove
|
| 6 |
+
from rate_limiter import RateLimiter
|
| 7 |
+
from replicate_handler import ReplicateHandler
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
|
|
|
| 9 |
|
| 10 |
# ------------------------------------------
|
| 11 |
+
# Load Configuration
|
| 12 |
# ------------------------------------------
|
| 13 |
|
| 14 |
+
with open("config.yaml", "r") as f:
|
| 15 |
+
config = yaml.safe_load(f)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
+
# Initialize modules
|
| 18 |
+
rate_limiter = RateLimiter(
|
| 19 |
+
session_file=config["rate_limit"]["session_file"],
|
| 20 |
+
daily_limit=config["rate_limit"]["default_daily_limit"],
|
| 21 |
+
dev_daily_limit=config["rate_limit"]["dev_daily_limit"]
|
| 22 |
+
)
|
| 23 |
|
| 24 |
+
replicate_handler = ReplicateHandler(
|
| 25 |
+
model=config["replicate"]["model"],
|
| 26 |
+
default_settings=config["replicate"]["default_settings"]
|
| 27 |
+
)
|
| 28 |
|
| 29 |
# ------------------------------------------
|
| 30 |
+
# Core Functions
|
| 31 |
# ------------------------------------------
|
| 32 |
|
| 33 |
+
def remove_background(img: Image.Image) -> Image.Image:
|
| 34 |
+
"""Remove background from image"""
|
| 35 |
+
if img is None:
|
| 36 |
+
raise gr.Error("Please upload an image first")
|
| 37 |
+
return remove(img)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def generate_headshot(
|
| 41 |
+
img: Image.Image,
|
| 42 |
+
headshot_style: str,
|
| 43 |
+
use_advanced: bool,
|
| 44 |
+
cfg: float,
|
| 45 |
+
steps: int,
|
| 46 |
+
denoise: float,
|
| 47 |
+
request: gr.Request
|
| 48 |
+
) -> Image.Image:
|
| 49 |
+
"""Generate professional headshot with selected style"""
|
| 50 |
|
| 51 |
+
# Check rate limit
|
| 52 |
+
allowed, remaining, reset_time = rate_limiter.check_limit(request)
|
| 53 |
+
if not allowed:
|
| 54 |
+
raise gr.Error(rate_limiter.get_limit_message(remaining, reset_time))
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
|
| 56 |
+
if img is None:
|
| 57 |
+
raise gr.Error("Please upload an image first")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
+
# Get prompt from config
|
| 60 |
+
style_config = config["headshots"][headshot_style]
|
| 61 |
+
prompt = style_config["prompt"]
|
| 62 |
+
negative_prompt = style_config["negative"]
|
| 63 |
|
| 64 |
+
# Prepare settings
|
| 65 |
+
custom_settings = None
|
| 66 |
+
if use_advanced:
|
| 67 |
+
custom_settings = {
|
| 68 |
+
"cfg": cfg,
|
| 69 |
+
"steps": steps,
|
| 70 |
+
"denoise": denoise
|
| 71 |
+
}
|
| 72 |
|
| 73 |
+
# Generate
|
| 74 |
+
try:
|
| 75 |
+
result = replicate_handler.generate(
|
| 76 |
+
input_image=img,
|
| 77 |
+
prompt=prompt,
|
| 78 |
+
negative_prompt=negative_prompt,
|
| 79 |
+
custom_settings=custom_settings
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
# Increment usage
|
| 83 |
+
rate_limiter.increment(request)
|
| 84 |
+
|
| 85 |
+
return result
|
| 86 |
+
|
| 87 |
+
except Exception as e:
|
| 88 |
+
raise gr.Error(f"Generation failed: {str(e)}")
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def generate_scene(
|
| 92 |
+
img: Image.Image,
|
| 93 |
+
scene_style: str,
|
| 94 |
+
use_advanced: bool,
|
| 95 |
+
cfg: float,
|
| 96 |
+
steps: int,
|
| 97 |
+
denoise: float,
|
| 98 |
+
request: gr.Request
|
| 99 |
+
) -> Image.Image:
|
| 100 |
+
"""Generate scene change with selected style"""
|
| 101 |
|
| 102 |
+
# Check rate limit
|
| 103 |
+
allowed, remaining, reset_time = rate_limiter.check_limit(request)
|
| 104 |
+
if not allowed:
|
| 105 |
+
raise gr.Error(rate_limiter.get_limit_message(remaining, reset_time))
|
| 106 |
|
| 107 |
+
if img is None:
|
| 108 |
+
raise gr.Error("Please upload an image first")
|
|
|
|
|
|
|
| 109 |
|
| 110 |
+
# Get prompt from config
|
| 111 |
+
style_config = config["scenes"][scene_style]
|
| 112 |
+
prompt = style_config["prompt"]
|
| 113 |
+
negative_prompt = style_config["negative"]
|
| 114 |
|
| 115 |
+
# Prepare settings
|
| 116 |
+
custom_settings = None
|
| 117 |
+
if use_advanced:
|
| 118 |
+
custom_settings = {
|
| 119 |
+
"cfg": cfg,
|
| 120 |
+
"steps": steps,
|
| 121 |
+
"denoise": denoise
|
| 122 |
+
}
|
| 123 |
|
| 124 |
+
# Generate
|
| 125 |
+
try:
|
| 126 |
+
result = replicate_handler.generate(
|
| 127 |
+
input_image=img,
|
| 128 |
+
prompt=prompt,
|
| 129 |
+
negative_prompt=negative_prompt,
|
| 130 |
+
custom_settings=custom_settings
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
# Increment usage
|
| 134 |
+
rate_limiter.increment(request)
|
| 135 |
+
|
| 136 |
+
return result
|
| 137 |
+
|
| 138 |
+
except Exception as e:
|
| 139 |
+
raise gr.Error(f"Generation failed: {str(e)}")
|
| 140 |
|
|
|
|
|
|
|
| 141 |
|
| 142 |
+
def get_rate_limit_status(request: gr.Request) -> str:
|
| 143 |
+
"""Get current rate limit status"""
|
| 144 |
+
allowed, remaining, reset_time = rate_limiter.check_limit(request)
|
| 145 |
+
return rate_limiter.get_limit_message(remaining, reset_time)
|
| 146 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
|
| 148 |
# ------------------------------------------
|
| 149 |
# Gradio Interface
|
|
|
|
| 153 |
gr.Markdown(
|
| 154 |
"""
|
| 155 |
# 🎨 FaceForge AI
|
| 156 |
+
### AI-Powered Professional Photo Generator
|
| 157 |
+
Upload your photo and transform it into professional headshots or place yourself in different scenes.
|
| 158 |
"""
|
| 159 |
)
|
| 160 |
+
|
| 161 |
+
# Rate limit status
|
| 162 |
+
rate_status = gr.Textbox(
|
| 163 |
+
label="📊 Usage Status",
|
| 164 |
+
interactive=False,
|
| 165 |
+
value="Loading..."
|
| 166 |
+
)
|
| 167 |
+
|
|
|
|
| 168 |
with gr.Row():
|
| 169 |
+
# Left Column - Input
|
| 170 |
with gr.Column(scale=1):
|
| 171 |
input_image = gr.Image(type="pil", label="📷 Upload Your Photo")
|
| 172 |
+
|
| 173 |
+
gr.Markdown("### ⚙️ Advanced Settings (Optional)")
|
| 174 |
+
|
| 175 |
+
use_advanced = gr.Checkbox(
|
| 176 |
+
label="Enable Advanced Settings",
|
| 177 |
+
value=False
|
|
|
|
|
|
|
| 178 |
)
|
| 179 |
+
|
| 180 |
+
with gr.Group(visible=True) as advanced_group:
|
| 181 |
+
cfg_slider = gr.Slider(
|
| 182 |
+
label="🎛️ CFG Scale",
|
| 183 |
+
minimum=config["replicate"]["advanced_ranges"]["cfg"]["min"],
|
| 184 |
+
maximum=config["replicate"]["advanced_ranges"]["cfg"]["max"],
|
| 185 |
+
value=config["replicate"]["default_settings"]["cfg"],
|
| 186 |
+
step=config["replicate"]["advanced_ranges"]["cfg"]["step"]
|
| 187 |
+
)
|
| 188 |
+
|
| 189 |
+
steps_slider = gr.Slider(
|
| 190 |
+
label="🔄 Steps",
|
| 191 |
+
minimum=config["replicate"]["advanced_ranges"]["steps"]["min"],
|
| 192 |
+
maximum=config["replicate"]["advanced_ranges"]["steps"]["max"],
|
| 193 |
+
value=config["replicate"]["default_settings"]["steps"],
|
| 194 |
+
step=config["replicate"]["advanced_ranges"]["steps"]["step"]
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
denoise_slider = gr.Slider(
|
| 198 |
+
label="✨ Denoise",
|
| 199 |
+
minimum=config["replicate"]["advanced_ranges"]["denoise"]["min"],
|
| 200 |
+
maximum=config["replicate"]["advanced_ranges"]["denoise"]["max"],
|
| 201 |
+
value=config["replicate"]["default_settings"]["denoise"],
|
| 202 |
+
step=config["replicate"]["advanced_ranges"]["denoise"]["step"]
|
| 203 |
+
)
|
| 204 |
+
|
| 205 |
+
# Toggle visibility of advanced settings
|
| 206 |
+
use_advanced.change(
|
| 207 |
+
fn=lambda x: gr.update(visible=x),
|
| 208 |
+
inputs=[use_advanced],
|
| 209 |
+
outputs=[advanced_group]
|
| 210 |
)
|
| 211 |
+
|
| 212 |
+
# Right Column - Outputs
|
| 213 |
+
with gr.Column(scale=1):
|
| 214 |
+
|
| 215 |
+
# Background Remover Section
|
| 216 |
+
gr.Markdown("### 🎭 Background Remover")
|
| 217 |
+
output_bg_removed = gr.Image(label="Background Removed", type="pil")
|
| 218 |
+
btn_bg_remove = gr.Button("🗑️ Remove Background", variant="secondary")
|
| 219 |
+
|
| 220 |
+
gr.Markdown("---")
|
| 221 |
+
|
| 222 |
+
# Professional Headshots Section
|
| 223 |
+
gr.Markdown("### 💼 Professional Headshots")
|
| 224 |
+
headshot_style = gr.Dropdown(
|
| 225 |
+
label="Choose Headshot Style",
|
| 226 |
+
choices=list(config["headshots"].keys()),
|
| 227 |
+
value=list(config["headshots"].keys())[0]
|
| 228 |
)
|
| 229 |
+
output_headshot = gr.Image(label="Professional Headshot", type="pil")
|
| 230 |
+
btn_headshot = gr.Button("📸 Generate Headshot", variant="primary")
|
| 231 |
+
|
| 232 |
+
gr.Markdown("---")
|
| 233 |
+
|
| 234 |
+
# Scene Changer Section
|
| 235 |
+
gr.Markdown("### 🌍 Scene Changer")
|
| 236 |
+
scene_style = gr.Dropdown(
|
| 237 |
+
label="Choose Scene",
|
| 238 |
+
choices=list(config["scenes"].keys()),
|
| 239 |
+
value=list(config["scenes"].keys())[0]
|
| 240 |
)
|
| 241 |
+
output_scene = gr.Image(label="Scene Changed", type="pil")
|
| 242 |
+
btn_scene = gr.Button("🎬 Change Scene", variant="primary")
|
| 243 |
+
|
| 244 |
+
# Button Actions
|
| 245 |
+
btn_bg_remove.click(
|
| 246 |
+
fn=remove_background,
|
| 247 |
+
inputs=[input_image],
|
| 248 |
+
outputs=[output_bg_removed]
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
btn_headshot.click(
|
| 252 |
+
fn=generate_headshot,
|
| 253 |
+
inputs=[
|
| 254 |
+
input_image,
|
| 255 |
+
headshot_style,
|
| 256 |
+
use_advanced,
|
| 257 |
+
cfg_slider,
|
| 258 |
+
steps_slider,
|
| 259 |
+
denoise_slider
|
| 260 |
+
],
|
| 261 |
+
outputs=[output_headshot]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 262 |
)
|
| 263 |
|
| 264 |
+
btn_scene.click(
|
| 265 |
+
fn=generate_scene,
|
| 266 |
+
inputs=[
|
| 267 |
+
input_image,
|
| 268 |
+
scene_style,
|
| 269 |
+
use_advanced,
|
| 270 |
+
cfg_slider,
|
| 271 |
+
steps_slider,
|
| 272 |
+
denoise_slider
|
| 273 |
+
],
|
| 274 |
+
outputs=[output_scene]
|
| 275 |
+
)
|
| 276 |
+
|
| 277 |
+
# Update rate limit status on load and after each generation
|
| 278 |
+
demo.load(fn=get_rate_limit_status, inputs=None, outputs=[rate_status])
|
| 279 |
+
btn_headshot.click(fn=get_rate_limit_status, inputs=None, outputs=[rate_status])
|
| 280 |
+
btn_scene.click(fn=get_rate_limit_status, inputs=None, outputs=[rate_status])
|
| 281 |
+
|
| 282 |
gr.Markdown(
|
| 283 |
"""
|
| 284 |
---
|
| 285 |
+
### ✨ Features
|
| 286 |
+
- 🎭 **Background Remover**: Clean background removal for any image
|
| 287 |
+
- 💼 **Professional Headshots**: Studio-quality headshots with multiple styles
|
| 288 |
+
- 🌍 **Scene Changer**: Place yourself in different environments
|
| 289 |
+
- ⚡ **AI-Powered**: InstantID technology for identity preservation
|
| 290 |
+
- 🔒 **Rate Limited**: Fair usage with daily limits
|
| 291 |
|
| 292 |
+
© 2025 Vijay S. Chaudhari | Powered by Replicate 🚀
|
| 293 |
"""
|
| 294 |
)
|
| 295 |
|
app.py.wip_avatar
DELETED
|
@@ -1,423 +0,0 @@
|
|
| 1 |
-
# ==========================================
|
| 2 |
-
# FaceForge AI – ZeroGPU Gradio Version
|
| 3 |
-
# Author: Vijay S. Chaudhari | 2025
|
| 4 |
-
# ==========================================
|
| 5 |
-
|
| 6 |
-
import importlib.util
|
| 7 |
-
import gradio as gr
|
| 8 |
-
import spaces
|
| 9 |
-
import torch
|
| 10 |
-
import cv2
|
| 11 |
-
import numpy as np
|
| 12 |
-
from pathlib import Path
|
| 13 |
-
|
| 14 |
-
from PIL import Image, ImageEnhance, ImageOps
|
| 15 |
-
from rembg import remove
|
| 16 |
-
from diffusers import StableDiffusionImg2ImgPipeline
|
| 17 |
-
from diffusers import StableDiffusionXLPipeline
|
| 18 |
-
import io
|
| 19 |
-
import os, sys, subprocess, warnings, logging
|
| 20 |
-
|
| 21 |
-
warnings.filterwarnings("ignore", category=UserWarning)
|
| 22 |
-
logging.getLogger("onnxruntime").setLevel(logging.ERROR)
|
| 23 |
-
os.environ["CUDA_VISIBLE_DEVICES"] = ""
|
| 24 |
-
|
| 25 |
-
# --- Ensure InstantID is available ---
|
| 26 |
-
if not Path("instantid").exists():
|
| 27 |
-
print("🔄 Cloning InstantID repository...")
|
| 28 |
-
subprocess.run(["git", "clone", "--depth", "1", "https://github.com/InstantID/InstantID.git", "instantid"],check=True)
|
| 29 |
-
|
| 30 |
-
repo_root = Path("instantid").resolve()
|
| 31 |
-
|
| 32 |
-
# 🧭 Search for a pipeline file that matches *instantid*.py under the repo
|
| 33 |
-
candidates = list(repo_root.rglob("pipeline*instantid*.py"))
|
| 34 |
-
if not candidates:
|
| 35 |
-
# Fallback common names across commits
|
| 36 |
-
fallback_names = [
|
| 37 |
-
"pipelines/pipeline_instantid.py",
|
| 38 |
-
"pipelines/pipeline_stable_diffusion_instantid.py",
|
| 39 |
-
"pipelines/pipeline_stable_diffusion_xl_instantid.py",
|
| 40 |
-
]
|
| 41 |
-
for name in fallback_names:
|
| 42 |
-
p = repo_root / name
|
| 43 |
-
if p.exists():
|
| 44 |
-
candidates = [p]
|
| 45 |
-
break
|
| 46 |
-
|
| 47 |
-
if not candidates:
|
| 48 |
-
raise FileNotFoundError(
|
| 49 |
-
"Could not locate an InstantID pipeline file under ./instantid. "
|
| 50 |
-
"Repo layout may have changed. Please check the repo structure."
|
| 51 |
-
)
|
| 52 |
-
|
| 53 |
-
pipeline_file = candidates[0]
|
| 54 |
-
print(f"✅ Using InstantID pipeline file: {pipeline_file.relative_to(repo_root)}")
|
| 55 |
-
|
| 56 |
-
# 🪄 Import the pipeline module by file path (no package needed)
|
| 57 |
-
spec = importlib.util.spec_from_file_location("instantid_pipeline", str(pipeline_file))
|
| 58 |
-
instantid_mod = importlib.util.module_from_spec(spec)
|
| 59 |
-
spec.loader.exec_module(instantid_mod) # type: ignore
|
| 60 |
-
|
| 61 |
-
# 🔎 Pick a pipeline class that looks like an InstantID Pipeline
|
| 62 |
-
InstantIDPipeline = None
|
| 63 |
-
for attr in dir(instantid_mod):
|
| 64 |
-
if "InstantID" in attr and "Pipeline" in attr:
|
| 65 |
-
InstantIDPipeline = getattr(instantid_mod, attr)
|
| 66 |
-
break
|
| 67 |
-
|
| 68 |
-
if InstantIDPipeline is None:
|
| 69 |
-
# Helpful diagnostics
|
| 70 |
-
print("Available names in module:", [a for a in dir(instantid_mod) if "Pipeline" in a])
|
| 71 |
-
raise ImportError(
|
| 72 |
-
"Could not find an InstantID pipeline class. "
|
| 73 |
-
"Looked for a class name containing both 'InstantID' and 'Pipeline'."
|
| 74 |
-
)
|
| 75 |
-
|
| 76 |
-
print(f"✅ Imported pipeline class: {InstantIDPipeline.__name__}")
|
| 77 |
-
|
| 78 |
-
'''
|
| 79 |
-
if os.path.exists("InstantID") and not os.path.exists("instantid"):
|
| 80 |
-
os.rename("InstantID", "instantid")
|
| 81 |
-
|
| 82 |
-
instantid_path = os.path.abspath("instantid")
|
| 83 |
-
sys.path.append(instantid_path)
|
| 84 |
-
sys.path.append(os.path.join(instantid_path, "pipelines"))
|
| 85 |
-
|
| 86 |
-
#sys.path.append(os.path.abspath("instantid"))
|
| 87 |
-
#sys.path.insert(0, os.path.join(os.getcwd(), 'InstantID'))
|
| 88 |
-
try:
|
| 89 |
-
from pipelines.pipeline_instantid import InstantIDPipeline
|
| 90 |
-
print("✅ InstantIDPipeline imported successfully.")
|
| 91 |
-
except Exception as e:
|
| 92 |
-
print("⚠️ Failed to import InstantIDPipeline:", e)
|
| 93 |
-
InstantIDPipeline = None # graceful fallback
|
| 94 |
-
'''
|
| 95 |
-
|
| 96 |
-
import torchvision
|
| 97 |
-
print("Printing Torch and TorchVision versions:")
|
| 98 |
-
print(torch.__version__)
|
| 99 |
-
print(torchvision.__version__)
|
| 100 |
-
|
| 101 |
-
# GPU libraries
|
| 102 |
-
from gfpgan import GFPGANer
|
| 103 |
-
from basicsr.archs.rrdbnet_arch import RRDBNet
|
| 104 |
-
from realesrgan import RealESRGANer
|
| 105 |
-
|
| 106 |
-
# ------------------------------------------
|
| 107 |
-
# Model Loading (Outside GPU decorator)
|
| 108 |
-
# ------------------------------------------
|
| 109 |
-
|
| 110 |
-
def load_models():
|
| 111 |
-
"""Load models once at startup"""
|
| 112 |
-
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 113 |
-
|
| 114 |
-
# RealESRGAN upsampler
|
| 115 |
-
model = RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=23, num_grow_ch=32, scale=2)
|
| 116 |
-
upsampler = RealESRGANer(
|
| 117 |
-
scale=2,
|
| 118 |
-
model_path='https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.1/RealESRGAN_x2plus.pth',
|
| 119 |
-
model=model,
|
| 120 |
-
tile=400,
|
| 121 |
-
tile_pad=10,
|
| 122 |
-
pre_pad=0,
|
| 123 |
-
half=True,
|
| 124 |
-
device=device
|
| 125 |
-
)
|
| 126 |
-
|
| 127 |
-
# GFPGAN enhancer
|
| 128 |
-
face_enhancer = GFPGANer(
|
| 129 |
-
model_path='https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.3.pth',
|
| 130 |
-
upscale=2,
|
| 131 |
-
arch='clean',
|
| 132 |
-
channel_multiplier=2,
|
| 133 |
-
bg_upsampler=upsampler,
|
| 134 |
-
device=device
|
| 135 |
-
)
|
| 136 |
-
|
| 137 |
-
# Stable Diffusion Img2Img pipeline (public model)
|
| 138 |
-
sd_pipe = StableDiffusionImg2ImgPipeline.from_pretrained(
|
| 139 |
-
"runwayml/stable-diffusion-v1-5",
|
| 140 |
-
torch_dtype=torch.float16
|
| 141 |
-
).to(device)
|
| 142 |
-
|
| 143 |
-
# Optimize for ZeroGPU memory
|
| 144 |
-
sd_pipe.enable_attention_slicing()
|
| 145 |
-
sd_pipe.enable_model_cpu_offload()
|
| 146 |
-
|
| 147 |
-
return face_enhancer, sd_pipe
|
| 148 |
-
|
| 149 |
-
# Load models globally
|
| 150 |
-
face_enhancer, sd_pipe = load_models()
|
| 151 |
-
|
| 152 |
-
# ------------------------------------------
|
| 153 |
-
# GPU-Accelerated Functions
|
| 154 |
-
# ------------------------------------------
|
| 155 |
-
|
| 156 |
-
@spaces.GPU
|
| 157 |
-
def enhance_face(img: Image.Image) -> Image.Image:
|
| 158 |
-
"""Enhance face using GFPGAN (GPU)"""
|
| 159 |
-
img_cv = cv2.cvtColor(np.array(img.convert('RGB')), cv2.COLOR_RGB2BGR)
|
| 160 |
-
|
| 161 |
-
with torch.no_grad():
|
| 162 |
-
_, _, restored_img = face_enhancer.enhance(
|
| 163 |
-
img_cv,
|
| 164 |
-
has_aligned=False,
|
| 165 |
-
only_center_face=False,
|
| 166 |
-
paste_back=True,
|
| 167 |
-
weight=0.5
|
| 168 |
-
)
|
| 169 |
-
|
| 170 |
-
restored_img = cv2.cvtColor(restored_img, cv2.COLOR_BGR2RGB)
|
| 171 |
-
return Image.fromarray(restored_img)
|
| 172 |
-
|
| 173 |
-
# ------------------------------------------
|
| 174 |
-
# Image Processing Functions
|
| 175 |
-
# ------------------------------------------
|
| 176 |
-
|
| 177 |
-
def enhance_image(img: Image.Image) -> Image.Image:
|
| 178 |
-
"""Basic enhancement"""
|
| 179 |
-
img = ImageEnhance.Contrast(img).enhance(1.15)
|
| 180 |
-
img = ImageEnhance.Sharpness(img).enhance(1.1)
|
| 181 |
-
return img
|
| 182 |
-
|
| 183 |
-
@spaces.GPU
|
| 184 |
-
def create_headshot(img: Image.Image) -> Image.Image:
|
| 185 |
-
"""Professional headshot with gradient background"""
|
| 186 |
-
# Enhance face
|
| 187 |
-
img_enhanced = enhance_face(img)
|
| 188 |
-
|
| 189 |
-
# Remove background
|
| 190 |
-
img_no_bg = remove(img_enhanced)
|
| 191 |
-
|
| 192 |
-
# Gradient background
|
| 193 |
-
bg = Image.new("RGB", img_no_bg.size, (200, 210, 230))
|
| 194 |
-
if img_no_bg.mode == 'RGBA':
|
| 195 |
-
bg.paste(img_no_bg, mask=img_no_bg.split()[3])
|
| 196 |
-
|
| 197 |
-
return enhance_image(bg)
|
| 198 |
-
|
| 199 |
-
@spaces.GPU
|
| 200 |
-
def create_passport(img: Image.Image) -> Image.Image:
|
| 201 |
-
"""Passport photo with white background"""
|
| 202 |
-
# Enhance face
|
| 203 |
-
img_enhanced = enhance_face(img)
|
| 204 |
-
|
| 205 |
-
# Remove background
|
| 206 |
-
img_no_bg = remove(img_enhanced)
|
| 207 |
-
|
| 208 |
-
# White background (600x600)
|
| 209 |
-
bg = Image.new("RGB", (600, 600), (255, 255, 255))
|
| 210 |
-
img_no_bg.thumbnail((550, 550), Image.Resampling.LANCZOS)
|
| 211 |
-
offset = ((600 - img_no_bg.width) // 2, (600 - img_no_bg.height) // 2)
|
| 212 |
-
|
| 213 |
-
if img_no_bg.mode == 'RGBA':
|
| 214 |
-
bg.paste(img_no_bg, offset, mask=img_no_bg.split()[3])
|
| 215 |
-
|
| 216 |
-
return bg
|
| 217 |
-
|
| 218 |
-
@spaces.GPU
|
| 219 |
-
def create_avatar(img: Image.Image, prompt: str, strength: float, guidance_scale: float) -> Image.Image:
|
| 220 |
-
"""
|
| 221 |
-
Create a stylized AI avatar while preserving facial identity using InstantID.
|
| 222 |
-
Retains core facial features, skin tone, and expressions of the input photo.
|
| 223 |
-
"""
|
| 224 |
-
|
| 225 |
-
# Stylize with SD prompt. We are selecting these from UI now.
|
| 226 |
-
#prompt = "highly detailed, digital portrait, professional lighting, cinematic style, artistic AI avatar"
|
| 227 |
-
#prompt = "stylized yet realistic portrait, balanced lighting, subtle gradient background, sharp focus on face"
|
| 228 |
-
#prompt = "studio portrait, even lighting, neutral background, realistic skin, confident pose"
|
| 229 |
-
#prompt = "realistic professional headshot, soft studio lighting, neutral background, crisp details, natural skin tone"
|
| 230 |
-
|
| 231 |
-
# --- Convert input ---
|
| 232 |
-
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 233 |
-
img = img.convert("RGB").resize((512, 512), Image.Resampling.LANCZOS)
|
| 234 |
-
|
| 235 |
-
# --- Step 1: Load InstantID + SDXL pipeline ---
|
| 236 |
-
pipe = StableDiffusionXLPipeline.from_pretrained(
|
| 237 |
-
"stabilityai/stable-diffusion-xl-base-1.0",
|
| 238 |
-
torch_dtype=torch.float16
|
| 239 |
-
).to(device)
|
| 240 |
-
|
| 241 |
-
instantid = InstantIDPipeline.from_pretrained("InstantID/InstantID", torch_dtype=torch.float16,)
|
| 242 |
-
pipe.to("cuda" if torch.cuda.is_available() else "cpu")
|
| 243 |
-
#pipe.load_ip_adapter(instantid)
|
| 244 |
-
|
| 245 |
-
# --- Step 2: Optimize for ZeroGPU memory ---
|
| 246 |
-
pipe.enable_attention_slicing()
|
| 247 |
-
pipe.enable_model_cpu_offload()
|
| 248 |
-
|
| 249 |
-
# --- Step 3: Prepare conditioning (face embedding) ---
|
| 250 |
-
np_img = np.array(img)
|
| 251 |
-
bgr_img = cv2.cvtColor(np_img, cv2.COLOR_RGB2BGR)
|
| 252 |
-
face_emb = instantid.extract_face_embedding(bgr_img) # key step: ID embedding guidance
|
| 253 |
-
|
| 254 |
-
# --- Step 4: Stylized generation ---
|
| 255 |
-
gen = pipe.generate_with_identity(
|
| 256 |
-
image=img,
|
| 257 |
-
face_embedding=face_emb,
|
| 258 |
-
prompt=(
|
| 259 |
-
prompt
|
| 260 |
-
+ ", portrait of the same person, consistent identity, detailed lighting, "
|
| 261 |
-
"highly realistic skin texture, cinematic color tones"
|
| 262 |
-
),
|
| 263 |
-
strength=float(strength),
|
| 264 |
-
guidance_scale=float(guidance_scale),
|
| 265 |
-
num_inference_steps=30
|
| 266 |
-
)
|
| 267 |
-
|
| 268 |
-
avatar = gen.images[0]
|
| 269 |
-
|
| 270 |
-
# --- Step 5 (Optional): Post-process with GFPGAN for crispness ---
|
| 271 |
-
try:
|
| 272 |
-
from gfpgan import GFPGANer
|
| 273 |
-
from realesrgan import RealESRGANer
|
| 274 |
-
from basicsr.archs.rrdbnet_arch import RRDBNet
|
| 275 |
-
|
| 276 |
-
model = RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64,
|
| 277 |
-
num_block=23, num_grow_ch=32, scale=2)
|
| 278 |
-
upsampler = RealESRGANer(
|
| 279 |
-
scale=2,
|
| 280 |
-
model_path='https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.1/RealESRGAN_x2plus.pth',
|
| 281 |
-
model=model,
|
| 282 |
-
tile=400,
|
| 283 |
-
tile_pad=10,
|
| 284 |
-
pre_pad=0,
|
| 285 |
-
half=True,
|
| 286 |
-
device=device
|
| 287 |
-
)
|
| 288 |
-
face_enhancer = GFPGANer(
|
| 289 |
-
model_path='https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.3.pth',
|
| 290 |
-
upscale=1,
|
| 291 |
-
arch='clean',
|
| 292 |
-
channel_multiplier=2,
|
| 293 |
-
bg_upsampler=upsampler,
|
| 294 |
-
device=device
|
| 295 |
-
)
|
| 296 |
-
|
| 297 |
-
img_cv = cv2.cvtColor(np.array(avatar), cv2.COLOR_RGB2BGR)
|
| 298 |
-
_, _, restored_img = face_enhancer.enhance(
|
| 299 |
-
img_cv, has_aligned=False, only_center_face=False,
|
| 300 |
-
paste_back=True, weight=0.4
|
| 301 |
-
)
|
| 302 |
-
avatar = Image.fromarray(cv2.cvtColor(restored_img, cv2.COLOR_BGR2RGB))
|
| 303 |
-
except Exception as e:
|
| 304 |
-
print(f"[WARN] GFPGAN post-process skipped: {e}")
|
| 305 |
-
|
| 306 |
-
return avatar
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
@spaces.GPU
|
| 310 |
-
def process_all(img: Image.Image):
|
| 311 |
-
"""Process all three types at once"""
|
| 312 |
-
headshot = create_headshot(img)
|
| 313 |
-
passport = create_passport(img)
|
| 314 |
-
avatar = create_avatar(img)
|
| 315 |
-
return headshot, passport, avatar
|
| 316 |
-
|
| 317 |
-
# ------------------------------------------
|
| 318 |
-
# Gradio Interface
|
| 319 |
-
# ------------------------------------------
|
| 320 |
-
|
| 321 |
-
with gr.Blocks(theme=gr.themes.Soft(), title="FaceForge AI") as demo:
|
| 322 |
-
gr.Markdown(
|
| 323 |
-
"""
|
| 324 |
-
# 🎨 FaceForge AI
|
| 325 |
-
### GPU-Accelerated Professional Headshot & Avatar Generator
|
| 326 |
-
Upload your photo and choose or customize how your AI avatar is generated.
|
| 327 |
-
"""
|
| 328 |
-
)
|
| 329 |
-
|
| 330 |
-
# --- Define a mapping: Short Label -> Full Prompt Text ---
|
| 331 |
-
PROMPT_MAP = {
|
| 332 |
-
"🎬 Cinematic Portrait": "highly detailed, digital portrait, professional lighting, cinematic style, artistic AI avatar",
|
| 333 |
-
"🎨 Stylized Realism": "stylized yet realistic portrait, balanced lighting, subtle gradient background, sharp focus on face",
|
| 334 |
-
"🏢 Studio Professional": "studio portrait, even lighting, neutral background, realistic skin, confident pose",
|
| 335 |
-
"🤵 Natural Headshot": "realistic professional headshot, soft studio lighting, neutral background, crisp details, natural skin tone"
|
| 336 |
-
}
|
| 337 |
-
|
| 338 |
-
with gr.Row():
|
| 339 |
-
with gr.Column(scale=1):
|
| 340 |
-
input_image = gr.Image(type="pil", label="📷 Upload Your Photo")
|
| 341 |
-
|
| 342 |
-
gr.Markdown("### ⚙️ Avatar Generation Settings")
|
| 343 |
-
|
| 344 |
-
# Dropdown shows short labels only
|
| 345 |
-
preset_prompt = gr.Dropdown(
|
| 346 |
-
label="🎨 Choose Avatar Style Preset",
|
| 347 |
-
choices=list(PROMPT_MAP.keys()),
|
| 348 |
-
value="🤵 Natural Headshot"
|
| 349 |
-
)
|
| 350 |
-
|
| 351 |
-
# Optional custom prompt box for flexibility
|
| 352 |
-
custom_prompt = gr.Textbox(
|
| 353 |
-
label="✏️ Custom Prompt (optional)",
|
| 354 |
-
placeholder="Enter your own prompt or leave blank to use preset...",
|
| 355 |
-
lines=2
|
| 356 |
-
)
|
| 357 |
-
|
| 358 |
-
strength_slider = gr.Slider(
|
| 359 |
-
label="🎛️ Style Strength (0.0 = keep original, 1.0 = full restyle)",
|
| 360 |
-
minimum=0.1,
|
| 361 |
-
maximum=1.0,
|
| 362 |
-
value=0.45,
|
| 363 |
-
step=0.05
|
| 364 |
-
)
|
| 365 |
-
|
| 366 |
-
guidance_slider = gr.Slider(
|
| 367 |
-
label="🎯 Prompt Guidance Scale (higher = more prompt influence)",
|
| 368 |
-
minimum=1.0,
|
| 369 |
-
maximum=10.0,
|
| 370 |
-
value=5.5,
|
| 371 |
-
step=0.5
|
| 372 |
-
)
|
| 373 |
-
|
| 374 |
-
with gr.Column(scale=1):
|
| 375 |
-
gr.Markdown("### Results")
|
| 376 |
-
|
| 377 |
-
# --- Independent Outputs & Buttons ---
|
| 378 |
-
output_headshot = gr.Image(label="💼 Professional Headshot", type="pil")
|
| 379 |
-
btn_headshot = gr.Button("📸 Generate Headshot", variant="secondary")
|
| 380 |
-
|
| 381 |
-
output_passport = gr.Image(label="🛂 Passport Photo", type="pil")
|
| 382 |
-
btn_passport = gr.Button("🪪 Generate Passport", variant="secondary")
|
| 383 |
-
|
| 384 |
-
output_avatar = gr.Image(label="🎭 AI Avatar", type="pil")
|
| 385 |
-
btn_avatar = gr.Button("✨ Generate Avatar", variant="primary")
|
| 386 |
-
|
| 387 |
-
# --- Functions for individual generations ---
|
| 388 |
-
def run_headshot(img):
|
| 389 |
-
return create_headshot(img)
|
| 390 |
-
|
| 391 |
-
def run_passport(img):
|
| 392 |
-
return create_passport(img)
|
| 393 |
-
|
| 394 |
-
def run_avatar(img, preset_label, custom, strength, guidance):
|
| 395 |
-
final_prompt = custom.strip() if custom and custom.strip() != "" else PROMPT_MAP[preset_label]
|
| 396 |
-
return create_avatar(img, final_prompt, strength, guidance)
|
| 397 |
-
|
| 398 |
-
# --- Button actions ---
|
| 399 |
-
btn_headshot.click(fn=run_headshot, inputs=[input_image], outputs=[output_headshot])
|
| 400 |
-
btn_passport.click(fn=run_passport, inputs=[input_image], outputs=[output_passport])
|
| 401 |
-
btn_avatar.click(
|
| 402 |
-
fn=run_avatar,
|
| 403 |
-
inputs=[input_image, preset_prompt, custom_prompt, strength_slider, guidance_slider],
|
| 404 |
-
outputs=[output_avatar]
|
| 405 |
-
)
|
| 406 |
-
|
| 407 |
-
gr.Markdown(
|
| 408 |
-
"""
|
| 409 |
-
---
|
| 410 |
-
### Features
|
| 411 |
-
- 💼 **Professional Headshots**: Perfect for LinkedIn and business profiles
|
| 412 |
-
- 🛂 **Passport Photos**: Standard 600x600px with white background
|
| 413 |
-
- 🎭 **AI Avatars**: Stylized versions for social media
|
| 414 |
-
- ⚡ **GPU-Accelerated**: Fast processing with GFPGAN enhancement
|
| 415 |
-
|
| 416 |
-
© 2025 Vijay S. Chaudhari | Powered by ZeroGPU 🚀
|
| 417 |
-
"""
|
| 418 |
-
)
|
| 419 |
-
|
| 420 |
-
# Launch
|
| 421 |
-
if __name__ == "__main__":
|
| 422 |
-
demo.queue(max_size=20)
|
| 423 |
-
demo.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
config.yaml
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# FaceForge AI Configuration
|
| 2 |
+
# Author: Vijay S. Chaudhari | 2025
|
| 3 |
+
|
| 4 |
+
# Rate Limiting
|
| 5 |
+
rate_limit:
|
| 6 |
+
default_daily_limit: 5
|
| 7 |
+
dev_daily_limit: 10
|
| 8 |
+
reset_timezone: "UTC"
|
| 9 |
+
session_file: "rate_limits.json"
|
| 10 |
+
|
| 11 |
+
# Replicate Model
|
| 12 |
+
replicate:
|
| 13 |
+
model: "zsxkib/instant-id-basic:beeceec1b60538a6b77c1549b8b1f91794b133c53443234feb8faa3c61ceab49"
|
| 14 |
+
|
| 15 |
+
# Default settings (used when advanced options disabled)
|
| 16 |
+
default_settings:
|
| 17 |
+
cfg: 4.5
|
| 18 |
+
steps: 30
|
| 19 |
+
width: 1600
|
| 20 |
+
height: 1600
|
| 21 |
+
denoise: 1
|
| 22 |
+
scheduler: "karras"
|
| 23 |
+
sampler_name: "ddpm"
|
| 24 |
+
output_format: "webp"
|
| 25 |
+
output_quality: 90
|
| 26 |
+
instantid_weight: 1
|
| 27 |
+
instantid_start_at: 0
|
| 28 |
+
instantid_end_at: 1
|
| 29 |
+
|
| 30 |
+
# Advanced settings ranges (for UI sliders when enabled)
|
| 31 |
+
advanced_ranges:
|
| 32 |
+
cfg:
|
| 33 |
+
min: 1.0
|
| 34 |
+
max: 10.0
|
| 35 |
+
step: 0.5
|
| 36 |
+
steps:
|
| 37 |
+
min: 20
|
| 38 |
+
max: 50
|
| 39 |
+
step: 5
|
| 40 |
+
denoise:
|
| 41 |
+
min: 0.5
|
| 42 |
+
max: 1.0
|
| 43 |
+
step: 0.1
|
| 44 |
+
|
| 45 |
+
# Scene Change Prompts
|
| 46 |
+
scenes:
|
| 47 |
+
"🏖️ Coastal Run":
|
| 48 |
+
prompt: |
|
| 49 |
+
Portrait of the same runner, black hoodie
|
| 50 |
+
running along a coastal path,
|
| 51 |
+
ocean and horizon in the background,
|
| 52 |
+
confident and relaxed expression,
|
| 53 |
+
warm sunrise lighting,
|
| 54 |
+
realistic lifestyle photography
|
| 55 |
+
negative: "NSFW, nudity, painting, drawing, illustration, glitch, deformed, mutated, cross-eyed, ugly, disfigured, wrinkles, open mouth, closed eyes, other characters"
|
| 56 |
+
|
| 57 |
+
"🌃 Urban Evening":
|
| 58 |
+
prompt: |
|
| 59 |
+
Portrait of the same runner,
|
| 60 |
+
evening run in an urban environment,
|
| 61 |
+
city lights in the background,
|
| 62 |
+
confident relaxed smile,
|
| 63 |
+
soft ambient night lighting,
|
| 64 |
+
realistic low-light photography
|
| 65 |
+
negative: "NSFW, nudity, painting, drawing, illustration, glitch, deformed, mutated, cross-eyed, ugly, disfigured, wrinkles, open mouth, closed eyes, other characters"
|
| 66 |
+
|
| 67 |
+
"🌲 Forest Trail":
|
| 68 |
+
prompt: |
|
| 69 |
+
Portrait of the same runner, identical person,
|
| 70 |
+
running on a forest trail,
|
| 71 |
+
trees and dirt path in the background,
|
| 72 |
+
relaxed expression,
|
| 73 |
+
diffused outdoor light,
|
| 74 |
+
realistic outdoor portrait
|
| 75 |
+
negative: "NSFW, nudity, painting, drawing, illustration, glitch, deformed, mutated, cross-eyed, ugly, disfigured, wrinkles, open mouth, closed eyes, other characters"
|
| 76 |
+
|
| 77 |
+
# Professional Headshot Prompts
|
| 78 |
+
headshots:
|
| 79 |
+
"📸 Studio Headshot":
|
| 80 |
+
prompt: |
|
| 81 |
+
Professional editorial headshot of the same person,
|
| 82 |
+
neutral studio background,
|
| 83 |
+
calm confident expression,
|
| 84 |
+
tailored professional attire,
|
| 85 |
+
soft diffused studio lighting,
|
| 86 |
+
natural skin texture,
|
| 87 |
+
realistic magazine photography
|
| 88 |
+
negative: |
|
| 89 |
+
cartoon, anime, illustration,
|
| 90 |
+
exaggerated features,
|
| 91 |
+
overly smooth skin,
|
| 92 |
+
plastic face,
|
| 93 |
+
dramatic shadows,
|
| 94 |
+
harsh lighting,
|
| 95 |
+
low quality
|
| 96 |
+
|
| 97 |
+
"🏢 Office Setting":
|
| 98 |
+
prompt: |
|
| 99 |
+
High-end professional headshot of the same person,
|
| 100 |
+
subtle office setting in the background,
|
| 101 |
+
softly blurred professional indoor environment,
|
| 102 |
+
composed and confident presence,
|
| 103 |
+
premium business attire,
|
| 104 |
+
soft directional studio lighting,
|
| 105 |
+
realistic editorial photography
|
| 106 |
+
negative: |
|
| 107 |
+
artificial office,
|
| 108 |
+
fake interior,
|
| 109 |
+
overly sharp background,
|
| 110 |
+
glass walls,
|
| 111 |
+
corporate stock photo,
|
| 112 |
+
dramatic lighting,
|
| 113 |
+
cinematic contrast
|
| 114 |
+
|
| 115 |
+
"✨ Premium Editorial":
|
| 116 |
+
prompt: |
|
| 117 |
+
High-end professional headshot of the same person,
|
| 118 |
+
minimal studio background,
|
| 119 |
+
composed and confident presence,
|
| 120 |
+
premium business attire,
|
| 121 |
+
soft directional studio lighting,
|
| 122 |
+
realistic editorial photography
|
| 123 |
+
negative: |
|
| 124 |
+
cartoon, anime, illustration,
|
| 125 |
+
exaggerated features,
|
| 126 |
+
overly smooth skin,
|
| 127 |
+
plastic face,
|
| 128 |
+
dramatic shadows,
|
| 129 |
+
harsh lighting,
|
| 130 |
+
low quality
|
env.example
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ==========================================
|
| 2 |
+
# FaceForge AI - Environment Configuration
|
| 3 |
+
# Copy this file to .env and update values
|
| 4 |
+
# ==========================================
|
| 5 |
+
|
| 6 |
+
# Replicate API Token (required)
|
| 7 |
+
# Get yours at: https://replicate.com/account/api-tokens
|
| 8 |
+
REPLICATE_API_TOKEN=your_token_here
|
| 9 |
+
|
| 10 |
+
# Dev Mode (optional)
|
| 11 |
+
# false = 5 generations/day (default)
|
| 12 |
+
# true = 10 generations/day (for development/testing)
|
| 13 |
+
DEV_MODE=false
|
requirements.txt
CHANGED
|
@@ -1,46 +1,8 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
# Image Processing
|
| 10 |
-
Pillow==10.2.0
|
| 11 |
-
opencv-python-headless==4.9.0.80
|
| 12 |
-
numpy==1.26.3
|
| 13 |
-
|
| 14 |
-
# Background Removal
|
| 15 |
-
rembg[gpu]==2.0.57
|
| 16 |
-
|
| 17 |
-
# Face Enhancement (GPU)
|
| 18 |
-
gfpgan==1.3.8
|
| 19 |
-
realesrgan==0.3.0
|
| 20 |
-
basicsr==1.4.2
|
| 21 |
-
|
| 22 |
-
# PyTorch (GPU) - Critical for CUDA
|
| 23 |
-
torch==2.1.2
|
| 24 |
-
torchvision==0.16.2
|
| 25 |
-
#--extra-index-url https://download.pytorch.org/whl/cu118
|
| 26 |
-
#--extra-index-url https://download.pytorch.org/whl/cu121
|
| 27 |
-
|
| 28 |
-
# Stable Diffusion
|
| 29 |
-
diffusers>=0.20.0
|
| 30 |
-
transformers==4.39.0
|
| 31 |
-
accelerate==0.28.0
|
| 32 |
-
|
| 33 |
-
# InstantID
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
# Core Dependencies
|
| 37 |
-
scipy==1.12.0
|
| 38 |
-
tqdm==4.66.1
|
| 39 |
-
facexlib==0.3.0
|
| 40 |
-
yapf==0.40.2
|
| 41 |
-
filterpy==1.4.5
|
| 42 |
-
|
| 43 |
-
# Optional: For better performance
|
| 44 |
-
# onnxruntime-gpu==1.17.0
|
| 45 |
-
|
| 46 |
-
accelerate
|
|
|
|
| 1 |
+
gradio>=4.0.0
|
| 2 |
+
replicate>=0.20.0
|
| 3 |
+
rembg>=2.0.0
|
| 4 |
+
Pillow>=10.0.0
|
| 5 |
+
PyYAML>=6.0
|
| 6 |
+
torch>=2.0.0
|
| 7 |
+
torchvision>=0.15.0
|
| 8 |
+
requests>=2.31.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/rateLimiter.py
ADDED
|
File without changes
|
src/replicateHandler.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import replicate
|
| 3 |
+
from PIL import Image
|
| 4 |
+
import io
|
| 5 |
+
import base64
|
| 6 |
+
import tempfile
|
| 7 |
+
from typing import Optional
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class ReplicateHandler:
|
| 11 |
+
def __init__(self, model: str, default_settings: dict):
|
| 12 |
+
self.model = model
|
| 13 |
+
self.default_settings = default_settings
|
| 14 |
+
|
| 15 |
+
# Verify API token
|
| 16 |
+
api_token = os.getenv("REPLICATE_API_TOKEN")
|
| 17 |
+
if not api_token:
|
| 18 |
+
raise ValueError("REPLICATE_API_TOKEN not found in environment variables")
|
| 19 |
+
|
| 20 |
+
def _image_to_base64_url(self, image: Image.Image) -> str:
|
| 21 |
+
"""Convert PIL Image to base64 data URL for Replicate"""
|
| 22 |
+
buffered = io.BytesIO()
|
| 23 |
+
image.save(buffered, format="PNG")
|
| 24 |
+
img_str = base64.b64encode(buffered.getvalue()).decode()
|
| 25 |
+
return f"data:image/png;base64,{img_str}"
|
| 26 |
+
|
| 27 |
+
def _save_temp_image(self, image: Image.Image) -> str:
|
| 28 |
+
"""Save image to temp file and return path"""
|
| 29 |
+
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
|
| 30 |
+
image.save(temp_file.name, format="PNG")
|
| 31 |
+
return temp_file.name
|
| 32 |
+
|
| 33 |
+
def generate(
|
| 34 |
+
self,
|
| 35 |
+
input_image: Image.Image,
|
| 36 |
+
prompt: str,
|
| 37 |
+
negative_prompt: str,
|
| 38 |
+
custom_settings: Optional[dict] = None
|
| 39 |
+
) -> Image.Image:
|
| 40 |
+
"""
|
| 41 |
+
Generate image using Replicate InstantID
|
| 42 |
+
|
| 43 |
+
Args:
|
| 44 |
+
input_image: PIL Image
|
| 45 |
+
prompt: Positive prompt
|
| 46 |
+
negative_prompt: Negative prompt
|
| 47 |
+
custom_settings: Override default settings (cfg, steps, etc.)
|
| 48 |
+
|
| 49 |
+
Returns:
|
| 50 |
+
Generated PIL Image
|
| 51 |
+
"""
|
| 52 |
+
# Merge settings
|
| 53 |
+
settings = {**self.default_settings}
|
| 54 |
+
if custom_settings:
|
| 55 |
+
settings.update(custom_settings)
|
| 56 |
+
|
| 57 |
+
# Save temp image and get file object
|
| 58 |
+
temp_path = self._save_temp_image(input_image)
|
| 59 |
+
|
| 60 |
+
try:
|
| 61 |
+
# Prepare input
|
| 62 |
+
input_params = {
|
| 63 |
+
"image": open(temp_path, "rb"),
|
| 64 |
+
"prompt": prompt,
|
| 65 |
+
"negative_prompt": negative_prompt,
|
| 66 |
+
**settings
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
# Run prediction (streaming)
|
| 70 |
+
output = replicate.run(self.model, input=input_params)
|
| 71 |
+
|
| 72 |
+
# Get final image from iterator
|
| 73 |
+
result_url = None
|
| 74 |
+
for item in output:
|
| 75 |
+
result_url = item # Last item is the final image URL
|
| 76 |
+
|
| 77 |
+
if not result_url:
|
| 78 |
+
raise ValueError("No output received from Replicate")
|
| 79 |
+
|
| 80 |
+
# Download and convert to PIL
|
| 81 |
+
import requests
|
| 82 |
+
response = requests.get(result_url)
|
| 83 |
+
result_image = Image.open(io.BytesIO(response.content))
|
| 84 |
+
|
| 85 |
+
return result_image
|
| 86 |
+
|
| 87 |
+
finally:
|
| 88 |
+
# Cleanup temp file
|
| 89 |
+
if os.path.exists(temp_path):
|
| 90 |
+
os.unlink(temp_path)
|