LogicGoInfotechSpaces commited on
Commit
3f3e311
·
verified ·
1 Parent(s): 1cab07a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +270 -270
app.py CHANGED
@@ -249,309 +249,203 @@ async def test_admin_db():
249
  except Exception as e:
250
  return {"ok": False, "error": str(e), "url": ADMIN_MONGO_URL}
251
 
252
- @fastapi_app.post("/face-swap", dependencies=[Depends(verify_token)])
253
- async def face_swap_api(
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
- credentials: HTTPAuthorizationCredentials = Security(security)
259
- ):
260
- start_time = datetime.utcnow()
261
 
262
- try:
263
 
264
- # --------------------------------------------------------------
265
- # NORMALIZE EMPTY STRINGS (BACKWARD COMPATIBILITY FOR ANDROID)
266
- # --------------------------------------------------------------
267
- if target_category_id == "":
268
- target_category_id = None
269
 
270
- if new_category_id == "":
271
- new_category_id = None
272
 
273
- if user_id == "":
274
- user_id = None
275
 
276
- # --------------------------------------------------------------
277
- # VALIDATION
278
- # --------------------------------------------------------------
279
- if target_category_id and new_category_id:
280
- raise HTTPException(400, "Provide only one of new_category_id or target_category_id.")
281
 
282
- if not target_category_id and not new_category_id:
283
- raise HTTPException(400, "Either new_category_id or target_category_id is required.")
284
 
285
- # ------------------------------------------------------------------
286
- # READ SOURCE IMAGE
287
- # ------------------------------------------------------------------
288
- src_bytes = await source.read()
289
- src_key = f"faceswap/source/{uuid.uuid4().hex}_{source.filename}"
290
- upload_to_spaces(src_bytes, src_key, content_type=source.content_type)
291
 
292
- # ------------------------------------------------------------------
293
- # CASE 1 : new_category_id → MongoDB lookup
294
- # ------------------------------------------------------------------
295
- if new_category_id:
296
 
297
- doc = await subcategories_col.find_one({
298
- "asset_images._id": ObjectId(new_category_id)
299
- })
300
 
301
- if not doc:
302
- raise HTTPException(404, "Asset image not found in database")
303
 
304
- # extract correct asset
305
- asset = next(
306
- (img for img in doc["asset_images"] if str(img["_id"]) == new_category_id),
307
- None
308
- )
309
 
310
- if not asset:
311
- raise HTTPException(404, "Asset image URL not found")
312
 
313
- # correct URL
314
- target_url = asset["url"]
315
 
316
- # correct categoryId (ObjectId)
317
- #category_oid = doc["categoryId"] # <-- DO NOT CONVERT TO STRING
318
- subcategory_oid = doc["_id"]
319
 
320
- # ------------------------------------------------------------------
321
- # MEDIA_CLICKS (ONLY IF user_id PRESENT)
322
- # ------------------------------------------------------------------
323
- if user_id:
324
- try:
325
- user_oid = ObjectId(user_id.strip())
326
- now = datetime.utcnow()
327
 
328
- # ----------------------------------------------------------
329
- # 1. UPDATE EXISTING SUBCATEGORY IF PRESENT
330
- # ----------------------------------------------------------
331
- update_result = await media_clicks_col.update_one(
332
- {
333
- "userId": user_oid,
334
- "subCategories.subCategoryId": subcategory_oid
335
- },
336
- {
337
- "$set": {
338
- "updatedAt": now,
339
- "subCategories.$.lastClickedAt": now
340
- },
341
- "$inc": {
342
- "subCategories.$.click_count": 1
343
- }
344
- }
345
- )
346
 
347
- # ----------------------------------------------------------
348
- # 2. If user exists BUT subCategory NOT found → push subCategory
349
- # ----------------------------------------------------------
350
- if update_result.matched_count == 0:
351
- update_result_user = await media_clicks_col.update_one(
352
- {"userId": user_oid},
353
- {
354
- "$set": {"updatedAt": now},
355
- "$push": {
356
- "subCategories": {
357
- "subCategoryId": subcategory_oid,
358
- "click_count": 1,
359
- "lastClickedAt": now
360
- }
361
- }
362
- }
363
- )
364
 
