a27ed596b4
- 对口型支持 TTS 直生模式:POST /lipsync/jobs 传 voice_id+script_text(+speed/emotion), 后端内部调 CosyVoice 合成音频→转存OSS→提交 MediaKit;保留 audio_url 直接音频模式 - cosyvoice_service: 新增 emotion 参数 + normalize_emotion(中文 自然/兴奋/沉稳/亲切 映射 natural/excited/calm/friendly),空值不透传 - TTS 链路 speed/emotion 全链路透传:schema→use_case(metadata)→workflow(单段/分段/重合成) →submit_synthesize_task(rate/emotion);preview 即时试听同步 - lipsync refresh: 中间状态(running/processing)同步DB;completed 输出视频转存自家OSS防过期 - 修复 lipsync/ai-avatar 路由 current_user.id → current_user.user.id(AuthenticatedUser 无 .id) - 新增 POST /ai-avatar/render/smart-cover 独立封面接口:复用 MediaKit extract_frames + cover_frame_scorer 评分选最佳帧(非 FFmpeg 首帧),渲染管线封面同样优先智能选帧 - 迁移 073: lipsync_jobs 增加 voice_id/script_text/speed/emotion 列,audio_url 改可空 - docs/ai-avatar-api-contract-1822.md: 前后端接口契约 + title_config 字段清单 - 新增 11 个单测(情绪归一化/payload透传/TTS直生/中间状态/智能封面),相关 82 测试全绿 Refs #1797 #1822
122 lines
3.5 KiB
Python
122 lines
3.5 KiB
Python
"""TTS 合成 API Schema。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from datetime import datetime
|
||
from typing import Any, Dict, List, Optional
|
||
|
||
from pydantic import BaseModel, Field
|
||
|
||
|
||
class TTSSynthesizeRequest(BaseModel):
|
||
"""TTS 合成请求。"""
|
||
|
||
text: str = Field(..., min_length=1, max_length=10000, description="合成文本")
|
||
voice_id: str = Field("", description="音色 ID")
|
||
output_name: str = Field("", description="输出文件名")
|
||
language: str = Field("zh-CN", description="语言")
|
||
speed: float = Field(1.0, ge=0.5, le=2.0, description="语速")
|
||
emotion: str = Field("", description="情绪(natural/excited/calm/friendly,或中文 自然/兴奋/沉稳/亲切)")
|
||
voice_model: str = Field("", description="语音模型名称")
|
||
voice_clone_profile_id: str = Field("", description="关联的音色克隆档案 ID")
|
||
format: str = Field("mp3", description="输出格式(mp3/wav/pcm)")
|
||
metadata_: Optional[Dict[str, Any]] = Field(default=None, alias="metadata", description="额外元数据")
|
||
|
||
class Config:
|
||
populate_by_name = True
|
||
|
||
|
||
class TTSJobResponse(BaseModel):
|
||
"""TTS 任务响应。"""
|
||
|
||
id: str
|
||
user_id: str
|
||
input_text: str
|
||
voice_id: str = ""
|
||
voice_model: str = ""
|
||
project_id: str = ""
|
||
voice_clone_profile_id: str = ""
|
||
status: str
|
||
output_audio_url: str = ""
|
||
output_audio_key: str = ""
|
||
duration: float = 0.0
|
||
file_size: int = 0
|
||
sample_rate: int = 22050
|
||
format: str = "mp3"
|
||
error_message: str = ""
|
||
retry_count: int = 0
|
||
max_retries: int = 3
|
||
metadata_: Optional[Dict[str, Any]] = Field(default=None, alias="metadata", description="额外元数据")
|
||
started_at: Optional[datetime] = None
|
||
completed_at: Optional[datetime] = None
|
||
created_at: datetime
|
||
updated_at: datetime
|
||
|
||
class Config:
|
||
populate_by_name = True
|
||
|
||
|
||
class TTSStatusResponse(BaseModel):
|
||
"""TTS 任务状态响应(用于轮询)。"""
|
||
|
||
id: str
|
||
status: str
|
||
output_audio_url: str = ""
|
||
error_message: str = ""
|
||
duration: float = 0.0
|
||
retry_count: int = 0
|
||
created_at: datetime
|
||
updated_at: datetime
|
||
|
||
|
||
class TTSSynthesizeResponse(BaseModel):
|
||
"""TTS 合成创建响应。"""
|
||
|
||
job_id: str
|
||
status: str
|
||
message: str = "合成任务已创建"
|
||
|
||
|
||
class ListTTSJobResponse(BaseModel):
|
||
"""TTS 任务列表响应。"""
|
||
|
||
items: List[TTSJobResponse]
|
||
total: int
|
||
page: int
|
||
page_size: int
|
||
|
||
|
||
class SaveToLibraryRequest(BaseModel):
|
||
"""保存到配音库请求。"""
|
||
|
||
name: Optional[str] = Field(None, description="配音素材名称,留空则自动生成")
|
||
|
||
|
||
class SaveToLibraryResponse(BaseModel):
|
||
"""保存到配音库响应。"""
|
||
|
||
id: str
|
||
name: str
|
||
audio_url: str
|
||
duration: float
|
||
voice_id: str
|
||
voice_name: str
|
||
status: str
|
||
|
||
|
||
class TTSPreviewRequest(BaseModel):
|
||
"""TTS 预览(试听)请求。"""
|
||
|
||
text: str = Field(..., min_length=1, max_length=200, description="合成文本,限制 200 字")
|
||
voice_id: str = Field(..., min_length=1, description="音色 ID")
|
||
speed: float = Field(1.0, ge=0.5, le=2.0, description="语速")
|
||
emotion: str = Field("", description="情绪(natural/excited/calm/friendly,或中文)")
|
||
pitch: float = Field(1.0, ge=0.5, le=2.0, description="音调(预留,当前未使用)")
|
||
|
||
|
||
class TTSPreviewResponse(BaseModel):
|
||
"""TTS 预览(试听)响应。"""
|
||
|
||
audio_url: str = Field(..., description="合成音频 URL")
|
||
duration: Optional[float] = Field(default=None, description="音频时长(秒)")
|