Commit 428e2657f4106b81e58d1ffd567f76c536e2c54b

Authored by panlinlin
1 parent aad3e404

添加录像查询和录像裁剪合并

src/main/java/top/panll/assist/config/StartConfig.java
... ... @@ -39,6 +39,14 @@ public class StartConfig implements CommandLineRunner {
39 39 logger.error("[userSettings.record]配置错误,请检查路径是否存在");
40 40 System.exit(1);
41 41 }
  42 + if (!recordFile.canRead()) {
  43 + logger.error("[userSettings.record]路径无法读取");
  44 + System.exit(1);
  45 + }
  46 + if (!recordFile.canWrite()) {
  47 + logger.error("[userSettings.record]路径无法写入");
  48 + System.exit(1);
  49 + }
42 50 try {
43 51 String ffmpegPath = userSettings.getFfmpeg();
44 52 String ffprobePath = userSettings.getFfprobe();
... ...
src/main/java/top/panll/assist/service/FFmpegExecUtils.java
1 1 package top.panll.assist.service;
2 2  
3 3 import net.bramp.ffmpeg.FFmpeg;
  4 +import net.bramp.ffmpeg.FFmpegExecutor;
  5 +import net.bramp.ffmpeg.FFmpegUtils;
4 6 import net.bramp.ffmpeg.FFprobe;
  7 +import net.bramp.ffmpeg.builder.FFmpegBuilder;
  8 +import net.bramp.ffmpeg.job.FFmpegJob;
  9 +import net.bramp.ffmpeg.probe.FFmpegProbeResult;
  10 +import net.bramp.ffmpeg.progress.Progress;
  11 +import net.bramp.ffmpeg.progress.ProgressListener;
  12 +import org.springframework.util.DigestUtils;
  13 +
  14 +import java.io.BufferedWriter;
  15 +import java.io.File;
  16 +import java.io.FileWriter;
  17 +import java.io.IOException;
  18 +import java.util.List;
  19 +import java.util.concurrent.TimeUnit;
5 20  
6 21 public class FFmpegExecUtils {
7 22  
... ... @@ -24,4 +39,82 @@ public class FFmpegExecUtils {
24 39 public FFprobe ffprobe;
25 40 public FFmpeg ffmpeg;
26 41  
  42 + public interface VideoHandEndCallBack {
  43 + public void run(String status, double percentage, String result);
  44 + }
  45 +
  46 + public static void mergeOrCutFile(List<File> fils, File dest, VideoHandEndCallBack callBack){
  47 + FFmpeg ffmpeg = FFmpegExecUtils.getInstance().ffmpeg;
  48 + FFprobe ffprobe = FFmpegExecUtils.getInstance().ffprobe;
  49 + if (fils == null || fils.size() == 0 || ffmpeg == null || ffprobe == null || dest== null || !dest.exists()){
  50 + callBack.run("error", 0.0, null);
  51 + return;
  52 + }
  53 +
  54 + String temp = DigestUtils.md5DigestAsHex(String.valueOf(System.currentTimeMillis()).getBytes());
  55 + File tempFile = new File(dest.getAbsolutePath() + File.separator + temp);
  56 + if (!tempFile.exists()) {
  57 + tempFile.mkdirs();
  58 + }
  59 + FFmpegExecutor executor = new FFmpegExecutor(ffmpeg, ffprobe);
  60 + String fileListName = tempFile.getName() + File.separator + "fileList";
  61 + double durationAll = 0.0;
  62 + try {
  63 + BufferedWriter bw =new BufferedWriter(new FileWriter(fileListName));
  64 + for (File file : fils) {
  65 + FFmpegProbeResult in = ffprobe.probe(file.getAbsolutePath());
  66 + double duration = in.getFormat().duration;
  67 + System.out.println(duration);
  68 + bw.write("file " + file.getAbsolutePath());
  69 + bw.newLine();
  70 + durationAll += duration;
  71 + }
  72 + bw.flush();
  73 + bw.close();
  74 + System.out.println(durationAll);
  75 + } catch (IOException e) {
  76 + e.printStackTrace();
  77 + callBack.run("error", 0.0, null);
  78 + }
  79 + long startTime = System.currentTimeMillis();
  80 + FFmpegBuilder builder = new FFmpegBuilder()
  81 +
  82 + .setFormat("concat")
  83 + .overrideOutputFiles(true)
  84 + .setInput(fileListName) // Or filename
  85 + .addExtraArgs("-safe", "0")
  86 + .addOutput(dest.getAbsolutePath() + File.separator + temp + ".mp4")
  87 + .setVideoCodec("copy")
  88 + .setFormat("mp4")
  89 +
  90 + .done();
  91 +
  92 +
  93 + double finalDurationAll = durationAll;
  94 + FFmpegJob job = executor.createJob(builder, (Progress progress) -> {
  95 + final double duration_ns = finalDurationAll * TimeUnit.SECONDS.toNanos(1);
  96 + double percentage = progress.out_time_ns / duration_ns;
  97 +
  98 + // Print out interesting information about the progress
  99 + System.out.println(String.format(
  100 + "[%.0f%%] status:%s frame:%d time:%s ms fps:%.0f speed:%.2fx",
  101 + percentage * 100,
  102 + progress.status,
  103 + progress.frame,
  104 + FFmpegUtils.toTimecode(progress.out_time_ns, TimeUnit.NANOSECONDS),
  105 + progress.fps.doubleValue(),
  106 + progress.speed
  107 + ));
  108 + if (progress.status.equals(Progress.Status.END)){
  109 + callBack.run(progress.status.name(), percentage,
  110 + dest.getAbsolutePath() + File.separator + temp + ".mp4");
  111 + System.out.println(System.currentTimeMillis() - startTime);
  112 + }else {
  113 + callBack.run(progress.status.name(), percentage, null);
  114 + }
  115 + });
  116 +
  117 + job.run();
  118 + }
  119 +
27 120 }
... ...
src/main/java/top/panll/assist/service/VideoFileService.java
... ... @@ -8,15 +8,15 @@ import org.springframework.beans.factory.annotation.Autowired;
8 8 import org.springframework.stereotype.Service;
9 9 import top.panll.assist.config.StartConfig;
10 10 import top.panll.assist.dto.UserSettings;
  11 +import top.panll.assist.utils.DateUtils;
11 12  
12 13 import java.io.File;
13 14 import java.io.FilenameFilter;
14 15 import java.io.IOException;
  16 +import java.lang.reflect.Array;
15 17 import java.text.ParseException;
16 18 import java.text.SimpleDateFormat;
17   -import java.util.ArrayList;
18   -import java.util.Date;
19   -import java.util.List;
  19 +import java.util.*;
20 20  
21 21 @Service
22 22 public class VideoFileService {
... ... @@ -27,6 +27,11 @@ public class VideoFileService {
27 27 private UserSettings userSettings;
28 28  
29 29  
  30 + /**
  31 + * 对视频文件重命名, 00:00:00-00:00:00
  32 + * @param file
  33 + * @throws ParseException
  34 + */
30 35 public void handFile(File file) throws ParseException {
31 36 FFprobe ffprobe = FFmpegExecUtils.getInstance().ffprobe;
32 37 if(file.isFile() && !file.getName().startsWith(".") && file.getName().indexOf(":") < 0) {
... ... @@ -54,6 +59,14 @@ public class VideoFileService {
54 59 }
55 60 }
56 61  
  62 + /**
  63 + * 获取制定推流的指定时间段内的推流
  64 + * @param app
  65 + * @param stream
  66 + * @param startTime
  67 + * @param endTime
  68 + * @return
  69 + */
57 70 public List<File> getFilesInTime(String app, String stream, Date startTime, Date endTime){
58 71  
59 72 List<File> result = new ArrayList<>();
... ... @@ -63,17 +76,15 @@ public class VideoFileService {
63 76 }
64 77  
65 78 SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  79 + SimpleDateFormat formatterForDate = new SimpleDateFormat("yyyy-MM-dd");
66 80 String startTimeStr = formatter.format(startTime);
67 81 String endTimeStr = formatter.format(endTime);
68 82 logger.debug("获取[app: {}, stream: {}, statime: {}, endTime: {}]的视频", app, stream,
69 83 startTimeStr, endTimeStr);
70 84  
71 85 File file = new File(userSettings.getRecord());
72   - File[] appFiles = file.listFiles(new FilenameFilter() {
73   - @Override
74   - public boolean accept(File dir, String name) {
  86 + File[] appFiles = file.listFiles((File dir, String name) -> {
75 87 return app.equals(name);
76   - }
77 88 });
78 89 if (appFiles.length == 0) {
79 90 logger.warn("获取[app: {}, stream: {}, statime: {}, endTime: {}]的视频时未找到目录: {}", app, stream,
... ... @@ -81,11 +92,8 @@ public class VideoFileService {
81 92 return result;
82 93 }
83 94 File appFile = appFiles[0];
84   - File[] streamFiles = appFile.listFiles(new FilenameFilter() {
85   - @Override
86   - public boolean accept(File dir, String name) {
  95 + File[] streamFiles = appFile.listFiles((File dir, String name)-> {
87 96 return stream.equals(name);
88   - }
89 97 });
90 98 if (streamFiles.length == 0) {
91 99 logger.warn("获取[app: {}, stream: {}, statime: {}, endTime: {}]的视频时未找到目录: {}", app, stream,
... ... @@ -93,10 +101,69 @@ public class VideoFileService {
93 101 return result;
94 102 }
95 103 File streamFile = streamFiles[0];
96   - // TODO 按时间获取文件
97 104  
  105 + File[] dateFiles = streamFile.listFiles((File dir, String name) -> {
  106 + Date fileDate = null;
  107 + try {
  108 + fileDate = formatterForDate.parse(name);
  109 + } catch (ParseException e) {
  110 + logger.error("过滤日期文件时异常: {}-{}", name, e.getMessage());
  111 + return false;
  112 + }
  113 + return DateUtils.getStartOfDay(fileDate).after(startTime) && DateUtils.getEndOfDay(fileDate).before(endTime);
  114 + });
98 115  
  116 + if (dateFiles != null && dateFiles.length > 0) {
  117 + for (File dateFile : dateFiles) {
  118 + // TODO 按时间获取文件
  119 + File[] files = dateFile.listFiles((File dir, String name) ->{
  120 + boolean filterResult = false;
  121 + if (name.contains(":") && name.endsWith(".mp4") && !name.startsWith(".")){
  122 + String[] timeArray = name.split("-");
  123 + if (timeArray.length == 2){
  124 + String fileStartTimeStr = dateFile.getName() + " " + timeArray[0];
  125 + String fileEndTimeStr = dateFile.getName() + " " + timeArray[1];
  126 + try {
  127 + filterResult = formatter.parse(fileStartTimeStr).after(startTime) && formatter.parse(fileEndTimeStr).before(endTime);
  128 + } catch (ParseException e) {
  129 + logger.error("过滤视频文件时异常: {}-{}", name, e.getMessage());
  130 + return false;
  131 + }
  132 + }
  133 + }
  134 + return filterResult;
  135 + });
  136 +
  137 + List<File> fileList = Arrays.asList(files);
  138 + result.addAll(fileList);
  139 + }
  140 + }
  141 + if (result.size() > 0) {
  142 + result.sort((File f1, File f2) -> {
  143 + boolean sortResult = false;
  144 + String[] timeArray1 = f1.getName().split("-");
  145 + String[] timeArray2 = f2.getName().split("-");
  146 + if (timeArray1.length == 2 && timeArray2.length == 2){
  147 + File dateFile1 = f1.getParentFile();
  148 + File dateFile2 = f2.getParentFile();
  149 + String fileStartTimeStr1 = dateFile1.getName() + " " + timeArray1[0];
  150 + String fileStartTimeStr2 = dateFile2.getName() + " " + timeArray1[0];
  151 + try {
  152 + sortResult = formatter.parse(fileStartTimeStr1).before(formatter.parse(fileStartTimeStr2)) ;
  153 + } catch (ParseException e) {
  154 + logger.error("排序视频文件时异常: {}-{}", fileStartTimeStr1, fileStartTimeStr2);
  155 + }
  156 + }
  157 + return sortResult?1 : 0;
  158 + });
  159 + }
99 160 return result;
100 161 }
101 162  
  163 +
  164 + public void mergeOrCut(String app, String stream, Date startTime, Date endTime) {
  165 + List<File> filesInTime = this.getFilesInTime(app, stream, startTime, endTime);
  166 +
  167 + }
  168 +
102 169 }
... ...
src/main/java/top/panll/assist/utils/DateUtils.java 0 → 100644
  1 +package top.panll.assist.utils;
  2 +
  3 +import java.time.Instant;
  4 +import java.time.LocalDateTime;
  5 +import java.time.LocalTime;
  6 +import java.time.ZoneId;
  7 +import java.util.Date;
  8 +
  9 +public class DateUtils {
  10 +
  11 + // 获得某天最大时间 2020-02-19 23:59:59
  12 + public static Date getEndOfDay(Date date) {
  13 + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault());;
  14 + LocalDateTime endOfDay = localDateTime.with(LocalTime.MAX);
  15 + return Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant());
  16 + }
  17 +
  18 + // 获得某天最小时间 2020-02-17 00:00:00
  19 + public static Date getStartOfDay(Date date) {
  20 + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault());
  21 + LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN);
  22 + return Date.from(startOfDay.atZone(ZoneId.systemDefault()).toInstant());
  23 + }
  24 +
  25 +}
... ...