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>
This commit is contained in:
2026-02-16 12:58:33 +01:00
commit 800edc08ea
31 changed files with 1784 additions and 0 deletions

0
app/schemas/__init__.py Normal file
View File

31
app/schemas/jobs.py Normal file
View File

@@ -0,0 +1,31 @@
from datetime import datetime
from enum import Enum
from typing import Optional
from pydantic import BaseModel
class JobStatus(str, Enum):
pending = 'pending'
processing = 'processing'
completed = 'completed'
failed = 'failed'
cancelled = 'cancelled'
class JobCreateResponse(BaseModel):
job_id: str
status: JobStatus
class JobStatusResponse(BaseModel):
job_id: str
status: JobStatus
created_at: datetime
updated_at: Optional[datetime] = None
error: Optional[str] = None
class JobDeleteResponse(BaseModel):
job_id: str
deleted: bool

114
app/schemas/process.py Normal file
View File

@@ -0,0 +1,114 @@
from typing import Any, Dict, List, Optional
from pydantic import BaseModel, Field
class FaceSwapperOptions(BaseModel):
model: str = 'hyperswap_1a_256'
pixel_boost: Optional[str] = None
weight: float = 0.5
class FaceEnhancerOptions(BaseModel):
model: str = 'gfpgan_1.4'
blend: int = 80
weight: float = 0.5
class FaceEditorOptions(BaseModel):
model: str = 'live_portrait'
eyebrow_direction: Optional[float] = None
eye_gaze_horizontal: Optional[float] = None
eye_gaze_vertical: Optional[float] = None
eye_open_ratio: Optional[float] = None
lip_open_ratio: Optional[float] = None
mouth_grim: Optional[float] = None
mouth_pout: Optional[float] = None
mouth_purse: Optional[float] = None
mouth_smile: Optional[float] = None
mouth_position_horizontal: Optional[float] = None
mouth_position_vertical: Optional[float] = None
head_pitch: Optional[float] = None
head_yaw: Optional[float] = None
head_roll: Optional[float] = None
class LipSyncerOptions(BaseModel):
model: str = 'wav2lip_96'
class AgeModifierOptions(BaseModel):
model: str = 'styleganex_age'
direction: int = 0
class ExpressionRestorerOptions(BaseModel):
model: str = 'live_portrait'
factor: int = 80
class FrameEnhancerOptions(BaseModel):
model: str = 'span_kendata_1x'
blend: int = 80
class FrameColorizerOptions(BaseModel):
model: str = 'ddcolor'
blend: int = 80
size: str = '256x256'
class BackgroundRemoverOptions(BaseModel):
model: str = 'isnet_general_use'
class FaceDetectorOptions(BaseModel):
model: str = 'yolo_face'
size: str = '640x640'
score: float = 0.5
class FaceSelectorOptions(BaseModel):
mode: str = 'reference'
order: str = 'large-small'
gender: Optional[str] = None
race: Optional[str] = None
age_start: Optional[int] = None
age_end: Optional[int] = None
class OutputOptions(BaseModel):
image_quality: int = 80
image_scale: float = 1.0
video_encoder: Optional[str] = None
video_preset: str = 'veryfast'
video_quality: int = 80
video_scale: float = 1.0
video_fps: Optional[float] = None
audio_encoder: Optional[str] = None
audio_quality: int = 80
audio_volume: int = 100
class ProcessingOptions(BaseModel):
processors: List[str] = Field(default_factory=lambda: ['face_swapper'])
face_swapper: Optional[FaceSwapperOptions] = None
face_enhancer: Optional[FaceEnhancerOptions] = None
face_editor: Optional[FaceEditorOptions] = None
lip_syncer: Optional[LipSyncerOptions] = None
age_modifier: Optional[AgeModifierOptions] = None
expression_restorer: Optional[ExpressionRestorerOptions] = None
frame_enhancer: Optional[FrameEnhancerOptions] = None
frame_colorizer: Optional[FrameColorizerOptions] = None
background_remover: Optional[BackgroundRemoverOptions] = None
face_detector: Optional[FaceDetectorOptions] = None
face_selector: Optional[FaceSelectorOptions] = None
output: Optional[OutputOptions] = None
execution_providers: Optional[List[str]] = None
execution_thread_count: Optional[int] = None
video_memory_strategy: Optional[str] = None
class ProcessingResponse(BaseModel):
output_path: str
processing_time: float

33
app/schemas/system.py Normal file
View File

@@ -0,0 +1,33 @@
from typing import Any, Dict, List, Optional
from pydantic import BaseModel
class HealthResponse(BaseModel):
status: str = 'ok'
class GpuDevice(BaseModel):
id: int
name: str
memory_total: Optional[int] = None
memory_used: Optional[int] = None
class SystemInfoResponse(BaseModel):
execution_providers: List[str]
gpu_devices: List[GpuDevice]
cpu_count: Optional[int] = None
memory_total: Optional[int] = None
memory_available: Optional[int] = None
class ProcessorInfo(BaseModel):
name: str
models: List[str]
class ModelInfo(BaseModel):
name: str
path: str
size_bytes: int