HariLogicgo commited on
Commit
3c79ccf
Β·
1 Parent(s): 15642e3

images accesed through digital ocean

Browse files
Files changed (2) hide show
  1. app.py +100 -88
  2. requirements.txt +1 -1
app.py CHANGED
@@ -17,13 +17,17 @@ from fastapi import FastAPI, UploadFile, File, HTTPException, Response, Depends,
17
  from fastapi.responses import RedirectResponse
18
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
19
  from pydantic import BaseModel
20
- from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorGridFSBucket
21
- from bson import ObjectId
22
 
23
  import uvicorn
24
  import gradio as gr
25
  from gradio import mount_gradio_app
26
 
 
 
 
 
 
27
  # --------------------- Logging ---------------------
28
  logging.basicConfig(level=logging.INFO)
29
  logger = logging.getLogger(__name__)
@@ -31,18 +35,21 @@ logger = logging.getLogger(__name__)
31
  # --------------------- Paths -----------------------
32
  REPO_ID = "HariLogicgo/face_swap_models"
33
  BASE_DIR = "./workspace"
34
- UPLOAD_DIR = os.path.join(BASE_DIR, "uploads")
35
- RESULT_DIR = os.path.join(BASE_DIR, "results")
36
  MODELS_DIR = "./models"
37
 
38
- os.makedirs(UPLOAD_DIR, exist_ok=True)
39
- os.makedirs(RESULT_DIR, exist_ok=True)
40
  os.makedirs(MODELS_DIR, exist_ok=True)
41
 
42
  # --------------------- Secrets ---------------------
43
  HF_TOKEN = os.getenv("HF_TOKEN") # Hugging Face private repo token
44
  API_SECRET_TOKEN = os.getenv("API_SECRET_TOKEN") # Bearer token for API
45
 
 
 
 
 
 
 
 
46
  # --------------------- Download Models ---------------------
47
  def download_models():
48
  logger.info("Downloading models from private HF repo...")
@@ -93,24 +100,22 @@ def ensure_codeformer():
93
 
94
  ensure_codeformer()
95
 
96
- # --------------------- MongoDB ---------------------
97
  MONGODB_URL = os.getenv("MONGODB_URL")
98
 
99
  client: AsyncIOMotorClient = None
100
  database = None
101
- fs_bucket: AsyncIOMotorGridFSBucket = None
102
 
103
  # --------------------- FastAPI ---------------------
104
  fastapi_app = FastAPI()
105
 
106
  @fastapi_app.on_event("startup")
107
  async def startup_db():
108
- global client, database, fs_bucket
109
- logger.info("Initializing MongoDB + GridFS...")
110
  client = AsyncIOMotorClient(MONGODB_URL)
111
  database = client.FaceSwap
112
- fs_bucket = AsyncIOMotorGridFSBucket(database)
113
- logger.info("MongoDB + GridFS initialized")
114
 
115
  @fastapi_app.on_event("shutdown")
116
  async def shutdown_db():
@@ -130,7 +135,7 @@ def verify_token(credentials: HTTPAuthorizationCredentials = Security(security))
130
  # --------------------- Logging API Hits ---------------------
131
  async def log_faceswap_hit(token: str, status: str = "success"):
132
  global database
133
- if database is None: # βœ… FIXED: explicit None check
134
  return
