471 lines
16 KiB
Markdown
471 lines
16 KiB
Markdown
# 场景多候选功能规格文档
|
||
|
||
## ADDED Requirements
|
||
|
||
### Requirement: 场景候选数据结构
|
||
MUST: 每个场景必须支持存储多个候选视频素材,替代原有的单一素材模式。
|
||
**优先级:** 高
|
||
**版本:** v1.0
|
||
|
||
**数据结构:**
|
||
```typescript
|
||
interface Scene {
|
||
index: number; // 场景序号
|
||
candidates: Material[]; // 候选素材列表
|
||
duration: number; // 单场景时长
|
||
}
|
||
|
||
interface Material {
|
||
fileId: number; // 素材文件ID
|
||
fileUrl: string; // 素材文件URL
|
||
fileDuration?: number; // 素材实际时长(可选)
|
||
}
|
||
```
|
||
|
||
**验证规则:**
|
||
- 每个场景至少包含 1 个候选素材
|
||
- 每个场景最多包含 10 个候选素材
|
||
- 同一场景内的候选素材不能重复(基于 fileId 判断)
|
||
- 候选素材必须为视频类型
|
||
|
||
#### Scenario: 创建新场景
|
||
用户调整总时长和单切片时长后,系统自动创建对应数量的空场景。每个场景初始化时包含一个空的候选列表,等待用户添加素材。
|
||
|
||
#### Scenario: 添加候选
|
||
用户点击场景格子,打开候选选择弹窗,从素材库中选择多个视频素材添加到场景的候选列表中。
|
||
|
||
#### Scenario: 验证重复
|
||
当用户尝试添加已在候选列表中的素材时,系统自动检查并阻止添加,同时在UI上显示提示信息。
|
||
|
||
#### Scenario: 限制数量
|
||
当场景的候选数量达到上限(10个)时,系统禁用添加按钮,并提示用户已达到最大候选数量。
|
||
|
||
---
|
||
|
||
### Requirement: 场景候选管理操作
|
||
MUST: 用户必须能够对场景的候选素材进行增删改查操作。
|
||
|
||
**操作类型:**
|
||
1. **添加候选**:从素材库选择视频添加到场景候选
|
||
2. **移除候选**:从场景候选中移除指定的素材
|
||
3. **清空场景**:移除场景的所有候选素材
|
||
4. **查看候选**:以弹窗或侧边栏形式展示所有候选
|
||
|
||
**交互规则:**
|
||
- 点击场景格子打开候选选择弹窗
|
||
- 弹窗中显示当前场景已选候选数量
|
||
- 素材库中已选候选显示"已选择"状态
|
||
- 支持批量选择多个候选后一次性确认
|
||
|
||
#### Scenario: 添加单个候选
|
||
用户在场景格子上的弹窗中选择一个素材,确认后该素材被添加到场景的候选列表中。
|
||
|
||
#### Scenario: 批量添加候选
|
||
用户在素材库中选择多个素材,然后点击"批量添加"按钮,一次性将所有选中的素材添加到场景候选列表中。
|
||
|
||
#### Scenario: 移除候选
|
||
用户在场景格子或弹窗中点击候选素材上的移除按钮,系统将该候选从场景候选列表中删除。
|
||
|
||
#### Scenario: 查看候选详情
|
||
用户点击场景格子,系统以弹窗形式展示该场景的所有候选素材,包括缩略图、文件名和时长信息。
|
||
|
||
---
|
||
|
||
### Requirement: 一键填充功能优化
|
||
MUST: 系统必须优化一键填充功能,自动为每个场景添加多个候选素材。
|
||
|
||
**填充策略:**
|
||
1. **随机分配**:从素材库中随机选择素材分配给每个场景
|
||
2. **防重复**:确保同一场景内的候选不重复
|
||
3. **尽量均匀**:尽可能平均分配素材到各个场景
|
||
4. **数量控制**:每个场景填充 3-5 个候选(根据素材库数量动态调整)
|
||
|
||
**算法逻辑:**
|
||
```
|
||
For each scene in scenes:
|
||
If scene.candidates.isEmpty():
|
||
randomly select 3-5 materials from groupFiles
|
||
ensure no duplicate within scene
|
||
add to scene.candidates
|
||
```
|
||
|
||
#### Scenario: 自动填充空场景
|
||
用户点击"一键填充"按钮,系统只填充空的场景,已有候选的场景保持不变。
|
||
|
||
#### Scenario: 补充候选数量
|
||
如果场景的候选数量不足默认数量(3个),系统自动补充候选素材到默认数量。
|
||
|
||
#### Scenario: 全量填充
|
||
用户选择"全量填充"选项,系统为所有场景(包括已有候选的场景)重新填充候选素材。
|
||
|
||
#### Scenario: 智能跳过
|
||
系统自动检测已填满的场景并跳过,只处理需要填充的场景。
|
||
|
||
---
|
||
|
||
### Requirement: 场景候选可视化展示
|
||
|
||
MUST: 系统必须在前端界面中清晰展示每个场景的候选数量和候选列表。
|
||
|
||
**UI 展示元素:**
|
||
1. **候选数量标签**:在场景格子上显示"候选数量/X"
|
||
2. **候选列表预览**:以缩略图或标签形式展示候选
|
||
3. **使用状态标识**:标识哪些候选已被使用
|
||
4. **悬停提示**:鼠标悬停显示候选详细信息
|
||
|
||
**样式规范:**
|
||
- 候选数量使用徽标组件(badge)展示
|
||
- 候选列表使用小缩略图或文件图标
|
||
- 已使用候选使用不同颜色或图标标识
|
||
- 悬停提示显示候选文件名和时长
|
||
|
||
#### Scenario: 查看候选概览
|
||
用户在主界面上可以直观地看到每个场景显示的候选数量,快速了解整体配置情况。
|
||
|
||
#### Scenario: 预览候选内容
|
||
用户将鼠标悬停在场景格子上,系统显示该场景所有候选的缩略图预览。
|
||
|
||
#### Scenario: 识别使用状态
|
||
用户可以通过不同的视觉标识(如颜色、图标)快速识别哪些候选素材已被使用。
|
||
|
||
#### Scenario: 快速定位
|
||
用户通过可视化展示快速定位需要编辑的场景,提高操作效率。
|
||
|
||
---
|
||
|
||
### Requirement: 场景候选防重复机制
|
||
|
||
MUST: 系统必须确保同一场景内的候选素材不重复,保证素材多样性。
|
||
|
||
**验证机制:**
|
||
1. **前端验证**:在选择素材时实时检查并提示
|
||
2. **后端验证**:在提交时进行最终验证
|
||
3. **UI 反馈**:已选择的素材显示禁用或选中状态
|
||
|
||
**重复判断规则:**
|
||
- 基于 `fileId` 进行唯一性判断
|
||
- `fileId` 相同视为重复素材
|
||
- 允许同一素材在不同场景中出现
|
||
|
||
#### Scenario: 阻止重复添加
|
||
用户在选择素材时,如果该素材已在候选列表中,系统立即提示"该素材已在候选列表中",并阻止添加。
|
||
|
||
#### Scenario: 视觉反馈
|
||
已选择的素材在素材库中显示为禁用状态,用户可以直观地看到哪些素材已被选择。
|
||
|
||
#### Scenario: 批量去重
|
||
一键填充功能自动去除重复候选,确保每个场景内的候选都是唯一的。
|
||
|
||
#### Scenario: 手动去重
|
||
用户可以在场景候选列表中手动移除重复的候选素材,系统保持列表的唯一性。
|
||
|
||
---
|
||
|
||
## MODIFIED Requirements
|
||
|
||
### Requirement: 混剪任务提交数据结构
|
||
MUST: 系统必须修改混剪任务提交数据结构以支持场景多候选模式。
|
||
|
||
**修改前:**
|
||
MUST: ```json
|
||
{
|
||
"title": "视频标题",
|
||
"materials": [
|
||
{"fileId": 1, "fileUrl": "url1", "duration": 3},
|
||
{"fileId": 2, "fileUrl": "url2", "duration": 3}
|
||
],
|
||
"produceCount": 3
|
||
}
|
||
```
|
||
|
||
**系统必须修改为以下结构:**
|
||
```json
|
||
{
|
||
"title": "视频标题",
|
||
"scenes": [
|
||
{
|
||
"duration": 3,
|
||
"candidates": [
|
||
{"fileId": 1, "fileUrl": "url1", "fileDuration": 60},
|
||
{"fileId": 2, "fileUrl": "url2", "fileDuration": 45}
|
||
]
|
||
},
|
||
{
|
||
"duration": 3,
|
||
"candidates": [
|
||
{"fileId": 3, "fileUrl": "url3", "fileDuration": 50},
|
||
{"fileId": 4, "fileUrl": "url4", "fileDuration": 55}
|
||
]
|
||
}
|
||
],
|
||
"produceCount": 3
|
||
}
|
||
```
|
||
|
||
**向后兼容:**
|
||
- 支持旧的 `materials` 字段格式
|
||
- 当接收到 `materials` 时,自动转换为新的 `scenes` 格式
|
||
- 保持现有 API 端点不变
|
||
|
||
#### Scenario: 提交新格式
|
||
前端使用新的 scenes 格式提交混剪任务,包含每个场景的候选素材列表。
|
||
|
||
#### Scenario: 兼容旧格式
|
||
后端接收到包含 materials 字段的旧格式数据时,自动将其转换为 scenes 格式(每个场景包含一个候选)。
|
||
|
||
#### Scenario: 数据转换
|
||
系统将旧格式的 materials 数组转换为新格式的 scenes 数组,每个场景包含一个候选素材。
|
||
|
||
#### Scenario: 版本协商
|
||
前后端协商确定使用的数据格式,优先使用新格式,向后兼容旧格式。
|
||
|
||
---
|
||
|
||
### Requirement: 批量混剪随机选择逻辑
|
||
MUST: 系统必须实现两层随机选择逻辑以最大化视频内容差异。
|
||
|
||
**修改前:**
|
||
MUST: 使用相同的素材列表,通过随机起点实现差异化:
|
||
```java
|
||
// 每个视频使用相同的素材,不同的截取起点
|
||
List<MaterialItem> materials = createReqVO.getMaterials();
|
||
for (int videoIndex = 0; videoIndex < produceCount; videoIndex++) {
|
||
produceSingleVideoWithOffset(materials, videoIndex, userId, cropMode);
|
||
}
|
||
```
|
||
|
||
**系统必须修改为以下结构:**
|
||
系统必须从每个场景的候选中随机选择一个素材,然后仍然使用随机起点:
|
||
```java
|
||
// 从每个场景的候选中随机选择素材,然后通过随机起点实现差异化
|
||
List<SceneConfig> scenes = createReqVO.getScenes();
|
||
for (int videoIndex = 0; videoIndex < produceCount; videoIndex++) {
|
||
List<MaterialItem> selectedMaterials = new ArrayList<>();
|
||
for (SceneConfig scene : scenes) {
|
||
// 从场景的候选中随机选择一个素材
|
||
MaterialItem selected = selectRandomCandidate(scene.getCandidates(), videoIndex, scene.getIndex());
|
||
selectedMaterials.add(selected);
|
||
}
|
||
// 对选中的素材使用随机起点生成视频
|
||
produceSingleVideoWithOffset(selectedMaterials, videoIndex, userId, cropMode);
|
||
}
|
||
```
|
||
|
||
**随机选择算法:**
|
||
- **第一层随机**:基于 `videoIndex`、`sceneIndex` 和候选素材 `fileId` 从每个场景的候选中随机选择一个素材
|
||
- **第二层随机**:对选中的素材仍然使用随机起点实现进一步差异化
|
||
- 确保同一 `videoIndex` 在不同时间运行结果一致
|
||
- 保证不同 `videoIndex` 选择的素材不同(尽可能)
|
||
|
||
**示例:**
|
||
- 场景1有候选[A, B, C],场景2有候选[D, E, F],场景3有候选[G, H, I]
|
||
- 视频1:可能选择 A + D + G,然后从A的随机起点1、B的随机起点2...生成
|
||
- 视频2:可能选择 B + E + H,然后从B的随机起点2、E的随机起点3...生成
|
||
- 视频3:可能选择 C + F + I,然后从C的随机起点3、F的随机起点1...生成
|
||
|
||
#### Scenario: 生成第一个视频
|
||
从每个场景的候选中使用第一层随机选择一个素材,然后对每个素材应用随机起点生成视频。
|
||
|
||
#### Scenario: 生成第二个视频
|
||
从每个场景的候选中再次随机选择(尽量与第一个视频不同),然后应用不同的随机起点。
|
||
|
||
#### Scenario: 生成第N个视频
|
||
每个视频都经历两层随机选择:第一层从场景候选中选择,第二层对选中素材应用随机起点。
|
||
|
||
#### Scenario: 保证差异化
|
||
通过两层随机性(候选选择 + 随机起点),最大化每个生成视频的内容差异。
|
||
|
||
---
|
||
|
||
### Requirement: 场景配置验证规则
|
||
MUST: 系统必须更新场景配置验证规则以支持多候选场景。
|
||
|
||
**修改前:**
|
||
MUST: 验证素材列表:
|
||
```java
|
||
// 验证素材列表不为空
|
||
if (req.getMaterials() == null || req.getMaterials().isEmpty()) {
|
||
throw new IllegalArgumentException("素材列表不能为空");
|
||
}
|
||
|
||
// 验证总时长
|
||
int totalDuration = req.getMaterials().stream()
|
||
.mapToInt(MixTaskSaveReqVO.MaterialItem::getDuration)
|
||
.sum();
|
||
```
|
||
|
||
**系统必须修改为以下结构:**
|
||
系统必须验证场景配置:
|
||
```java
|
||
// 验证场景列表不为空
|
||
if (req.getScenes() == null || req.getScenes().isEmpty()) {
|
||
throw new IllegalArgumentException("场景列表不能为空");
|
||
}
|
||
|
||
// 验证每个场景至少有一个候选
|
||
for (SceneConfig scene : req.getScenes()) {
|
||
if (scene.getCandidates() == null || scene.getCandidates().isEmpty()) {
|
||
throw new IllegalArgumentException("场景" + scene.getIndex() + "没有候选素材");
|
||
}
|
||
if (scene.getCandidates().size() > MAX_CANDIDATES_PER_SCENE) {
|
||
throw new IllegalArgumentException("场景候选数量不能超过" + MAX_CANDIDATES_PER_SCENE);
|
||
}
|
||
}
|
||
|
||
// 验证总时长
|
||
int totalDuration = req.getScenes().stream()
|
||
.mapToInt(scene -> scene.getDuration() * scene.getCandidates().size())
|
||
.sum();
|
||
```
|
||
|
||
#### Scenario: 验证场景完整性
|
||
检查所有场景都必须包含至少一个候选素材,缺少候选的场景抛出异常。
|
||
|
||
#### Scenario: 验证候选数量
|
||
检查每个场景的候选数量在允许范围内(1-10个),超过上限抛出异常。
|
||
|
||
#### Scenario: 验证总时长
|
||
根据场景数量和候选数量计算总时长,验证是否在 15-30 秒范围内。
|
||
|
||
#### Scenario: 验证素材有效性
|
||
检查所有候选素材的文件ID和URL有效性,无效素材导致验证失败。
|
||
|
||
---
|
||
|
||
## 性能要求
|
||
|
||
### Requirement: 场景加载性能
|
||
**目标:** 场景数据加载时间 < 2 秒
|
||
**测量:** 从用户选择素材分组到场景渲染完成的时间
|
||
**场景:** 50 个候选素材,5 个场景
|
||
|
||
#### Scenario: 正常加载
|
||
MUST: 在50个候选素材、5个场景的情况下,场景数据加载时间不超过2秒。
|
||
|
||
#### Scenario: 大量素材加载
|
||
测试100个候选素材、10个场景的加载性能,确保仍在可接受范围内。
|
||
|
||
#### Scenario: 网络延迟场景
|
||
在网络延迟300ms的情况下,场景加载时间仍在用户可接受范围内。
|
||
|
||
#### Scenario: 缓存优化
|
||
利用前端缓存机制,提升重复访问时的场景加载速度。
|
||
|
||
---
|
||
|
||
### Requirement: 批量混剪性能
|
||
**目标:** 混剪任务创建时间与现有实现持平(< 3 秒)
|
||
**测量:** 从用户点击"开始混剪"到任务创建成功的时间
|
||
**场景:** 5 个场景,每个场景 3-5 个候选,生成 5 个视频
|
||
|
||
#### Scenario: 标准场景混剪
|
||
MUST: 在标准配置下(5个场景,每个场景3-5个候选),混剪任务创建时间不超过3秒。
|
||
|
||
#### Scenario: 大量候选混剪
|
||
测试每个场景10个候选的极限情况,性能仍在可接受范围内。
|
||
|
||
#### Scenario: 批量生成性能
|
||
生成5个视频的批量混剪性能与现有实现持平。
|
||
|
||
#### Scenario: 并发场景
|
||
测试多个用户同时创建混剪任务的性能表现。
|
||
|
||
---
|
||
|
||
### Requirement: 内存使用
|
||
**目标:** 前端内存使用增长 < 20%
|
||
**测量:** 场景候选功能开启前后的内存使用对比
|
||
**场景:** 长时间使用混剪功能,累积创建多个任务
|
||
|
||
#### Scenario: 正常使用内存
|
||
MUST: 用户正常操作混剪功能,内存使用增长不超过20%。
|
||
|
||
#### Scenario: 长时间使用
|
||
用户连续使用混剪功能1小时,内存无明显泄漏。
|
||
|
||
#### Scenario: 大量数据处理
|
||
处理大量候选素材时,内存使用保持在合理范围内。
|
||
|
||
#### Scenario: 内存回收
|
||
页面切换或刷新后,前端内存能够正确释放。
|
||
|
||
---
|
||
|
||
## 兼容性要求
|
||
|
||
### Requirement: 向后兼容
|
||
**要求:** 支持现有的 `materials` 格式
|
||
**实现:** 自动转换旧格式为新格式
|
||
**测试:** 使用旧格式创建混剪任务
|
||
|
||
#### Scenario: 旧格式请求
|
||
MUST: 后端接收到包含materials字段的请求时,自动转换为scenes格式。
|
||
|
||
#### Scenario: 新格式请求
|
||
前端优先使用新的scenes格式提交请求。
|
||
|
||
#### Scenario: 格式检测
|
||
系统能够自动检测请求使用的格式并进行相应处理。
|
||
|
||
#### Scenario: 错误处理
|
||
当格式转换失败时,提供清晰的错误信息。
|
||
|
||
---
|
||
|
||
### Requirement: 渐进式迁移
|
||
**要求:** 用户可以选择使用新模式或旧模式
|
||
**实现:** 通过功能开关控制
|
||
**场景:** 新用户使用新模式,老用户可以选择继续使用旧模式
|
||
|
||
#### Scenario: 功能开关
|
||
MUST: 提供开关让用户选择使用新模式或旧模式。
|
||
|
||
#### Scenario: 用户偏好保存
|
||
用户的选择偏好能够持久化保存,下次访问时保持上次选择。
|
||
|
||
#### Scenario: 模式切换
|
||
用户可以在新旧模式之间自由切换。
|
||
|
||
#### Scenario: 默认模式
|
||
新用户默认使用新模式,老用户默认使用旧模式。
|
||
|
||
---
|
||
|
||
## 安全要求
|
||
|
||
### Requirement: 输入验证
|
||
**要求:** 严格验证所有用户输入
|
||
**范围:** 文件ID、URL、候选数量等
|
||
**场景:** 防止恶意用户提交非法数据
|
||
|
||
#### Scenario: 文件ID验证
|
||
MUST: 验证所有文件ID必须是有效的数字,且对应的文件存在。
|
||
|
||
#### Scenario: URL验证
|
||
验证所有URL必须是有效的OSS地址,防止XSS攻击。
|
||
|
||
#### Scenario: 候选数量限制
|
||
限制候选数量在合理范围内,防止DDoS攻击。
|
||
|
||
#### Scenario: SQL注入防护
|
||
使用参数化查询,防止SQL注入攻击。
|
||
|
||
---
|
||
|
||
### Requirement: 权限控制
|
||
**要求:** 候选素材必须属于当前用户或有权限访问
|
||
**实现:** 后端验证素材所有权
|
||
**场景:** 用户尝试添加他人素材到候选列表
|
||
|
||
#### Scenario: 素材所有权验证
|
||
MUST: 后端验证候选素材是否属于当前用户或用户有权限访问。
|
||
|
||
#### Scenario: 权限检查
|
||
对每个候选素材进行权限检查,无权限的素材拒绝添加。
|
||
|
||
#### Scenario: 越权防护
|
||
防止用户访问或操作其他用户的素材。
|
||
|
||
#### Scenario: 审计日志
|
||
记录所有素材访问和操作日志,便于安全审计。
|