FastAPI async wrapper for Freepik cloud AI API supporting image generation (Mystic, Flux Dev/Pro, SeedReam), video generation (Kling, MiniMax, Seedance), image editing (upscale, relight, style transfer, expand, inpaint), and utilities (background removal, classifier, audio isolation). Includes async task tracking with polling, Docker containerization, and Gitea CI/CD workflow. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
121 lines
4.2 KiB
Python
121 lines
4.2 KiB
Python
import asyncio
|
|
import logging
|
|
from datetime import datetime, timezone
|
|
|
|
from fastapi import APIRouter, HTTPException, Query
|
|
|
|
from app.schemas.common import TaskDetail, TaskResponse, TaskStatus
|
|
from app.schemas.image_editing import (
|
|
ExpandRequest,
|
|
InpaintRequest,
|
|
RelightRequest,
|
|
StyleTransferRequest,
|
|
UpscaleCreativeRequest,
|
|
UpscalePrecisionRequest,
|
|
)
|
|
from app.services import freepik_client, task_tracker
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix='/api/v1/edit', tags=['image-editing'])
|
|
|
|
|
|
async def _submit_and_respond(result: dict, sync: bool, metadata: dict) -> TaskResponse | TaskDetail:
|
|
data = result.get('data', result)
|
|
freepik_task_id = str(data.get('task_id') or data.get('id', ''))
|
|
if not freepik_task_id:
|
|
raise HTTPException(status_code=502, detail='No task_id in Freepik response')
|
|
|
|
internal_id = task_tracker.submit(freepik_task_id, metadata)
|
|
|
|
if not sync:
|
|
return TaskResponse(
|
|
task_id=internal_id,
|
|
status=TaskStatus.pending,
|
|
created_at=datetime.now(timezone.utc),
|
|
)
|
|
|
|
from app.config import settings
|
|
elapsed = 0
|
|
while elapsed < settings.task_poll_timeout_seconds:
|
|
await asyncio.sleep(2)
|
|
elapsed += 2
|
|
task = task_tracker.get_task(internal_id)
|
|
if not task:
|
|
raise HTTPException(status_code=500, detail='Task disappeared')
|
|
if task['status'] == TaskStatus.completed:
|
|
return TaskDetail(
|
|
task_id=internal_id,
|
|
status=TaskStatus.completed,
|
|
created_at=task['created_at'],
|
|
updated_at=task['updated_at'],
|
|
progress=1.0,
|
|
result_url=f'/api/v1/tasks/{internal_id}/result',
|
|
)
|
|
if task['status'] == TaskStatus.failed:
|
|
raise HTTPException(status_code=502, detail=task.get('error', 'Task failed'))
|
|
|
|
raise HTTPException(status_code=504, detail='Task did not complete in time')
|
|
|
|
|
|
@router.post('/upscale/creative', response_model=TaskResponse)
|
|
async def upscale_creative(request: UpscaleCreativeRequest, sync: bool = Query(False)):
|
|
result = await freepik_client.upscale_creative(
|
|
image=request.image,
|
|
prompt=request.prompt,
|
|
scale=request.scale,
|
|
creativity=request.creativity,
|
|
resemblance=request.resemblance,
|
|
)
|
|
return await _submit_and_respond(result, sync, {'operation': 'upscale-creative'})
|
|
|
|
|
|
@router.post('/upscale/precision', response_model=TaskResponse)
|
|
async def upscale_precision(request: UpscalePrecisionRequest, sync: bool = Query(False)):
|
|
result = await freepik_client.upscale_precision(
|
|
image=request.image,
|
|
scale=request.scale,
|
|
)
|
|
return await _submit_and_respond(result, sync, {'operation': 'upscale-precision'})
|
|
|
|
|
|
@router.post('/relight', response_model=TaskResponse)
|
|
async def relight(request: RelightRequest, sync: bool = Query(False)):
|
|
result = await freepik_client.relight_image(
|
|
image=request.image,
|
|
prompt=request.prompt,
|
|
light_source=request.light_source,
|
|
intensity=request.intensity,
|
|
)
|
|
return await _submit_and_respond(result, sync, {'operation': 'relight'})
|
|
|
|
|
|
@router.post('/style-transfer', response_model=TaskResponse)
|
|
async def style_transfer(request: StyleTransferRequest, sync: bool = Query(False)):
|
|
result = await freepik_client.style_transfer(
|
|
image=request.image,
|
|
style_reference=request.style_reference,
|
|
strength=request.strength,
|
|
)
|
|
return await _submit_and_respond(result, sync, {'operation': 'style-transfer'})
|
|
|
|
|
|
@router.post('/expand', response_model=TaskResponse)
|
|
async def expand(request: ExpandRequest, sync: bool = Query(False)):
|
|
result = await freepik_client.expand_image(
|
|
image=request.image,
|
|
prompt=request.prompt,
|
|
direction=request.direction,
|
|
)
|
|
return await _submit_and_respond(result, sync, {'operation': 'expand'})
|
|
|
|
|
|
@router.post('/inpaint', response_model=TaskResponse)
|
|
async def inpaint(request: InpaintRequest, sync: bool = Query(False)):
|
|
result = await freepik_client.inpaint(
|
|
image=request.image,
|
|
mask=request.mask,
|
|
prompt=request.prompt,
|
|
)
|
|
return await _submit_and_respond(result, sync, {'operation': 'inpaint'})
|