分镜功能业务逻辑分析文档
1. 功能概述
分镜功能是AIGC视频创作系统的核心模块,提供从提示词生成分镜、编辑分镜内容、匹配素材、添加配音到最终合成视频的完整工作流。该功能支持AI辅助生成和手动编辑两种模式,帮助用户快速创建高质量的视频内容。
2. 核心概念
| 概念 | 描述 |
|---|---|
| 分镜(StoryBoard) | 视频创作的基本单位,包含标题、描述、台词、特效、图片/视频素材、音频等元素 |
| 分镜数据(StoryBoardData) | 存储分镜所有信息的数据结构,包含标题、描述、台词、特效、媒体URL等字段 |
| 配置(Formula) | 用于大模型生成分镜的参数设置,包含提示词、系统提示词等 |
| 素材匹配 | 将分镜内容与系统中的视频素材进行关联的过程 |
| 视频合成 | 将分镜中的视频素材和音频素材合成为最终视频的过程 |
3. 模块结构
分镜功能采用组件化设计,主要包含以下核心组件:
views/aigc/component/
├── StoryBoard.vue # 分镜管理主组件
├── StoryBoardItem.vue # 单个分镜项组件
└── components/
├── VideoSynthesis.vue # 视频合成组件
├── GenerateStoryBoard.vue # 分镜生成组件
├── VideoArea.vue # 视频区域组件
├── AudioArea.vue # 音频区域组件
├── MaterialDialog.vue # 素材选择弹窗
├── VideoPlayerDialog.vue # 视频播放弹窗
└── VoiceDialog.vue # 配音弹窗各组件职责划分:
- StoryBoard: 管理多个分镜项,提供分镜添加、删除、下载和视频合成等功能
- StoryBoardItem: 负责单个分镜的内容编辑,包括标题、描述、台词、媒体素材管理等
- VideoSynthesis: 处理视频合成的完整流程,包括素材上传、工程创建、任务执行等
- GenerateStoryBoard: 提供AI生成分镜的功能,支持多种生成方式和平台选择
4. 业务流程图
flowchart TD
A[开始] --> B[创建/加载配置]
B --> C{是否使用AI生成?}
C -->|是| D[输入提示词]
D --> E[调用LLM生成分镜]
E --> F[生成多个分镜数据]
C -->|否| G[手动添加分镜]
F --> H[编辑分镜内容]
G --> H
H --> I[选择主题]
I --> J{是否匹配素材?}
J -->|是| K[打开素材对话框]
K --> L[确认选择素材]
J -->|否| M{是否添加配音?}
L --> M
M -->|是| N[打开配音对话框]
N --> O[生成并添加配音]
O --> P{是否合成视频?}
M -->|否| P
P -->|是| Q[选择合成方式]
Q --> R[上传素材到ICE]
R --> S[组建Timeline并创建工程]
S --> T[执行项目剪辑]
T --> U[查询生成任务状态]
U --> V[获取临时下载地址]
V --> W[保存单帧分镜]
W --> X[下载视频]
X --> Y[结束]5. 核心业务逻辑详解
5.1 分镜管理流程
功能说明:管理分镜列表,包括添加、删除、查看和编辑分镜。
核心方法:
addEmptyStoryBoard(): 添加一个空分镜到分镜列表deleteStoryBoard(index): 根据索引删除指定分镜handleItemUpdate(index, newValue): 更新指定分镜的数据
数据流向:
- StoryBoard组件维护storyBoardData数组,包含所有分镜数据
- 每个分镜通过StoryBoardItem组件独立渲染和编辑
- StoryBoardItem组件通过事件机制将更新后的数据传递回父组件
5.2 配置管理流程
功能说明:管理用于生成分镜的配置,包括创建、加载、保存和删除配置。
核心方法:
fetchFormulaList(): 获取配置列表fetchFormulaSave(): 保存当前配置loadFormula(e): 加载指定配置fetchFormulaDelete(index, rowId): 删除指定配置
数据流向:
- 配置数据从服务器获取并存储在formulaList中
- 用户可以选择加载现有配置或创建新配置
- 配置变更后通过API保存到服务器
5.3 LLM生成分镜流程
功能说明:利用大语言模型(LLM)根据用户提供的提示词自动生成分镜内容。
核心方法:
llmGenerate(): 调用LLM生成分镜数据
参数:
model: 使用的模型名称,默认为'deepseek-r1-250120'messages: 包含用户提示词和系统提示词的消息数组
执行流程:
- 收集用户输入的提示词和额外提示词
- 调用sendMessagesArk API向LLM发送请求
- 解析LLM返回的JSON格式结果
- 将结果转换为分镜数据并更新storyBoardData
5.4 分镜内容编辑流程
功能说明:编辑单个分镜的详细内容,包括标题、描述、台词、特效、媒体素材等。
核心方法:
topicChange(e): 处理主题选择变更confirmSelectMaterial(topicId): 确认选择素材并获取视频素材handleVoiceSuccess(audioUrl): 处理配音生成成功后的逻辑handleGenerateSuccess(result): 处理生成分镜成功后的逻辑
数据存储:
- 分镜数据实时保存在localStorage中,键名为
storyboard_item_${itemId} - 数据变更通过事件机制同步到父组件
5.5 视频合成流程
功能说明:将分镜中的视频和音频素材合成为最终视频。
核心方法:
synthesizeVideos(synthesisType = 0): 执行视频合成的完整流程
参数:
synthesisType: 合成类型,0表示每个分镜单独合成,1表示所有分镜合成为一个视频
执行流程:
- 上传素材到ICE: 调用fetchUploadMediaByUrl将视频和音频素材上传到阿里云ICE
- 组建Timeline并创建工程: 根据合成类型选择相应的Timeline构建方法,然后创建编辑工程
- 执行项目剪辑: 调用fetchSubmitMediaProducing提交剪辑任务
- 查询生成任务状态: 轮询调用fetchGetMediaProducingJob查询任务状态,直到任务完成或失败
- 获取临时下载地址: 调用fetchGetPresignUrl获取生成视频的临时下载地址
- 保存单帧分镜: 调用saveIce保存分镜数据
- 下载视频: 调用downloadVideos下载生成的视频
6. 核心API和方法索引
6.1 StoryBoard.vue 核心方法
| 方法名 | 描述 | 参数 | 返回值 |
|---|---|---|---|
llmGenerate | 调用LLM生成分镜 | 无 | Promise<void> |
fetchFormulaList | 获取配置列表 | 无 | Promise<void> |
fetchFormulaSave | 保存配置 | 无 | Promise<void> |
loadFormula | 加载配置 | e: 配置ID | void |
addEmptyStoryBoard | 添加空白分镜 | 无 | void |
deleteStoryBoard | 删除分镜 | index: 分镜索引 | void |
confirmSynthesis | 确认合成方式 | 无 | void |
startSynthesis | 开始合成视频 | 无 | Promise<void> |
downloadStoryBoard | 下载分镜 | 无 | Promise<void> |
6.x 分镜列表历史策略(更新)
- 增加字段:
generationId(字符串),表示一次分镜生成会话的唯一标识。 - 保存逻辑:
- 当
currentGenerationId为空时,首次保存会生成一个新的generationId并新增一条历史记录。 - 当
currentGenerationId存在时,保存将只更新对应的历史记录,而不新增。
- 当
- 加载逻辑:
- 从历史加载后,会将
entry.generationId绑定到currentGenerationId。 - 若旧历史项缺少
generationId,会回写一个基于时间戳的ID到该条目并持久化,确保后续保存为更新操作。
- 从历史加载后,会将
- 其他字段:
timestamp首次创建时间;updatedAt最近更新时间;expireAt过期时间(默认7天)。
6.2 StoryBoardItem.vue 核心方法
| 方法名 | 描述 | 参数 | 返回值 |
|---|---|---|---|
confirmSelectMaterial | 确认选择素材 | topicId: 主题ID | Promise<void> |
handleVoiceSuccess | 处理配音成功 | audioUrl: 音频URL | void |
onDelete | 删除当前分镜 | 无 | void |
openGenerateDialog | 打开生成分镜对话框 | 无 | void |
handleGenerateSuccess | 处理生成分镜成功 | result: 生成结果 | void |
loadImageTopics | 加载图片主题列表 | 无 | Promise<void> |
topicChange | 处理主题变更 | e: 选中的主题 | void |
6.3 VideoSynthesis.vue 核心方法
| 方法名 | 描述 | 参数 | 返回值 |
|---|---|---|---|
synthesizeVideos | 执行视频合成流程 | synthesisType: 合成类型(0/1) | Promise<void> |
saveIce | 保存单帧分镜 | data: 分镜数据 | Promise<void> |
downloadVideos | 下载视频 | synthesisType: 合成类型(0/1) | Promise<void> |
6.4 GenerateStoryBoard.vue 核心方法
| 方法名 | 描述 | 参数 | 返回值 |
|---|---|---|---|
comfyuiGenerate | 使用comfyui生成图片 | 无 | Promise<Array> |
generateVideoWithFramepack | 使用framepack生成视频 | 无 | Promise<void> |
selectImage | 选择图片 | image: 图片URL | Promise<void> |
7. 数据结构定义
7.1 分镜数据结构 (StoryBoardItemData)
{
description: string, // 分镜描述
line: string, // 角色台词
title: string, // 分镜标题
name: string, // 角色名
effect: string, // 特效描述
prompt: string, // 提示词
videoUrl: string, // 视频URL
audioUrl: string, // 音频URL
thumbUrl: string, // 缩略图URL
videoKey: string, // 视频键名
projectId: string, // 工程ID
jobId: string, // 任务ID
mediaURL: string, // 媒体URL
iceVideoUrl: string, // ICE视频URL
bucket: string, // OSS桶名
key: string // OSS键名
}7.2 配置数据结构 (Formula)
{
name: string, // 配置名称
description: string, // 配置描述
data: {
prompt: string, // 用户提示词
extraPrompt: string,// 额外提示词
systemPrompt: string,// 系统提示词
number: number // 分镜数量
}
}8. 使用场景与典型操作
8.1 AI辅助生成分镜
场景说明:用户希望通过AI快速生成多个分镜。
操作流程:
- 在"LLM生成分镜"区域输入提示词
- 可选择性地添加额外提示词
- 点击"生成"按钮
- 系统调用LLM生成多个分镜
- 查看生成的分镜列表,可进行进一步编辑
8.2 手动编辑分镜
场景说明:用户希望手动创建和编辑分镜内容。
操作流程:
- 点击"添加分镜"按钮创建空分镜
- 编辑分镜标题、描述、台词等信息
- 选择主题以匹配相关配置
- 点击"匹配素材"按钮选择视频素材
- 点击"配音"按钮为分镜添加语音
- 可选择性地通过"生成分镜"按钮使用AI生成内容
8.3 合成视频
场景说明:用户完成分镜编辑后,希望将其合成为视频。
操作流程:
- 点击"合成视频"按钮
- 选择合成方式:每个分镜单独合成或所有分镜合成为一个视频
- 系统自动执行视频合成的完整流程
- 合成完成后自动下载生成的视频
9. 常见问题与解决方案
问题:生成分镜时没有结果 解决方案:检查提示词是否清晰明确,尝试调整提示词内容
问题:无法选择素材主题 解决方案:确保已正确加载主题列表,刷新页面后重试
问题:视频合成失败 解决方案:检查分镜中是否包含有效的视频和音频素材,确保网络连接稳定
问题:下载的视频无法播放 解决方案:确认视频格式兼容性,尝试使用其他播放器
10. 输入输出示例
10.1 LLM生成分镜示例
输入:
// 提示词输入
formula.data.prompt = "创作一个关于未来科技城市的短视频分镜";
formula.data.extraPrompt = "要求展现城市的空中交通、智能建筑和未来生活场景";
// 调用生成分镜方法
llmGenerate();输出:
// 生成的分镜数据示例
storyBoardData = [
{
title: "未来城市空中交通",
description: "清晨的未来科技城市,空中交通繁忙而有序,自动驾驶飞行器在高楼间穿梭",
line: "欢迎来到2150年的新都市,这里的交通已经彻底改变了我们的生活方式",
name: "解说员",
effect: "从空中俯瞰城市全景,镜头缓慢推进",
prompt: "A futuristic cityscape with flying cars and tall skyscrapers in the morning, digital art style",
videoUrl: "",
audioUrl: "",
thumbUrl: ""
},
// 更多分镜...
]11. 详细流程图(方法、数据示例、错误示例)
11.1 页面结构与核心组件
views/aigc/index.vue
├── el-tabs
│ ├── StoryBoard(分镜主组件)
│ └── StoryBoardConfig(配置管理)
└── 关键交互:
├── confirmSynthesis → 打开 VideoSynthesis 弹窗
└── startSynthesis → 调用 VideoSynthesis.synthesizeVideos(synthesisType)
views/aigc/component/StoryBoard.vue
├── StoryBoardItem 列表(单项编辑与素材匹配)
├── VideoSynthesis 弹窗(完整合成流程)
└── 常用方法:
├── confirmSynthesis() / startSynthesis()
├── downloadStoryBoard()
├── loadImageTopics()(主题缓存与加载)
└── handleItemUpdate(index, newValue)
views/aigc/component/StoryBoardItem.vue
├── VideoArea(视频区域:打开素材、播放、删除)
├── AudioArea(音频区域:配音与设置)
├── MaterialDialog(素材选择)
├── VideoPlayerDialog(视频预览)
├── VoiceDialog(配音:GSV/Azure 等)
└── 关键方法:
├── confirmSelectMaterial(topicId) → 拉取/选择素材
├── handleVoiceSuccess(result) → 处理配音生成与直传
└── onDelete / openGenerateDialog / handleGenerateSuccess
views/aigc/component/components/VideoSynthesis.vue
└── 完整合成 6 步:
1) 上传/注册素材到 ICE
2) 组建 Timeline 并创建工程
3) 执行项目剪辑
4) 轮询生成任务状态
5) 获取临时下载地址(OSS 预签名)
6) 保存单帧分镜并下载11.2 分镜生成流程(GenerateStoryBoard.vue)
flowchart TD
A[开始:填写提示词/参数] --> B[拉取生成记录 fetchRemoteGenerationRecords]
B --> C{选择平台/方式}
C -->|即梦| D[发起生成任务]
D --> E[轮询/回填视频结果]
E --> F{是否保存为素材?}
F -->|是| G[下载视频 Blob]
G --> H[adminDirectUpload → OSS]
H --> I[material 记录入库]
F -->|否| J[结束]
subgraph 输入
P1(prompt)
P2(description/effect → 组合提示词)
P3(imageTopicId)
end
subgraph 输出
O1(videoUrl)
O2(proxyPreviewUrl)
O3(material.key)
end核心方法
buildPromptFromDescEffect(description, effect): 组合提示词fetchRemoteGenerationRecords():合并远端/本地生成记录downloadVideoBlob(url):下载生成的视频结果adminDirectUpload(blob, { key, contentType }):直传 OSS
输入示例
{
"prompt": "未来城市空中交通,数字艺术风格",
"generateType": "文生视频",
"platform": "即梦",
"frames": 121,
"aspectRatio": "9:16",
"imageTopicId": 1024
}- 输出示例
{
"videoUrl": "https://example.com/generated.mp4",
"proxyPreviewUrl": "https://example.com/proxy/preview.mp4",
"operation": { "saved": true },
"material": { "key": "ai/temp/video/task-12345.mp4" }
}- 错误示例
{ "message": "视频下载失败", "code": 10000 }11.3 素材选择与视频区域(StoryBoardItem.vue + VideoArea.vue)
flowchart TD
A[打开素材弹窗] --> B[confirmSelectMaterial(topicId)]
B --> C[匹配/选择视频素材]
C --> D[回填 item.videoUrl / videoKey]
D --> E[VideoArea.tryPlay() 自动播放]
D --> F[VideoPlayerDialog 预览]
D --> G[删除视频 → onRemoveVideo]
subgraph 输入
P1(topicId)
P2(item.title/description/line)
end
subgraph 输出
O1(item.videoUrl)
O2(item.videoKey):::oss
O3(item.thumbUrl)
end
classDef oss fill:#eef,stroke:#66f,stroke-width:1px;方法索引
confirmSelectMaterial(topicId):根据主题取素材handleVoiceSuccess(result):处理配音生成、确定后缀、直传/预签名VideoArea.onOpenMaterial/onOpenVideo/onRemoveVideo:交互能力
错误示例
{ "message": "未找到符合条件的素材", "code": 10000 }11.4 视频合成详细流程(VideoSynthesis.vue)
flowchart TD
S[开始 synthesizeVideos] --> U1[步骤1: 素材上传/注册]
U1 --> U2[步骤2: 组建Timeline并创建工程]
U2 --> U3[步骤3: 执行项目剪辑]
U3 --> U4[步骤4: 查询生成任务状态]
U4 --> U5[步骤5: 获取临时下载地址]
U5 --> U6[步骤6: 保存单帧分镜]
U6 --> D[下载视频]
subgraph 步骤1 细节
A1[videoKey → ossObjectExists]
A1 -->|存在| A2[fetchRegisterMediaInfo(video, oss://bucket/key)]
A1 -->|不存在| A3[使用 http(s) videoUrl]
A4[audioPath → ossObjectExists]
A4 -->|存在| A5[fetchRegisterMediaInfo(audio, oss://bucket/key)]
A4 -->|不存在| A6[ensureAudioPresignedUrl(blob→PUT uploadUrl→GET presign_url)]
A7[fetchUploadMediaByUrl({data}) → 返回 videoMediaId/audioMediaId]
end
subgraph 步骤2 细节
B1[buildTimelineSingle]
B2[fetchCreateEditingProject → ProjectId]
end
subgraph 步骤3 细节
C1[fetchSubmitMediaProducing → JobId]
end
subgraph 步骤4 细节
D1[循环 3-5s]
D2[fetchGetMediaProducingJob → Status]
D3{Status}
D3 -->|Success| D4[继续]
D3 -->|Failed| E1[报错并停止]
end
subgraph 步骤5 细节
F1[fetchGetPresignUrl(bucket,key) → presign_url]
end
subgraph 步骤6 细节
G1[fetchIceSave(data) → 成功]
end- 输入示例(步骤1:上传/注册)
{
"data": [
{
"title": "未来城市空中交通",
"description": "清晨的未来科技城市...",
"line": "欢迎来到2150年的新都市...",
"videoUrl": "https://img.stooland.com/ai/data/image/2-153-14908.mp4?...",
"audioUrl": "https://gsv.ai-lab.top/outputs/61dc5b...e.wav",
"thumbUrl": "https://img.stooland.com/...thumb.webp"
}
]
}- 输出示例(上传结果)
[
{
"videoMediaId": "606c6bf32a7171f0b86c0361c0d66502",
"audioMediaId": "90f192f32a7171f0bfc20360d1d76502",
"videoJobId": "9b5b2d640b6ac838",
"audioJobId": "40cbd605e042e02c"
}
]- 输入示例(步骤2:创建工程)
{
"title": "ICE自动剪辑2025-05-11-12-00",
"coverURL": "https://img.stooland.com/...thumb.webp",
"timeline": { "VideoTracks": [...], "AudioTracks": [...], "SubtitleTracks": [...] }
}- 输出示例(创建工程)
{ "ProjectId": "75573520d5294d87b6310f76bb32cd4d", "Status": 1 }- 输入示例(步骤3:提交剪辑)
{
"projectId": "75573520d5294d87b6310f76bb32cd4d",
"outputMediaTarget": "oss-object",
"outputMediaConfig": { "MediaURL": "https://aigc-sz-linkt.oss-cn-shenzhen.aliyuncs.com/ice-output/ICE自动剪辑2025-05-11-12-00.mp4" }
}- 输出示例(提交剪辑)
{ "JobId": "6de19229972c4e1795fc3aac4df1487d", "MediaId": "f40d73...", "ProjectId": "75573520..." }- 输出示例(步骤4:查询任务)
{ "MediaProducingJob": { "Status": "Success", "MediaURL": "https://aigc-sz-linkt.../ICE自动剪辑2025-05-11-12-00.mp4" } }- 输入/输出示例(步骤5:预签名)
// 输入
{ "bucket": "aigc-sz-linkt", "key": "ice-output/ICE自动剪辑2025-05-11-12-00.mp4" }
// 输出
{ "presign_url": "http://aigc-sz-linkt.oss-cn-shenzhen.aliyuncs.com/ice-output/ICE...mp4?x-oss-..." }- 输入/输出示例(步骤6:保存分镜)
// 输入
{ "title": "未来城市空中交通", "projectId": "...", "jobId": "...", "iceVideoUrl": "http://..." }
// 输出
{ "responseHeader": { "returnCode": 0, "message": "成功" } }- 下载示例
{ "fileName": "未来城市空中交通.mp4", "url": "http://...presign_url..." }11.5 错误数据示例与兼容策略
- 上传/注册异常:
{ "message": "上传ICE素材异常:返回为空", "returnCode": 10000 }- 缺少视频素材ID(步骤1校验)
{ "message": "第 1 条素材上传/注册失败:缺少视频素材ID", "returnCode": 10000 }- 生成任务失败(步骤4)
{ "MediaProducingJob": { "Status": "Failed", "Message": "剪辑失败" } }- 获取临时下载地址失败(步骤5)
{ "timestamp": 1760596678, "returnCode": 1001, "message": "参数错误,system_error_lack_params" }说明:后端已修正 getPresignUrl 的参数解析(region 默认 cn-shenzhen),该错误在缺省参数场景下已被避免。
- ICE 媒体已存在(后端兼容)
// 原始 ICE 异常
{ "code": "MediaAlreadyExist", "message": "registerMediaInfo: mediaId: 60b6c2d... 已存在" }
// 后端兼容后的返回(成功透传 MediaId)
{ "responseHeader": { "returnCode": 0, "message": "成功" }, "responseData": { "MediaId": "60b6c2d32e5071f080580360d1d76502", "existed": 1 } }11.6 方法索引与组件映射(扩展)
StoryBoard.vue
confirmSynthesis():合成确认弹窗startSynthesis():调用子组件synthesizeVideos(synthesisType)downloadStoryBoard():导出 ZIP 与 presign 视频loadImageTopics():主题列表加载与缓存
StoryBoardItem.vue
confirmSelectMaterial(topicId):素材选择handleVoiceSuccess(result):配音生成与直传处理openGenerateDialog()/handleGenerateSuccess(result):AI 生成分镜
VideoSynthesis.vue
synthesizeVideos(synthesisType):完整 6 步合成流程saveIce(data):保存单帧分镜downloadVideos(synthesisType):下载合成结果
utils/ice-lib.js
buildTimelineSingle(videoMediaId, audioMediaId, line, main, subtitleSegments, includeSubtitles):单分镜时间线convertStoryBoardDataToTimeline(storyBoardData, videoClipConfig, audioClipConfig, includeSubtitles):整片时间线
11.7 使用建议与注意事项
- 优先使用
videoKey/audioPath(OSS)注册素材,减少重复上传与网络风险。 - 当
audioUrl为blob:时,自动走直传流程以生成 ICE 可访问的presign_url。 - 轮询生成任务时合理控制间隔(3-5s),避免过度请求。
fetchGetPresignUrl已对region设置默认值,前端无需传参即可使用。- 后端对
MediaAlreadyExist进行兼容,前端只需使用返回的MediaId。
11.8 团队Wiki拆分与链接
为方便团队协作与按主题阅读,已将第11节拆分为4个子页:
- 分镜生成(Generate)→
./storyboard-wiki-generation.md - 素材处理(Materials)→
./storyboard-wiki-materials.md - 工程剪辑与合成(Synthesis)→
./storyboard-wiki-synthesis.md - 错误与排障(Errors & Troubleshooting)→
./storyboard-wiki-errors.md
如需导出到外部Wiki系统,可直接使用以上MD文件。
10.2 合成视频示例
输入:
// 准备好的分镜数据
const storyBoardData = [/* 包含视频URL和音频URL的分镜数据 */];
// 调用视频合成方法
startSynthesis();输出:
// 合成成功后的分镜数据
storyBoardData = [
{
// 原有分镜数据
iceVideoUrl: "https://example.com/synthesized-video.mp4", // 新增的合成视频URL
projectId: "prj-123456", // 新增的工程ID
jobId: "job-789012" // 新增的任务ID
},
// 更多分镜...
]