365
- # ------------------------------------------------------
366
- # 3. If user document does NOT exist → CREATE NEW DOC
367
- # ------------------------------------------------------
368
- if update_result_user.matched_count == 0:
369
- await media_clicks_col.insert_one({
370
- "userId": user_oid,
371
- "createdAt": now,
372
- "updatedAt": now,
373
- "categories": [], # keep empty or retain existing structure
374
- "subCategories": [
375
- {
376
- "subCategoryId": subcategory_oid,
377
- "click_count": 1,
378
- "lastClickedAt": now
379
- }
380
- ]
381
- })
382
 
383
- except Exception as media_err:
384
- logger.error(f"MEDIA_CLICK ERROR: {media_err}")
385
-
386
-
387
- # ------------------------------------------------------------------
388
- # CASE 2 : target_category_id → DigitalOcean path (unchanged logic)
389
- # ------------------------------------------------------------------
390
- if target_category_id:
391
- client = get_spaces_client()
392
- base_prefix = "faceswap/target/"
393
- resp = client.list_objects_v2(
394
- Bucket=DO_SPACES_BUCKET, Prefix=base_prefix, Delimiter="/"
395
- )
396
- categories = [p["Prefix"].split("/")[2] for p in resp.get("CommonPrefixes", [])]
397
-
398
- target_url = None
399
-
400
- for category in categories:
401
- original_prefix = f"faceswap/target/{category}/original/"
402
- original_objs = client.list_objects_v2(
403
- Bucket=DO_SPACES_BUCKET, Prefix=original_prefix
404
- ).get("Contents", [])
405
-
406
- original_files = sorted([obj["Key"] for obj in original_objs])
407
-
408
- for idx, file_key in enumerate(original_files, start=1):
409
- cid = f"{category.lower()}image_{idx}"
410
- if cid == target_category_id:
411
- target_url = f"{DO_SPACES_ENDPOINT}/{DO_SPACES_BUCKET}/{file_key}"
412
- break
413
-
414
- if target_url:
415
- break
416
-
417
- if not target_url:
418
- raise HTTPException(404, "Target categoryId not found")
419
-
420
- # ------------------------------------------------------------------
421
- # DOWNLOAD TARGET IMAGE
422
- # ------------------------------------------------------------------
423
- tgt_bytes = requests.get(target_url).content
424
-
425
- src_bgr = cv2.imdecode(np.frombuffer(src_bytes, np.uint8), cv2.IMREAD_COLOR)
426
- tgt_bgr = cv2.imdecode(np.frombuffer(tgt_bytes, np.uint8), cv2.IMREAD_COLOR)
427
-
428
- if src_bgr is None or tgt_bgr is None:
429
- raise HTTPException(400, "Invalid image data")
430
-
431
- src_rgb = cv2.cvtColor(src_bgr, cv2.COLOR_BGR2RGB)
432
- tgt_rgb = cv2.cvtColor(tgt_bgr, cv2.COLOR_BGR2RGB)
433
-
434
- # ------------------------------------------------------------------
435
- # FACE SWAP EXECUTION
436
- # ------------------------------------------------------------------
437
- final_img, final_path, err = face_swap_and_enhance(src_rgb, tgt_rgb)
438
- if err:
439
- raise HTTPException(500, err)
440
-
441
- with open(final_path, "rb") as f:
442
- result_bytes = f.read()
443
-
444
- result_key = f"faceswap/result/{uuid.uuid4().hex}_enhanced.png"
445
- result_url = upload_to_spaces(result_bytes, result_key,content_type="image/png")
446
-
447
- end_time = datetime.utcnow()
448
- response_time_ms = (end_time - start_time).total_seconds() * 1000
449
- if database is not None:
450
- await database.api_logs.insert_one({
451
- "endpoint": "/face-swap",
452
- "status": "success",
453
- "response_time_ms": response_time_ms,
454
- "timestamp": end_time
455
- })
456
-
457
-
458
- return {
459
- "result_key": result_key,
460
- "result_url": result_url
461
- }
462
-
463
-
464
- except Exception as e:
465
- end_time = datetime.utcnow()
466
- response_time_ms = (end_time - start_time).total_seconds() * 1000
467
-
468
- if database is not None:
469
- await database.api_logs.insert_one({
470
- "endpoint": "/face-swap",
471
- "status": "fail",
472
- "response_time_ms": response_time_ms,
473
- "timestamp": end_time,
474
- "error": str(e)
475
- })
476
-
477
- raise HTTPException(500, f"Face swap failed: {str(e)}")
478
-
479
-
480
 
 
 
