LogicGoInfotechSpaces commited on
Commit
98a6c5d
·
verified ·
1 Parent(s): b2062f5

Update app.py

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