LogicGoInfotechSpaces commited on
Commit
533b0a8
·
verified ·
1 Parent(s): a79508a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +79 -216
app.py CHANGED
@@ -14,13 +14,12 @@ import insightface
14
  from insightface.app import FaceAnalysis
15
  from huggingface_hub import hf_hub_download
16
 
17
- from fastapi import FastAPI, UploadFile, File, HTTPException, Response, Depends, Security, Query
18
  from fastapi.responses import RedirectResponse
19
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
20
- from fastapi.concurrency import run_in_threadpool
21
- from pydantic import BaseModel
22
  from motor.motor_asyncio import AsyncIOMotorClient
23
-
 
24
  import uvicorn
25
  import gradio as gr
26
  from gradio import mount_gradio_app
@@ -28,36 +27,41 @@ from gradio import mount_gradio_app
28
  # DigitalOcean Spaces
29
  import boto3
30
  from botocore.client import Config
31
- from io import BytesIO
32
- from typing import Optional
33
  # --------------------- Logging ---------------------
34
  logging.basicConfig(level=logging.INFO)
35
  logger = logging.getLogger(__name__)
36
 
37
- # --------------------- Paths -----------------------
38
  REPO_ID = "HariLogicgo/face_swap_models"
39
- BASE_DIR = "./workspace"
40
  MODELS_DIR = "./models"
41
-
42
  os.makedirs(MODELS_DIR, exist_ok=True)
43
 
44
- # --------------------- Secrets ---------------------
45
- HF_TOKEN = os.getenv("HF_TOKEN") # Hugging Face private repo token
46
- API_SECRET_TOKEN = os.getenv("API_SECRET_TOKEN") # Bearer token for API
47
- # --------------------- DigitalOcean Spaces Credentials ---------------------
48
- DO_SPACES_REGION = os.getenv("DO_SPACES_REGION", "blr1") # Default region = Bangalore
49
- DO_SPACES_ENDPOINT = os.getenv("DO_SPACES_ENDPOINT", f"https://{DO_SPACES_REGION}.digitaloceanspaces.com")
50
- DO_SPACES_KEY = os.getenv("DO_SPACES_KEY") # Your Access Key
51
- DO_SPACES_SECRET = os.getenv("DO_SPACES_SECRET") # Your Secret Key
52
- DO_SPACES_BUCKET = os.getenv("DO_SPACES_BUCKET") # Bucket Name
53
- ADMIN_MONGO_URL = os.getenv("ADMIN_MONGO_URL") #MONGODB Storage for usage count
 
54
  admin_client = AsyncIOMotorClient(ADMIN_MONGO_URL)
55
  admin_db = admin_client.adminPanel
56
  subcategories_col = admin_db.subcategories
57
  media_clicks_col = admin_db.media_clicks
 
 
 
 
 
 
58
  # --------------------- Download Models ---------------------
59
  def download_models():
60
- logger.info("Downloading models from private HF repo...")
61
  inswapper_path = hf_hub_download(
62
  repo_id=REPO_ID,
63
  filename="models/inswapper_128.onnx",
@@ -66,24 +70,20 @@ def download_models():
66
  token=HF_TOKEN
67
  )
68
 
69
- buffalo_files = [
70
- "1k3d68.onnx",
71
- "2d106det.onnx",
72
- "genderage.onnx",
73
- "det_10g.onnx",
74
- "w600k_r50.onnx"
75
- ]
76
  for f in buffalo_files:
77
  hf_hub_download(
78
  repo_id=REPO_ID,
79
- filename=f"models/buffalo_l/{f}",
80
  repo_type="model",
81
  local_dir=MODELS_DIR,
82
  token=HF_TOKEN
83
  )
84
- logger.info("Models downloaded successfully")
 
85
  return inswapper_path
86
 
 
87
  inswapper_path = download_models()
88
 
89
  # --------------------- Face Analysis + Swapper ---------------------
@@ -105,11 +105,6 @@ def ensure_codeformer():
105
 
