earncoding's picture
Update main.py
d23892c verified
Raw
History Blame Contribute Delete
4.79 kB
import os
import io
from fastapi import FastAPI, UploadFile, File, HTTPException, Security
from fastapi.security import APIKeyHeader
from fastapi.middleware.cors import CORSMiddleware # Required for website access
from PIL import Image
from starlette.status import HTTP_403_FORBIDDEN
from fastapi.responses import Response
# ---------------------------------------------------------
# 1. APP CONFIGURATION
# ---------------------------------------------------------
app = FastAPI(
title="Secure Image Compressor API",
description="Compresses images via API using a secure key. CORS enabled for external websites.",
version="1.0"
)
# ---------------------------------------------------------
# 2. CORS MIDDLEWARE (Crucial for Website Integration)
# ---------------------------------------------------------
# This allows your API to be called from ANY domain (your portfolio, localhost, etc.)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Allows requests from ANY website
allow_credentials=True,
allow_methods=["*"], # Allows GET, POST, etc.
allow_headers=["*"], # Allows Custom Headers like x-api-key
)
# ---------------------------------------------------------
# 3. SECURITY CONFIGURATION
# ---------------------------------------------------------
# Define the header name clients must use
API_KEY_NAME = "x-api-key"
api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)
# Retrieve the secret from Hugging Face Environment Secrets
REAL_API_KEY = os.getenv("API_SECRET_KEY")
async def get_api_key(api_key_header: str = Security(api_key_header)):
"""Validates the API Key from the header."""
if not REAL_API_KEY:
# Fallback if secret isn't set in Settings
raise HTTPException(
status_code=500,
detail="Server Security Config Error: API_SECRET_KEY not set in env."
)
if api_key_header == REAL_API_KEY:
return api_key_header
raise HTTPException(
status_code=HTTP_403_FORBIDDEN,
detail="Could not validate credentials. Invalid API Key."
)
# ---------------------------------------------------------
# 4. ROUTE LOGIC
# ---------------------------------------------------------
@app.get("/")
def home():
"""Health check endpoint to ensure the server is running."""
return {"message": "Image Compressor API is active. POST to /compress with your API Key."}
@app.post("/compress")
async def compress_image(
file: UploadFile = File(...),
quality: int = 60,
api_key: str = Security(get_api_key)
):
"""
Uploads an image, compresses it, and returns the bytes.
- **file**: The image file (JPEG, PNG, WebP).
- **quality**: Compression quality (1-100). Default is 60.
- **x-api-key**: Header required for auth.
"""
# 1. Validate file type
if not file.content_type.startswith("image/"):
raise HTTPException(400, detail="File must be an image.")
try:
# 2. Read image into Pillow
image_data = await file.read()
image = Image.open(io.BytesIO(image_data))
# 3. Determine output format
# Use original format if supported, otherwise fallback to JPEG
output_format = image.format
if output_format not in ["JPEG", "PNG", "WEBP"]:
output_format = "JPEG"
# Handle transparency (RGBA) if converting to a format that doesn't support it (like JPEG)
if output_format == "JPEG" and image.mode in ("RGBA", "P"):
image = image.convert("RGB")
# 4. Compress Image into Memory Buffer
buffer = io.BytesIO()
# optimize=True is CPU intensive but provides better compression
image.save(
buffer,
format=output_format,
quality=quality,
optimize=True
)
# Move cursor to start of buffer to read it
buffer.seek(0)
compressed_bytes = buffer.getvalue()
# 5. Calculate stats for headers
original_size = len(image_data)
compressed_size = len(compressed_bytes)
savings = original_size - compressed_size
# 6. Return the raw image bytes with correct media type and stats headers
return Response(
content=compressed_bytes,
media_type=f"image/{output_format.lower()}",
headers={
"X-Original-Size": str(original_size),
"X-Compressed-Size": str(compressed_size),
"X-Savings-Bytes": str(savings)
}
)
except Exception as e:
# Catch unexpected errors (corrupt files, memory issues)
raise HTTPException(500, detail=f"Image processing failed: {str(e)}")