4d09bd630e
- 文档 §5/§6:标题是单个 title_config dict(非 titles[] 数组),字段以 build_title_drawtext_filter() 为准:text/content、font_size/size、 font_color/color、position(top/center/bottom/custom)、pos_x/pos_y、bold、 stroke、shadow、enabled;删除不存在的 titles[]/fontSize/frame/start/end 描述 - script_id 改为可选:手动输入文案/TTS 直生场景不关联文案库条目,留空不校验, 避免手动文案用户被 ScriptNotFound 卡住;选了文案库时仍做归属校验 - 迁移 074:ai_avatar_render_jobs.script_id 默认空串 - 新增 schema 单测(script_id 空/空白/title_config 单 dict),94 测试全绿 Refs #1797 #1826
8.1 KiB
8.1 KiB
AI 数字人前后端接口契约(#1797 / #1822)
分支:
fix/ai-avatar-v3-1797范围:TTS→对口型链路打通、语速/情绪透传、封面智能选帧、标题字段对齐 本文档为前后端联调的唯一字段口径。
1. 对口型创建接口 POST /api/v1/lipsync/jobs
支持两种输入模式,二选一:
模式 A(推荐):TTS 直生 —— 传音色 + 文案,后端内部合成音频
前端无需先调 TTS。后端收到请求后:先调 CosyVoice 合成音频 → 转存 OSS → 再提交 MediaKit 对口型。
{
"video_url": "https://oss.../person.mp4", // 必填,人物视频(MP4)
"voice_id": "cosyvoice-v3-flash-99-xxxx", // 必填,音色 ID(预置音色 或 克隆 profile UUID)
"script_text": "省是浙江省,市是永康市……", // 必填,要合成的文案
"speed": 1.0, // 可选,语速 0.5~2.0,默认 1.0
"emotion": "excited", // 可选,情绪,见 §3
"enable_video_loop": false, // 可选,音频长于视频时是否循环画面
"project_id": "" // 可选
}
模式 B:直接音频 —— 前端已准备好音频
{
"video_url": "https://oss.../person.mp4", // 必填
"audio_url": "https://oss.../voice.mp3", // 必填,mp3/aac/wav/m4a/flac
"enable_video_loop": false
}
校验与错误码
| 场景 | HTTP | detail.code |
|---|---|---|
| 既无 audio_url 又无 voice_id+script_text | 422 | (schema 校验) |
| video_url 非 MP4 / audio_url 格式不支持 | 422 | (schema 校验) |
| 克隆音色不属于当前用户 | 403 | VoiceForbidden |
| 克隆音色尚未合成完成 | 400 | VoiceNotReady |
| TTS 合成失败(如 CosyVoice 欠费) | 502 | TTSSynthesisFailed |
| MediaKit 提交失败 | 502 | *(透传 MediaKit code) |
轮询
GET /api/v1/lipsync/jobs/{id}:非终态任务先返回 DB 缓存,后台异步刷新 MediaKit(不会阻塞轮询)。status流转:pending→submitted→running/processing(MediaKit 中间态同步)→completed/failed。completed时output_video_url为已转存自家 OSS 的非临时 URL(不会过期)。- 前端每 3s 轮询,命中
completed/failed即停。
2. TTS 合成接口语速/情绪透传
POST /api/v1/tts/synthesize(异步任务)与POST /api/v1/tts/preview(即时试听)均新增:speed:float,0.5~2.0,默认 1.0 → 透传 CosyVoice payload 的rateemotion:string,见 §3 映射 → 透传emotion
- 透传链路:
route → CreateTTSJobUseCase(metadata) → TTSJobWorkflow.start_synthesis / 分段合成 → CosyVoiceService.submit_synthesize_task(rate/emotion)。 - 分段合成(长文案)与失败重合成路径同样透传 speed/emotion。
3. 情绪枚举(前后端统一)
前端把中文选项映射成英文后传后端;后端同时接受中文/英文,非法值忽略(走默认自然)。
| 前端选项 | 传参值 | CosyVoice 枚举 |
|---|---|---|
| 自然 | natural |
natural |
| 兴奋 | excited |
excited |
| 沉稳 | calm |
calm |
| 亲切 | friendly |
friendly |
后端 normalize_emotion() 也接受中文(自然/兴奋/沉稳/亲切)做兜底映射。
4. 智能封面接口 POST /api/v1/ai-avatar/render/smart-cover
独立接口,不依赖渲染任务,前端「智能获取封面」按钮直接调用。
请求
{
"video_url": "https://oss.../avatar_output.mp4", // 必填,数字人视频
"max_frames": 5 // 可选,抽帧数量 1~10,默认 5
}
响应
{
"cover_url": "https://oss.../ai-avatar/covers/xxx/cover_yy.jpg", // OSS 非临时 URL
"status": "completed", // completed / fallback_failed
"message": "" // 失败原因
}
实现:复用智能剪辑同款能力 —— MediaKit extract_frames(SpecifiedFrames) 抽 5 帧 → cover_frame_scorer.score_frames(清晰度+亮度+色彩)评分选最佳 → 转存 OSS。
不再使用 FFmpeg 简单首帧。渲染管线最终封面也优先走该智能选帧,MediaKit 不可用时才回退 FFmpeg。
5. 渲染接口 POST /api/v1/ai-avatar/render
{
"lipsync_job_id": "7c29a3b2-...", // 必填,已 completed 的对口型任务
"script_id": "", // 可选!见下方说明
"b_roll_segments": [], // 可选,B-roll 片段
"title_config": { ... }, // 可选,单个标题配置 dict(见 §6)
"cover_config": { ... }, // 可选,封面配置(建议改用 smart-cover)
"project_id": ""
}
script_id 是否必填:可选。
- 从文案库选了文案时传对应文案 ID(后端做归属校验)。
- 手动输入文案、走 TTS 直生模式时不传(留空)即可——渲染管线不依赖文案内容,
script_id仅用于归属校验。留空不会卡手动文案用户。
6. 标题配置 title_config 字段清单(以 build_title_drawtext_filter 为准)
渲染请求收的是单个 title_config dict(不是 titles[] 数组),字段与 packages/domain/video_filter_builder.py 的 build_title_drawtext_filter() 完全对齐:
| 字段 | 别名 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|---|
text |
content |
string | ✅ | — | 标题文字;为空或 enabled=false 时不渲染标题 |
enabled |
— | bool | ❌ | true |
是否启用标题;false 跳过 |
font |
font_preset |
string | ❌ | 思源黑体 | 字体名(后端按名字解析字体文件) |
font_size |
size |
int | ❌ | 36 | 字号(像素) |
font_color |
color |
string | ❌ | #ffffff |
文字颜色,#RRGGBB;后端自动去掉 #,也可传 RRGGBB 或颜色名 |
position |
— | string | ❌ | top |
预设位置:top(y=50) / center(垂直居中) / bottom(底部上移50px) / custom |
pos_x |
— | int/float | ❌ | — | 自定义 X 坐标(像素),仅 position=custom 生效 |
pos_y |
— | int/float | ❌ | — | 自定义 Y 坐标(像素),仅 position=custom 生效 |
bold |
— | bool | ❌ | true |
粗体(Bold 字体变体,回退 borderw 模拟) |
stroke |
— | bool/object | ❌ | — | 描边。true=黑描边宽2;object 见下 |
stroke.enabled |
— | bool | ❌ | true | 是否描边 |
stroke.width |
— | int | ❌ | 2 | 描边宽度 |
stroke.color |
— | string | ❌ | #000000 |
描边颜色 |
shadow |
— | bool/object | ❌ | — | 阴影。true=黑色阴影偏移2px;object 见下 |
shadow.enabled |
— | bool | ❌ | true | 是否阴影 |
shadow.color |
— | string | ❌ | #000000 |
阴影颜色 |
shadow.offset_x |
— | int | ❌ | 2 | 阴影 X 偏移 |
shadow.offset_y |
— | int | ❌ | 2 | 阴影 Y 偏移 |
前端注意事项
- 标题是整条成片一个标题(单个 dict),不是按时间段的标题数组;没有
start/end/frame/fontSize这些字段。 - 位置用
position四档枚举;自由摆放用position="custom"+pos_x/pos_y(像素坐标,非比例)。 - 颜色统一传
#RRGGBB即可,后端会处理#;三档预设位置下标题始终水平居中。 stroke/shadow传true用默认样式,或传 object 精细控制颜色/宽度/偏移。
7. 前端对接清单
- 对口型:改用模式 A(voice_id + script_text + speed + emotion),不要再先调 TTS 拿 audio_url。
- 音色 ID:
voice_id可直接传克隆音色的 profile UUID,后端会解析为 CosyVoice voice_id(与 /tts 一致)。 - 情绪下拉:自然/兴奋/沉稳/亲切 → natural/excited/calm/friendly。
- 封面:点「智能获取封面」→ POST
/ai-avatar/render/smart-cover,用返回的cover_url。 - 渲染:手动文案直生场景
script_id留空;标题传单个title_configdict(字段见 §6)。 - 轮询:识别
running等中间态,不要只认submitted。