Files
sionrui/openspec/changes/refactor-mix-scene编排/proposal.md
2025-12-22 00:15:02 +08:00

13 KiB
Raw Blame History

混剪场景编排功能重新设计提案

变更概述

变更ID refactor-mix-scene编排 日期: 2025-12-21 优先级:

Why (为什么需要这个变更)

当前混剪功能的单一场景模式导致批量生成视频时内容高度相似,无法满足用户对视频多样性的需求。通过引入多候选场景模式,用户可以为每个场景准备多个候选素材,系统在批量混剪时从每个场景的候选中随机选择,从而生成内容差异显著的多个视频。这将显著提升用户体验,满足内容创作者对多样性的追求。

问题背景

当前的混剪场景编排功能存在以下限制:

  1. 场景素材单一性:每个场景只能选择一个视频素材,导致批量混剪时视频内容相似度极高
  2. 多样性不足:虽然后端通过随机起点实现差异化,但本质上仍使用相同的素材池
  3. 用户需求未满足:用户希望一次混剪能生成内容差异更大的多个视频

解决方案

核心设计理念

重新设计场景编排为**"多候选场景模式"**

  • 每个场景包含多个候选视频(每个场景内视频不重复)
  • 批量混剪时,从每个场景的候选中随机选择一个视频
  • 仍然使用随机起点对选中的素材进行二次随机处理
  • 两层随机性(候选选择 + 随机起点)极大增加最终视频的多样性

关键特性

  1. 场景多候选:每个场景可以添加多个候选视频素材
  2. 防重复机制:同一场景内的候选视频不能重复
  3. 智能填充
    • 一键自动为每个场景添加多个候选
    • 支持从素材库快速选择
  4. 随机生成:批量混剪时从每个场景的候选中随机选择
  5. 可视化展示:清晰展示每个场景的候选数量和使用状态

技术架构调整

前端变更

文件位置: frontend/app/web-gold/src/views/material/Mix.vue

主要改动:

1. 数据结构重构

// 原有结构(单一素材)
const scene = {
  fileId: 123,
  fileUrl: 'xxx.mp4'
}

// 新结构(多候选)
const scene = {
  index: 0,
  duration: 3,
  candidates: [
    {fileId: 123, fileUrl: 'xxx1.mp4', fileDuration: 60},
    {fileId: 124, fileUrl: 'xxx2.mp4', fileDuration: 45},
    {fileId: 125, fileUrl: 'xxx3.mp4', fileDuration: 55}
  ]
}

2. 场景格子 UI 更新

  • 候选数量标签:在场景格子上方显示 候选 3/10
  • 候选列表预览:悬停时显示候选素材的缩略图列表
  • 状态指示
    • 空场景:虚线边框,提示"点击选择"
    • 已填充:实线边框,显示候选数量徽标
    • 部分填充:不同颜色标识
  • 移除按钮:每个候选右上角显示删除按钮

3. 交互流程优化

  • 点击场景格子 → 打开候选选择弹窗
  • 弹窗内容
    • 顶部显示:场景1 - 已选择 3/10 个候选
    • 主体区域:素材库网格(支持多选)
    • 底部操作:全选 反选 确定 取消
  • 批量操作
    • 支持 Ctrl+Click 多选
    • 支持 Shift+Click 范围选择
    • 一键全选/清空

4. 一键填充增强(核心优化)

功能描述: 一键填充功能从原有的"随机填充空场景"升级为"智能多候选填充",能够自动为每个场景分配多个不重复的候选素材。

填充策略选择:

// 提供三种填充模式
const FILL_STRATEGIES = {
  EMPTY_ONLY: 'empty_only',      // 仅填充空场景(默认)
  SUPPLEMENT: 'supplement',      // 补充不足场景到3个候选
  FULL_FILL: 'full_fill'         // 全量重新填充所有场景
}

智能分配算法:

/**
 * 优化后的一键填充逻辑
 * @param strategy 填充策略
 * @param targetCount 目标候选数量默认3-5个
 */
