"""Request bodies. Field names mirror the TypeScript request objects (camelCase). Every model forbids unknown fields; that is what keeps a target-face image out of synthesizeIdentity. """ from __future__ import annotations from typing import Annotated, Literal from pydantic import BaseModel, ConfigDict, Field, StrictBool, model_validator Ms = Annotated[int, Field(ge=0, le=7 * 24 * 3600 * 1000)] Unit = Annotated[float, Field(ge=0.0, le=1.0)] Path = Annotated[str, Field(min_length=1, max_length=4096)] SampleFps = Annotated[float, Field(gt=0.0, le=60.0)] TimesMs = Annotated[list[Ms], Field(max_length=100_000)] class Strict(BaseModel): model_config = ConfigDict(extra="forbid", strict=True) class NormalizedBox(Strict): x: Unit y: Unit w: Unit h: Unit class NormalizedPoint(Strict): x: Unit y: Unit class BoxKeyframe(Strict): tMs: Ms box: NormalizedBox class PathRequest(Strict): path: Path class TranscribeRequest(PathRequest): language: Annotated[str, Field(min_length=2, max_length=16, pattern=r"^[A-Za-z-]+$")] | None = None class DetectFacesRequest(PathRequest): sampleFps: SampleFps timesMs: TimesMs | None = None class TrackFacesRequest(PathRequest): sampleFps: SampleFps class TimesRequest(PathRequest): timesMs: TimesMs class ClassifyContentRequest(PathRequest): sampleFps: SampleFps extraTimesMs: TimesMs | None = None class MarkPrompt(Strict): tMs: Ms box: NormalizedBox | None = None point: NormalizedPoint | None = None @model_validator(mode="after") def needs_box_or_point(self) -> MarkPrompt: if self.box is None and self.point is None: raise ValueError("prompt needs a box or a point") return self class SegmentPromptedRequest(PathRequest): prompt: MarkPrompt propagate: StrictBool outDir: Path class InpaintVideoRequest(PathRequest): maskDir: Path outPath: Path RedactionMode = Literal[ "blur", "pixelate", "solid", "sticker", "partial_eyes", "partial_upper", "partial_lower", "cover" ] class LandmarkFrame(Strict): tMs: Ms points: Annotated[list[NormalizedPoint], Field(max_length=512)] class RedactionInstruction(Strict): keyframes: Annotated[list[BoxKeyframe], Field(max_length=200_000)] mode: RedactionMode paddingPct: Annotated[float, Field(ge=0.0, le=100.0)] featherPct: Annotated[float, Field(ge=0.0, le=50.0)] color: Annotated[str, Field(pattern=r"^#[0-9a-fA-F]{6}$")] | None = None landmarks: Annotated[list[LandmarkFrame], Field(max_length=200_000)] | None = None maskDir: Path | None = None @model_validator(mode="after") def needs_region(self) -> RedactionInstruction: if not self.keyframes and not self.maskDir: raise ValueError("redaction needs keyframes or a maskDir") return self class RedactRequest(PathRequest): redactions: Annotated[list[RedactionInstruction], Field(min_length=1, max_length=1000)] outPath: Path class SynthesizeIdentityRequest(PathRequest): track: Annotated[list[BoxKeyframe], Field(min_length=1, max_length=200_000)] seed: Annotated[int, Field(ge=-(2**53 - 1), le=2**53 - 1)] outPath: Path