481
 
482
- #-------------------------------------------------------------------------------------------------------------------------------#
483
- ####OLD CODE------------------------------------------------------------------------------------
484
- # @fastapi_app.post("/face-swap", dependencies=[Depends(verify_token)])
485
- # async def face_swap_api(
486
- # source: UploadFile = File(...),
487
- # target_category_id: str = Form(...),
488
- # credentials: HTTPAuthorizationCredentials = Security(security)
489
- # ):
490
- # start_time = datetime.utcnow() # start timer
491
- # try:
492
- # # Read source image
493
- # src_bytes = await source.read()
494
 
495
- # # Save source to Spaces
496
- # src_key = f"faceswap/source/{uuid.uuid4().hex}_{source.filename}"
497
- # upload_to_spaces(src_bytes, src_key, content_type=source.content_type)
498
 
499
- # # Find target image URL from categoryId
500
- # client = get_spaces_client()
501
- # base_prefix = "faceswap/target/"
502
- # resp = client.list_objects_v2(Bucket=DO_SPACES_BUCKET, Prefix=base_prefix, Delimiter="/")
503
- # categories = [prefix["Prefix"].split("/")[2] for prefix in resp.get("CommonPrefixes", [])]
504
- # target_url = None
505
- # for category in categories:
506
- # original_prefix = f"faceswap/target/{category}/original/"
507
- # thumb_prefix = f"faceswap/target/{category}/thumb/"
508
- # original_objects = client.list_objects_v2(Bucket=DO_SPACES_BUCKET, Prefix=original_prefix)
509
- # thumb_objects = client.list_objects_v2(Bucket=DO_SPACES_BUCKET, Prefix=thumb_prefix)
510
- # original_files = [obj["Key"].split("/")[-1] for obj in original_objects.get("Contents", []) if obj["Key"].endswith(".png")]
511
- # thumb_files = [obj["Key"].split("/")[-1] for obj in thumb_objects.get("Contents", []) if obj["Key"].endswith(".png")]
512
- # for idx, filename in enumerate(sorted(original_files), start=1):
513
- # cid = f"{category.lower()}image_{idx}"
514
- # if filename in thumb_files and cid == target_category_id:
515
- # target_url = f"https://{DO_SPACES_BUCKET}.blr1.digitaloceanspaces.com/{original_prefix}{filename}"
516
- # break
517
- # if target_url:
518
- # break
519
- # if not target_url:
520
- # raise HTTPException(status_code=404, detail="Target categoryId not found")
521
-
522
- # # Download target image from Spaces
523
- # resp = requests.get(target_url)
524
- # if resp.status_code != 200:
525
- # raise HTTPException(status_code=404, detail="Target image not found in Spaces")
526
- # tgt_bytes = resp.content
527
-
528
- # # Decode for processing
529
- # src_array = np.frombuffer(src_bytes, np.uint8)
530
- # tgt_array = np.frombuffer(tgt_bytes, np.uint8)
531
- # src_bgr = cv2.imdecode(src_array, cv2.IMREAD_COLOR)
532
- # tgt_bgr = cv2.imdecode(tgt_array, cv2.IMREAD_COLOR)
533
  # if src_bgr is None or tgt_bgr is None:
534
- # raise HTTPException(status_code=400, detail="Invalid image data")
535
 
536
  # src_rgb = cv2.cvtColor(src_bgr, cv2.COLOR_BGR2RGB)
