Commit fb7063c400704cf4a379c7701625a2fe9fd2edb0

Authored by 648540858
2 parents 80cafd65 ede562fc

Merge branch '优化'

... ... @@ -9,7 +9,7 @@
9 9 </parent>
10 10 <groupId>top.panll.assist</groupId>
11 11 <artifactId>wvp-pro-assist</artifactId>
12   - <version>2.6.8</version>
  12 + <version>2.6.9</version>
13 13 <name>wvp-pro-assist</name>
14 14 <description></description>
15 15 <properties>
... ...
src/main/java/top/panll/assist/config/StartConfig.java deleted 100755 → 0
1   -package top.panll.assist.config;
2   -
3   -import net.bramp.ffmpeg.FFmpeg;
4   -import net.bramp.ffmpeg.FFprobe;
5   -import org.slf4j.Logger;
6   -import org.slf4j.LoggerFactory;
7   -import org.springframework.beans.factory.annotation.Autowired;
8   -import org.springframework.beans.factory.annotation.Value;
9   -import org.springframework.boot.CommandLineRunner;
10   -import org.springframework.core.annotation.Order;
11   -import org.springframework.stereotype.Component;
12   -import top.panll.assist.dto.UserSettings;
13   -import top.panll.assist.service.FFmpegExecUtils;
14   -import top.panll.assist.service.VideoFileService;
15   -
16   -import java.io.*;
17   -import java.nio.charset.StandardCharsets;
18   -
19   -/**
20   - * 用于启动检查环境
21   - */
22   -@Component
23   -@Order(value=1)
24   -public class StartConfig implements CommandLineRunner {
25   -
26   - private final static Logger logger = LoggerFactory.getLogger(StartConfig.class);
27   -
28   - @Value("${server.port}")
29   - private String port;
30   -
31   - @Autowired
32   - private UserSettings userSettings;
33   -
34   - @Autowired
35   - private VideoFileService videoFileService;
36   -
37   -
38   - @Override
39   - public void run(String... args) {
40   - String record = userSettings.getRecord();
41   - if (!record.endsWith(File.separator)) {
42   - userSettings.setRecord(userSettings.getRecord() + File.separator);
43   - }
44   -
45   - File recordFile = new File(record);
46   - if (!recordFile.exists()){
47   - logger.warn("[userSettings.record]路径不存在,开始创建");
48   - boolean mkResult = recordFile.mkdirs();
49   - if (!mkResult) {
50   - logger.info("[userSettings.record]目录创建失败");
51   - System.exit(1);
52   - }
53   - }else {
54   - if ( !recordFile.isDirectory()) {
55   - logger.warn("[userSettings.record]路径是文件,请修改为目录");
56   - System.exit(1);
57   - }
58   - if (!recordFile.canRead()) {
59   - logger.error("[userSettings.record]路径无法读取");
60   - System.exit(1);
61   - }
62   - if (!recordFile.canWrite()) {
63   - logger.error("[userSettings.record]路径无法写入");
64   - System.exit(1);
65   - }
66   - }
67   -
68   - try {
69   -
70   - // 对目录进行预整理
71   - File[] appFiles = recordFile.listFiles();
72   - if (appFiles != null && appFiles.length > 0) {
73   - for (File appFile : appFiles) {
74   - if (appFile.getName().equals("recordTemp")) {
75   - continue;
76   - }
77   - File[] streamFiles = appFile.listFiles();
78   - if (streamFiles != null && streamFiles.length > 0) {
79   - for (File streamFile : streamFiles) {
80   - File[] dateFiles = streamFile.listFiles();
81   - if (dateFiles != null && dateFiles.length > 0) {
82   - for (File dateFile : dateFiles) {
83   - File[] files = dateFile.listFiles();
84   - if (files != null && files.length > 0) {
85   - for (File file : files) {
86   - videoFileService.handFile(file, appFile.getName(), streamFile.getName());
87   - }
88   - }
89   - }
90   - }
91   -
92   - }
93   - }
94   -
95   - }
96   - }
97   -
98   - } catch (Exception exception){
99   - exception.printStackTrace();
100   - logger.error("环境错误: " + exception.getMessage());
101   - }
102   - }
103   -
104   -// private void writeAssistDownPage(File recordFile) {
105   -// try {
106   -// File file = new File(recordFile.getParentFile().getAbsolutePath(), "download.html");
107   -// if (file.exists()) {
108   -// file.delete();
109   -// }
110   -// file.createNewFile();
111   -// FileOutputStream fs = new FileOutputStream(file);
112   -// StringBuffer stringBuffer = new StringBuffer();
113   -// String content = "<!DOCTYPE html>\n" +
114   -// "<html lang=\"en\">\n" +
115   -// "<head>\n" +
116   -// " <meta charset=\"UTF-8\">\n" +
117   -// " <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n" +
118   -// " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n" +
119   -// " <title>下载</title>\n" +
120   -// "</head>\n" +
121   -// "<body>\n" +
122   -// " <a id=\"download\" download />\n" +
123   -// " <script>\n" +
124   -// " (function(){\n" +
125   -// " let searchParams = new URLSearchParams(location.search);\n" +
126   -// " var download = document.getElementById(\"download\");\n" +
127   -// " download.setAttribute(\"href\", searchParams.get(\"url\"))\n" +
128   -// " download.click()\n" +
129   -// " setTimeout(()=>{\n" +
130   -// " window.location.href=\"about:blank\";\n" +
131   -// "\t\t\t window.close();\n" +
132   -// " },200)\n" +
133   -// " })();\n" +
134   -// " \n" +
135   -// " </script>\n" +
136   -// "</body>\n" +
137   -// "</html>";
138   -// fs.write(content.getBytes(StandardCharsets.UTF_8));
139   -// logger.info("已写入html配置页面: " + file.getAbsolutePath());
140   -// } catch (FileNotFoundException e) {
141   -// logger.error("写入html页面错误", e);
142   -// } catch (IOException e) {
143   -// logger.error("写入html页面错误", e);
144   -// }
145   -//
146   -//
147   -// }
148   -}
src/main/java/top/panll/assist/config/WebMvcConfig.java
... ... @@ -17,7 +17,7 @@ public class WebMvcConfig extends WebMvcConfigurerAdapter {
17 17  
18 18 @Override
19 19 public void addResourceHandlers(ResourceHandlerRegistry registry) {
20   - File file = new File(userSettings.getRecord());
  20 + File file = new File(userSettings.getRecordTempPath());
21 21 registry.addResourceHandler("/download/**").addResourceLocations("file://" + file.getAbsolutePath() + "/");
22 22 super.addResourceHandlers(registry);
23 23 }
... ...
src/main/java/top/panll/assist/controller/DownController.java deleted 100755 → 0
1   -package top.panll.assist.controller;
2   -
3   -
4   -import org.apache.catalina.connector.ClientAbortException;
5   -import org.mp4parser.BasicContainer;
6   -import org.mp4parser.Container;
7   -import org.mp4parser.muxer.Movie;
8   -import org.mp4parser.muxer.Track;
9   -import org.mp4parser.muxer.builder.DefaultMp4Builder;
10   -import org.mp4parser.muxer.builder.Mp4Builder;
11   -import org.mp4parser.muxer.container.mp4.MovieCreator;
12   -import org.mp4parser.muxer.tracks.AppendTrack;
13   -import org.slf4j.Logger;
14   -import org.slf4j.LoggerFactory;
15   -import org.springframework.beans.factory.annotation.Autowired;
16   -import org.springframework.stereotype.Controller;
17   -import org.springframework.web.bind.annotation.GetMapping;
18   -import org.springframework.web.bind.annotation.RequestMapping;
19   -import org.springframework.web.bind.annotation.ResponseBody;
20   -import top.panll.assist.dto.UserSettings;
21   -
22   -import javax.servlet.http.HttpServletRequest;
23   -import javax.servlet.http.HttpServletResponse;
24   -import java.io.*;
25   -import java.nio.channels.Channels;
26   -import java.nio.channels.WritableByteChannel;
27   -import java.nio.charset.StandardCharsets;
28   -import java.util.ArrayList;
29   -import java.util.LinkedList;
30   -import java.util.List;
31   -
32   -@Controller
33   -@RequestMapping("/down")
34   -public class DownController {
35   -
36   - private final static Logger logger = LoggerFactory.getLogger(DownController.class);
37   -
38   - @Autowired
39   - private UserSettings userSettings;
40   -
41   - /**
42   - * 获取app+stream列表
43   - *
44   - * @return
45   - */
46   - @GetMapping(value = "/**")
47   - @ResponseBody
48   - public void download(HttpServletRequest request, HttpServletResponse response) throws IOException {
49   -
50   - List<String> videoList = new ArrayList<>();
51   - videoList.add("/home/lin/server/test/zlm/Debug/www/record/rtp/34020000002000000003_34020000001310000001/2023-03-20/16-09-07.mp4");
52   - videoList.add("/home/lin/server/test/zlm/Debug/www/record/rtp/34020000002000000003_34020000001310000001/2023-03-20/17-12-10.mp4");
53   - videoList.add("/home/lin/server/test/zlm/Debug/www/record/rtp/34020000002000000003_34020000001310000001/2023-03-20/17-38-36.mp4");
54   - List<Movie> sourceMovies = new ArrayList<>();
55   - for (String video : videoList) {
56   - sourceMovies.add(MovieCreator.build(video));
57   - }
58   -
59   - List<Track> videoTracks = new LinkedList<>();
60   - List<Track> audioTracks = new LinkedList<>();
61   - for (Movie movie : sourceMovies) {
62   - for (Track track : movie.getTracks()) {
63   - if ("soun".equals(track.getHandler())) {
64   - audioTracks.add(track);
65   - }
66   -
67   - if ("vide".equals(track.getHandler())) {
68   - videoTracks.add(track);
69   - }
70   - }
71   - }
72   - Movie mergeMovie = new Movie();
73   - if (audioTracks.size() > 0) {
74   - mergeMovie.addTrack(new AppendTrack(audioTracks.toArray(new Track[audioTracks.size()])));
75   - }
76   -
77   - if (videoTracks.size() > 0) {
78   - mergeMovie.addTrack(new AppendTrack(videoTracks.toArray(new Track[videoTracks.size()])));
79   - }
80   -
81   - BasicContainer out = (BasicContainer)new DefaultMp4Builder().build(mergeMovie);
82   -
83   - // 文件名
84   - String fileName = "测试.mp4";
85   - // 文件类型
86   - String contentType = request.getServletContext().getMimeType(fileName);
87   -
88   - // 解决下载文件时文件名乱码问题
89   - byte[] fileNameBytes = fileName.getBytes(StandardCharsets.UTF_8);
90   - fileName = new String(fileNameBytes, 0, fileNameBytes.length, StandardCharsets.ISO_8859_1);
91   -
92   - response.setHeader("Content-Type", contentType);
93   - response.setHeader("Content-Length", String.valueOf(out));
94   - //inline表示浏览器直接使用,attachment表示下载,fileName表示下载的文件名
95   - response.setHeader("Content-Disposition", "inline;filename=" + fileName);
96   - response.setContentType(contentType);
97   -
98   - WritableByteChannel writableByteChannel = Channels.newChannel(response.getOutputStream());
99   - out.writeContainer(writableByteChannel);
100   - writableByteChannel.close();
101   -
102   - }
103   -}
src/main/java/top/panll/assist/controller/RecordController.java
1 1 package top.panll.assist.controller;
2 2  
3   -import com.alibaba.fastjson.JSON;
4   -import com.alibaba.fastjson.JSONObject;
5 3 import io.swagger.v3.oas.annotations.Operation;
6 4 import io.swagger.v3.oas.annotations.Parameter;
7 5 import io.swagger.v3.oas.annotations.tags.Tag;
8   -import org.apache.commons.io.FileUtils;
9 6 import org.slf4j.Logger;
10 7 import org.slf4j.LoggerFactory;
11 8 import org.springframework.beans.factory.annotation.Autowired;
12   -import org.springframework.http.HttpStatus;
13   -import org.springframework.http.ResponseEntity;
14 9 import org.springframework.web.bind.annotation.*;
15   -import top.panll.assist.controller.bean.ControllerException;
16   -import top.panll.assist.controller.bean.ErrorCode;
17   -import top.panll.assist.controller.bean.RecordFile;
18   -import top.panll.assist.controller.bean.WVPResult;
  10 +import top.panll.assist.controller.bean.*;
19 11 import top.panll.assist.dto.*;
20 12 import top.panll.assist.service.VideoFileService;
21   -import top.panll.assist.utils.PageInfo;
22 13 import top.panll.assist.utils.RedisUtil;
23 14  
24   -import javax.servlet.http.HttpServletRequest;
25   -import java.io.File;
26   -import java.text.DateFormat;
27   -import java.text.ParseException;
28 15 import java.text.SimpleDateFormat;
29 16 import java.util.*;
30 17  
... ... @@ -58,233 +45,19 @@ public class RecordController {
58 45 return userSettings;
59 46 }
60 47  
61   - /**
62   - * 获取app+stream列表
63   - * @return
64   - */
65   - @Operation(summary ="分页获取app+stream的列表")
66   - @Parameter(name = "page", description = "当前页", required = true)
67   - @Parameter(name = "count", description = "每页查询数量", required = true)
68   - @GetMapping(value = "/list")
69   - @ResponseBody
70   - public PageInfo<Map<String, String>> getList(@RequestParam int page,
71   - @RequestParam int count){
72   - List<Map<String, String>> appList = videoFileService.getList();
73   -
74   - PageInfo<Map<String, String>> stringPageInfo = new PageInfo<>(appList);
75   - stringPageInfo.startPage(page, count);
76   - return stringPageInfo;
77   - }
78   -
79   - /**
80   - * 分页获取app列表
81   - * @return
82   - */
83   - @Operation(summary ="分页获取app列表")
84   - @Parameter(name = "page", description = "当前页", required = true)
85   - @Parameter(name = "count", description = "每页查询数量", required = true)
86   - @GetMapping(value = "/app/list")
87   - @ResponseBody
88   - public PageInfo<String> getAppList(@RequestParam int page,
89   - @RequestParam int count){
90   - List<String> resultData = new ArrayList<>();
91   - List<File> appList = videoFileService.getAppList(true);
92   - if (appList.size() > 0) {
93   - for (File file : appList) {
94   - resultData.add(file.getName());
95   - }
96   - }
97   - Collections.sort(resultData);
98   -
99   - PageInfo<String> stringPageInfo = new PageInfo<>(resultData);
100   - stringPageInfo.startPage(page, count);
101   - return stringPageInfo;
102   - }
103   -
104   - /**
105   - * 分页stream列表
106   - * @return
107   - */
108   - @Operation(summary ="分页stream列表")
109   - @Parameter(name = "page", description = "当前页", required = true)
110   - @Parameter(name = "count", description = "每页查询数量", required = true)
111   - @Parameter(name = "app", description = "应用名", required = true)
112   - @GetMapping(value = "/stream/list")
113   - @ResponseBody
114   - public PageInfo<String> getStreamList(@RequestParam int page,
115   - @RequestParam int count,
116   - @RequestParam String app ){
117   - List<String> resultData = new ArrayList<>();
118   - if (app == null) {
119   - throw new ControllerException(ErrorCode.ERROR400.getCode(), "app不能为空");
120   - }
121   - List<File> streamList = videoFileService.getStreamList(app, true);
122   - if (streamList != null) {
123   - for (File file : streamList) {
124   - resultData.add(file.getName());
125   - }
126   - }
127   - PageInfo<String> stringPageInfo = new PageInfo<>(resultData);
128   - stringPageInfo.startPage(page, count);
129   - return stringPageInfo;
130   - }
131   -
132   - /**
133   - * 获取日期文件夹列表
134   - * @return
135   - */
136   - @Operation(summary ="获取日期文件夹列表")
137   - @Parameter(name = "year", description = "月", required = true)
138   - @Parameter(name = "month", description = "年", required = true)
139   - @Parameter(name = "app", description = "应用名", required = true)
140   - @Parameter(name = "stream", description = "流ID", required = true)
141   - @GetMapping(value = "/date/list")
142   - @ResponseBody
143   - public List<String> getDateList( @RequestParam(required = false) Integer year,
144   - @RequestParam(required = false) Integer month,
145   - @RequestParam String app,
146   - @RequestParam String stream ){
147   - List<String> resultData = new ArrayList<>();
148   - if (app == null) {
149   - throw new ControllerException(ErrorCode.ERROR400.getCode(), "app不能为空");
150   - };
151   - if (stream == null) {
152   - throw new ControllerException(ErrorCode.ERROR400.getCode(), "stream不能为空");
153   - }
154   - List<File> dateList = videoFileService.getDateList(app, stream, year, month, true);
155   - for (File file : dateList) {
156   - resultData.add(file.getName());
157   - }
158   - return resultData;
159   - }
160   -
161   - /**
162   - * 获取视频文件列表
163   - * @return
164   - */
165   - @Operation(summary ="获取视频文件列表")
166   - @Parameter(name = "page", description = "当前页", required = true)
167   - @Parameter(name = "count", description = "每页查询数量", required = true)
168   - @Parameter(name = "app", description = "应用名", required = true)
169   - @Parameter(name = "stream", description = "流ID", required = true)
170   - @Parameter(name = "startTime", description = "开始时间(yyyy-MM-dd HH:mm:ss)", required = true)
171   - @Parameter(name = "endTime", description = "结束时间(yyyy-MM-dd HH:mm:ss)", required = true)
172   - @GetMapping(value = "/file/list")
173   - @ResponseBody
174   - public PageInfo<String> getRecordList(@RequestParam int page,
175   - @RequestParam int count,
176   - @RequestParam String app,
177   - @RequestParam String stream,
178   - @RequestParam(required = false) String startTime,
179   - @RequestParam(required = false) String endTime
180   - ){
181   -
182   - // 开始时间与结束时间可不传或只传其一
183   - List<String> recordList = new ArrayList<>();
184   - try {
185   - Date startTimeDate = null;
186   - Date endTimeDate = null;
187   - if (startTime != null ) {
188   - startTimeDate = formatter.parse(startTime);
189   - }
190   - if (endTime != null ) {
191   - endTimeDate = formatter.parse(endTime);
192   - }
193   -
194   - List<File> filesInTime = videoFileService.getFilesInTime(app, stream, startTimeDate, endTimeDate);
195   - if (filesInTime != null && filesInTime.size() > 0) {
196   - for (File file : filesInTime) {
197   - recordList.add(file.getName());
198   - }
199   - }
200   - PageInfo<String> stringPageInfo = new PageInfo<>(recordList);
201   - stringPageInfo.startPage(page, count);
202   - return stringPageInfo;
203   - } catch (ParseException e) {
204   - logger.error("错误的开始时间[{}]或结束时间[{}]", startTime, endTime);
205   - throw new ControllerException(ErrorCode.ERROR400.getCode(), "错误的开始时间或结束时间, e=" + e.getMessage());
206   - }
207   - }
208   -
209   - /**
210   - * 获取视频文件列表
211   - * @return
212   - */
213   - @Operation(summary ="获取视频文件列表")
214   - @Parameter(name = "page", description = "当前页", required = true)
215   - @Parameter(name = "count", description = "每页查询数量", required = true)
216   - @Parameter(name = "app", description = "应用名", required = true)
217   - @Parameter(name = "stream", description = "流ID", required = true)
218   - @Parameter(name = "startTime", description = "开始时间(yyyy-MM-dd HH:mm:ss)", required = true)
219   - @Parameter(name = "endTime", description = "结束时间(yyyy-MM-dd HH:mm:ss)", required = true)
220   - @GetMapping(value = "/file/listWithDate")
221   - @ResponseBody
222   - public PageInfo<RecordFile> getRecordListWithDate(@RequestParam int page,
223   - @RequestParam int count,
224   - @RequestParam String app,
225   - @RequestParam String stream,
226   - @RequestParam(required = false) String startTime,
227   - @RequestParam(required = false) String endTime
228   - ){
229   -
230   - // 开始时间与结束时间可不传或只传其一
231   - List<RecordFile> recordList = new ArrayList<>();
232   - try {
233   - Date startTimeDate = null;
234   - Date endTimeDate = null;
235   - if (startTime != null ) {
236   - startTimeDate = formatter.parse(startTime);
237   - }
238   - if (endTime != null ) {
239   - endTimeDate = formatter.parse(endTime);
240   - }
241   -
242   - List<File> filesInTime = videoFileService.getFilesInTime(app, stream, startTimeDate, endTimeDate);
243   - if (filesInTime != null && filesInTime.size() > 0) {
244   - for (File file : filesInTime) {
245   - recordList.add(RecordFile.instance(app, stream, file.getName(), file.getParentFile().getName()));
246   - }
247   - }
248   - PageInfo<RecordFile> stringPageInfo = new PageInfo<>(recordList);
249   - stringPageInfo.startPage(page, count);
250   - return stringPageInfo;
251   - } catch (ParseException e) {
252   - logger.error("错误的开始时间[{}]或结束时间[{}]", startTime, endTime);
253   - throw new ControllerException(ErrorCode.ERROR400.getCode(), "错误的开始时间或结束时间, e=" + e.getMessage());
254   - }
255   - }
256   -
257 48  
258 49 /**
259 50 * 添加视频裁剪合并任务
260 51 */
261 52 @Operation(summary ="添加视频裁剪合并任务")
262   - @Parameter(name = "app", description = "应用名", required = true)
263   - @Parameter(name = "stream", description = "流ID", required = true)
264   - @Parameter(name = "startTime", description = "开始时间(yyyy-MM-dd HH:mm:ss)", required = true)
265   - @Parameter(name = "endTime", description = "结束时间(yyyy-MM-dd HH:mm:ss)", required = true)
266   - @Parameter(name = "remoteHost", description = "服务的IP:端口(用于直接返回完整播放地址以及下载地址)", required = true)
267   - @GetMapping(value = "/file/download/task/add")
  53 + @Parameter(name = "videoTaskInfo", description = "视频合并任务的信息", required = true)
  54 + @PostMapping(value = "/file/download/task/add")
268 55 @ResponseBody
269   - public String addTaskForDownload(@RequestParam String app,
270   - @RequestParam String stream,
271   - @RequestParam(required = false) String startTime,
272   - @RequestParam(required = false) String endTime,
273   - @RequestParam(required = false) String remoteHost
274   - ){
275   - Date startTimeDate = null;
276   - Date endTimeDate = null;
277   - try {
278   - if (startTime != null ) {
279   - startTimeDate = formatter.parse(startTime);
280   - }
281   - if (endTime != null ) {
282   - endTimeDate = formatter.parse(endTime);
283   - }
284   - } catch (ParseException e) {
285   - e.printStackTrace();
  56 + public String addTaskForDownload(@RequestBody VideoTaskInfo videoTaskInfo ){
  57 + if (videoTaskInfo.getFilePathList() == null || videoTaskInfo.getFilePathList().isEmpty()) {
  58 + throw new ControllerException(ErrorCode.ERROR100.getCode(), "视频文件列表不可为空");
286 59 }
287   - String id = videoFileService.mergeOrCut(app, stream, startTimeDate, endTimeDate, remoteHost);
  60 + String id = videoFileService.mergeOrCut(videoTaskInfo);
288 61 if (id== null) {
289 62 throw new ControllerException(ErrorCode.ERROR100.getCode(), "可能未找到视频文件");
290 63 }
... ... @@ -295,8 +68,6 @@ public class RecordController {
295 68 * 查询视频裁剪合并任务列表
296 69 */
297 70 @Operation(summary ="查询视频裁剪合并任务列表")
298   - @Parameter(name = "app", description = "应用名", required = true)
299   - @Parameter(name = "stream", description = "流ID", required = true)
300 71 @Parameter(name = "taskId", description = "任务ID", required = true)
301 72 @Parameter(name = "isEnd", description = "是否结束", required = true)
302 73 @GetMapping(value = "/file/download/task/list")
... ... @@ -304,9 +75,10 @@ public class RecordController {
304 75 public List<MergeOrCutTaskInfo> getTaskListForDownload(
305 76 @RequestParam(required = false) String app,
306 77 @RequestParam(required = false) String stream,
  78 + @RequestParam(required = false) String callId,
307 79 @RequestParam(required = false) String taskId,
308 80 @RequestParam(required = false) Boolean isEnd){
309   - List<MergeOrCutTaskInfo> taskList = videoFileService.getTaskListForDownload(isEnd, app, stream, taskId);
  81 + List<MergeOrCutTaskInfo> taskList = videoFileService.getTaskListForDownload(app, stream, callId, isEnd, taskId);
310 82 if (taskList == null) {
311 83 throw new ControllerException(ErrorCode.ERROR100);
312 84 }
... ... @@ -314,64 +86,6 @@ public class RecordController {
314 86 }
315 87  
316 88 /**
317   - * 收藏录像(被收藏的录像不会被清理任务清理)
318   - */
319   - @Operation(summary ="收藏录像(被收藏的录像不会被清理任务清理)")
320   - @Parameter(name = "type", description = "类型", required = true)
321   - @Parameter(name = "app", description = "应用名", required = true)
322   - @Parameter(name = "stream", description = "流ID", required = true)
323   - @GetMapping(value = "/file/collection/add")
324   - @ResponseBody
325   - public void collection(
326   - @RequestParam(required = true) String type,
327   - @RequestParam(required = true) String app,
328   - @RequestParam(required = true) String stream){
329   -
330   - boolean collectionResult = videoFileService.collection(app, stream, type);
331   - if (!collectionResult) {
332   - throw new ControllerException(ErrorCode.ERROR100);
333   - }
334   - }
335   -
336   - /**
337   - * 移除收藏录像
338   - */
339   - @Operation(summary ="移除收藏录像")
340   - @Parameter(name = "type", description = "类型", required = true)
341   - @Parameter(name = "app", description = "应用名", required = true)
342   - @Parameter(name = "stream", description = "流ID", required = true)
343   - @GetMapping(value = "/file/collection/remove")
344   - @ResponseBody
345   - public void removeCollection(
346   - @RequestParam(required = true) String type,
347   - @RequestParam(required = true) String app,
348   - @RequestParam(required = true) String stream){
349   -
350   - boolean collectionResult = videoFileService.removeCollection(app, stream, type);
351   - if (!collectionResult) {
352   - throw new ControllerException(ErrorCode.ERROR100);
353   - }
354   - }
355   -
356   - /**
357   - * 收藏录像列表
358   - */
359   - @Operation(summary ="收藏录像列表")
360   - @Parameter(name = "type", description = "类型", required = false)
361   - @Parameter(name = "app", description = "应用名", required = false)
362   - @Parameter(name = "stream", description = "流ID", required = false)
363   - @GetMapping(value = "/file/collection/list")
364   - @ResponseBody
365   - public List<SignInfo> collectionList(
366   - @RequestParam(required = false) String type,
367   - @RequestParam(required = false) String app,
368   - @RequestParam(required = false) String stream){
369   -
370   - List<SignInfo> signInfos = videoFileService.getCollectionList(app, stream, type);
371   - return signInfos;
372   - }
373   -
374   - /**
375 89 * 中止视频裁剪合并任务列表
376 90 */
377 91 @Operation(summary ="中止视频裁剪合并任务列表(暂不支持)")
... ... @@ -391,35 +105,6 @@ public class RecordController {
391 105 }
392 106  
393 107 /**
394   - * 录制完成的通知, 对用zlm的hook
395   - * @return
396   - */
397   - @ResponseBody
398   - @PostMapping(value = "/on_record_mp4", produces = "application/json;charset=UTF-8")
399   - public ResponseEntity<String> onRecordMp4(@RequestBody JSONObject json) {
400   - JSONObject ret = new JSONObject();
401   - ret.put("code", 0);
402   - ret.put("msg", "success");
403   - String file_path = json.getString("file_path");
404   -
405   - String app = json.getString("app");
406   - String stream = json.getString("stream");
407   - logger.debug("ZLM 录制完成,文件路径:" + file_path);
408   -
409   - if (file_path == null) {
410   - return new ResponseEntity<String>(ret.toString(), HttpStatus.OK);
411   - }
412   - if (userSettings.getRecordDay() <= 0) {
413   - logger.info("录像保存事件为{}天,直接删除: {}", userSettings.getRecordDay(), file_path);
414   - FileUtils.deleteQuietly(new File(file_path));
415   - }else {
416   - videoFileService.handFile(new File(file_path), app, stream);
417   - }
418   -
419   - return new ResponseEntity<String>(ret.toString(), HttpStatus.OK);
420   - }
421   -
422   - /**
423 108 * 磁盘空间查询
424 109 */
425 110 @Operation(summary ="磁盘空间查询")
... ... @@ -430,21 +115,6 @@ public class RecordController {
430 115 }
431 116  
432 117 /**
433   - * 增加推流的鉴权信息,用于录像存储使用
434   - */
435   - @Operation(summary ="增加推流的鉴权信息")
436   - @Parameter(name = "app", description = "应用名", required = true)
437   - @Parameter(name = "stream", description = "流ID", required = true)
438   - @Parameter(name = "callId", description = "录像自鉴权ID", required = true)
439   - @ResponseBody
440   - @GetMapping(value = "/addStreamCallInfo", produces = "application/json;charset=UTF-8")
441   - @PostMapping(value = "/addStreamCallInfo", produces = "application/json;charset=UTF-8")
442   - public void addStreamCallInfo(String app, String stream, String callId) {
443   - String key = AssistConstants.STREAM_CALL_INFO + userSettings.getId() + "_" + app + "_" + stream;
444   - redisUtil.set(key, callId, -1);
445   - }
446   -
447   - /**
448 118 * 录像文件的时长
449 119 */
450 120 @Operation(summary ="录像文件的时长")
... ...
src/main/java/top/panll/assist/controller/bean/FileLIstInfo.java 0 → 100644
  1 +package top.panll.assist.controller.bean;
  2 +
  3 +import java.util.List;
  4 +
  5 +public class FileLIstInfo {
  6 +
  7 + private List<String> filePathList;
  8 +
  9 + public List<String> getFilePathList() {
  10 + return filePathList;
  11 + }
  12 +
  13 + public void setFilePathList(List<String> filePathList) {
  14 + this.filePathList = filePathList;
  15 + }
  16 +}
... ...
src/main/java/top/panll/assist/dto/MergeOrCutTaskInfo.java
... ... @@ -3,12 +3,7 @@ package top.panll.assist.dto;
3 3  
4 4 public class MergeOrCutTaskInfo {
5 5 private String id;
6   - private String app;
7   - private String stream;
8   - private String startTime;
9   - private String endTime;
10 6 private String createTime;
11   -
12 7 private String percentage;
13 8  
14 9 private String recordFile;
... ... @@ -17,6 +12,12 @@ public class MergeOrCutTaskInfo {
17 12  
18 13 private String playFile;
19 14  
  15 + private String app;
  16 + private String stream;
  17 + private String startTime;
  18 + private String endTime;
  19 + private String callId;
  20 +
20 21 public String getId() {
21 22 return id;
22 23 }
... ... @@ -25,38 +26,6 @@ public class MergeOrCutTaskInfo {
25 26 this.id = id;
26 27 }
27 28  
28   - public String getStartTime() {
29   - return startTime;
30   - }
31   -
32   - public void setStartTime(String startTime) {
33   - this.startTime = startTime;
34   - }
35   -
36   - public String getEndTime() {
37   - return endTime;
38   - }
39   -
40   - public void setEndTime(String endTime) {
41   - this.endTime = endTime;
42   - }
43   -
44   - public String getApp() {
45   - return app;
46   - }
47   -
48   - public void setApp(String app) {
49   - this.app = app;
50   - }
51   -
52   - public String getStream() {
53   - return stream;
54   - }
55   -
56   - public void setStream(String stream) {
57   - this.stream = stream;
58   - }
59   -
60 29 public String getPercentage() {
61 30 return percentage;
62 31 }
... ... @@ -96,4 +65,44 @@ public class MergeOrCutTaskInfo {
96 65 public void setCreateTime(String createTime) {
97 66 this.createTime = createTime;
98 67 }
  68 +
  69 + public String getApp() {
  70 + return app;
  71 + }
  72 +
  73 + public void setApp(String app) {
  74 + this.app = app;
  75 + }
  76 +
  77 + public String getStream() {
  78 + return stream;
  79 + }
  80 +
  81 + public void setStream(String stream) {
  82 + this.stream = stream;
  83 + }
  84 +
  85 + public String getStartTime() {
  86 + return startTime;
  87 + }
  88 +
  89 + public void setStartTime(String startTime) {
  90 + this.startTime = startTime;
  91 + }
  92 +
  93 + public String getEndTime() {
  94 + return endTime;
  95 + }
  96 +
  97 + public void setEndTime(String endTime) {
  98 + this.endTime = endTime;
  99 + }
  100 +
  101 + public String getCallId() {
  102 + return callId;
  103 + }
  104 +
  105 + public void setCallId(String callId) {
  106 + this.callId = callId;
  107 + }
99 108 }
... ...
src/main/java/top/panll/assist/dto/UserSettings.java
... ... @@ -12,13 +12,10 @@ public class UserSettings {
12 12 @Value("${userSettings.id}")
13 13 private String id;
14 14  
15   - @Value("${userSettings.record}")
16   - private String record;
  15 + @Value("${userSettings.record-temp:./recordTemp}")
  16 + private String recordTempPath;
17 17  
18   - @Value("${userSettings.recordDay:7}")
19   - private int recordDay;
20   -
21   - @Value("${userSettings.recordTempDay:-1}")
  18 + @Value("${userSettings.record-temp-day:7}")
22 19 private int recordTempDay;
23 20  
24 21 @Value("${userSettings.ffmpeg}")
... ... @@ -38,14 +35,6 @@ public class UserSettings {
38 35 this.id = id;
39 36 }
40 37  
41   - public String getRecord() {
42   - return record;
43   - }
44   -
45   - public void setRecord(String record) {
46   - this.record = record;
47   - }
48   -
49 38 public String getFfmpeg() {
50 39 return ffmpeg;
51 40 }
... ... @@ -62,20 +51,9 @@ public class UserSettings {
62 51 this.ffprobe = ffprobe;
63 52 }
64 53  
65   - public int getRecordDay() {
66   - return recordDay;
67   - }
68   -
69   - public void setRecordDay(int recordDay) {
70   - this.recordDay = recordDay;
71   - }
72 54  
73 55 public int getRecordTempDay() {
74   - if (recordTempDay == -1) {
75   - return recordDay;
76   - }else {
77   - return recordTempDay;
78   - }
  56 + return recordTempDay;
79 57 }
80 58  
81 59 public void setRecordTempDay(int recordTempDay) {
... ... @@ -89,4 +67,12 @@ public class UserSettings {
89 67 public void setThreads(int threads) {
90 68 this.threads = threads;
91 69 }
  70 +
  71 + public String getRecordTempPath() {
  72 + return recordTempPath;
  73 + }
  74 +
  75 + public void setRecordTempPath(String recordTempPath) {
  76 + this.recordTempPath = recordTempPath;
  77 + }
92 78 }
... ...
src/main/java/top/panll/assist/dto/VideoTaskInfo.java 0 → 100644
  1 +package top.panll.assist.dto;
  2 +
  3 +import io.swagger.v3.oas.annotations.Parameter;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +
  6 +import java.util.List;
  7 +
  8 +@Schema(description = "视频合并任务的信息")
  9 +public class VideoTaskInfo {
  10 +
  11 + private String app;
  12 + private String stream;
  13 + private String startTime;
  14 + private String endTime;
  15 + private String callId;
  16 +
  17 +
  18 + @Schema(description = "视频文件路径列表")
  19 + private List<String> filePathList;
  20 +
  21 + @Schema(description = "返回地址时的远程地址")
  22 + private String remoteHost;
  23 +
  24 + public List<String> getFilePathList() {
  25 + return filePathList;
  26 + }
  27 +
  28 + public void setFilePathList(List<String> filePathList) {
  29 + this.filePathList = filePathList;
  30 + }
  31 +
  32 + public String getRemoteHost() {
  33 + return remoteHost;
  34 + }
  35 +
  36 + public void setRemoteHost(String remoteHost) {
  37 + this.remoteHost = remoteHost;
  38 + }
  39 +
  40 + public String getApp() {
  41 + return app;
  42 + }
  43 +
  44 + public void setApp(String app) {
  45 + this.app = app;
  46 + }
  47 +
  48 + public String getStream() {
  49 + return stream;
  50 + }
  51 +
  52 + public void setStream(String stream) {
  53 + this.stream = stream;
  54 + }
  55 +
  56 + public String getStartTime() {
  57 + return startTime;
  58 + }
  59 +
  60 + public void setStartTime(String startTime) {
  61 + this.startTime = startTime;
  62 + }
  63 +
  64 + public String getEndTime() {
  65 + return endTime;
  66 + }
  67 +
  68 + public void setEndTime(String endTime) {
  69 + this.endTime = endTime;
  70 + }
  71 +
  72 + public String getCallId() {
  73 + return callId;
  74 + }
  75 +
  76 + public void setCallId(String callId) {
  77 + this.callId = callId;
  78 + }
  79 +}
... ...
src/main/java/top/panll/assist/service/FFmpegExecUtils.java
... ... @@ -77,7 +77,7 @@ public class FFmpegExecUtils implements InitializingBean{
77 77 }
78 78  
79 79 @Async
80   - public void mergeOrCutFile(List<File> fils, File dest, String destFileName, VideoHandEndCallBack callBack){
  80 + public void mergeOrCutFile(List<File> fils, File dest, String destFileName, VideoHandEndCallBack callBack){
81 81  
82 82 if (fils == null || fils.size() == 0 || ffmpeg == null || ffprobe == null || dest== null || !dest.exists()){
83 83 callBack.run("error", 0.0, null);
... ... @@ -95,7 +95,7 @@ public class FFmpegExecUtils implements InitializingBean{
95 95 BufferedWriter bw =new BufferedWriter(new FileWriter(fileListName));
96 96 for (File file : fils) {
97 97 VideoFile videoFile = VideoFileFactory.createFile(this, file);
98   - if (videoFile == null || !videoFile.isTargetFormat()) {
  98 + if (videoFile == null) {
99 99 return;
100 100 }
101 101 bw.write("file " + file.getAbsolutePath());
... ... @@ -129,15 +129,15 @@ public class FFmpegExecUtils implements InitializingBean{
129 129 double percentage = progress.out_time_ns / duration_ns;
130 130  
131 131 // Print out interesting information about the progress
132   - System.out.println(String.format(
133   - "[%.0f%%] status:%s frame:%d time:%s ms fps:%.0f speed:%.2fx",
134   - percentage * 100,
135   - progress.status,
136   - progress.frame,
137   - FFmpegUtils.toTimecode(progress.out_time_ns, TimeUnit.NANOSECONDS),
138   - progress.fps.doubleValue(),
139   - progress.speed
140   - ));
  132 +// System.out.println(String.format(
  133 +// "[%.0f%%] status:%s frame:%d time:%s ms fps:%.0f speed:%.2fx",
  134 +// percentage * 100,
  135 +// progress.status,
  136 +// progress.frame,
  137 +// FFmpegUtils.toTimecode(progress.out_time_ns, TimeUnit.NANOSECONDS),
  138 +// progress.fps.doubleValue(),
  139 +// progress.speed
  140 +// ));
141 141  
142 142 if (progress.status.equals(Progress.Status.END)){
143 143 callBack.run(progress.status.name(), percentage, recordFileResultPath);
... ...
src/main/java/top/panll/assist/service/FileManagerTimer.java
... ... @@ -39,112 +39,47 @@ public class FileManagerTimer {
39 39 // @Scheduled(fixedDelay = 2000) //测试 20秒执行一次
40 40 @Scheduled(cron = "0 0 0 * * ?") //每天的0点执行
41 41 public void execute(){
42   - if (userSettings.getRecord() == null) {
  42 + if (userSettings.getRecordTempPath() == null) {
43 43 return;
44 44 }
45   - int recordDay = userSettings.getRecordDay();
46   - Date lastDate=new Date();
47   - Calendar lastCalendar = Calendar.getInstance();
48   - if (recordDay > 0) {
49   - lastCalendar.setTime(lastDate);
50   - lastCalendar.add(Calendar.DAY_OF_MONTH, 0 - recordDay);
51   - lastDate = lastCalendar.getTime();
52   - }
53 45  
54   - logger.info("[录像巡查]移除 {} 之前的文件", formatter.format(lastDate));
55   - File recordFileDir = new File(userSettings.getRecord());
56   - if (recordFileDir.canWrite()) {
57   - List<File> appList = videoFileService.getAppList(false);
58   - if (appList != null && appList.size() > 0) {
59   - for (File appFile : appList) {
60   - if ("download.html".equals(appFile.getName())) {
61   - continue;
62   - }
63   - List<File> streamList = videoFileService.getStreamList(appFile, false);
64   - if (streamList != null && streamList.size() > 0) {
65   - for (File streamFile : streamList) {
66   - // 带有sig标记文件的为收藏文件,不被自动清理任务移除
67   - File[] signFiles = streamFile.listFiles((File dir, String name) -> {
68   - File currentFile = new File(dir.getAbsolutePath() + File.separator + name);
69   - return currentFile.isFile() && name.endsWith(".sign");
70   - });
71   - if (signFiles != null && signFiles.length > 0) {
72   - continue;
73   - }
74   - List<File> dateList = videoFileService.getDateList(streamFile, null, null, false);
75   - if (dateList != null && dateList.size() > 0) {
76   - for (File dateFile : dateList) {
77   - try {
78   - Date parse = formatter.parse(dateFile.getName());
79   - if (parse.before(lastDate)) {
80   - boolean result = FileUtils.deleteQuietly(dateFile);
81   - if (result) {
82   - logger.info("[录像巡查]成功移除 {} ", dateFile.getAbsolutePath());
83   - }else {
84   - logger.info("[录像巡查]移除失败 {} ", dateFile.getAbsolutePath());
85   - }
86   - }
87   - } catch (ParseException e) {
88   - e.printStackTrace();
89   - }
90   - }
91   - }
92   - if (streamFile.listFiles() == null || streamFile.listFiles().length == 0) {
93   - boolean result = FileUtils.deleteQuietly(streamFile);
94   - if (result) {
95   - logger.info("[录像巡查]成功移除 {} ", streamFile.getAbsolutePath());
96   - }else {
97   - logger.info("[录像巡查]移除失败 {} ", streamFile.getAbsolutePath());
98   - }
99   - }
100   - }
101   - }
102   - if (appFile.listFiles() == null || appFile.listFiles().length == 0) {
103   - boolean result = FileUtils.deleteQuietly(appFile);
104   - if (result) {
105   - logger.info("[录像巡查]成功移除 {} ", appFile.getAbsolutePath());
106   - }else {
107   - logger.info("[录像巡查]移除失败 {} ", appFile.getAbsolutePath());
108   - }
109   - }
110   - }
111   - }
112   - }
113 46 // 清理任务临时文件
114 47 int recordTempDay = userSettings.getRecordTempDay();
115 48 Date lastTempDate = new Date();
116 49 Calendar lastTempCalendar = Calendar.getInstance();
117 50 lastTempCalendar.setTime(lastTempDate);
118   - lastTempCalendar.add(Calendar.DAY_OF_MONTH, 0 - recordTempDay);
  51 + lastTempCalendar.add(Calendar.DAY_OF_MONTH, -recordTempDay);
119 52 lastTempDate = lastTempCalendar.getTime();
120 53 logger.info("[录像巡查]移除合并任务临时文件 {} 之前的文件", formatter.format(lastTempDate));
121   - File recordTempFile = new File(userSettings.getRecord() + "recordTemp");
  54 + File recordTempFile = new File(userSettings.getRecordTempPath());
122 55 if (recordTempFile.exists() && recordTempFile.isDirectory() && recordTempFile.canWrite()) {
123 56 File[] tempFiles = recordTempFile.listFiles();
124   - for (File tempFile : tempFiles) {
125   - if (tempFile.isDirectory() && new Date(tempFile.lastModified()).before(lastTempDate)) {
126   - boolean result = FileUtils.deleteQuietly(tempFile);
127   - if (result) {
128   - logger.info("[录像巡查]成功移除合并任务临时文件 {} ", tempFile.getAbsolutePath());
129   - }else {
130   - logger.info("[录像巡查]合并任务临时文件移除失败 {} ", tempFile.getAbsolutePath());
  57 + if (tempFiles != null) {
  58 + for (File tempFile : tempFiles) {
  59 + if (tempFile.isFile() && tempFile.lastModified() < lastTempDate.getTime()) {
  60 + boolean result = FileUtils.deleteQuietly(tempFile);
  61 + if (result) {
  62 + logger.info("[录像巡查]成功移除合并任务临时文件 {} ", tempFile.getAbsolutePath());
  63 + }else {
  64 + logger.info("[录像巡查]合并任务临时文件移除失败 {} ", tempFile.getAbsolutePath());
  65 + }
131 66 }
132 67 }
133 68 }
134 69 }
135 70 // 清理redis记录
136   - String key = String.format("%S_%S_*_*_*", AssistConstants.MERGEORCUT, userSettings.getId());
  71 + String key = String.format("%S_%S_*", AssistConstants.MERGEORCUT, userSettings.getId());
137 72 List<Object> taskKeys = redisUtil.scan(key);
138 73 for (Object taskKeyObj : taskKeys) {
139 74 String taskKey = (String) taskKeyObj;
140 75 MergeOrCutTaskInfo mergeOrCutTaskInfo = (MergeOrCutTaskInfo)redisUtil.get(taskKey);
141 76 try {
142   - if (StringUtils.isEmpty(mergeOrCutTaskInfo.getCreateTime())
  77 + if (StringUtils.hasLength(mergeOrCutTaskInfo.getCreateTime())
143 78 || simpleDateFormatForTime.parse(mergeOrCutTaskInfo.getCreateTime()).before(lastTempDate)) {
144 79 redisUtil.del(taskKey);
145 80 }
146 81 } catch (ParseException e) {
147   - e.printStackTrace();
  82 + logger.error("[清理过期的redis合并任务信息] 失败", e);
148 83 }
149 84 }
150 85 }
... ...
src/main/java/top/panll/assist/service/VideoFileService.java
... ... @@ -10,6 +10,9 @@ import org.springframework.beans.factory.annotation.Autowired;
10 10 import org.springframework.data.redis.core.RedisTemplate;
11 11 import org.springframework.stereotype.Service;
12 12 import org.springframework.util.DigestUtils;
  13 +import org.springframework.util.ObjectUtils;
  14 +import top.panll.assist.controller.bean.ControllerException;
  15 +import top.panll.assist.controller.bean.ErrorCode;
13 16 import top.panll.assist.dto.*;
14 17 import top.panll.assist.utils.RedisUtil;
15 18 import top.panll.assist.utils.DateUtils;
... ... @@ -44,7 +47,7 @@ public class VideoFileService {
44 47 private final SimpleDateFormat simpleDateFormatForTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
45 48  
46 49 public List<File> getAppList(Boolean sort) {
47   - File recordFile = new File(userSettings.getRecord());
  50 + File recordFile = new File(userSettings.getRecordTempPath());
48 51 if (recordFile.isDirectory()) {
49 52 File[] files = recordFile.listFiles((File dir, String name) -> {
50 53 File currentFile = new File(dir.getAbsolutePath() + File.separator + name);
... ... @@ -61,17 +64,13 @@ public class VideoFileService {
61 64 }
62 65  
63 66 public SpaceInfo getSpaceInfo(){
64   - File recordFile = new File(userSettings.getRecord());
  67 + File recordFile = new File(userSettings.getRecordTempPath());
65 68 SpaceInfo spaceInfo = new SpaceInfo();
66 69 spaceInfo.setFree(recordFile.getFreeSpace());
67 70 spaceInfo.setTotal(recordFile.getTotalSpace());
68 71 return spaceInfo;
69 72 }
70 73  
71   - public List<File> getStreamList(String app, Boolean sort) {
72   - File appFile = new File(userSettings.getRecord() + File.separator + app);
73   - return getStreamList(appFile, sort);
74   - }
75 74  
76 75 public List<File> getStreamList(File appFile, Boolean sort) {
77 76 if (appFile != null && appFile.isDirectory()) {
... ... @@ -90,79 +89,6 @@ public class VideoFileService {
90 89 }
91 90  
92 91 /**
93   - * 对视频文件重命名
94   - */
95   - public void handFile(File file,String app, String stream) {
96   - VideoFile videoFile = VideoFileFactory.createFile(ffmpegExecUtils, file);
97   - if (videoFile == null || videoFile.isTargetFormat()) {
98   - return;
99   - }
100   -
101   - SimpleDateFormat dateFormat = new SimpleDateFormat("HHmmss");
102   -
103   - String key = AssistConstants.STREAM_CALL_INFO + userSettings.getId() + "_" + app + "_" + stream;
104   - String callId = (String) redisUtil.get(key);
105   -
106   - String streamNew = (callId == null? stream : stream + "_" + callId);
107   - File newPath = new File(userSettings.getRecord() + File.separator + app + File.separator + streamNew
108   - + File.separator + DateUtils.getDateStr(videoFile.getStartTime()));
109   - if (!newPath.exists()) {
110   - newPath.mkdirs();
111   - }
112   -
113   - String newName = newPath.getAbsolutePath() + File.separator+ dateFormat.format(videoFile.getStartTime())
114   - + "-" + dateFormat.format(videoFile.getEndTime()) + ".mp4";
115   - logger.info("[处理文件] {}->{}", file.getAbsolutePath(), newName);
116   - boolean renameTo = file.renameTo(new File(newName));
117   - if (!renameTo) {
118   - logger.info("[处理文件]文件重命名失败 {}->{}", file.getAbsolutePath(), newName);
119   - }
120   - }
121   -
122   - public List<Map<String, String>> getList() {
123   -
124   - List<Map<String, String>> result = new ArrayList<>();
125   -
126   - List<File> appList = getAppList(true);
127   - if (appList != null && appList.size() > 0) {
128   - for (File appFile : appList) {
129   - if (appFile.isDirectory()) {
130   - List<File> streamList = getStreamList(appFile.getName(), true);
131   - if (streamList != null && streamList.size() > 0) {
132   - for (File streamFile : streamList) {
133   - Map<String, String> data = new HashMap<>();
134   - data.put("app", appFile.getName());
135   - data.put("stream", streamFile.getName());
136   -
137   - BasicFileAttributes bAttributes = null;
138   - try {
139   - bAttributes = Files.readAttributes(streamFile.toPath(),
140   - BasicFileAttributes.class);
141   - } catch (IOException e) {
142   - e.printStackTrace();
143   - }
144   - data.put("time", simpleDateFormatForTime.format(new Date(bAttributes.lastModifiedTime().toMillis())));
145   - result.add(data);
146   - }
147   - }
148   - }
149   - }
150   - }
151   - result.sort((Map f1, Map f2)->{
152   - Date time1 = null;
153   - Date time2 = null;
154   - try {
155   - time1 = simpleDateFormatForTime.parse(f1.get("time").toString());
156   - time2 = simpleDateFormatForTime.parse(f2.get("time").toString());
157   - } catch (ParseException e) {
158   - logger.error("时间格式化失败", e);
159   - }
160   - return time1.compareTo(time2) * -1;
161   - });
162   - return result;
163   - }
164   -
165   - /**
166 92 * 获取制定推流的指定时间段内的推流
167 93 * @param app
168 94 * @param stream
... ... @@ -191,7 +117,7 @@ public class VideoFileService {
191 117 logger.debug("获取[app: {}, stream: {}, statime: {}, endTime: {}]的视频", app, stream,
192 118 startTimeStr, endTimeStr);
193 119  
194   - File recordFile = new File(userSettings.getRecord());
  120 + File recordFile = new File(userSettings.getRecordTempPath());
195 121 File streamFile = new File(recordFile.getAbsolutePath() + File.separator + app + File.separator + stream + File.separator);
196 122 if (!streamFile.exists()) {
197 123 logger.warn("获取[app: {}, stream: {}, statime: {}, endTime: {}]的视频时未找到目录: {}", app, stream,
... ... @@ -282,80 +208,79 @@ public class VideoFileService {
282 208 }
283 209  
284 210  
285   - public String mergeOrCut(String app, String stream, Date startTime, Date endTime, String remoteHost) {
286   - List<File> filesInTime = this.getFilesInTime(app, stream, startTime, endTime);
287   - if (filesInTime== null || filesInTime.isEmpty()){
288   - logger.info("此时间段未未找到视频文件, {}/{} {}->{}", app, stream,
289   - startTime == null? null:DateUtils.getDateTimeStr(startTime),
290   - endTime == null? null:DateUtils.getDateTimeStr(endTime));
291   - return null;
292   - }
  211 + public String mergeOrCut(VideoTaskInfo videoTaskInfo) {
  212 + assert videoTaskInfo.getFilePathList() != null;
  213 + assert !videoTaskInfo.getFilePathList().isEmpty();
293 214 String taskId = DigestUtils.md5DigestAsHex(String.valueOf(System.currentTimeMillis()).getBytes());
294   - logger.info("[录像合并] 开始合并,APP:{}, STREAM: {}, 任务ID:{}", app, stream, taskId);
295   - String destDir = "recordTemp" + File.separator + taskId + File.separator + app;
296   - File recordFile = new File(userSettings.getRecord() + destDir );
  215 + String logInfo = String.format("app: %S, stream: %S, callId: %S, 任务ID:%S",
  216 + videoTaskInfo.getApp(), videoTaskInfo.getStream(), videoTaskInfo.getCallId(), taskId);
  217 + logger.info("[录像合并] 开始合并,{} ", logInfo);
  218 + List<File> fileList = new ArrayList<>();
  219 + for (String filePath : videoTaskInfo.getFilePathList()) {
  220 + File file = new File(filePath);
  221 + if (!file.exists()) {
  222 + logger.info("[录像合并] 失败,{} ", logInfo);
  223 + throw new ControllerException(ErrorCode.ERROR100.getCode(), filePath + "文件不存在");
  224 + }
  225 + logger.info("[录像合并] 添加文件,{}, 文件: {}", logInfo, filePath);
  226 + fileList.add(file);
  227 + }
  228 +
  229 + File recordFile = new File(userSettings.getRecordTempPath() );
297 230 if (!recordFile.exists()) {
298   - recordFile.mkdirs();
  231 + if (!recordFile.mkdirs()) {
  232 + logger.info("[录像合并] 失败,{}, 创建临时目录失败", logInfo);
  233 + throw new ControllerException(ErrorCode.ERROR100.getCode(), "创建临时目录失败");
  234 + }
299 235 }
300 236 MergeOrCutTaskInfo mergeOrCutTaskInfo = new MergeOrCutTaskInfo();
301 237 mergeOrCutTaskInfo.setId(taskId);
302   - mergeOrCutTaskInfo.setApp(app);
303   - mergeOrCutTaskInfo.setStream(stream);
  238 + mergeOrCutTaskInfo.setApp(videoTaskInfo.getApp());
  239 + mergeOrCutTaskInfo.setStream(videoTaskInfo.getStream());
  240 + mergeOrCutTaskInfo.setCallId(videoTaskInfo.getCallId());
  241 + mergeOrCutTaskInfo.setStartTime(videoTaskInfo.getStartTime());
  242 + mergeOrCutTaskInfo.setEndTime(videoTaskInfo.getEndTime());
304 243 mergeOrCutTaskInfo.setCreateTime(simpleDateFormatForTime.format(System.currentTimeMillis()));
305   - if(startTime != null) {
306   - mergeOrCutTaskInfo.setStartTime(simpleDateFormatForTime.format(startTime));
307   - }else {
308   - String startTimeInFile = filesInTime.get(0).getParentFile().getName() + " "
309   - + filesInTime.get(0).getName().split("-")[0];
310   - mergeOrCutTaskInfo.setStartTime(startTimeInFile);
311   - }
312   - if(endTime != null) {
313   - mergeOrCutTaskInfo.setEndTime(simpleDateFormatForTime.format(endTime));
314   - }else {
315   - String endTimeInFile = filesInTime.get(filesInTime.size()- 1).getParentFile().getName() + " "
316   - + filesInTime.get(filesInTime.size()- 1).getName().split("-")[1];
317   - mergeOrCutTaskInfo.setEndTime(endTimeInFile);
318   - }
319   - if (filesInTime.size() == 1) {
  244 + String destFileName = videoTaskInfo.getStream() + "_" + videoTaskInfo.getCallId();
  245 + if (fileList.size() == 1) {
320 246  
321 247 // 文件只有一个则不合并,直接复制过去
322 248 mergeOrCutTaskInfo.setPercentage("1");
323 249 // 处理文件路径
324   - String recordFileResultPath = recordFile.getAbsolutePath() + File.separator + stream + ".mp4";
325   - Path relativize = Paths.get(userSettings.getRecord()).relativize(Paths.get(recordFileResultPath));
  250 +
  251 + String recordFileResultPath = recordFile.getAbsolutePath() + File.separator + destFileName + ".mp4";
326 252 try {
327   - Files.copy(filesInTime.get(0).toPath(), Paths.get(recordFileResultPath));
  253 + Files.copy(fileList.get(0).toPath(), Paths.get(recordFileResultPath));
328 254 } catch (IOException e) {
329   - e.printStackTrace();
330   - logger.info("[录像合并] 失败,APP:{}, STREAM: {}, 任务ID:{}", app, stream, taskId);
331   - return taskId;
  255 + logger.info("[录像合并] 失败, {}", logInfo, e);
  256 + throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage());
332 257 }
333   - mergeOrCutTaskInfo.setRecordFile("/download/" + relativize.toString());
334   - if (remoteHost != null) {
335   - mergeOrCutTaskInfo.setDownloadFile(remoteHost + "/download.html?url=download/" + relativize);
336   - mergeOrCutTaskInfo.setPlayFile(remoteHost + "/download/" + relativize);
  258 + mergeOrCutTaskInfo.setRecordFile("/download/" + destFileName + ".mp4");
  259 + if (videoTaskInfo.getRemoteHost() != null) {
  260 + mergeOrCutTaskInfo.setDownloadFile(videoTaskInfo.getRemoteHost() + "/download.html?url=download/" + destFileName + ".mp4");
  261 + mergeOrCutTaskInfo.setPlayFile(videoTaskInfo.getRemoteHost() + "/download/" + destFileName + ".mp4");
337 262 }
338   - String key = String.format("%S_%S_%S_%S_%S", AssistConstants.MERGEORCUT , userSettings.getId(), mergeOrCutTaskInfo.getApp(), mergeOrCutTaskInfo.getStream(), mergeOrCutTaskInfo.getId());
  263 + String key = String.format("%S_%S_%S", AssistConstants.MERGEORCUT , userSettings.getId(), mergeOrCutTaskInfo.getId());
339 264 redisUtil.set(key, mergeOrCutTaskInfo);
340   - logger.info("[录像合并] 合并完成,APP:{}, STREAM: {}, 任务ID:{}", app, stream, taskId);
  265 + logger.info("[录像合并] 成功, 任务ID:{}", taskId);
341 266 }else {
342   - ffmpegExecUtils.mergeOrCutFile(filesInTime, recordFile, stream, (status, percentage, result)->{
  267 + ffmpegExecUtils.mergeOrCutFile(fileList, recordFile, destFileName, (status, percentage, result)->{
343 268 // 发出redis通知
344 269 if (status.equals(Progress.Status.END.name())) {
345 270 mergeOrCutTaskInfo.setPercentage("1");
346 271  
347 272 // 处理文件路径
348   - Path relativize = Paths.get(userSettings.getRecord()).relativize(Paths.get(result));
  273 + String relativize = new File(result).getName();
349 274 mergeOrCutTaskInfo.setRecordFile(relativize.toString());
350   - if (remoteHost != null) {
351   - mergeOrCutTaskInfo.setDownloadFile(remoteHost + "/download.html?url=download/" + relativize);
352   - mergeOrCutTaskInfo.setPlayFile(remoteHost + "/download/" + relativize);
  275 + if (videoTaskInfo.getRemoteHost() != null) {
  276 + mergeOrCutTaskInfo.setDownloadFile(videoTaskInfo.getRemoteHost() + "/download.html?url=download/" + relativize);
  277 + mergeOrCutTaskInfo.setPlayFile(videoTaskInfo.getRemoteHost() + "/download/" + relativize);
353 278 }
354   - logger.info("[录像合并] 合并完成,APP:{}, STREAM: {}, 任务ID:{}", app, stream, taskId);
  279 + logger.info("[录像合并] 成功, {}", logInfo);
355 280 }else {
356 281 mergeOrCutTaskInfo.setPercentage(percentage + "");
357 282 }
358   - String key = String.format("%S_%S_%S_%S_%S", AssistConstants.MERGEORCUT, userSettings.getId(), mergeOrCutTaskInfo.getApp(), mergeOrCutTaskInfo.getStream(), mergeOrCutTaskInfo.getId());
  283 + String key = String.format("%S_%S_%S", AssistConstants.MERGEORCUT, userSettings.getId(), mergeOrCutTaskInfo.getId());
359 284 redisUtil.set(key, mergeOrCutTaskInfo);
360 285 });
361 286 }
... ... @@ -363,19 +288,6 @@ public class VideoFileService {
363 288 return taskId;
364 289 }
365 290  
366   - /**
367   - * 获取指定时间的日期文件夹
368   - * @param app
369   - * @param stream
370   - * @param year
371   - * @param month
372   - * @return
373   - */
374   - public List<File> getDateList(String app, String stream, Integer year, Integer month, Boolean sort) {
375   - File recordFile = new File(userSettings.getRecord());
376   - File streamFile = new File(recordFile.getAbsolutePath() + File.separator + app + File.separator + stream);
377   - return getDateList(streamFile, year, month, sort);
378   - }
379 291 public List<File> getDateList(File streamFile, Integer year, Integer month, Boolean sort) {
380 292 if (!streamFile.exists() && streamFile.isDirectory()) {
381 293 logger.warn("获取[]的视频时未找到目录: {}",streamFile.getName());
... ... @@ -428,35 +340,39 @@ public class VideoFileService {
428 340 return dateFileList;
429 341 }
430 342  
431   - public List<MergeOrCutTaskInfo> getTaskListForDownload(Boolean idEnd, String app, String stream, String taskId) {
  343 + public List<MergeOrCutTaskInfo> getTaskListForDownload(String app, String stream, String callId, Boolean isEnd, String taskId) {
  344 + logger.info("[查询录像合成列表] app: {}, stream: {}, callId: {}, isEnd: {}, taskId: {}",
  345 + app, stream, callId, isEnd, taskId);
432 346 ArrayList<MergeOrCutTaskInfo> result = new ArrayList<>();
433   - if (app == null) {
434   - app = "*";
435   - }
436   - if (stream == null) {
437   - stream = "*";
438   - }
439 347 if (taskId == null) {
440 348 taskId = "*";
441 349 }
442   - List<Object> taskCatch = redisUtil.scan(String.format("%S_%S_%S_%S_%S", AssistConstants.MERGEORCUT,
443   - userSettings.getId(), app, stream, taskId));
  350 + List<Object> taskCatch = redisUtil.scan(String.format("%S_%S_%S", AssistConstants.MERGEORCUT,
  351 + userSettings.getId(), taskId));
444 352 for (int i = 0; i < taskCatch.size(); i++) {
445 353 String keyItem = taskCatch.get(i).toString();
446 354 MergeOrCutTaskInfo mergeOrCutTaskInfo = (MergeOrCutTaskInfo)redisUtil.get(keyItem);
447   - if (mergeOrCutTaskInfo != null && mergeOrCutTaskInfo.getPercentage() != null){
448   - if (idEnd != null ) {
449   - if (idEnd) {
450   - if (Double.parseDouble(mergeOrCutTaskInfo.getPercentage()) == 1){
451   - result.add(mergeOrCutTaskInfo);
  355 + if (mergeOrCutTaskInfo != null){
  356 + if ((!ObjectUtils.isEmpty(app) && !mergeOrCutTaskInfo.getApp().equals(app))
  357 + || (!ObjectUtils.isEmpty(stream) && !mergeOrCutTaskInfo.getStream().equals(stream))
  358 + || (!ObjectUtils.isEmpty(callId) && !mergeOrCutTaskInfo.getCallId().equals(callId))
  359 + ) {
  360 + continue;
  361 + }
  362 + if (mergeOrCutTaskInfo.getPercentage() != null){
  363 + if (isEnd != null ) {
  364 + if (isEnd) {
  365 + if (Double.parseDouble(mergeOrCutTaskInfo.getPercentage()) == 1){
  366 + result.add(mergeOrCutTaskInfo);
  367 + }
  368 + }else {
  369 + if (Double.parseDouble(mergeOrCutTaskInfo.getPercentage()) < 1){
  370 + result.add((MergeOrCutTaskInfo)redisUtil.get(keyItem));
  371 + }
452 372 }
453 373 }else {
454   - if (Double.parseDouble(mergeOrCutTaskInfo.getPercentage()) < 1){
455   - result.add((MergeOrCutTaskInfo)redisUtil.get(keyItem));
456   - }
  374 + result.add((MergeOrCutTaskInfo)redisUtil.get(keyItem));
457 375 }
458   - }else {
459   - result.add((MergeOrCutTaskInfo)redisUtil.get(keyItem));
460 376 }
461 377 }
462 378 }
... ... @@ -465,10 +381,10 @@ public class VideoFileService {
465 381 try {
466 382 sortResult = simpleDateFormatForTime.parse(m1.getCreateTime()).compareTo(simpleDateFormatForTime.parse(m2.getCreateTime()));
467 383 if (sortResult == 0) {
468   - sortResult = simpleDateFormatForTime.parse(m1.getStartTime()).compareTo(simpleDateFormatForTime.parse(m2.getStartTime()));
  384 + sortResult = simpleDateFormatForTime.parse(m1.getCreateTime()).compareTo(simpleDateFormatForTime.parse(m2.getCreateTime()));
469 385 }
470 386 if (sortResult == 0) {
471   - sortResult = simpleDateFormatForTime.parse(m1.getEndTime()).compareTo(simpleDateFormatForTime.parse(m2.getEndTime()));
  387 + sortResult = simpleDateFormatForTime.parse(m1.getCreateTime()).compareTo(simpleDateFormatForTime.parse(m2.getCreateTime()));
472 388 }
473 389 } catch (ParseException e) {
474 390 e.printStackTrace();
... ... @@ -480,7 +396,7 @@ public class VideoFileService {
480 396 }
481 397  
482 398 public boolean collection(String app, String stream, String type) {
483   - File streamFile = new File(userSettings.getRecord() + File.separator + app + File.separator + stream);
  399 + File streamFile = new File(userSettings.getRecordTempPath() + File.separator + app + File.separator + stream);
484 400 boolean result = false;
485 401 if (streamFile.exists() && streamFile.isDirectory() && streamFile.canWrite()) {
486 402 File signFile = new File(streamFile.getAbsolutePath() + File.separator + type + ".sign");
... ... @@ -494,7 +410,7 @@ public class VideoFileService {
494 410 }
495 411  
496 412 public boolean removeCollection(String app, String stream, String type) {
497   - File signFile = new File(userSettings.getRecord() + File.separator + app + File.separator + stream + File.separator + type + ".sign");
  413 + File signFile = new File(userSettings.getRecordTempPath() + File.separator + app + File.separator + stream + File.separator + type + ".sign");
498 414 boolean result = false;
499 415 if (signFile.exists() && signFile.isFile()) {
500 416 result = signFile.delete();
... ... @@ -565,4 +481,24 @@ public class VideoFileService {
565 481 }
566 482 return durationResult;
567 483 }
  484 +
  485 + public int deleteFile(List<String> filePathList) {
  486 + assert filePathList != null;
  487 + assert filePathList.isEmpty();
  488 + int deleteResult = 0;
  489 + for (String filePath : filePathList) {
  490 + File file = new File(filePath);
  491 + if (file.exists()) {
  492 + if (file.delete()) {
  493 + deleteResult ++;
  494 + }
  495 + }else {
  496 + logger.warn("[删除文件] 文件不存在,{}", filePath);
  497 + }
  498 + }
  499 + if (deleteResult == 0) {
  500 + throw new ControllerException(ErrorCode.ERROR100.getCode(), "未删除任何文件");
  501 + }
  502 + return deleteResult;
  503 + }
568 504 }
... ...
src/main/java/top/panll/assist/utils/PageInfo.java deleted 100755 → 0
1   -package top.panll.assist.utils;
2   -
3   -import java.util.ArrayList;
4   -import java.util.List;
5   -
6   -public class PageInfo<T> {
7   - //当前页
8   - private int pageNum;
9   - //每页的数量
10   - private int pageSize;
11   - //当前页的数量
12   - private int size;
13   - //总页数
14   - private int pages;
15   - //总数
16   - private int total;
17   -
18   - private List<T> resultData;
19   -
20   - private List<T> list;
21   -
22   - public PageInfo(List<T> resultData) {
23   - this.resultData = resultData;
24   - }
25   -
26   - public void startPage(int page, int count) {
27   - if (count <= 0) count = 10;
28   - if (page <= 0) page = 1;
29   - this.pageNum = page;
30   - this.pageSize = count;
31   - this.total = resultData.size();
32   -
33   - this.pages = total%count == 0 ? total/count : total/count + 1;
34   - int fromIndx = (page - 1) * count;
35   - if ( fromIndx > this.total - 1) {
36   - this.list = new ArrayList<>();
37   - this.size = 0;
38   - return;
39   - }
40   -
41   - int toIndx = page * count;
42   - if (toIndx > this.total) {
43   - toIndx = this.total;
44   - }
45   - this.list = this.resultData.subList(fromIndx, toIndx);
46   - this.size = this.list.size();
47   - }
48   -
49   - public int getPageNum() {
50   - return pageNum;
51   - }
52   -
53   - public void setPageNum(int pageNum) {
54   - this.pageNum = pageNum;
55   - }
56   -
57   - public int getPageSize() {
58   - return pageSize;
59   - }
60   -
61   - public void setPageSize(int pageSize) {
62   - this.pageSize = pageSize;
63   - }
64   -
65   - public int getSize() {
66   - return size;
67   - }
68   -
69   - public void setSize(int size) {
70   - this.size = size;
71   - }
72   -
73   - public int getPages() {
74   - return pages;
75   - }
76   -
77   - public void setPages(int pages) {
78   - this.pages = pages;
79   - }
80   -
81   - public int getTotal() {
82   - return total;
83   - }
84   -
85   - public void setTotal(int total) {
86   - this.total = total;
87   - }
88   -
89   - public List<T> getList() {
90   - return list;
91   - }
92   -
93   - public void setList(List<T> list) {
94   - this.list = list;
95   - }
96   -}
src/main/resources/all-application.yml
... ... @@ -28,10 +28,10 @@ server:
28 28  
29 29 # [根据业务需求配置]
30 30 user-settings:
31   - # [可选 ] zlm配置的录像路径,不配置则使用当前目录下的record目录 即: ./record
32   - record: /media/lin/Server/ZLMediaKit/dev/ZLMediaKit/release/linux/Debug/www/record
  31 + # [可选 ] 临时录像路径
  32 + record-temp-path: ./recordTemp
33 33 # [可选 ] 录像保存时长(单位: 天)每天晚12点自动对过期文件执行清理, 不配置则不删除
34   - recordDay: 7
  34 + record-temp-day: 7
35 35 # [可选 ] 录像下载合成临时文件保存时长, 不配置默认取值recordDay(单位: 天)每天晚12点自动对过期文件执行清理
36 36 # recordTempDay: 7
37 37 # [必选 ] ffmpeg路径
... ...
src/main/resources/application-local.yml
... ... @@ -11,7 +11,7 @@ spring:
11 11 # [可选] 数据库 DB
12 12 database: 1
13 13 # [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接
14   - password: adminadmin123.
  14 + password:
15 15 # 以下为集群配置
16 16 # cluster:
17 17 # nodes: 192.168.1.242:7001
... ... @@ -33,18 +33,13 @@ server:
33 33  
34 34 # [根据业务需求配置]
35 35 userSettings:
36   - # [必选 ] 服务ID
37   - id: 334533
38   - # [必选 ] 录像路径
39   - record: /home/lin/record/
40   - # [可选 ] 录像保存时长(单位: 天)每天晚12点自动对过期文件执行清理
41   - recordDay: 10
42   - # [可选 ] 录像下载合成临时文件保存时长, 不配置默认取值recordDay(单位: 天)每天晚12点自动对过期文件执行清理
43   - # recordTempDay: 7
  36 + id: 111
  37 + # [可选 ] 录像保存时长(单位: 天)每天晚12点自动对过期文件执行清理, 不配置则不删除
  38 + record-temp-day: 7
44 39 # [必选 ] ffmpeg路径
45   - ffmpeg: /home/lin/IdeaProjects/wvp-pro-assist/lib/ffmpeg
  40 + ffmpeg: ./lib/ffmpeg
46 41 # [必选 ] ffprobe路径, 一般安装ffmpeg就会自带, 一般跟ffmpeg在同一目录,用于查询文件的信息,
47   - ffprobe: /home/lin/IdeaProjects/wvp-pro-assist/lib/ffprobe
  42 + ffprobe: ./lib/ffprobe
48 43 # [可选 ] 限制 ffmpeg 合并文件使用的线程数,间接限制cpu使用率, 默认2 限制到50%
49 44 threads: 2
50 45  
... ...