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)