LogicGoInfotechSpaces commited on
Commit
190f26a
·
verified ·
1 Parent(s): 2f2f562

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +49 -1299
app.py CHANGED
@@ -1,6 +1,7 @@
1
  import os
2
  import io
3
  import json
 
4
  import traceback
5
  from datetime import datetime,timedelta
6
  from typing import Optional
@@ -11,7 +12,7 @@ from fastapi import FastAPI, File, UploadFile, Form, HTTPException, Request, Dep
11
  from fastapi.responses import StreamingResponse, JSONResponse
12
  from fastapi.middleware.cors import CORSMiddleware
13
  from pydantic import BaseModel
14
- from pymongo import MongoClient
15
  import gridfs
16
  from gridfs.errors import NoFile
17
  from bson.objectid import ObjectId
@@ -76,6 +77,7 @@ DB_NAME = "polaroid_db"
76
  mongo = MongoClient(MONGODB_URI)
77
  db = mongo[DB_NAME]
78
  fs = gridfs.GridFS(db)
 
79
 
80
  # Separate Logs DB
81
  logs_client = None
@@ -196,13 +198,14 @@ def upload_to_digitalocean(image_bytes: bytes, folder: str, filename: str) -> st
196
  Upload image to DigitalOcean Spaces and return the public URL.
197
  folder: 'source' or 'results'
198
  """
199
- key = f"valentine/{folder}/{filename}"
 
200
  try:
201
  s3_client.put_object(
202
  Bucket=DO_SPACES_BUCKET,
203
  Key=key,
204
  Body=image_bytes,
205
- ContentType="image/jpeg" if filename.endswith('.jpg') else "image/png",
206
  ACL='public-read'
207
  )
208
  url = f"{DO_SPACES_ENDPOINT}/{DO_SPACES_BUCKET}/{key}"
@@ -210,6 +213,35 @@ def upload_to_digitalocean(image_bytes: bytes, folder: str, filename: str) -> st
210
  except Exception as e:
211
  raise RuntimeError(f"Failed to upload to DigitalOcean Spaces: {e}")
212
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  def generate_image_id() -> str:
214
  """
215
  Generate a random image ID (same format as MongoDB ObjectId for consistency)
@@ -399,6 +431,12 @@ async def generate(
399
  user=Depends(verify_firebase_token)
400
  ):
401
  start_time = time.time()
 
 
 
 
 
 
402
 
403
  # -------------------------
404
  # 1. VALIDATE & READ IMAGES
