Commit
·
3105993
1
Parent(s):
738e35e
Add compressed image support with 2-3MB target file size - Add Compressed_Image_URL field to colorize endpoint response - Implement compress_image_to_target_size function with iterative quality/dimension adjustment - Original download_url returns uncompressed PNG (model output) - Compressed_Image_URL returns JPEG compressed to 2-3MB file size
Browse files- app/main.py +170 -2
app/main.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
from fastapi import FastAPI, File, UploadFile, HTTPException, Header, Request, Form, Depends, Body
|
| 2 |
-
from typing import Optional
|
| 3 |
from fastapi.responses import FileResponse
|
| 4 |
from huggingface_hub import hf_hub_download
|
| 5 |
import uuid
|
|
@@ -81,8 +81,10 @@ else:
|
|
| 81 |
# -------------------------------------------------
|
| 82 |
UPLOAD_DIR = "/tmp/uploads"
|
| 83 |
RESULTS_DIR = "/tmp/results"
|
|
|
|
| 84 |
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
| 85 |
os.makedirs(RESULTS_DIR, exist_ok=True)
|
|
|
|
| 86 |
|
| 87 |
MEDIA_CLICK_DEFAULT_CATEGORY = os.getenv("DEFAULT_CATEGORY_FALLBACK", "69368fcd2e46bd68ae1889b2")
|
| 88 |
|
|
@@ -182,6 +184,105 @@ def colorize_image(img: Image.Image, model_type: str = "gan", cco_model: str = "
|
|
| 182 |
else:
|
| 183 |
return colorize_image_gan(img)
|
| 184 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
# -------------------------------------------------
|
| 186 |
# 🗄️ MongoDB Initialization
|
| 187 |
# -------------------------------------------------
|
|
@@ -737,8 +838,35 @@ async def colorize(
|
|
| 737 |
|
| 738 |
result_id = f"{uuid.uuid4()}.png"
|
| 739 |
output_path = os.path.join(RESULTS_DIR, result_id)
|
|
|
|
| 740 |
output_img.save(output_path, "PNG")
|
| 741 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 742 |
base_url = "https://logicgoinfotechspaces-text-guided-image-colorization.hf.space"
|
| 743 |
|
| 744 |
result_id_clean = result_id.replace(".png", "")
|
|
@@ -752,7 +880,8 @@ async def colorize(
|
|
| 752 |
"download_url": f"{base_url}/results/{result_id}",
|
| 753 |
"api_download_url": f"{base_url}/download/{result_id_clean}",
|
| 754 |
"filename": result_id,
|
| 755 |
-
"caption": caption
|
|
|
|
| 756 |
}
|
| 757 |
|
| 758 |
# Log to MongoDB (colorization_db -> colorizations)
|
|
@@ -915,6 +1044,14 @@ def get_upload(request: Request, filename: str):
|
|
| 915 |
)
|
| 916 |
raise HTTPException(status_code=404, detail="File not found")
|
| 917 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 918 |
log_api_call(
|
| 919 |
endpoint=f"/uploads/{filename}",
|
| 920 |
method="GET",
|
|
@@ -924,3 +1061,34 @@ def get_upload(request: Request, filename: str):
|
|
| 924 |
)
|
| 925 |
|
| 926 |
return FileResponse(path, media_type=media_type)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from fastapi import FastAPI, File, UploadFile, HTTPException, Header, Request, Form, Depends, Body
|
| 2 |
+
from typing import Optional, Tuple
|
| 3 |
from fastapi.responses import FileResponse
|
| 4 |
from huggingface_hub import hf_hub_download
|
| 5 |
import uuid
|
|
|
|
| 81 |
# -------------------------------------------------
|
| 82 |
UPLOAD_DIR = "/tmp/uploads"
|
| 83 |
RESULTS_DIR = "/tmp/results"
|
| 84 |
+
COMPRESSED_DIR = "/tmp/compressed"
|
| 85 |
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
| 86 |
os.makedirs(RESULTS_DIR, exist_ok=True)
|
| 87 |
+
os.makedirs(COMPRESSED_DIR, exist_ok=True)
|
| 88 |
|
| 89 |
MEDIA_CLICK_DEFAULT_CATEGORY = os.getenv("DEFAULT_CATEGORY_FALLBACK", "69368fcd2e46bd68ae1889b2")
|
| 90 |
|
|
|
|
| 184 |
else:
|
| 185 |
return colorize_image_gan(img)
|
| 186 |
|
| 187 |
+
def compress_image_to_target_size(img: Image.Image, target_size_mb: float = 2.5, min_size_mb: float = 2.0, max_size_mb: float = 3.0) -> Tuple[Image.Image, str]:
|
| 188 |
+
"""
|
| 189 |
+
Compress image to target file size (2-3MB) by adjusting quality and dimensions
|
| 190 |
+
|
| 191 |
+
Args:
|
| 192 |
+
img: PIL Image to compress
|
| 193 |
+
target_size_mb: Target file size in MB (default: 2.5MB)
|
| 194 |
+
min_size_mb: Minimum acceptable file size in MB (default: 2.0MB)
|
| 195 |
+
max_size_mb: Maximum acceptable file size in MB (default: 3.0MB)
|
| 196 |
+
|
| 197 |
+
Returns:
|
| 198 |
+
Tuple of (compressed PIL Image, path to saved compressed image)
|
| 199 |
+
"""
|
| 200 |
+
import tempfile
|
| 201 |
+
|
| 202 |
+
# Ensure image is RGB
|
| 203 |
+
if img.mode != "RGB":
|
| 204 |
+
img = img.convert("RGB")
|
| 205 |
+
|
| 206 |
+
# Get original dimensions
|
| 207 |
+
original_size = img.size
|
| 208 |
+
current_img = img.copy()
|
| 209 |
+
|
| 210 |
+
# Start with high quality and reduce if needed
|
| 211 |
+
quality = 85
|
| 212 |
+
scale_factor = 1.0
|
| 213 |
+
|
| 214 |
+
# Try different quality levels and scales to achieve target size
|
| 215 |
+
for attempt in range(20): # Max 20 attempts
|
| 216 |
+
# Create temporary file to check size
|
| 217 |
+
with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as tmp_file:
|
| 218 |
+
temp_path = tmp_file.name
|
| 219 |
+
|
| 220 |
+
try:
|
| 221 |
+
# Resize if needed
|
| 222 |
+
if scale_factor < 1.0:
|
| 223 |
+
new_width = int(original_size[0] * scale_factor)
|
| 224 |
+
new_height = int(original_size[1] * scale_factor)
|
| 225 |
+
resized_img = current_img.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
| 226 |
+
else:
|
| 227 |
+
resized_img = current_img
|
| 228 |
+
|
| 229 |
+
# Save with current quality
|
| 230 |
+
resized_img.save(temp_path, "JPEG", quality=quality, optimize=True)
|
| 231 |
+
|
| 232 |
+
# Check file size
|
| 233 |
+
file_size_bytes = os.path.getsize(temp_path)
|
| 234 |
+
file_size_mb = file_size_bytes / (1024 * 1024)
|
| 235 |
+
|
| 236 |
+
logger.info("Compression attempt %d: quality=%d, scale=%.2f, size=%.2f MB",
|
| 237 |
+
attempt + 1, quality, scale_factor, file_size_mb)
|
| 238 |
+
|
| 239 |
+
# If size is within target range, we're done
|
| 240 |
+
if min_size_mb <= file_size_mb <= max_size_mb:
|
| 241 |
+
logger.info("Target size achieved: %.2f MB", file_size_mb)
|
| 242 |
+
return resized_img, temp_path
|
| 243 |
+
|
| 244 |
+
# If too large, reduce quality or scale
|
| 245 |
+
if file_size_mb > max_size_mb:
|
| 246 |
+
if quality > 30:
|
| 247 |
+
quality -= 5 # Reduce quality
|
| 248 |
+
elif scale_factor > 0.5:
|
| 249 |
+
scale_factor -= 0.05 # Reduce dimensions
|
| 250 |
+
quality = 75 # Reset quality when scaling
|
| 251 |
+
else:
|
| 252 |
+
# Already at minimum, accept current result
|
| 253 |
+
logger.warning("Reached minimum compression, file size: %.2f MB", file_size_mb)
|
| 254 |
+
return resized_img, temp_path
|
| 255 |
+
|
| 256 |
+
# If too small but still reasonable (above 1MB), try to increase quality slightly
|
| 257 |
+
elif file_size_mb < min_size_mb:
|
| 258 |
+
if file_size_mb < 1.0:
|
| 259 |
+
# Very small file, increase quality more aggressively
|
| 260 |
+
if quality < 90:
|
| 261 |
+
quality += 5
|
| 262 |
+
elif scale_factor < 1.0:
|
| 263 |
+
# Increase scale if we reduced it
|
| 264 |
+
scale_factor = min(1.0, scale_factor + 0.1)
|
| 265 |
+
elif quality < 95:
|
| 266 |
+
# Close to target, fine-tune quality
|
| 267 |
+
quality += 2
|
| 268 |
+
if quality > 95:
|
| 269 |
+
quality = 95
|
| 270 |
+
else:
|
| 271 |
+
# Already at max quality and scale, accept current result
|
| 272 |
+
logger.info("File size %.2f MB is below minimum but at max quality, accepting", file_size_mb)
|
| 273 |
+
return resized_img, temp_path
|
| 274 |
+
|
| 275 |
+
finally:
|
| 276 |
+
# Clean up temp file if we're continuing
|
| 277 |
+
if os.path.exists(temp_path) and attempt < 19:
|
| 278 |
+
try:
|
| 279 |
+
os.unlink(temp_path)
|
| 280 |
+
except:
|
| 281 |
+
pass
|
| 282 |
+
|
| 283 |
+
# Return the last attempt's result
|
| 284 |
+
return resized_img, temp_path
|
| 285 |
+
|
| 286 |
# -------------------------------------------------
|
| 287 |
# 🗄️ MongoDB Initialization
|
| 288 |
# -------------------------------------------------
|
|
|
|
| 838 |
|
| 839 |
result_id = f"{uuid.uuid4()}.png"
|
| 840 |
output_path = os.path.join(RESULTS_DIR, result_id)
|
| 841 |
+
# Save original image as PNG (uncompressed) - this is what the model produces
|
| 842 |
output_img.save(output_path, "PNG")
|
| 843 |
|
| 844 |
+
# Create compressed version targeting 2-3MB file size
|
| 845 |
+
logger.info("Creating compressed version targeting 2-3MB file size...")
|
| 846 |
+
compressed_img, temp_compressed_path = compress_image_to_target_size(
|
| 847 |
+
output_img,
|
| 848 |
+
target_size_mb=2.5,
|
| 849 |
+
min_size_mb=2.0,
|
| 850 |
+
max_size_mb=3.0
|
| 851 |
+
)
|
| 852 |
+
|
| 853 |
+
# Move compressed image to final location
|
| 854 |
+
compressed_filename = result_id.replace(".png", "_compressed.jpg")
|
| 855 |
+
compressed_path = os.path.join(COMPRESSED_DIR, compressed_filename)
|
| 856 |
+
|
| 857 |
+
# If temp file exists, move it; otherwise save the compressed image
|
| 858 |
+
if os.path.exists(temp_compressed_path):
|
| 859 |
+
import shutil
|
| 860 |
+
shutil.move(temp_compressed_path, compressed_path)
|
| 861 |
+
else:
|
| 862 |
+
compressed_img.save(compressed_path, "JPEG", quality=75, optimize=True)
|
| 863 |
+
|
| 864 |
+
# Log compressed file size
|
| 865 |
+
compressed_size_mb = os.path.getsize(compressed_path) / (1024 * 1024)
|
| 866 |
+
original_size_mb = os.path.getsize(output_path) / (1024 * 1024)
|
| 867 |
+
logger.info("Original image size: %.2f MB, Compressed image size: %.2f MB",
|
| 868 |
+
original_size_mb, compressed_size_mb)
|
| 869 |
+
|
| 870 |
base_url = "https://logicgoinfotechspaces-text-guided-image-colorization.hf.space"
|
| 871 |
|
| 872 |
result_id_clean = result_id.replace(".png", "")
|
|
|
|
| 880 |
"download_url": f"{base_url}/results/{result_id}",
|
| 881 |
"api_download_url": f"{base_url}/download/{result_id_clean}",
|
| 882 |
"filename": result_id,
|
| 883 |
+
"caption": caption,
|
| 884 |
+
"Compressed_Image_URL": f"{base_url}/compressed/{compressed_filename}"
|
| 885 |
}
|
| 886 |
|
| 887 |
# Log to MongoDB (colorization_db -> colorizations)
|
|
|
|
| 1044 |
)
|
| 1045 |
raise HTTPException(status_code=404, detail="File not found")
|
| 1046 |
|
| 1047 |
+
# Determine media type based on file extension
|
| 1048 |
+
if filename.lower().endswith('.png'):
|
| 1049 |
+
media_type = "image/png"
|
| 1050 |
+
elif filename.lower().endswith('.jpg') or filename.lower().endswith('.jpeg'):
|
| 1051 |
+
media_type = "image/jpeg"
|
| 1052 |
+
else:
|
| 1053 |
+
media_type = "image/jpeg" # Default to JPEG
|
| 1054 |
+
|
| 1055 |
log_api_call(
|
| 1056 |
endpoint=f"/uploads/{filename}",
|
| 1057 |
method="GET",
|
|
|
|
| 1061 |
)
|
| 1062 |
|
| 1063 |
return FileResponse(path, media_type=media_type)
|
| 1064 |
+
|
| 1065 |
+
# -------------------------------------------------
|
| 1066 |
+
# 🌐 Public Compressed File
|
| 1067 |
+
# -------------------------------------------------
|
| 1068 |
+
@app.get("/compressed/{filename}")
|
| 1069 |
+
def get_compressed(request: Request, filename: str):
|
| 1070 |
+
ip_address = request.client.host if request.client else None
|
| 1071 |
+
|
| 1072 |
+
path = os.path.join(COMPRESSED_DIR, filename)
|
| 1073 |
+
if not os.path.exists(path):
|
| 1074 |
+
log_api_call(
|
| 1075 |
+
endpoint=f"/compressed/{filename}",
|
| 1076 |
+
method="GET",
|
| 1077 |
+
status_code=404,
|
| 1078 |
+
error="Compressed file not found",
|
| 1079 |
+
ip_address=ip_address
|
| 1080 |
+
)
|
| 1081 |
+
raise HTTPException(status_code=404, detail="Compressed file not found")
|
| 1082 |
+
|
| 1083 |
+
# Compressed images are JPEG
|
| 1084 |
+
media_type = "image/jpeg"
|
| 1085 |
+
|
| 1086 |
+
log_api_call(
|
| 1087 |
+
endpoint=f"/compressed/{filename}",
|
| 1088 |
+
method="GET",
|
| 1089 |
+
status_code=200,
|
| 1090 |
+
request_data={"filename": filename},
|
| 1091 |
+
ip_address=ip_address
|
| 1092 |
+
)
|
| 1093 |
+
|
| 1094 |
+
return FileResponse(path, media_type=media_type)
|