feat: 优化功能

This commit is contained in:
2025-12-22 00:15:02 +08:00
parent b80de78d7c
commit 8d7bc0d47f
12 changed files with 2513 additions and 595 deletions

View File

@@ -50,8 +50,14 @@ public class MixTaskServiceImpl implements MixTaskService {
// 1. 校验时长
validateDuration(createReqVO);
log.info("[MixTask][创建任务] userId={}, title={}, materialCount={}, produceCount={}",
// 记录日志(支持新格式)
if (createReqVO.isUsingNewFormat()) {
log.info("[MixTask][创建任务] userId={}, title={}, sceneCount={}, produceCount={}",
userId, createReqVO.getTitle(), createReqVO.getScenes().size(), createReqVO.getProduceCount());
} else {
log.info("[MixTask][创建任务] userId={}, title={}, materialCount={}, produceCount={}",
userId, createReqVO.getTitle(), createReqVO.getMaterials().size(), createReqVO.getProduceCount());
}
// 1. 创建初始任务对象
MixTaskDO task = MixTaskUtils.createInitialTask(createReqVO, userId);
@@ -387,10 +393,25 @@ public class MixTaskServiceImpl implements MixTaskService {
List<String> jobIdWithUrls = new ArrayList<>();
int produceCount = createReqVO.getProduceCount();
// 循环生成多个视频,每个视频使用不同的截取起始点
// 循环生成多个视频
for (int videoIndex = 0; videoIndex < produceCount; videoIndex++) {
List<MixTaskSaveReqVO.MaterialItem> selectedMaterials;
// 检查是否使用新格式(场景多候选)
if (createReqVO.isUsingNewFormat() && createReqVO.getScenes() != null) {
// 新格式:从每个场景的候选中随机选择一个素材
selectedMaterials = selectRandomMaterialsFromScenes(
createReqVO.getScenes(),
videoIndex
);
} else {
// 兼容旧格式:使用原有素材列表
selectedMaterials = createReqVO.getMaterials();
}
// 对选中的素材应用随机起点生成视频
String jobIdWithUrl = batchProduceAlignment.produceSingleVideoWithOffset(
createReqVO.getMaterials(),
selectedMaterials,
videoIndex,
userId,
createReqVO.getCropMode()
@@ -411,6 +432,48 @@ public class MixTaskServiceImpl implements MixTaskService {
}
}
/**
* 从场景候选中随机选择素材(第一层随机)
* @param scenes 场景配置列表
* @param videoIndex 视频索引(用于随机种子)
* @return 选中的素材列表
*/
private List<MixTaskSaveReqVO.MaterialItem> selectRandomMaterialsFromScenes(
List<MixTaskSaveReqVO.SceneConfig> scenes, int videoIndex) {
List<MixTaskSaveReqVO.MaterialItem> selected = new ArrayList<>();
for (int sceneIndex = 0; sceneIndex < scenes.size(); sceneIndex++) {
MixTaskSaveReqVO.SceneConfig scene = scenes.get(sceneIndex);
List<MixTaskSaveReqVO.MaterialItem> candidates = scene.getCandidates();
if (candidates == null || candidates.isEmpty()) {
log.warn("[MixTask][场景{}没有候选素材]", sceneIndex);
continue;
}
// 基于 videoIndex、sceneIndex 和素材ID创建确定性随机
int seed = videoIndex * 1000 + sceneIndex * 100;
if (!candidates.isEmpty() && candidates.get(0) != null) {
seed += candidates.get(0).getFileId().intValue();
}
java.util.Random random = new java.util.Random(seed);
// 随机选择一个候选
int selectedIndex = random.nextInt(candidates.size());
MixTaskSaveReqVO.MaterialItem selectedCandidate = candidates.get(selectedIndex);
// 设置场景时长
selectedCandidate.setDuration(scene.getDuration());
selected.add(selectedCandidate);
log.debug("[MixTask][视频{}场景{}选择素材{}]",
videoIndex, sceneIndex, selectedCandidate.getFileId());
}
return selected;
}
/**
* 更新任务状态
@@ -534,6 +597,81 @@ public class MixTaskServiceImpl implements MixTaskService {
* 校验混剪任务时长
*/
private void validateDuration(MixTaskSaveReqVO req) {
// 检查是否使用新格式(场景多候选)
if (req.isUsingNewFormat()) {
// 新格式:验证场景配置
validateScenesFormat(req);
} else {
// 兼容旧格式:验证素材列表
validateMaterialsFormat(req);
}
}
/**
* 验证新格式场景配置
*/
private void validateScenesFormat(MixTaskSaveReqVO req) {
// 1. 场景列表不能为空
if (req.getScenes() == null || req.getScenes().isEmpty()) {
throw new IllegalArgumentException("场景列表不能为空");
}
// 2. 验证每个场景
for (int i = 0; i < req.getScenes().size(); i++) {
MixTaskSaveReqVO.SceneConfig scene = req.getScenes().get(i);
// 场景序号验证
if (scene.getIndex() == null) {
scene.setIndex(i);
}
// 候选列表验证
if (scene.getCandidates() == null || scene.getCandidates().isEmpty()) {
throw new IllegalArgumentException("场景" + i + "没有候选素材");
}
// 候选数量限制最多10个
if (scene.getCandidates().size() > 10) {
throw new IllegalArgumentException("场景" + i + "候选数量不能超过10个");
}
// 场景时长验证
if (scene.getDuration() < 3 || scene.getDuration() > 5) {
throw new IllegalArgumentException("场景" + i + "时长需在3-5秒之间当前" + scene.getDuration() + "");
}
// 候选素材验证
for (int j = 0; j < scene.getCandidates().size(); j++) {
MixTaskSaveReqVO.MaterialItem candidate = scene.getCandidates().get(j);
if (candidate.getFileId() == null) {
throw new IllegalArgumentException("场景" + i + "候选" + j + "的文件ID不能为空");
}
if (candidate.getFileUrl() == null || candidate.getFileUrl().trim().isEmpty()) {
throw new IllegalArgumentException("场景" + i + "候选" + j + "的文件URL不能为空");
}
}
}
// 3. 计算总时长
int totalDuration = req.getScenes().stream()
.mapToInt(MixTaskSaveReqVO.SceneConfig::getDuration)
.sum();
// 4. 总时长校验15s-30s
if (totalDuration < 15) {
throw new IllegalArgumentException("总时长不能小于15秒当前" + totalDuration + "");
}
if (totalDuration > 30) {
throw new IllegalArgumentException("总时长不能超过30秒当前" + totalDuration + "");
}
log.info("[MixTask][新格式场景校验通过] totalDuration={}s, sceneCount={}", totalDuration, req.getScenes().size());
}
/**
* 验证旧格式素材列表
*/
private void validateMaterialsFormat(MixTaskSaveReqVO req) {
// 1. 素材列表不能为空
if (req.getMaterials() == null || req.getMaterials().isEmpty()) {
throw new IllegalArgumentException("素材列表不能为空");
@@ -559,6 +697,6 @@ public class MixTaskServiceImpl implements MixTaskService {
}
}
log.info("[MixTask][时长校验通过] totalDuration={}s, materialCount={}", totalDuration, req.getMaterials().size());
log.info("[MixTask][旧格式素材校验通过] totalDuration={}s, materialCount={}", totalDuration, req.getMaterials().size());
}
}

View File

@@ -29,13 +29,25 @@ public class MixTaskUtils {
task.setTitle(reqVO.getTitle());
task.setText(null); // 纯画面模式,不需要文案
// 存储素材配置JSON
String materialsJson = JsonUtils.toJsonString(reqVO.getMaterials());
// 存储素材配置JSON(新格式优先)
String materialsJson;
if (reqVO.isUsingNewFormat() && reqVO.getScenes() != null) {
// 新格式:使用 getEffectiveMaterials() 转换 scenes 为 materials
materialsJson = JsonUtils.toJsonString(reqVO.getEffectiveMaterials());
} else {
// 旧格式:直接使用 materials
materialsJson = JsonUtils.toJsonString(reqVO.getMaterials());
}
task.setMaterialsJson(materialsJson);
// 兼容旧版本:同时存储 videoUrls取第一个视频的URL用于兼容查询)
if (reqVO.getMaterials() != null && !reqVO.getMaterials().isEmpty()) {
List<String> videoUrls = reqVO.getMaterials().stream()
// 兼容旧版本:同时存储 videoUrls用于兼容查询
// 优先使用新格式转换后的materials
List<MixTaskSaveReqVO.MaterialItem> effectiveMaterials = reqVO.isUsingNewFormat()
? reqVO.getEffectiveMaterials()
: reqVO.getMaterials();
if (effectiveMaterials != null && !effectiveMaterials.isEmpty()) {
List<String> videoUrls = effectiveMaterials.stream()
.map(MixTaskSaveReqVO.MaterialItem::getFileUrl)
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
task.setVideoUrlList(videoUrls);

View File

@@ -18,10 +18,43 @@ public class MixTaskSaveReqVO {
@NotBlank(message = "视频标题不能为空")
private String title;
@Schema(description = "素材配置列表", required = true)
@NotEmpty(message = "素材列表不能为空")
@Schema(description = "素材配置列表(兼容旧版)")
private List<MaterialItem> materials;
@Schema(description = "场景配置列表(新版本,支持多候选)")
private List<SceneConfig> scenes;
/**
* 获取有效的配置数据(优先使用新格式)
*/
public List<MaterialItem> getEffectiveMaterials() {
// 优先使用新格式 scenes
if (scenes != null && !scenes.isEmpty()) {
// 转换场景格式为素材格式(取第一个候选)
return scenes.stream()
.map(scene -> {
if (scene.getCandidates() != null && !scene.getCandidates().isEmpty()) {
MaterialItem first = scene.getCandidates().get(0);
first.setDuration(scene.getDuration());
return first;
}
return null;
})
.filter(java.util.Objects::nonNull)
.toList();
}
// 兼容旧格式 materials
return materials;
}
/**
* 检查是否使用新格式
*/
public boolean isUsingNewFormat() {
return scenes != null && !scenes.isEmpty();
}
@Schema(description = "生成数量", required = true, example = "1")
@NotNull(message = "生成数量不能为空")
private Integer produceCount = 1; // 默认生成1个
@@ -50,4 +83,20 @@ public class MixTaskSaveReqVO {
@Schema(description = "素材实际时长(秒)", example = "60")
private Integer fileDuration;
}
@Schema(description = "场景配置")
@Data
public static class SceneConfig {
@Schema(description = "场景序号", example = "0")
private Integer index;
@Schema(description = "场景时长(秒)", required = true, example = "3")
@NotNull(message = "场景时长不能为空")
private Integer duration;
@Schema(description = "候选素材列表", required = true)
@NotEmpty(message = "场景候选列表不能为空")
private List<MaterialItem> candidates;
}
}