混剪优化
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package cn.iocoder.yudao.module.tik.media;
|
||||
|
||||
import cn.iocoder.yudao.module.infra.api.file.FileApi;
|
||||
import cn.iocoder.yudao.module.tik.file.service.TikOssInitService;
|
||||
import cn.iocoder.yudao.module.tik.mix.config.IceProperties;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
@@ -36,6 +37,7 @@ public class BatchProduceAlignment {
|
||||
|
||||
private final IceProperties properties;
|
||||
private final FileApi fileApi;
|
||||
private final TikOssInitService ossInitService;
|
||||
private Client iceClient;
|
||||
|
||||
public void initClient() throws Exception {
|
||||
@@ -60,7 +62,7 @@ public class BatchProduceAlignment {
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> batchProduceAlignment(String title, String[] videoArray, int produceCount) throws Exception {
|
||||
public List<String> batchProduceAlignment(String title, String[] videoArray, int produceCount, Long userId) throws Exception {
|
||||
// 初始化 ICE 客户端
|
||||
if (iceClient == null) {
|
||||
initClient();
|
||||
@@ -71,7 +73,7 @@ public class BatchProduceAlignment {
|
||||
|
||||
if (produceCount <= 1) {
|
||||
// 生成1个视频,包含所有片段
|
||||
String jobIdWithUrl = produceSingleVideo(title, videoArray);
|
||||
String jobIdWithUrl = produceSingleVideo(title, videoArray, userId);
|
||||
jobIdWithUrls.add(jobIdWithUrl);
|
||||
} else {
|
||||
// 生成多个视频:将视频数组分成多份,每份生成一个视频
|
||||
@@ -90,7 +92,7 @@ public class BatchProduceAlignment {
|
||||
String[] groupVideos = Arrays.copyOfRange(videoArray, start, start + groupSize);
|
||||
|
||||
// 生成单个视频
|
||||
String jobIdWithUrl = produceSingleVideo(title, groupVideos);
|
||||
String jobIdWithUrl = produceSingleVideo(title, groupVideos, userId);
|
||||
jobIdWithUrls.add(jobIdWithUrl);
|
||||
|
||||
start += groupSize;
|
||||
@@ -101,7 +103,7 @@ public class BatchProduceAlignment {
|
||||
return jobIdWithUrls;
|
||||
}
|
||||
|
||||
public String produceSingleVideo(String title, String[] videoArray) throws Exception {
|
||||
public String produceSingleVideo(String title, String[] videoArray, Long userId) throws Exception {
|
||||
// 初始化 ICE 客户端
|
||||
if (iceClient == null) {
|
||||
initClient();
|
||||
@@ -113,7 +115,6 @@ public class BatchProduceAlignment {
|
||||
|
||||
// 按顺序拼接视频片段(不随机打乱)
|
||||
for (int i = 0; i < videoArray.length; i++) {
|
||||
String clipId = "clip" + i;
|
||||
String videoUrl = videoArray[i];
|
||||
|
||||
// 验证视频URL必须是阿里云OSS地址
|
||||
@@ -130,32 +131,33 @@ public class BatchProduceAlignment {
|
||||
// 为每个视频片段添加静音的音频轨道
|
||||
JSONObject audioClip = new JSONObject();
|
||||
audioClip.put("MediaURL", videoUrl);
|
||||
// 添加静音效果
|
||||
JSONObject volumeEffect = new JSONObject();
|
||||
volumeEffect.put("Type", "Volume");
|
||||
volumeEffect.put("Gain", 0); // 0 表示静音
|
||||
JSONArray effects = new JSONArray();
|
||||
effects.add(volumeEffect);
|
||||
audioClip.put("Effects", effects);
|
||||
audioClip.put("Effects", new JSONArray() {{
|
||||
add(new JSONObject() {{
|
||||
put("Type", "Volume");
|
||||
put("Gain", 0); // 0 表示静音
|
||||
}});
|
||||
}});
|
||||
audioClipArray.add(audioClip);
|
||||
}
|
||||
|
||||
// 构建时间线,包含视频轨道和音频轨道
|
||||
String timeline = "{\"VideoTracks\":[{\"VideoTrackClips\":"+videoClipArray.toJSONString()+"}],\"AudioTracks\":[{\"AudioTrackClips\":"+audioClipArray.toJSONString()+"}]}";
|
||||
|
||||
//
|
||||
// 生成输出文件路径:{用户目录}/mix/{yyyyMMdd}/{uuid}.mp4
|
||||
String targetFileName = UUID.randomUUID().toString().replace("-", "");
|
||||
String mixDirectory = ossInitService.getOssDirectoryByCategory(userId, "mix");
|
||||
String dateDir = java.time.LocalDate.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
String outputMediaPath = mixDirectory + "/" + dateDir + "/" + targetFileName + ".mp4";
|
||||
|
||||
// 使用默认的阿里云OSS endpoint格式
|
||||
String bucketEndpoint = "https://" + properties.getBucket() + ".oss-" + properties.getRegionId() + ".aliyuncs.com";
|
||||
String outputMediaPath = "/ice_output/" + targetFileName + ".mp4";
|
||||
String outputMediaUrl = bucketEndpoint + outputMediaPath;
|
||||
|
||||
// 生成签名URL(有效期24小时)使用公共API
|
||||
String signedUrl = fileApi.presignGetUrl(outputMediaUrl, 24 * 3600);
|
||||
String outputMediaUrl = bucketEndpoint + "/" + outputMediaPath;
|
||||
|
||||
// ICE需要将处理结果写入到该URL,签名URL会导致写入失败
|
||||
int width = 720;
|
||||
int height = 1280;
|
||||
int bitrate = 2000; // 输出码率 2000 Kbit/s(符合 ICE API 文档推荐)
|
||||
String outputMediaConfig = "{\"MediaURL\":\"" + signedUrl + "\",\"Width\":" + width + ",\"Height\":" + height + ",\"Bitrate\":" + bitrate + "}";
|
||||
String outputMediaConfig = "{\"MediaURL\":\"" + outputMediaUrl + "\",\"Width\":" + width + ",\"Height\":" + height + ",\"Bitrate\":" + bitrate + "}";
|
||||
|
||||
SubmitMediaProducingJobRequest request = new SubmitMediaProducingJobRequest();
|
||||
request.setTimeline(timeline);
|
||||
@@ -166,6 +168,7 @@ public class BatchProduceAlignment {
|
||||
|
||||
String jobId = response.getBody().getJobId();
|
||||
log.info("[ICE][任务提交成功][jobId={}, outputMediaUrl={}]", jobId, outputMediaUrl);
|
||||
// 返回原始URL,签名URL在用户下载时生成
|
||||
return jobId + " : " + outputMediaUrl;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.tik.media.controller;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import cn.iocoder.yudao.module.tik.media.BatchProduceAlignment;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@@ -31,8 +32,14 @@ public class BatchProduceAlignmentController {
|
||||
// 生成的成片数
|
||||
int produceCount) {
|
||||
try {
|
||||
// 获取当前登录用户ID
|
||||
Long userId = SecurityFrameworkUtils.getLoginUserId();
|
||||
if (userId == null) {
|
||||
return CommonResult.error(new ErrorCode(401, "用户未登录"));
|
||||
}
|
||||
|
||||
// 纯画面模式:仅传入视频数组,无需text和bgMusicArray
|
||||
List<String> jobIds = batchProduceAlignment.batchProduceAlignment(title, videoArray, produceCount);
|
||||
List<String> jobIds = batchProduceAlignment.batchProduceAlignment(title, videoArray, produceCount, userId);
|
||||
return CommonResult.success(jobIds);
|
||||
} catch (Exception e) {
|
||||
return CommonResult.error(new ErrorCode(500, "Produce failed. Exception: " + e));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package cn.iocoder.yudao.module.tik.mix.controller;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
@@ -14,6 +15,9 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 混剪任务 Controller
|
||||
*
|
||||
@@ -98,4 +102,38 @@ public class MixTaskController {
|
||||
mixTaskService.cancelTask(id);
|
||||
return CommonResult.success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/signed-url/{id}")
|
||||
@Operation(summary = "获取任务输出文件的签名URL")
|
||||
public CommonResult<List<String>> getSignedUrls(@PathVariable Long id) {
|
||||
try {
|
||||
// 获取任务信息
|
||||
MixTaskRespVO mixTaskVO = mixTaskService.getMixTask(id);
|
||||
if (mixTaskVO == null || mixTaskVO.getOutputUrls() == null) {
|
||||
return CommonResult.success(List.of());
|
||||
}
|
||||
|
||||
// 重新生成所有输出URL的签名URL(避免使用过期的签名URL)
|
||||
List<String> signedUrls = new ArrayList<>();
|
||||
for (String outputUrl : mixTaskVO.getOutputUrls()) {
|
||||
try {
|
||||
// 每次都重新生成签名URL(有效期24小时)
|
||||
String signedUrl = mixTaskService.generateSignedUrl(outputUrl, 24 * 3600);
|
||||
if (StrUtil.isNotEmpty(signedUrl)) {
|
||||
signedUrls.add(signedUrl);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[MixTask][生成签名URL失败] url={}, error={}", outputUrl, e.getMessage(), e);
|
||||
// 单个URL失败不影响其他URL
|
||||
}
|
||||
}
|
||||
|
||||
log.info("[MixTask][获取签名URL成功] taskId={}, urlCount={}", id, signedUrls.size());
|
||||
return CommonResult.success(signedUrls);
|
||||
} catch (Exception e) {
|
||||
log.error("[MixTask][获取签名URL失败] taskId={}, error={}", id, e.getMessage(), e);
|
||||
// 不返回详细错误信息给前端,避免XML/HTML解析问题
|
||||
return CommonResult.error(500, "获取签名URL失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,4 +71,13 @@ public interface MixTaskService {
|
||||
* 保存任务结果
|
||||
*/
|
||||
void saveTaskResult(Long taskId, List<String> outputUrls);
|
||||
|
||||
/**
|
||||
* 生成签名URL
|
||||
*
|
||||
* @param ossUrl OSS文件完整URL
|
||||
* @param expirationSeconds 有效期(秒)
|
||||
* @return 签名URL
|
||||
*/
|
||||
String generateSignedUrl(String ossUrl, Integer expirationSeconds);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.tik.mix.service;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.module.infra.service.file.FileService;
|
||||
import cn.iocoder.yudao.module.tik.mix.client.IceClient;
|
||||
import cn.iocoder.yudao.module.tik.mix.constants.MixTaskConstants;
|
||||
import cn.iocoder.yudao.module.tik.mix.dal.dataobject.MixTaskDO;
|
||||
@@ -9,6 +11,7 @@ import cn.iocoder.yudao.module.tik.mix.dal.mysql.MixTaskMapper;
|
||||
import cn.iocoder.yudao.module.tik.mix.util.MixTaskUtils;
|
||||
import cn.iocoder.yudao.module.tik.mix.vo.*;
|
||||
import cn.iocoder.yudao.module.tik.media.BatchProduceAlignment;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -16,6 +19,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -36,6 +40,9 @@ public class MixTaskServiceImpl implements MixTaskService {
|
||||
private final BatchProduceAlignment batchProduceAlignment;
|
||||
private final IceClient iceClient;
|
||||
|
||||
@Resource
|
||||
private FileService fileService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long createMixTask(MixTaskSaveReqVO createReqVO, Long userId) {
|
||||
@@ -51,9 +58,9 @@ public class MixTaskServiceImpl implements MixTaskService {
|
||||
// 3. 异步提交到阿里云 ICE
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
submitToICE(task.getId(), createReqVO);
|
||||
submitToICE(task.getId(), createReqVO, userId);
|
||||
} catch (Exception e) {
|
||||
log.error("[MixTask][提交ICE失败] taskId={}, error={}", task.getId(), e.getMessage(), e);
|
||||
log.error("[MixTask][提交ICE失败] taskId={}", task.getId(), e);
|
||||
updateTaskError(task.getId(), "提交任务失败: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
@@ -88,7 +95,24 @@ public class MixTaskServiceImpl implements MixTaskService {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 执行删除
|
||||
// 2. 删除OSS文件
|
||||
try {
|
||||
List<String> outputUrls = existTask.getOutputUrlList();
|
||||
if (outputUrls != null && !outputUrls.isEmpty()) {
|
||||
log.info("[MixTask][删除OSS文件] taskId={}, fileCount={}", id, outputUrls.size());
|
||||
for (String url : outputUrls) {
|
||||
try {
|
||||
fileService.deleteFileByUrl(url);
|
||||
} catch (Exception e) {
|
||||
log.error("[MixTask][删除单个OSS文件失败] taskId={}, url={}", id, url, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[MixTask][删除OSS文件失败] taskId={}", id, e);
|
||||
}
|
||||
|
||||
// 3. 执行删除
|
||||
mixTaskMapper.deleteById(id);
|
||||
}
|
||||
|
||||
@@ -149,9 +173,9 @@ public class MixTaskServiceImpl implements MixTaskService {
|
||||
saveReqVO.setTitle(existTask.getTitle());
|
||||
saveReqVO.setVideoUrls(existTask.getVideoUrlList());
|
||||
saveReqVO.setProduceCount(existTask.getProduceCount());
|
||||
submitToICE(id, saveReqVO);
|
||||
submitToICE(id, saveReqVO, existTask.getUserId());
|
||||
} catch (Exception e) {
|
||||
log.error("[MixTask][重新提交失败] taskId={}, error={}", id, e.getMessage(), e);
|
||||
log.error("[MixTask][重新提交失败] taskId={}", id, e);
|
||||
updateTaskError(id, "重新提交失败: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
@@ -218,7 +242,7 @@ public class MixTaskServiceImpl implements MixTaskService {
|
||||
syncTaskStatus(task.getId(), jobId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[MixTask][单个任务检查失败] taskId={}, error={}", task.getId(), e.getMessage(), e);
|
||||
log.error("[MixTask][单个任务检查失败] taskId={}", task.getId(), e);
|
||||
failureCount++;
|
||||
// 单个任务失败不影响其他任务
|
||||
}
|
||||
@@ -247,8 +271,10 @@ public class MixTaskServiceImpl implements MixTaskService {
|
||||
|
||||
// 根据ICE状态更新任务
|
||||
if ("Success".equalsIgnoreCase(status) || "success".equalsIgnoreCase(status)) {
|
||||
// 任务成功完成,更新为100%
|
||||
updateTaskStatus(taskId, MixTaskConstants.STATUS_SUCCESS, MixTaskConstants.PROGRESS_COMPLETED);
|
||||
// 任务成功完成,需要获取实际的输出URL
|
||||
log.info("[MixTask][ICE任务执行成功] taskId={}, jobId={}", taskId, jobId);
|
||||
// 重新生成签名URL并更新任务
|
||||
updateTaskSuccess(taskId, jobId);
|
||||
} else if ("Failed".equalsIgnoreCase(status) || "failed".equalsIgnoreCase(status) || "Failure".equalsIgnoreCase(status)) {
|
||||
// 任务失败 - 获取详细错误信息
|
||||
String errorMsg = "ICE任务执行失败";
|
||||
@@ -262,18 +288,38 @@ public class MixTaskServiceImpl implements MixTaskService {
|
||||
taskId, jobId, status, errorMsg);
|
||||
updateTaskError(taskId, errorMsg);
|
||||
} else if ("Running".equalsIgnoreCase(status) || "running".equalsIgnoreCase(status) || "Processing".equalsIgnoreCase(status)) {
|
||||
// 任务仍在运行,更新进度为70%
|
||||
updateTaskStatus(taskId, MixTaskConstants.STATUS_RUNNING, 70);
|
||||
// 任务仍在运行,逐步更新进度
|
||||
// 1. 获取当前进度
|
||||
MixTaskDO currentTask = mixTaskMapper.selectById(taskId);
|
||||
if (currentTask != null) {
|
||||
int currentProgress = currentTask.getProgress() != null ? currentTask.getProgress() : 0;
|
||||
// 2. 根据当前进度逐步提升,但不超过95%
|
||||
int newProgress;
|
||||
if (currentProgress < 50) {
|
||||
newProgress = 50; // 首次看到Running时更新到50%
|
||||
} else if (currentProgress < 80) {
|
||||
newProgress = 80; // 第二次更新到80%
|
||||
} else if (currentProgress < 95) {
|
||||
newProgress = 95; // 最后更新到95%
|
||||
} else {
|
||||
newProgress = currentProgress; // 已经很高了,保持不变
|
||||
}
|
||||
|
||||
if (newProgress != currentProgress) {
|
||||
updateTaskStatus(taskId, MixTaskConstants.STATUS_RUNNING, newProgress);
|
||||
log.info("[MixTask][进度更新] taskId={}, from={}%, to={}%", taskId, currentProgress, newProgress);
|
||||
}
|
||||
}
|
||||
} else if ("Pending".equalsIgnoreCase(status) || "pending".equalsIgnoreCase(status)) {
|
||||
// 任务等待中,更新进度为60%
|
||||
updateTaskStatus(taskId, MixTaskConstants.STATUS_RUNNING, 60);
|
||||
// 任务等待中,更新进度为30%
|
||||
updateTaskStatus(taskId, MixTaskConstants.STATUS_RUNNING, 30);
|
||||
} else {
|
||||
// 未知状态,记录日志但不更新
|
||||
log.warn("[MixTask][未知ICE状态] taskId={}, jobId={}, iceStatus={}", taskId, jobId, status);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[MixTask][状态同步异常] taskId={}, jobId={}, error={}", taskId, jobId, e.getMessage(), e);
|
||||
log.error("[MixTask][状态同步异常] taskId={}, jobId={}", taskId, jobId, e);
|
||||
updateTaskError(taskId, "查询任务状态失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -291,75 +337,49 @@ public class MixTaskServiceImpl implements MixTaskService {
|
||||
mixTaskMapper.updateById(updateTask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateSignedUrl(String ossUrl, Integer expirationSeconds) {
|
||||
try {
|
||||
if (StrUtil.isEmpty(ossUrl)) {
|
||||
return null;
|
||||
}
|
||||
// 使用FileService生成签名URL
|
||||
return fileService.presignGetUrl(ossUrl, expirationSeconds);
|
||||
} catch (Exception e) {
|
||||
log.error("[MixTask][生成签名URL失败] url={}", ossUrl, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交任务到阿里云 ICE
|
||||
*/
|
||||
private void submitToICE(Long taskId, MixTaskSaveReqVO createReqVO) {
|
||||
private void submitToICE(Long taskId, MixTaskSaveReqVO createReqVO, Long userId) {
|
||||
try {
|
||||
// 1. 更新任务状态为运行中,进度10%
|
||||
updateTaskStatus(taskId, MixTaskConstants.STATUS_RUNNING, MixTaskConstants.PROGRESS_SUBMITTED);
|
||||
|
||||
// 2. 转换为ICE需要的参数格式
|
||||
// 1. 转换为ICE需要的参数格式
|
||||
String[] videoArray = createReqVO.getVideoUrls().toArray(new String[0]);
|
||||
|
||||
// 3. 调用ICE批量生成接口(纯画面模式:无需text和bgMusic)
|
||||
// 2. 调用ICE批量生成接口(纯画面模式:无需text和bgMusic)
|
||||
List<String> jobIdWithUrls = batchProduceAlignment.batchProduceAlignment(
|
||||
createReqVO.getTitle(),
|
||||
videoArray,
|
||||
createReqVO.getProduceCount()
|
||||
createReqVO.getProduceCount(),
|
||||
userId
|
||||
);
|
||||
|
||||
// 4. 解析jobId和输出URL
|
||||
// 3. 解析jobId和输出URL
|
||||
MixTaskUtils.JobIdUrlPair jobIdUrlPair = MixTaskUtils.parseJobIdsAndUrls(jobIdWithUrls);
|
||||
|
||||
// 5. 更新任务信息
|
||||
// 4. 更新任务信息(包含状态和进度)
|
||||
updateTaskWithResults(taskId, jobIdUrlPair.getJobIds(), jobIdUrlPair.getOutputUrls(),
|
||||
MixTaskConstants.STATUS_RUNNING, MixTaskConstants.PROGRESS_UPLOADED);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[MixTask][ICE提交失败] taskId={}, error={}", taskId, e.getMessage(), e);
|
||||
updateTaskError(taskId, "ICE处理失败: " + e.getMessage());
|
||||
// 注意:异步线程中不抛出异常,避免未处理异常
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交任务到阿里云 ICE(新版本,使用 IceClient)
|
||||
*/
|
||||
private void submitToIceV2(Long taskId, MixTaskSaveReqVO createReqVO) {
|
||||
try {
|
||||
// 1. 更新任务状态
|
||||
updateTaskStatus(taskId, MixTaskConstants.STATUS_RUNNING, MixTaskConstants.PROGRESS_SUBMITTED);
|
||||
|
||||
// 2. 构建 timeline 和 outputMediaConfig
|
||||
String timeline = buildTimeline(createReqVO);
|
||||
String outputMediaConfig = buildOutputMediaConfig();
|
||||
|
||||
// 3. 提交任务
|
||||
// TODO: 这里需要将 BatchProduceAlignment 的逻辑移到 IceClient 中
|
||||
// String jobId = iceClient.submitMediaProducingJob(timeline, outputMediaConfig);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[MixTask][V2提交失败] taskId={}, error={}", taskId, e.getMessage(), e);
|
||||
log.error("[MixTask][ICE提交失败] taskId={}", taskId, e);
|
||||
updateTaskError(taskId, "ICE处理失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 ICE 时间线
|
||||
*/
|
||||
private String buildTimeline(MixTaskSaveReqVO reqVO) {
|
||||
// TODO: 实现 timeline 构建逻辑
|
||||
return "{}";
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建输出媒体配置
|
||||
*/
|
||||
private String buildOutputMediaConfig() {
|
||||
// TODO: 实现输出配置构建逻辑
|
||||
return "{}";
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新任务状态
|
||||
@@ -399,6 +419,62 @@ public class MixTaskServiceImpl implements MixTaskService {
|
||||
mixTaskMapper.updateById(updateTask);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新任务成功状态(重新生成签名URL)
|
||||
*/
|
||||
private void updateTaskSuccess(Long taskId, String jobId) {
|
||||
try {
|
||||
// 查询任务信息
|
||||
MixTaskDO task = mixTaskMapper.selectById(taskId);
|
||||
if (task == null) {
|
||||
log.warn("[MixTask][任务不存在] taskId={}", taskId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取原始输出URL(不是签名URL)
|
||||
List<String> outputUrls = task.getOutputUrlList();
|
||||
if (outputUrls == null || outputUrls.isEmpty()) {
|
||||
log.warn("[MixTask][任务成功但没有输出URL] taskId={}", taskId);
|
||||
updateTaskStatus(taskId, MixTaskConstants.STATUS_SUCCESS, MixTaskConstants.PROGRESS_COMPLETED);
|
||||
return;
|
||||
}
|
||||
|
||||
// 任务成功时,直接保存原始URL(不是签名URL)
|
||||
// 签名URL在用户下载时通过 /signed-url/{id} 接口动态生成
|
||||
MixTaskDO updateTask = new MixTaskDO();
|
||||
updateTask.setId(taskId);
|
||||
updateTask.setStatus(MixTaskConstants.STATUS_SUCCESS);
|
||||
updateTask.setProgress(MixTaskConstants.PROGRESS_COMPLETED);
|
||||
updateTask.setOutputUrlList(outputUrls);
|
||||
updateTask.setFinishTime(java.time.LocalDateTime.now());
|
||||
mixTaskMapper.updateById(updateTask);
|
||||
|
||||
log.info("[MixTask][任务成功更新] taskId={}, urlCount={}", taskId, outputUrls.size());
|
||||
} catch (Exception e) {
|
||||
log.error("[MixTask][更新任务成功状态失败] taskId={}", taskId, e);
|
||||
// 即使更新失败,也标记为成功
|
||||
updateTaskStatus(taskId, MixTaskConstants.STATUS_SUCCESS, MixTaskConstants.PROGRESS_COMPLETED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从OSS URL中提取相对路径
|
||||
*/
|
||||
private String extractPathFromUrl(String ossUrl) {
|
||||
if (StrUtil.isEmpty(ossUrl)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 查找 ".aliyuncs.com/" 的位置
|
||||
int index = ossUrl.indexOf(".aliyuncs.com/");
|
||||
if (index == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 提取 "/" 后的路径
|
||||
return ossUrl.substring(index + ".aliyuncs.com/".length());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将DO分页结果转换为VO分页结果,消除冗余代码
|
||||
* 优化点:
|
||||
|
||||
Reference in New Issue
Block a user