|
|
@@ -3,15 +3,12 @@ package com.tzld.supply.job;
|
|
|
|
|
|
import cn.hutool.core.collection.CollectionUtil;
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
|
|
import com.tzld.supply.api.AigcFFmpegApiService;
|
|
|
import com.tzld.supply.api.fish.FishApiService;
|
|
|
import com.tzld.supply.common.enums.ProduceVideoStatusEnum;
|
|
|
-import com.tzld.supply.common.enums.SpiderContentMediaStatusEnum;
|
|
|
import com.tzld.supply.common.enums.SpiderContentStatusEnum;
|
|
|
-import com.tzld.supply.dao.mapper.supply.spider.AiModelTtsMapper;
|
|
|
-import com.tzld.supply.dao.mapper.supply.spider.ProduceVideoAudioMapper;
|
|
|
-import com.tzld.supply.dao.mapper.supply.spider.ProduceVideoMapper;
|
|
|
-import com.tzld.supply.dao.mapper.supply.spider.ProduceVideoMaterialMapper;
|
|
|
+import com.tzld.supply.dao.mapper.supply.spider.*;
|
|
|
import com.tzld.supply.dao.mapper.supply.spider.ext.SpiderMapperExt;
|
|
|
import com.tzld.supply.model.param.FFmpeg.CommandParam;
|
|
|
import com.tzld.supply.model.param.FFmpeg.VideoInfoParam;
|
|
|
@@ -32,11 +29,16 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.stereotype.Component;
|
|
|
|
|
|
import java.util.*;
|
|
|
+import java.util.concurrent.*;
|
|
|
|
|
|
@Slf4j
|
|
|
@Component
|
|
|
public class VideoGenerateJob {
|
|
|
|
|
|
+ @Autowired
|
|
|
+ private SpiderContentMapper spiderContentMapper;
|
|
|
+ @Autowired
|
|
|
+ private SpiderContentMediaMapper spiderContentMediaMapper;
|
|
|
@Autowired
|
|
|
private SpiderMapperExt spiderMapperExt;
|
|
|
@Autowired
|
|
|
@@ -54,6 +56,7 @@ public class VideoGenerateJob {
|
|
|
|
|
|
/**
|
|
|
* 生成音频
|
|
|
+ *
|
|
|
* @param param
|
|
|
* @return
|
|
|
*/
|
|
|
@@ -64,8 +67,13 @@ public class VideoGenerateJob {
|
|
|
startTime = DateUtils.getStartOfDay(param, "yyyyMMdd");
|
|
|
}
|
|
|
Long endTime = startTime + 86400 * 1000;
|
|
|
- List<SpiderContent> contentList = spiderMapperExt.getVideoGenerateContent(SpiderContentStatusEnum.PASSED.getCode(),
|
|
|
- startTime, endTime);
|
|
|
+ List<SpiderContent> contentList = new ArrayList<>();
|
|
|
+ if (StringUtils.isNotBlank(param)) {
|
|
|
+ contentList.add(spiderContentMapper.selectByPrimaryKey(Long.parseLong(param)));
|
|
|
+ } else {
|
|
|
+ contentList = spiderMapperExt.getVideoGenerateContent(SpiderContentStatusEnum.PASSED.getCode(),
|
|
|
+ startTime, endTime);
|
|
|
+ }
|
|
|
if (CollectionUtil.isEmpty(contentList)) {
|
|
|
return ReturnT.SUCCESS;
|
|
|
}
|
|
|
@@ -130,21 +138,22 @@ public class VideoGenerateJob {
|
|
|
* 视频素材生成
|
|
|
* 1. 图片转短视频
|
|
|
* 2. 视频剪切
|
|
|
+ *
|
|
|
* @param param
|
|
|
* @return
|
|
|
*/
|
|
|
@XxlJob("videoMaterialGenerateJob")
|
|
|
public ReturnT<String> videoMaterialGenerateJob(String param) {
|
|
|
- List<ProduceVideo> produceList = getProcessingVideoList();
|
|
|
+ List<ProduceVideo> produceList = new ArrayList<>();
|
|
|
+ if (StringUtils.isNotBlank(param)) {
|
|
|
+ produceList.add(produceVideoMapper.selectByPrimaryKey(param));
|
|
|
+ } else {
|
|
|
+ produceList = getProcessingVideoList();
|
|
|
+ }
|
|
|
if (CollectionUtil.isEmpty(produceList)) {
|
|
|
return ReturnT.SUCCESS;
|
|
|
}
|
|
|
for (ProduceVideo produceVideo : produceList) {
|
|
|
- // 检查是否存在素材
|
|
|
- Long materialCount = countVideoMaterial(produceVideo);
|
|
|
- if (materialCount > 0) {
|
|
|
- continue;
|
|
|
- }
|
|
|
// image to short video
|
|
|
imageToVideoMaterial(produceVideo);
|
|
|
// video cut
|
|
|
@@ -159,27 +168,48 @@ public class VideoGenerateJob {
|
|
|
return produceVideoMaterialMapper.countByExample(example);
|
|
|
}
|
|
|
|
|
|
+ // 线程池
|
|
|
+ private final ExecutorService pool = new ThreadPoolExecutor(
|
|
|
+ 5,
|
|
|
+ 5,
|
|
|
+ 0L, TimeUnit.SECONDS,
|
|
|
+ new LinkedBlockingQueue<>(10000),
|
|
|
+ new ThreadFactoryBuilder().setNameFormat("imageToVideo-%d").build(),
|
|
|
+ new ThreadPoolExecutor.AbortPolicy());
|
|
|
+
|
|
|
private void imageToVideoMaterial(ProduceVideo produceVideo) {
|
|
|
- List<SpiderContentMedia> imageList = spiderMapperExt.getMediaSearchSpiderContentMedia(SpiderContentStatusEnum.PASSED.getCode(),
|
|
|
- null, null, SpiderContentMediaStatusEnum.PASSED.getCode(), "image");
|
|
|
+ List<SpiderContentMedia> imageList = spiderMapperExt.getVideoMaterialGenerateMedia(produceVideo.getId(), "image");
|
|
|
if (CollectionUtil.isNotEmpty(imageList)) {
|
|
|
+ CountDownLatch latch = new CountDownLatch(imageList.size());
|
|
|
for (SpiderContentMedia image : imageList) {
|
|
|
- // 现仅一种效果,后更改为多种效果随机
|
|
|
- String outputVideoName = String.format("%s_%d.mp4", produceVideo.getContentId(), System.currentTimeMillis());
|
|
|
- String command = randomImageToVideoAnimation(image.getOssKey(), outputVideoName);
|
|
|
- CommandParam commandParam = CommandParam.builder()
|
|
|
- .ffmpegCommand(command)
|
|
|
- .outputFile(outputVideoName)
|
|
|
- .outputType("video")
|
|
|
- .build();
|
|
|
- String commandResponseData = ffmpegApiService.command(commandParam);
|
|
|
- String fileName = String.format("supply/produce/material/video/%s", outputVideoName);
|
|
|
- String imageToVideoOssUrl = AliOssFileTool.downloadAndSaveInOSS(fileName, commandResponseData, "video/mp4");
|
|
|
+ // 异步处理
|
|
|
+ pool.submit(() -> {
|
|
|
+ try {
|
|
|
+ // 现仅一种效果,后更改为多种效果随机
|
|
|
+ String outputVideoName = String.format("%s_%d.mp4", produceVideo.getContentId(), System.currentTimeMillis());
|
|
|
+ String command = randomImageToVideoAnimation(image.getOssKey(), outputVideoName);
|
|
|
+ CommandParam commandParam = CommandParam.builder()
|
|
|
+ .ffmpegCommand(command)
|
|
|
+ .outputFile(outputVideoName)
|
|
|
+ .outputType("video")
|
|
|
+ .build();
|
|
|
+ String commandResponseData = ffmpegApiService.command(commandParam);
|
|
|
+ String fileName = String.format("supply/produce/material/video/%s", outputVideoName);
|
|
|
+ String imageToVideoOssUrl = AliOssFileTool.downloadAndSaveInOSS(fileName, commandResponseData, "video/mp4");
|
|
|
|
|
|
- // 获取视频信息
|
|
|
- MediaInfo mediaInfo = getMediaInfo(imageToVideoOssUrl);
|
|
|
- // 保存视频素材
|
|
|
- saveVideoMaterial(produceVideo, image.getId(), imageToVideoOssUrl, mediaInfo, 1);
|
|
|
+ // 获取视频信息
|
|
|
+ MediaInfo mediaInfo = getMediaInfo(imageToVideoOssUrl);
|
|
|
+ // 保存视频素材
|
|
|
+ saveVideoMaterial(produceVideo, image.getId(), imageToVideoOssUrl, mediaInfo, 1);
|
|
|
+ } finally {
|
|
|
+ latch.countDown();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ latch.await();
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ log.error("视频素材生成 InterruptedException", e);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -199,33 +229,14 @@ public class VideoGenerateJob {
|
|
|
break;
|
|
|
// 平移
|
|
|
case 1:
|
|
|
- // 生成随机方向
|
|
|
- String[][] directions = {
|
|
|
- {"0", "0", "(W-w)", "(H-h)"}, // 左上 -> 右下
|
|
|
- {"(W-w)", "(H-h)", "0", "0"}, // 右下 -> 左上
|
|
|
- {"0", "(H-h)", "(W-w)", "0"}, // 左下 -> 右上
|
|
|
- {"(W-w)", "0", "0", "(H-h)"} // 右上 -> 左下
|
|
|
- };
|
|
|
- Random rand = new Random();
|
|
|
- int index = rand.nextInt(directions.length);
|
|
|
- String[] dir = directions[index];
|
|
|
- String xExpr = String.format("x='%s + (%s-%s)*t/%d'", dir[0], dir[2], dir[0], 3);
|
|
|
- String yExpr = String.format("y='%s + (%s-%s)*t/%d'", dir[1], dir[3], dir[1], 3);
|
|
|
- // 构造 FFmpeg 命令
|
|
|
- command = String.format("ffmpeg -y -loop 1 -i %s -filter_complex" +
|
|
|
- "\"[0:v]scale=720:1280:force_original_aspect_ratio=increase,boxblur=40:40,crop=720:1280[bg];" +
|
|
|
- "[0:v]scale=720:1280:force_original_aspect_ratio=decrease,setsar=1[fg];" +
|
|
|
- "[bg][fg]overlay=" + xExpr + ":" + yExpr + ",format=yuv420p\"" +
|
|
|
- "-t3 %s", imageUrl, outputVideoName);
|
|
|
- break;
|
|
|
- // 背景模糊
|
|
|
+ // 背景模糊
|
|
|
default:
|
|
|
- command = String.format("ffmpeg -loop 1 -i %s -t 3 -r 25 -filter_complex " +
|
|
|
- "\"[0:v]scale=720:1280:force_original_aspect_ratio=increase,crop=720:1280,gblur=sigma=5[bg];" +
|
|
|
+ command = String.format("ffmpeg -y -loop 1 -i %s -t 3 -r 25 -filter_complex " +
|
|
|
+ "\"[0:v]scale=720:1280:force_original_aspect_ratio=increase,crop=720:1280,boxblur=10:1[bg];" +
|
|
|
"[0:v]scale=720:1280:force_original_aspect_ratio=decrease,pad=720:1280:(ow-iw)/2:(oh-ih)/2:color=black@0[fg];" +
|
|
|
- "[fg]zoompan=z='min(1.00+0.000266*on,1.02)':d=1:x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':s=720x1280[fg_anim];" +
|
|
|
- "[bg][fg_anim]overlay=0:0:format=auto\" " +
|
|
|
- "-c:v libx264 -pix_fmt yuv420p -movflags +faststart \"%s\"",
|
|
|
+ "[fg]zoompan=z='min(1.00+0.0003*on,1.03)':d=75:x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':s=720x1280[fg_anim];" +
|
|
|
+ "[bg][fg_anim]overlay=0:0:format=auto,format=yuv420p\" " +
|
|
|
+ "-c:v libx264 -pix_fmt yuv420p -movflags +faststart %s",
|
|
|
imageUrl, outputVideoName);
|
|
|
}
|
|
|
return command;
|
|
|
@@ -251,36 +262,48 @@ public class VideoGenerateJob {
|
|
|
}
|
|
|
|
|
|
private void videoCutMaterial(ProduceVideo produceVideo) {
|
|
|
- List<SpiderContentMedia> videoList = spiderMapperExt.getMediaSearchSpiderContentMedia(SpiderContentStatusEnum.PASSED.getCode(),
|
|
|
- null, null, SpiderContentMediaStatusEnum.PASSED.getCode(), "video");
|
|
|
+ List<SpiderContentMedia> videoList = spiderMapperExt.getVideoMaterialGenerateMedia(produceVideo.getId(), "video");
|
|
|
if (CollectionUtil.isNotEmpty(videoList)) {
|
|
|
+ CountDownLatch latch = new CountDownLatch(videoList.size());
|
|
|
for (SpiderContentMedia video : videoList) {
|
|
|
- // 随机截取时间
|
|
|
- List<Integer> cutTime = randomCutTime(video.getDuration());
|
|
|
- // 按时长截取视频片段 起始时间为上次结束时间 结束时间为上次结束时间+时长
|
|
|
- // 第一个视频片段起始时间为0
|
|
|
- String lastEndTime = "00:00:00";
|
|
|
- for (Integer time : cutTime) {
|
|
|
- // 本次结束时间为上次结束时间+时长
|
|
|
- int lastSeconds = parseTimeToSeconds(lastEndTime);
|
|
|
- int currentSeconds = time / 1000; // 毫秒转秒数
|
|
|
- int endSeconds = lastSeconds + currentSeconds;
|
|
|
- String endTime = formatSecondsToTime(endSeconds);
|
|
|
- // 截取视频片段
|
|
|
- VideoTimeCutParam timeCutParam = VideoTimeCutParam.builder()
|
|
|
- .videoUrl(video.getUrl())
|
|
|
- .startTime(lastEndTime)
|
|
|
- .endTime(endTime)
|
|
|
- .build();
|
|
|
- lastEndTime = endTime;
|
|
|
- String videoCutUrl = ffmpegApiService.timeCutVideo(timeCutParam);
|
|
|
- String fileName = String.format("supply/produce/material/video/%s_%d.mp4", produceVideo.getContentId(), System.currentTimeMillis());
|
|
|
- String videoCutOssUrl = AliOssFileTool.downloadAndSaveInOSS(fileName, videoCutUrl, "video/mp4");
|
|
|
- // 获取视频信息
|
|
|
- MediaInfo mediaInfo = getMediaInfo(videoCutOssUrl);
|
|
|
- // 保存视频素材
|
|
|
- saveVideoMaterial(produceVideo, video.getId(), videoCutOssUrl, mediaInfo, 2);
|
|
|
- }
|
|
|
+ // 异步处理
|
|
|
+ pool.submit(() -> {
|
|
|
+ try {
|
|
|
+ // 随机截取时间
|
|
|
+ List<Integer> cutTime = randomCutTime(video.getDuration());
|
|
|
+ // 按时长截取视频片段 起始时间为上次结束时间 结束时间为上次结束时间+时长
|
|
|
+ // 第一个视频片段起始时间为0
|
|
|
+ String lastEndTime = "00:00:00";
|
|
|
+ for (Integer time : cutTime) {
|
|
|
+ // 本次结束时间为上次结束时间+时长
|
|
|
+ int lastSeconds = parseTimeToSeconds(lastEndTime);
|
|
|
+ int currentSeconds = time / 1000; // 毫秒转秒数
|
|
|
+ int endSeconds = lastSeconds + currentSeconds;
|
|
|
+ String endTime = formatSecondsToTime(endSeconds);
|
|
|
+ // 截取视频片段
|
|
|
+ VideoTimeCutParam timeCutParam = VideoTimeCutParam.builder()
|
|
|
+ .videoUrl(video.getUrl())
|
|
|
+ .startTime(lastEndTime)
|
|
|
+ .endTime(endTime)
|
|
|
+ .build();
|
|
|
+ lastEndTime = endTime;
|
|
|
+ String videoCutUrl = ffmpegApiService.timeCutVideo(timeCutParam);
|
|
|
+ String fileName = String.format("supply/produce/material/video/%s_%d.mp4", produceVideo.getContentId(), System.currentTimeMillis());
|
|
|
+ String videoCutOssUrl = AliOssFileTool.downloadAndSaveInOSS(fileName, videoCutUrl, "video/mp4");
|
|
|
+ // 获取视频信息
|
|
|
+ MediaInfo mediaInfo = getMediaInfo(videoCutOssUrl);
|
|
|
+ // 保存视频素材
|
|
|
+ saveVideoMaterial(produceVideo, video.getId(), videoCutOssUrl, mediaInfo, 2);
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ latch.countDown();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ latch.await();
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ log.error("视频素材生成 InterruptedException", e);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -328,6 +351,7 @@ public class VideoGenerateJob {
|
|
|
* 视频生成
|
|
|
* 1. 视频素材拼接长视频
|
|
|
* 2. 添加音频
|
|
|
+ *
|
|
|
* @param param
|
|
|
* @return
|
|
|
*/
|
|
|
@@ -398,11 +422,8 @@ public class VideoGenerateJob {
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
- while (iterator.hasNext()) {
|
|
|
- ProduceVideoMaterial material = iterator.next();
|
|
|
- if (duration < 0) {
|
|
|
- break;
|
|
|
- }
|
|
|
+ while (duration > 0) {
|
|
|
+ ProduceVideoMaterial material = materialList.get(new Random().nextInt(materialList.size()));
|
|
|
videoUrls.add(material.getUrl());
|
|
|
duration -= material.getDuration();
|
|
|
}
|