LogicGoInfotechSpaces commited on
Commit
31b7c7a
·
verified ·
1 Parent(s): f4e2d1c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +231 -87
app.py CHANGED
@@ -50,7 +50,11 @@ DO_SPACES_ENDPOINT = os.getenv("DO_SPACES_ENDPOINT", f"https://{DO_SPACES_REGION
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
-
 
 
 
 
54
  # --------------------- Download Models ---------------------
55
  def download_models():
56
  logger.info("Downloading models from private HF repo...")
@@ -239,134 +243,179 @@ def root():
239
  @fastapi_app.get("/health")
240
  async def health():
241
  return {"status": "healthy"}
242
- # @fastapi_app.get("/health")
243
- # async def health():
244
- # mongo_ok = False
245
- # spaces_ok = False
246
- # mongo_error = None
247
- # spaces_error = None
248
-
249
- # # ---- MongoDB Check (ASYNC SAFE) ----
250
- # try:
251
- # if database is None:
252
- # raise Exception("MongoDB client not initialized")
253
-
254
- # await database.command("ping")
255
- # mongo_ok = True
256
-
257
- # except Exception as e:
258
- # mongo_error = str(e)
259
-
260
- # # ---- DigitalOcean Spaces Check (RUN IN THREADPOOL) ----
261
- # try:
262
- # def check_spaces():
263
- # client = get_spaces_client()
264
- # client.list_objects_v2(
265
- # Bucket=DO_SPACES_BUCKET,
266
- # MaxKeys=1
267
- # )
268
-
269
- # await run_in_threadpool(check_spaces)
270
- # spaces_ok = True
271
-
272
- # except Exception as e:
273
- # spaces_error = str(e)
274
-
275
- # # ---- FINAL RESPONSE ----
276
- # if mongo_ok and spaces_ok:
277
- # return {
278
- # "status": "healthy",
279
- # "mongo": "connected",
280
- # "spaces": "connected",
281
- # "timestamp": datetime.utcnow().isoformat()
282
- # }
283
-
284
- # raise HTTPException(
285
- # status_code=503,
286
- # detail={
287
- # "status": "unhealthy",
288
- # "mongo": "connected" if mongo_ok else "failed",
289
- # "spaces": "connected" if spaces_ok else "failed",
290
- # "mongo_error": mongo_error,
291
- # "spaces_error": spaces_error,
292
- # "timestamp": datetime.utcnow().isoformat()
293
- # }
294
- # )
295
 
296
  from fastapi import Form
297
  import requests
298
-
299
  @fastapi_app.post("/face-swap", dependencies=[Depends(verify_token)])
300
  async def face_swap_api(
301
  source: UploadFile = File(...),
302
- target_category_id: str = Form(...),
 
 
303
  credentials: HTTPAuthorizationCredentials = Security(security)
304
  ):
305
- start_time = datetime.utcnow() # start timer
 
306
  try:
307
- # Read source image
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  src_bytes = await source.read()
309
 
310
  # Save source to Spaces
311
  src_key = f"faceswap/source/{uuid.uuid4().hex}_{source.filename}"
312
  upload_to_spaces(src_bytes, src_key, content_type=source.content_type)
313
 
314
- # Find target image URL from categoryId
315
- client = get_spaces_client()
316
- base_prefix = "faceswap/target/"
317
- resp = client.list_objects_v2(Bucket=DO_SPACES_BUCKET, Prefix=base_prefix, Delimiter="/")
318
- categories = [prefix["Prefix"].split("/")[2] for prefix in resp.get("CommonPrefixes", [])]
319
- target_url = None
320
- for category in categories:
321
- original_prefix = f"faceswap/target/{category}/original/"
322
- thumb_prefix = f"faceswap/target/{category}/thumb/"
323
- original_objects = client.list_objects_v2(Bucket=DO_SPACES_BUCKET, Prefix=original_prefix)
324
- thumb_objects = client.list_objects_v2(Bucket=DO_SPACES_BUCKET, Prefix=thumb_prefix)
325
- original_files = [obj["Key"].split("/")[-1] for obj in original_objects.get("Contents", []) if obj["Key"].endswith(".png")]
326
- thumb_files = [obj["Key"].split("/")[-1] for obj in thumb_objects.get("Contents", []) if obj["Key"].endswith(".png")]
327
- for idx, filename in enumerate(sorted(original_files), start=1):
328
- cid = f"{category.lower()}image_{idx}"
329
- if filename in thumb_files and cid == target_category_id:
330
- target_url = f"https://{DO_SPACES_BUCKET}.blr1.digitaloceanspaces.com/{original_prefix}{filename}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
  break
332
- if target_url:
333
- break
334
- if not target_url:
335
- raise HTTPException(status_code=404, detail="Target categoryId not found")
336
 
337
- # Download target image from Spaces
 
 
 
 
 
338
  resp = requests.get(target_url)
339
  if resp.status_code != 200:
340
- raise HTTPException(status_code=404, detail="Target image not found in Spaces")
 
341
  tgt_bytes = resp.content
342
 
343
- # Decode for processing
344
  src_array = np.frombuffer(src_bytes, np.uint8)
345
  tgt_array = np.frombuffer(tgt_bytes, np.uint8)
 
346
  src_bgr = cv2.imdecode(src_array, cv2.IMREAD_COLOR)
347
  tgt_bgr = cv2.imdecode(tgt_array, cv2.IMREAD_COLOR)
 
348
  if src_bgr is None or tgt_bgr is None:
349
  raise HTTPException(status_code=400, detail="Invalid image data")
350
 
351
  src_rgb = cv2.cvtColor(src_bgr, cv2.COLOR_BGR2RGB)
352
  tgt_rgb = cv2.cvtColor(tgt_bgr, cv2.COLOR_BGR2RGB)
353
 
354
- # Run face swap pipeline
 
 
355
  final_img, final_path, err = face_swap_and_enhance(src_rgb, tgt_rgb)
356
  if err:
357
  raise HTTPException(status_code=500, detail=err)
358
 
359
- # Upload result to Spaces
360
  with open(final_path, "rb") as f:
361
  result_bytes = f.read()
 
362
  result_key = f"faceswap/result/{uuid.uuid4().hex}_enhanced.png"
363
  result_url = upload_to_spaces(result_bytes, result_key, content_type="image/png")
364
 
365
- # Calculate response time
366
  end_time = datetime.utcnow()
367
  response_time_ms = (end_time - start_time).total_seconds() * 1000
368
 
369
- # Log response time only
370
  if database is not None:
371
  await database.api_logs.insert_one({
372
  "endpoint": "/face-swap",
@@ -378,7 +427,7 @@ async def face_swap_api(
378
  return {"result_key": result_key, "result_url": result_url}
379
 
380
  except Exception as e:
381
- # Log response time even on error
382
  end_time = datetime.utcnow()
383
  response_time_ms = (end_time - start_time).total_seconds() * 1000
384
  if database is not None:
@@ -391,6 +440,101 @@ async def face_swap_api(
391
  })
392
  raise HTTPException(status_code=500, detail=f"Face swap failed: {str(e)}")
393
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
394
 
395
 
396
  @fastapi_app.get("/preview/{result_key:path}")
 
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...")
 
243
  @fastapi_app.get("/health")
244
  async def health():
245
  return {"status": "healthy"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
 
247
  from fastapi import Form
248
  import requests
 
249
  @fastapi_app.post("/face-swap", dependencies=[Depends(verify_token)])
250
  async def face_swap_api(
251
  source: UploadFile = File(...),
252
+ target_category_id: str = Form(None),
253
+ new_category_id: str = Form(None),
254
+ user_id: Optional[str] = Form(None),
255
  credentials: HTTPAuthorizationCredentials = Security(security)
256
  ):
257
+ start_time = datetime.utcnow()
258
+
259
  try:
260
+ # ---------- VALIDATION ----------
261
+ if target_category_id and new_category_id:
262
+ raise HTTPException(
263
+ status_code=400,
264
+ detail="Provide only one of new_category_id or target_category_id, not both."
265
+ )
266
+
267
+ if not target_category_id and not new_category_id:
268
+ raise HTTPException(
269
+ status_code=400,
270
+ detail="Either new_category_id or target_category_id is required."
271
+ )
272
+
273
+ # ---------- READ SOURCE ----------
274
  src_bytes = await source.read()
275
 
276
  # Save source to Spaces
277
  src_key = f"faceswap/source/{uuid.uuid4().hex}_{source.filename}"
278
  upload_to_spaces(src_bytes, src_key, content_type=source.content_type)
279
 
280
+ # ==========================================================
281
+ # CASE 1: NEW_CATEGORY_ID → Fetch from MongoDB
282
+ # ==========================================================
283
+ if new_category_id:
284
+ from bson import ObjectId
285
+
286
+ doc = await subcategories_col.find_one({
287
+ "asset_images._id": ObjectId(new_category_id)
288
+ })
289
+
290
+ if not doc:
291
+ raise HTTPException(status_code=404, detail="Asset image not found in database")
292
+
293
+ # Find the asset image inside array
294
+ asset = None
295
+ for img in doc["asset_images"]:
296
+ if str(img["_id"]) == str(new_category_id):
297
+ asset = img
298
+ break
299
+
300
+ if not asset:
301
+ raise HTTPException(status_code=404, detail="Asset image URL not found")
302
+
303
+ target_url = asset["url"]
304
+ category_id = str(doc["categoryId"])
305
+
306
+ # ---------- media_clicks update only if user_id exists ----------
307
+ if user_id:
308
+ user_oid = ObjectId(user_id)
309
+ cat_oid = ObjectId(category_id)
310
+
311
+ now = datetime.utcnow()
312
+
313
+ # Try to update existing category entry
314
+ update_result = await media_clicks_col.update_one(
315
+ {
316
+ "userId": user_oid,
317
+ "categories.categoryId": cat_oid
318
+ },
319
+ {
320
+ "$set": {
321
+ "updatedAt": now,
322
+ "categories.$.lastClickedAt": now
323
+ },
324
+ "$inc": {
325
+ "categories.$.click_count": 1
326
+ }
327
+ }
328
+ )
329
+
330
+ # If category did not exist, push new category object
331
+ if update_result.matched_count == 0:
332
+ await media_clicks_col.update_one(
333
+ {"userId": user_oid},
334
+ {
335
+ "$setOnInsert": {"createdAt": now},
336
+ "$set": {"updatedAt": now},
337
+ "$push": {
338
+ "categories": {
339
+ "categoryId": cat_oid,
340
+ "click_count": 1,
341
+ "lastClickedAt": now
342
+ }
343
+ }
344
+ },
345
+ upsert=True
346
+ )
347
+
348
+ # ==========================================================
349
+ # CASE 2: EXISTING TARGET_CATEGORY_ID (DigitalOcean)
350
+ # ==========================================================
351
+ if target_category_id:
352
+ client = get_spaces_client()
353
+ base_prefix = "faceswap/target/"
354
+ resp = client.list_objects_v2(Bucket=DO_SPACES_BUCKET, Prefix=base_prefix, Delimiter="/")
355
+ categories = [prefix["Prefix"].split("/")[2] for prefix in resp.get("CommonPrefixes", [])]
356
+
357
+ target_url = None
358
+ for category in categories:
359
+ original_prefix = f"faceswap/target/{category}/original/"
360
+ thumb_prefix = f"faceswap/target/{category}/thumb/"
361
+
362
+ original_objects = client.list_objects_v2(Bucket=DO_SPACES_BUCKET, Prefix=original_prefix)
363
+ thumb_objects = client.list_objects_v2(Bucket=DO_SPACES_BUCKET, Prefix=thumb_prefix)
364
+
365
+ original_files = [obj["Key"].split("/")[-1] for obj in original_objects.get("Contents", []) if obj["Key"].endswith(".png")]
366
+ thumb_files = [obj["Key"].split("/")[-1] for obj in thumb_objects.get("Contents", []) if obj["Key"].endswith(".png")]
367
+
368
+ for idx, filename in enumerate(sorted(original_files), start=1):
369
+ cid = f"{category.lower()}image_{idx}"
370
+ if filename in thumb_files and cid == target_category_id:
371
+ target_url = f"https://{DO_SPACES_BUCKET}.blr1.digitaloceanspaces.com/{original_prefix}{filename}"
372
+ break
373
+ if target_url:
374
  break
 
 
 
 
375
 
376
+ if not target_url:
377
+ raise HTTPException(status_code=404, detail="Target categoryId not found")
378
+
379
+ # ==========================================================
380
+ # DOWNLOAD TARGET IMAGE
381
+ # ==========================================================
382
  resp = requests.get(target_url)
383
  if resp.status_code != 200:
384
+ raise HTTPException(status_code=404, detail="Target image could not be fetched")
385
+
386
  tgt_bytes = resp.content
387
 
388
+ # Decode images
389
  src_array = np.frombuffer(src_bytes, np.uint8)
390
  tgt_array = np.frombuffer(tgt_bytes, np.uint8)
391
+
392
  src_bgr = cv2.imdecode(src_array, cv2.IMREAD_COLOR)
393
  tgt_bgr = cv2.imdecode(tgt_array, cv2.IMREAD_COLOR)
394
+
395
  if src_bgr is None or tgt_bgr is None:
396
  raise HTTPException(status_code=400, detail="Invalid image data")
397
 
398
  src_rgb = cv2.cvtColor(src_bgr, cv2.COLOR_BGR2RGB)
399
  tgt_rgb = cv2.cvtColor(tgt_bgr, cv2.COLOR_BGR2RGB)
400
 
401
+ # ==========================================================
402
+ # FACE SWAP
403
+ # ==========================================================
404
  final_img, final_path, err = face_swap_and_enhance(src_rgb, tgt_rgb)
405
  if err:
406
  raise HTTPException(status_code=500, detail=err)
407
 
408
+ # Upload output to Spaces
409
  with open(final_path, "rb") as f:
410
  result_bytes = f.read()
411
+
412
  result_key = f"faceswap/result/{uuid.uuid4().hex}_enhanced.png"
413
  result_url = upload_to_spaces(result_bytes, result_key, content_type="image/png")
414
 
415
+ # Log success
416
  end_time = datetime.utcnow()
417
  response_time_ms = (end_time - start_time).total_seconds() * 1000
418
 
 
419
  if database is not None:
420
  await database.api_logs.insert_one({
421
  "endpoint": "/face-swap",
 
427
  return {"result_key": result_key, "result_url": result_url}
428
 
429
  except Exception as e:
430
+ # Log failure
431
  end_time = datetime.utcnow()
432
  response_time_ms = (end_time - start_time).total_seconds() * 1000
433
  if database is not None:
 
440
  })
441
  raise HTTPException(status_code=500, detail=f"Face swap failed: {str(e)}")
442
 
443
+ # @fastapi_app.post("/face-swap", dependencies=[Depends(verify_token)])
444
+ # async def face_swap_api(
445
+ # source: UploadFile = File(...),
446
+ # target_category_id: str = Form(...),
447
+ # credentials: HTTPAuthorizationCredentials = Security(security)
448
+ # ):
449
+ # start_time = datetime.utcnow() # start timer
450
+ # try:
451
+ # # Read source image
452
+ # src_bytes = await source.read()
453
+
454
+ # # Save source to Spaces
455
+ # src_key = f"faceswap/source/{uuid.uuid4().hex}_{source.filename}"
456
+ # upload_to_spaces(src_bytes, src_key, content_type=source.content_type)
457
+
458
+ # # Find target image URL from categoryId
459
+ # client = get_spaces_client()
460
+ # base_prefix = "faceswap/target/"
461
+ # resp = client.list_objects_v2(Bucket=DO_SPACES_BUCKET, Prefix=base_prefix, Delimiter="/")
462
+ # categories = [prefix["Prefix"].split("/")[2] for prefix in resp.get("CommonPrefixes", [])]
463
+ # target_url = None
464
+ # for category in categories:
465
+ # original_prefix = f"faceswap/target/{category}/original/"
466
+ # thumb_prefix = f"faceswap/target/{category}/thumb/"
467
+ # original_objects = client.list_objects_v2(Bucket=DO_SPACES_BUCKET, Prefix=original_prefix)
468
+ # thumb_objects = client.list_objects_v2(Bucket=DO_SPACES_BUCKET, Prefix=thumb_prefix)
469
+ # original_files = [obj["Key"].split("/")[-1] for obj in original_objects.get("Contents", []) if obj["Key"].endswith(".png")]
470
+ # thumb_files = [obj["Key"].split("/")[-1] for obj in thumb_objects.get("Contents", []) if obj["Key"].endswith(".png")]
471
+ # for idx, filename in enumerate(sorted(original_files), start=1):
472
+ # cid = f"{category.lower()}image_{idx}"
473
+ # if filename in thumb_files and cid == target_category_id:
474
+ # target_url = f"https://{DO_SPACES_BUCKET}.blr1.digitaloceanspaces.com/{original_prefix}{filename}"
475
+ # break
476
+ # if target_url:
477
+ # break
478
+ # if not target_url:
479
+ # raise HTTPException(status_code=404, detail="Target categoryId not found")
480
+
481
+ # # Download target image from Spaces
482
+ # resp = requests.get(target_url)
483
+ # if resp.status_code != 200:
484
+ # raise HTTPException(status_code=404, detail="Target image not found in Spaces")
485
+ # tgt_bytes = resp.content
486
+
487
+ # # Decode for processing
488
+ # src_array = np.frombuffer(src_bytes, np.uint8)
489
+ # tgt_array = np.frombuffer(tgt_bytes, np.uint8)
490
+ # src_bgr = cv2.imdecode(src_array, cv2.IMREAD_COLOR)
491
+ # tgt_bgr = cv2.imdecode(tgt_array, cv2.IMREAD_COLOR)
492
+ # if src_bgr is None or tgt_bgr is None:
493
+ # raise HTTPException(status_code=400, detail="Invalid image data")
494
+
495
+ # src_rgb = cv2.cvtColor(src_bgr, cv2.COLOR_BGR2RGB)
496
+ # tgt_rgb = cv2.cvtColor(tgt_bgr, cv2.COLOR_BGR2RGB)
497
+
498
+ # # Run face swap pipeline
499
+ # final_img, final_path, err = face_swap_and_enhance(src_rgb, tgt_rgb)
500
+ # if err:
501
+ # raise HTTPException(status_code=500, detail=err)
502
+
503
+ # # Upload result to Spaces
504
+ # with open(final_path, "rb") as f:
505
+ # result_bytes = f.read()
506
+ # result_key = f"faceswap/result/{uuid.uuid4().hex}_enhanced.png"
507
+ # result_url = upload_to_spaces(result_bytes, result_key, content_type="image/png")
508
+
509
+ # # Calculate response time
510
+ # end_time = datetime.utcnow()
511
+ # response_time_ms = (end_time - start_time).total_seconds() * 1000
512
+
513
+ # # Log response time only
514
+ # if database is not None:
515
+ # await database.api_logs.insert_one({
516
+ # "endpoint": "/face-swap",
517
+ # "status": "success",
518
+ # "response_time_ms": response_time_ms,
519
+ # "timestamp": end_time
520
+ # })
521
+
522
+ # return {"result_key": result_key, "result_url": result_url}
523
+
524
+ # except Exception as e:
525
+ # # Log response time even on error
526
+ # end_time = datetime.utcnow()
527
+ # response_time_ms = (end_time - start_time).total_seconds() * 1000
528
+ # if database is not None:
529
+ # await database.api_logs.insert_one({
530
+ # "endpoint": "/face-swap",
531
+ # "status": "fail",
532
+ # "response_time_ms": response_time_ms,
533
+ # "timestamp": end_time,
534
+ # "error": str(e)
535
+ # })
536
+ # raise HTTPException(status_code=500, detail=f"Face swap failed: {str(e)}")
537
+
538
 
539
 
540
  @fastapi_app.get("/preview/{result_key:path}")