FastAPI wrapper around FaceFusion v3.5.3 submodule with: - Sync and async (job-based) processing endpoints - FaceFusion bridge with manual key registration and Lock-serialized processing - Multi-target Dockerfile (CPU + CUDA GPU) - Docker Compose configs for dev, prod-cpu, and prod-gpu - Gitea CI/CD workflow with dual image builds - All 11 FaceFusion processors supported via options API Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
71 lines
2.2 KiB
Python
71 lines
2.2 KiB
Python
import json
|
|
import logging
|
|
from time import time
|
|
from typing import List, Optional
|
|
|
|
from fastapi import APIRouter, File, Form, HTTPException, UploadFile
|
|
from fastapi.responses import FileResponse
|
|
|
|
from app.schemas.process import ProcessingOptions
|
|
from app.services import facefusion_bridge, file_manager
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix='/api/v1', tags=['processing'])
|
|
|
|
|
|
@router.post('/process')
|
|
async def process_sync(
|
|
target: UploadFile = File(...),
|
|
source: Optional[List[UploadFile]] = File(None),
|
|
options: Optional[str] = Form(None),
|
|
):
|
|
"""Synchronous face processing. Returns the result file directly."""
|
|
request_dir = file_manager.create_request_dir()
|
|
|
|
try:
|
|
# Parse options
|
|
parsed_options = None
|
|
if options:
|
|
try:
|
|
parsed_options = json.loads(options)
|
|
ProcessingOptions(**parsed_options) # validate
|
|
except (json.JSONDecodeError, Exception) as e:
|
|
raise HTTPException(status_code=422, detail=f'Invalid options: {e}')
|
|
|
|
# Save uploads
|
|
target_path = await file_manager.save_upload(target, request_dir)
|
|
source_paths = []
|
|
if source:
|
|
source_paths = await file_manager.save_uploads(source, request_dir)
|
|
|
|
output_path = file_manager.generate_output_path(target_path)
|
|
|
|
# Build args and process
|
|
args = facefusion_bridge.build_args_from_options(
|
|
source_paths=source_paths,
|
|
target_path=target_path,
|
|
output_path=output_path,
|
|
options=parsed_options,
|
|
)
|
|
|
|
start_time = time()
|
|
facefusion_bridge.process_sync(args)
|
|
processing_time = time() - start_time
|
|
|
|
logger.info(f'Sync processing completed in {processing_time:.2f}s')
|
|
|
|
return FileResponse(
|
|
path=output_path,
|
|
media_type='application/octet-stream',
|
|
filename=target.filename,
|
|
headers={'X-Processing-Time': f'{processing_time:.2f}'},
|
|
)
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f'Processing failed: {e}')
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
finally:
|
|
file_manager.cleanup_directory(request_dir)
|