LogicGoInfotechSpaces commited on
Commit
d2df5a7
·
verified ·
1 Parent(s): 3e726a0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +612 -7
app.py CHANGED
@@ -8,6 +8,8 @@ import numpy as np
8
  import threading
9
  import subprocess
10
  import logging
 
 
11
  from datetime import datetime,timedelta
12
 
13
  import insightface
@@ -18,7 +20,8 @@ from fastapi.responses import RedirectResponse
18
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
19
  from motor.motor_asyncio import AsyncIOMotorClient
20
  from bson import ObjectId
21
- import requests
 
22
  import uvicorn
23
  import gradio as gr
24
  from gradio import mount_gradio_app
@@ -146,10 +149,12 @@ async def log_faceswap_hit(token: str, status: str = "success"):
146
  # --------------------- Face Swap Pipeline ---------------------
147
  swap_lock = threading.Lock()
148
 
149
- def face_swap_and_enhance(src_img, tgt_img, temp_dir="/tmp/faceswap_work"):
150
  try:
151
  with swap_lock:
152
  # Use a temp dir for intermediate files
 
 
153
  if os.path.exists(temp_dir):
154
  shutil.rmtree(temp_dir)
155
  os.makedirs(temp_dir, exist_ok=True)
@@ -169,7 +174,8 @@ def face_swap_and_enhance(src_img, tgt_img, temp_dir="/tmp/faceswap_work"):
169
 
170
  cv2.imwrite(swapped_path, swapped_bgr)
171
 
172
- cmd = f"python {CODEFORMER_PATH} -w 0.7 --input_path {swapped_path} --output_path {temp_dir} --bg_upsampler realesrgan --face_upsample"
 
173
  result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
174
  if result.returncode != 0:
175
  return None, None, f"❌ CodeFormer failed:\n{result.stderr}"
@@ -180,7 +186,10 @@ def face_swap_and_enhance(src_img, tgt_img, temp_dir="/tmp/faceswap_work"):
180
  return None, None, "❌ No enhanced image found"
181
 
182
  final_path = os.path.join(final_results_dir, final_files[0])
183
- final_img = cv2.cvtColor(cv2.imread(final_path), cv2.COLOR_BGR2RGB)
 
 
 
184
 
185
  return final_img, final_path, ""
186
 