106
  ensure_codeformer()
107
 
108
- # --------------------- MongoDB (for API logs only) ---------------------
109
- MONGODB_URL = os.getenv("MONGODB_URL")
110
-
111
- client = None
112
- database = None
113
 
114
  # --------------------- FastAPI ---------------------
115
  fastapi_app = FastAPI()
@@ -259,106 +254,63 @@ async def face_swap_api(
259
  source: UploadFile = File(...),
260
  target_category_id: str = Form(None),
261
  new_category_id: str = Form(None),
262
- user_id: Optional[str] = Form(None),
263
- credentials: HTTPAuthorizationCredentials = Security(security)
264
  ):
265
  start_time = datetime.utcnow()
266
 
267
  try:
268
- # ---------- VALIDATION ----------
269
  if target_category_id and new_category_id:
270
- raise HTTPException(
271
- status_code=400,
272
- detail="Provide only one of new_category_id or target_category_id, not both."
273
- )
274
 
275
  if not target_category_id and not new_category_id:
276
- raise HTTPException(
277
- status_code=400,
278
- detail="Either new_category_id or target_category_id is required."
279
- )
280
 
281
- # ---------- READ SOURCE ----------
282
  src_bytes = await source.read()
283
-
284
- # Save source to Spaces
285
  src_key = f"faceswap/source/{uuid.uuid4().hex}_{source.filename}"
286
- upload_to_spaces(src_bytes, src_key, content_type=source.content_type)
287
 
288
- # ==========================================================
289
- # CASE 1: NEW_CATEGORY_ID → Fetch from MongoDB
290
- # ==========================================================
291
  if new_category_id:
292
- from bson import ObjectId
293
-
294
- doc = await subcategories_col.find_one({
295
- "asset_images._id": ObjectId(new_category_id)
296
- })
297
 
298
  if not doc:
299
- raise HTTPException(status_code=404, detail="Asset image not found in database")
300
 
301
- # Find the asset image inside array
302
- asset = None
303
- for img in doc["asset_images"]:
304
- if str(img["_id"]) == str(new_category_id):
305
- asset = img
306
- break
307
 
308
  if not asset:
309
- raise HTTPException(status_code=404, detail="Asset image URL not found")
310
 
311
  target_url = asset["url"]
312
- category_id = str(doc["categoryId"])
313
 
314
- # ---------- media_clicks update only if user_id exists ----------
315
- # ---------- media_clicks update only if user_id exists ----------
316
  if user_id:
317
  try:
318
- logger.info("======= MEDIA_CLICKS DEBUG LOG =======")
319
- logger.info(f"user_id received: {user_id}")
320
- logger.info(f"category_id resolved from subcategory: {category_id}")
321
- logger.info(f"ADMIN_MONGO_URL: {ADMIN_MONGO_URL}")
322
-
323
- try:
324
- user_oid = ObjectId(user_id)
325
- cat_oid = ObjectId(category_id)
326
- except Exception as conv_err:
327
- logger.error(f"❌ ObjectId conversion failed: {conv_err}")
328
- raise HTTPException(status_code=400, detail=f"Invalid user_id or category_id: {conv_err}")
329
-
330
  now = datetime.utcnow()
331
-
332
  update_result = await media_clicks_col.update_one(
 
333
  {
334
- "userId": user_oid,
335
- "categories.categoryId": cat_oid
336
- },
337
- {
338
- "$set": {
339
- "updatedAt": now,
340
- "categories.$.lastClickedAt": now
341
- },
342
- "$inc": {
343
- "categories.$.click_count": 1
344
- }
345
  }
346
  )
347
-
348
- logger.info(f"Existing category update matched_count: {update_result.matched_count}")
349
- logger.info(f"Existing category update modified_count: {update_result.modified_count}")
350
-
351
  if update_result.matched_count == 0:
