|
|
"""Creative upload, analyze, and modify endpoints.""" |
|
|
|
|
|
import os |
|
|
from datetime import datetime |
|
|
from fastapi import APIRouter, HTTPException, Depends, File, UploadFile |
|
|
|
|
|
from api.schemas import ( |
|
|
CreativeAnalyzeRequest, |
|
|
CreativeAnalysisResponse, |
|
|
CreativeModifyRequest, |
|
|
CreativeModifyResponse, |
|
|
FileUploadResponse, |
|
|
) |
|
|
from services.creative_modifier import creative_modifier_service |
|
|
from services.image import image_service |
|
|
from services.auth_dependency import get_current_user |
|
|
from config import settings |
|
|
|
|
|
router = APIRouter(tags=["creative"]) |
|
|
|
|
|
|
|
|
@router.post("/api/creative/upload", response_model=FileUploadResponse) |
|
|
async def upload_creative( |
|
|
file: UploadFile = File(...), |
|
|
username: str = Depends(get_current_user), |
|
|
): |
|
|
""" |
|
|
Upload a creative image for analysis and modification. |
|
|
Accepts PNG, JPG, JPEG, WebP. Returns image URL for subsequent steps. |
|
|
""" |
|
|
allowed_types = ["image/png", "image/jpeg", "image/jpg", "image/webp"] |
|
|
if file.content_type not in allowed_types: |
|
|
raise HTTPException(status_code=400, detail=f"Invalid file type. Allowed: PNG, JPG, JPEG, WebP. Got: {file.content_type}") |
|
|
contents = await file.read() |
|
|
if len(contents) > 10 * 1024 * 1024: |
|
|
raise HTTPException(status_code=400, detail="File too large. Maximum size is 10MB.") |
|
|
try: |
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") |
|
|
unique_id = __import__("uuid").uuid4().hex[:8] |
|
|
ext = file.filename.split(".")[-1] if file.filename else "png" |
|
|
filename = f"upload_{username}_{timestamp}_{unique_id}.{ext}" |
|
|
r2_url = None |
|
|
try: |
|
|
from services.r2_storage import get_r2_storage |
|
|
r2_storage = get_r2_storage() |
|
|
if r2_storage: |
|
|
r2_url = r2_storage.upload_image(image_bytes=contents, filename=filename, niche="uploads") |
|
|
except Exception: |
|
|
pass |
|
|
if not r2_url: |
|
|
local_path = os.path.join(settings.output_dir, filename) |
|
|
os.makedirs(os.path.dirname(local_path), exist_ok=True) |
|
|
with open(local_path, "wb") as f: |
|
|
f.write(contents) |
|
|
r2_url = f"/images/{filename}" |
|
|
return {"status": "success", "image_url": r2_url, "filename": filename} |
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
|
|
|
|
@router.post("/api/creative/analyze", response_model=CreativeAnalysisResponse) |
|
|
async def analyze_creative( |
|
|
request: CreativeAnalyzeRequest, |
|
|
username: str = Depends(get_current_user), |
|
|
): |
|
|
"""Analyze a creative image using AI vision (via URL).""" |
|
|
if not request.image_url: |
|
|
raise HTTPException(status_code=400, detail="image_url must be provided") |
|
|
try: |
|
|
image_bytes = await image_service.load_image(image_url=request.image_url) |
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=400, detail=f"Failed to fetch image from URL: {e}") |
|
|
if not image_bytes: |
|
|
raise HTTPException(status_code=400, detail="Failed to load image") |
|
|
try: |
|
|
result = await creative_modifier_service.analyze_creative(image_bytes) |
|
|
if result["status"] != "success": |
|
|
return CreativeAnalysisResponse(status="error", error=result.get("error", "Analysis failed")) |
|
|
return CreativeAnalysisResponse( |
|
|
status="success", |
|
|
analysis=result.get("analysis"), |
|
|
suggested_angles=result.get("suggested_angles"), |
|
|
suggested_concepts=result.get("suggested_concepts"), |
|
|
) |
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
|
|
|
|
@router.post("/api/creative/analyze/upload", response_model=CreativeAnalysisResponse) |
|
|
async def analyze_creative_upload( |
|
|
file: UploadFile = File(...), |
|
|
username: str = Depends(get_current_user), |
|
|
): |
|
|
"""Analyze a creative image using AI vision (via file upload).""" |
|
|
allowed_types = ["image/png", "image/jpeg", "image/jpg", "image/webp"] |
|
|
if file.content_type not in allowed_types: |
|
|
raise HTTPException(status_code=400, detail=f"Invalid file type. Allowed: PNG, JPG, JPEG, WebP. Got: {file.content_type}") |
|
|
image_bytes = await file.read() |
|
|
if not image_bytes: |
|
|
raise HTTPException(status_code=400, detail="Failed to load image") |
|
|
try: |
|
|
result = await creative_modifier_service.analyze_creative(image_bytes) |
|
|
if result["status"] != "success": |
|
|
return CreativeAnalysisResponse(status="error", error=result.get("error", "Analysis failed")) |
|
|
return CreativeAnalysisResponse( |
|
|
status="success", |
|
|
analysis=result.get("analysis"), |
|
|
suggested_angles=result.get("suggested_angles"), |
|
|
suggested_concepts=result.get("suggested_concepts"), |
|
|
) |
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
|
|
|
|
@router.post("/api/creative/modify", response_model=CreativeModifyResponse) |
|
|
async def modify_creative( |
|
|
request: CreativeModifyRequest, |
|
|
username: str = Depends(get_current_user), |
|
|
): |
|
|
""" |
|
|
Modify a creative with angle and/or concept. |
|
|
Modes: 'modify' (image-to-image) or 'inspired' (new generation). |
|
|
""" |
|
|
if not request.angle and not request.concept: |
|
|
raise HTTPException(status_code=400, detail="At least one of 'angle' or 'concept' must be provided") |
|
|
analysis = request.analysis |
|
|
if not analysis: |
|
|
try: |
|
|
image_bytes = await image_service.load_image(image_url=request.image_url) |
|
|
if not image_bytes: |
|
|
raise HTTPException(status_code=400, detail="Failed to load image from URL") |
|
|
analysis_result = await creative_modifier_service.analyze_creative(image_bytes) |
|
|
if analysis_result["status"] != "success": |
|
|
raise HTTPException(status_code=500, detail=analysis_result.get("error", "Analysis failed")) |
|
|
analysis = analysis_result.get("analysis", {}) |
|
|
except HTTPException: |
|
|
raise |
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=f"Failed to analyze image: {e}") |
|
|
try: |
|
|
result = await creative_modifier_service.modify_creative( |
|
|
image_url=request.image_url, |
|
|
analysis=analysis, |
|
|
user_angle=request.angle, |
|
|
user_concept=request.concept, |
|
|
mode=request.mode, |
|
|
image_model=request.image_model, |
|
|
user_prompt=request.user_prompt, |
|
|
) |
|
|
if result["status"] != "success": |
|
|
return CreativeModifyResponse(status="error", error=result.get("error", "Modification failed")) |
|
|
return CreativeModifyResponse(status="success", prompt=result.get("prompt"), image=result.get("image")) |
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=str(e)) |
|
|
|