"""对口型 API Schema 定义 — #1796.""" from __future__ import annotations from datetime import datetime from typing import Optional from pydantic import BaseModel, Field, field_validator class LipsyncJobResponse(BaseModel): """对口型任务响应.""" id: str user_id: str project_id: str video_url: str audio_url: str enable_video_loop: bool mediakit_task_id: str status: str output_video_url: str output_duration: float error_message: str error_code: str submitted_at: Optional[datetime] = None completed_at: Optional[datetime] = None created_at: datetime updated_at: datetime class Config: from_attributes = True class CreateLipsyncJobRequest(BaseModel): """创建对口型任务请求.""" video_url: str = Field(..., description="人物视频 URL(MP4,≤30min,单人真人)") audio_url: str = Field(..., description="驱动音频 URL(mp3/aac/wav/m4a/flac)") enable_video_loop: bool = Field(False, description="音频长于视频时是否循环画面") project_id: str = Field("", description="项目 ID(可选)") @field_validator("video_url") @classmethod def validate_video_url(cls, v: str) -> str: v = v.strip() if not v: raise ValueError("video_url 不能为空") if not v.startswith(("http://", "https://")): raise ValueError("video_url 必须是 HTTP/HTTPS URL") # 仅支持 MP4 lower = v.lower().split("?")[0] if not lower.endswith(".mp4"): raise ValueError("video_url 仅支持 MP4 格式") return v @field_validator("audio_url") @classmethod def validate_audio_url(cls, v: str) -> str: v = v.strip() if not v: raise ValueError("audio_url 不能为空") if not v.startswith(("http://", "https://")): raise ValueError("audio_url 必须是 HTTP/HTTPS URL") # 支持的音频格式 lower = v.lower().split("?")[0] allowed_exts = (".mp3", ".aac", ".wav", ".m4a", ".flac") if not any(lower.endswith(ext) for ext in allowed_exts): raise ValueError(f"audio_url 格式不支持,仅支持: {', '.join(allowed_exts)}") return v