VcRlAgent commited on
Commit
d7c5b1f
·
1 Parent(s): e2c2f76

Refactor for Headshort and Scene Generation using Instant-ID model hosted in Replicate

Browse files
Files changed (10) hide show
  1. .env.example +16 -0
  2. .gitignore +4 -1
  3. README.md +153 -65
  4. app.py +241 -227
  5. app.py.wip_avatar +0 -423
  6. config.yaml +130 -0
  7. env.example +13 -0
  8. requirements.txt +8 -46
  9. src/rateLimiter.py +0 -0
  10. 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
- title: FaceForgeAI ZeroGPU
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
- # 🎨 FaceForge AI – ZeroGPU Gradio Edition
15
- [![Hugging Face Space](https://img.shields.io/badge/🤗%20Open%20in-Hugging%20Face%20Space-yellow)](https://huggingface.co/spaces/VcRlAgent/FaceForgeAI_ZeroGPU)
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 open-source generative AI:
24
- - 🪄 **Background Remover** – clean gradient background for profile photos
25
- - 🛂 **Passport Photo** – compliant 600×600 white-background image (Requires picture with frontal face)
26
- - 🎭 **Stylized AI Avatar** – realistic yet personalized stylization
27
 
28
- This edition is optimized for **ZeroGPU Spaces** efficient, on-demand GPU execution using CPU offload and attention slicing for lightweight inference.
29
 
30
  ---
31
 
32
- ## ⚙️ Key Features
33
- ✅ **Three Modes**
34
- - **Background Remover** – removes background and enhances clarity
35
- - **Passport** – white background, standard size
36
- - **Avatar** – realistic stylization via Stable Diffusion Img2Img
 
37
 
38
- ✅ **User Control for Avatar Generation**
39
- - Preset prompt styles + custom text input
40
- - Adjustable `strength` & `guidance_scale` sliders
 
 
 
 
 
 
 
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
- | **Stable Diffusion v1.5** | Img2Img stylization |
51
- | **GFPGAN 1.3.8** | Facial restoration |
52
- | **Real-ESRGAN 0.3.0** | Super-resolution |
53
  | **Rembg 2.x** | Background removal |
54
- | **Pillow / OpenCV / Torch** | Image processing + GPU acceleration |
55
-
56
 
57
  ---
58
 
59
- ## 🧰 Installation
60
 
61
  ### 1️⃣ Clone Repository
62
  ```bash
63
  git clone https://github.com/agentofAI/FaceForgeAI.git
64
- cd FaceForgeAI_ZeroGPU
65
  ```
66
 
67
  ### 2️⃣ Install Dependencies
@@ -69,7 +63,15 @@ cd FaceForgeAI_ZeroGPU
69
  pip install -r requirements.txt
70
  ```
71
 
72
- ### 3️⃣ Run Locally
 
 
 
 
 
 
 
 
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
- ## 🎨 Avatar Prompt Presets
81
 
82
- | Style Label | Prompt |
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
- ## 🧾 Model Credits
92
- | Model | Source / License |
93
- | ------------------------- | ------------------------------------------------------------------------------------------------------------- |
94
- | **Stable Diffusion v1.5** | [runwayml/stable-diffusion-v1-5](https://huggingface.co/runwayml/stable-diffusion-v1-5) (CompVis / Runway ML) |
95
- | **GFPGAN v1.3** | [TencentARC/GFPGAN](https://github.com/TencentARC/GFPGAN) (MIT License) |
96
- | **Real-ESRGAN x2Plus** | [xinntao/Real-ESRGAN](https://github.com/xinntao/Real-ESRGAN) (BSD License) |
97
- | **Rembg** | [danielgatis/rembg](https://github.com/danielgatis/rembg) |
98
- | **Gradio** | [gradio-app/gradio](https://github.com/gradio-app/gradio) |
99
 
100
  ---
101
 
102
- ## 🧠 Project Structure
 
103
  ```
104
  faceforge-ai/
105
 
106
- ├── app.py # Main application
107
- ├── requirements.txt # Dependencies
108
- ├── assets/ # Optional screenshots and samples
109
- └── README.md # Documentation
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ [![Hugging Face Space](https://img.shields.io/badge/🤗%20Open%20in-Hugging%20Face%20Space-yellow)](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 spaces
8
  import torch
9
- import cv2
10
- import numpy as np
11
- from PIL import Image, ImageEnhance, ImageOps
12
  from rembg import remove
13
- from diffusers import StableDiffusionImg2ImgPipeline
14
- from accelerate import Accelerator
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
- # Model Loading (Outside GPU decorator)
32
  # ------------------------------------------
33
 
34
- def load_models():
35
- """Load models once at startup"""
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
- return face_enhancer, sd_pipe
 
 
 
 
 
79
 
80
- # Load models globally
81
- face_enhancer, sd_pipe = load_models()
 
 
82
 
83
  # ------------------------------------------
84
- # GPU-Accelerated Functions
85
  # ------------------------------------------
86
 
87
- @spaces.GPU
88
- def enhance_face(img: Image.Image) -> Image.Image:
89
- """Enhance face using GFPGAN (GPU)"""
90
- img_cv = cv2.cvtColor(np.array(img.convert('RGB')), cv2.COLOR_RGB2BGR)
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
- with torch.no_grad():
93
- _, _, restored_img = face_enhancer.enhance(
94
- img_cv,
95
- has_aligned=False,
96
- only_center_face=False,
97
- paste_back=True,
98
- weight=0.5
99
- )
100
 
101
- restored_img = cv2.cvtColor(restored_img, cv2.COLOR_BGR2RGB)
102
- return Image.fromarray(restored_img)
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
- # Remove background
121
- img_no_bg = remove(img_enhanced)
 
 
122
 
123
- # Gradient background
124
- bg = Image.new("RGB", img_no_bg.size, (200, 210, 230))
125
- if img_no_bg.mode == 'RGBA':
126
- bg.paste(img_no_bg, mask=img_no_bg.split()[3])
 
 
 
 
127
 
128
- return enhance_image(bg)
129
-
130
- @spaces.GPU
131
- def create_passport(img: Image.Image) -> Image.Image:
132
- """Passport photo with white background"""
133
- # Enhance face
134
- img_enhanced = enhance_face(img)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
- # Remove background
137
- img_no_bg = remove(img_enhanced)
 
 
138
 
139
- # White background (600x600)
140
- bg = Image.new("RGB", (600, 600), (255, 255, 255))
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
- if img_no_bg.mode == 'RGBA':
145
- bg.paste(img_no_bg, offset, mask=img_no_bg.split()[3])
 
 
146
 
147
- return bg
148
-
149
- @spaces.GPU
150
- def create_avatar(img: Image.Image, prompt: str, strength: float, guidance_scale: float) -> Image.Image:
151
- """Stylized AI avatar using Stable Diffusion Img2Img with user inputs"""
152
- # Enhance face
153
- img_enhanced = enhance_face(img)
 
154
 
155
- # Resize for SD (512x512)
156
- img_resized = img_enhanced.convert("RGB").resize((512, 512))
157
-
158
- # Stylize with SD prompt. We are selecting these from UI now.
159
- #prompt = "highly detailed, digital portrait, professional lighting, cinematic style, artistic AI avatar"
160
- #prompt = "stylized yet realistic portrait, balanced lighting, subtle gradient background, sharp focus on face"
161
- #prompt = "studio portrait, even lighting, neutral background, realistic skin, confident pose"
162
- #prompt = "realistic professional headshot, soft studio lighting, neutral background, crisp details, natural skin tone"
 
 
 
 
 
 
 
 
163
 
164
- with torch.autocast("cuda"):
165
- result = sd_pipe(prompt=prompt, image=img_resized, strength=strength, guidance_scale=guidance_scale)
166
 
167
- avatar = enhance_face(result.images[0])
168
-
169
- return avatar
 
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
- ### GPU-Accelerated Professional Headshot & Avatar Generator
188
- Upload your photo and choose or customize how your AI avatar is generated.
189
  """
190
  )
191
-
192
- # --- Define a mapping: Short Label -> Full Prompt Text ---
193
- PROMPT_MAP = {
194
- "🎬 Cinematic Portrait": "highly detailed, digital portrait, professional lighting, cinematic style, artistic AI avatar",
195
- "🎨 Stylized Realism": "stylized yet realistic portrait, balanced lighting, subtle gradient background, sharp focus on face",
196
- "🏢 Studio Professional": "studio portrait, even lighting, neutral background, realistic skin, confident pose",
197
- "🤵 Natural Headshot": "realistic professional headshot, soft studio lighting, neutral background, crisp details, natural skin tone"
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("### ⚙️ Avatar Generation Settings")
205
-
206
- # Dropdown shows short labels only
207
- preset_prompt = gr.Dropdown(
208
- label="🎨 Choose Avatar Style Preset",
209
- choices=list(PROMPT_MAP.keys()),
210
- value="🤵 Natural Headshot"
211
  )
212
-
213
- # Optional custom prompt box for flexibility
214
- custom_prompt = gr.Textbox(
215
- label="✏️ Custom Prompt (optional)",
216
- placeholder="Enter your own prompt or leave blank to use preset...",
217
- lines=2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  )
219
-
220
- strength_slider = gr.Slider(
221
- label="🎛️ Style Strength (0.0 = keep original, 1.0 = full restyle)",
222
- minimum=0.1,
223
- maximum=1.0,
224
- value=0.45,
225
- step=0.05
 
 
 
 
 
 
 
 
 
 
226
  )
227
-
228
- guidance_slider = gr.Slider(
229
- label="🎯 Prompt Guidance Scale (higher = more prompt influence)",
230
- minimum=1.0,
231
- maximum=10.0,
232
- value=5.5,
233
- step=0.5
 
 
 
 
234
  )
235
-
236
- with gr.Column(scale=1):
237
- gr.Markdown("### Results")
238
-
239
- # --- Independent Outputs & Buttons ---
240
- output_headshot = gr.Image(label="💼 Professional Headshot", type="pil")
241
- btn_headshot = gr.Button("📸 Generate Headshot", variant="secondary")
242
-
243
- output_passport = gr.Image(label="🛂 Passport Photo", type="pil")
244
- btn_passport = gr.Button("🪪 Generate Passport", variant="secondary")
245
-
246
- output_avatar = gr.Image(label="🎭 AI Avatar", type="pil")
247
- btn_avatar = gr.Button("✨ Generate Avatar", variant="primary")
248
-
249
- # --- Functions for individual generations ---
250
- def run_headshot(img):
251
- return create_headshot(img)
252
-
253
- def run_passport(img):
254
- return create_passport(img)
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
- - 💼 **Professional Headshots**: Perfect for LinkedIn and business profiles
274
- - 🛂 **Passport Photos**: Standard 600x600px with white background
275
- - 🎭 **AI Avatars**: Stylized versions for social media (Work In Progress - Identity Retention)
276
- - ⚡ **GPU-Accelerated**: Fast processing with GFPGAN enhancement
 
277
 
278
- © 2025 Vijay S. Chaudhari | Powered by ZeroGPU 🚀
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
- # FaceForge AI - ZeroGPU Gradio Requirements
3
- # ==========================================
4
-
5
- # Gradio & ZeroGPU (REQUIRED)
6
- gradio==4.16.0
7
- spaces==0.19.4
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)