Commit ·
e371999
1
Parent(s): 603d886
Add collage-maker appname support with MongoDB integration
Browse files- Add appname parameter to InpaintRequest and all inpaint endpoints
- Add MongoDB helper functions for collage-maker database
- Auto-fetch category_id from collage-maker when appname=collage-maker
- Store media clicks in collage-maker MongoDB when appname=collage-maker
- Store appname in api_logs collection
- Add environment variables: MONGODB_COLLAGE_MAKER, MONGODB_COLLAGE_MAKER_DB_NAME, MONGODB_COLLAGE_MAKER_ADMIN_DB_NAME
- Update all endpoints: /inpaint, /inpaint-url, /inpaint-multipart, /remove-pink
- API_Usage_Guide.md +0 -221
- api/main.py +163 -17
API_Usage_Guide.md
DELETED
|
@@ -1,221 +0,0 @@
|
|
| 1 |
-
# API Usage Guide - Photo Object Removal
|
| 2 |
-
|
| 3 |
-
## Overview
|
| 4 |
-
This guide provides step-by-step instructions for using the Photo Object Removal API to remove objects from images using AI inpainting.
|
| 5 |
-
|
| 6 |
-
**Base URL:** `https://logicgoinfotechspaces-object-remover.hf.space`
|
| 7 |
-
**Authentication:** Bearer token (optional)
|
| 8 |
-
**Storage:** Uploaded images/masks are saved in MongoDB GridFS (database `object_remover`); IDs returned by upload endpoints are pulled from GridFS before sending to Gemini.
|
| 9 |
-
- Processing is delegated to Google Gemini/Imagen edit API; only lightweight CPU work (mask prep, file IO) happens on this server.
|
| 10 |
-
|
| 11 |
-
## Quick Start
|
| 12 |
-
|
| 13 |
-
### 1. Health Check
|
| 14 |
-
Verify the API is running:
|
| 15 |
-
```bash
|
| 16 |
-
curl -H "Authorization: Bearer <API_TOKEN>" \
|
| 17 |
-
https://logicgoinfotechspaces-object-remover.hf.space/health
|
| 18 |
-
```
|
| 19 |
-
**Response:** `{"status":"healthy"}`
|
| 20 |
-
|
| 21 |
-
### 2. Upload Image
|
| 22 |
-
Upload the image you want to edit:
|
| 23 |
-
```bash
|
| 24 |
-
curl -H "Authorization: Bearer <API_TOKEN>" \
|
| 25 |
-
-F image=@your_image.jpg \
|
| 26 |
-
https://logicgoinfotechspaces-object-remover.hf.space/upload-image
|
| 27 |
-
```
|
| 28 |
-
**Response:** `{"id":"9cf61445-f83b-4c97-9272-c81647f90d68","filename":"your_image.jpg"}`
|
| 29 |
-
|
| 30 |
-
### 3. Upload Mask
|
| 31 |
-
Upload a mask showing areas to remove (white/colored areas = remove):
|
| 32 |
-
```bash
|
| 33 |
-
curl -H "Authorization: Bearer <API_TOKEN>" \
|
| 34 |
-
-F mask=@mask.png \
|
| 35 |
-
https://logicgoinfotechspaces-object-remover.hf.space/upload-mask
|
| 36 |
-
```
|
| 37 |
-
**Response:** `{"id":"d044a390-dde2-408a-b7cf-d508385e56ed","filename":"mask.png"}`
|
| 38 |
-
|
| 39 |
-
### 4. Process Image
|
| 40 |
-
Remove objects using the uploaded image and mask:
|
| 41 |
-
```bash
|
| 42 |
-
curl -H "Authorization: Bearer <API_TOKEN>" \
|
| 43 |
-
-H "Content-Type: application/json" \
|
| 44 |
-
-d '{"image_id":"9cf61445-f83b-4c97-9272-c81647f90d68","mask_id":"d044a390-dde2-408a-b7cf-d508385e56ed","prompt":"Describe what should be removed"}' \
|
| 45 |
-
https://logicgoinfotechspaces-object-remover.hf.space/inpaint
|
| 46 |
-
```
|
| 47 |
-
**Response:** `{"result":"output_b09568698bbd4aa591b1598c01f2f745.png"}`
|
| 48 |
-
|
| 49 |
-
### 5. View Result
|
| 50 |
-
Open the result image in your browser:
|
| 51 |
-
```
|
| 52 |
-
https://logicgoinfotechspaces-object-remover.hf.space/result/output_b09568698bbd4aa591b1598c01f2f745.png
|
| 53 |
-
```
|
| 54 |
-
|
| 55 |
-
## Alternative Methods
|
| 56 |
-
|
| 57 |
-
### Method 1: Get Direct URL
|
| 58 |
-
Use `/inpaint-url` to get a shareable URL:
|
| 59 |
-
```bash
|
| 60 |
-
curl -H "Authorization: Bearer <API_TOKEN>" \
|
| 61 |
-
-H "Content-Type: application/json" \
|
| 62 |
-
-d '{"image_id":"<image_id>","mask_id":"<mask_id>","prompt":"Describe what should be removed"}' \
|
| 63 |
-
https://logicgoinfotechspaces-object-remover.hf.space/inpaint-url
|
| 64 |
-
```
|
| 65 |
-
**Response:**
|
| 66 |
-
```json
|
| 67 |
-
{
|
| 68 |
-
"result":"output_xxx.png",
|
| 69 |
-
"url":"https://logicgoinfotechspaces-object-remover.hf.space/download/output_xxx.png"
|
| 70 |
-
}
|
| 71 |
-
```
|
| 72 |
-
|
| 73 |
-
### Method 2: One-Step Processing
|
| 74 |
-
Upload and process in a single request:
|
| 75 |
-
```bash
|
| 76 |
-
curl -H "Authorization: Bearer <API_TOKEN>" \
|
| 77 |
-
-F image=@image.jpg \
|
| 78 |
-
-F mask=@mask.jpg \
|
| 79 |
-
-F prompt="Describe what should be removed" \
|
| 80 |
-
https://logicgoinfotechspaces-object-remover.hf.space/inpaint-multipart
|
| 81 |
-
```
|
| 82 |
-
|
| 83 |
-
### Method 3: Download Result
|
| 84 |
-
Download the result file:
|
| 85 |
-
```bash
|
| 86 |
-
curl -L https://logicgoinfotechspaces-object-remover.hf.space/download/output_xxx.png \
|
| 87 |
-
-o result.png
|
| 88 |
-
```
|
| 89 |
-
|
| 90 |
-
## Postman Setup
|
| 91 |
-
|
| 92 |
-
### 1. Upload Image
|
| 93 |
-
- **Method:** POST
|
| 94 |
-
- **URL:** `https://logicgoinfotechspaces-object-remover.hf.space/upload-image`
|
| 95 |
-
- **Headers:** `Authorization: Bearer <API_TOKEN>`
|
| 96 |
-
- **Body:** form-data
|
| 97 |
-
- Key: `image` (Type: File)
|
| 98 |
-
- Value: Select your image file
|
| 99 |
-
|
| 100 |
-
### 2. Upload Mask
|
| 101 |
-
- **Method:** POST
|
| 102 |
-
- **URL:** `https://logicgoinfotechspaces-object-remover.hf.space/upload-mask`
|
| 103 |
-
- **Headers:** `Authorization: Bearer <API_TOKEN>`
|
| 104 |
-
- **Body:** form-data
|
| 105 |
-
- Key: `mask` (Type: File)
|
| 106 |
-
- Value: Select your mask file
|
| 107 |
-
|
| 108 |
-
### 3. Process Image
|
| 109 |
-
- **Method:** POST
|
| 110 |
-
- **URL:** `https://logicgoinfotechspaces-object-remover.hf.space/inpaint`
|
| 111 |
-
- **Headers:**
|
| 112 |
-
- `Authorization: Bearer <API_TOKEN>`
|
| 113 |
-
- `Content-Type: application/json`
|
| 114 |
-
- **Body:** raw JSON
|
| 115 |
-
```json
|
| 116 |
-
{
|
| 117 |
-
"image_id": "9cf61445-f83b-4c97-9272-c81647f90d68",
|
| 118 |
-
"mask_id": "d044a390-dde2-408a-b7cf-d508385e56ed",
|
| 119 |
-
"prompt": "Describe what should be removed"
|
| 120 |
-
}
|
| 121 |
-
```
|
| 122 |
-
|
| 123 |
-
## Mask Creation Guide
|
| 124 |
-
|
| 125 |
-
### Mask Formats
|
| 126 |
-
- **RGBA PNG:** Pixels with alpha=0 are treated as areas to remove
|
| 127 |
-
- **RGB/Grayscale:** Pixels with value > 0 are treated as areas to remove
|
| 128 |
-
|
| 129 |
-
### Creating Masks
|
| 130 |
-
1. **Paint Method:** Use any image editor to paint white/colored areas over objects you want to remove
|
| 131 |
-
2. **Transparency Method:** Create a PNG with transparent areas where you want objects removed
|
| 132 |
-
3. **Grayscale Method:** Create a black image and paint white areas over objects to remove
|
| 133 |
-
|
| 134 |
-
### Tips
|
| 135 |
-
- Use high contrast (white on black) for best results
|
| 136 |
-
- Make sure mask areas are clearly defined
|
| 137 |
-
- Avoid thin lines or small details in the mask
|
| 138 |
-
|
| 139 |
-
## Error Handling
|
| 140 |
-
|
| 141 |
-
### Common Errors
|
| 142 |
-
|
| 143 |
-
**422 Unprocessable Entity**
|
| 144 |
-
- **Cause:** Missing required fields or invalid file format
|
| 145 |
-
- **Solution:** Check file upload and field names
|
| 146 |
-
|
| 147 |
-
**404 Not Found**
|
| 148 |
-
- **Cause:** Invalid image_id or mask_id
|
| 149 |
-
- **Solution:** Re-upload files to get fresh IDs
|
| 150 |
-
|
| 151 |
-
**401 Unauthorized**
|
| 152 |
-
- **Cause:** Missing or invalid API token
|
| 153 |
-
- **Solution:** Add correct Authorization header
|
| 154 |
-
|
| 155 |
-
**403 Forbidden**
|
| 156 |
-
- **Cause:** Wrong API token
|
| 157 |
-
- **Solution:** Use the correct token you configured
|
| 158 |
-
|
| 159 |
-
### Troubleshooting
|
| 160 |
-
|
| 161 |
-
**File Upload Issues in Postman:**
|
| 162 |
-
- Remove warning icons next to filenames
|
| 163 |
-
- Re-select files if warning appears
|
| 164 |
-
- Try different file formats (PNG, JPG)
|
| 165 |
-
- Check file isn't corrupted
|
| 166 |
-
|
| 167 |
-
**Slow Processing:**
|
| 168 |
-
- Large images take longer to process
|
| 169 |
-
- Typical processing time: 1-3 seconds
|
| 170 |
-
- Wait for completion before checking results
|
| 171 |
-
|
| 172 |
-
## Complete Workflow Example
|
| 173 |
-
|
| 174 |
-
```bash
|
| 175 |
-
# 1. Health check
|
| 176 |
-
curl -H "Authorization: Bearer <API_TOKEN>" \
|
| 177 |
-
https://logicgoinfotechspaces-object-remover.hf.space/health
|
| 178 |
-
|
| 179 |
-
# 2. Upload image
|
| 180 |
-
curl -H "Authorization: Bearer <API_TOKEN>" \
|
| 181 |
-
-F image=@photo.jpg \
|
| 182 |
-
https://logicgoinfotechspaces-object-remover.hf.space/upload-image
|
| 183 |
-
|
| 184 |
-
# 3. Upload mask
|
| 185 |
-
curl -H "Authorization: Bearer <API_TOKEN>" \
|
| 186 |
-
-F mask=@mask.png \
|
| 187 |
-
https://logicgoinfotechspaces-object-remover.hf.space/upload-mask
|
| 188 |
-
|
| 189 |
-
# 4. Process (replace with actual IDs from steps 2&3)
|
| 190 |
-
curl -H "Authorization: Bearer <API_TOKEN>" \
|
| 191 |
-
-H "Content-Type: application/json" \
|
| 192 |
-
-d '{"image_id":"IMAGE_ID","mask_id":"MASK_ID"}' \
|
| 193 |
-
https://logicgoinfotechspaces-object-remover.hf.space/inpaint
|
| 194 |
-
|
| 195 |
-
# 5. View result (replace with actual filename from step 4)
|
| 196 |
-
# Open in browser: https://logicgoinfotechspaces-object-remover.hf.space/result/OUTPUT_FILENAME
|
| 197 |
-
```
|
| 198 |
-
|
| 199 |
-
## API Endpoints Summary
|
| 200 |
-
|
| 201 |
-
| Method | Endpoint | Description | Auth Required |
|
| 202 |
-
|--------|----------|-------------|---------------|
|
| 203 |
-
| GET | `/health` | Health check | No |
|
| 204 |
-
| GET | `/` | API information | No |
|
| 205 |
-
| POST | `/upload-image` | Upload image file | Optional |
|
| 206 |
-
| POST | `/upload-mask` | Upload mask file | Optional |
|
| 207 |
-
| POST | `/inpaint` | Process with IDs | Optional |
|
| 208 |
-
| POST | `/inpaint-url` | Process with URL response | Optional |
|
| 209 |
-
| POST | `/inpaint-multipart` | One-step processing | Optional |
|
| 210 |
-
| GET | `/download/{filename}` | Download result | No |
|
| 211 |
-
| GET | `/result/{filename}` | View result in browser | No |
|
| 212 |
-
| GET | `/logs` | View processing logs | Optional |
|
| 213 |
-
| GET | `/docs` | API documentation | No |
|
| 214 |
-
|
| 215 |
-
## Support
|
| 216 |
-
|
| 217 |
-
For issues or questions:
|
| 218 |
-
1. Check the API documentation at `/docs`
|
| 219 |
-
2. Verify your request format matches examples
|
| 220 |
-
3. Ensure files are properly uploaded
|
| 221 |
-
4. Check authentication token is correct
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
api/main.py
CHANGED
|
@@ -98,6 +98,93 @@ ADMIN_MONGO_URI = os.environ.get("MONGODB_ADMIN")
|
|
| 98 |
DEFAULT_CATEGORY_ID = "69368f722e46bd68ae188984"
|
| 99 |
admin_media_clicks = None
|
| 100 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
|
| 102 |
def _init_admin_mongo() -> None:
|
| 103 |
global admin_media_clicks
|
|
@@ -320,6 +407,7 @@ class InpaintRequest(BaseModel):
|
|
| 320 |
prompt: Optional[str] = None # Optional: describe what to remove
|
| 321 |
user_id: Optional[str] = None
|
| 322 |
category_id: Optional[str] = None
|
|
|
|
| 323 |
|
| 324 |
|
| 325 |
class SimpleRemoveRequest(BaseModel):
|
|
@@ -349,10 +437,23 @@ def _coerce_category_id(category_id: Optional[str]) -> ObjectId:
|
|
| 349 |
return _coerce_object_id(raw_str)
|
| 350 |
|
| 351 |
|
| 352 |
-
def log_media_click(user_id: Optional[str], category_id: Optional[str]) -> None:
|
| 353 |
-
"""Log to admin media_clicks collection only if user_id is provided.
|
| 354 |
-
|
| 355 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 356 |
# Only log if user_id is provided (not None/empty)
|
| 357 |
if not user_id or not user_id.strip():
|
| 358 |
return
|
|
@@ -362,14 +463,14 @@ def log_media_click(user_id: Optional[str], category_id: Optional[str]) -> None:
|
|
| 362 |
now = datetime.utcnow()
|
| 363 |
today = now.date()
|
| 364 |
|
| 365 |
-
doc =
|
| 366 |
if doc:
|
| 367 |
existing_daily = doc.get("ai_edit_daily_count")
|
| 368 |
updated_daily = _build_ai_edit_daily_count(existing_daily, today)
|
| 369 |
categories = doc.get("categories") or []
|
| 370 |
if any(cat.get("categoryId") == category_obj for cat in categories):
|
| 371 |
# Category exists: increment click_count and ai_edit_complete, update dates
|
| 372 |
-
|
| 373 |
{"_id": doc["_id"], "categories.categoryId": category_obj},
|
| 374 |
{
|
| 375 |
"$inc": {
|
|
@@ -386,7 +487,7 @@ def log_media_click(user_id: Optional[str], category_id: Optional[str]) -> None:
|
|
| 386 |
)
|
| 387 |
else:
|
| 388 |
# New category to existing document: push category, increment ai_edit_complete
|
| 389 |
-
|
| 390 |
{"_id": doc["_id"]},
|
| 391 |
{
|
| 392 |
"$push": {
|
|
@@ -407,7 +508,7 @@ def log_media_click(user_id: Optional[str], category_id: Optional[str]) -> None:
|
|
| 407 |
else:
|
| 408 |
# New user: create document with default ai_edit_complete=0, then increment to 1
|
| 409 |
daily_for_new = _build_ai_edit_daily_count(None, today)
|
| 410 |
-
|
| 411 |
{"userId": user_obj},
|
| 412 |
{
|
| 413 |
"$setOnInsert": {
|
|
@@ -432,15 +533,17 @@ def log_media_click(user_id: Optional[str], category_id: Optional[str]) -> None:
|
|
| 432 |
)
|
| 433 |
except Exception as err:
|
| 434 |
err_str = str(err)
|
|
|
|
| 435 |
if "Unauthorized" in err_str or "not authorized" in err_str.lower():
|
| 436 |
log.warning(
|
| 437 |
-
"
|
| 438 |
"Check MongoDB user permissions.",
|
| 439 |
-
|
| 440 |
-
|
|
|
|
| 441 |
)
|
| 442 |
else:
|
| 443 |
-
log.warning("
|
| 444 |
|
| 445 |
|
| 446 |
@app.get("/")
|
|
@@ -612,6 +715,13 @@ def inpaint(req: InpaintRequest, request: Request, _: None = Depends(bearer_auth
|
|
| 612 |
compressed_url = None
|
| 613 |
|
| 614 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 615 |
img_rgba = _load_rgba_image_from_gridfs(req.image_id, "image")
|
| 616 |
mask_img = _load_rgba_image_from_gridfs(req.mask_id, "mask")
|
| 617 |
mask_rgba = _load_rgba_mask_from_image(mask_img)
|
|
@@ -643,7 +753,7 @@ def inpaint(req: InpaintRequest, request: Request, _: None = Depends(bearer_auth
|
|
| 643 |
log.warning("Failed to create compressed image: %s", compress_err)
|
| 644 |
compressed_url = None
|
| 645 |
|
| 646 |
-
log_media_click(req.user_id, req.
|
| 647 |
response = {"result": output_name}
|
| 648 |
if compressed_url:
|
| 649 |
response["Compressed_Image_URL"] = compressed_url
|
|
@@ -668,6 +778,10 @@ def inpaint(req: InpaintRequest, request: Request, _: None = Depends(bearer_auth
|
|
| 668 |
"response_time_ms": response_time_ms
|
| 669 |
}
|
| 670 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 671 |
if error_msg:
|
| 672 |
log_doc["error"] = error_msg
|
| 673 |
|
|
@@ -729,6 +843,13 @@ def inpaint_url(req: InpaintRequest, request: Request, _: None = Depends(bearer_
|
|
| 729 |
result_name = None
|
| 730 |
|
| 731 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 732 |
img_rgba = _load_rgba_image_from_gridfs(req.image_id, "image")
|
| 733 |
mask_img = _load_rgba_image_from_gridfs(req.mask_id, "mask") # may be RGB/gray/RGBA
|
| 734 |
mask_rgba = _load_rgba_mask_from_image(mask_img)
|
|
@@ -748,7 +869,7 @@ def inpaint_url(req: InpaintRequest, request: Request, _: None = Depends(bearer_
|
|
| 748 |
|
| 749 |
url = str(request.url_for("download_file", filename=result_name))
|
| 750 |
logs.append({"result": result_name, "url": url, "timestamp": datetime.utcnow().isoformat()})
|
| 751 |
-
log_media_click(req.user_id, req.
|
| 752 |
return {"result": result_name, "url": url}
|
| 753 |
except Exception as e:
|
| 754 |
status = "fail"
|
|
@@ -767,6 +888,9 @@ def inpaint_url(req: InpaintRequest, request: Request, _: None = Depends(bearer_
|
|
| 767 |
"ts": int(time.time()),
|
| 768 |
"response_time_ms": response_time_ms,
|
| 769 |
}
|
|
|
|
|
|
|
|
|
|
| 770 |
if error_msg:
|
| 771 |
log_doc["error"] = error_msg
|
| 772 |
if mongo_logs is not None:
|
|
@@ -802,6 +926,7 @@ def inpaint_multipart(
|
|
| 802 |
prompt: Optional[str] = Form(None),
|
| 803 |
user_id: Optional[str] = Form(None),
|
| 804 |
category_id: Optional[str] = Form(None),
|
|
|
|
| 805 |
_: None = Depends(bearer_auth),
|
| 806 |
) -> Dict[str, str]:
|
| 807 |
start_time = time.time()
|
|
@@ -810,6 +935,13 @@ def inpaint_multipart(
|
|
| 810 |
result_name = None
|
| 811 |
|
| 812 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 813 |
# Load in-memory
|
| 814 |
img = Image.open(image.file).convert("RGBA")
|
| 815 |
m = Image.open(mask.file).convert("RGBA")
|
|
@@ -835,7 +967,7 @@ def inpaint_multipart(
|
|
| 835 |
resp: Dict[str, str] = {"result": result_name}
|
| 836 |
if url:
|
| 837 |
resp["url"] = url
|
| 838 |
-
log_media_click(user_id,
|
| 839 |
return resp
|
| 840 |
|
| 841 |
if mask_is_painted:
|
|
@@ -936,7 +1068,7 @@ def inpaint_multipart(
|
|
| 936 |
resp: Dict[str, str] = {"result": result_name}
|
| 937 |
if url:
|
| 938 |
resp["url"] = url
|
| 939 |
-
log_media_click(user_id,
|
| 940 |
return resp
|
| 941 |
except Exception as e:
|
| 942 |
status = "fail"
|
|
@@ -954,6 +1086,9 @@ def inpaint_multipart(
|
|
| 954 |
"ts": int(time.time()),
|
| 955 |
"response_time_ms": response_time_ms,
|
| 956 |
}
|
|
|
|
|
|
|
|
|
|
| 957 |
if error_msg:
|
| 958 |
log_doc["error"] = error_msg
|
| 959 |
if mongo_logs is not None:
|
|
@@ -984,6 +1119,7 @@ def remove_pink_segments(
|
|
| 984 |
request: Request = None,
|
| 985 |
user_id: Optional[str] = Form(None),
|
| 986 |
category_id: Optional[str] = Form(None),
|
|
|
|
| 987 |
_: None = Depends(bearer_auth),
|
| 988 |
) -> Dict[str, str]:
|
| 989 |
"""
|
|
@@ -998,6 +1134,13 @@ def remove_pink_segments(
|
|
| 998 |
result_name = None
|
| 999 |
|
| 1000 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1001 |
log.info(f"Simple remove-pink: processing image {image.filename}")
|
| 1002 |
|
| 1003 |
# Load the image (with pink paint on it)
|
|
@@ -1094,7 +1237,7 @@ def remove_pink_segments(
|
|
| 1094 |
resp: Dict[str, str] = {"result": result_name, "pink_segments_detected": str(nonzero)}
|
| 1095 |
if url:
|
| 1096 |
resp["url"] = url
|
| 1097 |
-
log_media_click(user_id,
|
| 1098 |
return resp
|
| 1099 |
except Exception as e:
|
| 1100 |
status = "fail"
|
|
@@ -1112,6 +1255,9 @@ def remove_pink_segments(
|
|
| 1112 |
"ts": int(time.time()),
|
| 1113 |
"response_time_ms": response_time_ms,
|
| 1114 |
}
|
|
|
|
|
|
|
|
|
|
| 1115 |
if error_msg:
|
| 1116 |
log_doc["error"] = error_msg
|
| 1117 |
if mongo_logs is not None:
|
|
|
|
| 98 |
DEFAULT_CATEGORY_ID = "69368f722e46bd68ae188984"
|
| 99 |
admin_media_clicks = None
|
| 100 |
|
| 101 |
+
# Collage-maker MongoDB configuration
|
| 102 |
+
COLLAGE_MAKER_MONGO_URI = os.environ.get("MONGODB_COLLAGE_MAKER")
|
| 103 |
+
COLLAGE_MAKER_DB_NAME = os.environ.get("MONGODB_COLLAGE_MAKER_DB_NAME", "collage-maker")
|
| 104 |
+
COLLAGE_MAKER_ADMIN_DB_NAME = os.environ.get("MONGODB_COLLAGE_MAKER_ADMIN_DB_NAME", "adminPanel")
|
| 105 |
+
collage_maker_client = None
|
| 106 |
+
collage_maker_db = None
|
| 107 |
+
collage_maker_admin_db = None
|
| 108 |
+
collage_maker_media_clicks = None
|
| 109 |
+
collage_maker_categories = None
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def get_collage_maker_client() -> Optional[MongoClient]:
|
| 113 |
+
"""Get collage-maker MongoDB client."""
|
| 114 |
+
global collage_maker_client
|
| 115 |
+
if collage_maker_client is None and COLLAGE_MAKER_MONGO_URI:
|
| 116 |
+
try:
|
| 117 |
+
collage_maker_client = MongoClient(COLLAGE_MAKER_MONGO_URI)
|
| 118 |
+
log.info("Collage-maker MongoDB client initialized")
|
| 119 |
+
except Exception as err:
|
| 120 |
+
log.error("Failed to initialize collage-maker MongoDB client: %s", err)
|
| 121 |
+
collage_maker_client = None
|
| 122 |
+
return collage_maker_client
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def get_collage_maker_database() -> Optional[Any]:
|
| 126 |
+
"""Get collage-maker database instance."""
|
| 127 |
+
global collage_maker_db
|
| 128 |
+
client = get_collage_maker_client()
|
| 129 |
+
if client is None:
|
| 130 |
+
return None
|
| 131 |
+
if collage_maker_db is None:
|
| 132 |
+
try:
|
| 133 |
+
collage_maker_db = client[COLLAGE_MAKER_DB_NAME]
|
| 134 |
+
log.info("Collage-maker database initialized: %s", COLLAGE_MAKER_DB_NAME)
|
| 135 |
+
except Exception as err:
|
| 136 |
+
log.error("Failed to get collage-maker database: %s", err)
|
| 137 |
+
collage_maker_db = None
|
| 138 |
+
return collage_maker_db
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def _init_collage_maker_mongo() -> None:
|
| 142 |
+
"""Initialize collage-maker MongoDB connections."""
|
| 143 |
+
global collage_maker_admin_db, collage_maker_media_clicks, collage_maker_categories
|
| 144 |
+
client = get_collage_maker_client()
|
| 145 |
+
if client is None:
|
| 146 |
+
log.info("Collage-maker Mongo URI not provided; collage-maker features disabled")
|
| 147 |
+
return
|
| 148 |
+
try:
|
| 149 |
+
collage_maker_admin_db = client[COLLAGE_MAKER_ADMIN_DB_NAME]
|
| 150 |
+
collage_maker_media_clicks = collage_maker_admin_db["media_clicks"]
|
| 151 |
+
collage_maker_categories = collage_maker_admin_db["categories"]
|
| 152 |
+
log.info(
|
| 153 |
+
"Collage-maker admin initialized: db=%s, media_clicks=%s, categories=%s",
|
| 154 |
+
COLLAGE_MAKER_ADMIN_DB_NAME,
|
| 155 |
+
collage_maker_media_clicks.name,
|
| 156 |
+
collage_maker_categories.name,
|
| 157 |
+
)
|
| 158 |
+
except Exception as err:
|
| 159 |
+
log.error("Failed to init collage-maker admin Mongo: %s", err)
|
| 160 |
+
collage_maker_admin_db = None
|
| 161 |
+
collage_maker_media_clicks = None
|
| 162 |
+
collage_maker_categories = None
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
_init_collage_maker_mongo()
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def get_category_id_from_collage_maker() -> Optional[str]:
|
| 169 |
+
"""Query category ID from collage-maker categories collection."""
|
| 170 |
+
if collage_maker_categories is None:
|
| 171 |
+
log.warning("Collage-maker categories collection not initialized")
|
| 172 |
+
return None
|
| 173 |
+
try:
|
| 174 |
+
# Query the categories collection - you may need to adjust the query based on your schema
|
| 175 |
+
# This assumes there's a default category or we get the first one
|
| 176 |
+
category_doc = collage_maker_categories.find_one()
|
| 177 |
+
if category_doc:
|
| 178 |
+
category_id = str(category_doc.get("_id", ""))
|
| 179 |
+
log.info("Found category ID from collage-maker: %s", category_id)
|
| 180 |
+
return category_id
|
| 181 |
+
else:
|
| 182 |
+
log.warning("No categories found in collage-maker collection")
|
| 183 |
+
return None
|
| 184 |
+
except Exception as err:
|
| 185 |
+
log.error("Failed to query collage-maker categories: %s", err)
|
| 186 |
+
return None
|
| 187 |
+
|
| 188 |
|
| 189 |
def _init_admin_mongo() -> None:
|
| 190 |
global admin_media_clicks
|
|
|
|
| 407 |
prompt: Optional[str] = None # Optional: describe what to remove
|
| 408 |
user_id: Optional[str] = None
|
| 409 |
category_id: Optional[str] = None
|
| 410 |
+
appname: Optional[str] = None # Optional: app name (e.g., "collage-maker")
|
| 411 |
|
| 412 |
|
| 413 |
class SimpleRemoveRequest(BaseModel):
|
|
|
|
| 437 |
return _coerce_object_id(raw_str)
|
| 438 |
|
| 439 |
|
| 440 |
+
def log_media_click(user_id: Optional[str], category_id: Optional[str], appname: Optional[str] = None) -> None:
|
| 441 |
+
"""Log to admin media_clicks collection only if user_id is provided.
|
| 442 |
+
|
| 443 |
+
If appname='collage-maker', logs to collage-maker MongoDB instead of regular admin MongoDB.
|
| 444 |
+
"""
|
| 445 |
+
# Determine which media_clicks collection to use
|
| 446 |
+
target_media_clicks = None
|
| 447 |
+
if appname == "collage-maker":
|
| 448 |
+
target_media_clicks = collage_maker_media_clicks
|
| 449 |
+
if target_media_clicks is None:
|
| 450 |
+
log.warning("Collage-maker media_clicks not initialized, skipping log")
|
| 451 |
+
return
|
| 452 |
+
else:
|
| 453 |
+
target_media_clicks = admin_media_clicks
|
| 454 |
+
if target_media_clicks is None:
|
| 455 |
+
return
|
| 456 |
+
|
| 457 |
# Only log if user_id is provided (not None/empty)
|
| 458 |
if not user_id or not user_id.strip():
|
| 459 |
return
|
|
|
|
| 463 |
now = datetime.utcnow()
|
| 464 |
today = now.date()
|
| 465 |
|
| 466 |
+
doc = target_media_clicks.find_one({"userId": user_obj})
|
| 467 |
if doc:
|
| 468 |
existing_daily = doc.get("ai_edit_daily_count")
|
| 469 |
updated_daily = _build_ai_edit_daily_count(existing_daily, today)
|
| 470 |
categories = doc.get("categories") or []
|
| 471 |
if any(cat.get("categoryId") == category_obj for cat in categories):
|
| 472 |
# Category exists: increment click_count and ai_edit_complete, update dates
|
| 473 |
+
target_media_clicks.update_one(
|
| 474 |
{"_id": doc["_id"], "categories.categoryId": category_obj},
|
| 475 |
{
|
| 476 |
"$inc": {
|
|
|
|
| 487 |
)
|
| 488 |
else:
|
| 489 |
# New category to existing document: push category, increment ai_edit_complete
|
| 490 |
+
target_media_clicks.update_one(
|
| 491 |
{"_id": doc["_id"]},
|
| 492 |
{
|
| 493 |
"$push": {
|
|
|
|
| 508 |
else:
|
| 509 |
# New user: create document with default ai_edit_complete=0, then increment to 1
|
| 510 |
daily_for_new = _build_ai_edit_daily_count(None, today)
|
| 511 |
+
target_media_clicks.update_one(
|
| 512 |
{"userId": user_obj},
|
| 513 |
{
|
| 514 |
"$setOnInsert": {
|
|
|
|
| 533 |
)
|
| 534 |
except Exception as err:
|
| 535 |
err_str = str(err)
|
| 536 |
+
target_name = "collage-maker" if appname == "collage-maker" else "admin"
|
| 537 |
if "Unauthorized" in err_str or "not authorized" in err_str.lower():
|
| 538 |
log.warning(
|
| 539 |
+
"%s media click logging failed (permissions): user lacks read/write on db=%s collection=%s. "
|
| 540 |
"Check MongoDB user permissions.",
|
| 541 |
+
target_name,
|
| 542 |
+
target_media_clicks.database.name,
|
| 543 |
+
target_media_clicks.name,
|
| 544 |
)
|
| 545 |
else:
|
| 546 |
+
log.warning("%s media click logging failed: %s", target_name, err)
|
| 547 |
|
| 548 |
|
| 549 |
@app.get("/")
|
|
|
|
| 715 |
compressed_url = None
|
| 716 |
|
| 717 |
try:
|
| 718 |
+
# Handle appname="collage-maker": get category_id from collage-maker if not provided
|
| 719 |
+
category_id = req.category_id
|
| 720 |
+
if req.appname == "collage-maker" and not category_id:
|
| 721 |
+
category_id = get_category_id_from_collage_maker()
|
| 722 |
+
if category_id:
|
| 723 |
+
log.info("Using category_id from collage-maker: %s", category_id)
|
| 724 |
+
|
| 725 |
img_rgba = _load_rgba_image_from_gridfs(req.image_id, "image")
|
| 726 |
mask_img = _load_rgba_image_from_gridfs(req.mask_id, "mask")
|
| 727 |
mask_rgba = _load_rgba_mask_from_image(mask_img)
|
|
|
|
| 753 |
log.warning("Failed to create compressed image: %s", compress_err)
|
| 754 |
compressed_url = None
|
| 755 |
|
| 756 |
+
log_media_click(req.user_id, category_id, req.appname)
|
| 757 |
response = {"result": output_name}
|
| 758 |
if compressed_url:
|
| 759 |
response["Compressed_Image_URL"] = compressed_url
|
|
|
|
| 778 |
"response_time_ms": response_time_ms
|
| 779 |
}
|
| 780 |
|
| 781 |
+
# Store appname in api_logs if provided
|
| 782 |
+
if req.appname:
|
| 783 |
+
log_doc["appname"] = req.appname
|
| 784 |
+
|
| 785 |
if error_msg:
|
| 786 |
log_doc["error"] = error_msg
|
| 787 |
|
|
|
|
| 843 |
result_name = None
|
| 844 |
|
| 845 |
try:
|
| 846 |
+
# Handle appname="collage-maker": get category_id from collage-maker if not provided
|
| 847 |
+
category_id = req.category_id
|
| 848 |
+
if req.appname == "collage-maker" and not category_id:
|
| 849 |
+
category_id = get_category_id_from_collage_maker()
|
| 850 |
+
if category_id:
|
| 851 |
+
log.info("Using category_id from collage-maker: %s", category_id)
|
| 852 |
+
|
| 853 |
img_rgba = _load_rgba_image_from_gridfs(req.image_id, "image")
|
| 854 |
mask_img = _load_rgba_image_from_gridfs(req.mask_id, "mask") # may be RGB/gray/RGBA
|
| 855 |
mask_rgba = _load_rgba_mask_from_image(mask_img)
|
|
|
|
| 869 |
|
| 870 |
url = str(request.url_for("download_file", filename=result_name))
|
| 871 |
logs.append({"result": result_name, "url": url, "timestamp": datetime.utcnow().isoformat()})
|
| 872 |
+
log_media_click(req.user_id, category_id, req.appname)
|
| 873 |
return {"result": result_name, "url": url}
|
| 874 |
except Exception as e:
|
| 875 |
status = "fail"
|
|
|
|
| 888 |
"ts": int(time.time()),
|
| 889 |
"response_time_ms": response_time_ms,
|
| 890 |
}
|
| 891 |
+
# Store appname in api_logs if provided
|
| 892 |
+
if req.appname:
|
| 893 |
+
log_doc["appname"] = req.appname
|
| 894 |
if error_msg:
|
| 895 |
log_doc["error"] = error_msg
|
| 896 |
if mongo_logs is not None:
|
|
|
|
| 926 |
prompt: Optional[str] = Form(None),
|
| 927 |
user_id: Optional[str] = Form(None),
|
| 928 |
category_id: Optional[str] = Form(None),
|
| 929 |
+
appname: Optional[str] = Form(None),
|
| 930 |
_: None = Depends(bearer_auth),
|
| 931 |
) -> Dict[str, str]:
|
| 932 |
start_time = time.time()
|
|
|
|
| 935 |
result_name = None
|
| 936 |
|
| 937 |
try:
|
| 938 |
+
# Handle appname="collage-maker": get category_id from collage-maker if not provided
|
| 939 |
+
final_category_id = category_id
|
| 940 |
+
if appname == "collage-maker" and not final_category_id:
|
| 941 |
+
final_category_id = get_category_id_from_collage_maker()
|
| 942 |
+
if final_category_id:
|
| 943 |
+
log.info("Using category_id from collage-maker: %s", final_category_id)
|
| 944 |
+
|
| 945 |
# Load in-memory
|
| 946 |
img = Image.open(image.file).convert("RGBA")
|
| 947 |
m = Image.open(mask.file).convert("RGBA")
|
|
|
|
| 967 |
resp: Dict[str, str] = {"result": result_name}
|
| 968 |
if url:
|
| 969 |
resp["url"] = url
|
| 970 |
+
log_media_click(user_id, final_category_id, appname)
|
| 971 |
return resp
|
| 972 |
|
| 973 |
if mask_is_painted:
|
|
|
|
| 1068 |
resp: Dict[str, str] = {"result": result_name}
|
| 1069 |
if url:
|
| 1070 |
resp["url"] = url
|
| 1071 |
+
log_media_click(user_id, final_category_id, appname)
|
| 1072 |
return resp
|
| 1073 |
except Exception as e:
|
| 1074 |
status = "fail"
|
|
|
|
| 1086 |
"ts": int(time.time()),
|
| 1087 |
"response_time_ms": response_time_ms,
|
| 1088 |
}
|
| 1089 |
+
# Store appname in api_logs if provided
|
| 1090 |
+
if appname:
|
| 1091 |
+
log_doc["appname"] = appname
|
| 1092 |
if error_msg:
|
| 1093 |
log_doc["error"] = error_msg
|
| 1094 |
if mongo_logs is not None:
|
|
|
|
| 1119 |
request: Request = None,
|
| 1120 |
user_id: Optional[str] = Form(None),
|
| 1121 |
category_id: Optional[str] = Form(None),
|
| 1122 |
+
appname: Optional[str] = Form(None),
|
| 1123 |
_: None = Depends(bearer_auth),
|
| 1124 |
) -> Dict[str, str]:
|
| 1125 |
"""
|
|
|
|
| 1134 |
result_name = None
|
| 1135 |
|
| 1136 |
try:
|
| 1137 |
+
# Handle appname="collage-maker": get category_id from collage-maker if not provided
|
| 1138 |
+
final_category_id = category_id
|
| 1139 |
+
if appname == "collage-maker" and not final_category_id:
|
| 1140 |
+
final_category_id = get_category_id_from_collage_maker()
|
| 1141 |
+
if final_category_id:
|
| 1142 |
+
log.info("Using category_id from collage-maker: %s", final_category_id)
|
| 1143 |
+
|
| 1144 |
log.info(f"Simple remove-pink: processing image {image.filename}")
|
| 1145 |
|
| 1146 |
# Load the image (with pink paint on it)
|
|
|
|
| 1237 |
resp: Dict[str, str] = {"result": result_name, "pink_segments_detected": str(nonzero)}
|
| 1238 |
if url:
|
| 1239 |
resp["url"] = url
|
| 1240 |
+
log_media_click(user_id, final_category_id, appname)
|
| 1241 |
return resp
|
| 1242 |
except Exception as e:
|
| 1243 |
status = "fail"
|
|
|
|
| 1255 |
"ts": int(time.time()),
|
| 1256 |
"response_time_ms": response_time_ms,
|
| 1257 |
}
|
| 1258 |
+
# Store appname in api_logs if provided
|
| 1259 |
+
if appname:
|
| 1260 |
+
log_doc["appname"] = appname
|
| 1261 |
if error_msg:
|
| 1262 |
log_doc["error"] = error_msg
|
| 1263 |
if mongo_logs is not None:
|