@@ -322,12 +331,19 @@ async def face_swap_api(
322
  # ------------------------------------------------------------------#
323
  if user_id:
324
  try:
325
- user_oid = ObjectId(user_id.strip())
 
 
 
 
 
 
 
 
326
  now = datetime.utcnow()
327
 
328
  # Normalize dates (UTC midnight)
329
  today_date = datetime(now.year, now.month, now.day)
330
- yesterday_date = today_date - timedelta(days=1)
331
 
332
  # -------------------------------------------------
333
  # STEP 1: Ensure root document exists
@@ -511,7 +527,10 @@ async def face_swap_api(
511
  # # ------------------------------------------------------------------
512
  # # DOWNLOAD TARGET IMAGE
513
  # # ------------------------------------------------------------------
514
- tgt_bytes = requests.get(target_url).content
 
 
 
515
 
516
  src_bgr = cv2.imdecode(np.frombuffer(src_bytes, np.uint8), cv2.IMREAD_COLOR)
517
  tgt_bgr = cv2.imdecode(np.frombuffer(tgt_bytes, np.uint8), cv2.IMREAD_COLOR)
@@ -582,3 +601,589 @@ fastapi_app = mount_gradio_app(fastapi_app, demo, path="/gradio")
582
 
583
  if __name__ == "__main__":
584
  uvicorn.run(fastapi_app, host="0.0.0.0", port=7860)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  import threading
9
  import subprocess
10
  import logging
11
+ import tempfile
12
+ import sys
13
  from datetime import datetime,timedelta
14
 
15
  import insightface
 
20
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
21
  from motor.motor_asyncio import AsyncIOMotorClient
22
  from bson import ObjectId
23
+ from bson.errors import InvalidId
24
+ import httpx
25
  import uvicorn
26
  import gradio as gr
27
  from gradio import mount_gradio_app
 
149
  # --------------------- Face Swap Pipeline ---------------------
150
  swap_lock = threading.Lock()
151
 
152
+ def face_swap_and_enhance(src_img, tgt_img, temp_dir=None):
153
  try:
154
  with swap_lock:
155
  # Use a temp dir for intermediate files
156
+ if temp_dir is None:
157
+ temp_dir = os.path.join(tempfile.gettempdir(), f"faceswap_work_{uuid.uuid4().hex[:8]}")
158
  if os.path.exists(temp_dir):
159
  shutil.rmtree(temp_dir)
160
  os.makedirs(temp_dir, exist_ok=True)
 
174
 
175
  cv2.imwrite(swapped_path, swapped_bgr)
176
 
177
+ python_cmd = sys.executable if sys.executable else "python3"
178
+ cmd = f"{python_cmd} {CODEFORMER_PATH} -w 0.7 --input_path {swapped_path} --output_path {temp_dir} --bg_upsampler realesrgan --face_upsample"
179
  result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
180
  if result.returncode != 0:
181
  return None, None, f"❌ CodeFormer failed:\n{result.stderr}"
 
186
  return None, None, "❌ No enhanced image found"
187
 
188
  final_path = os.path.join(final_results_dir, final_files[0])
189
+ final_img_bgr = cv2.imread(final_path)
190
+ if final_img_bgr is None:
191
+ return None, None, "❌ Failed to read enhanced image file"
192
+ final_img = cv2.cvtColor(final_img_bgr, cv2.COLOR_BGR2RGB)
193
 
194
  return final_img, final_path, ""
195
 
 
331
  # ------------------------------------------------------------------#
332
  if user_id:
333
  try:
334
+ user_id_clean = user_id.strip()
335
+ if not user_id_clean:
336
+ raise ValueError("user_id cannot be empty")
337
+ try:
338
+ user_oid = ObjectId(user_id_clean)
339
+ except (InvalidId, ValueError) as e:
340
+ logger.error(f"Invalid user_id format: {user_id_clean}")
341
+ raise ValueError(f"Invalid user_id format: {user_id_clean}")
342
+
343
  now = datetime.utcnow()
344
 
345
  # Normalize dates (UTC midnight)
346
  today_date = datetime(now.year, now.month, now.day)
 
347
 
348
  # -------------------------------------------------
349
  # STEP 1: Ensure root document exists
 
527
  # # ------------------------------------------------------------------
528
  # # DOWNLOAD TARGET IMAGE
529
  # # ------------------------------------------------------------------
530
+ async with httpx.AsyncClient(timeout=30.0) as client:
531
+ response = await client.get(target_url)
532
+ response.raise_for_status()
533
+ tgt_bytes = response.content
534
 
535
  src_bgr = cv2.imdecode(np.frombuffer(src_bytes, np.uint8), cv2.IMREAD_COLOR)
536
  tgt_bgr = cv2.imdecode(np.frombuffer(tgt_bytes, np.uint8), cv2.IMREAD_COLOR)
 
601
 
602
  if __name__ == "__main__":
603
  uvicorn.run(fastapi_app, host="0.0.0.0", port=7860)
604
+
605
+
606
+ # # --------------------- List Images Endpoint ---------------------
607
+ # import os
608
+ # os.environ["OMP_NUM_THREADS"] = "1"
609
+ # import shutil
610
+ # import uuid
611
+ # import cv2
612
+ # import numpy as np
613
+ # import threading
614
+ # import subprocess
615
+ # import logging
616
+ # from datetime import datetime,timedelta
617
+
618
+ # import insightface
619
+ # from insightface.app import FaceAnalysis
620
+ # from huggingface_hub import hf_hub_download
621
+ # from fastapi import FastAPI, UploadFile, File, HTTPException, Response, Depends, Security, Form
622
+ # from fastapi.responses import RedirectResponse
623
+ # from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
624
+ # from motor.motor_asyncio import AsyncIOMotorClient
625
+ # from bson import ObjectId
626
+ # import requests
627
+ # import uvicorn
628
+ # import gradio as gr
629
+ # from gradio import mount_gradio_app
630
+
631
+ # # DigitalOcean Spaces
632
+ # import boto3
633
+ # from botocore.client import Config
634
+ # from typing import Optional
635
+
636
+ # # --------------------- Logging ---------------------
637
+ # logging.basicConfig(level=logging.INFO)
638
+ # logger = logging.getLogger(__name__)
639
+
640
+ # # --------------------- Secrets & Paths ---------------------
641
+ # REPO_ID = "HariLogicgo/face_swap_models"
642
+ # MODELS_DIR = "./models"
643
+ # os.makedirs(MODELS_DIR, exist_ok=True)
644
+
645
+ # HF_TOKEN = os.getenv("HF_TOKEN")
646
+ # API_SECRET_TOKEN = os.getenv("API_SECRET_TOKEN")
647
+
648
+ # DO_SPACES_REGION = os.getenv("DO_SPACES_REGION", "blr1")
649
+ # DO_SPACES_ENDPOINT = f"https://{DO_SPACES_REGION}.digitaloceanspaces.com"
650
+ # DO_SPACES_KEY = os.getenv("DO_SPACES_KEY")
651
+ # DO_SPACES_SECRET = os.getenv("DO_SPACES_SECRET")
652
+ # DO_SPACES_BUCKET = os.getenv("DO_SPACES_BUCKET")
653
+
654
+ # # NEW admin DB
655
+ # ADMIN_MONGO_URL = os.getenv("ADMIN_MONGO_URL")
656
+ # admin_client = AsyncIOMotorClient(ADMIN_MONGO_URL)
657
+ # admin_db = admin_client.adminPanel
658
+ # subcategories_col = admin_db.subcategories
659
+ # media_clicks_col = admin_db.media_clicks
660
+
661
+ # # OLD logs DB
662
+ # MONGODB_URL = os.getenv("MONGODB_URL")
663
+ # client = None
664
+ # database = None
665
+
666
+ # # --------------------- Download Models ---------------------
667
+ # def download_models():
668
+ # logger.info("Downloading models...")
669
+ # inswapper_path = hf_hub_download(
670
+ # repo_id=REPO_ID,
671
+ # filename="models/inswapper_128.onnx",
672
+ # repo_type="model",
673
+ # local_dir=MODELS_DIR,
674
+ # token=HF_TOKEN
675
+ # )
676
+
677
+ # buffalo_files = ["1k3d68.onnx", "2d106det.onnx", "genderage.onnx", "det_10g.onnx", "w600k_r50.onnx"]
678
+ # for f in buffalo_files:
679
+ # hf_hub_download(
680
+ # repo_id=REPO_ID,
681
+ # filename=f"models/buffalo_l/" + f,
682
+ # repo_type="model",
683
+ # local_dir=MODELS_DIR,
684
+ # token=HF_TOKEN
685
+ # )
686
+
687
+ # logger.info("Models downloaded.")
688
+ # return inswapper_path
689
+
690
+
691
+ # inswapper_path = download_models()
692
+
693
+ # # --------------------- Face Analysis + Swapper ---------------------
694
+ # providers = ['CUDAExecutionProvider', 'CPUExecutionProvider']
695
+ # face_analysis_app = FaceAnalysis(name="buffalo_l", root=MODELS_DIR, providers=providers)
696
+ # face_analysis_app.prepare(ctx_id=0, det_size=(640, 640))
697
+ # swapper = insightface.model_zoo.get_model(inswapper_path, providers=providers)
698
+
699
+ # # --------------------- CodeFormer ---------------------
700
+ # CODEFORMER_PATH = "CodeFormer/inference_codeformer.py"
701
+
702
+ # def ensure_codeformer():
703
+ # if not os.path.exists("CodeFormer"):
704
+ # subprocess.run("git clone https://github.com/sczhou/CodeFormer.git", shell=True, check=True)
705
+ # subprocess.run("pip install -r CodeFormer/requirements.txt", shell=True, check=True)
706
+ # subprocess.run("python CodeFormer/basicsr/setup.py develop", shell=True, check=True)
707
+ # subprocess.run("python CodeFormer/scripts/download_pretrained_models.py facelib", shell=True, check=True)
708
+ # subprocess.run("python CodeFormer/scripts/download_pretrained_models.py CodeFormer", shell=True, check=True)
709
+
710
+ # ensure_codeformer()
711
+
712
+
713
+ # # --------------------- FastAPI ---------------------
714
+ # fastapi_app = FastAPI()
715
+
716
+ # @fastapi_app.on_event("startup")
717
+ # async def startup_db():
718
+ # global client, database
719
+ # logger.info("Initializing MongoDB for API logs...")
720
+ # client = AsyncIOMotorClient(MONGODB_URL)
721
+ # database = client.FaceSwap
722
+ # logger.info("MongoDB initialized for API logs")
723
+
724
+ # @fastapi_app.on_event("shutdown")
725
+ # async def shutdown_db():
726
+ # global client
727
+ # if client:
728
+ # client.close()
729
+ # logger.info("MongoDB connection closed")
730
+
731
+ # # --------------------- Auth ---------------------
732
+ # security = HTTPBearer()
733
+
734
+ # def verify_token(credentials: HTTPAuthorizationCredentials = Security(security)):
735
+ # if credentials.credentials != API_SECRET_TOKEN:
736
+ # raise HTTPException(status_code=401, detail="Invalid or missing token")
737
+ # return credentials.credentials
738
+
739
+ # # --------------------- Logging API Hits ---------------------
740
+ # async def log_faceswap_hit(token: str, status: str = "success"):
741
+ # global database
742
+ # if database is None:
743
+ # return
744
+ # await database.api_logs.insert_one({
745
+ # "token": token,
746
+ # "endpoint": "/faceswap",
747
+ # "status": status,
748
+ # "timestamp": datetime.utcnow()
749
+ # })
750
+
751
+ # # --------------------- Face Swap Pipeline ---------------------
752
+ # swap_lock = threading.Lock()
753
+
754
+ # def face_swap_and_enhance(src_img, tgt_img, temp_dir="/tmp/faceswap_work"):
755
+ # try:
756
+ # with swap_lock:
757
+ # # Use a temp dir for intermediate files
758
+ # if os.path.exists(temp_dir):
759
+ # shutil.rmtree(temp_dir)
760
+ # os.makedirs(temp_dir, exist_ok=True)
761
+
762
+ # src_bgr = cv2.cvtColor(src_img, cv2.COLOR_RGB2BGR)
763
+ # tgt_bgr = cv2.cvtColor(tgt_img, cv2.COLOR_RGB2BGR)
764
+
765
+ # src_faces = face_analysis_app.get(src_bgr)
766
+ # tgt_faces = face_analysis_app.get(tgt_bgr)
767
+ # if not src_faces or not tgt_faces:
768
+ # return None, None, "❌ Face not detected in one of the images"
769
+
770
+ # swapped_path = os.path.join(temp_dir, f"swapped_{uuid.uuid4().hex[:8]}.jpg")
771
+ # swapped_bgr = swapper.get(tgt_bgr, tgt_faces[0], src_faces[0])
772
+ # if swapped_bgr is None:
773
+ # return None, None, "❌ Face swap failed"
774
+
775
+ # cv2.imwrite(swapped_path, swapped_bgr)
776
+
777
+ # cmd = f"python {CODEFORMER_PATH} -w 0.7 --input_path {swapped_path} --output_path {temp_dir} --bg_upsampler realesrgan --face_upsample"
778
+ # result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
779
+ # if result.returncode != 0:
780
+ # return None, None, f"❌ CodeFormer failed:\n{result.stderr}"
781
+
782
+ # final_results_dir = os.path.join(temp_dir, "final_results")
783
+ # final_files = [f for f in os.listdir(final_results_dir) if f.endswith(".png")]
784
+ # if not final_files:
785
+ # return None, None, "❌ No enhanced image found"
786
+
787
+ # final_path = os.path.join(final_results_dir, final_files[0])
788
+ # final_img = cv2.cvtColor(cv2.imread(final_path), cv2.COLOR_BGR2RGB)
789
+
790
+ # return final_img, final_path, ""
791
+
792
+ # except Exception as e:
793
+ # return None, None, f"❌ Error: {str(e)}"
794
+
795
+ # # --------------------- Gradio ---------------------
796
+ # with gr.Blocks() as demo:
797
+ # gr.Markdown("Face Swap")
798
+
799
+ # with gr.Row():
800
+ # src_input = gr.Image(type="numpy", label="Upload Your Face")
801
+ # tgt_input = gr.Image(type="numpy", label="Upload Target Image")
802
+
803
+ # btn = gr.Button("Swap Face")
804
+ # output_img = gr.Image(type="numpy", label="Enhanced Output")
805
+ # download = gr.File(label="⬇️ Download Enhanced Image")
806
+ # error_box = gr.Textbox(label="Logs / Errors", interactive=False)
807
+
808
+ # def process(src, tgt):
809
+ # img, path, err = face_swap_and_enhance(src, tgt)
810
+ # return img, path, err
811
+
812
+ # btn.click(process, [src_input, tgt_input], [output_img, download, error_box])
813
+
814
+ # # --------------------- DigitalOcean Spaces Helper ---------------------
815
+ # def get_spaces_client():
816
+ # session = boto3.session.Session()
817
+ # client = session.client(
818
+ # 's3',
819
+ # region_name=DO_SPACES_REGION,
820
+ # endpoint_url=DO_SPACES_ENDPOINT,
821
+ # aws_access_key_id=DO_SPACES_KEY,
822
+ # aws_secret_access_key=DO_SPACES_SECRET,
823
+ # config=Config(signature_version='s3v4')
824
+ # )
825
+ # return client
826
+
827
+ # def upload_to_spaces(file_bytes, key, content_type="image/png"):
828
+ # client = get_spaces_client()
829
+ # client.put_object(Bucket=DO_SPACES_BUCKET, Key=key, Body=file_bytes, ContentType=content_type, ACL='public-read')
830
+ # return f"{DO_SPACES_ENDPOINT}/{DO_SPACES_BUCKET}/{key}"
831
+
832
+ # def download_from_spaces(key):
833
+ # client = get_spaces_client()
834
+ # obj = client.get_object(Bucket=DO_SPACES_BUCKET, Key=key)
835
+ # return obj['Body'].read()
836
+
837
+ # # --------------------- API Endpoints ---------------------
838
+ # @fastapi_app.get("/")
839
+ # def root():
840
+ # return RedirectResponse("/gradio")
841
+
842
+ # @fastapi_app.get("/health")
843
+ # async def health():
844
+ # return {"status": "healthy"}
845
+
846
+ # from fastapi import Form
847
+ # import requests
848
+ # @fastapi_app.get("/test-admin-db")
849
+ # async def test_admin_db():
850
+ # try:
851
+ # doc = await admin_db.list_collection_names()
852
+ # return {"ok": True, "collections": doc}
853
+ # except Exception as e:
854
+ # return {"ok": False, "error": str(e), "url": ADMIN_MONGO_URL}
855
+
856
+ # @fastapi_app.post("/face-swap", dependencies=[Depends(verify_token)])
857
+ # async def face_swap_api(
858
+ # source: UploadFile = File(...),
859
+ # target_category_id: str = Form(None),
860
+ # new_category_id: str = Form(None),
861
+ # user_id: Optional[str] = Form(None),
862
+ # credentials: HTTPAuthorizationCredentials = Security(security)
863
+ # ):
864
+ # start_time = datetime.utcnow()
865
+
866
+ # try:
867
+ # # ------------------------------------------------------------------
868
+ # # VALIDATION
869
+ # # ------------------------------------------------------------------
870
+ # # --------------------------------------------------------------
871
+ # # BACKWARD COMPATIBILITY FOR OLD ANDROID VERSIONS
872
+ # # --------------------------------------------------------------
873
+ # if target_category_id == "":
874
+ # target_category_id = None
875
+
876
+ # if new_category_id == "":
877
+ # new_category_id = None
878
+
879
+ # if user_id == "":
880
+ # user_id = None
881
+
882
+ # logger.info(f"[FaceSwap] Incoming request → target_category_id={target_category_id}, new_category_id={new_category_id}, user_id={user_id}")
883
+
884
+ # if target_category_id and new_category_id:
885
+ # raise HTTPException(400, "Provide only one of new_category_id or target_category_id.")
886
+
887
+ # if not target_category_id and not new_category_id:
888
+ # raise HTTPException(400, "Either new_category_id or target_category_id is required.")
889
+
890
+ # # ------------------------------------------------------------------
891
+ # # READ SOURCE IMAGE
892
+ # # ------------------------------------------------------------------
893
+ # src_bytes = await source.read()
894
+ # src_key = f"faceswap/source/{uuid.uuid4().hex}_{source.filename}"
895
+ # upload_to_spaces(src_bytes, src_key, content_type=source.content_type)
896
+
897
+ # # ------------------------------------------------------------------
898
+ # # CASE 1 : new_category_id → MongoDB lookup
899
+ # # ------------------------------------------------------------------
900
+ # if new_category_id:
901
+
902
+ # doc = await subcategories_col.find_one({
903
+ # "asset_images._id": ObjectId(new_category_id)
904
+ # })
905
+
906
+ # if not doc:
907
+ # raise HTTPException(404, "Asset image not found in database")
908
+
909
+ # # extract correct asset
910
+ # asset = next(
911
+ # (img for img in doc["asset_images"] if str(img["_id"]) == new_category_id),
912
+ # None
913
+ # )
914
+
915
+ # if not asset:
916
+ # raise HTTPException(404, "Asset image URL not found")
917
+
918
+ # # correct URL
919
+ # target_url = asset["url"]
920
+
921
+ # # correct categoryId (ObjectId)
922
+ # #category_oid = doc["categoryId"] # <-- DO NOT CONVERT TO STRING
923
+ # subcategory_oid = doc["_id"]
924
+
925
+ # # ------------------------------------------------------------------#
926
+ # # # MEDIA_CLICKS (ONLY IF user_id PRESENT)
927
+ # # ------------------------------------------------------------------#
928
+ # if user_id:
929
+ # try:
930
+ # user_oid = ObjectId(user_id.strip())
931
+ # now = datetime.utcnow()
932
+
933
+ # # Normalize dates (UTC midnight)
934
+ # today_date = datetime(now.year, now.month, now.day)
935
+ # yesterday_date = today_date - timedelta(days=1)
936
+
937
+ # # -------------------------------------------------
938
+ # # STEP 1: Ensure root document exists
939
+ # # -------------------------------------------------
940
+ # await media_clicks_col.update_one(
941
+ # {"userId": user_oid},
942
+ # {
943
+ # "$setOnInsert": {
944
+ # "userId": user_oid,
945
+ # "createdAt": now,
946
+ # "ai_edit_complete": 0,
947
+ # "ai_edit_daily_count": []
948
+ # }
949
+ # },
950
+ # upsert=True
951
+ # )
952
+ # # -------------------------------------------------
953
+ # # STEP 2: Handle DAILY USAGE (BINARY, NO DUPLICATES)
954
+ # # -------------------------------------------------
955
+ # doc = await media_clicks_col.find_one(
956
+ # {"userId": user_oid},
957
+ # {"ai_edit_daily_count": 1}
958
+ # )
959
+
960
+ # daily_entries = doc.get("ai_edit_daily_count", []) if doc else []
961
+
962
+ # # Normalize today to UTC midnight
963
+ # today_date = datetime(now.year, now.month, now.day)
964
+
965
+ # # Build normalized date → count map (THIS ENFORCES UNIQUENESS)
966
+ # daily_map = {}
967
+ # for entry in daily_entries:
968
+ # d = entry["date"]
969
+ # if isinstance(d, datetime):
970
+ # d = datetime(d.year, d.month, d.day)
971
+ # daily_map[d] = entry["count"] # overwrite = no duplicates
972
+
973
+ # # Determine last recorded date
974
+ # last_date = max(daily_map.keys()) if daily_map else today_date
975
+
976
+ # # Fill ALL missing days with count = 0
977
+ # next_day = last_date + timedelta(days=1)
978
+ # while next_day < today_date:
979
+ # daily_map.setdefault(next_day, 0)
980
+ # next_day += timedelta(days=1)
981
+
982
+ # # Mark today as used (binary)
983
+ # daily_map[today_date] = 1
984
+
985
+ # # Rebuild list: OLDEST → NEWEST
986
+ # final_daily_entries = [
987
+ # {"date": d, "count": daily_map[d]}
988
+ # for d in sorted(daily_map.keys())
989
+ # ]
990
+
991
+ # # Keep only last 32 days
992
+ # final_daily_entries = final_daily_entries[-32:]
993
+
994
+ # # Atomic replace
995
+ # await media_clicks_col.update_one(
996
+ # {"userId": user_oid},
997
+ # {
998
+ # "$set": {
999
+ # "ai_edit_daily_count": final_daily_entries,
1000
+ # "updatedAt": now
1001
+ # }
1002
+ # }
1003
+ # )
1004
+
1005
+ # # -------------------------------------------------
1006
+ # # STEP 3: Try updating existing subCategory
1007
+ # # -------------------------------------------------
1008
+ # update_result = await media_clicks_col.update_one(
1009
+ # {
1010
+ # "userId": user_oid,
1011
+ # "subCategories.subCategoryId": subcategory_oid
1012
+ # },
1013
+ # {
1014
+ # "$inc": {
1015
+ # "subCategories.$.click_count": 1,
1016
+ # "ai_edit_complete": 1
1017
+ # },
1018
+ # "$set": {
1019
+ # "subCategories.$.lastClickedAt": now,
1020
+ # "ai_edit_last_date": now,
1021
+ # "updatedAt": now
1022
+ # }
1023
+ # }
1024
+ # )
1025
+
1026
+ # # -------------------------------------------------
1027
+ # # STEP 4: Push subCategory if missing
1028
+ # # -------------------------------------------------
1029
+ # if update_result.matched_count == 0:
1030
+ # await media_clicks_col.update_one(
1031
+ # {"userId": user_oid},
1032
+ # {
1033
+ # "$inc": {
1034
+ # "ai_edit_complete": 1
1035
+ # },
1036
+ # "$set": {
1037
+ # "ai_edit_last_date": now,
1038
+ # "updatedAt": now
1039
+ # },
1040
+ # "$push": {
1041
+ # "subCategories": {
1042
+ # "subCategoryId": subcategory_oid,
1043
+ # "click_count": 1,
1044
+ # "lastClickedAt": now
1045
+ # }
1046
+ # }
1047
+ # }
1048
+ # )
1049
+
1050
+ # logger.info(
1051
+ # "[MEDIA_CLICK] user=%s subCategory=%s ai_edit_complete++ daily_tracked",
1052
+ # user_id,
1053
+ # str(subcategory_oid)
1054
+ # )
1055
+
1056
+ # except Exception as media_err:
1057
+ # logger.error(f"MEDIA_CLICK ERROR: {media_err}")
1058
+
1059
+ # # # ------------------------------------------------------------------
1060
+ # # # CASE 2 : target_category_id → DigitalOcean path (unchanged logic)
1061
+ # # # ------------------------------------------------------------------
1062
+ # if target_category_id:
1063
+ # client = get_spaces_client()
1064
+ # base_prefix = "faceswap/target/"
1065
+ # resp = client.list_objects_v2(
1066
+ # Bucket=DO_SPACES_BUCKET, Prefix=base_prefix, Delimiter="/"
1067
+ # )
1068
+
1069
+ # # Extract categories from the CommonPrefixes
1070
+ # categories = [p["Prefix"].split("/")[2] for p in resp.get("CommonPrefixes", [])]
1071
+
1072
+ # target_url = None
1073
+
1074
+ # # --- FIX STARTS HERE ---
1075
+ # for category in categories:
1076
+ # original_prefix = f"faceswap/target/{category}/original/"
1077
+ # thumb_prefix = f"faceswap/target/{category}/thumb/" # Keep for file list check (optional but safe)
1078
+
1079
+ # # List objects in original/
1080
+ # original_objects = client.list_objects_v2(
1081
+ # Bucket=DO_SPACES_BUCKET, Prefix=original_prefix
1082
+ # ).get("Contents", [])
1083
+
1084
+ # # List objects in thumb/ (optional: for the old code's extra check)
1085
+ # thumb_objects = client.list_objects_v2(
1086
+ # Bucket=DO_SPACES_BUCKET, Prefix=thumb_prefix
1087
+ # ).get("Contents", [])
1088
+
1089
+ # # Extract only the filenames and filter for .png
1090
+ # original_filenames = sorted([
1091
+ # obj["Key"].split("/")[-1] for obj in original_objects
1092
+ # if obj["Key"].split("/")[-1].endswith(".png")
1093
+ # ])
1094
+ # thumb_filenames = [
1095
+ # obj["Key"].split("/")[-1] for obj in thumb_objects
1096
+ # ]
1097
+
1098
+ # # Replicate the old indexing logic based on sorted filenames
1099
+ # for idx, filename in enumerate(original_filenames, start=1):
1100
+ # cid = f"{category.lower()}image_{idx}"
1101
+
1102
+ # # Optional: Replicate the thumb file check for 100% parity
1103
+ # # if filename in thumb_filenames and cid == target_category_id:
1104
+ # # Simpler check just on the ID, assuming thumb files are present
1105
+ # if cid == target_category_id:
1106
+ # # Construct the final target URL using the full prefix and the filename
1107
+ # target_url = f"{DO_SPACES_ENDPOINT}/{DO_SPACES_BUCKET}/{original_prefix}{filename}"
1108
+ # break
1109
+
1110
+ # if target_url:
1111
+ # break
1112
+ # # --- FIX ENDS HERE ---
1113
+
1114
+ # if not target_url:
1115
+ # raise HTTPException(404, "Target categoryId not found")
1116
+ # # # ------------------------------------------------------------------
1117
+ # # # DOWNLOAD TARGET IMAGE
1118
+ # # # ------------------------------------------------------------------
1119
+ # tgt_bytes = requests.get(target_url).content
1120
+
1121
+ # src_bgr = cv2.imdecode(np.frombuffer(src_bytes, np.uint8), cv2.IMREAD_COLOR)
1122
+ # tgt_bgr = cv2.imdecode(np.frombuffer(tgt_bytes, np.uint8), cv2.IMREAD_COLOR)
1123
+
1124
+ # if src_bgr is None or tgt_bgr is None:
1125
+ # raise HTTPException(400, "Invalid image data")
1126
+
1127
+ # src_rgb = cv2.cvtColor(src_bgr, cv2.COLOR_BGR2RGB)
1128
+ # tgt_rgb = cv2.cvtColor(tgt_bgr, cv2.COLOR_BGR2RGB)
1129
+
1130
+ # # ------------------------------------------------------------------
1131
+ # # FACE SWAP EXECUTION
1132
+ # # ------------------------------------------------------------------
1133
+ # final_img, final_path, err = face_swap_and_enhance(src_rgb, tgt_rgb)
1134
+ # if err:
1135
+ # raise HTTPException(500, err)
1136
+
1137
+ # with open(final_path, "rb") as f:
1138
+ # result_bytes = f.read()
1139
+
1140
+ # result_key = f"faceswap/result/{uuid.uuid4().hex}_enhanced.png"
1141
+ # result_url = upload_to_spaces(result_bytes, result_key)
1142
+ # end_time = datetime.utcnow()
1143
+ # response_time_ms = (end_time - start_time).total_seconds() * 1000
1144
+
1145
+ # if database is not None:
1146
+ # await database.api_logs.insert_one({
1147
+ # "endpoint": "/face-swap",
1148
+ # "status": "success",
1149
+ # "response_time_ms": response_time_ms,
1150
+ # "timestamp": end_time
1151
+ # })
1152
+
1153
+
1154
+ # return {
1155
+ # "result_key": result_key,
1156
+ # "result_url": result_url
1157
+ # }
1158
+
1159
+ # except Exception as e:
1160
+ # end_time = datetime.utcnow()
1161
+ # response_time_ms = (end_time - start_time).total_seconds() * 1000
1162
+
1163
+ # if database is not None:
1164
+ # await database.api_logs.insert_one({
1165
+ # "endpoint": "/face-swap",
1166
+ # "status": "fail",
1167
+ # "response_time_ms": response_time_ms,
1168
+ # "timestamp": end_time,
1169
+ # "error": str(e)
1170
+ # })
1171
+
1172
+ # raise HTTPException(500, f"Face swap failed: {str(e)}")
1173
+
1174
+ # @fastapi_app.get("/preview/{result_key:path}")
1175
+ # async def preview_result(result_key: str):
1176
+ # try:
1177
+ # img_bytes = download_from_spaces(result_key)
1178
+ # except Exception:
1179
+ # raise HTTPException(status_code=404, detail="Result not found")
1180
+ # return Response(
1181
+ # content=img_bytes,
1182
+ # media_type="image/png",
1183
+ # headers={"Content-Disposition": "inline; filename=result.png"}
1184
+ # )
1185
+ # # --------------------- Mount Gradio ---------------------
1186
+ # fastapi_app = mount_gradio_app(fastapi_app, demo, path="/gradio")
1187
+
1188
+ # if __name__ == "__main__":
1189
+ # uvicorn.run(fastapi_app, host="0.0.0.0", port=7860)