LogicGoInfotechSpaces commited on
Commit
62fa43d
·
verified ·
1 Parent(s): 6f427ff

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +708 -70
app.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import os
2
  import io
3
  import json
@@ -5,6 +6,8 @@ import traceback
5
  from datetime import datetime,timedelta
6
  from typing import Optional
7
  import time
 
 
8
  from fastapi import FastAPI, File, UploadFile, Form, HTTPException, Request, Depends
9
  from fastapi.responses import StreamingResponse, JSONResponse
10
  from fastapi.middleware.cors import CORSMiddleware
@@ -64,6 +67,27 @@ db = mongo[DB_NAME]
64
  fs = gridfs.GridFS(db)
65
  logs_collection = db["logs"]
66
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  # ---------------------------------------------------------------------
68
  # FastAPI app setup
69
  # ---------------------------------------------------------------------
@@ -146,6 +170,30 @@ def prepare_image(file_bytes: bytes) -> Image.Image:
146
  img = Image.open(io.BytesIO(file_bytes)).convert("RGB")
147
  img = resize_image_if_needed(img, max_size=(1024, 1024))
148
  return img
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
 
150
  MAX_COMPRESSED_SIZE = 2 * 1024 * 1024 # 2 MB
151
  def compress_pil_image_to_2mb(
@@ -307,7 +355,7 @@ async def root():
307
  "success": True,
308
  "message": "Polaroid,Kiddo,Makeup,Hairstyle API",
309
  "data": {
310
- "version": "1.0.0",
311
  "Product Name":"Beauty Camera - GlowCam AI Studio",
312
  "Released By" : "LogicGo Infotech"
313
  }
@@ -337,31 +385,24 @@ async def generate(
337
  try:
338
  img1_bytes = await image1.read()
339
  pil_img1 = prepare_image(img1_bytes)
340
- input1_id = fs.put(
341
- img1_bytes,
342
- filename=image1.filename,
343
- contentType=image1.content_type,
344
- metadata={"role": "input"},
345
- expireAt=expiry(6) # delete after 6 hours
346
- )
347
  except Exception as e:
348
  raise HTTPException(400, f"Failed to read first image: {e}")
349
 
350
  img2_bytes = None
351
  input2_id = None
 
352
  pil_img2 = None
353
 
354
  if image2:
355
  try:
356
  img2_bytes = await image2.read()
357
  pil_img2 = prepare_image(img2_bytes)
358
- input2_id = fs.put(
359
- img2_bytes,
360
- filename=image2.filename,
361
- contentType=image2.content_type,
362
- metadata={"role": "input"},
363
- expireAt=expiry(6)
364
- )
365
  except Exception as e:
366
  raise HTTPException(400, f"Failed to read second image: {e}")
367
 
@@ -527,8 +568,8 @@ async def generate(
527
  logs_collection.insert_one({
528
  "timestamp": datetime.utcnow(),
529
  "status": "failure",
530
- "input1_id": str(input1_id),
531
- "input2_id": str(input2_id) if input2_id else None,
532
  "prompt": prompt,
533
  "user_email": user.get("email"),
534
  "error": str(e),
@@ -540,23 +581,14 @@ async def generate(
540
  # -------------------------
541
  # 5. SAVE OUTPUT IMAGE
542
  # -------------------------
 
543
  out_buf = io.BytesIO()
544
  pil_output.save(out_buf, format="PNG")
545
  out_bytes = out_buf.getvalue()
 
 
 
546
 
547
- out_id = fs.put(
548
- out_bytes,
549
- filename=f"result_{input1_id}.png",
550
- contentType="image/png",
551
- metadata={
552
- "role": "output",
553
- "prompt": prompt,
554
- "input1_id": str(input1_id),
555
- "input2_id": str(input2_id) if input2_id else None,
556
- "user_email": user.get("email"),
557
- },
558
- expireAt=expiry(24) # 24 hours
559
- )
560
  # -------------------------
561
  # 5b. SAVE COMPRESSED IMAGE
562
  # -------------------------
@@ -565,18 +597,8 @@ async def generate(
565
  max_dim=1280
566
  )
567
 
568
- compressed_id = fs.put(
569
- compressed_bytes,
570
- filename=f"result_{input1_id}_compressed.jpg",
571
- contentType="image/jpeg",
572
- metadata={
573
- "role": "output_compressed",
574
- "original_output_id": str(out_id),
575
- "prompt": prompt,
576
- "user_email": user.get("email")
577
- },
578
- expireAt=expiry(24) # 24 hours
579
- )
580
 
581
 
582
  response_time_ms = round((time.time() - start_time) * 1000)
@@ -587,9 +609,9 @@ async def generate(
587
  logs_collection.insert_one({
588
  "timestamp": datetime.utcnow(),
589
  "status": "success",
590
- "input1_id": str(input1_id),
591
- "input2_id": str(input2_id) if input2_id else None,
592
- "output_id": str(out_id),
593
  "prompt": prompt,
594
  "user_email": user.get("email"),
595
  "response_time_ms": response_time_ms,
@@ -597,35 +619,15 @@ async def generate(
597
  })
598
 
599
  return JSONResponse({
600
- "output_image_id": str(out_id),
601
  "user": user.get("email"),
602
  "response_time_ms": response_time_ms,
603
- "Compressed_Image_URL": (
604
- f"https://logicgoinfotechspaces-polaroidimage.hf.space/image/{compressed_id}"
605
- )
606
  })
607
 
608
- @app.get("/image/{image_id}")
609
- def get_image(image_id: str, download: Optional[bool] = False):
610
- """Retrieve stored image by ID (no authentication required)."""
611
- try:
612
- oid = ObjectId(image_id)
613
- grid_out = fs.get(oid)
614
- except Exception:
615
- raise HTTPException(status_code=404, detail="Image not found")
616
-
617
- def iterfile():
618
- yield grid_out.read()
619
-
620
- headers = {}
621
- if download:
622
- headers["Content-Disposition"] = f'attachment; filename="{grid_out.filename}"'
623
-
624
- return StreamingResponse(
625
- iterfile(),
626
- media_type=grid_out.content_type or "application/octet-stream",
627
- headers=headers
628
- )
629
 
630
  # ---------------------------------------------------------------------
631
  # Run locally
@@ -633,4 +635,640 @@ def get_image(image_id: str, download: Optional[bool] = False):
633
  if __name__ == "__main__":
634
  import uvicorn
635
  uvicorn.run("app:app", host="0.0.0.0", port=7860, reload=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
636
 
 
1
+
2
  import os
3
  import io
4
  import json
 
6
  from datetime import datetime,timedelta
7
  from typing import Optional
8
  import time
9
+ import uuid
10
+ import boto3
11
  from fastapi import FastAPI, File, UploadFile, Form, HTTPException, Request, Depends
12
  from fastapi.responses import StreamingResponse, JSONResponse
13
  from fastapi.middleware.cors import CORSMiddleware
 
67
  fs = gridfs.GridFS(db)
68
  logs_collection = db["logs"]
69
 
70
+ # ---------------------------------------------------------------------
71
+ # DigitalOcean Spaces setup
72
+ # ---------------------------------------------------------------------
73
+ DO_SPACES_KEY = os.getenv("DO_SPACES_KEY")
74
+ DO_SPACES_SECRET = os.getenv("DO_SPACES_SECRET")
75
+ DO_SPACES_REGION = os.getenv("DO_SPACES_REGION", "blr1")
76
+ DO_SPACES_BUCKET = os.getenv("DO_SPACES_BUCKET")
77
+ DO_SPACES_ENDPOINT = os.getenv("DO_SPACES_ENDPOINT", f"https://{DO_SPACES_REGION}.digitaloceanspaces.com")
78
+
79
+ if not DO_SPACES_KEY or not DO_SPACES_SECRET:
80
+ raise RuntimeError("Missing DigitalOcean Spaces credentials in environment variables")
81
+
82
+ # Initialize S3 client for DigitalOcean Spaces
83
+ s3_client = boto3.client(
84
+ 's3',
85
+ region_name=DO_SPACES_REGION,
86
+ endpoint_url=DO_SPACES_ENDPOINT,
87
+ aws_access_key_id=DO_SPACES_KEY,
88
+ aws_secret_access_key=DO_SPACES_SECRET
89
+ )
90
+
91
  # ---------------------------------------------------------------------
92
  # FastAPI app setup
93
  # ---------------------------------------------------------------------
 
170
  img = Image.open(io.BytesIO(file_bytes)).convert("RGB")
171
  img = resize_image_if_needed(img, max_size=(1024, 1024))
172
  return img
173
+
174
+ def upload_to_digitalocean(image_bytes: bytes, folder: str, filename: str) -> str:
175
+ """
176
+ Upload image to DigitalOcean Spaces and return the public URL.
177
+ folder: 'source' or 'results'
178
+ """
179
+ key = f"valentine/{folder}/{filename}"
180
+ try:
181
+ s3_client.put_object(
182
+ Bucket=DO_SPACES_BUCKET,
183
+ Key=key,
184
+ Body=image_bytes,
185
+ ContentType="image/jpeg" if filename.endswith('.jpg') else "image/png"
186
+ )
187
+ url = f"{DO_SPACES_ENDPOINT}/{DO_SPACES_BUCKET}/{key}"
188
+ return url
189
+ except Exception as e:
190
+ raise RuntimeError(f"Failed to upload to DigitalOcean Spaces: {e}")
191
+
192
+ def generate_image_id() -> str:
193
+ """
194
+ Generate a random image ID (same format as MongoDB ObjectId for consistency)
195
+ """
196
+ return str(uuid.uuid4().hex[:24])
197
 
198
  MAX_COMPRESSED_SIZE = 2 * 1024 * 1024 # 2 MB
199
  def compress_pil_image_to_2mb(
 
355
  "success": True,
356
  "message": "Polaroid,Kiddo,Makeup,Hairstyle API",
357
  "data": {
358
+ "version": "1.0.1",
359
  "Product Name":"Beauty Camera - GlowCam AI Studio",
360
  "Released By" : "LogicGo Infotech"
361
  }
 
385
  try:
386
  img1_bytes = await image1.read()
387
  pil_img1 = prepare_image(img1_bytes)
388
+ input1_id = generate_image_id()
389
+ input1_filename = f"{input1_id}_{image1.filename}"
390
+ input1_url = upload_to_digitalocean(img1_bytes, "source", input1_filename)
 
 
 
 
391
  except Exception as e:
392
  raise HTTPException(400, f"Failed to read first image: {e}")
393
 
394
  img2_bytes = None
395
  input2_id = None
396
+ input2_url = None
397
  pil_img2 = None
398
 
399
  if image2:
400
  try:
401
  img2_bytes = await image2.read()
402
  pil_img2 = prepare_image(img2_bytes)
403
+ input2_id = generate_image_id()
404
+ input2_filename = f"{input2_id}_{image2.filename}"
405
+ input2_url = upload_to_digitalocean(img2_bytes, "source", input2_filename)
 
 
 
 
406
  except Exception as e:
407
  raise HTTPException(400, f"Failed to read second image: {e}")
408
 
 
568
  logs_collection.insert_one({
569
  "timestamp": datetime.utcnow(),
570
  "status": "failure",
571
+ "input1_id": input1_id,
572
+ "input2_id": input2_id if input2_id else None,
573
  "prompt": prompt,
574
  "user_email": user.get("email"),
575
  "error": str(e),
 
581
  # -------------------------
582
  # 5. SAVE OUTPUT IMAGE
583
  # -------------------------
584
+ output_image_id = generate_image_id()
585
  out_buf = io.BytesIO()
586
  pil_output.save(out_buf, format="PNG")
587
  out_bytes = out_buf.getvalue()
588
+
589
+ out_filename = f"{output_image_id}_result.png"
590
+ out_url = upload_to_digitalocean(out_bytes, "results", out_filename)
591
 
 
 
 
 
 
 
 
 
 
 
 
 
 
592
  # -------------------------
593
  # 5b. SAVE COMPRESSED IMAGE
594
  # -------------------------
 
597
  max_dim=1280
598
  )
599
 
600
+ compressed_filename = f"{output_image_id}_compressed.jpg"
601
+ compressed_url = upload_to_digitalocean(compressed_bytes, "results", compressed_filename)
 
 
 
 
 
 
 
 
 
 
602
 
603
 
604
  response_time_ms = round((time.time() - start_time) * 1000)
 
609
  logs_collection.insert_one({
610
  "timestamp": datetime.utcnow(),
611
  "status": "success",
612
+ "input1_id": input1_id,
613
+ "input2_id": input2_id if input2_id else None,
614
+ "output_id": output_image_id,
615
  "prompt": prompt,
616
  "user_email": user.get("email"),
617
  "response_time_ms": response_time_ms,
 
619
  })
620
 
621
  return JSONResponse({
622
+ "output_image_id": output_image_id,
623
  "user": user.get("email"),
624
  "response_time_ms": response_time_ms,
625
+ "Compressed_Image_URL": compressed_url
 
 
626
  })
627
 
628
+
629
+ # Image endpoint removed - images are now stored directly in DigitalOcean Spaces
630
+ # and are publicly accessible via the Compressed_Image_URL returned in the /generate response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
631
 
632
  # ---------------------------------------------------------------------
633
  # Run locally
 
635
  if __name__ == "__main__":
636
  import uvicorn
637
  uvicorn.run("app:app", host="0.0.0.0", port=7860, reload=True)
638
+ #################################################################################----------VERSION -1 CODE ----####################################################################
639
+ # import os
640
+ # import io
641
+ # import json
642
+ # import traceback
643
+ # from datetime import datetime,timedelta
644
+ # from typing import Optional
645
+ # import time
646
+ # from fastapi import FastAPI, File, UploadFile, Form, HTTPException, Request, Depends
647
+ # from fastapi.responses import StreamingResponse, JSONResponse
648
+ # from fastapi.middleware.cors import CORSMiddleware
649
+ # from pydantic import BaseModel
650
+ # from pymongo import MongoClient
651
+ # import gridfs
652
+ # from bson.objectid import ObjectId
653
+ # from PIL import Image
654
+ # from fastapi.concurrency import run_in_threadpool
655
+ # import shutil
656
+ # import firebase_admin
657
+ # from firebase_admin import credentials, auth
658
+ # from PIL import Image
659
+ # from huggingface_hub import InferenceClient
660
+ # # ---------------------------------------------------------------------
661
+ # # Load Firebase Config from env (stringified JSON)
662
+ # # ---------------------------------------------------------------------
663
+ # firebase_config_json = os.getenv("firebase_config")
664
+ # if not firebase_config_json:
665
+ # raise RuntimeError("❌ Missing Firebase config in environment variable 'firebase_config'")
666
+
667
+ # try:
668
+ # firebase_creds_dict = json.loads(firebase_config_json)
669
+ # cred = credentials.Certificate(firebase_creds_dict)
670
+ # firebase_admin.initialize_app(cred)
671
+ # except Exception as e:
672
+ # raise RuntimeError(f"Failed to initialize Firebase Admin SDK: {e}")
673
+
674
+ # # ---------------------------------------------------------------------
675
+ # # Hugging Face setup
676
+ # # ---------------------------------------------------------------------
677
+ # HF_TOKEN = os.getenv("HF_TOKEN")
678
+ # if not HF_TOKEN:
679
+ # raise RuntimeError("HF_TOKEN not set in environment variables")
680
+
681
+ # hf_client = InferenceClient(token=HF_TOKEN)
682
+ # # ---------------------------------------------------------------------
683
+ # # MODEL SELECTION
684
+ # # ---------------------------------------------------------------------
685
+ # genai = None # ✅ IMPORTANT: module-level declaration
686
+ # MODEL = os.getenv("IMAGE_MODEL", "GEMINI").upper()
687
+ # GEMINI_FORCE_CATEGORY_ID = "69368e741224bcb6bdb98076"
688
+ # # ---------------------------------------------------------------------
689
+ # # Gemini setup (ONLY used if MODEL == "GEMINI")
690
+ # # ---------------------------------------------------------------------
691
+ # GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
692
+ # GEMINI_IMAGE_MODEL = os.getenv("GEMINI_IMAGE_MODEL", "gemini-2.5-flash-image")
693
+
694
+ # # ---------------------------------------------------------------------
695
+ # # MongoDB setup
696
+ # # ---------------------------------------------------------------------
697
+ # MONGODB_URI=os.getenv("MONGODB_URI")
698
+ # DB_NAME = "polaroid_db"
699
+
700
+ # mongo = MongoClient(MONGODB_URI)
701
+ # db = mongo[DB_NAME]
702
+ # fs = gridfs.GridFS(db)
703
+ # logs_collection = db["logs"]
704
+
705
+ # # ---------------------------------------------------------------------
706
+ # # FastAPI app setup
707
+ # # ---------------------------------------------------------------------
708
+ # app = FastAPI(title="Qwen Image Edit API with Firebase Auth")
709
+ # app.add_middleware(
710
+ # CORSMiddleware,
711
+ # allow_origins=["*"],
712
+ # allow_credentials=True,
713
+ # allow_methods=["*"],
714
+ # allow_headers=["*"],
715
+ # )
716
+
717
+ # # ---------------------------------------------------------------------
718
+ # # Auth dependency
719
+ # # ---------------------------------------------------------------------
720
+ # async def verify_firebase_token(request: Request):
721
+ # """Middleware-like dependency to verify Firebase JWT from Authorization header."""
722
+ # auth_header = request.headers.get("Authorization")
723
+ # if not auth_header or not auth_header.startswith("Bearer "):
724
+ # raise HTTPException(status_code=401, detail="Missing or invalid Authorization header")
725
+
726
+ # id_token = auth_header.split("Bearer ")[1]
727
+ # try:
728
+ # decoded_token = auth.verify_id_token(id_token)
729
+ # request.state.user = decoded_token
730
+ # return decoded_token
731
+ # except Exception as e:
732
+ # raise HTTPException(status_code=401, detail=f"Invalid or expired Firebase token: {e}")
733
+
734
+ # # ---------------------------------------------------------------------
735
+ # # Models
736
+ # # ---------------------------------------------------------------------
737
+ # class HealthResponse(BaseModel):
738
+ # status: str
739
+ # db: str
740
+ # model: str
741
+
742
+ # # --------------------- UTILS ---------------------
743
+ # def resize_image_if_needed(img: Image.Image, max_size=(1024, 1024)) -> Image.Image:
744
+ # """
745
+ # Resize image to fit within max_size while keeping aspect ratio.
746
+ # """
747
+ # if img.width > max_size[0] or img.height > max_size[1]:
748
+ # img.thumbnail(max_size, Image.ANTIALIAS)
749
+ # return img
750
+ # # ---------------------------------------------------------------------
751
+ # # Lazy Gemini Initialization
752
+ # # ---------------------------------------------------------------------
753
+ # _genai_initialized = False
754
+ # def init_gemini():
755
+ # global _genai_initialized, genai
756
+
757
+ # if _genai_initialized:
758
+ # return
759
+
760
+ # if not GEMINI_API_KEY:
761
+ # raise RuntimeError("❌ GEMINI_API_KEY not set")
762
+
763
+ # import google.generativeai as genai
764
+ # genai.configure(api_key=GEMINI_API_KEY)
765
+
766
+ # _genai_initialized = True
767
+
768
+ # def expiry(hours: int):
769
+ # """Return UTC datetime for TTL expiration"""
770
+ # return datetime.utcnow() + timedelta(hours=hours)
771
+
772
+ # def prepare_image(file_bytes: bytes) -> Image.Image:
773
+ # """
774
+ # Open image and resize if larger than 1024x1024
775
+ # """
776
+ # img = Image.open(io.BytesIO(file_bytes)).convert("RGB")
777
+
778
+ # # ✅ MIN SIZE CHECK
779
+ # if img.width < 200 or img.height < 200:
780
+ # raise HTTPException(
781
+ # status_code=400,
782
+ # detail="Image size is below 200x200 pixels. Please upload a larger image."
783
+ # )
784
+ # img = Image.open(io.BytesIO(file_bytes)).convert("RGB")
785
+ # img = resize_image_if_needed(img, max_size=(1024, 1024))
786
+ # return img
787
+
788
+ # MAX_COMPRESSED_SIZE = 2 * 1024 * 1024 # 2 MB
789
+ # def compress_pil_image_to_2mb(
790
+ # pil_img: Image.Image,
791
+ # max_dim: int = 1280
792
+ # ) -> bytes:
793
+ # """
794
+ # Resize + compress PIL image to <= 2MB.
795
+ # Returns JPEG bytes.
796
+ # """
797
+ # img = pil_img.convert("RGB")
798
+
799
+ # # Resize (maintain aspect ratio)
800
+ # img.thumbnail((max_dim, max_dim), Image.LANCZOS)
801
+
802
+ # quality = 85
803
+ # buffer = io.BytesIO()
804
+
805
+ # while quality >= 40:
806
+ # buffer.seek(0)
807
+ # buffer.truncate()
808
+
809
+ # img.save(
810
+ # buffer,
811
+ # format="JPEG",
812
+ # quality=quality,
813
+ # optimize=True,
814
+ # progressive=True
815
+ # )
816
+
817
+ # if buffer.tell() <= MAX_COMPRESSED_SIZE:
818
+ # break
819
+
820
+ # quality -= 5
821
+
822
+ # return buffer.getvalue()
823
+
824
+ # def run_image_generation(
825
+ # image1: Image.Image,
826
+ # prompt: str,
827
+ # image2: Optional[Image.Image] = None,
828
+ # force_model: Optional[str] = None
829
+ # ) -> Image.Image:
830
+ # """
831
+ # Unified image generation interface.
832
+ # QWEN -> merges images if image2 exists
833
+ # GEMINI -> passes images separately
834
+ # """
835
+
836
+ # effective_model = (force_model or MODEL).upper()
837
+
838
+ # # ---------------- QWEN ----------------
839
+ # if effective_model == "QWEN":
840
+
841
+ # # ✅ Merge images ONLY for QWEN
842
+ # if image2:
843
+ # total_width = image1.width + image2.width
844
+ # max_height = max(image1.height, image2.height)
845
+ # merged = Image.new("RGB", (total_width, max_height))
846
+ # merged.paste(image1, (0, 0))
847
+ # merged.paste(image2, (image1.width, 0))
848
+ # else:
849
+ # merged = image1
850
+
851
+ # return hf_client.image_to_image(
852
+ # image=merged,
853
+ # prompt=prompt,
854
+ # model="Qwen/Qwen-Image-Edit"
855
+ # )
856
+
857
+ # # ---------------- GEMINI ----------------
858
+ # elif effective_model == "GEMINI":
859
+ # init_gemini()
860
+ # model = genai.GenerativeModel(GEMINI_IMAGE_MODEL)
861
+
862
+ # parts = [prompt]
863
+
864
+ # def add_image(img: Image.Image):
865
+ # buf = io.BytesIO()
866
+ # img.save(buf, format="PNG")
867
+ # buf.seek(0)
868
+ # parts.append({
869
+ # "mime_type": "image/png",
870
+ # "data": buf.getvalue()
871
+ # })
872
+
873
+ # add_image(image1)
874
+
875
+ # if image2:
876
+ # add_image(image2)
877
+
878
+ # response = model.generate_content(parts)
879
+
880
+ # # -------- SAFE IMAGE EXTRACTION --------
881
+ # import base64
882
+
883
+ # image_bytes = None
884
+ # for candidate in response.candidates:
885
+ # for part in candidate.content.parts:
886
+ # if hasattr(part, "inline_data") and part.inline_data:
887
+ # data = part.inline_data.data
888
+ # image_bytes = (
889
+ # data if isinstance(data, (bytes, bytearray))
890
+ # else base64.b64decode(data)
891
+ # )
892
+ # break
893
+ # if image_bytes:
894
+ # break
895
+
896
+ # if not image_bytes:
897
+ # raise RuntimeError("Gemini did not return an image")
898
+
899
+ # img = Image.open(io.BytesIO(image_bytes))
900
+ # img.verify()
901
+ # return Image.open(io.BytesIO(image_bytes)).convert("RGB")
902
+
903
+ # else:
904
+ # raise RuntimeError(f"Unsupported IMAGE_MODEL: {effective_model}")
905
+
906
+
907
+
908
+ # def get_admin_db(appname: Optional[str]):
909
+ # """
910
+ # Returns (categories_collection, media_clicks_collection)
911
+ # based on appname
912
+ # """
913
+ # # ---- Collage Maker ----
914
+ # if appname == "collage-maker":
915
+ # collage_uri = os.getenv("COLLAGE_MAKER_DB_URL")
916
+ # if not collage_uri:
917
+ # raise RuntimeError("COLLAGE_MAKER_DB_URL not set")
918
+
919
+ # client = MongoClient(collage_uri)
920
+ # db = client["adminPanel"]
921
+ # return db.categories, db.media_clicks
922
+
923
+ # # ---- AI ENHANCER ----
924
+ # if appname == "AI-Enhancer":
925
+ # enhancer_uri = os.getenv("AI_ENHANCER_DB_URL")
926
+ # if not enhancer_uri:
927
+ # raise RuntimeError("AI_ENHANCER_DB_URL not set")
928
+
929
+ # client = MongoClient(enhancer_uri)
930
+ # db = client["test"]
931
+ # return db.categories, db.media_clicks
932
+
933
+ # # DEFAULT (existing behavior)
934
+ # admin_client = MongoClient(os.getenv("ADMIN_MONGODB_URI"))
935
+ # db = admin_client["adminPanel"]
936
+ # return db.categories, db.media_clicks
937
+
938
+ # # ---------------------------------------------------------------------
939
+ # # Endpoints
940
+ # # ---------------------------------------------------------------------
941
+ # @app.get("/")
942
+ # async def root():
943
+ # """Root endpoint"""
944
+ # return {
945
+ # "success": True,
946
+ # "message": "Polaroid,Kiddo,Makeup,Hairstyle API",
947
+ # "data": {
948
+ # "version": "1.0.0",
949
+ # "Product Name":"Beauty Camera - GlowCam AI Studio",
950
+ # "Released By" : "LogicGo Infotech"
951
+ # }
952
+ # }
953
+
954
+ # @app.get("/health", response_model=HealthResponse)
955
+ # def health():
956
+ # """Public health check"""
957
+ # mongo.admin.command("ping")
958
+ # return HealthResponse(status="ok", db=db.name, model="Qwen/Qwen-Image-Edit")
959
+
960
+ # @app.post("/generate")
961
+ # async def generate(
962
+ # prompt: str = Form(...),
963
+ # image1: UploadFile = File(...),
964
+ # image2: Optional[UploadFile] = File(None),
965
+ # user_id: Optional[str] = Form(None),
966
+ # category_id: Optional[str] = Form(None),
967
+ # appname: Optional[str] = Form(None),
968
+ # user=Depends(verify_firebase_token)
969
+ # ):
970
+ # start_time = time.time()
971
+
972
+ # # -------------------------
973
+ # # 1. VALIDATE & READ IMAGES
974
+ # # -------------------------
975
+ # try:
976
+ # img1_bytes = await image1.read()
977
+ # pil_img1 = prepare_image(img1_bytes)
978
+ # input1_id = fs.put(
979
+ # img1_bytes,
980
+ # filename=image1.filename,
981
+ # contentType=image1.content_type,
982
+ # metadata={"role": "input"},
983
+ # expireAt=expiry(6) # delete after 6 hours
984
+ # )
985
+ # except Exception as e:
986
+ # raise HTTPException(400, f"Failed to read first image: {e}")
987
+
988
+ # img2_bytes = None
989
+ # input2_id = None
990
+ # pil_img2 = None
991
+
992
+ # if image2:
993
+ # try:
994
+ # img2_bytes = await image2.read()
995
+ # pil_img2 = prepare_image(img2_bytes)
996
+ # input2_id = fs.put(
997
+ # img2_bytes,
998
+ # filename=image2.filename,
999
+ # contentType=image2.content_type,
1000
+ # metadata={"role": "input"},
1001
+ # expireAt=expiry(6)
1002
+ # )
1003
+ # except Exception as e:
1004
+ # raise HTTPException(400, f"Failed to read second image: {e}")
1005
+
1006
+ # # -------------------------
1007
+ # # 3. CATEGORY CLICK LOGIC
1008
+ # # -------------------------
1009
+ # if user_id and category_id:
1010
+ # try:
1011
+ # admin_client = MongoClient(os.getenv("ADMIN_MONGODB_URI"))
1012
+ # admin_db = admin_client["adminPanel"]
1013
+
1014
+ # categories_col, media_clicks_col = get_admin_db(appname)
1015
+ # # categories_col = admin_db.categories
1016
+ # # media_clicks_col = admin_db.media_clicks
1017
+
1018
+ # # Validate user_oid & category_oid
1019
+ # user_oid = ObjectId(user_id)
1020
+ # category_oid = ObjectId(category_id)
1021
+
1022
+ # # Check category exists
1023
+ # category_doc = categories_col.find_one({"_id": category_oid})
1024
+ # if not category_doc:
1025
+ # raise HTTPException(400, f"Invalid category_id: {category_id}")
1026
+
1027
+ # now = datetime.utcnow()
1028
+
1029
+ # # Normalize dates (UTC midnight)
1030
+ # today_date = datetime(now.year, now.month, now.day)
1031
+ # yesterday_date = today_date - timedelta(days=1)
1032
+
1033
+ # # --------------------------------------------------
1034
+ # # AI EDIT USAGE TRACKING (GLOBAL PER USER)
1035
+ # # --------------------------------------------------
1036
+ # media_clicks_col.update_one(
1037
+ # {"userId": user_oid},
1038
+ # {
1039
+ # "$setOnInsert": {
1040
+ # "createdAt": now,
1041
+ # "ai_edit_daily_count": []
1042
+ # },
1043
+ # "$set": {
1044
+ # "ai_edit_last_date": now,
1045
+ # "updatedAt": now
1046
+ # },
1047
+ # "$inc": {
1048
+ # "ai_edit_complete": 1
1049
+ # }
1050
+ # },
1051
+ # upsert=True
1052
+ # )
1053
+
1054
+ # # --------------------------------------------------
1055
+ # # DAILY COUNT LOGIC
1056
+ # # --------------------------------------------------
1057
+ # now = datetime.utcnow()
1058
+ # today_date = datetime(now.year, now.month, now.day)
1059
+
1060
+ # doc = media_clicks_col.find_one(
1061
+ # {"userId": user_oid},
1062
+ # {"ai_edit_daily_count": 1}
1063
+ # )
1064
+
1065
+ # daily_entries = doc.get("ai_edit_daily_count", []) if doc else []
1066
+
1067
+ # # Build UNIQUE date -> count map
1068
+ # daily_map = {}
1069
+ # for entry in daily_entries:
1070
+ # d = entry["date"]
1071
+ # d = datetime(d.year, d.month, d.day) if isinstance(d, datetime) else d
1072
+ # daily_map[d] = entry["count"] # overwrite = no duplicates
1073
+
1074
+ # # Find last known date
1075
+ # last_date = max(daily_map.keys()) if daily_map else today_date
1076
+
1077
+ # # Fill ALL missing days with 0
1078
+ # next_day = last_date + timedelta(days=1)
1079
+ # while next_day < today_date:
1080
+ # daily_map.setdefault(next_day, 0)
1081
+ # next_day += timedelta(days=1)
1082
+
1083
+ # # Mark today as used (binary)
1084
+ # daily_map[today_date] = 1
1085
+
1086
+ # # Rebuild list (OLD → NEW)
1087
+ # final_daily_entries = [
1088
+ # {"date": d, "count": daily_map[d]}
1089
+ # for d in sorted(daily_map.keys())
1090
+ # ]
1091
+
1092
+ # # Keep last 32 days only
1093
+ # final_daily_entries = final_daily_entries[-32:]
1094
+
1095
+ # # ATOMIC REPLACE (NO PUSH)
1096
+ # media_clicks_col.update_one(
1097
+ # {"userId": user_oid},
1098
+ # {
1099
+ # "$set": {
1100
+ # "ai_edit_daily_count": final_daily_entries,
1101
+ # "ai_edit_last_date": now,
1102
+ # "updatedAt": now
1103
+ # }
1104
+ # }
1105
+ # )
1106
+
1107
+
1108
+ # # --------------------------------------------------
1109
+ # # CATEGORY CLICK LOGIC
1110
+ # # --------------------------------------------------
1111
+ # update_res = media_clicks_col.update_one(
1112
+ # {"userId": user_oid, "categories.categoryId": category_oid},
1113
+ # {
1114
+ # "$set": {
1115
+ # "updatedAt": now,
1116
+ # "categories.$.lastClickedAt": now
1117
+ # },
1118
+ # "$inc": {
1119
+ # "categories.$.click_count": 1
1120
+ # }
1121
+ # }
1122
+ # )
1123
+
1124
+ # # If category does not exist → push new
1125
+ # if update_res.matched_count == 0:
1126
+ # media_clicks_col.update_one(
1127
+ # {"userId": user_oid},
1128
+ # {
1129
+ # "$set": {"updatedAt": now},
1130
+ # "$push": {
1131
+ # "categories": {
1132
+ # "categoryId": category_oid,
1133
+ # "click_count": 1,
1134
+ # "lastClickedAt": now
1135
+ # }
1136
+ # }
1137
+ # },
1138
+ # upsert=True
1139
+ # )
1140
+
1141
+ # except Exception as e:
1142
+ # print("CATEGORY_LOG_ERROR:", e)
1143
+ # # -------------------------
1144
+ # # 4. HF INFERENCE
1145
+ # # -------------------------
1146
+ # try:
1147
+ # # --------------------------------------------------
1148
+ # # MODEL OVERRIDE BASED ON CATEGORY
1149
+ # # --------------------------------------------------
1150
+ # force_model = None
1151
+
1152
+ # if category_id == GEMINI_FORCE_CATEGORY_ID:
1153
+ # force_model = "GEMINI"
1154
+
1155
+ # pil_output = run_image_generation(
1156
+ # image1=pil_img1,
1157
+ # image2=pil_img2,
1158
+ # prompt=prompt,
1159
+ # force_model="GEMINI" if category_id == GEMINI_FORCE_CATEGORY_ID else None
1160
+ # )
1161
+
1162
+
1163
+ # except Exception as e:
1164
+ # response_time_ms = round((time.time() - start_time) * 1000)
1165
+ # logs_collection.insert_one({
1166
+ # "timestamp": datetime.utcnow(),
1167
+ # "status": "failure",
1168
+ # "input1_id": str(input1_id),
1169
+ # "input2_id": str(input2_id) if input2_id else None,
1170
+ # "prompt": prompt,
1171
+ # "user_email": user.get("email"),
1172
+ # "error": str(e),
1173
+ # "response_time_ms": response_time_ms,
1174
+ # "appname": appname
1175
+ # })
1176
+ # raise HTTPException(500, f"Inference failed: {e}")
1177
+
1178
+ # # -------------------------
1179
+ # # 5. SAVE OUTPUT IMAGE
1180
+ # # -------------------------
1181
+ # out_buf = io.BytesIO()
1182
+ # pil_output.save(out_buf, format="PNG")
1183
+ # out_bytes = out_buf.getvalue()
1184
+
1185
+ # out_id = fs.put(
1186
+ # out_bytes,
1187
+ # filename=f"result_{input1_id}.png",
1188
+ # contentType="image/png",
1189
+ # metadata={
1190
+ # "role": "output",
1191
+ # "prompt": prompt,
1192
+ # "input1_id": str(input1_id),
1193
+ # "input2_id": str(input2_id) if input2_id else None,
1194
+ # "user_email": user.get("email"),
1195
+ # },
1196
+ # expireAt=expiry(24) # 24 hours
1197
+ # )
1198
+ # # -------------------------
1199
+ # # 5b. SAVE COMPRESSED IMAGE
1200
+ # # -------------------------
1201
+ # compressed_bytes = compress_pil_image_to_2mb(
1202
+ # pil_output,
1203
+ # max_dim=1280
1204
+ # )
1205
+
1206
+ # compressed_id = fs.put(
1207
+ # compressed_bytes,
1208
+ # filename=f"result_{input1_id}_compressed.jpg",
1209
+ # contentType="image/jpeg",
1210
+ # metadata={
1211
+ # "role": "output_compressed",
1212
+ # "original_output_id": str(out_id),
1213
+ # "prompt": prompt,
1214
+ # "user_email": user.get("email")
1215
+ # },
1216
+ # expireAt=expiry(24) # 24 hours
1217
+ # )
1218
+
1219
+
1220
+ # response_time_ms = round((time.time() - start_time) * 1000)
1221
+
1222
+ # # -------------------------
1223
+ # # 6. LOG SUCCESS
1224
+ # # -------------------------
1225
+ # logs_collection.insert_one({
1226
+ # "timestamp": datetime.utcnow(),
1227
+ # "status": "success",
1228
+ # "input1_id": str(input1_id),
1229
+ # "input2_id": str(input2_id) if input2_id else None,
1230
+ # "output_id": str(out_id),
1231
+ # "prompt": prompt,
1232
+ # "user_email": user.get("email"),
1233
+ # "response_time_ms": response_time_ms,
1234
+ # "appname": appname
1235
+ # })
1236
+
1237
+ # return JSONResponse({
1238
+ # "output_image_id": str(out_id),
1239
+ # "user": user.get("email"),
1240
+ # "response_time_ms": response_time_ms,
1241
+ # "Compressed_Image_URL": (
1242
+ # f"https://logicgoinfotechspaces-polaroidimage.hf.space/image/{compressed_id}"
1243
+ # )
1244
+ # })
1245
+
1246
+ # @app.get("/image/{image_id}")
1247
+ # def get_image(image_id: str, download: Optional[bool] = False):
1248
+ # """Retrieve stored image by ID (no authentication required)."""
1249
+ # try:
1250
+ # oid = ObjectId(image_id)
1251
+ # grid_out = fs.get(oid)
1252
+ # except Exception:
1253
+ # raise HTTPException(status_code=404, detail="Image not found")
1254
+
1255
+ # def iterfile():
1256
+ # yield grid_out.read()
1257
+
1258
+ # headers = {}
1259
+ # if download:
1260
+ # headers["Content-Disposition"] = f'attachment; filename="{grid_out.filename}"'
1261
+
1262
+ # return StreamingResponse(
1263
+ # iterfile(),
1264
+ # media_type=grid_out.content_type or "application/octet-stream",
1265
+ # headers=headers
1266
+ # )
1267
+
1268
+ # # ---------------------------------------------------------------------
1269
+ # # Run locally
1270
+ # # ---------------------------------------------------------------------
1271
+ # if __name__ == "__main__":
1272
+ # import uvicorn
1273
+ # uvicorn.run("app:app", host="0.0.0.0", port=7860, reload=True)
1274