352
- logger.info("Category not found → pushing new category entry")
353
-
354
- insert_result = await media_clicks_col.update_one(
355
  {"userId": user_oid},
356
  {
357
  "$setOnInsert": {"createdAt": now},
358
  "$set": {"updatedAt": now},
359
  "$push": {
360
  "categories": {
361
- "categoryId": cat_oid,
362
  "click_count": 1,
363
  "lastClickedAt": now
364
  }
@@ -366,151 +318,62 @@ async def face_swap_api(
366
  },
367
  upsert=True
368
  )
369
-
370
- logger.info(f"Insert/upsert raw_result: {insert_result.raw_result}")
371
-
372
- logger.info("======= END MEDIA_CLICKS LOG =======")
373
-
374
-
375
- except Exception as media_err:
376
- print("❌ ERROR while saving media_clicks:", str(media_err))
377
- # DO NOT break the face-swap flow
378
-
379
- # if user_id:
380
- # user_oid = ObjectId(user_id)
381
- # cat_oid = ObjectId(category_id)
382
-
383
- # now = datetime.utcnow()
384
-
385
- # # Try to update existing category entry
386
- # update_result = await media_clicks_col.update_one(
387
- # {
388
- # "userId": user_oid,
389
- # "categories.categoryId": cat_oid
390
- # },
391
- # {
392
- # "$set": {
393
- # "updatedAt": now,
394
- # "categories.$.lastClickedAt": now
395
- # },
396
- # "$inc": {
397
- # "categories.$.click_count": 1
398
- # }
399
- # }
400
- # )
401
-
402
- # # If category did not exist, push new category object
403
- # if update_result.matched_count == 0:
404
- # await media_clicks_col.update_one(
405
- # {"userId": user_oid},
406
- # {
407
- # "$setOnInsert": {"createdAt": now},
408
- # "$set": {"updatedAt": now},
409
- # "$push": {
410
- # "categories": {
411
- # "categoryId": cat_oid,
412
- # "click_count": 1,
413
- # "lastClickedAt": now
414
- # }
415
- # }
416
- # },
417
- # upsert=True
418
- # )
419
-
420
- # ==========================================================
421
- # CASE 2: EXISTING TARGET_CATEGORY_ID (DigitalOcean)
422
- # ==========================================================
423
  if target_category_id:
424
  client = get_spaces_client()
425
  base_prefix = "faceswap/target/"
426
  resp = client.list_objects_v2(Bucket=DO_SPACES_BUCKET, Prefix=base_prefix, Delimiter="/")
427
- categories = [prefix["Prefix"].split("/")[2] for prefix in resp.get("CommonPrefixes", [])]
428
 
 
429
  target_url = None
 
430
  for category in categories:
431
  original_prefix = f"faceswap/target/{category}/original/"
432
- thumb_prefix = f"faceswap/target/{category}/thumb/"
433
-
434
- original_objects = client.list_objects_v2(Bucket=DO_SPACES_BUCKET, Prefix=original_prefix)
435
- thumb_objects = client.list_objects_v2(Bucket=DO_SPACES_BUCKET, Prefix=thumb_prefix)
436
 
437
- original_files = [obj["Key"].split("/")[-1] for obj in original_objects.get("Contents", []) if obj["Key"].endswith(".png")]
438
- thumb_files = [obj["Key"].split("/")[-1] for obj in thumb_objects.get("Contents", []) if obj["Key"].endswith(".png")]
439
-
440
- for idx, filename in enumerate(sorted(original_files), start=1):
441
  cid = f"{category.lower()}image_{idx}"
442
- if filename in thumb_files and cid == target_category_id:
443
- target_url = f"https://{DO_SPACES_BUCKET}.blr1.digitaloceanspaces.com/{original_prefix}{filename}"
 
444
  break
 
445
  if target_url:
446
  break
447
 
448
  if not target_url:
449
- raise HTTPException(status_code=404, detail="Target categoryId not found")
450
-
451
- # ==========================================================
452
- # DOWNLOAD TARGET IMAGE
453
- # ==========================================================
454
- resp = requests.get(target_url)
455
- if resp.status_code != 200:
456
- raise HTTPException(status_code=404, detail="Target image could not be fetched")
457
-
458
- tgt_bytes = resp.content
459
-
460
- # Decode images
461
- src_array = np.frombuffer(src_bytes, np.uint8)
462
- tgt_array = np.frombuffer(tgt_bytes, np.uint8)
463
 
464
- src_bgr = cv2.imdecode(src_array, cv2.IMREAD_COLOR)
465
- tgt_bgr = cv2.imdecode(tgt_array, cv2.IMREAD_COLOR)
 
 
466
 
467
  if src_bgr is None or tgt_bgr is None:
468
- raise HTTPException(status_code=400, detail="Invalid image data")
469
 
 
470
  src_rgb = cv2.cvtColor(src_bgr, cv2.COLOR_BGR2RGB)
471
  tgt_rgb = cv2.cvtColor(tgt_bgr, cv2.COLOR_BGR2RGB)
472
 
473
- # ==========================================================
474
- # FACE SWAP
475
- # ==========================================================
476
  final_img, final_path, err = face_swap_and_enhance(src_rgb, tgt_rgb)
477
  if err:
478
- raise HTTPException(status_code=500, detail=err)
479
 
480
- # Upload output to Spaces
481
- with open(final_path, "rb") as f:
482
- result_bytes = f.read()
483
-
484
- result_key = f"faceswap/result/{uuid.uuid4().hex}_enhanced.png"
485
- result_url = upload_to_spaces(result_bytes, result_key, content_type="image/png")
486
-
487
- # Log success
488
- end_time = datetime.utcnow()
489
- response_time_ms = (end_time - start_time).total_seconds() * 1000
490
-
491
- if database is not None:
492
- await database.api_logs.insert_one({
493
- "endpoint": "/face-swap",
494
- "status": "success",
495
- "response_time_ms": response_time_ms,
496
- "timestamp": end_time
497
- })
498
 
499
  return {"result_key": result_key, "result_url": result_url}
500
 
501
  except Exception as e:
502
- # Log failure
503
- end_time = datetime.utcnow()
504
- response_time_ms = (end_time - start_time).total_seconds() * 1000
505
- if database is not None:
506
- await database.api_logs.insert_one({
507
- "endpoint": "/face-swap",
508
- "status": "fail",
509
- "response_time_ms": response_time_ms,
510
- "timestamp": end_time,
511
- "error": str(e)
512
- })
513
- raise HTTPException(status_code=500, detail=f"Face swap failed: {str(e)}")
514
 
515
  # @fastapi_app.post("/face-swap", dependencies=[Depends(verify_token)])
516
  # async def face_swap_api(
 
14
  from insightface.app import FaceAnalysis
15
  from huggingface_hub import hf_hub_download
16
 
17
+ from fastapi import FastAPI, UploadFile, File, HTTPException, Response, Depends, Security, Form
18
  from fastapi.responses import RedirectResponse
19
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
 
 
20
  from motor.motor_asyncio import AsyncIOMotorClient
21
+ from bson import ObjectId
22
+ import requests
23
  import uvicorn
24
  import gradio as gr
25
  from gradio import mount_gradio_app
 
27
  # DigitalOcean Spaces
28
  import boto3
29
  from botocore.client import Config
30
+ from typing import Optional
31
+
32
  # --------------------- Logging ---------------------
33
  logging.basicConfig(level=logging.INFO)
34
  logger = logging.getLogger(__name__)
35
 
36
+ # --------------------- Secrets & Paths ---------------------
37
  REPO_ID = "HariLogicgo/face_swap_models"
 
38
  MODELS_DIR = "./models"
 
39
  os.makedirs(MODELS_DIR, exist_ok=True)
40
 
41
+ HF_TOKEN = os.getenv("HF_TOKEN")
42
+ API_SECRET_TOKEN = os.getenv("API_SECRET_TOKEN")
43
+
44
+ DO_SPACES_REGION = os.getenv("DO_SPACES_REGION", "blr1")
45
+ DO_SPACES_ENDPOINT = f"https://{DO_SPACES_REGION}.digitaloceanspaces.com"
46
+ DO_SPACES_KEY = os.getenv("DO_SPACES_KEY")
47
+ DO_SPACES_SECRET = os.getenv("DO_SPACES_SECRET")
48
+ DO_SPACES_BUCKET = os.getenv("DO_SPACES_BUCKET")
49
+
50
+ # NEW admin DB
51
+ ADMIN_MONGO_URL = os.getenv("ADMIN_MONGO_URL")
52
  admin_client = AsyncIOMotorClient(ADMIN_MONGO_URL)
53
  admin_db = admin_client.adminPanel
54
  subcategories_col = admin_db.subcategories
55
  media_clicks_col = admin_db.media_clicks
56
+
57
+ # OLD logs DB
58
+ MONGODB_URL = os.getenv("MONGODB_URL")
59
+ client = None
60
+ database = None
61
+
62
  # --------------------- Download Models ---------------------
63
  def download_models():
64
+ logger.info("Downloading models...")
65
  inswapper_path = hf_hub_download(
66
  repo_id=REPO_ID,
67
  filename="models/inswapper_128.onnx",
 
70
  token=HF_TOKEN
71
  )
72
 
73
+ buffalo_files = ["1k3d68.onnx", "2d106det.onnx", "genderage.onnx", "det_10g.onnx", "w600k_r50.onnx"]
 
 
 
 
 
 
74
  for f in buffalo_files:
75
  hf_hub_download(
76
  repo_id=REPO_ID,
77
+ filename=f"models/buffalo_l/" + f,
78
  repo_type="model",
79
  local_dir=MODELS_DIR,
80
  token=HF_TOKEN
81
  )
82
+
83
+ logger.info("Models downloaded.")
84
  return inswapper_path
85
 
86
+
87
  inswapper_path = download_models()
88
 
89
  # --------------------- Face Analysis + Swapper ---------------------
 
105
 
106
  ensure_codeformer()
107
 
 
 
 
 
 
108
 
109
  # --------------------- FastAPI ---------------------
110
  fastapi_app = FastAPI()
 
254
  source: UploadFile = File(...),
255
  target_category_id: str = Form(None),
256
  new_category_id: str = Form(None),
257
+ user_id: Optional[str] = Form(None)
 
258
  ):
259
  start_time = datetime.utcnow()
260
 
261
  try:
262
+ # ============ VALIDATION ============
263
  if target_category_id and new_category_id:
264
+ raise HTTPException(400, "Provide only one of new_category_id or target_category_id.")
 
 
 
265
 
266
  if not target_category_id and not new_category_id:
267
+ raise HTTPException(400, "Either new_category_id or target_category_id is required.")
 
 
 
268
 
269
+ # Read source image
270
  src_bytes = await source.read()
 
 
271
  src_key = f"faceswap/source/{uuid.uuid4().hex}_{source.filename}"
272
+ upload_to_spaces(src_bytes, src_key)
273
 
274
+ # ============ CASE 1: MongoDB new_category_id ============
 
 
275
  if new_category_id:
276
+ doc = await subcategories_col.find_one({"asset_images._id": ObjectId(new_category_id)})
 
 
 
 
277
 
278
  if not doc:
279
+ raise HTTPException(404, "Asset image not found.")
280
 
281
+ # find correct asset image
282
+ asset = next((img for img in doc["asset_images"] if str(img["_id"]) == new_category_id), None)
 
 
 
 
283
 
284
  if not asset:
285
+ raise HTTPException(404, "Asset image URL not found.")
286
 
287
  target_url = asset["url"]
288
+ category_oid = doc["categoryId"] # REAL ObjectId
289
 
290
+ # ---- MEDIA_CLICKS ONLY IF user_id EXISTS ----
 
291
  if user_id:
292
  try:
293
+ user_id = user_id.strip()
294
+ user_oid = ObjectId(user_id)
 
 
 
 
 
 
 
 
 
 
295
  now = datetime.utcnow()
296
+
297
  update_result = await media_clicks_col.update_one(
298
+ {"userId": user_oid, "categories.categoryId": category_oid},
299
  {
300
+ "$set": {"updatedAt": now, "categories.$.lastClickedAt": now},
301
+ "$inc": {"categories.$.click_count": 1}
 
 
 
 
 
 
 
 
 
302
  }
303
  )
304
+
 
 
 
305
  if update_result.matched_count == 0:
306
+ await media_clicks_col.update_one(
 
 
307
  {"userId": user_oid},
308
  {
309
  "$setOnInsert": {"createdAt": now},
310
  "$set": {"updatedAt": now},
311
  "$push": {
312
  "categories": {
313
+ "categoryId": category_oid,
314
  "click_count": 1,
315
  "lastClickedAt": now
316
  }
 
318
  },
319
  upsert=True
320
  )
321
+
322
+ except Exception as err:
323
+ logger.error(f"MEDIA_CLICK ERROR: {err}")
324
+ # Do NOT break face-swap
325
+
326
+ # ============ CASE 2: DigitalOcean target_category_id ============
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
  if target_category_id:
328
  client = get_spaces_client()
329
  base_prefix = "faceswap/target/"
330
  resp = client.list_objects_v2(Bucket=DO_SPACES_BUCKET, Prefix=base_prefix, Delimiter="/")
 
331
 
332
+ categories = [p["Prefix"].split("/")[2] for p in resp.get("CommonPrefixes", [])]
333
  target_url = None
334
+
335
  for category in categories:
336
  original_prefix = f"faceswap/target/{category}/original/"
337
+ original_files = client.list_objects_v2(Bucket=DO_SPACES_BUCKET, Prefix=original_prefix)
 
 
 
338
 
339
+ for idx, obj in enumerate(sorted(original_files.get("Contents", [])), start=1):
 
 
 
340
  cid = f"{category.lower()}image_{idx}"
341
+ if cid == target_category_id:
342
+ filename = obj["Key"].split("/")[-1]
343
+ target_url = f"{DO_SPACES_ENDPOINT}/{DO_SPACES_BUCKET}/{original_prefix}{filename}"
344
  break
345
+
346
  if target_url:
347
  break
348
 
349
  if not target_url:
350
+ raise HTTPException(404, "Target categoryId not found.")
 
 
 
 
 
 
 
 
 
 
 
 
 
351
 
352
+ # ============ DOWNLOAD TARGET IMAGE ============
353
+ target_bytes = requests.get(target_url).content
354
+ tgt_bgr = cv2.imdecode(np.frombuffer(target_bytes, np.uint8), cv2.IMREAD_COLOR)
355
+ src_bgr = cv2.imdecode(np.frombuffer(src_bytes, np.uint8), cv2.IMREAD_COLOR)
356
 
357
  if src_bgr is None or tgt_bgr is None:
358
+ raise HTTPException(400, "Invalid image data")
359
 
360
+ # ============ FACE SWAP ============
361
  src_rgb = cv2.cvtColor(src_bgr, cv2.COLOR_BGR2RGB)
362
  tgt_rgb = cv2.cvtColor(tgt_bgr, cv2.COLOR_BGR2RGB)
363
 
 
 
 
364
  final_img, final_path, err = face_swap_and_enhance(src_rgb, tgt_rgb)
365
  if err:
366
+ raise HTTPException(500, err)
367
 
368
+ result_bytes = open(final_path, "rb").read()
369
+ result_key = f"faceswap/result/{uuid.uuid4().hex}.png"
370
+ result_url = upload_to_spaces(result_bytes, result_key)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
371
 
372
  return {"result_key": result_key, "result_url": result_url}
373
 
374
  except Exception as e:
375
+ raise HTTPException(500, f"Face swap failed: {str(e)}")
376
+
 
 
 
 
 
 
 
 
 
 
377
 
378
  # @fastapi_app.post("/face-swap", dependencies=[Depends(verify_token)])
379
  # async def face_swap_api(