|
@@ -11,7 +11,6 @@ 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.jeecg.modules.opencv.utils.VideoProcessing;
|
|
|
import org.opencv.core.Core;
|
|
|
import org.opencv.core.Mat;
|
|
|
import org.opencv.core.Size;
|
|
@@ -56,6 +55,7 @@ public class FfmpegServiceImpl implements IFfmpegService {
|
|
|
command.add("copy");
|
|
|
command.add(m4apath);
|
|
|
runCommand(command);
|
|
|
+ log.info("提取音频文件成功:{}", m4apath);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -79,6 +79,7 @@ public class FfmpegServiceImpl implements IFfmpegService {
|
|
|
command.add(endSecond);
|
|
|
command.add(targetPath);
|
|
|
runCommand(command);
|
|
|
+ log.info("音频文件截取成功:{}", targetPath);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -87,18 +88,69 @@ public class FfmpegServiceImpl implements IFfmpegService {
|
|
|
*/
|
|
|
@Override
|
|
|
public void audioMosaic(String audioPart1, String audioPart2, String finalAudio) {
|
|
|
- List<String> command = new ArrayList<>();
|
|
|
- command.add(FFMPEG_PATH);
|
|
|
- command.add("-i");
|
|
|
- command.add(audioPart1);
|
|
|
- command.add("-i");
|
|
|
- command.add(audioPart2);
|
|
|
- command.add("-filter_complex");
|
|
|
- command.add("\"[0:0] [1:0] concat=n=2:v=0:a=1 [a]\"");
|
|
|
- command.add("-map");
|
|
|
- command.add("[a]");
|
|
|
- command.add(finalAudio);
|
|
|
- runCommand(command);
|
|
|
+ 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();
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -110,20 +162,33 @@ public class FfmpegServiceImpl implements IFfmpegService {
|
|
|
*/
|
|
|
@Override
|
|
|
public void mergeAudioAndVideo(String srcVideoPath, String srcAudioPath, String finalVideoPath) {
|
|
|
- List<String> command = new ArrayList<>();
|
|
|
- command.add(FFMPEG_PATH);
|
|
|
- command.add("-i");
|
|
|
- command.add(srcVideoPath);
|
|
|
- command.add("-i");
|
|
|
- command.add(srcAudioPath);
|
|
|
- command.add("-c:v");
|
|
|
- command.add("copy");
|
|
|
- command.add("-c:a");
|
|
|
- command.add("aac");
|
|
|
- command.add("-strict");
|
|
|
- command.add("experimental");
|
|
|
- command.add(finalVideoPath);
|
|
|
- runCommand(command);
|
|
|
+ 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();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -148,6 +213,7 @@ public class FfmpegServiceImpl implements IFfmpegService {
|
|
|
command.add(frameNumber + "");
|
|
|
command.add(videoFilePath);
|
|
|
runCommand(command);
|
|
|
+ log.info("图片合成视频成功:{}", videoFilePath);
|
|
|
}
|
|
|
|
|
|
private void runCommand(List<String> command) {
|
|
@@ -204,7 +270,6 @@ public class FfmpegServiceImpl implements IFfmpegService {
|
|
|
}.start();
|
|
|
// 等待进程执行结束
|
|
|
process.waitFor();
|
|
|
- process.destroy();
|
|
|
log.info("ffmpeg命令执行完成");
|
|
|
} catch (Exception e) {
|
|
|
e.printStackTrace();
|
|
@@ -251,15 +316,24 @@ public class FfmpegServiceImpl implements IFfmpegService {
|
|
|
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:切分视频
|
|
|
- VideoProcessing.grabberVideoFramer(srcFilePath, splitSrcPath);
|
|
|
- VideoProcessing.grabberVideoFramer(fillFilePath, splitFillPath);
|
|
|
+ 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());
|
|
@@ -273,13 +347,13 @@ public class FfmpegServiceImpl implements IFfmpegService {
|
|
|
//视频替换结束帧数
|
|
|
int exchangeEndFrameNum = config.getFullScreenEndFrame();
|
|
|
//2.1 复制原始视频无变化图片
|
|
|
- for (int i = 0; i < exchangeStartFrameNum; i++) {
|
|
|
+ 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 + "0.jpg";
|
|
|
+ String getImageName = splitFillPath + "1.jpg";
|
|
|
Mat image = Imgcodecs.imread(getImageName, Imgcodecs.IMREAD_COLOR);
|
|
|
//(1)径向模糊图片
|
|
|
Mat radialBlur = ImageUtils.radialBlur(image);
|
|
@@ -303,7 +377,7 @@ public class FfmpegServiceImpl implements IFfmpegService {
|
|
|
Imgcodecs.imwrite(splitFinalPath + exchangeStartFrameNum + ".jpg", wholeImage);
|
|
|
exchangeStartFrameNum++;
|
|
|
//2.3复制填充视频无变化图片
|
|
|
- int checkNum = 1;
|
|
|
+ int checkNum = 2;
|
|
|
for (int i = exchangeStartFrameNum; i < exchangeEndFrameNum; i++) {
|
|
|
String fillImagePath = splitFillPath + checkNum + ".jpg";
|
|
|
File file = new File(fillImagePath);
|
|
@@ -315,30 +389,45 @@ public class FfmpegServiceImpl implements IFfmpegService {
|
|
|
}
|
|
|
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");
|
|
|
- System.out.println(finalUrl);
|
|
|
+ Long current12 = System.currentTimeMillis();
|
|
|
+ log.info("文件上传oss完成:总时长{}毫秒", current12 - current11);
|
|
|
ModelVideoCompositeInfo compositeInfo = new ModelVideoCompositeInfo(userId, videoInfo, finalUrl);
|
|
|
compositeInfoService.saveOrUpdate(compositeInfo);
|
|
|
//删除文件
|
|
@@ -354,6 +443,32 @@ public class FfmpegServiceImpl implements IFfmpegService {
|
|
|
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);
|