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
170 lines
8.1 KiB
Markdown
170 lines
8.1 KiB
Markdown
# AI 数字人前后端接口契约(#1797 / #1822)
|
||
|
||
> 分支:`fix/ai-avatar-v3-1797`
|
||
> 范围:TTS→对口型链路打通、语速/情绪透传、封面智能选帧、标题字段对齐
|
||
> 本文档为前后端联调的唯一字段口径。
|
||
|
||
---
|
||
|
||
## 1. 对口型创建接口 `POST /api/v1/lipsync/jobs`
|
||
|
||
支持两种输入模式,**二选一**:
|
||
|
||
### 模式 A(推荐):TTS 直生 —— 传音色 + 文案,后端内部合成音频
|
||
|
||
前端无需先调 TTS。后端收到请求后:先调 CosyVoice 合成音频 → 转存 OSS → 再提交 MediaKit 对口型。
|
||
|
||
```jsonc
|
||
{
|
||
"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:直接音频 —— 前端已准备好音频
|
||
|
||
```jsonc
|
||
{
|
||
"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 的 `rate`
|
||
- `emotion`: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`
|
||
|
||
独立接口,**不依赖渲染任务**,前端「智能获取封面」按钮直接调用。
|
||
|
||
**请求**
|
||
```jsonc
|
||
{
|
||
"video_url": "https://oss.../avatar_output.mp4", // 必填,数字人视频
|
||
"max_frames": 5 // 可选,抽帧数量 1~10,默认 5
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
```jsonc
|
||
{
|
||
"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`
|
||
|
||
```jsonc
|
||
{
|
||
"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. 前端对接清单
|
||
|
||
1. 对口型:改用**模式 A**(voice_id + script_text + speed + emotion),不要再先调 TTS 拿 audio_url。
|
||
2. 音色 ID:`voice_id` 可直接传克隆音色的 profile UUID,后端会解析为 CosyVoice voice_id(与 /tts 一致)。
|
||
3. 情绪下拉:自然/兴奋/沉稳/亲切 → natural/excited/calm/friendly。
|
||
4. 封面:点「智能获取封面」→ POST `/ai-avatar/render/smart-cover`,用返回的 `cover_url`。
|
||
5. 渲染:手动文案直生场景 `script_id` 留空;标题传**单个** `title_config` dict(字段见 §6)。
|
||
6. 轮询:识别 `running` 等中间态,不要只认 `submitted`。
|