feat: 收口角色动作资产发布前端与验证文档
This commit is contained in:
@@ -0,0 +1,315 @@
|
||||
# 图片、视频、动作外部生成手动验证运行手册
|
||||
|
||||
日期:`2026-04-23`
|
||||
|
||||
## 1. 文档目的
|
||||
|
||||
这份文档用于冻结 `验证清单.md` 第四项“图片、视频、动作的生成要真实走到外部服务的生成服务上,而不是用占位符来敷衍”的验证口径。
|
||||
|
||||
本次先解决两个问题:
|
||||
|
||||
1. 当前仓库里“真实外部生成链”和“Stage 1 占位兼容链”同时存在,若不先写清楚,很容易把占位产物误记为通过。
|
||||
2. 现有技术设计文档描述了多条资产链,但没有一份面向人工联调的统一运行手册,导致每次验证都要重新猜入口、猜日志、猜通过标准。
|
||||
|
||||
## 2. 当前结论总览
|
||||
|
||||
截至 `2026-04-23` 当前代码状态,第 4 项仍不能整体直接判定“已通过”,原因是不同资产链状态不同。
|
||||
|
||||
### 2.1 当前已经接入真实外部图片生成的入口
|
||||
|
||||
以下入口当前会真实请求外部图片生成服务,而不是只生成本地占位图:
|
||||
|
||||
1. `Big Fish` 结果页:
|
||||
- `生成背景`
|
||||
- `生成并应用正式图` -> `Lv.x 主图`
|
||||
- `生成并应用正式图` -> `Lv.x 动作工坊`
|
||||
2. `custom world / RPG 创作`:
|
||||
- 场景图生成
|
||||
- 作品封面 AI 生成
|
||||
|
||||
这些入口当前统一会走 Rust `api-server`,并向 DashScope 图片生成接口发起请求,再落到 OSS 与兼容读路径。
|
||||
|
||||
### 2.2 当前仍未完全闭环的入口
|
||||
|
||||
以下入口当前仍不能直接判定为“动作资产全后端闭环”:
|
||||
|
||||
1. 角色资产工坊 `image-sequence`
|
||||
- 当前生成的是服务端 SVG 帧,不是真实外部序列图模型结果。
|
||||
2. 角色资产工坊 `motion-transfer / reference-to-video`
|
||||
- 当前仍未接入真实外部模型主链。
|
||||
3. 角色资产工坊 `image-to-video`
|
||||
- 当前已真实请求 Ark 生成 OSS 草稿区 `preview.mp4`。
|
||||
- 但正式帧抽取和去绿幕仍在前端浏览器完成,再回传后端发布。
|
||||
|
||||
因此:
|
||||
|
||||
1. 第 4 项里“图片真实外部生成”目前可以做人工验证。
|
||||
2. 第 4 项里“视频真实外部生成”已有 `image-to-video` 主链证据,但“动作正式资产全后端闭环”仍需要继续验证与收口,不能把前端抽帧回传链直接记成完全通过。
|
||||
|
||||
## 3. 代码级判定依据
|
||||
|
||||
### 3.1 已接真实外部图片服务的依据
|
||||
|
||||
#### 3.1.1 Big Fish 正式图片链
|
||||
|
||||
`server-rs/crates/api-server/src/big_fish.rs`
|
||||
|
||||
当前 `generate_big_fish_formal_asset(...)` 会执行:
|
||||
|
||||
1. 读取 Big Fish 草稿 prompt
|
||||
2. 调用 `require_big_fish_dashscope_settings(...)`
|
||||
3. 调用 `create_big_fish_text_to_image_generation(...)`
|
||||
4. 向 DashScope `text2image/image-synthesis` 发起异步任务请求
|
||||
5. 下载远端生成图片
|
||||
6. 上传 OSS
|
||||
7. 确认 `asset_object`
|
||||
8. 绑定到 Big Fish 槽位
|
||||
|
||||
这条链已经不是占位图写盘。
|
||||
|
||||
#### 3.1.2 Custom World 场景图与封面图
|
||||
|
||||
`server-rs/crates/api-server/src/custom_world_ai.rs`
|
||||
|
||||
当前 `create_text_to_image_generation(...)` 与 `create_reference_image_generation(...)` 会:
|
||||
|
||||
1. 真实请求 DashScope 图片生成接口
|
||||
2. 轮询任务状态或解析生成结果
|
||||
3. 下载远端图片
|
||||
4. 上传 OSS
|
||||
5. 生成 `asset_object` 与实体绑定
|
||||
|
||||
因此场景图、AI 封面图当前属于“真实外部图片生成”。
|
||||
|
||||
### 3.2 仍未完全闭环的依据
|
||||
|
||||
#### 3.2.1 角色动作资产工坊
|
||||
|
||||
`server-rs/crates/api-server/src/character_animation_assets.rs`
|
||||
|
||||
当前链路现状:
|
||||
|
||||
1. `image-to-video` 已真实请求 Ark 生成视频
|
||||
2. 成功结果会下载并写入 `generated-character-drafts/*/preview.mp4`
|
||||
3. `publish` 当前仍读取前端传入的 `framesDataUrls`
|
||||
4. 前端仍通过 `HTMLVideoElement + canvas` 自行抽帧并做去绿幕
|
||||
|
||||
因此当前状态应判定为“真实外部视频生成主链已完成,但正式动作资产后端闭环尚未完成”。
|
||||
|
||||
## 4. 本次验证范围
|
||||
|
||||
本次人工验证分成两部分。
|
||||
|
||||
### 4.1 可直接操作并验证通过/失败的范围
|
||||
|
||||
1. Big Fish 主图生成是否真实打到 DashScope
|
||||
2. Big Fish 动作工坊静态关键帧图是否真实打到 DashScope
|
||||
3. Big Fish 背景图是否真实打到 DashScope
|
||||
4. Custom World 场景图是否真实打到 DashScope
|
||||
5. Custom World AI 封面图是否真实打到 DashScope
|
||||
|
||||
### 4.2 本次要明确记录为“未通过”的范围
|
||||
|
||||
1. 角色资产工坊 `生成角色形象`
|
||||
2. 角色资产工坊 `生成动作`
|
||||
3. 任何依赖仓库内占位视频或 SVG 帧的动作生成入口
|
||||
|
||||
这些入口本次可以操作,但只能用于确认“当前仍未完全闭环”的具体断点,不能把前端抽帧回传链计入“动作资产全后端闭环”通过证据。
|
||||
|
||||
## 5. 前置条件
|
||||
|
||||
开始验证前,必须同时满足以下条件:
|
||||
|
||||
1. 仓库根目录 `.env.local` 已配置:
|
||||
- `DASHSCOPE_API_KEY`
|
||||
- `ALIYUN_OSS_BUCKET`
|
||||
- `ALIYUN_OSS_ENDPOINT`
|
||||
- `ALIYUN_OSS_ACCESS_KEY_ID`
|
||||
- `ALIYUN_OSS_ACCESS_KEY_SECRET`
|
||||
2. 本机已安装:
|
||||
- `cargo`
|
||||
- `node`
|
||||
- `spacetime`
|
||||
- `ffmpeg`
|
||||
- `ffprobe`
|
||||
3. 本地端口可用或已有可复用 Rust 栈:
|
||||
- Web:`3000`
|
||||
- Rust API:`8082`
|
||||
- SpacetimeDB:`3101`
|
||||
4. 必须使用 Rust 栈,而不是旧 Node 栈。
|
||||
|
||||
说明:
|
||||
|
||||
1. 当前 Vite 前端必须指向 Rust `api-server`,否则会把验证结果混入旧链路。
|
||||
2. 验证时必须能实时查看 Rust `api-server` 日志。
|
||||
|
||||
## 6. 启动方式
|
||||
|
||||
推荐统一使用:
|
||||
|
||||
```powershell
|
||||
npm run dev:rust
|
||||
```
|
||||
|
||||
该命令会完成以下动作:
|
||||
|
||||
1. 启动本地 `SpacetimeDB standalone`
|
||||
2. 发布 `server-rs/crates/spacetime-module`
|
||||
3. 启动 Rust `api-server`
|
||||
4. 启动 Vite Web 开发服务器
|
||||
|
||||
若已有栈在运行,至少确认:
|
||||
|
||||
1. Web 可访问:`http://127.0.0.1:3000`
|
||||
2. Rust API 为当前前端的实际代理目标
|
||||
3. `api-server` 正在输出日志
|
||||
|
||||
## 7. 手动验证入口
|
||||
|
||||
### 7.1 Big Fish 正式图片链
|
||||
|
||||
前端路径:
|
||||
|
||||
1. 打开 `http://127.0.0.1:3000`
|
||||
2. 进入平台创作入口
|
||||
3. 选择 `Big Fish`
|
||||
4. 先完成草稿编译
|
||||
5. 进入结果页
|
||||
6. 在结果页依次操作:
|
||||
- `生成背景`
|
||||
- 打开某个等级的 `主图工坊`,点击 `生成并应用正式图`
|
||||
- 打开某个等级的 `动作工坊`,点击 `生成并应用正式图`
|
||||
|
||||
期望日志特征:
|
||||
|
||||
1. Rust `api-server` 中出现 `provider = dashscope`
|
||||
2. 有 Big Fish 正式图片生成请求
|
||||
3. 有 DashScope 任务创建或轮询相关日志
|
||||
4. 生成成功后出现 OSS 写入或正式路径返回
|
||||
|
||||
前端期望结果:
|
||||
|
||||
1. 资源 URL 不再是 `/generated-big-fish/...`
|
||||
2. 而是 `/generated-big-fish-assets/...`
|
||||
3. 结果页状态显示为 `已生成`,而不是 `占位已生成`
|
||||
|
||||
### 7.2 Custom World 场景图
|
||||
|
||||
前端路径:
|
||||
|
||||
1. 进入 RPG / Custom World 创作流程
|
||||
2. 打开场景或地标编辑入口
|
||||
3. 点击场景图生成相关操作
|
||||
|
||||
期望日志特征:
|
||||
|
||||
1. Rust `api-server` 中出现 `provider = dashscope`
|
||||
2. 有图片生成任务创建与轮询
|
||||
3. 成功后有 OSS 对象写入和读取兼容路径
|
||||
|
||||
前端期望结果:
|
||||
|
||||
1. 返回图片不是本地 SVG 占位
|
||||
2. 保存后场景主图可稳定显示
|
||||
|
||||
### 7.3 Custom World AI 封面图
|
||||
|
||||
前端路径:
|
||||
|
||||
1. 进入作品编辑页
|
||||
2. 打开 `编辑作品封面`
|
||||
3. 选择 `AI 生成作品封面`
|
||||
4. 输入封面氛围提示词
|
||||
5. 点击生成并保存
|
||||
|
||||
期望日志特征:
|
||||
|
||||
1. Rust `api-server` 中出现 `provider = dashscope`
|
||||
2. 有封面图生成任务
|
||||
3. 成功后有 OSS 上传与对象确认日志
|
||||
|
||||
前端期望结果:
|
||||
|
||||
1. 生成结果可预览
|
||||
2. 保存后作品封面更新为正式图
|
||||
|
||||
### 7.4 角色资产工坊反向验证
|
||||
|
||||
前端路径:
|
||||
|
||||
1. 打开任一角色的 AI 资产工坊
|
||||
2. 点击 `生成角色形象`
|
||||
3. 再点击 `生成动作`
|
||||
|
||||
本入口的验证目标不是“通过”,而是确认它当前仍未接真实外部视频/图片服务。
|
||||
|
||||
期望证据:
|
||||
|
||||
1. `生成角色形象` 返回的是 SVG 草稿候选
|
||||
2. `生成动作` 若未导入参考视频,会回退预置占位视频
|
||||
3. 日志或结果模型字段不应被当作真实外部视频生成通过证据
|
||||
|
||||
## 8. 通过标准
|
||||
|
||||
第 4 项只有在以下条件全部满足时,才能勾成通过:
|
||||
|
||||
1. 至少一条图片生成入口已拿到真实外部服务调用证据。
|
||||
2. 至少一条视频或动作生成入口已拿到真实外部服务调用证据。
|
||||
3. 这些证据不能依赖 SVG 占位、仓库内预置视频或本地占位文件。
|
||||
4. 前端结果能与日志中的正式链路一一对应。
|
||||
|
||||
换言之:
|
||||
|
||||
1. 仅图片链通过,不代表第 4 项整体通过。
|
||||
2. 仅 Big Fish 动作工坊生成出一张静态图,也不等于“视频/动作真实生成”通过。
|
||||
|
||||
## 9. 当前预判结论
|
||||
|
||||
按当前代码基线,本次更可能得到以下结论:
|
||||
|
||||
1. 图片真实外部生成:可以拿到通过证据。
|
||||
2. 视频、动作真实外部生成:`image-to-video` 主链已可拿到真实外部视频生成证据,但正式动作资产后端闭环仍需要继续收口。
|
||||
|
||||
因此本次人工验证完成后,建议把第 4 项拆成至少两条独立清单:
|
||||
|
||||
1. 图片生成真实外部服务验证
|
||||
2. 视频生成真实外部服务验证
|
||||
3. 动作正式资产后端闭环验证
|
||||
|
||||
否则会把“已完成的图片链 / 视频生成链”与“仍未完成的正式动作发布后端闭环”混成一个模糊状态。
|
||||
|
||||
## 10. 失败判定与排查
|
||||
|
||||
### 10.1 图片入口失败
|
||||
|
||||
优先看 Rust `api-server` 日志中的错误文本:
|
||||
|
||||
1. `dashscope api key 未配置`
|
||||
- 说明环境变量缺失。
|
||||
2. `构造 DashScope HTTP 客户端失败`
|
||||
- 说明本地网络或 TLS 运行环境异常。
|
||||
3. `读取生成响应失败`
|
||||
- 说明上游请求已发出,但响应解析失败。
|
||||
4. `下载远端图片失败`
|
||||
- 说明上游已生成图片,但下载或签名读链出错。
|
||||
5. OSS 相关错误
|
||||
- 说明生成已成功,但落 OSS 或确认对象失败。
|
||||
|
||||
### 10.2 角色资产工坊“看起来成功”
|
||||
|
||||
若角色工坊前端看起来成功,不应立刻视为通过,需要先核对:
|
||||
|
||||
1. 当前策略是否是 `image-sequence / motion-transfer / reference-to-video`
|
||||
2. 若是 `image-to-video`,`preview.mp4` 是否来自真实 Ark 生成
|
||||
3. 正式发布是否仍要求前端回传 `framesDataUrls`
|
||||
|
||||
若只是“后端出真实视频、前端再抽帧回传”,则只能记为“视频生成主链通过,正式动作发布后端闭环未完成”,不能直接把整条动作资产链记为完全通过。
|
||||
|
||||
## 11. 关联文档
|
||||
|
||||
1. [BIG_FISH_FORMAL_IMAGE_GENERATION_2026-04-23.md](./BIG_FISH_FORMAL_IMAGE_GENERATION_2026-04-23.md)
|
||||
2. [M6_CHARACTER_VISUAL_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md](./M6_CHARACTER_VISUAL_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md)
|
||||
3. [M6_CHARACTER_ANIMATION_IMPORT_AND_TEMPLATE_STAGE1_2026-04-22.md](./M6_CHARACTER_ANIMATION_IMPORT_AND_TEMPLATE_STAGE1_2026-04-22.md)
|
||||
4. [M6_CUSTOM_WORLD_ASSET_OSS_INTEGRATION_STAGE2_2026-04-22.md](./M6_CUSTOM_WORLD_ASSET_OSS_INTEGRATION_STAGE2_2026-04-22.md)
|
||||
5. [AI_CHARACTER_ANIMATION_TECHNICAL_SOLUTION_2026-04-04.md](./AI_CHARACTER_ANIMATION_TECHNICAL_SOLUTION_2026-04-04.md)
|
||||
6. [M6_CHARACTER_ANIMATION_BACKEND_FRAME_EXTRACTION_AND_PUBLISH_STAGE3_2026-04-23.md](./M6_CHARACTER_ANIMATION_BACKEND_FRAME_EXTRACTION_AND_PUBLISH_STAGE3_2026-04-23.md)
|
||||
@@ -0,0 +1,277 @@
|
||||
# M6 角色动作外部真实生成 Stage 2 设计
|
||||
|
||||
日期:`2026-04-23`
|
||||
|
||||
## 1. 文档目的
|
||||
|
||||
这份文档用于冻结 `POST /api/assets/character-animation/generate` 从 Stage 1 占位动作链切到真实外部动作生成后的实现口径。
|
||||
|
||||
本阶段优先级最高的是当前前端正在使用的 `image-to-video` 主链;如条件允许,再同步补齐 `image-sequence`、`motion-transfer`、`reference-to-video` 的真实实现。
|
||||
|
||||
## 2. 当前问题
|
||||
|
||||
Stage 1 当前存在以下占位行为:
|
||||
|
||||
1. `CHARACTER_ANIMATION_MODEL = "rust-placeholder-character-animation"`
|
||||
2. `image-sequence` 直接产出 SVG 帧
|
||||
3. 视频类策略会复用参考视频或仓库占位预览视频
|
||||
4. 即使返回 `previewVideoPath`,也不代表真实调用了外部视频模型
|
||||
|
||||
这会导致角色资产工坊的动作生成不能通过“真实外部生成”验证。
|
||||
|
||||
## 3. Stage 2 范围
|
||||
|
||||
### 3.1 本阶段必须完成
|
||||
|
||||
1. `image-to-video` 改为真实 Ark 视频生成
|
||||
2. 继续兼容前端当前请求字段
|
||||
3. 继续把预览视频写入 `generated-character-drafts/*`
|
||||
4. 任务查询和正式发布 contract 继续保持兼容
|
||||
5. 删除 `image-to-video` 的占位视频回退
|
||||
|
||||
### 3.2 争取同步完成
|
||||
|
||||
1. `image-sequence` 接真实 DashScope 连续图片生成
|
||||
2. `motion-transfer` 接真实 DashScope 视频驱动
|
||||
3. `reference-to-video` 接真实 DashScope 参考生视频
|
||||
|
||||
### 3.3 本阶段不做
|
||||
|
||||
1. 不新增 SpacetimeDB 动作任务表
|
||||
2. 不改前端调用字段名
|
||||
3. 不把动作生成迁回 Node
|
||||
|
||||
## 4. 主链优先级
|
||||
|
||||
当前前端 `useRoleAnimationWorkflow.ts` 固定使用:
|
||||
|
||||
1. `strategy = image-to-video`
|
||||
2. `videoModel = doubao-seedance-2-0-fast-260128`
|
||||
3. `motionTransferModel = wan2.2-animate-move`
|
||||
4. `referenceVideoModel = wan2.7-r2v`
|
||||
|
||||
因此 Stage 2 的必须项是先把 `image-to-video` 主链迁成真实实现。
|
||||
|
||||
## 5. image-to-video 真实协议
|
||||
|
||||
### 5.1 上游与模型
|
||||
|
||||
`image-to-video` 统一走 Ark:
|
||||
|
||||
1. 基础 URL:`ARK_CHARACTER_VIDEO_BASE_URL` 或 `ARK_BASE_URL`
|
||||
2. 默认 URL:`https://ark.cn-beijing.volces.com/api/v3`
|
||||
3. API Key:`ARK_CHARACTER_VIDEO_API_KEY` 或 `ARK_API_KEY`
|
||||
4. 默认模型:`doubao-seedance-2-0-fast-260128`
|
||||
|
||||
### 5.2 创建任务接口
|
||||
|
||||
请求:
|
||||
|
||||
`POST {ARK_BASE_URL}/contents/generations/tasks`
|
||||
|
||||
请求体固定遵循旧 Node 已验证协议:
|
||||
|
||||
1. `model`
|
||||
2. `content[0] = { type: "text", text: prompt }`
|
||||
3. `content[1] = { type: "image_url", role: "first_frame", image_url: { url } }`
|
||||
4. `content[2] = { type: "image_url", role: "last_frame", image_url: { url } }`
|
||||
5. `resolution = "480p"`
|
||||
6. `ratio = "1:1"`
|
||||
7. `duration = 4`
|
||||
8. `watermark = false`
|
||||
|
||||
说明:
|
||||
|
||||
1. 即使前端传 `720P / 16:9 / 7s`,Stage 2 也必须按现网主链固定为 `480p / 1:1 / 4`
|
||||
2. 这是旧 Node 测试已经冻结的真实协议
|
||||
|
||||
### 5.3 轮询接口
|
||||
|
||||
请求:
|
||||
|
||||
`GET {ARK_BASE_URL}/contents/generations/tasks/{taskId}`
|
||||
|
||||
成功判定:
|
||||
|
||||
1. 返回状态进入 `completed/succeeded/done` 等完成态
|
||||
2. 或响应里已经能抽取到 `video_url`
|
||||
|
||||
失败判定:
|
||||
|
||||
1. `failed`
|
||||
2. `canceled/cancelled`
|
||||
3. `error`
|
||||
4. `rejected`
|
||||
5. `expired`
|
||||
6. `unknown`
|
||||
|
||||
## 6. 输入媒体解析
|
||||
|
||||
### 6.1 首帧与尾帧
|
||||
|
||||
`image-to-video` 需要:
|
||||
|
||||
1. `visualSource` 作为首帧主参考
|
||||
2. `lastFrameImageDataUrl` 存在时用作尾帧
|
||||
3. 若未传尾帧,则回落到 `visualSource`
|
||||
|
||||
### 6.2 支持的输入
|
||||
|
||||
媒体源继续兼容:
|
||||
|
||||
1. Data URL
|
||||
2. `/generated-*` 旧路径
|
||||
|
||||
Rust 需要把它们统一转成可直接给 Ark 的 Data URL。
|
||||
|
||||
## 7. Prompt 口径
|
||||
|
||||
### 7.1 image-to-video prompt
|
||||
|
||||
Stage 2 要求尽量对齐旧 Node `buildArkCharacterAnimationPrompt` 语义,至少包含:
|
||||
|
||||
1. 单人 NPC 全身动作视频
|
||||
2. 动作英文名
|
||||
3. 角色固定为首尾两张图中的同一人
|
||||
4. 右向斜侧身
|
||||
5. 轮廓清晰,武器不可丢失
|
||||
6. 避免多角色与镜头切换
|
||||
7. 纯绿色绿幕
|
||||
8. 首帧严格使用图片 1
|
||||
9. 尾帧严格使用图片 2
|
||||
|
||||
### 7.2 审核降级
|
||||
|
||||
仅 `image-to-video` 允许在命中不当内容时重试一次保守 prompt。
|
||||
|
||||
重试规则:
|
||||
|
||||
1. 第一次请求用正式 prompt
|
||||
2. 若 Ark 返回错误信息明确命中不当内容
|
||||
3. 且存在保守 prompt
|
||||
4. 则用保守 prompt 再请求一次
|
||||
|
||||
除此之外不允许再回退到占位视频。
|
||||
|
||||
## 8. 其他策略口径
|
||||
|
||||
### 8.1 image-sequence
|
||||
|
||||
若本轮同步落地:
|
||||
|
||||
1. 走 DashScope 图片生成接口
|
||||
2. 请求体沿用旧 Node 的 `image-generation/generation`
|
||||
3. `messages[0].content = [{ text }, { image }, ...]`
|
||||
4. `parameters.n = frameCount`
|
||||
5. `parameters.size = 768*1024`
|
||||
6. `parameters.enable_sequential = true`
|
||||
7. `parameters.prompt_extend = true`
|
||||
8. `parameters.watermark = false`
|
||||
|
||||
### 8.2 motion-transfer
|
||||
|
||||
若本轮同步落地:
|
||||
|
||||
1. 走 DashScope `image2video/video-synthesis`
|
||||
2. 先把主图和参考视频上传为 `oss://` 资源
|
||||
3. 请求头显式带:
|
||||
1. `X-DashScope-Async: enable`
|
||||
2. `X-DashScope-OssResourceResolve: enable`
|
||||
|
||||
### 8.3 reference-to-video
|
||||
|
||||
若本轮同步落地:
|
||||
|
||||
1. 走 DashScope `video-generation/video-synthesis`
|
||||
2. 支持主图、参考图、参考视频混合媒体
|
||||
3. 同样依赖 DashScope upload policy 上传为 `oss://` 资源
|
||||
|
||||
## 9. 预览视频落 OSS
|
||||
|
||||
所有真实视频策略成功后都必须:
|
||||
|
||||
1. 下载远程 `video_url`
|
||||
2. 以 `preview.mp4` 落到 `generated-character-drafts/{character}/animation/{animation}/{taskId}/`
|
||||
3. 返回 `/generated-character-drafts/*/preview.mp4`
|
||||
|
||||
Stage 2 明确禁止:
|
||||
|
||||
1. 复用仓库内占位视频
|
||||
2. 仅在 `referenceVideoDataUrls[0]` 不为空时偷传参考视频作为结果
|
||||
|
||||
## 10. 配置项
|
||||
|
||||
### 10.1 DashScope
|
||||
|
||||
继续使用已有:
|
||||
|
||||
1. `DASHSCOPE_BASE_URL`
|
||||
2. `DASHSCOPE_API_KEY`
|
||||
3. `DASHSCOPE_IMAGE_REQUEST_TIMEOUT_MS`
|
||||
|
||||
### 10.2 Ark
|
||||
|
||||
Stage 2 新增动作视频专属配置:
|
||||
|
||||
1. `ARK_CHARACTER_VIDEO_BASE_URL`
|
||||
2. `ARK_CHARACTER_VIDEO_API_KEY`
|
||||
3. `ARK_CHARACTER_VIDEO_REQUEST_TIMEOUT_MS`
|
||||
4. `ARK_CHARACTER_VIDEO_MODEL`
|
||||
|
||||
兼容回退顺序:
|
||||
|
||||
1. 先读 `ARK_CHARACTER_VIDEO_*`
|
||||
2. 再读 `ARK_*`
|
||||
3. 最后回退到默认值
|
||||
|
||||
## 11. 错误与回退策略
|
||||
|
||||
### 11.1 直接报错的情况
|
||||
|
||||
以下情况不再允许伪成功:
|
||||
|
||||
1. 缺少 `ARK_API_KEY` 且策略为 `image-to-video`
|
||||
2. 上游创建任务失败
|
||||
3. 上游轮询失败
|
||||
4. 上游成功但没有视频 URL
|
||||
5. 下载视频失败
|
||||
6. OSS 写入失败
|
||||
|
||||
### 11.2 禁止继续保留的回退
|
||||
|
||||
1. `load_stage1_placeholder_preview_video()`
|
||||
2. 无模型调用时伪造 `previewVideoPath`
|
||||
3. `image-sequence` 继续用 SVG 帧充当真实结果
|
||||
|
||||
## 12. 任务状态口径
|
||||
|
||||
继续复用现有阶段:
|
||||
|
||||
1. `prepare_prompt`
|
||||
2. `request_model`
|
||||
3. `normalize_result`
|
||||
4. `persist_result`
|
||||
|
||||
新增要求:
|
||||
|
||||
1. `request_model` 需记录真实上游任务 id
|
||||
2. `normalize_result` 需记录 `previewVideoPath` 或 `imageSources`
|
||||
3. `failed` 状态必须返回真实错误消息,不再隐藏为占位成功
|
||||
|
||||
## 13. 验收标准
|
||||
|
||||
满足以下条件视为 Stage 2 完成:
|
||||
|
||||
1. 角色资产工坊点击生成动作时,`image-to-video` 会真实请求 Ark
|
||||
2. 请求体包含 `first_frame` 和 `last_frame`
|
||||
3. 请求参数固定为 `480p / 1:1 / 4`
|
||||
4. 返回视频真实来自上游而非占位文件
|
||||
5. 预览视频成功写入 OSS 草稿区
|
||||
6. 正式发布链仍能产出 `generated-animations/*` manifest
|
||||
7. 不再依赖仓库内占位视频完成主流程
|
||||
|
||||
## 14. 关联文档
|
||||
|
||||
1. [M6_CHARACTER_ANIMATION_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md](./M6_CHARACTER_ANIMATION_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md)
|
||||
2. [ASSET_EXTERNAL_GENERATION_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md](./ASSET_EXTERNAL_GENERATION_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md)
|
||||
3. [AI_CHARACTER_ANIMATION_TECHNICAL_SOLUTION_2026-04-04.md](./AI_CHARACTER_ANIMATION_TECHNICAL_SOLUTION_2026-04-04.md)
|
||||
@@ -0,0 +1,258 @@
|
||||
# M6 角色动作后端抽帧与正式发布 Stage 3 设计
|
||||
|
||||
日期:`2026-04-23`
|
||||
|
||||
## 1. 文档目的
|
||||
|
||||
这份文档用于冻结角色动作资产链路从“后端生成预览视频,前端抽帧后回传发布”继续推进到“后端生成预览视频,后端抽帧、抠绿幕、上传 OSS、落库绑定”的 Stage 3 实现口径。
|
||||
|
||||
本阶段的目标不是新增一套全新动作系统,而是在现有 `POST /api/assets/character-animation/generate` 与 `POST /api/assets/character-animation/publish` 的 contract 基础上,尽量用最小迁移把动作资产正式发布链完整收口到 Rust `api-server`。
|
||||
|
||||
## 2. 当前问题
|
||||
|
||||
截至 `2026-04-23` Stage 2 完成后,角色动作链路已经具备以下能力:
|
||||
|
||||
1. `image-to-video` 会真实请求 Ark 生成 `preview.mp4`
|
||||
2. 预览视频会真实写入 OSS 草稿区
|
||||
3. 正式发布时会把动作 manifest、动作集 manifest 上传 OSS,并通过 `asset_object + asset_entity_binding` 绑定到角色
|
||||
|
||||
但仍存在一个关键断点:
|
||||
|
||||
1. `preview.mp4` 的抽帧仍在前端浏览器完成
|
||||
2. 去绿幕仍在前端 `canvas` 中完成
|
||||
3. 前端要把整组 `framesDataUrls` 再回传给后端,才会触发正式发布
|
||||
|
||||
这与当前工程约束冲突:
|
||||
|
||||
1. 前端只负责表现,不负责正式资产加工
|
||||
2. 外部 I/O、文件处理、OSS 写入应尽量统一收口在后端
|
||||
|
||||
## 3. Stage 3 范围
|
||||
|
||||
### 3.1 本阶段必须完成
|
||||
|
||||
1. `character-animation/publish` 支持仅凭 `previewVideoPath` 在后端完成抽帧
|
||||
2. 后端抽帧后负责做去绿幕、尺寸归一、PNG 输出、正式帧上传
|
||||
3. 正式动作 manifest 与动作集 manifest 继续由后端生成并上传 OSS
|
||||
4. 正式动作集继续由后端确认 `asset_object` 并绑定到角色
|
||||
5. 前端主链不再要求本地生成 `framesDataUrls`
|
||||
|
||||
### 3.2 本阶段必须保留的兼容能力
|
||||
|
||||
1. 若前端仍传 `framesDataUrls`,后端继续按旧路径发布
|
||||
2. 旧 `previewVideoPath` 字段名不改
|
||||
3. 已存在的 `animationMap` 结构不改
|
||||
|
||||
### 3.3 本阶段不做
|
||||
|
||||
1. 不把外部视频解码逻辑放进 SpacetimeDB reducer / procedure
|
||||
2. 不新增动作资产专用 SpacetimeDB 表
|
||||
3. 不改前端 `generate` 接口字段名
|
||||
4. 不重做 UI 面板结构
|
||||
|
||||
## 4. 后端边界
|
||||
|
||||
Stage 3 的后端抽帧与上传逻辑统一落在 Rust `api-server`,不进入 `spacetime-module`。
|
||||
|
||||
原因:
|
||||
|
||||
1. 抽帧依赖外部进程与本地临时文件,不满足 reducer 的 deterministic 约束
|
||||
2. OSS 读写与外部视频工具调用属于平台 I/O,不应落入 SpacetimeDB 真相层
|
||||
3. `spacetime-module` 继续只负责资产对象确认与绑定后的真相落库
|
||||
|
||||
因此本阶段职责边界为:
|
||||
|
||||
1. `api-server`:下载草稿视频、抽帧、去绿幕、上传 OSS、生成 manifest、调用 `spacetime-client`
|
||||
2. `spacetime-module`:继续只承接 `asset_object` 与 `asset_entity_binding`
|
||||
|
||||
## 5. 技术决策
|
||||
|
||||
## 5.1 抽帧工具
|
||||
|
||||
Stage 3 统一采用系统 `ffmpeg + ffprobe`,不在 Rust 内部新增重型视频解码依赖。
|
||||
|
||||
原因:
|
||||
|
||||
1. 当前仓库没有可复用的视频解码能力
|
||||
2. 仅为动作发布链引入一整套 Rust 视频编解码栈,风险高且集成成本大
|
||||
3. 当前动作帧数很低,采用外部命令行工具能更快稳定落地
|
||||
|
||||
## 5.2 运行时依赖
|
||||
|
||||
部署与本地联调环境必须满足:
|
||||
|
||||
1. `ffmpeg` 可执行
|
||||
2. `ffprobe` 可执行
|
||||
|
||||
配置项:
|
||||
|
||||
1. `CHARACTER_ANIMATION_FFMPEG_PATH`
|
||||
2. `CHARACTER_ANIMATION_FFPROBE_PATH`
|
||||
3. `CHARACTER_ANIMATION_FRAME_EXTRACT_TIMEOUT_MS`
|
||||
|
||||
默认值:
|
||||
|
||||
1. `ffmpeg`
|
||||
2. `ffprobe`
|
||||
3. `120000`
|
||||
|
||||
说明:
|
||||
|
||||
1. 若未显式配置路径,则默认从系统 `PATH` 查找
|
||||
2. 若运行环境缺少 `ffmpeg` 或 `ffprobe`,发布动作时必须直接报错,禁止偷偷回退到前端抽帧或占位帧
|
||||
|
||||
## 5.3 去绿幕方案
|
||||
|
||||
Stage 3 继续保持“后端产出透明背景 PNG 序列”的口径。
|
||||
|
||||
实现方式:
|
||||
|
||||
1. `ffmpeg` 负责按指定时间点截取原始视频帧
|
||||
2. Rust 使用 `image` crate 读取单帧 PNG
|
||||
3. Rust 复用前端现有绿幕去底逻辑的同等阈值与邻域策略,对 RGBA 像素做去背景与边缘去污染
|
||||
4. Rust 再把去底后的图像按 contain 方式绘制到目标帧尺寸画布中,输出正式 PNG 帧
|
||||
|
||||
这样做的原因:
|
||||
|
||||
1. 抽帧交给 `ffmpeg`,减少视频解码复杂度
|
||||
2. 去底逻辑留在 Rust,方便与前端当前视觉结果对齐
|
||||
3. 正式帧统一输出 PNG,便于当前 `animationMap` 与运行时消费保持稳定
|
||||
|
||||
## 6. Contract 变更
|
||||
|
||||
## 6.1 `CharacterAnimationDraftPayload`
|
||||
|
||||
在保留旧字段的基础上,新增后端抽帧所需参数:
|
||||
|
||||
1. `frameCount?: number`
|
||||
2. `applyChromaKey?: boolean`
|
||||
3. `sampleStartRatio?: number`
|
||||
4. `sampleEndRatio?: number`
|
||||
|
||||
兼容规则:
|
||||
|
||||
1. `framesDataUrls` 允许为空或省略
|
||||
2. 当 `framesDataUrls` 非空时,后端继续沿用旧发布路径
|
||||
3. 当 `framesDataUrls` 为空且存在 `previewVideoPath` 时,后端进入 Stage 3 抽帧路径
|
||||
|
||||
## 6.2 默认采样规则
|
||||
|
||||
若前端未显式传采样区间,则后端按以下默认值执行:
|
||||
|
||||
1. `loop = true` 时:
|
||||
- `sampleStartRatio = 0.12`
|
||||
- `sampleEndRatio = 0.94`
|
||||
2. `loop = false` 时:
|
||||
- `sampleStartRatio = 0`
|
||||
- `sampleEndRatio = 1`
|
||||
|
||||
若 `frameCount` 未传,则后端默认使用 `8`。
|
||||
|
||||
## 7. 正式发布算法
|
||||
|
||||
当 `publish` 收到某个动作草稿时,后端按以下顺序处理:
|
||||
|
||||
1. 若 `framesDataUrls` 非空:
|
||||
- 继续读取图片源
|
||||
- 继续上传正式帧
|
||||
- 继续生成动作 manifest
|
||||
2. 若 `framesDataUrls` 为空但 `previewVideoPath` 存在:
|
||||
- 从 OSS 草稿区读取视频
|
||||
- 写入临时目录
|
||||
- 调用 `ffprobe` 读取时长
|
||||
- 依据 `frameCount / sampleStartRatio / sampleEndRatio / loop` 计算采样时间点
|
||||
- 对每个时间点调用 `ffmpeg` 抽一张原始 PNG 帧
|
||||
- 对每帧做去绿幕与 contain 尺寸归一
|
||||
- 以 `generated-animations/{character}/{animationSetId}/{action}/framexx.png` 上传正式帧
|
||||
- 生成动作 manifest
|
||||
3. 若两者都没有:
|
||||
- 直接报 `400`
|
||||
|
||||
## 8. Manifest 与资产绑定口径
|
||||
|
||||
Stage 3 不改变正式 manifest 的结构真相:
|
||||
|
||||
1. 动作级 manifest 仍记录:
|
||||
- `action`
|
||||
- `frameCount`
|
||||
- `fps`
|
||||
- `loop`
|
||||
- `frameWidth`
|
||||
- `frameHeight`
|
||||
- `previewVideoPath`
|
||||
- `framePaths`
|
||||
2. 动作集 manifest 仍记录:
|
||||
- `animationSetId`
|
||||
- `characterId`
|
||||
- `visualAssetId`
|
||||
- `actions`
|
||||
- `animationMap`
|
||||
|
||||
资产落库口径继续保持:
|
||||
|
||||
1. 只对动作集根 manifest 执行 `confirm_asset_object`
|
||||
2. 再把该对象绑定到角色 `animation_set` 槽位
|
||||
|
||||
说明:
|
||||
|
||||
1. Stage 3 的重点是“正式帧生产链后移”,不是扩展资产对象颗粒度
|
||||
2. 单帧 PNG 与单动作 manifest 继续只作为 OSS 内部发布物,不额外逐一建表
|
||||
|
||||
## 9. 前端迁移口径
|
||||
|
||||
Stage 3 后前端职责收口为:
|
||||
|
||||
1. 请求后端生成 `preview.mp4`
|
||||
2. 记录动作元数据:
|
||||
- `fps`
|
||||
- `loop`
|
||||
- `frameWidth`
|
||||
- `frameHeight`
|
||||
- `frameCount`
|
||||
- `applyChromaKey`
|
||||
- `sampleStartRatio`
|
||||
- `sampleEndRatio`
|
||||
- `previewVideoPath`
|
||||
3. 调用 `publish`,让后端完成正式帧生产
|
||||
4. 用返回的 `animationMap` 做 UI 预览与角色配置
|
||||
|
||||
前端不再承担:
|
||||
|
||||
1. 本地视频解码
|
||||
2. 本地抽帧
|
||||
3. 本地去绿幕
|
||||
4. 把整组帧图片 Data URL 回传后端
|
||||
|
||||
## 10. 错误策略
|
||||
|
||||
以下情况必须直接失败,不允许伪成功:
|
||||
|
||||
1. `previewVideoPath` 存在但 OSS 读取失败
|
||||
2. `ffprobe` 执行失败
|
||||
3. `ffmpeg` 执行失败
|
||||
4. 抽帧数量不足
|
||||
5. PNG 解码失败
|
||||
6. 去绿幕或 PNG 编码失败
|
||||
7. 正式帧上传 OSS 失败
|
||||
|
||||
错误返回要求:
|
||||
|
||||
1. 明确指出失败发生在“视频读取 / 抽帧 / 去底 / 上传 / 落库”哪个阶段
|
||||
2. 缺少 `ffmpeg` 或 `ffprobe` 时,错误消息必须能直接提示环境缺失
|
||||
|
||||
## 11. 验收标准
|
||||
|
||||
满足以下条件视为 Stage 3 完成:
|
||||
|
||||
1. 角色动作 `generate` 会真实生成并返回 OSS 草稿区 `preview.mp4`
|
||||
2. 角色动作 `publish` 在不传 `framesDataUrls` 的情况下也能成功
|
||||
3. 正式动作帧由后端抽出并写入 `generated-animations/*`
|
||||
4. `manifest.json` 中 `framePaths` 指向正式 PNG 帧
|
||||
5. 动作集对象 `object_key` 成功确认并绑定到角色
|
||||
6. 前端主链不再依赖浏览器本地抽帧
|
||||
|
||||
## 12. 关联文档
|
||||
|
||||
1. [M6_CHARACTER_ANIMATION_ASSET_EXTERNAL_GENERATION_STAGE2_2026-04-23.md](./M6_CHARACTER_ANIMATION_ASSET_EXTERNAL_GENERATION_STAGE2_2026-04-23.md)
|
||||
2. [ASSET_EXTERNAL_GENERATION_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md](./ASSET_EXTERNAL_GENERATION_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md)
|
||||
3. [AI_CHARACTER_ANIMATION_TECHNICAL_SOLUTION_2026-04-04.md](./AI_CHARACTER_ANIMATION_TECHNICAL_SOLUTION_2026-04-04.md)
|
||||
@@ -0,0 +1,227 @@
|
||||
# M6 角色主形象外部真实生成 Stage 2 设计
|
||||
|
||||
日期:`2026-04-23`
|
||||
|
||||
## 1. 文档目的
|
||||
|
||||
这份文档用于冻结 `POST /api/assets/character-visual/generate` 从 Stage 1 SVG 占位草稿切到真实外部图片生成后的实现口径。
|
||||
|
||||
本阶段目标不是重做整套资产系统,而是在保留现有 `OSS + asset_object + asset_entity_binding + AiTaskService` 主链不变的前提下,把“候选图生成”改成真实调用图片模型。
|
||||
|
||||
## 2. 当前问题
|
||||
|
||||
Stage 1 当前存在以下占位实现:
|
||||
|
||||
1. `server-rs/crates/api-server/src/character_visual_assets.rs`
|
||||
2. `CHARACTER_VISUAL_MODEL = "rust-svg-character-visual"`
|
||||
3. `persist_visual_drafts(...)` 直接生成 `candidate-xx.svg`
|
||||
4. `publish` 虽然已经能把候选对象发布到 `generated-characters/*`,但候选对象本身不是真实模型产物
|
||||
|
||||
这会导致第 4 项验证里“角色资产工坊图片真实外部生成”不能通过。
|
||||
|
||||
## 3. Stage 2 范围
|
||||
|
||||
### 3.1 本阶段必须完成
|
||||
|
||||
1. `character-visual/generate` 改为真实 DashScope 图片生成
|
||||
2. 支持 `text-to-image`
|
||||
3. 支持 `image-to-image`
|
||||
4. 参考图继续兼容 Data URL 与 `/generated-*` 旧路径
|
||||
5. 候选草稿继续写入 `generated-character-drafts/{character}/visual/{taskId}/candidate-xx.*`
|
||||
6. `publish` 继续沿用现有 `OSS + asset_object + asset_entity_binding` 正式主链
|
||||
7. 返回 contract 继续保持前端可直接消费
|
||||
|
||||
### 3.2 本阶段不做
|
||||
|
||||
1. 不新增 SpacetimeDB 资产任务真相表
|
||||
2. 不回写本地 `public/generated-*`
|
||||
3. 不新增前端协议字段
|
||||
4. 不把角色主图生成迁回 `server-node`
|
||||
|
||||
## 4. 真实模型与上游协议
|
||||
|
||||
### 4.1 默认模型
|
||||
|
||||
默认模型统一使用:
|
||||
|
||||
`wan2.7-image-pro`
|
||||
|
||||
说明:
|
||||
|
||||
1. 这是旧 Node 角色主图正式链的默认模型
|
||||
2. 当前前端 `useRoleVisualCandidateWorkflow.ts` 也固定传该模型
|
||||
3. Rust 允许请求显式覆盖 `imageModel`,但默认值必须与旧链一致
|
||||
|
||||
### 4.2 DashScope 接口
|
||||
|
||||
#### 文生图
|
||||
|
||||
当 `sourceMode = text-to-image` 时:
|
||||
|
||||
1. 请求 `POST {DASHSCOPE_BASE_URL}/services/aigc/image-generation/generation`
|
||||
2. 头部带 `Authorization: Bearer {DASHSCOPE_API_KEY}`
|
||||
3. 头部带 `X-DashScope-Async: enable`
|
||||
4. body 使用旧 Node 已验证的 `messages[0].content = [{ text }]` 结构
|
||||
5. `parameters` 需显式带:
|
||||
1. `n`
|
||||
2. `size`
|
||||
3. `negative_prompt`
|
||||
4. `prompt_extend = true`
|
||||
5. `watermark = false`
|
||||
6. 创建成功后按 `/tasks/{task_id}` 轮询直到成功或失败
|
||||
|
||||
#### 图生图
|
||||
|
||||
当 `sourceMode = image-to-image` 时:
|
||||
|
||||
1. 请求 `POST {DASHSCOPE_BASE_URL}/services/aigc/image-generation/generation`
|
||||
2. body 仍使用旧 Node 已验证的 `messages[0].content = [{ text }, { image }, ...]`
|
||||
3. 参考图顺序保持为“文字在前,图片在后”
|
||||
4. `parameters` 与文生图一致
|
||||
5. 同样使用异步任务轮询
|
||||
|
||||
## 5. Prompt 口径
|
||||
|
||||
### 5.1 正向 prompt
|
||||
|
||||
Rust Stage 2 不再使用“LLM 先摘要再拼 SVG”的链路。
|
||||
|
||||
新的生成 prompt 口径:
|
||||
|
||||
1. 以请求里的 `promptText + characterBriefText` 组装正式主图 prompt
|
||||
2. 约束必须覆盖:
|
||||
1. 单人
|
||||
2. 右向斜侧身
|
||||
3. 1:1 正方形画布
|
||||
4. 纯绿色绿幕
|
||||
5. 3 到 4 头身
|
||||
6. 像素动作角色
|
||||
7. 不要扩写复杂背景
|
||||
3. 主目标是与旧 Node `buildNpcVisualPrompt` 生成出的正式约束保持同方向
|
||||
|
||||
### 5.2 负向 prompt
|
||||
|
||||
Rust Stage 2 需要显式带负向提示词,至少覆盖以下禁止项:
|
||||
|
||||
1. 正面视角
|
||||
2. 左朝向
|
||||
3. 纯 90 度侧视
|
||||
4. 半身像
|
||||
5. 多角色
|
||||
6. 复杂背景
|
||||
7. 文字
|
||||
8. 水印
|
||||
9. UI 元素
|
||||
10. 软萌 Q 版大头贴
|
||||
|
||||
## 6. 参考图解析口径
|
||||
|
||||
参考图继续兼容两类输入:
|
||||
|
||||
1. `data:image/*;base64,...`
|
||||
2. `/generated-*` 旧路径
|
||||
|
||||
不再兼容:
|
||||
|
||||
1. 任意仓库磁盘路径
|
||||
2. 非 `/generated-*` 的普通相对路径
|
||||
|
||||
Rust 解析步骤:
|
||||
|
||||
1. Data URL 直接转发
|
||||
2. `/generated-*` 通过 OSS 签名读获取二进制
|
||||
3. 下载后重新编码为 `data:{mime};base64,...`
|
||||
4. 再提交给 DashScope
|
||||
|
||||
## 7. 候选图下载与存储
|
||||
|
||||
DashScope 成功后需要:
|
||||
|
||||
1. 下载返回的远程图片 URL
|
||||
2. 归一化 mime type 和扩展名
|
||||
3. 对 PNG 做去绿幕/去白底透明化处理
|
||||
4. 上传到 OSS 草稿区
|
||||
5. 返回 `/generated-character-drafts/*`
|
||||
|
||||
### 7.1 去底规则
|
||||
|
||||
为保持与旧 Node 可见效果一致:
|
||||
|
||||
1. 当下载结果是 PNG 时,对图像执行背景透明化
|
||||
2. 透明化逻辑需兼容:
|
||||
1. 纯绿底
|
||||
2. 白底边缘
|
||||
3. 绿边污染
|
||||
3. 若处理失败,则保留原图,不因后处理失败阻断整个生成链
|
||||
|
||||
## 8. 配置项
|
||||
|
||||
Stage 2 统一使用以下配置:
|
||||
|
||||
1. `DASHSCOPE_BASE_URL`
|
||||
2. `DASHSCOPE_API_KEY`
|
||||
3. `DASHSCOPE_IMAGE_REQUEST_TIMEOUT_MS`
|
||||
|
||||
补充默认值:
|
||||
|
||||
1. `DASHSCOPE_BASE_URL` 默认 `https://dashscope.aliyuncs.com/api/v1`
|
||||
2. 超时继续沿用现有 `dashscope_image_request_timeout_ms`
|
||||
|
||||
当前阶段不新增新的角色主图专属环境变量。
|
||||
|
||||
## 9. 错误与回退策略
|
||||
|
||||
Stage 2 明确取消 SVG 占位回退。
|
||||
|
||||
### 9.1 允许的失败表现
|
||||
|
||||
当以下条件不满足时,接口直接返回错误:
|
||||
|
||||
1. 缺少 `DASHSCOPE_API_KEY`
|
||||
2. DashScope 创建任务失败
|
||||
3. DashScope 轮询失败
|
||||
4. DashScope 成功但未返回图片 URL
|
||||
5. 下载图片失败
|
||||
6. OSS 写入失败
|
||||
|
||||
### 9.2 禁止的回退
|
||||
|
||||
以下回退在 Stage 2 禁止继续保留:
|
||||
|
||||
1. LLM 摘要后生成 SVG 占位图
|
||||
2. 本地静态预置图
|
||||
3. 返回“伪成功”但实际未调模型
|
||||
|
||||
## 10. 任务状态口径
|
||||
|
||||
`AiTaskService` 继续复用现有阶段:
|
||||
|
||||
1. `prepare_prompt`
|
||||
2. `request_model`
|
||||
3. `normalize_result`
|
||||
4. `persist_result`
|
||||
|
||||
阶段语义调整如下:
|
||||
|
||||
1. `prepare_prompt`:冻结最终 prompt、source mode、参考图数量
|
||||
2. `request_model`:记录真实模型名、DashScope task id、实际 prompt
|
||||
3. `normalize_result`:记录候选对象路径
|
||||
4. `persist_result`:确认 OSS 草稿已落成
|
||||
|
||||
## 11. 验收标准
|
||||
|
||||
满足以下条件视为 Stage 2 完成:
|
||||
|
||||
1. 调 `character-visual/generate` 时不再生成 SVG 草稿
|
||||
2. 返回的候选对象是 DashScope 真实产图
|
||||
3. 草稿对象仍写到 `generated-character-drafts/*`
|
||||
4. `publish` 仍能发布到 `generated-characters/*`
|
||||
5. 发布后仍能形成 `asset_object`
|
||||
6. 发布后仍能形成 `asset_entity_binding`
|
||||
7. 前端角色资产工坊无须改协议即可继续使用
|
||||
|
||||
## 12. 关联文档
|
||||
|
||||
1. [M6_CHARACTER_VISUAL_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md](./M6_CHARACTER_VISUAL_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md)
|
||||
2. [ASSET_EXTERNAL_GENERATION_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md](./ASSET_EXTERNAL_GENERATION_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md)
|
||||
3. [BIG_FISH_FORMAL_IMAGE_GENERATION_2026-04-23.md](./BIG_FISH_FORMAL_IMAGE_GENERATION_2026-04-23.md)
|
||||
@@ -111,6 +111,10 @@ export type CharacterAnimationDraftPayload = {
|
||||
loop: boolean;
|
||||
frameWidth: number;
|
||||
frameHeight: number;
|
||||
frameCount?: number;
|
||||
applyChromaKey?: boolean;
|
||||
sampleStartRatio?: number;
|
||||
sampleEndRatio?: number;
|
||||
previewVideoPath?: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import {
|
||||
buildAnimationClipFromVideoSource,
|
||||
type DraftAnimationClip,
|
||||
} from '../asset-studio/characterAssetWorkflowModel';
|
||||
import { generateCharacterAnimationDraft } from '../asset-studio/characterAssetWorkflowPersistence';
|
||||
import type { CharacterAnimationGenerationPayload } from '../asset-studio/characterAssetWorkflowPersistence';
|
||||
|
||||
@@ -17,7 +13,7 @@ export function useRoleAnimationWorkflow() {
|
||||
animationPromptText: string;
|
||||
characterBriefText: string;
|
||||
role: EditableCustomWorldRole;
|
||||
}): Promise<DraftAnimationClip> => {
|
||||
}) => {
|
||||
const { actionConfig, animationPromptText, characterBriefText, role } =
|
||||
params;
|
||||
|
||||
@@ -53,20 +49,22 @@ export function useRoleAnimationWorkflow() {
|
||||
throw new Error('当前自定义世界动作工坊只支持图生视频方案。');
|
||||
}
|
||||
|
||||
return buildAnimationClipFromVideoSource(result.previewVideoPath, {
|
||||
animation: actionConfig.animation,
|
||||
return {
|
||||
fps: actionConfig.fps,
|
||||
loop: actionConfig.loop,
|
||||
frameWidth: 192,
|
||||
frameHeight: 256,
|
||||
frameCount: actionConfig.frameCount,
|
||||
applyChromaKey: true,
|
||||
sampleStartRatio: actionConfig.loop ? 0.12 : 0,
|
||||
sampleEndRatio: actionConfig.loop ? 0.94 : 1,
|
||||
});
|
||||
previewVideoPath: result.previewVideoPath,
|
||||
};
|
||||
};
|
||||
|
||||
const publishAnimationClipForRole = async (params: {
|
||||
actionConfig: CustomWorldAiActionConfig;
|
||||
clip: DraftAnimationClip;
|
||||
clip: Awaited<ReturnType<typeof generateAnimationClipForRole>>;
|
||||
role: EditableCustomWorldRole;
|
||||
}) => {
|
||||
const { actionConfig, clip, role } = params;
|
||||
@@ -80,11 +78,15 @@ export function useRoleAnimationWorkflow() {
|
||||
visualAssetId: role.generatedVisualAssetId,
|
||||
animations: {
|
||||
[actionConfig.animation]: {
|
||||
framesDataUrls: clip.frames,
|
||||
framesDataUrls: [],
|
||||
fps: clip.fps,
|
||||
loop: clip.loop,
|
||||
frameWidth: clip.frameWidth,
|
||||
frameHeight: clip.frameHeight,
|
||||
frameCount: clip.frameCount,
|
||||
applyChromaKey: clip.applyChromaKey,
|
||||
sampleStartRatio: clip.sampleStartRatio,
|
||||
sampleEndRatio: clip.sampleEndRatio,
|
||||
previewVideoPath: clip.previewVideoPath,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -62,7 +62,6 @@ import {
|
||||
type ScenePresetInfo,
|
||||
WorldType,
|
||||
} from '../../types';
|
||||
import { buildAnimationClipFromVideoSource } from '../asset-studio/characterAssetWorkflowModel';
|
||||
import {
|
||||
type CharacterAnimationGenerationPayload,
|
||||
generateCharacterAnimationDraft,
|
||||
@@ -3832,28 +3831,19 @@ function RoleSkillEditorModal({
|
||||
throw new Error('当前技能动作预览仅支持图生视频生成。');
|
||||
}
|
||||
|
||||
const clip = await buildAnimationClipFromVideoSource(
|
||||
generationResult.previewVideoPath,
|
||||
{
|
||||
animation: AnimationState.ATTACK,
|
||||
fps: 10,
|
||||
loop: false,
|
||||
frameCount: 8,
|
||||
applyChromaKey: true,
|
||||
},
|
||||
);
|
||||
|
||||
const publishResult = await publishCharacterAnimationAssets({
|
||||
characterId: role.id,
|
||||
visualAssetId: role.generatedVisualAssetId,
|
||||
animations: {
|
||||
[actionKey]: {
|
||||
framesDataUrls: clip.frames,
|
||||
fps: clip.fps,
|
||||
loop: clip.loop,
|
||||
frameWidth: clip.frameWidth,
|
||||
frameHeight: clip.frameHeight,
|
||||
previewVideoPath: clip.previewVideoPath,
|
||||
framesDataUrls: [],
|
||||
fps: 10,
|
||||
loop: false,
|
||||
frameWidth: 192,
|
||||
frameHeight: 256,
|
||||
frameCount: 8,
|
||||
applyChromaKey: true,
|
||||
previewVideoPath: generationResult.previewVideoPath,
|
||||
},
|
||||
},
|
||||
updateCharacterOverride: false,
|
||||
|
||||
Reference in New Issue
Block a user