Files
facefusion-api/app/routers/process.py
Sebastian Krüger 800edc08ea Initial commit: FaceFusion REST API
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>
2026-02-16 12:58:33 +01:00

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)