@@ -407,7 +445,8 @@ async def generate(
407
  img1_bytes = await image1.read()
408
  pil_img1 = prepare_image(img1_bytes)
409
  input1_id = generate_image_id()
410
- input1_filename = f"{input1_id}_{image1.filename}"
 
411
  input1_url = upload_to_digitalocean(img1_bytes, "source", input1_filename)
412
  except Exception as e:
413
  raise HTTPException(400, f"Failed to read first image: {e}")
@@ -422,7 +461,10 @@ async def generate(
422
  img2_bytes = await image2.read()
423
  pil_img2 = prepare_image(img2_bytes)
424
  input2_id = generate_image_id()
425
- input2_filename = f"{input2_id}_{image2.filename}"
 
 
 
426
  input2_url = upload_to_digitalocean(img2_bytes, "source", input2_filename)
427
  except Exception as e:
428
  raise HTTPException(400, f"Failed to read second image: {e}")
@@ -617,7 +659,7 @@ async def generate(
617
  pil_output.save(out_buf, format="PNG")
618
  out_bytes = out_buf.getvalue()
619
 
620
- out_filename = f"{output_image_id}_result.png"
621
  out_url = upload_to_digitalocean(out_bytes, "results", out_filename)
622
  try:
623
  fs.put(
@@ -642,26 +684,12 @@ async def generate(
642
  max_dim=1280
643
  )
644
 
645
- compressed_filename = f"{output_image_id}_compressed.jpg"
646
  compressed_url = upload_to_digitalocean(compressed_bytes, "results", compressed_filename)
647
 
648
 
649
  response_time_ms = round((time.time() - start_time) * 1000)
650
 
651
- # -------------------------
652
- # 6. LOG SUCCESS
653
- # -------------------------
654
- # logs_collection.insert_one({
655
- # "timestamp": datetime.utcnow(),
656
- # "status": "success",
657
- # "input1_id": input1_id,
658
- # "input2_id": input2_id if input2_id else None,
659
- # "output_id": output_image_id,
660
- # "prompt": prompt,
661
- # "user_email": user.get("email"),
662
- # "response_time_ms": response_time_ms,
663
- # "appname": appname
664
- # })
665
  if logs_collection is not None:
666
  logs_collection.insert_one({
667
  "endpoint": "/generate",
@@ -714,1281 +742,3 @@ if __name__ == "__main__":
714
  import uvicorn
715
  uvicorn.run("app:app", host="0.0.0.0", port=7860, reload=True)
716
 
717
-
718
-
719
-
720
-
721
- # import os
722
- # import io
723
- # import json
724
- # import traceback
725
- # from datetime import datetime,timedelta
726
- # from typing import Optional
727
- # import time
728
- # import uuid
729
- # import boto3
730
- # from fastapi import FastAPI, File, UploadFile, Form, HTTPException, Request, Depends
731
- # from fastapi.responses import StreamingResponse, JSONResponse
732
- # from fastapi.middleware.cors import CORSMiddleware
733
- # from pydantic import BaseModel
734
- # from pymongo import MongoClient
735
- # import gridfs
736
- # from bson.objectid import ObjectId
737
- # from PIL import Image
738
- # from fastapi.concurrency import run_in_threadpool
739
- # import shutil
740
- # import firebase_admin
741
- # from firebase_admin import credentials, auth
742
- # from PIL import Image
743
- # from huggingface_hub import InferenceClient
744
- # # ---------------------------------------------------------------------
745
- # # Load Firebase Config from env (stringified JSON)
746
- # # ---------------------------------------------------------------------
747
- # firebase_config_json = os.getenv("firebase_config")
748
- # if not firebase_config_json:
749
- # raise RuntimeError("❌ Missing Firebase config in environment variable 'firebase_config'")
750
-
751
- # try:
752
- # firebase_creds_dict = json.loads(firebase_config_json)
753
- # cred = credentials.Certificate(firebase_creds_dict)
754
- # firebase_admin.initialize_app(cred)
755
- # except Exception as e:
756
- # raise RuntimeError(f"Failed to initialize Firebase Admin SDK: {e}")
757
-
758
- # # ---------------------------------------------------------------------
759
- # # Hugging Face setup
760
- # # ---------------------------------------------------------------------
761
- # HF_TOKEN = os.getenv("HF_TOKEN")
762
- # if not HF_TOKEN:
763
- # raise RuntimeError("HF_TOKEN not set in environment variables")
764
-
765
- # hf_client = InferenceClient(token=HF_TOKEN)
766
- # # ---------------------------------------------------------------------
767
- # # MODEL SELECTION
768
- # # ---------------------------------------------------------------------
769
- # genai = None # ✅ IMPORTANT: module-level declaration
770
- # MODEL = os.getenv("IMAGE_MODEL", "GEMINI").upper()
771
- # GEMINI_FORCE_CATEGORY_ID = "69368e741224bcb6bdb98076"
772
- # # ---------------------------------------------------------------------
773
- # # Gemini setup (ONLY used if MODEL == "GEMINI")
774
- # # ---------------------------------------------------------------------
775
- # GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
776
- # GEMINI_IMAGE_MODEL = os.getenv("GEMINI_IMAGE_MODEL", "gemini-2.5-flash-image")
777
-
778
- # # ---------------------------------------------------------------------
779
- # # MongoDB setup
780
- # # ---------------------------------------------------------------------
781
- # MONGODB_URI=os.getenv("MONGODB_URI")
782
- # DB_NAME = "polaroid_db"
783
-
784
- # mongo = MongoClient(MONGODB_URI)
785
- # db = mongo[DB_NAME]
786
- # fs = gridfs.GridFS(db)
787
- # logs_collection = db["logs"]
788
-
789
- # # ---------------------------------------------------------------------
790
- # # DigitalOcean Spaces setup
791
- # # ---------------------------------------------------------------------
792
- # DO_SPACES_KEY = os.getenv("DO_SPACES_KEY")
793
- # DO_SPACES_SECRET = os.getenv("DO_SPACES_SECRET")
794
- # DO_SPACES_REGION = os.getenv("DO_SPACES_REGION", "blr1")
795
- # DO_SPACES_BUCKET = os.getenv("DO_SPACES_BUCKET")
796
- # DO_SPACES_ENDPOINT = os.getenv("DO_SPACES_ENDPOINT", f"https://{DO_SPACES_REGION}.digitaloceanspaces.com")
797
-
798
- # if not DO_SPACES_KEY or not DO_SPACES_SECRET:
799
- # raise RuntimeError("Missing DigitalOcean Spaces credentials in environment variables")
800
-
801
- # # Initialize S3 client for DigitalOcean Spaces
802
- # s3_client = boto3.client(
803
- # 's3',
804
- # region_name=DO_SPACES_REGION,
805
- # endpoint_url=DO_SPACES_ENDPOINT,
806
- # aws_access_key_id=DO_SPACES_KEY,
807
- # aws_secret_access_key=DO_SPACES_SECRET
808
- # )
809
-
810
- # # ---------------------------------------------------------------------
811
- # # FastAPI app setup
812
- # # ---------------------------------------------------------------------
813
- # app = FastAPI(title="Qwen Image Edit API with Firebase Auth")
814
- # app.add_middleware(
815
- # CORSMiddleware,
816
- # allow_origins=["*"],
817
- # allow_credentials=True,
818
- # allow_methods=["*"],
819
- # allow_headers=["*"],
820
- # )
821
-
822
- # # ---------------------------------------------------------------------
823
- # # Auth dependency
824
- # # ---------------------------------------------------------------------
825
- # async def verify_firebase_token(request: Request):
826
- # """Middleware-like dependency to verify Firebase JWT from Authorization header."""
827
- # auth_header = request.headers.get("Authorization")
828
- # if not auth_header or not auth_header.startswith("Bearer "):
829
- # raise HTTPException(status_code=401, detail="Missing or invalid Authorization header")
830
-
831
- # id_token = auth_header.split("Bearer ")[1]
832
- # try:
833
- # decoded_token = auth.verify_id_token(id_token)
834
- # request.state.user = decoded_token
835
- # return decoded_token
836
- # except Exception as e:
837
- # raise HTTPException(status_code=401, detail=f"Invalid or expired Firebase token: {e}")
838
-
839
- # # ---------------------------------------------------------------------
840
- # # Models
841
- # # ---------------------------------------------------------------------
842
- # class HealthResponse(BaseModel):
843
- # status: str
844
- # db: str
845
- # model: str
846
-
847
- # # --------------------- UTILS ---------------------
848
- # def resize_image_if_needed(img: Image.Image, max_size=(1024, 1024)) -> Image.Image:
849
- # """
850
- # Resize image to fit within max_size while keeping aspect ratio.
851
- # """
852
- # if img.width > max_size[0] or img.height > max_size[1]:
853
- # img.thumbnail(max_size, Image.ANTIALIAS)
854
- # return img
855
- # # ---------------------------------------------------------------------
856
- # # Lazy Gemini Initialization
857
- # # ---------------------------------------------------------------------
858
- # _genai_initialized = False
859
- # def init_gemini():
860
- # global _genai_initialized, genai
861
-
862
- # if _genai_initialized:
863
- # return
864
-
865
- # if not GEMINI_API_KEY:
866
- # raise RuntimeError("❌ GEMINI_API_KEY not set")
867
-
868
- # import google.generativeai as genai
869
- # genai.configure(api_key=GEMINI_API_KEY)
870
-
871
- # _genai_initialized = True
872
-
873
- # def expiry(hours: int):
874
- # """Return UTC datetime for TTL expiration"""
875
- # return datetime.utcnow() + timedelta(hours=hours)
876
-
877
- # def prepare_image(file_bytes: bytes) -> Image.Image:
878
- # """
879
- # Open image and resize if larger than 1024x1024
880
- # """
881
- # img = Image.open(io.BytesIO(file_bytes)).convert("RGB")
882
-
883
- # # ✅ MIN SIZE CHECK
884
- # if img.width < 200 or img.height < 200:
885
- # raise HTTPException(
886
- # status_code=400,
887
- # detail="Image size is below 200x200 pixels. Please upload a larger image."
888
- # )
889
- # img = Image.open(io.BytesIO(file_bytes)).convert("RGB")
890
- # img = resize_image_if_needed(img, max_size=(1024, 1024))
891
- # return img
892
-
893
- # def upload_to_digitalocean(image_bytes: bytes, folder: str, filename: str) -> str:
894
- # """
895
- # Upload image to DigitalOcean Spaces and return the public URL.
896
- # folder: 'source' or 'results'
897
- # """
898
- # key = f"valentine/{folder}/{filename}"
899
- # try:
900
- # s3_client.put_object(
901
- # Bucket=DO_SPACES_BUCKET,
902
- # Key=key,
903
- # Body=image_bytes,
904
- # ContentType="image/jpeg" if filename.endswith('.jpg') else "image/png",
905
- # ACL='public-read'
906
- # )
907
- # url = f"{DO_SPACES_ENDPOINT}/{DO_SPACES_BUCKET}/{key}"
908
- # return url
909
- # except Exception as e:
910
- # raise RuntimeError(f"Failed to upload to DigitalOcean Spaces: {e}")
911
-
912
- # def generate_image_id() -> str:
913
- # """
914
- # Generate a random image ID (same format as MongoDB ObjectId for consistency)
915
- # """
916
- # return str(uuid.uuid4().hex[:24])
917
-
918
- # MAX_COMPRESSED_SIZE = 2 * 1024 * 1024 # 2 MB
919
- # def compress_pil_image_to_2mb(
920
- # pil_img: Image.Image,
921
- # max_dim: int = 1280
922
- # ) -> bytes:
923
- # """
924
- # Resize + compress PIL image to <= 2MB.
925
- # Returns JPEG bytes.
926
- # """
927
- # img = pil_img.convert("RGB")
928
-
929
- # # Resize (maintain aspect ratio)
930
- # img.thumbnail((max_dim, max_dim), Image.LANCZOS)
931
-
932
- # quality = 85
933
- # buffer = io.BytesIO()
934
-
935
- # while quality >= 40:
936
- # buffer.seek(0)
937
- # buffer.truncate()
938
-
939
- # img.save(
940
- # buffer,
941
- # format="JPEG",
942
- # quality=quality,
943
- # optimize=True,
944
- # progressive=True
945
- # )
946
-
947
- # if buffer.tell() <= MAX_COMPRESSED_SIZE:
948
- # break
949
-
950
- # quality -= 5
951
-
952
- # return buffer.getvalue()
953
-
954
- # def run_image_generation(
955
- # image1: Image.Image,
956
- # prompt: str,
957
- # image2: Optional[Image.Image] = None,
958
- # force_model: Optional[str] = None
959
- # ) -> Image.Image:
960
- # """
961
- # Unified image generation interface.
962
- # QWEN -> merges images if image2 exists
963
- # GEMINI -> passes images separately
964
- # """
965
-
966
- # effective_model = (force_model or MODEL).upper()
967
-
968
- # # ---------------- QWEN ----------------
969
- # if effective_model == "QWEN":
970
-
971
- # # ✅ Merge images ONLY for QWEN
972
- # if image2:
973
- # total_width = image1.width + image2.width
974
- # max_height = max(image1.height, image2.height)
975
- # merged = Image.new("RGB", (total_width, max_height))
976
- # merged.paste(image1, (0, 0))
977
- # merged.paste(image2, (image1.width, 0))
978
- # else:
979
- # merged = image1
980
-
981
- # return hf_client.image_to_image(
982
- # image=merged,
983
- # prompt=prompt,
984
- # model="Qwen/Qwen-Image-Edit"
985
- # )
986
-
987
- # # ---------------- GEMINI ----------------
988
- # elif effective_model == "GEMINI":
989
- # init_gemini()
990
- # model = genai.GenerativeModel(GEMINI_IMAGE_MODEL)
991
-
992
- # parts = [prompt]
993
-
994
- # def add_image(img: Image.Image):
995
- # buf = io.BytesIO()
996
- # img.save(buf, format="PNG")
997
- # buf.seek(0)
998
- # parts.append({
999
- # "mime_type": "image/png",
1000
- # "data": buf.getvalue()
1001
- # })
1002
-
1003
- # add_image(image1)
1004
-
1005
- # if image2:
1006
- # add_image(image2)
1007
-
1008
- # response = model.generate_content(parts)
1009
-
1010
- # # -------- SAFE IMAGE EXTRACTION --------
1011
- # import base64
1012
-
1013
- # image_bytes = None
1014
- # for candidate in response.candidates:
1015
- # for part in candidate.content.parts:
1016
- # if hasattr(part, "inline_data") and part.inline_data:
1017
- # data = part.inline_data.data
1018
- # image_bytes = (
1019
- # data if isinstance(data, (bytes, bytearray))
1020
- # else base64.b64decode(data)
1021
- # )
1022
- # break
1023
- # if image_bytes:
1024
- # break
1025
-
1026
- # if not image_bytes:
1027
- # raise RuntimeError("Gemini did not return an image")
1028
-
1029
- # img = Image.open(io.BytesIO(image_bytes))
1030
- # img.verify()
1031
- # return Image.open(io.BytesIO(image_bytes)).convert("RGB")
1032
-
1033
- # else:
1034
- # raise RuntimeError(f"Unsupported IMAGE_MODEL: {effective_model}")
1035
-
1036
-
1037
-
1038
- # def get_admin_db(appname: Optional[str]):
1039
- # """
1040
- # Returns (categories_collection, media_clicks_collection)
1041
- # based on appname
1042
- # """
1043
- # # ---- Collage Maker ----
1044
- # if appname == "collage-maker":
1045
- # collage_uri = os.getenv("COLLAGE_MAKER_DB_URL")
1046
- # if not collage_uri:
1047
- # raise RuntimeError("COLLAGE_MAKER_DB_URL not set")
1048
-
1049
- # client = MongoClient(collage_uri)
1050
- # db = client["adminPanel"]
1051
- # return db.categories, db.media_clicks
1052
-
1053
- # # ---- AI ENHANCER ----
1054
- # if appname == "AI-Enhancer":
1055
- # enhancer_uri = os.getenv("AI_ENHANCER_DB_URL")
1056
- # if not enhancer_uri:
1057
- # raise RuntimeError("AI_ENHANCER_DB_URL not set")
1058
-
1059
- # client = MongoClient(enhancer_uri)
1060
- # db = client["test"]
1061
- # return db.categories, db.media_clicks
1062
-
1063
- # # DEFAULT (existing behavior)
1064
- # admin_client = MongoClient(os.getenv("ADMIN_MONGODB_URI"))
1065
- # db = admin_client["adminPanel"]
1066
- # return db.categories, db.media_clicks
1067
-
1068
- # # ---------------------------------------------------------------------
1069
- # # Endpoints
1070
- # # ---------------------------------------------------------------------
1071
- # @app.get("/")
1072
- # async def root():
1073
- # """Root endpoint"""
1074
- # return {
1075
- # "success": True,
1076
- # "message": "Polaroid,Kiddo,Makeup,Hairstyle API",
1077
- # "data": {
1078
- # "version": "1.0.1",
1079
- # "Product Name":"Beauty Camera - GlowCam AI Studio",
1080
- # "Released By" : "LogicGo Infotech"
1081
- # }
1082
- # }
1083
-
1084
- # @app.get("/health", response_model=HealthResponse)
1085
- # def health():
1086
- # """Public health check"""
1087
- # mongo.admin.command("ping")
1088
- # return HealthResponse(status="ok", db=db.name, model="Qwen/Qwen-Image-Edit")
1089
-
1090
- # @app.post("/generate")
1091
- # async def generate(
1092
- # prompt: str = Form(...),
1093
- # image1: UploadFile = File(...),
1094
- # image2: Optional[UploadFile] = File(None),
1095
- # user_id: Optional[str] = Form(None),
1096
- # category_id: Optional[str] = Form(None),
1097
- # appname: Optional[str] = Form(None),
1098
- # user=Depends(verify_firebase_token)
1099
- # ):
1100
- # start_time = time.time()
1101
-
1102
- # # -------------------------
1103
- # # 1. VALIDATE & READ IMAGES
1104
- # # -------------------------
1105
- # try:
1106
- # img1_bytes = await image1.read()
1107
- # pil_img1 = prepare_image(img1_bytes)
1108
- # input1_id = generate_image_id()
1109
- # input1_filename = f"{input1_id}_{image1.filename}"
1110
- # input1_url = upload_to_digitalocean(img1_bytes, "source", input1_filename)
1111
- # except Exception as e:
1112
- # raise HTTPException(400, f"Failed to read first image: {e}")
1113
-
1114
- # img2_bytes = None
1115
- # input2_id = None
1116
- # input2_url = None
1117
- # pil_img2 = None
1118
-
1119
- # if image2:
1120
- # try:
1121
- # img2_bytes = await image2.read()
1122
- # pil_img2 = prepare_image(img2_bytes)
1123
- # input2_id = generate_image_id()
1124
- # input2_filename = f"{input2_id}_{image2.filename}"
1125
- # input2_url = upload_to_digitalocean(img2_bytes, "source", input2_filename)
1126
- # except Exception as e:
1127
- # raise HTTPException(400, f"Failed to read second image: {e}")
1128
-
1129
- # # -------------------------
1130
- # # 3. CATEGORY CLICK LOGIC
1131
- # # -------------------------
1132
- # if user_id and category_id:
1133
- # try:
1134
- # admin_client = MongoClient(os.getenv("ADMIN_MONGODB_URI"))
1135
- # admin_db = admin_client["adminPanel"]
1136
-
1137
- # categories_col, media_clicks_col = get_admin_db(appname)
1138
- # # categories_col = admin_db.categories
1139
- # # media_clicks_col = admin_db.media_clicks
1140
-
1141
- # # Validate user_oid & category_oid
1142
- # user_oid = ObjectId(user_id)
1143
- # category_oid = ObjectId(category_id)
1144
-
1145
- # # Check category exists
1146
- # category_doc = categories_col.find_one({"_id": category_oid})
1147
- # if not category_doc:
1148
- # raise HTTPException(400, f"Invalid category_id: {category_id}")
1149
-
1150
- # now = datetime.utcnow()
1151
-
1152
- # # Normalize dates (UTC midnight)
1153
- # today_date = datetime(now.year, now.month, now.day)
1154
- # yesterday_date = today_date - timedelta(days=1)
1155
-
1156
- # # --------------------------------------------------
1157
- # # AI EDIT USAGE TRACKING (GLOBAL PER USER)
1158
- # # --------------------------------------------------
1159
- # media_clicks_col.update_one(
1160
- # {"userId": user_oid},
1161
- # {
1162
- # "$setOnInsert": {
1163
- # "createdAt": now,
1164
- # "ai_edit_daily_count": []
1165
- # },
1166
- # "$set": {
1167
- # "ai_edit_last_date": now,
1168
- # "updatedAt": now
1169
- # },
1170
- # "$inc": {
1171
- # "ai_edit_complete": 1
1172
- # }
1173
- # },
1174
- # upsert=True
1175
- # )
1176
-
1177
- # # --------------------------------------------------
1178
- # # DAILY COUNT LOGIC
1179
- # # --------------------------------------------------
1180
- # now = datetime.utcnow()
1181
- # today_date = datetime(now.year, now.month, now.day)
1182
-
1183
- # doc = media_clicks_col.find_one(
1184
- # {"userId": user_oid},
1185
- # {"ai_edit_daily_count": 1}
1186
- # )
1187
-
1188
- # daily_entries = doc.get("ai_edit_daily_count", []) if doc else []
1189
-
1190
- # # Build UNIQUE date -> count map
1191
- # daily_map = {}
1192
- # for entry in daily_entries:
1193
- # d = entry["date"]
1194
- # d = datetime(d.year, d.month, d.day) if isinstance(d, datetime) else d
1195
- # daily_map[d] = entry["count"] # overwrite = no duplicates
1196
-
1197
- # # Find last known date
1198
- # last_date = max(daily_map.keys()) if daily_map else today_date
1199
-
1200
- # # Fill ALL missing days with 0
1201
- # next_day = last_date + timedelta(days=1)
1202
- # while next_day < today_date:
1203
- # daily_map.setdefault(next_day, 0)
1204
- # next_day += timedelta(days=1)
1205
-
1206
- # # Mark today as used (binary)
1207
- # daily_map[today_date] = 1
1208
-
1209
- # # Rebuild list (OLD → NEW)
1210
- # final_daily_entries = [
1211
- # {"date": d, "count": daily_map[d]}
1212
- # for d in sorted(daily_map.keys())
1213
- # ]
1214
-
1215
- # # Keep last 32 days only
1216
- # final_daily_entries = final_daily_entries[-32:]
1217
-
1218
- # # ATOMIC REPLACE (NO PUSH)
1219
- # media_clicks_col.update_one(
1220
- # {"userId": user_oid},
1221
- # {
1222
- # "$set": {
1223
- # "ai_edit_daily_count": final_daily_entries,
1224
- # "ai_edit_last_date": now,
1225
- # "updatedAt": now
1226
- # }
1227
- # }
1228
- # )
1229
-
1230
-
1231
- # # --------------------------------------------------
1232
- # # CATEGORY CLICK LOGIC
1233
- # # --------------------------------------------------
1234
- # update_res = media_clicks_col.update_one(
1235
- # {"userId": user_oid, "categories.categoryId": category_oid},
1236
- # {
1237
- # "$set": {
1238
- # "updatedAt": now,
1239
- # "categories.$.lastClickedAt": now
1240
- # },
1241
- # "$inc": {
1242
- # "categories.$.click_count": 1
1243
- # }
1244
- # }
1245
- # )
1246
-
1247
- # # If category does not exist → push new
1248
- # if update_res.matched_count == 0:
1249
- # media_clicks_col.update_one(
1250
- # {"userId": user_oid},
1251
- # {
1252
- # "$set": {"updatedAt": now},
1253
- # "$push": {
1254
- # "categories": {
1255
- # "categoryId": category_oid,
1256
- # "click_count": 1,
1257
- # "lastClickedAt": now
1258
- # }
1259
- # }
1260
- # },
1261
- # upsert=True
1262
- # )
1263
-
1264
- # except Exception as e:
1265
- # print("CATEGORY_LOG_ERROR:", e)
1266
- # # -------------------------
1267
- # # 4. HF INFERENCE
1268
- # # -------------------------
1269
- # try:
1270
- # # --------------------------------------------------
1271
- # # MODEL OVERRIDE BASED ON CATEGORY
1272
- # # --------------------------------------------------
1273
- # force_model = None
1274
-
1275
- # if category_id == GEMINI_FORCE_CATEGORY_ID:
1276
- # force_model = "GEMINI"
1277
-
1278
- # pil_output = run_image_generation(
1279
- # image1=pil_img1,
1280
- # image2=pil_img2,
1281
- # prompt=prompt,
1282
- # force_model="GEMINI" if category_id == GEMINI_FORCE_CATEGORY_ID else None
1283
- # )
1284
-
1285
-
1286
- # except Exception as e:
1287
- # response_time_ms = round((time.time() - start_time) * 1000)
1288
- # logs_collection.insert_one({
1289
- # "timestamp": datetime.utcnow(),
1290
- # "status": "failure",
1291
- # "input1_id": input1_id,
1292
- # "input2_id": input2_id if input2_id else None,
1293
- # "prompt": prompt,
1294
- # "user_email": user.get("email"),
1295
- # "error": str(e),
1296
- # "response_time_ms": response_time_ms,
1297
- # "appname": appname
1298
- # })
1299
- # raise HTTPException(500, f"Inference failed: {e}")
1300
-
1301
- # # -------------------------
1302
- # # 5. SAVE OUTPUT IMAGE
1303
- # # -------------------------
1304
- # output_image_id = generate_image_id()
1305
- # out_buf = io.BytesIO()
1306
- # pil_output.save(out_buf, format="PNG")
1307
- # out_bytes = out_buf.getvalue()
1308
-
1309
- # out_filename = f"{output_image_id}_result.png"
1310
- # out_url = upload_to_digitalocean(out_bytes, "results", out_filename)
1311
-
1312
- # # -------------------------
1313
- # # 5b. SAVE COMPRESSED IMAGE
1314
- # # -------------------------
1315
- # compressed_bytes = compress_pil_image_to_2mb(
1316
- # pil_output,
1317
- # max_dim=1280
1318
- # )
1319
-
1320
- # compressed_filename = f"{output_image_id}_compressed.jpg"
1321
- # compressed_url = upload_to_digitalocean(compressed_bytes, "results", compressed_filename)
1322
-
1323
-
1324
- # response_time_ms = round((time.time() - start_time) * 1000)
1325
-
1326
- # # -------------------------
1327
- # # 6. LOG SUCCESS
1328
- # # -------------------------
1329
- # logs_collection.insert_one({
1330
- # "timestamp": datetime.utcnow(),
1331
- # "status": "success",
1332
- # "input1_id": input1_id,
1333
- # "input2_id": input2_id if input2_id else None,
1334
- # "output_id": output_image_id,
1335
- # "prompt": prompt,
1336
- # "user_email": user.get("email"),
1337
- # "response_time_ms": response_time_ms,
1338
- # "appname": appname
1339
- # })
1340
-
1341
- # return JSONResponse({
1342
- # "output_image_id": output_image_id,
1343
- # "user": user.get("email"),
1344
- # "response_time_ms": response_time_ms,
1345
- # "Compressed_Image_URL": compressed_url
1346
- # })
1347
-
1348
-
1349
- # # Image endpoint removed - images are now stored directly in DigitalOcean Spaces
1350
- # # and are publicly accessible via the Compressed_Image_URL returned in the /generate response
1351
-
1352
- # # ---------------------------------------------------------------------
1353
- # # Run locally
1354
- # # ---------------------------------------------------------------------
1355
- # if __name__ == "__main__":
1356
- # import uvicorn
1357
- # uvicorn.run("app:app", host="0.0.0.0", port=7860, reload=True)
1358
- # ################################################################################----------VERSION -1 CODE ----####################################################################
1359
- # import os
1360
- # import io
1361
- # import json
1362
- # import traceback
1363
- # from datetime import datetime,timedelta
1364
- # from typing import Optional
1365
- # import time
1366
- # from fastapi import FastAPI, File, UploadFile, Form, HTTPException, Request, Depends
1367
- # from fastapi.responses import StreamingResponse, JSONResponse
1368
- # from fastapi.middleware.cors import CORSMiddleware
1369
- # from pydantic import BaseModel
1370
- # from pymongo import MongoClient
1371
- # import gridfs
1372
- # from bson.objectid import ObjectId
1373
- # from PIL import Image
1374
- # from fastapi.concurrency import run_in_threadpool
1375
- # import shutil
1376
- # import firebase_admin
1377
- # from firebase_admin import credentials, auth
1378
- # from PIL import Image
1379
- # from huggingface_hub import InferenceClient
1380
- # # ---------------------------------------------------------------------
1381
- # # Load Firebase Config from env (stringified JSON)
1382
- # # ---------------------------------------------------------------------
1383
- # firebase_config_json = os.getenv("firebase_config")
1384
- # if not firebase_config_json:
1385
- # raise RuntimeError("❌ Missing Firebase config in environment variable 'firebase_config'")
1386
-
1387
- # try:
1388
- # firebase_creds_dict = json.loads(firebase_config_json)
1389
- # cred = credentials.Certificate(firebase_creds_dict)
1390
- # firebase_admin.initialize_app(cred)
1391
- # except Exception as e:
1392
- # raise RuntimeError(f"Failed to initialize Firebase Admin SDK: {e}")
1393
-
1394
- # # ---------------------------------------------------------------------
1395
- # # Hugging Face setup
1396
- # # ---------------------------------------------------------------------
1397
- # HF_TOKEN = os.getenv("HF_TOKEN")
1398
- # if not HF_TOKEN:
1399
- # raise RuntimeError("HF_TOKEN not set in environment variables")
1400
-
1401
- # hf_client = InferenceClient(token=HF_TOKEN)
1402
- # # ---------------------------------------------------------------------
1403
- # # MODEL SELECTION
1404
- # # ---------------------------------------------------------------------
1405
- # genai = None # ✅ IMPORTANT: module-level declaration
1406
- # MODEL = os.getenv("IMAGE_MODEL", "GEMINI").upper()
1407
- # GEMINI_FORCE_CATEGORY_ID = "69368e741224bcb6bdb98076"
1408
- # # ---------------------------------------------------------------------
1409
- # # Gemini setup (ONLY used if MODEL == "GEMINI")
1410
- # # ---------------------------------------------------------------------
1411
- # GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
1412
- # GEMINI_IMAGE_MODEL = os.getenv("GEMINI_IMAGE_MODEL", "gemini-2.5-flash-image")
1413
-
1414
- # # ---------------------------------------------------------------------
1415
- # # MongoDB setup
1416
- # # ---------------------------------------------------------------------
1417
- # MONGODB_URI=os.getenv("MONGODB_URI")
1418
- # DB_NAME = "polaroid_db"
1419
-
1420
- # mongo = MongoClient(MONGODB_URI)
1421
- # db = mongo[DB_NAME]
1422
- # fs = gridfs.GridFS(db)
1423
- # logs_collection = db["logs"]
1424
-
1425
- # # ---------------------------------------------------------------------
1426
- # # FastAPI app setup
1427
- # # ---------------------------------------------------------------------
1428
- # app = FastAPI(title="Qwen Image Edit API with Firebase Auth")
1429
- # app.add_middleware(
1430
- # CORSMiddleware,
1431
- # allow_origins=["*"],
1432
- # allow_credentials=True,
1433
- # allow_methods=["*"],
1434
- # allow_headers=["*"],
1435
- # )
1436
-
1437
- # # ---------------------------------------------------------------------
1438
- # # Auth dependency
1439
- # # ---------------------------------------------------------------------
1440
- # async def verify_firebase_token(request: Request):
1441
- # """Middleware-like dependency to verify Firebase JWT from Authorization header."""
1442
- # auth_header = request.headers.get("Authorization")
1443
- # if not auth_header or not auth_header.startswith("Bearer "):
1444
- # raise HTTPException(status_code=401, detail="Missing or invalid Authorization header")
1445
-
1446
- # id_token = auth_header.split("Bearer ")[1]
1447
- # try:
1448
- # decoded_token = auth.verify_id_token(id_token)
1449
- # request.state.user = decoded_token
1450
- # return decoded_token
1451
- # except Exception as e:
1452
- # raise HTTPException(status_code=401, detail=f"Invalid or expired Firebase token: {e}")
1453
-
1454
- # # ---------------------------------------------------------------------
1455
- # # Models
1456
- # # ---------------------------------------------------------------------
1457
- # class HealthResponse(BaseModel):
1458
- # status: str
1459
- # db: str
1460
- # model: str
1461
-
1462
- # # --------------------- UTILS ---------------------
1463
- # def resize_image_if_needed(img: Image.Image, max_size=(1024, 1024)) -> Image.Image:
1464
- # """
1465
- # Resize image to fit within max_size while keeping aspect ratio.
1466
- # """
1467
- # if img.width > max_size[0] or img.height > max_size[1]:
1468
- # img.thumbnail(max_size, Image.ANTIALIAS)
1469
- # return img
1470
- # # ---------------------------------------------------------------------
1471
- # # Lazy Gemini Initialization
1472
- # # ---------------------------------------------------------------------
1473
- # _genai_initialized = False
1474
- # def init_gemini():
1475
- # global _genai_initialized, genai
1476
-
1477
- # if _genai_initialized:
1478
- # return
1479
-
1480
- # if not GEMINI_API_KEY:
1481
- # raise RuntimeError("❌ GEMINI_API_KEY not set")
1482
-
1483
- # import google.generativeai as genai
1484
- # genai.configure(api_key=GEMINI_API_KEY)
1485
-
1486
- # _genai_initialized = True
1487
-
1488
- # def expiry(hours: int):
1489
- # """Return UTC datetime for TTL expiration"""
1490
- # return datetime.utcnow() + timedelta(hours=hours)
1491
-
1492
- # def prepare_image(file_bytes: bytes) -> Image.Image:
1493
- # """
1494
- # Open image and resize if larger than 1024x1024
1495
- # """
1496
- # img = Image.open(io.BytesIO(file_bytes)).convert("RGB")
1497
-
1498
- # # ✅ MIN SIZE CHECK
1499
- # if img.width < 200 or img.height < 200:
1500
- # raise HTTPException(
1501
- # status_code=400,
1502
- # detail="Image size is below 200x200 pixels. Please upload a larger image."
1503
- # )
1504
- # img = Image.open(io.BytesIO(file_bytes)).convert("RGB")
1505
- # img = resize_image_if_needed(img, max_size=(1024, 1024))
1506
- # return img
1507
-
1508
- # MAX_COMPRESSED_SIZE = 2 * 1024 * 1024 # 2 MB
1509
- # def compress_pil_image_to_2mb(
1510
- # pil_img: Image.Image,
1511
- # max_dim: int = 1280
1512
- # ) -> bytes:
1513
- # """
1514
- # Resize + compress PIL image to <= 2MB.
1515
- # Returns JPEG bytes.
1516
- # """
1517
- # img = pil_img.convert("RGB")
1518
-
1519
- # # Resize (maintain aspect ratio)
1520
- # img.thumbnail((max_dim, max_dim), Image.LANCZOS)
1521
-
1522
- # quality = 85
1523
- # buffer = io.BytesIO()
1524
-
1525
- # while quality >= 40:
1526
- # buffer.seek(0)
1527
- # buffer.truncate()
1528
-
1529
- # img.save(
1530
- # buffer,
1531
- # format="JPEG",
1532
- # quality=quality,
1533
- # optimize=True,
1534
- # progressive=True
1535
- # )
1536
-
1537
- # if buffer.tell() <= MAX_COMPRESSED_SIZE:
1538
- # break
1539
-
1540
- # quality -= 5
1541
-
1542
- # return buffer.getvalue()
1543
-
1544
- # def run_image_generation(
1545
- # image1: Image.Image,
1546
- # prompt: str,
1547
- # image2: Optional[Image.Image] = None,
1548
- # force_model: Optional[str] = None
1549
- # ) -> Image.Image:
1550
- # """
1551
- # Unified image generation interface.
1552
- # QWEN -> merges images if image2 exists
1553
- # GEMINI -> passes images separately
1554
- # """
1555
-
1556
- # effective_model = (force_model or MODEL).upper()
1557
-
1558
- # # ---------------- QWEN ----------------
1559
- # if effective_model == "QWEN":
1560
-
1561
- # # ✅ Merge images ONLY for QWEN
1562
- # if image2:
1563
- # total_width = image1.width + image2.width
1564
- # max_height = max(image1.height, image2.height)
1565
- # merged = Image.new("RGB", (total_width, max_height))
1566
- # merged.paste(image1, (0, 0))
1567
- # merged.paste(image2, (image1.width, 0))
1568
- # else:
1569
- # merged = image1
1570
-
1571
- # return hf_client.image_to_image(
1572
- # image=merged,
1573
- # prompt=prompt,
1574
- # model="Qwen/Qwen-Image-Edit"
1575
- # )
1576
-
1577
- # # ---------------- GEMINI ----------------
1578
- # elif effective_model == "GEMINI":
1579
- # init_gemini()
1580
- # model = genai.GenerativeModel(GEMINI_IMAGE_MODEL)
1581
-
1582
- # parts = [prompt]
1583
-
1584
- # def add_image(img: Image.Image):
1585
- # buf = io.BytesIO()
1586
- # img.save(buf, format="PNG")
1587
- # buf.seek(0)
1588
- # parts.append({
1589
- # "mime_type": "image/png",
1590
- # "data": buf.getvalue()
1591
- # })
1592
-
1593
- # add_image(image1)
1594
-
1595
- # if image2:
1596
- # add_image(image2)
1597
-
1598
- # response = model.generate_content(parts)
1599
-
1600
- # # -------- SAFE IMAGE EXTRACTION --------
1601
- # import base64
1602
-
1603
- # image_bytes = None
1604
- # for candidate in response.candidates:
1605
- # for part in candidate.content.parts:
1606
- # if hasattr(part, "inline_data") and part.inline_data:
1607
- # data = part.inline_data.data
1608
- # image_bytes = (
1609
- # data if isinstance(data, (bytes, bytearray))
1610
- # else base64.b64decode(data)
1611
- # )
1612
- # break
1613
- # if image_bytes:
1614
- # break
1615
-
1616
- # if not image_bytes:
1617
- # raise RuntimeError("Gemini did not return an image")
1618
-
1619
- # img = Image.open(io.BytesIO(image_bytes))
1620
- # img.verify()
1621
- # return Image.open(io.BytesIO(image_bytes)).convert("RGB")
1622
-
1623
- # else:
1624
- # raise RuntimeError(f"Unsupported IMAGE_MODEL: {effective_model}")
1625
-
1626
-
1627
-
1628
- # def get_admin_db(appname: Optional[str]):
1629
- # """
1630
- # Returns (categories_collection, media_clicks_collection)
1631
- # based on appname
1632
- # """
1633
- # # ---- Collage Maker ----
1634
- # if appname == "collage-maker":
1635
- # collage_uri = os.getenv("COLLAGE_MAKER_DB_URL")
1636
- # if not collage_uri:
1637
- # raise RuntimeError("COLLAGE_MAKER_DB_URL not set")
1638
-
1639
- # client = MongoClient(collage_uri)
1640
- # db = client["adminPanel"]
1641
- # return db.categories, db.media_clicks
1642
-
1643
- # # ---- AI ENHANCER ----
1644
- # if appname == "AI-Enhancer":
1645
- # enhancer_uri = os.getenv("AI_ENHANCER_DB_URL")
1646
- # if not enhancer_uri:
1647
- # raise RuntimeError("AI_ENHANCER_DB_URL not set")
1648
-
1649
- # client = MongoClient(enhancer_uri)
1650
- # db = client["test"]
1651
- # return db.categories, db.media_clicks
1652
-
1653
- # # DEFAULT (existing behavior)
1654
- # admin_client = MongoClient(os.getenv("ADMIN_MONGODB_URI"))
1655
- # db = admin_client["adminPanel"]
1656
- # return db.categories, db.media_clicks
1657
-
1658
- # # ---------------------------------------------------------------------
1659
- # # Endpoints
1660
- # # ---------------------------------------------------------------------
1661
- # @app.get("/")
1662
- # async def root():
1663
- # """Root endpoint"""
1664
- # return {
1665
- # "success": True,
1666
- # "message": "Polaroid,Kiddo,Makeup,Hairstyle API",
1667
- # "data": {
1668
- # "version": "1.0.0",
1669
- # "Product Name":"Beauty Camera - GlowCam AI Studio",
1670
- # "Released By" : "LogicGo Infotech"
1671
- # }
1672
- # }
1673
-
1674
- # @app.get("/health", response_model=HealthResponse)
1675
- # def health():
1676
- # """Public health check"""
1677
- # mongo.admin.command("ping")
1678
- # return HealthResponse(status="ok", db=db.name, model="Qwen/Qwen-Image-Edit")
1679
-
1680
- # @app.post("/generate")
1681
- # async def generate(
1682
- # prompt: str = Form(...),
1683
- # image1: UploadFile = File(...),
1684
- # image2: Optional[UploadFile] = File(None),
1685
- # user_id: Optional[str] = Form(None),
1686
- # category_id: Optional[str] = Form(None),
1687
- # appname: Optional[str] = Form(None),
1688
- # user=Depends(verify_firebase_token)
1689
- # ):
1690
- # start_time = time.time()
1691
-
1692
- # # -------------------------
1693
- # # 1. VALIDATE & READ IMAGES
1694
- # # -------------------------
1695
- # try:
1696
- # img1_bytes = await image1.read()
1697
- # pil_img1 = prepare_image(img1_bytes)
1698
- # input1_id = fs.put(
1699
- # img1_bytes,
1700
- # filename=image1.filename,
1701
- # contentType=image1.content_type,
1702
- # metadata={"role": "input"},
1703
- # expireAt=expiry(6) # delete after 6 hours
1704
- # )
1705
- # except Exception as e:
1706
- # raise HTTPException(400, f"Failed to read first image: {e}")
1707
-
1708
- # img2_bytes = None
1709
- # input2_id = None
1710
- # pil_img2 = None
1711
-
1712
- # if image2:
1713
- # try:
1714
- # img2_bytes = await image2.read()
1715
- # pil_img2 = prepare_image(img2_bytes)
1716
- # input2_id = fs.put(
1717
- # img2_bytes,
1718
- # filename=image2.filename,
1719
- # contentType=image2.content_type,
1720
- # metadata={"role": "input"},
1721
- # expireAt=expiry(6)
1722
- # )
1723
- # except Exception as e:
1724
- # raise HTTPException(400, f"Failed to read second image: {e}")
1725
-
1726
- # # -------------------------
1727
- # # 3. CATEGORY CLICK LOGIC
1728
- # # -------------------------
1729
- # if user_id and category_id:
1730
- # try:
1731
- # admin_client = MongoClient(os.getenv("ADMIN_MONGODB_URI"))
1732
- # admin_db = admin_client["adminPanel"]
1733
-
1734
- # categories_col, media_clicks_col = get_admin_db(appname)
1735
- # # categories_col = admin_db.categories
1736
- # # media_clicks_col = admin_db.media_clicks
1737
-
1738
- # # Validate user_oid & category_oid
1739
- # user_oid = ObjectId(user_id)
1740
- # category_oid = ObjectId(category_id)
1741
-
1742
- # # Check category exists
1743
- # category_doc = categories_col.find_one({"_id": category_oid})
1744
- # if not category_doc:
1745
- # raise HTTPException(400, f"Invalid category_id: {category_id}")
1746
-
1747
- # now = datetime.utcnow()
1748
-
1749
- # # Normalize dates (UTC midnight)
1750
- # today_date = datetime(now.year, now.month, now.day)
1751
- # yesterday_date = today_date - timedelta(days=1)
1752
-
1753
- # # --------------------------------------------------
1754
- # # AI EDIT USAGE TRACKING (GLOBAL PER USER)
1755
- # # --------------------------------------------------
1756
- # media_clicks_col.update_one(
1757
- # {"userId": user_oid},
1758
- # {
1759
- # "$setOnInsert": {
1760
- # "createdAt": now,
1761
- # "ai_edit_daily_count": []
1762
- # },
1763
- # "$set": {
1764
- # "ai_edit_last_date": now,
1765
- # "updatedAt": now
1766
- # },
1767
- # "$inc": {
1768
- # "ai_edit_complete": 1
1769
- # }
1770
- # },
1771
- # upsert=True
1772
- # )
1773
-
1774
- # # --------------------------------------------------
1775
- # # DAILY COUNT LOGIC
1776
- # # --------------------------------------------------
1777
- # now = datetime.utcnow()
1778
- # today_date = datetime(now.year, now.month, now.day)
1779
-
1780
- # doc = media_clicks_col.find_one(
1781
- # {"userId": user_oid},
1782
- # {"ai_edit_daily_count": 1}
1783
- # )
1784
-
1785
- # daily_entries = doc.get("ai_edit_daily_count", []) if doc else []
1786
-
1787
- # # Build UNIQUE date -> count map
1788
- # daily_map = {}
1789
- # for entry in daily_entries:
1790
- # d = entry["date"]
1791
- # d = datetime(d.year, d.month, d.day) if isinstance(d, datetime) else d
1792
- # daily_map[d] = entry["count"] # overwrite = no duplicates
1793
-
1794
- # # Find last known date
1795
- # last_date = max(daily_map.keys()) if daily_map else today_date
1796
-
1797
- # # Fill ALL missing days with 0
1798
- # next_day = last_date + timedelta(days=1)
1799
- # while next_day < today_date:
1800
- # daily_map.setdefault(next_day, 0)
1801
- # next_day += timedelta(days=1)
1802
-
1803
- # # Mark today as used (binary)
1804
- # daily_map[today_date] = 1
1805
-
1806
- # # Rebuild list (OLD → NEW)
1807
- # final_daily_entries = [
1808
- # {"date": d, "count": daily_map[d]}
1809
- # for d in sorted(daily_map.keys())
1810
- # ]
1811
-
1812
- # # Keep last 32 days only
1813
- # final_daily_entries = final_daily_entries[-32:]
1814
-
1815
- # # ATOMIC REPLACE (NO PUSH)
1816
- # media_clicks_col.update_one(
1817
- # {"userId": user_oid},
1818
- # {
1819
- # "$set": {
1820
- # "ai_edit_daily_count": final_daily_entries,
1821
- # "ai_edit_last_date": now,
1822
- # "updatedAt": now
1823
- # }
1824
- # }
1825
- # )
1826
-
1827
-
1828
- # # --------------------------------------------------
1829
- # # CATEGORY CLICK LOGIC
1830
- # # --------------------------------------------------
1831
- # update_res = media_clicks_col.update_one(
1832
- # {"userId": user_oid, "categories.categoryId": category_oid},
1833
- # {
1834
- # "$set": {
1835
- # "updatedAt": now,
1836
- # "categories.$.lastClickedAt": now
1837
- # },
1838
- # "$inc": {
1839
- # "categories.$.click_count": 1
1840
- # }
1841
- # }
1842
- # )
1843
-
1844
- # # If category does not exist → push new
1845
- # if update_res.matched_count == 0:
1846
- # media_clicks_col.update_one(
1847
- # {"userId": user_oid},
1848
- # {
1849
- # "$set": {"updatedAt": now},
1850
- # "$push": {
1851
- # "categories": {
1852
- # "categoryId": category_oid,
1853
- # "click_count": 1,
1854
- # "lastClickedAt": now
1855
- # }
1856
- # }
1857
- # },
1858
- # upsert=True
1859
- # )
1860
-
1861
- # except Exception as e:
1862
- # print("CATEGORY_LOG_ERROR:", e)
1863
- # # -------------------------
1864
- # # 4. HF INFERENCE
1865
- # # -------------------------
1866
- # try:
1867
- # # --------------------------------------------------
1868
- # # MODEL OVERRIDE BASED ON CATEGORY
1869
- # # --------------------------------------------------
1870
- # force_model = None
1871
-
1872
- # if category_id == GEMINI_FORCE_CATEGORY_ID:
1873
- # force_model = "GEMINI"
1874
-
1875
- # pil_output = run_image_generation(
1876
- # image1=pil_img1,
1877
- # image2=pil_img2,
1878
- # prompt=prompt,
1879
- # force_model="GEMINI" if category_id == GEMINI_FORCE_CATEGORY_ID else None
1880
- # )
1881
-
1882
-
1883
- # except Exception as e:
1884
- # response_time_ms = round((time.time() - start_time) * 1000)
1885
- # logs_collection.insert_one({
1886
- # "timestamp": datetime.utcnow(),
1887
- # "status": "failure",
1888
- # "input1_id": str(input1_id),
1889
- # "input2_id": str(input2_id) if input2_id else None,
1890
- # "prompt": prompt,
1891
- # "user_email": user.get("email"),
1892
- # "error": str(e),
1893
- # "response_time_ms": response_time_ms,
1894
- # "appname": appname
1895
- # })
1896
- # raise HTTPException(500, f"Inference failed: {e}")
1897
-
1898
- # # -------------------------
1899
- # # 5. SAVE OUTPUT IMAGE
1900
- # # -------------------------
1901
- # out_buf = io.BytesIO()
1902
- # pil_output.save(out_buf, format="PNG")
1903
- # out_bytes = out_buf.getvalue()
1904
-
1905
- # out_id = fs.put(
1906
- # out_bytes,
1907
- # filename=f"result_{input1_id}.png",
1908
- # contentType="image/png",
1909
- # metadata={
1910
- # "role": "output",
1911
- # "prompt": prompt,
1912
- # "input1_id": str(input1_id),
1913
- # "input2_id": str(input2_id) if input2_id else None,
1914
- # "user_email": user.get("email"),
1915
- # },
1916
- # expireAt=expiry(24) # 24 hours
1917
- # )
1918
- # # -------------------------
1919
- # # 5b. SAVE COMPRESSED IMAGE
1920
- # # -------------------------
1921
- # compressed_bytes = compress_pil_image_to_2mb(
1922
- # pil_output,
1923
- # max_dim=1280
1924
- # )
1925
-
1926
- # compressed_id = fs.put(
1927
- # compressed_bytes,
1928
- # filename=f"result_{input1_id}_compressed.jpg",
1929
- # contentType="image/jpeg",
1930
- # metadata={
1931
- # "role": "output_compressed",
1932
- # "original_output_id": str(out_id),
1933
- # "prompt": prompt,
1934
- # "user_email": user.get("email")
1935
- # },
1936
- # expireAt=expiry(24) # 24 hours
1937
- # )
1938
-
1939
-
1940
- # response_time_ms = round((time.time() - start_time) * 1000)
1941
-
1942
- # # -------------------------
1943
- # # 6. LOG SUCCESS
1944
- # # -------------------------
1945
- # logs_collection.insert_one({
1946
- # "timestamp": datetime.utcnow(),
1947
- # "status": "success",
1948
- # "input1_id": str(input1_id),
1949
- # "input2_id": str(input2_id) if input2_id else None,
1950
- # "output_id": str(out_id),
1951
- # "prompt": prompt,
1952
- # "user_email": user.get("email"),
1953
- # "response_time_ms": response_time_ms,
1954
- # "appname": appname
1955
- # })
1956
-
1957
- # return JSONResponse({
1958
- # "output_image_id": str(out_id),
1959
- # "user": user.get("email"),
1960
- # "response_time_ms": response_time_ms,
1961
- # "Compressed_Image_URL": (
1962
- # f"https://logicgoinfotechspaces-polaroidimage.hf.space/image/{compressed_id}"
1963
- # )
1964
- # })
1965
-
1966
- # @app.get("/image/{image_id}")
1967
- # def get_image(image_id: str, download: Optional[bool] = False):
1968
- # """Retrieve stored image by ID (no authentication required)."""
1969
- # try:
1970
- # oid = ObjectId(image_id)
1971
- # grid_out = fs.get(oid)
1972
- # except Exception:
1973
- # raise HTTPException(status_code=404, detail="Image not found")
1974
-
1975
- # def iterfile():
1976
- # yield grid_out.read()
1977
-
1978
- # headers = {}
1979
- # if download:
1980
- # headers["Content-Disposition"] = f'attachment; filename="{grid_out.filename}"'
1981
-
1982
- # return StreamingResponse(
1983
- # iterfile(),
1984
- # media_type=grid_out.content_type or "application/octet-stream",
1985
- # headers=headers
1986
- # )
1987
-
1988
- # # ---------------------------------------------------------------------
1989
- # # Run locally
1990
- # # ---------------------------------------------------------------------
1991
- # if __name__ == "__main__":
1992
- # import uvicorn
1993
- # uvicorn.run("app:app", host="0.0.0.0", port=7860, reload=True)
1994
-
 
1
  import os
2
  import io
3
  import json
4
+ import mimetypes
5
  import traceback
6
  from datetime import datetime,timedelta
7
  from typing import Optional
 
12
  from fastapi.responses import StreamingResponse, JSONResponse
13
  from fastapi.middleware.cors import CORSMiddleware
14
  from pydantic import BaseModel
15
+ from pymongo import MongoClient, ReturnDocument
16
  import gridfs
17
  from gridfs.errors import NoFile
18
  from bson.objectid import ObjectId
 
77
  mongo = MongoClient(MONGODB_URI)
78
  db = mongo[DB_NAME]
79
  fs = gridfs.GridFS(db)
80
+ counters_collection = db["counters"]
81
 
82
  # Separate Logs DB
83
  logs_client = None
 
198
  Upload image to DigitalOcean Spaces and return the public URL.
199
  folder: 'source' or 'results'
200
  """
201
+ content_type = mimetypes.guess_type(filename)[0] or "application/octet-stream"
202
+ key = f"valentine/polaroid/{folder}/{filename}"
203
  try:
204
  s3_client.put_object(
205
  Bucket=DO_SPACES_BUCKET,
206
  Key=key,
207
  Body=image_bytes,
208
+ ContentType=content_type,
209
  ACL='public-read'
210
  )
211
  url = f"{DO_SPACES_ENDPOINT}/{DO_SPACES_BUCKET}/{key}"
 
213
  except Exception as e:
214
  raise RuntimeError(f"Failed to upload to DigitalOcean Spaces: {e}")
215
 
216
+ def get_file_extension(filename: Optional[str], default_ext: str) -> str:
217
+ """
218
+ Return a safe file extension with a leading dot.
219
+ """
220
+ if filename:
221
+ _, ext = os.path.splitext(filename)
222
+ if ext:
223
+ return ext.lower()
224
+ return default_ext
225
+
226
+ def get_next_polaroid_sequence() -> int:
227
+ """
228
+ Atomically allocate the next shared sequence number for source/result assets.
229
+ """
230
+ try:
231
+ counter_doc = counters_collection.find_one_and_update(
232
+ {"_id": "polaroid_sequence"},
233
+ {
234
+ "$inc": {"value": 1},
235
+ "$setOnInsert": {"created_at": datetime.utcnow()}
236
+ },
237
+ upsert=True,
238
+ return_document=ReturnDocument.AFTER
239
+ )
240
+ except Exception as e:
241
+ raise RuntimeError(f"Failed to determine next Polaroid sequence: {e}")
242
+
243
+ return int(counter_doc["value"])
244
+
245
  def generate_image_id() -> str:
246
  """
247
  Generate a random image ID (same format as MongoDB ObjectId for consistency)
 
431
  user=Depends(verify_firebase_token)
432
  ):
433
  start_time = time.time()
434
+ sequence_number = None
435
+
436
+ try:
437
+ sequence_number = get_next_polaroid_sequence()
438
+ except Exception as e:
439
+ raise HTTPException(500, f"Failed to allocate Polaroid file sequence: {e}")
440
 
441
  # -------------------------
442
  # 1. VALIDATE & READ IMAGES
 
445
  img1_bytes = await image1.read()
446
  pil_img1 = prepare_image(img1_bytes)
447
  input1_id = generate_image_id()
448
+ image1_ext = get_file_extension(image1.filename, ".jpg")
449
+ input1_filename = f"source_{sequence_number}{image1_ext}"
450
  input1_url = upload_to_digitalocean(img1_bytes, "source", input1_filename)
451
  except Exception as e:
452
  raise HTTPException(400, f"Failed to read first image: {e}")
 
461
  img2_bytes = await image2.read()
462
  pil_img2 = prepare_image(img2_bytes)
463
  input2_id = generate_image_id()
464
+ image2_ext = get_file_extension(image2.filename, ".jpg")
465
+ input1_filename = f"source_{sequence_number}_a{image1_ext}"
466
+ input2_filename = f"source_{sequence_number}_b{image2_ext}"
467
+ input1_url = upload_to_digitalocean(img1_bytes, "source", input1_filename)
468
  input2_url = upload_to_digitalocean(img2_bytes, "source", input2_filename)
469
  except Exception as e:
470
  raise HTTPException(400, f"Failed to read second image: {e}")
 
659
  pil_output.save(out_buf, format="PNG")
660
  out_bytes = out_buf.getvalue()
661
 
662
+ out_filename = f"result_{sequence_number}.png"
663
  out_url = upload_to_digitalocean(out_bytes, "results", out_filename)
664
  try:
665
  fs.put(
 
684
  max_dim=1280
685
  )
686
 
687
+ compressed_filename = f"result_{sequence_number}_compressed.jpg"
688
  compressed_url = upload_to_digitalocean(compressed_bytes, "results", compressed_filename)
689
 
690
 
691
  response_time_ms = round((time.time() - start_time) * 1000)
692
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
693
  if logs_collection is not None:
694
  logs_collection.insert_one({
695
  "endpoint": "/generate",
 
742
  import uvicorn
743
  uvicorn.run("app:app", host="0.0.0.0", port=7860, reload=True)
744