537
  # tgt_rgb = cv2.cvtColor(tgt_bgr, cv2.COLOR_BGR2RGB)
538
 
539
- # # Run face swap pipeline
 
 
540
  # final_img, final_path, err = face_swap_and_enhance(src_rgb, tgt_rgb)
541
  # if err:
542
- # raise HTTPException(status_code=500, detail=err)
543
 
544
- # # Upload result to Spaces
545
  # with open(final_path, "rb") as f:
546
  # result_bytes = f.read()
547
- # result_key = f"faceswap/result/{uuid.uuid4().hex}_enhanced.png"
548
- # result_url = upload_to_spaces(result_bytes, result_key, content_type="image/png")
549
 
550
- # # Calculate response time
 
 
551
  # end_time = datetime.utcnow()
552
  # response_time_ms = (end_time - start_time).total_seconds() * 1000
553
-
554
- # # Log response time only
555
  # if database is not None:
556
  # await database.api_logs.insert_one({
557
  # "endpoint": "/face-swap",
@@ -560,12 +454,17 @@ async def face_swap_api(
560
  # "timestamp": end_time
561
  # })
562
 
563
- # return {"result_key": result_key, "result_url": result_url}
 
 
 
 
 
564
 
565
  # except Exception as e:
566
- # # Log response time even on error
567
  # end_time = datetime.utcnow()
568
  # response_time_ms = (end_time - start_time).total_seconds() * 1000
 
569
  # if database is not None:
570
  # await database.api_logs.insert_one({
571
  # "endpoint": "/face-swap",
@@ -574,7 +473,108 @@ async def face_swap_api(
574
  # "timestamp": end_time,
575
  # "error": str(e)
576
  # })
577
- # raise HTTPException(status_code=500, detail=f"Face swap failed: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
578
 
579
 
580
 
 
249
  except Exception as e:
250
  return {"ok": False, "error": str(e), "url": ADMIN_MONGO_URL}
251
 
252
+ # @fastapi_app.post("/face-swap", dependencies=[Depends(verify_token)])
253
+ # async def face_swap_api(
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
+ # credentials: HTTPAuthorizationCredentials = Security(security)
259
+ # ):
260
+ # start_time = datetime.utcnow()
261
 
262
+ # try:
263
 
264
+ # # --------------------------------------------------------------
265
+ # # NORMALIZE EMPTY STRINGS (BACKWARD COMPATIBILITY FOR ANDROID)
266
+ # # --------------------------------------------------------------
267
+ # if target_category_id == "":
268
+ # target_category_id = None
269
 
270
+ # if new_category_id == "":
271
+ # new_category_id = None
272
 
273
+ # if user_id == "":
274
+ # user_id = None
275
 
276
+ # # --------------------------------------------------------------
277
+ # # VALIDATION
278
+ # # --------------------------------------------------------------
279
+ # if target_category_id and new_category_id:
280
+ # raise HTTPException(400, "Provide only one of new_category_id or target_category_id.")
281
 
282
+ # if not target_category_id and not new_category_id:
283
+ # raise HTTPException(400, "Either new_category_id or target_category_id is required.")
284
 
285
+ # # ------------------------------------------------------------------
286
+ # # READ SOURCE IMAGE
287
+ # # ------------------------------------------------------------------
288
+ # src_bytes = await source.read()
289
+ # src_key = f"faceswap/source/{uuid.uuid4().hex}_{source.filename}"
290
+ # upload_to_spaces(src_bytes, src_key, content_type=source.content_type)
291
 
292
+ # # ------------------------------------------------------------------
293
+ # # CASE 1 : new_category_id → MongoDB lookup
294
+ # # ------------------------------------------------------------------
295
+ # if new_category_id:
296
 
297
+ # doc = await subcategories_col.find_one({
298
+ # "asset_images._id": ObjectId(new_category_id)
299
+ # })
300
 
301
+ # if not doc:
302
+ # raise HTTPException(404, "Asset image not found in database")
303
 
304
+ # # extract correct asset
305
+ # asset = next(
306
+ # (img for img in doc["asset_images"] if str(img["_id"]) == new_category_id),
307
+ # None
308
+ # )
309
 
310
+ # if not asset:
311
+ # raise HTTPException(404, "Asset image URL not found")
312
 
313
+ # # correct URL
314
+ # target_url = asset["url"]
315
 
316
+ # # correct categoryId (ObjectId)
317
+ # #category_oid = doc["categoryId"] # <-- DO NOT CONVERT TO STRING
318
+ # subcategory_oid = doc["_id"]
319
 
320
+ # # ------------------------------------------------------------------
321
+ # # MEDIA_CLICKS (ONLY IF user_id PRESENT)
322
+ # # ------------------------------------------------------------------
323
+ # if user_id:
324
+ # try:
325
+ # user_oid = ObjectId(user_id.strip())
326
+ # now = datetime.utcnow()
327
 
328
+ # # ----------------------------------------------------------
329
+ # # 1. UPDATE EXISTING SUBCATEGORY IF PRESENT
330
+ # # ----------------------------------------------------------
331
+ # update_result = await media_clicks_col.update_one(
332
+ # {
333
+ # "userId": user_oid,
334
+ # "subCategories.subCategoryId": subcategory_oid
335
+ # },
336
+ # {
337
+ # "$set": {
338
+ # "updatedAt": now,
339
+ # "subCategories.$.lastClickedAt": now
340
+ # },
341
+ # "$inc": {
342
+ # "subCategories.$.click_count": 1
343
+ # }
344
+ # }
345
+ # )
346
 
347
+ # # ----------------------------------------------------------
348
+ # # 2. If user exists BUT subCategory NOT found → push subCategory
349
+ # # ----------------------------------------------------------
350
+ # if update_result.matched_count == 0:
351
+ # update_result_user = await media_clicks_col.update_one(
352
+ # {"userId": user_oid},
353
+ # {
354
+ # "$set": {"updatedAt": now},
355
+ # "$push": {
356
+ # "subCategories": {
357
+ # "subCategoryId": subcategory_oid,
358
+ # "click_count": 1,
359
+ # "lastClickedAt": now
360
+ # }
361
+ # }
362
+ # }
363
+ # )
364
 
365
+ # # ------------------------------------------------------
366
+ # # 3. If user document does NOT exist → CREATE NEW DOC
367
+ # # ------------------------------------------------------
368
+ # if update_result_user.matched_count == 0:
369
+ # await media_clicks_col.insert_one({
370
+ # "userId": user_oid,
371
+ # "createdAt": now,
372
+ # "updatedAt": now,
373
+ # "categories": [], # keep empty or retain existing structure
374
+ # "subCategories": [
375
+ # {
376
+ # "subCategoryId": subcategory_oid,
377
+ # "click_count": 1,
378
+ # "lastClickedAt": now
379
+ # }
380
+ # ]
381
+ # })
382
 
383
+ # except Exception as media_err:
384
+ # logger.error(f"MEDIA_CLICK ERROR: {media_err}")
385
+
386
+
387
+ # # ------------------------------------------------------------------
388
+ # # CASE 2 : target_category_id → DigitalOcean path (unchanged logic)
389
+ # # ------------------------------------------------------------------
390
+ # if target_category_id:
391
+ # client = get_spaces_client()
392
+ # base_prefix = "faceswap/target/"
393
+ # resp = client.list_objects_v2(
394
+ # Bucket=DO_SPACES_BUCKET, Prefix=base_prefix, Delimiter="/"
395
+ # )
396
+ # categories = [p["Prefix"].split("/")[2] for p in resp.get("CommonPrefixes", [])]
397
+
398
+ # target_url = None
399
+
400
+ # for category in categories:
401
+ # original_prefix = f"faceswap/target/{category}/original/"
402
+ # original_objs = client.list_objects_v2(
403
+ # Bucket=DO_SPACES_BUCKET, Prefix=original_prefix
404
+ # ).get("Contents", [])
405
+
406
+ # original_files = sorted([obj["Key"] for obj in original_objs])
407
+
408
+ # for idx, file_key in enumerate(original_files, start=1):
409
+ # cid = f"{category.lower()}image_{idx}"
410
+ # if cid == target_category_id:
411
+ # target_url = f"{DO_SPACES_ENDPOINT}/{DO_SPACES_BUCKET}/{file_key}"
412
+ # break
413
+
414
+ # if target_url:
415
+ # break
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
 
417
+ # if not target_url:
418
+ # raise HTTPException(404, "Target categoryId not found")
419
 
420
+ # # ------------------------------------------------------------------
421
+ # # DOWNLOAD TARGET IMAGE
422
+ # # ------------------------------------------------------------------
423
+ # tgt_bytes = requests.get(target_url).content
 
 
 
 
 
 
 
 
424
 
425
+ # src_bgr = cv2.imdecode(np.frombuffer(src_bytes, np.uint8), cv2.IMREAD_COLOR)
426
+ # tgt_bgr = cv2.imdecode(np.frombuffer(tgt_bytes, np.uint8), cv2.IMREAD_COLOR)
 
427
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
428
  # if src_bgr is None or tgt_bgr is None:
429
+ # raise HTTPException(400, "Invalid image data")
430
 
431
  # src_rgb = cv2.cvtColor(src_bgr, cv2.COLOR_BGR2RGB)
432
  # tgt_rgb = cv2.cvtColor(tgt_bgr, cv2.COLOR_BGR2RGB)
433
 
434
+ # # ------------------------------------------------------------------
435
+ # # FACE SWAP EXECUTION
436
+ # # ------------------------------------------------------------------
437
  # final_img, final_path, err = face_swap_and_enhance(src_rgb, tgt_rgb)
438
  # if err:
439
+ # raise HTTPException(500, err)
440
 
 
441
  # with open(final_path, "rb") as f:
442
  # result_bytes = f.read()
 
 
443
 
444
+ # result_key = f"faceswap/result/{uuid.uuid4().hex}_enhanced.png"
445
+ # result_url = upload_to_spaces(result_bytes, result_key,content_type="image/png")
446
+
447
  # end_time = datetime.utcnow()
448
  # response_time_ms = (end_time - start_time).total_seconds() * 1000
 
 
449
  # if database is not None:
450
  # await database.api_logs.insert_one({
451
  # "endpoint": "/face-swap",
 
454
  # "timestamp": end_time
455
  # })
456
 
457
+
458
+ # return {
459
+ # "result_key": result_key,
460
+ # "result_url": result_url
461
+ # }
462
+
463
 
464
  # except Exception as e:
 
465
  # end_time = datetime.utcnow()
466
  # response_time_ms = (end_time - start_time).total_seconds() * 1000
467
+
468
  # if database is not None:
469
  # await database.api_logs.insert_one({
470
  # "endpoint": "/face-swap",
 
473
  # "timestamp": end_time,
474
  # "error": str(e)
475
  # })
476
+
477
+ # raise HTTPException(500, f"Face swap failed: {str(e)}")
478
+
479
+
480
+
481
+
482
+ #-------------------------------------------------------------------------------------------------------------------------------#
483
+ ####OLD CODE------------------------------------------------------------------------------------
484
+ @fastapi_app.post("/face-swap", dependencies=[Depends(verify_token)])
485
+ async def face_swap_api(
486
+ source: UploadFile = File(...),
487
+ target_category_id: str = Form(...),
488
+ credentials: HTTPAuthorizationCredentials = Security(security)
489
+ ):
490
+ start_time = datetime.utcnow() # start timer
491
+ try:
492
+ # Read source image
493
+ src_bytes = await source.read()
494
+
495
+ # Save source to Spaces
496
+ src_key = f"faceswap/source/{uuid.uuid4().hex}_{source.filename}"
497
+ upload_to_spaces(src_bytes, src_key, content_type=source.content_type)
498
+
499
+ # Find target image URL from categoryId
500
+ client = get_spaces_client()
501
+ base_prefix = "faceswap/target/"
502
+ resp = client.list_objects_v2(Bucket=DO_SPACES_BUCKET, Prefix=base_prefix, Delimiter="/")
503
+ categories = [prefix["Prefix"].split("/")[2] for prefix in resp.get("CommonPrefixes", [])]
504
+ target_url = None
505
+ for category in categories:
506
+ original_prefix = f"faceswap/target/{category}/original/"
507
+ thumb_prefix = f"faceswap/target/{category}/thumb/"
508
+ original_objects = client.list_objects_v2(Bucket=DO_SPACES_BUCKET, Prefix=original_prefix)
509
+ thumb_objects = client.list_objects_v2(Bucket=DO_SPACES_BUCKET, Prefix=thumb_prefix)
510
+ original_files = [obj["Key"].split("/")[-1] for obj in original_objects.get("Contents", []) if obj["Key"].endswith(".png")]
511
+ thumb_files = [obj["Key"].split("/")[-1] for obj in thumb_objects.get("Contents", []) if obj["Key"].endswith(".png")]
512
+ for idx, filename in enumerate(sorted(original_files), start=1):
513
+ cid = f"{category.lower()}image_{idx}"
514
+ if filename in thumb_files and cid == target_category_id:
515
+ target_url = f"https://{DO_SPACES_BUCKET}.blr1.digitaloceanspaces.com/{original_prefix}{filename}"
516
+ break
517
+ if target_url:
518
+ break
519
+ if not target_url:
520
+ raise HTTPException(status_code=404, detail="Target categoryId not found")
521
+
522
+ # Download target image from Spaces
523
+ resp = requests.get(target_url)
524
+ if resp.status_code != 200:
525
+ raise HTTPException(status_code=404, detail="Target image not found in Spaces")
526
+ tgt_bytes = resp.content
527
+
528
+ # Decode for processing
529
+ src_array = np.frombuffer(src_bytes, np.uint8)
530
+ tgt_array = np.frombuffer(tgt_bytes, np.uint8)
531
+ src_bgr = cv2.imdecode(src_array, cv2.IMREAD_COLOR)
532
+ tgt_bgr = cv2.imdecode(tgt_array, cv2.IMREAD_COLOR)
533
+ if src_bgr is None or tgt_bgr is None:
534
+ raise HTTPException(status_code=400, detail="Invalid image data")
535
+
536
+ src_rgb = cv2.cvtColor(src_bgr, cv2.COLOR_BGR2RGB)
537
+ tgt_rgb = cv2.cvtColor(tgt_bgr, cv2.COLOR_BGR2RGB)
538
+
539
+ # Run face swap pipeline
540
+ final_img, final_path, err = face_swap_and_enhance(src_rgb, tgt_rgb)
541
+ if err:
542
+ raise HTTPException(status_code=500, detail=err)
543
+
544
+ # Upload result to Spaces
545
+ with open(final_path, "rb") as f:
546
+ result_bytes = f.read()
547
+ result_key = f"faceswap/result/{uuid.uuid4().hex}_enhanced.png"
548
+ result_url = upload_to_spaces(result_bytes, result_key, content_type="image/png")
549
+
550
+ # Calculate response time
551
+ end_time = datetime.utcnow()
552
+ response_time_ms = (end_time - start_time).total_seconds() * 1000
553
+
554
+ # Log response time only
555
+ if database is not None:
556
+ await database.api_logs.insert_one({
557
+ "endpoint": "/face-swap",
558
+ "status": "success",
559
+ "response_time_ms": response_time_ms,
560
+ "timestamp": end_time
561
+ })
562
+
563
+ return {"result_key": result_key, "result_url": result_url}
564
+
565
+ except Exception as e:
566
+ # Log response time even on error
567
+ end_time = datetime.utcnow()
568
+ response_time_ms = (end_time - start_time).total_seconds() * 1000
569
+ if database is not None:
570
+ await database.api_logs.insert_one({
571
+ "endpoint": "/face-swap",
572
+ "status": "fail",
573
+ "response_time_ms": response_time_ms,
574
+ "timestamp": end_time,
575
+ "error": str(e)
576
+ })
577
+ raise HTTPException(status_code=500, detail=f"Face swap failed: {str(e)}")
578
 
579
 
580