Spaces:
Sleeping
Sleeping
Commit
·
71bc000
1
Parent(s):
aded6c4
add: new endpoint product page-->batch nto,cto,mto generation
Browse files- app.py +76 -1
- src/components/product_page_nto_cto_gen.py +272 -0
app.py
CHANGED
@@ -2,6 +2,7 @@ import mimetypes
|
|
2 |
import os
|
3 |
import tempfile
|
4 |
import time
|
|
|
5 |
from typing import List
|
6 |
|
7 |
import requests
|
@@ -11,15 +12,27 @@ from starlette.responses import JSONResponse
|
|
11 |
from supabase import create_client
|
12 |
|
13 |
from src.components.each_necklace_video_gen import EachVideoCreator
|
|
|
14 |
from src.components.vidgen import VideoCreator
|
15 |
from src.utils.logs import logger
|
16 |
|
17 |
supabase_url = os.getenv('SUPABASE_URL')
|
18 |
supabase_key = os.getenv('SUPABASE_KEY')
|
19 |
supabase = create_client(supabase_url, supabase_key)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
app = FastAPI()
|
21 |
|
22 |
-
RESOURCES_DIR = "
|
23 |
|
24 |
os.makedirs(RESOURCES_DIR, exist_ok=True)
|
25 |
TEMP_VIDEO_DIR = f"{RESOURCES_DIR}/temp_video"
|
@@ -337,6 +350,68 @@ async def get_information():
|
|
337 |
return JSONResponse(content=json, status_code=200)
|
338 |
|
339 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
340 |
if __name__ == "__main__":
|
341 |
import uvicorn
|
342 |
|
|
|
2 |
import os
|
3 |
import tempfile
|
4 |
import time
|
5 |
+
from dataclasses import dataclass
|
6 |
from typing import List
|
7 |
|
8 |
import requests
|
|
|
12 |
from supabase import create_client
|
13 |
|
14 |
from src.components.each_necklace_video_gen import EachVideoCreator
|
15 |
+
from src.components.product_page_nto_cto_gen import ProductPageImageGeneration
|
16 |
from src.components.vidgen import VideoCreator
|
17 |
from src.utils.logs import logger
|
18 |
|
19 |
supabase_url = os.getenv('SUPABASE_URL')
|
20 |
supabase_key = os.getenv('SUPABASE_KEY')
|
21 |
supabase = create_client(supabase_url, supabase_key)
|
22 |
+
|
23 |
+
prod_page = ProductPageImageGeneration()
|
24 |
+
|
25 |
+
|
26 |
+
@dataclass
|
27 |
+
class MakeupColors:
|
28 |
+
lipstick: str
|
29 |
+
eyeliner: str
|
30 |
+
eyeshadow: str
|
31 |
+
|
32 |
+
|
33 |
app = FastAPI()
|
34 |
|
35 |
+
RESOURCES_DIR = "resources"
|
36 |
|
37 |
os.makedirs(RESOURCES_DIR, exist_ok=True)
|
38 |
TEMP_VIDEO_DIR = f"{RESOURCES_DIR}/temp_video"
|
|
|
350 |
return JSONResponse(content=json, status_code=200)
|
351 |
|
352 |
|
353 |
+
def ensure_json_serializable(data):
|
354 |
+
if isinstance(data, list):
|
355 |
+
return [ensure_json_serializable(item) for item in data]
|
356 |
+
elif isinstance(data, dict):
|
357 |
+
return {key: ensure_json_serializable(value) for key, value in data.items()}
|
358 |
+
elif hasattr(data, "__dict__"):
|
359 |
+
return ensure_json_serializable(data.__dict__)
|
360 |
+
else:
|
361 |
+
return data
|
362 |
+
|
363 |
+
|
364 |
+
@app.post("/product_page_image_generation")
|
365 |
+
async def product_page_image_generation(model_image: str, necklace_id: str, necklace_category: str, storename: str,
|
366 |
+
x_offset: str,
|
367 |
+
y_offset: str,
|
368 |
+
clothing_list: List[str],
|
369 |
+
makeup_colors: MakeupColors):
|
370 |
+
try:
|
371 |
+
model_name = model_image.split("/")[-1].split(".")[0]
|
372 |
+
|
373 |
+
model_image_path = download_image(model_image)
|
374 |
+
|
375 |
+
if model_image_path is None:
|
376 |
+
return JSONResponse(content={"status": "error", "message": "Failed to download model image"},
|
377 |
+
status_code=400)
|
378 |
+
|
379 |
+
response = prod_page.process_full_tryon_sequence(
|
380 |
+
model_image_path,
|
381 |
+
necklace_id,
|
382 |
+
{
|
383 |
+
"id": necklace_id,
|
384 |
+
"category": necklace_category,
|
385 |
+
"store_name": storename,
|
386 |
+
"offset_x": x_offset,
|
387 |
+
"offset_y": y_offset
|
388 |
+
},
|
389 |
+
|
390 |
+
clothing_list,
|
391 |
+
makeup_colors,
|
392 |
+
model_name=model_name
|
393 |
+
)
|
394 |
+
|
395 |
+
nto_results = response["nto_results"]
|
396 |
+
cto_results = response["cto_results"]
|
397 |
+
mto_results = response["mto_results"]
|
398 |
+
|
399 |
+
json_response = {
|
400 |
+
"status": "success",
|
401 |
+
"message": "Product page images generated successfully.",
|
402 |
+
"nto_results": ensure_json_serializable(nto_results),
|
403 |
+
"cto_results": ensure_json_serializable(cto_results),
|
404 |
+
"mto_results": ensure_json_serializable(mto_results)
|
405 |
+
}
|
406 |
+
|
407 |
+
return JSONResponse(content=json_response, status_code=200)
|
408 |
+
|
409 |
+
|
410 |
+
|
411 |
+
except Exception as e:
|
412 |
+
return JSONResponse(content={"status": "error", "message": str(e)}, status_code=500)
|
413 |
+
|
414 |
+
|
415 |
if __name__ == "__main__":
|
416 |
import uvicorn
|
417 |
|
src/components/product_page_nto_cto_gen.py
ADDED
@@ -0,0 +1,272 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from dataclasses import dataclass
|
3 |
+
from typing import List, Dict, Optional
|
4 |
+
|
5 |
+
import requests
|
6 |
+
from supabase import create_client
|
7 |
+
|
8 |
+
from src.utils.logs import logger
|
9 |
+
|
10 |
+
supabase_url = os.getenv('SUPABASE_URL')
|
11 |
+
supabase_key = os.getenv('SUPABASE_KEY')
|
12 |
+
supabase = create_client(supabase_url, supabase_key)
|
13 |
+
|
14 |
+
|
15 |
+
@dataclass
|
16 |
+
class MakeupColors:
|
17 |
+
lipstick: str
|
18 |
+
eyeliner: str
|
19 |
+
eyeshadow: str
|
20 |
+
|
21 |
+
|
22 |
+
class ProductPageImageGeneration:
|
23 |
+
def __init__(self):
|
24 |
+
self.nto_endpoint = "https://techconspartners-cfgvigrclbw4urtwcjficydv0edz7x.hf.space/necklaceTryOnID"
|
25 |
+
self.cto_endpoint = "https://techconspartners-cfgvigrclbw4urtwcjficydv0edz7x.hf.space/clothingTryOnV2"
|
26 |
+
self.mto_endpoint = "https://techconspartners-cfgvigrclbw4urtwcjficydv0edz7x.hf.space/makeup_tryon/tryon"
|
27 |
+
self.destination_bucket = "ProductPageOutputs"
|
28 |
+
|
29 |
+
def generate_filename(self,
|
30 |
+
base_id: str,
|
31 |
+
model_name: str,
|
32 |
+
try_on_type: str,
|
33 |
+
item_type: Optional[str] = None,
|
34 |
+
makeup_colors: Optional[MakeupColors] = None) -> str:
|
35 |
+
"""Generate a consistent filename for storage"""
|
36 |
+
parts = [base_id, model_name, try_on_type]
|
37 |
+
|
38 |
+
if item_type and not isinstance(item_type, MakeupColors):
|
39 |
+
logger.info(f"Item type: {item_type}")
|
40 |
+
parts.append(item_type.replace(' ', '_'))
|
41 |
+
|
42 |
+
if makeup_colors:
|
43 |
+
colors_str = f"lip_{makeup_colors.lipstick}_eye_{makeup_colors.eyeliner}_shadow_{makeup_colors.eyeshadow}"
|
44 |
+
parts.append(colors_str.replace(' ', '_'))
|
45 |
+
|
46 |
+
return f"{'-'.join(parts)}.png"
|
47 |
+
|
48 |
+
def check_file_exists(self, filename: str) -> bool:
|
49 |
+
"""Check if file already exists in Supabase storage"""
|
50 |
+
try:
|
51 |
+
supabase.storage.from_(self.destination_bucket).get_public_url(filename)
|
52 |
+
return True
|
53 |
+
except:
|
54 |
+
return False
|
55 |
+
|
56 |
+
def upload_to_supabase(self, image_content: bytes, filename: str) -> str:
|
57 |
+
"""Upload or replace file in Supabase storage"""
|
58 |
+
try:
|
59 |
+
# Delete existing file if it exists
|
60 |
+
if self.check_file_exists(filename):
|
61 |
+
try:
|
62 |
+
supabase.storage.from_(self.destination_bucket).remove([filename])
|
63 |
+
except Exception as e:
|
64 |
+
logger.error(f"Error deleting existing file {filename}: {str(e)}")
|
65 |
+
|
66 |
+
# Upload new file
|
67 |
+
result = supabase.storage.from_(self.destination_bucket).upload(
|
68 |
+
filename,
|
69 |
+
image_content,
|
70 |
+
{"content-type": "image/png"}
|
71 |
+
)
|
72 |
+
|
73 |
+
return supabase.storage.from_(self.destination_bucket).get_public_url(filename)
|
74 |
+
except Exception as e:
|
75 |
+
print(f"Error uploading {filename}: {str(e)}")
|
76 |
+
return ""
|
77 |
+
|
78 |
+
def process_necklace_tryon(self,
|
79 |
+
image_path: str,
|
80 |
+
necklace_id: str,
|
81 |
+
category: str,
|
82 |
+
store_name: str,
|
83 |
+
offset_x: float,
|
84 |
+
offset_y: float) -> dict:
|
85 |
+
"""Process a necklace try-on request"""
|
86 |
+
try:
|
87 |
+
with open(image_path, 'rb') as img_file:
|
88 |
+
files = {
|
89 |
+
'image': (os.path.basename(image_path), img_file, 'image/jpeg')
|
90 |
+
}
|
91 |
+
data = {
|
92 |
+
'necklaceImageId': necklace_id,
|
93 |
+
'necklaceCategory': category,
|
94 |
+
'storename': store_name,
|
95 |
+
'offset_x': str(offset_x),
|
96 |
+
'offset_y': str(offset_y)
|
97 |
+
}
|
98 |
+
response = requests.post(self.nto_endpoint, files=files, data=data)
|
99 |
+
response.raise_for_status()
|
100 |
+
logger.info(f"Necklace Try on Completed for {necklace_id}")
|
101 |
+
return response.json()
|
102 |
+
except Exception as e:
|
103 |
+
print(f"Error in necklace try-on: {str(e)}")
|
104 |
+
return None
|
105 |
+
|
106 |
+
def process_clothing_tryon(self, image_path: str, clothing_type: str) -> dict:
|
107 |
+
"""Process a clothing try-on request"""
|
108 |
+
try:
|
109 |
+
with open(image_path, 'rb') as img_file:
|
110 |
+
files = {
|
111 |
+
'image': (os.path.basename(image_path), img_file, 'image/jpeg')
|
112 |
+
}
|
113 |
+
data = {
|
114 |
+
'clothing_type': clothing_type
|
115 |
+
}
|
116 |
+
response = requests.post(self.cto_endpoint, files=files, data=data)
|
117 |
+
response.raise_for_status()
|
118 |
+
logger.info(f"Clothing Try on Completed for {clothing_type}")
|
119 |
+
return response.json()
|
120 |
+
|
121 |
+
except Exception as e:
|
122 |
+
logger.info(f"Error in clothing try-on: {str(e)}")
|
123 |
+
return None
|
124 |
+
|
125 |
+
def process_makeup_tryon(self,
|
126 |
+
image_path: str,
|
127 |
+
makeup_colors: MakeupColors) -> dict:
|
128 |
+
"""Process a makeup try-on request"""
|
129 |
+
try:
|
130 |
+
with open(image_path, 'rb') as img_file:
|
131 |
+
files = {
|
132 |
+
'image': (os.path.basename(image_path), img_file, 'image/jpeg')
|
133 |
+
}
|
134 |
+
data = {
|
135 |
+
'lipstick_color': makeup_colors.lipstick,
|
136 |
+
'eyeliner_color': makeup_colors.eyeliner,
|
137 |
+
'eyeshadow_color': makeup_colors.eyeshadow
|
138 |
+
}
|
139 |
+
response = requests.post(self.mto_endpoint, files=files, data=data)
|
140 |
+
response.raise_for_status()
|
141 |
+
logger.info(f"Makeup Try on Completed for {makeup_colors}")
|
142 |
+
return response.json()
|
143 |
+
except Exception as e:
|
144 |
+
print(f"Error in makeup try-on: {str(e)}")
|
145 |
+
return None
|
146 |
+
|
147 |
+
def process_full_tryon_sequence(self,
|
148 |
+
model_image_path: str,
|
149 |
+
model_id: str,
|
150 |
+
necklace_config: Dict,
|
151 |
+
clothing_list: List[str],
|
152 |
+
makeup_colors: MakeupColors,
|
153 |
+
model_name) -> dict:
|
154 |
+
"""Process a complete sequence of try-ons"""
|
155 |
+
results = {
|
156 |
+
"nto_results": [],
|
157 |
+
"cto_results": [],
|
158 |
+
"mto_results": []
|
159 |
+
}
|
160 |
+
|
161 |
+
# 1. First, process necklace try-on
|
162 |
+
nto_response = self.process_necklace_tryon(
|
163 |
+
model_image_path,
|
164 |
+
necklace_config['id'],
|
165 |
+
necklace_config['category'],
|
166 |
+
necklace_config['store_name'],
|
167 |
+
necklace_config['offset_x'],
|
168 |
+
necklace_config['offset_y']
|
169 |
+
)
|
170 |
+
|
171 |
+
if nto_response and 'output' in nto_response:
|
172 |
+
try:
|
173 |
+
image_response = requests.get(nto_response['output'])
|
174 |
+
filename = self.generate_filename(model_id, model_name, "nto", necklace_config['category'])
|
175 |
+
url = self.upload_to_supabase(image_response.content, filename)
|
176 |
+
if url:
|
177 |
+
results["nto_results"].append({
|
178 |
+
"necklace_id": necklace_config['id'],
|
179 |
+
"url": url
|
180 |
+
})
|
181 |
+
except Exception as e:
|
182 |
+
print(f"Error processing necklace result: {str(e)}")
|
183 |
+
|
184 |
+
# 2. Process each clothing type
|
185 |
+
for clothing_type in clothing_list:
|
186 |
+
cto_response = self.process_clothing_tryon(model_image_path, clothing_type)
|
187 |
+
|
188 |
+
if cto_response and cto_response.get("code") == 200:
|
189 |
+
try:
|
190 |
+
# Get the clothing try-on result
|
191 |
+
cto_image = requests.get(cto_response['output'])
|
192 |
+
|
193 |
+
# Save clothing result
|
194 |
+
filename = self.generate_filename(model_id, "cto", clothing_type)
|
195 |
+
cto_url = self.upload_to_supabase(cto_image.content, filename)
|
196 |
+
|
197 |
+
if cto_url:
|
198 |
+
results["cto_results"].append({
|
199 |
+
"clothing_type": clothing_type,
|
200 |
+
"url": cto_url
|
201 |
+
})
|
202 |
+
|
203 |
+
# 3. Apply makeup to clothing result
|
204 |
+
temp_path = f"temp_{model_id}_{clothing_type}.png"
|
205 |
+
with open(temp_path, 'wb') as f:
|
206 |
+
f.write(cto_image.content)
|
207 |
+
|
208 |
+
mto_response = self.process_makeup_tryon(temp_path, makeup_colors)
|
209 |
+
os.remove(temp_path) # Clean up temp file
|
210 |
+
|
211 |
+
if mto_response and mto_response.get("code") == 200:
|
212 |
+
mto_image = requests.get(mto_response['output'])
|
213 |
+
filename = self.generate_filename(
|
214 |
+
model_id,
|
215 |
+
model_name,
|
216 |
+
"mto",
|
217 |
+
item_type=clothing_type,
|
218 |
+
makeup_colors=makeup_colors
|
219 |
+
)
|
220 |
+
mto_url = self.upload_to_supabase(mto_image.content, filename)
|
221 |
+
|
222 |
+
if mto_url:
|
223 |
+
results["mto_results"].append({
|
224 |
+
"clothing_type": clothing_type,
|
225 |
+
"makeup_colors": makeup_colors,
|
226 |
+
"url": mto_url
|
227 |
+
})
|
228 |
+
|
229 |
+
logger.info(f"Makeup Try on Completed for {clothing_type}")
|
230 |
+
|
231 |
+
except Exception as e:
|
232 |
+
logger.error(f"Error processing {clothing_type}: {str(e)}")
|
233 |
+
print(f"Error processing {clothing_type}: {str(e)}")
|
234 |
+
|
235 |
+
return results
|
236 |
+
|
237 |
+
|
238 |
+
# Example usage:
|
239 |
+
"""
|
240 |
+
processor = FashionTryOnProcessor()
|
241 |
+
|
242 |
+
# Configuration
|
243 |
+
necklace_config = {
|
244 |
+
"id": "necklace123",
|
245 |
+
"category": "traditional",
|
246 |
+
"store_name": "jewelry_store",
|
247 |
+
"offset_x": 0,
|
248 |
+
"offset_y": 0
|
249 |
+
}
|
250 |
+
|
251 |
+
clothing_list = ["silk saree", "kurti", "casual dress"]
|
252 |
+
|
253 |
+
makeup_colors = MakeupColors(
|
254 |
+
lipstick="Carmine Red",
|
255 |
+
eyeliner="Black",
|
256 |
+
eyeshadow="Maroon"
|
257 |
+
)
|
258 |
+
|
259 |
+
# Process everything
|
260 |
+
results = processor.process_full_tryon_sequence(
|
261 |
+
model_image_path="path/to/model/image.jpg",
|
262 |
+
model_id="model123",
|
263 |
+
necklace_config=necklace_config,
|
264 |
+
clothing_list=clothing_list,
|
265 |
+
makeup_colors=makeup_colors
|
266 |
+
)
|
267 |
+
|
268 |
+
# Print results
|
269 |
+
print("Necklace try-ons:", len(results["nto_results"]))
|
270 |
+
print("Clothing try-ons:", len(results["cto_results"]))
|
271 |
+
print("Makeup try-ons:", len(results["mto_results"]))
|
272 |
+
"""
|