LogicGoInfotechSpaces commited on
Commit
b3620b8
·
verified ·
1 Parent(s): a2164da

Update app.py

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