const autoFillScenes = (strategy = 'empty_only', targetCount = 3) => {
  // 1. 收集所有可用的素材
  const availableMaterials = [...groupFiles.value];

  // 2. 统计当前已使用的素材(避免重复)
  const usedMaterialIds = new Set();
  scenes.value.forEach(scene => {
    scene.candidates.forEach(candidate => {
      usedMaterialIds.add(candidate.fileId);
    });
  });

  // 3. 过滤可用素材(排除已使用的)
  const unusedMaterials = availableMaterials.filter(
    material => !usedMaterialIds.has(material.id)
  );

  // 4. 根据策略执行填充
  scenes.value.forEach((scene, sceneIndex) => {
    const currentCount = scene.candidates.length;
    let needFill = false;
    let fillCount = targetCount;

    // 判断是否需要填充
    switch (strategy) {
      case 'empty_only':
        needFill = currentCount === 0;
        break;
      case 'supplement':
        needFill = currentCount < targetCount;
        fillCount = targetCount - currentCount;
        break;
      case 'full_fill':
        needFill = true;
        fillCount = targetCount;
        break;
    }

    if (needFill && unusedMaterials.length > 0) {
      // 5. 为当前场景随机选择素材(确保不重复)
      const selectedMaterials = randomlySelectMaterials(
        fillCount,
        unusedMaterials,
        sceneIndex  // 使用场景索引作为随机种子的一部分
      );

      // 6. 添加到场景候选列表
      scene.candidates.push(...selectedMaterials);

      // 7. 从可用素材中移除已选择的(避免分配给其他场景)
      selectedMaterials.forEach(selected => {
        const index = unusedMaterials.findIndex(m => m.id === selected.id);
        if (index > -1) {
          unusedMaterials.splice(index, 1);
        }
      });
    }
  });

  // 8. 显示填充结果提示
  showFillResultNotification();
}

/**
 * 随机选择素材工具函数
 * @param count 需要选择的数量
 * @param materials 素材池
 * @param seed 随机种子(基于场景索引)
 * @returns 选中的素材数组
 */