135
  await database.api_logs.insert_one({
136
  "token": token,
@@ -142,13 +147,13 @@ async def log_faceswap_hit(token: str, status: str = "success"):
142
  # --------------------- Face Swap Pipeline ---------------------
143
  swap_lock = threading.Lock()
144
 
145
- def face_swap_and_enhance(src_img, tgt_img):
146
  try:
147
  with swap_lock:
148
- shutil.rmtree(UPLOAD_DIR, ignore_errors=True)
149
- shutil.rmtree(RESULT_DIR, ignore_errors=True)
150
- os.makedirs(UPLOAD_DIR, exist_ok=True)
151
- os.makedirs(RESULT_DIR, exist_ok=True)
152
 
153
  src_bgr = cv2.cvtColor(src_img, cv2.COLOR_RGB2BGR)
154
  tgt_bgr = cv2.cvtColor(tgt_img, cv2.COLOR_RGB2BGR)
@@ -158,19 +163,19 @@ def face_swap_and_enhance(src_img, tgt_img):
158
  if not src_faces or not tgt_faces:
159
  return None, None, "❌ Face not detected in one of the images"
160
 
161
- swapped_path = os.path.join(UPLOAD_DIR, f"swapped_{uuid.uuid4().hex[:8]}.jpg")
162
  swapped_bgr = swapper.get(tgt_bgr, tgt_faces[0], src_faces[0])
163
  if swapped_bgr is None:
164
  return None, None, "❌ Face swap failed"
165
 
166
  cv2.imwrite(swapped_path, swapped_bgr)
167
 
168
- cmd = f"python {CODEFORMER_PATH} -w 0.7 --input_path {swapped_path} --output_path {RESULT_DIR} --bg_upsampler realesrgan --face_upsample"
169
  result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
170
  if result.returncode != 0:
171
  return None, None, f"❌ CodeFormer failed:\n{result.stderr}"
172
 
173
- final_results_dir = os.path.join(RESULT_DIR, "final_results")
174
  final_files = [f for f in os.listdir(final_results_dir) if f.endswith(".png")]
175
  if not final_files:
176
  return None, None, "❌ No enhanced image found"
@@ -202,6 +207,29 @@ with gr.Blocks() as demo:
202
 
203
  btn.click(process, [src_input, tgt_input], [output_img, download, error_box])
204
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  # --------------------- API Endpoints ---------------------
206
  @fastapi_app.get("/")
207
  def root():
@@ -211,82 +239,66 @@ def root():
211
  async def health():
212
  return {"status": "healthy"}
213
 
214
- @fastapi_app.post("/source", dependencies=[Depends(verify_token)])
215
- async def upload_source(image: UploadFile = File(...)):
216
- contents = await image.read()
217
- file_id = await fs_bucket.upload_from_stream(image.filename, contents, metadata={"type": "source"})
218
- return {"source_id": str(file_id)}
219
-
220
- @fastapi_app.get("/targets", dependencies=[Depends(verify_token)])
221
- async def list_targets():
222
- files = []
223
- async for file in database.fs.files.find({"metadata.type": "target", "metadata.predefined": True}):
224
- files.append({
225
- "id": str(file["_id"]),
226
- "filename": file["filename"]
227
- })
228
- return {"targets": files}
229
-
230
- @fastapi_app.post("/target", dependencies=[Depends(verify_token)])
231
- async def upload_target(image: UploadFile = File(...)):
232
- contents = await image.read()
233
- file_id = await fs_bucket.upload_from_stream(image.filename, contents, metadata={"type": "target"})
234
- return {"target_id": str(file_id)}
235
-
236
- class FaceSwapRequest(BaseModel):
237
- source_id: str
238
- target_id: str
239
-
240
- @fastapi_app.post("/faceswap", dependencies=[Depends(verify_token)])
241
- async def perform_faceswap(request: FaceSwapRequest, credentials: HTTPAuthorizationCredentials = Security(security)):
242
  try:
243
- src_stream = await fs_bucket.open_download_stream(ObjectId(request.source_id))
244
- src_bytes = await src_stream.read()
245
- tgt_stream = await fs_bucket.open_download_stream(ObjectId(request.target_id))
246
- tgt_bytes = await tgt_stream.read()
247
- except Exception:
248
- await log_faceswap_hit(credentials.credentials, status="error")
249
- raise HTTPException(status_code=404, detail="Source or Target not found")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
 
251
- src_array = np.frombuffer(src_bytes, np.uint8)
252
- src_bgr = cv2.imdecode(src_array, cv2.IMREAD_COLOR)
253
- tgt_array = np.frombuffer(tgt_bytes, np.uint8)
254
- tgt_bgr = cv2.imdecode(tgt_array, cv2.IMREAD_COLOR)
255
-
256
- if src_bgr is None or tgt_bgr is None:
257
- await log_faceswap_hit(credentials.credentials, status="error")
258
- raise HTTPException(status_code=400, detail="Invalid image data")
259
-
260
- src_rgb = cv2.cvtColor(src_bgr, cv2.COLOR_BGR2RGB)
261
- tgt_rgb = cv2.cvtColor(tgt_bgr, cv2.COLOR_BGR2RGB)
262
-
263
- final_img, final_path, err = face_swap_and_enhance(src_rgb, tgt_rgb)
264
- if err:
265
  await log_faceswap_hit(credentials.credentials, status="error")
266
- raise HTTPException(status_code=500, detail=err)
267
-
268
- with open(final_path, "rb") as f:
269
- final_bytes = f.read()
270
 
271
- result_id = await fs_bucket.upload_from_stream("enhanced.png", final_bytes, metadata={"type": "result"})
272
-
273
- # βœ… Log success
274
- await log_faceswap_hit(credentials.credentials, status="success")
275
-
276
- return {"result_id": str(result_id)}
277
-
278
- @fastapi_app.get("/download/{result_id}", dependencies=[Depends(verify_token)])
279
- async def download_result(result_id: str):
280
  try:
281
- stream = await fs_bucket.open_download_stream(ObjectId(result_id))
282
- data = await stream.read()
283
  except Exception:
284
  raise HTTPException(status_code=404, detail="Result not found")
285
-
286
  return Response(
287
- content=data,
288
  media_type="image/png",
289
- headers={"Content-Disposition": "attachment; filename=result.png"}
290
  )
291
 
292
  # --------------------- Mount Gradio ---------------------
 
17
  from fastapi.responses import RedirectResponse
18
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
19
  from pydantic import BaseModel
20
+ from motor.motor_asyncio import AsyncIOMotorClient
 
21
 
22
  import uvicorn
23
  import gradio as gr
24
  from gradio import mount_gradio_app
25
 
26
+ # DigitalOcean Spaces
27
+ import boto3
28
+ from botocore.client import Config
29
+ from io import BytesIO
30
+ from typing import Optional
31
  # --------------------- Logging ---------------------
32
  logging.basicConfig(level=logging.INFO)
33
  logger = logging.getLogger(__name__)
 
35
  # --------------------- Paths -----------------------
36
  REPO_ID = "HariLogicgo/face_swap_models"
37
  BASE_DIR = "./workspace"
 
 
38
  MODELS_DIR = "./models"
39
 
 
 
40
  os.makedirs(MODELS_DIR, exist_ok=True)
41
 
42
  # --------------------- Secrets ---------------------
43
  HF_TOKEN = os.getenv("HF_TOKEN") # Hugging Face private repo token
44
  API_SECRET_TOKEN = os.getenv("API_SECRET_TOKEN") # Bearer token for API
45
 
46
+ # DigitalOcean Spaces config
47
+ DO_SPACES_KEY = os.getenv("DO_SPACES_KEY")
48
+ DO_SPACES_SECRET = os.getenv("DO_SPACES_SECRET")
49
+ DO_SPACES_REGION = os.getenv("DO_SPACES_REGION")
50
+ DO_SPACES_ENDPOINT = os.getenv("DO_SPACES_ENDPOINT", f"https://{DO_SPACES_REGION}.digitaloceanspaces.com")
51
+ DO_SPACES_BUCKET = os.getenv("DO_SPACES_BUCKET")
52
+
53
  # --------------------- Download Models ---------------------
54
  def download_models():
55
  logger.info("Downloading models from private HF repo...")
 
100
 
101
  ensure_codeformer()
102
 
103
+ # --------------------- MongoDB (for API logs only) ---------------------
104
  MONGODB_URL = os.getenv("MONGODB_URL")
105
 
106
  client: AsyncIOMotorClient = None
107
  database = None
 
108
 
109
  # --------------------- FastAPI ---------------------
110
  fastapi_app = FastAPI()
111
 
112
  @fastapi_app.on_event("startup")
113
  async def startup_db():
114
+ global client, database
115
+ logger.info("Initializing MongoDB for API logs...")
116
  client = AsyncIOMotorClient(MONGODB_URL)
117
  database = client.FaceSwap
118
+ logger.info("MongoDB initialized for API logs")
 
119
 
120
  @fastapi_app.on_event("shutdown")
121
  async def shutdown_db():
 
135
  # --------------------- Logging API Hits ---------------------
136
  async def log_faceswap_hit(token: str, status: str = "success"):
137
  global database
138
+ if database is None:
139
  return
140
  await database.api_logs.insert_one({
141
  "token": token,
 
147
  # --------------------- Face Swap Pipeline ---------------------
148
  swap_lock = threading.Lock()
149
 
150
+ def face_swap_and_enhance(src_img, tgt_img, temp_dir="/tmp/faceswap_work"):
151
  try:
152
  with swap_lock:
153
+ # Use a temp dir for intermediate files
154
+ if os.path.exists(temp_dir):
155
+ shutil.rmtree(temp_dir)
156
+ os.makedirs(temp_dir, exist_ok=True)
157
 
158
  src_bgr = cv2.cvtColor(src_img, cv2.COLOR_RGB2BGR)
159
  tgt_bgr = cv2.cvtColor(tgt_img, cv2.COLOR_RGB2BGR)
 
163
  if not src_faces or not tgt_faces:
164
  return None, None, "❌ Face not detected in one of the images"
165
 
166
+ swapped_path = os.path.join(temp_dir, f"swapped_{uuid.uuid4().hex[:8]}.jpg")
167
  swapped_bgr = swapper.get(tgt_bgr, tgt_faces[0], src_faces[0])
168
  if swapped_bgr is None:
169
  return None, None, "❌ Face swap failed"
170
 
171
  cv2.imwrite(swapped_path, swapped_bgr)
172
 
173
+ cmd = f"python {CODEFORMER_PATH} -w 0.7 --input_path {swapped_path} --output_path {temp_dir} --bg_upsampler realesrgan --face_upsample"
174
  result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
175
  if result.returncode != 0:
176
  return None, None, f"❌ CodeFormer failed:\n{result.stderr}"
177
 
178
+ final_results_dir = os.path.join(temp_dir, "final_results")
179
  final_files = [f for f in os.listdir(final_results_dir) if f.endswith(".png")]
180
  if not final_files:
181
  return None, None, "❌ No enhanced image found"
 
207
 
208
  btn.click(process, [src_input, tgt_input], [output_img, download, error_box])
209
 
210
+ # --------------------- DigitalOcean Spaces Helper ---------------------
211
+ def get_spaces_client():
212
+ session = boto3.session.Session()
213
+ client = session.client(
214
+ 's3',
215
+ region_name=DO_SPACES_REGION,
216
+ endpoint_url=DO_SPACES_ENDPOINT,
217
+ aws_access_key_id=DO_SPACES_KEY,
218
+ aws_secret_access_key=DO_SPACES_SECRET,
219
+ config=Config(signature_version='s3v4')
220
+ )
221
+ return client
222
+
223
+ def upload_to_spaces(file_bytes, key, content_type="image/png"):
224
+ client = get_spaces_client()
225
+ client.put_object(Bucket=DO_SPACES_BUCKET, Key=key, Body=file_bytes, ContentType=content_type, ACL='public-read')
226
+ return f"{DO_SPACES_ENDPOINT}/{DO_SPACES_BUCKET}/{key}"
227
+
228
+ def download_from_spaces(key):
229
+ client = get_spaces_client()
230
+ obj = client.get_object(Bucket=DO_SPACES_BUCKET, Key=key)
231
+ return obj['Body'].read()
232
+
233
  # --------------------- API Endpoints ---------------------
234
  @fastapi_app.get("/")
235
  def root():
 
239
  async def health():
240
  return {"status": "healthy"}
241
 
242
+ @fastapi_app.post("/face-swap", dependencies=[Depends(verify_token)])
243
+ async def face_swap_api(
244
+ source: UploadFile = File(...),
245
+ target: UploadFile = File(...),
246
+ credentials: HTTPAuthorizationCredentials = Security(security)
247
+ ):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
  try:
249
+ # Read images
250
+ src_bytes = await source.read()
251
+ tgt_bytes = await target.read()
252
+
253
+ # Save to Spaces (source/target)
254
+ src_key = f"faceswap/source/{uuid.uuid4().hex}_{source.filename}"
255
+ tgt_key = f"faceswap/target/{uuid.uuid4().hex}_{target.filename}"
256
+ upload_to_spaces(src_bytes, src_key, content_type=source.content_type)
257
+ upload_to_spaces(tgt_bytes, tgt_key, content_type=target.content_type)
258
+
259
+ # Decode for processing
260
+ src_array = np.frombuffer(src_bytes, np.uint8)
261
+ tgt_array = np.frombuffer(tgt_bytes, np.uint8)
262
+ src_bgr = cv2.imdecode(src_array, cv2.IMREAD_COLOR)
263
+ tgt_bgr = cv2.imdecode(tgt_array, cv2.IMREAD_COLOR)
264
+ if src_bgr is None or tgt_bgr is None:
265
+ await log_faceswap_hit(credentials.credentials, status="error")
266
+ raise HTTPException(status_code=400, detail="Invalid image data")
267
+
268
+ src_rgb = cv2.cvtColor(src_bgr, cv2.COLOR_BGR2RGB)
269
+ tgt_rgb = cv2.cvtColor(tgt_bgr, cv2.COLOR_BGR2RGB)
270
+
271
+ # Run face swap pipeline
272
+ final_img, final_path, err = face_swap_and_enhance(src_rgb, tgt_rgb)
273
+ if err:
274
+ await log_faceswap_hit(credentials.credentials, status="error")
275
+ raise HTTPException(status_code=500, detail=err)
276
+
277
+ # Upload result to Spaces
278
+ with open(final_path, "rb") as f:
279
+ result_bytes = f.read()
280
+ result_key = f"faceswap/result/{uuid.uuid4().hex}_enhanced.png"
281
+ result_url = upload_to_spaces(result_bytes, result_key, content_type="image/png")
282
+
283
+ # Log success
284
+ await log_faceswap_hit(credentials.credentials, status="success")
285
+
286
+ return {"result_key": result_key, "result_url": result_url}
287
 
288
+ except Exception as e:
 
 
 
 
 
 
 
 
 
 
 
 
 
289
  await log_faceswap_hit(credentials.credentials, status="error")
290
+ raise HTTPException(status_code=500, detail=f"Face swap failed: {str(e)}")
 
 
 
291
 
292
+ @fastapi_app.get("/preview/{result_key:path}")
293
+ async def preview_result(result_key: str):
 
 
 
 
 
 
 
294
  try:
295
+ img_bytes = download_from_spaces(result_key)
 
296
  except Exception:
297
  raise HTTPException(status_code=404, detail="Result not found")
 
298
  return Response(
299
+ content=img_bytes,
300
  media_type="image/png",
301
+ headers={"Content-Disposition": "inline; filename=result.png"}
302
  )
303
 
304
  # --------------------- Mount Gradio ---------------------
requirements.txt CHANGED
@@ -35,6 +35,6 @@ huggingface_hub
35
  fastapi
36
  uvicorn
37
  motor
38
-
39
  # Optional but included in CodeFormer repo
40
  tb-nightly
 
35
  fastapi
36
  uvicorn
37
  motor
38
+ boto3
39
  # Optional but included in CodeFormer repo
40
  tb-nightly