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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +258 -253
app.py CHANGED
@@ -249,281 +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
- # # 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",
@@ -532,12 +431,17 @@ async def face_swap_api(
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",
@@ -546,7 +450,108 @@ async def face_swap_api(
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
+ # 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