const randomlySelectMaterials = (count, materials, seed) => {
  // 使用Fisher-Yates洗牌算法确保随机性
  const shuffled = [...materials];

  // 基于种子创建确定性随机(同一场景索引结果一致)
  const random = createDeterministicRandom(seed);

  // 洗牌
  for (let i = shuffled.length - 1; i > 0; i--) {
    const j = Math.floor(random() * (i + 1));
    [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
  }

  // 返回前N个
  return shuffled.slice(0, Math.min(count, shuffled.length));
}

防重复机制(优化):

  1. 场景内去重:确保同一场景内的候选素材不重复(必须)
  2. 跨场景复用(可选):允许同一素材在不同场景中出现
    • 优点:提高素材利用率,适合素材库不足的场景
    • 缺点:可能降低视频差异性
    • 配置项:用户可选择"严格模式"(禁止跨场景重复)或"宽松模式"(允许跨场景重复)
  3. 实时更新:每次填充后立即更新已使用素材列表
  4. 视觉反馈
    • 严格模式:已使用素材显示禁用状态
    • 宽松模式:已使用素材显示使用次数标记(如"已使用 2 次"

数量控制逻辑:

  • 默认数量:每个场景填充 3 个候选
  • 自适应调整:根据素材库总量动态调整
    • 素材库 < 10个每个场景 1-2个候选
    • 素材库 10-50个每个场景 3-4个候选
    • 素材库 > 50个每个场景 4-5个候选
  • 上限保护:单个场景最多 10 个候选

用户体验优化:

  • 进度提示:填充过程中显示进度条
  • 结果反馈:填充完成后显示"已为X个场景填充Y个候选"
  • 撤销操作:支持一键撤销最近的填充操作
  • 智能建议:根据素材库情况建议最佳填充策略

边界情况处理:

  1. 素材库不足场景

    // 场景5个场景每个需要3个候选但素材库只有10个素材
    // 解决方案:
    // 1. 自动切换到"宽松模式",允许跨场景复用
    // 2. 调整目标数量:根据素材库/场景数计算最优分配
    // 3. 提示用户:"素材库不足,已自动调整为宽松模式"
    
  2. 素材库为空

    • 提示"素材库为空,请先上传素材"
    • 禁用一键填充按钮
    • 提供快速跳转链接到素材上传页
  3. 场景数过多

    • 当场景数 × 目标候选数 > 素材库数量时
    • 自动建议减少场景数或增加素材库
    • 提供"智能合并场景"建议
  4. 批量操作确认

    • 全选/清空等操作前显示确认对话框
    • 显示影响范围:如"将影响 5 个场景,共 15 个候选"
    • 提供预览功能
  5. 数据一致性检查

    • 页面刷新后自动恢复场景配置
    • 检测并修复损坏的场景数据
    • 提示用户进行数据同步

示例场景:

素材库:[A, B, C, D, E, F, G, H, I, J] (10个素材)
场景数3个场景
目标每个场景3个候选

填充结果:
- 场景1[A, D, G]
- 场景2[B, E, H]
- 场景3[C, F, I]
剩余素材:[J] (未使用,避免浪费)

5. 候选管理功能

  • 添加候选:从素材库选择 → 检查重复 → 添加到候选列表
  • 移除候选:点击候选右上角 × → 从列表中移除
  • 查看候选详情:点击场景格子 → 弹窗显示所有候选详情
  • 清空场景:点击"清空"按钮 → 移除所有候选

6. 防重复验证

  • 前端实时检查:选择素材时检查是否已存在于候选列表
  • 视觉反馈:已选择的素材显示禁用状态或"已选择"标记
  • 提示信息:尝试添加重复素材时显示提示"该素材已在候选列表中"

7. 数据提交调整

// 修改 handleSubmit 中的数据结构
const submitData = {
  title: formData.value.title,
  scenes: scenes.value.map(scene => ({
    duration: scene.duration,
    candidates: scene.candidates
  })),
  produceCount: formData.value.produceCount,
  cropMode: formData.value.cropMode
};

后端变更

文件位置:

  • yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/mix/vo/MixTaskSaveReqVO.java
  • yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/mix/service/MixTaskServiceImpl.java
  • yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/media/BatchProduceAlignment.java

主要改动:

  1. 修改 API 数据结构:支持场景多候选
  2. 更新批量混剪逻辑:从每个场景候选中随机选择素材,然后使用随机起点
  3. 实现两层随机算法:第一层从候选中选择,第二层使用随机起点

数据库变更

影响范围: 无需数据库结构变更

  • 前端本地存储场景配置
  • 后端通过 JSON 传递候选数据

预期效果

用户体验提升

  1. 多样性提升:批量混剪的视频内容差异显著增大
  2. 操作便捷性:一键填充和批量选择功能
  3. 可视化体验:清晰的场景候选展示

技术收益

  1. 代码复用:保持现有框架结构
  2. 性能优化:随机选择算法高效
  3. 向后兼容:可选模式,不影响现有功能

风险评估

技术风险

  • 中等风险:需要修改前后端多个文件
  • 兼容性:需要确保现有功能不受影响

缓解措施

  1. 渐进式迁移:保留现有模式作为备选
  2. 充分测试:覆盖各种使用场景
  3. 回滚方案:保留现有代码分支

实施计划

阶段一:数据结构设计

  • 设计新的前后端数据结构
  • 定义 API 接口规范

阶段二:前端实现

  • 修改 Mix.vue 组件
  • 更新数据处理逻辑
  • 优化用户界面

阶段三:后端实现

  • 更新 VO 对象
  • 修改混剪服务逻辑
  • 调整随机算法

阶段四:测试验证

  • 单元测试
  • 集成测试
  • 用户验收测试

成功标准

  1. 功能完整性:所有设计功能正常工作
  2. 性能指标:批量混剪性能无明显下降
  3. 用户体验:操作流程顺畅,界面直观
  4. 代码质量:代码结构清晰,有充分注释

相关资源

  • 前端代码: frontend/app/web-gold/src/views/material/Mix.vue
  • 后端 API yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/mix/
  • 混剪服务: yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/mix/service/MixTaskServiceImpl.java
  • 批量处理: yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/media/BatchProduceAlignment.java

决策点

  1. 默认候选数量建议每个场景默认3-5个候选
  2. 最大候选限制建议每个场景最多10个候选
  3. 随机算法基于文件ID和场景索引的确定性随机
  4. UI 展示方式:采用标签页或下拉列表展示候选

后续优化

  1. 智能推荐:基于视频相似度推荐候选
  2. 场景模板:保存和复用场景配置
  3. 批量编辑:支持跨场景批量操作