|
@@ -0,0 +1,548 @@
|
|
|
+package org.jeecg.modules.opencv.service.impl;
|
|
|
+
|
|
|
+import cn.com.ctop.common.module.utils.*;
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.jeecg.modules.opencv.entity.ModelVideoCompositeInfo;
|
|
|
+import org.jeecg.modules.opencv.entity.ModelVideoInfo;
|
|
|
+import org.jeecg.modules.opencv.entity.ModelVideoInfoConfig;
|
|
|
+import org.jeecg.modules.opencv.service.IFfmpegService;
|
|
|
+import org.jeecg.modules.opencv.service.IModelVideoCompositeInfoService;
|
|
|
+import org.jeecg.modules.opencv.service.IModelVideoInfoConfigService;
|
|
|
+import org.jeecg.modules.opencv.service.IModelVideoInfoService;
|
|
|
+import org.jeecg.modules.opencv.utils.ImageUtils;
|
|
|
+import org.opencv.core.Core;
|
|
|
+import org.opencv.core.Mat;
|
|
|
+import org.opencv.core.Size;
|
|
|
+import org.opencv.imgcodecs.Imgcodecs;
|
|
|
+import org.opencv.imgproc.Imgproc;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.io.*;
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+@Service
|
|
|
+@Slf4j
|
|
|
+public class FfmpegServiceImpl implements IFfmpegService {
|
|
|
+ /**
|
|
|
+ * FFmpeg全路径
|
|
|
+ */
|
|
|
+ @Value("${jeecg.ffmpeg.bin}")
|
|
|
+ private String FFMPEG_PATH;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从视频中抽取音频文件
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void getM4aFromMp4(String videoPath, String m4apath) {
|
|
|
+ /**
|
|
|
+ * D:\java\ffmpeg\bin\ffmpeg.exe -i D:\data\test\\video.mp4 -vn -y -acodec copy D:\data\test\\video.m4a
|
|
|
+ */
|
|
|
+ List<String> command = new ArrayList<>();
|
|
|
+ command.add(FFMPEG_PATH);
|
|
|
+ command.add("-i");
|
|
|
+ command.add(videoPath);
|
|
|
+ command.add("-vn");
|
|
|
+ command.add("-y");
|
|
|
+ command.add("-acodec");
|
|
|
+ command.add("copy");
|
|
|
+ command.add(m4apath);
|
|
|
+ runCommand(command);
|
|
|
+ log.info("提取音频文件成功:{}", m4apath);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 截取音频文件
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void cutM4a(String srcPath, String targetPath, String startSecond, String endSecond) {
|
|
|
+ /**
|
|
|
+ *D:\java\ffmpeg\bin\ffmpeg.exe -i D:\data\test\\video.m4a -vn -acodec copy -ss 00:00:00.000 -t 00:00:08.000 D:\data\test\\cut.m4a
|
|
|
+ */
|
|
|
+ List<String> command = new ArrayList<>();
|
|
|
+ command.add(FFMPEG_PATH);
|
|
|
+ command.add("-i");
|
|
|
+ command.add(srcPath);
|
|
|
+ command.add("-vn");
|
|
|
+ command.add("-acodec");
|
|
|
+ command.add("copy");
|
|
|
+ command.add("-ss");
|
|
|
+ command.add(startSecond);
|
|
|
+ command.add("-t");
|
|
|
+ command.add(endSecond);
|
|
|
+ command.add(targetPath);
|
|
|
+ runCommand(command);
|
|
|
+ log.info("音频文件截取成功:{}", targetPath);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 音频拼接
|
|
|
+ * D:\java\ffmpeg\bin\ffmpeg.exe -i D:\data\test\\cut.m4a -i D:\data\test\\fill.m4a -filter_complex "[0:0] [1:0] concat=n=2:v=0:a=1 [a]" -map [a] D:\data\test\\final.m4a
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void audioMosaic(String audioPart1, String audioPart2, String finalAudio) {
|
|
|
+ StringBuffer command = new StringBuffer();
|
|
|
+ command.append(FFMPEG_PATH);
|
|
|
+ command.append(" -i ");
|
|
|
+ command.append(audioPart1);
|
|
|
+ command.append(" -i ");
|
|
|
+ command.append(audioPart2);
|
|
|
+ command.append(" -filter_complex ");
|
|
|
+ command.append("'[0:0] [1:0] concat=n=2:v=0:a=1 [a]'");
|
|
|
+ command.append(" -map ");
|
|
|
+ command.append("[a] ");
|
|
|
+ command.append(finalAudio);
|
|
|
+ Runtime runtime = Runtime.getRuntime();
|
|
|
+ Process pro = null;
|
|
|
+ try {
|
|
|
+ pro = runtime.exec(new String[]{"sh", "-c", command.toString()});
|
|
|
+ printProcessMsg(pro);
|
|
|
+ int status = pro.waitFor();
|
|
|
+ if (status == 0) {
|
|
|
+ log.info("音频文件合成成功:{}", command.toString());
|
|
|
+ } else {
|
|
|
+ log.info("音频文件合成失败:{}", command.toString());
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理process输出流和错误流,防止进程阻塞,在process.waitFor();前调用
|
|
|
+ *
|
|
|
+ * @param exec
|
|
|
+ * @throws IOException
|
|
|
+ */
|
|
|
+ private void printProcessMsg(Process exec) throws IOException {
|
|
|
+ //防止ffmpeg进程塞满缓存造成死锁
|
|
|
+ InputStream error = exec.getErrorStream();
|
|
|
+ InputStream is = exec.getInputStream();
|
|
|
+
|
|
|
+ StringBuffer result = new StringBuffer();
|
|
|
+ String line = null;
|
|
|
+ try {
|
|
|
+ BufferedReader br = new BufferedReader(new InputStreamReader(error));
|
|
|
+ BufferedReader br2 = new BufferedReader(new InputStreamReader(is));
|
|
|
+
|
|
|
+ while ((line = br.readLine()) != null) {
|
|
|
+ result.append(line + "\n");
|
|
|
+ }
|
|
|
+// log.info("FFMPEG视频转换进程错误信息:" + result.toString());
|
|
|
+
|
|
|
+ result = new StringBuffer();
|
|
|
+ line = null;
|
|
|
+
|
|
|
+ while ((line = br2.readLine()) != null) {
|
|
|
+ result.append(line + "\n");
|
|
|
+ }
|
|
|
+// log.info("FFMPEG视频转换进程输出内容为:" + result.toString());
|
|
|
+ } catch (IOException e2) {
|
|
|
+ e2.printStackTrace();
|
|
|
+ } finally {
|
|
|
+ error.close();
|
|
|
+ is.close();
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 音频+视频合成视频
|
|
|
+ *
|
|
|
+ * @param srcVideoPath
|
|
|
+ * @param srcAudioPath
|
|
|
+ * @param finalVideoPath D:\java\ffmpeg\bin\ffmpeg.exe -i D:\data\test\\merge.mp4 -i D:\data\test\\final.m4a -c:v copy -c:a aac -strict experimental D:\data\test\\final.mp4
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void mergeAudioAndVideo(String srcVideoPath, String srcAudioPath, String finalVideoPath) {
|
|
|
+ StringBuffer command = new StringBuffer();
|
|
|
+ command.append(FFMPEG_PATH);
|
|
|
+ command.append(" -i ");
|
|
|
+ command.append(srcVideoPath);
|
|
|
+ command.append(" -i ");
|
|
|
+ command.append(srcAudioPath);
|
|
|
+ command.append(" -c:v");
|
|
|
+ command.append(" copy");
|
|
|
+ command.append(" -c:a");
|
|
|
+ command.append(" aac");
|
|
|
+ command.append(" -strict");
|
|
|
+ command.append(" experimental ");
|
|
|
+ command.append(finalVideoPath);
|
|
|
+ Runtime runtime = Runtime.getRuntime();
|
|
|
+ Process pro = null;
|
|
|
+ try {
|
|
|
+ pro = runtime.exec(new String[]{"sh", "-c", command.toString()});
|
|
|
+ printProcessMsg(pro);
|
|
|
+ int status = pro.waitFor();
|
|
|
+ if (status == 0) {
|
|
|
+ log.info("视频合成成功:{}", command.toString());
|
|
|
+ } else {
|
|
|
+ log.info("视频合成失败:{}", command.toString());
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将图片合成为视频
|
|
|
+ * D:\java\ffmpeg\bin\ffmpeg.exe -f image2 -i D:\\data\\video\\%d.jpg -vcodec libx264 -r 25 D:\\data\\merge.mp4
|
|
|
+ *
|
|
|
+ * @param imagePath
|
|
|
+ * @param frameNumber
|
|
|
+ * @param videoFilePath
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void mergeImageToVideo(String imagePath, Integer frameNumber, String videoFilePath) {
|
|
|
+ List<String> command = new ArrayList<>();
|
|
|
+ command.add(FFMPEG_PATH);
|
|
|
+ command.add("-f");
|
|
|
+ command.add("image2");
|
|
|
+ command.add("-i");
|
|
|
+ command.add(imagePath + "%d.jpg");
|
|
|
+ command.add("-vcodec");
|
|
|
+ command.add("libx264");
|
|
|
+ command.add("-r");
|
|
|
+ command.add(frameNumber + "");
|
|
|
+ command.add(videoFilePath);
|
|
|
+ runCommand(command);
|
|
|
+ log.info("图片合成视频成功:{}", videoFilePath);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void runCommand(List<String> command) {
|
|
|
+ try {
|
|
|
+ ProcessBuilder builder = new ProcessBuilder();
|
|
|
+ builder.command(command);
|
|
|
+ builder.redirectErrorStream(true);
|
|
|
+ Process process = builder.start();
|
|
|
+ //获取进程的标准输入流
|
|
|
+ final InputStream is1 = process.getInputStream();
|
|
|
+ //获取进城的错误流
|
|
|
+ final InputStream is2 = process.getErrorStream();
|
|
|
+ //启动两个线程,一个线程负责读标准输出流,另一个负责读标准错误流
|
|
|
+ new Thread() {
|
|
|
+ public void run() {
|
|
|
+ BufferedReader br1 = new BufferedReader(new InputStreamReader(is1));
|
|
|
+ try {
|
|
|
+ String line1 = null;
|
|
|
+ while ((line1 = br1.readLine()) != null) {
|
|
|
+ if (line1 != null) {
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (IOException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ } finally {
|
|
|
+ try {
|
|
|
+ is1.close();
|
|
|
+ } catch (IOException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }.start();
|
|
|
+
|
|
|
+ new Thread() {
|
|
|
+ public void run() {
|
|
|
+ BufferedReader br2 = new BufferedReader(new InputStreamReader(is2));
|
|
|
+ try {
|
|
|
+ String line2 = null;
|
|
|
+ while ((line2 = br2.readLine()) != null) {
|
|
|
+ if (line2 != null) {
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (IOException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ } finally {
|
|
|
+ try {
|
|
|
+ is2.close();
|
|
|
+ } catch (IOException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }.start();
|
|
|
+ // 等待进程执行结束
|
|
|
+ process.waitFor();
|
|
|
+ log.info("ffmpeg命令执行完成");
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Map<String, Object> videoMerge(JSONObject data, String userId) {
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
+ try {
|
|
|
+ Long modelId = data.getLong("modelId");
|
|
|
+ String url = data.getString("url");
|
|
|
+ if (null == modelId || modelId == 0 || null == url || "".equals(url.trim())) {
|
|
|
+ ResultMapUtils.setResultMap(result, StatusCode.MODEL_VIDEO_NOT_EXIST);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ ModelVideoInfo videoInfo = modelVideoInfoService.getById(modelId);
|
|
|
+ if (null == videoInfo || videoInfo.getStatus() == 0) {
|
|
|
+ ResultMapUtils.setResultMap(result, StatusCode.MODEL_VIDEO_NOT_EXIST);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ //1:
|
|
|
+ url = "https:" + url;
|
|
|
+ String getUrl = mergeVideo(videoInfo, url, userId);
|
|
|
+ ResultMapUtils.setResultMap(result, StatusCode.COMMON_SUCCESS);
|
|
|
+ result.put("url", getUrl);
|
|
|
+ return result;
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ ResultMapUtils.setResultMap(result, StatusCode.COMMON_SERVER_ERROR);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private IModelVideoInfoConfigService configService;
|
|
|
+
|
|
|
+ private String mergeVideo(ModelVideoInfo videoInfo, String url, String userId) throws Exception {
|
|
|
+ Long current = System.currentTimeMillis();
|
|
|
+ System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
|
|
|
+ //根据url下载视频
|
|
|
+ String srcVideoPath = PropertiesUtils.getValue("kuaishou_config", "model_video_src_path");
|
|
|
+ String fillVideoPath = PropertiesUtils.getValue("kuaishou_config", "model_video_fill_path");
|
|
|
+ String finalVideoPath = PropertiesUtils.getValue("kuaishou_config", "model_video_final_path");
|
|
|
+
|
|
|
+ String fillFilePath = LoadFileUtil.downLoadFromUrl(url, fillVideoPath);
|
|
|
+ Long current1 = System.currentTimeMillis();
|
|
|
+ log.info("替换视频下载完成:总时长{}毫秒", current1 - current);
|
|
|
+ String srcFilePath = LoadFileUtil.downLoadFromUrl(videoInfo.getModelVideoUrl(), srcVideoPath);
|
|
|
+ Long current2 = System.currentTimeMillis();
|
|
|
+ log.info("模板文件下载完成:总时长{}毫秒", current2 - current1);
|
|
|
+ String splitSrcPath = srcVideoPath + current + File.separator;
|
|
|
+ LoadFileUtil.checkFile(splitSrcPath);
|
|
|
+ String splitFillPath = fillVideoPath + current + File.separator;
|
|
|
+ LoadFileUtil.checkFile(splitFillPath);
|
|
|
+ String splitFinalPath = finalVideoPath + current + File.separator;
|
|
|
+ LoadFileUtil.checkFile(splitFinalPath);
|
|
|
+ //1:切分视频
|
|
|
+ videoSplitToImage(srcFilePath, splitSrcPath);
|
|
|
+ Long current3 = System.currentTimeMillis();
|
|
|
+ log.info("模板文件切分完成:总时长{}毫秒", current3 - current2);
|
|
|
+ videoSplitToImage(fillFilePath, splitFillPath);
|
|
|
+ Long current4 = System.currentTimeMillis();
|
|
|
+ log.info("填充文件切分完成:总时长{}毫秒", current4 - current3);
|
|
|
+ ModelVideoInfoConfig config = configService.getByModelId(videoInfo.getId());
|
|
|
+ if (null == config) {
|
|
|
+ log.error("配置文件尚未配置=>modelId:{}", videoInfo.getId());
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ if (config.getType() == 1) {
|
|
|
+ //简单的最终追加
|
|
|
+ //2:合并图片
|
|
|
+ //视频替换开始帧数
|
|
|
+ int exchangeStartFrameNum = config.getFullScreenStartFrame();
|
|
|
+ //视频替换结束帧数
|
|
|
+ int exchangeEndFrameNum = config.getFullScreenEndFrame();
|
|
|
+ //2.1 复制原始视频无变化图片
|
|
|
+ for (int i = 1; i < exchangeStartFrameNum; i++) {
|
|
|
+ Mat srcNoChangeImage = Imgcodecs.imread(splitSrcPath + i + ".jpg");
|
|
|
+ Imgcodecs.imwrite(splitFinalPath + i + ".jpg", srcNoChangeImage);
|
|
|
+ }
|
|
|
+
|
|
|
+ //2.2 转场动画(从fill文件夹的第一帧开始)
|
|
|
+ String getImageName = splitFillPath + "1.jpg";
|
|
|
+ Mat image = Imgcodecs.imread(getImageName, Imgcodecs.IMREAD_COLOR);
|
|
|
+ //(1)径向模糊图片
|
|
|
+ Mat radialBlur = ImageUtils.radialBlur(image);
|
|
|
+ Imgcodecs.imwrite(splitFinalPath + exchangeStartFrameNum + ".jpg", radialBlur);
|
|
|
+ exchangeStartFrameNum++;
|
|
|
+ //(2)放大后径向模糊图片
|
|
|
+ Mat bigImamge = new Mat();
|
|
|
+ Imgproc.resize(image, bigImamge, new Size(image.cols() * 1.25, image.rows() * 1.25));
|
|
|
+ Mat destBigImamge = ImageUtils.cutFromCenter(bigImamge, image.width(), image.height());
|
|
|
+ Mat radialBlurDestBigImamge = ImageUtils.radialBlur(destBigImamge);
|
|
|
+ Imgcodecs.imwrite(splitFinalPath + exchangeStartFrameNum + ".jpg", radialBlurDestBigImamge);
|
|
|
+ exchangeStartFrameNum++;
|
|
|
+ //(3)缩小之后重复平铺加径向模糊
|
|
|
+ Mat smallImage = new Mat();
|
|
|
+ Mat halfImage = new Mat();
|
|
|
+ Mat wholeImage = new Mat();
|
|
|
+ Imgproc.resize(image, smallImage, new Size(image.cols() / 2, image.rows() / 2));
|
|
|
+ ImageUtils.hconcat(smallImage, smallImage, halfImage);
|
|
|
+ ImageUtils.vconcat(halfImage, halfImage, wholeImage);
|
|
|
+ wholeImage = ImageUtils.radialBlur(wholeImage);
|
|
|
+ Imgcodecs.imwrite(splitFinalPath + exchangeStartFrameNum + ".jpg", wholeImage);
|
|
|
+ exchangeStartFrameNum++;
|
|
|
+ //2.3复制填充视频无变化图片
|
|
|
+ int checkNum = 2;
|
|
|
+ for (int i = exchangeStartFrameNum; i < exchangeEndFrameNum; i++) {
|
|
|
+ String fillImagePath = splitFillPath + checkNum + ".jpg";
|
|
|
+ File file = new File(fillImagePath);
|
|
|
+ if (file.exists()) {
|
|
|
+ Mat srcMat = Imgcodecs.imread(fillImagePath);
|
|
|
+ Imgcodecs.imwrite(splitFinalPath + i + ".jpg", srcMat);
|
|
|
+ } else {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ checkNum++;
|
|
|
+ }
|
|
|
+ Long current5 = System.currentTimeMillis();
|
|
|
+ log.info("图片替换完成:总时长{}毫秒", current5 - current4);
|
|
|
+ //3开始合成视频
|
|
|
+ //3.1 抽取音频文件
|
|
|
+ String srcM4aFilePath = splitSrcPath + "src.m4a";
|
|
|
+ getM4aFromMp4(srcFilePath, srcM4aFilePath);
|
|
|
+ Long current6 = System.currentTimeMillis();
|
|
|
+ log.info("抽取模板音频文件完成:总时长{}毫秒", current6 - current4);
|
|
|
+ String fillM4aFilePath = splitSrcPath + "fill.m4a";
|
|
|
+ getM4aFromMp4(fillFilePath, fillM4aFilePath);
|
|
|
+ Long current7 = System.currentTimeMillis();
|
|
|
+ log.info("抽取填充音频文件完成:总时长{}毫秒", current7 - current6);
|
|
|
+ //3.2 截取源模板音频文件
|
|
|
+ String cutM4aFilePath = splitSrcPath + "cut.m4a";
|
|
|
+ //计算需要截取的音频时长
|
|
|
+ String secondStr = getSecond(exchangeStartFrameNum);
|
|
|
+ cutM4a(srcM4aFilePath, cutM4aFilePath, "00:00:00.000", secondStr);
|
|
|
+ Long current8 = System.currentTimeMillis();
|
|
|
+ log.info("音频文件截取完成:总时长{}毫秒", current8 - current7);
|
|
|
+ //3.3 合并两个音频文件
|
|
|
+ String finalM4aFilePath = splitSrcPath + "final.m4a";
|
|
|
+ audioMosaic(cutM4aFilePath, fillM4aFilePath, finalM4aFilePath);
|
|
|
+ Long current9 = System.currentTimeMillis();
|
|
|
+ log.info("音频文件合成完成:总时长{}毫秒", current9 - current8);
|
|
|
+ //3.4 将最终生成的图片合成为视频
|
|
|
+ String noVoiceVideoFilePath = splitSrcPath + "novoice.mp4";
|
|
|
+ mergeImageToVideo(splitFinalPath, 25, noVoiceVideoFilePath);
|
|
|
+ Long current10 = System.currentTimeMillis();
|
|
|
+ log.info("图片合成视频完成:总时长{}毫秒", current10 - current9);
|
|
|
+ //3.5 合并视频和音频
|
|
|
+ String finalVideoFilePath = splitSrcPath + "final.mp4";
|
|
|
+ mergeAudioAndVideo(noVoiceVideoFilePath, finalM4aFilePath, finalVideoFilePath);
|
|
|
+ Long current11 = System.currentTimeMillis();
|
|
|
+ log.info("视频合成完成:总时长{}毫秒", current11 - current10);
|
|
|
+ //3.6 上传视频文件至OSS并返回url存入数据库
|
|
|
+ InputStream inputStream = new FileInputStream(finalVideoFilePath);
|
|
|
+ String finalUrl = OSSUtils.uploadFile2OSS(inputStream, "model/final/", videoInfo.getId() + "_" + current + ".mp4");
|
|
|
+ Long current12 = System.currentTimeMillis();
|
|
|
+ log.info("文件上传oss完成:总时长{}毫秒", current12 - current11);
|
|
|
+ ModelVideoCompositeInfo compositeInfo = new ModelVideoCompositeInfo(userId, videoInfo, finalUrl);
|
|
|
+ compositeInfoService.saveOrUpdate(compositeInfo);
|
|
|
+ //删除文件
|
|
|
+ deleteFile(splitSrcPath, splitFillPath, splitFinalPath, srcFilePath, fillFilePath);
|
|
|
+ log.info("视频合成成功:总时长{}毫秒", System.currentTimeMillis() - current);
|
|
|
+ return finalUrl;
|
|
|
+ } else if (config.getType() == 2) {
|
|
|
+ //2:合并图片
|
|
|
+
|
|
|
+ } else {
|
|
|
+ log.error("此类模板视频尚未配置=>type:{}", config.getType());
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void videoSplitToImage(String srcFilePath, String splitSrcPath) {
|
|
|
+ StringBuffer command = new StringBuffer();
|
|
|
+ command.append(FFMPEG_PATH);
|
|
|
+ command.append(" -i ");
|
|
|
+ command.append(srcFilePath);
|
|
|
+ command.append(" -threads 5");
|
|
|
+ command.append(" -preset");
|
|
|
+ command.append(" ultrafast ");
|
|
|
+ command.append(splitSrcPath + "%d.jpg");
|
|
|
+ Runtime runtime = Runtime.getRuntime();
|
|
|
+ Process pro = null;
|
|
|
+ try {
|
|
|
+ pro = runtime.exec(new String[]{"sh", "-c", command.toString()});
|
|
|
+ printProcessMsg(pro);
|
|
|
+ int status = pro.waitFor();
|
|
|
+ if (status == 0) {
|
|
|
+ log.info("视频切分成功:{}", command.toString());
|
|
|
+ } else {
|
|
|
+ log.info("视频切分失败:{}", command.toString());
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void deleteFile(String splitSrcPath, String splitFillPath, String splitFinalPath, String srcFilePath, String fillFilePath) {
|
|
|
+ LoadFileUtil.delFile(splitFillPath);
|
|
|
+ LoadFileUtil.delFile(splitSrcPath);
|
|
|
+ LoadFileUtil.delFile(splitFinalPath);
|
|
|
+ LoadFileUtil.delFile(srcFilePath);
|
|
|
+ LoadFileUtil.delFile(fillFilePath);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private IModelVideoCompositeInfoService compositeInfoService;
|
|
|
+
|
|
|
+ private String getSecond(int exchangeStartFrameNum) {
|
|
|
+ BigDecimal second = new BigDecimal(exchangeStartFrameNum).divide(new BigDecimal("25"), 3, RoundingMode.HALF_UP);
|
|
|
+ if (second.compareTo(new BigDecimal("10")) < 0) {
|
|
|
+ return "00:00:0" + second.toString();
|
|
|
+ } else {
|
|
|
+ return "00:00:" + second.toString();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private IModelVideoInfoService modelVideoInfoService;
|
|
|
+
|
|
|
+// public void cornerCheck(){
|
|
|
+// int srcPhoneFrameNumFrom = 163;
|
|
|
+// int changeSenceFrameNumFrom = 234;
|
|
|
+// //2.1 复制原始视频无变化图片
|
|
|
+// for (int i = 1; i < srcPhoneFrameNumFrom; i++) {
|
|
|
+// Mat srcNoChangeImage = Imgcodecs.imread(srcPath + i + ".jpg");
|
|
|
+// Imgcodecs.imwrite(targetPath + i + ".jpg", srcNoChangeImage);
|
|
|
+// }
|
|
|
+// String getImageName = "";
|
|
|
+// //2.2 填充手机内部图片画面
|
|
|
+// for (int i = 163; i < changeSenceFrameNumFrom; i++) {
|
|
|
+// String backImg = srcPath + (i) + ".jpg";
|
|
|
+// String phoneImg = fillPath + (i - 162) + ".jpg";
|
|
|
+// String targetImg = targetPath + i + ".jpg";
|
|
|
+// Perspective.perpectiveImg(backImg, phoneImg, targetImg);
|
|
|
+// getImageName = targetImg;
|
|
|
+// }
|
|
|
+// //2.3 转场动画
|
|
|
+// Mat image = Imgcodecs.imread(getImageName, Imgcodecs.IMREAD_COLOR);
|
|
|
+// //(1)径向模糊图片
|
|
|
+// Mat radialBlur = ImageUtils.radialBlur(image);
|
|
|
+// Imgcodecs.imwrite(targetPath + changeSenceFrameNumFrom + ".jpg", radialBlur);
|
|
|
+// changeSenceFrameNumFrom++;
|
|
|
+// //(2)放大后径向模糊图片
|
|
|
+// Mat bigImamge = new Mat();
|
|
|
+// Imgproc.resize(image, bigImamge, new Size(image.cols() * 1.25, image.rows() * 1.25));
|
|
|
+// Mat destBigImamge = ImageUtils.cutFromCenter(bigImamge, image.width(), image.height());
|
|
|
+// Mat radialBlurDestBigImamge = ImageUtils.radialBlur(destBigImamge);
|
|
|
+// Imgcodecs.imwrite(targetPath + changeSenceFrameNumFrom + ".jpg", radialBlurDestBigImamge);
|
|
|
+// changeSenceFrameNumFrom++;
|
|
|
+// //(3)缩小之后重复平铺加径向模糊
|
|
|
+// Mat smallImage = new Mat();
|
|
|
+// Mat halfImage = new Mat();
|
|
|
+// Mat wholeImage = new Mat();
|
|
|
+// Imgproc.resize(image, smallImage, new Size(image.cols() / 2, image.rows() / 2));
|
|
|
+// ImageUtils.hconcat(smallImage, smallImage, halfImage);
|
|
|
+// ImageUtils.vconcat(halfImage, halfImage, wholeImage);
|
|
|
+// wholeImage = ImageUtils.radialBlur(wholeImage);
|
|
|
+// Imgcodecs.imwrite(targetPath + changeSenceFrameNumFrom + ".jpg", wholeImage);
|
|
|
+// changeSenceFrameNumFrom++;
|
|
|
+// //复制填充视频无变化图片
|
|
|
+// for (int i = changeSenceFrameNumFrom; i < 100000; i++) {
|
|
|
+// int checkNum = i - 3 - srcPhoneFrameNumFrom + 1;
|
|
|
+// String fillImagePath = fillPath + checkNum + ".jpg";
|
|
|
+// File file = new File(fillImagePath);
|
|
|
+// if (file.exists()) {
|
|
|
+// Mat srcMat = Imgcodecs.imread(fillImagePath);
|
|
|
+// Imgcodecs.imwrite(targetPath + i + ".jpg", srcMat);
|
|
|
+// } else {
|
|
|
+// break;
|
|
|
+// }
|
|
|
+// }
|
|
|
+// }
|
|
|
+}
|