Commit 60dfc69c58f9fcaea1baea2f76cbf78fe19c86dd

Authored by 648540858
1 parent 7360de7f

优化录像路径配置,不再使用zlm默认http服务器

Showing 32 changed files with 504 additions and 88 deletions
.gitignore 100644 → 100755
LICENSE 100644 → 100755
README.md 100644 → 100755
pom.xml 100644 → 100755
... ... @@ -69,10 +69,28 @@
69 69 </dependency>
70 70  
71 71 <dependency>
  72 + <groupId>org.mp4parser</groupId>
  73 + <artifactId>muxer</artifactId>
  74 + <version>1.9.56</version>
  75 + </dependency>
  76 + <dependency>
  77 + <groupId>org.mp4parser</groupId>
  78 + <artifactId>streaming</artifactId>
  79 + <version>1.9.56</version>
  80 + </dependency>
  81 +
  82 + <dependency>
  83 + <groupId>org.mp4parser</groupId>
  84 + <artifactId>isoparser</artifactId>
  85 + <version>1.9.27</version>
  86 + </dependency>
  87 +
  88 + <dependency>
72 89 <groupId>org.springframework.boot</groupId>
73 90 <artifactId>spring-boot-starter-test</artifactId>
74 91 <scope>test</scope>
75 92 </dependency>
  93 +
76 94 </dependencies>
77 95  
78 96 <build>
... ...
src/main/java/top/panll/assist/WvpProAssistApplication.java 100644 → 100755
src/main/java/top/panll/assist/config/FastJsonRedisSerializer.java 100644 → 100755
src/main/java/top/panll/assist/config/GlobalExceptionHandler.java 100644 → 100755
src/main/java/top/panll/assist/config/GlobalResponseAdvice.java 100644 → 100755
src/main/java/top/panll/assist/config/RedisConfig.java 100644 → 100755
src/main/java/top/panll/assist/config/SpringDocConfig.java 100644 → 100755
src/main/java/top/panll/assist/config/StartConfig.java 100644 → 100755
... ... @@ -41,29 +41,39 @@ public class StartConfig implements CommandLineRunner {
41 41 if (!record.endsWith(File.separator)) {
42 42 userSettings.setRecord(userSettings.getRecord() + File.separator);
43 43 }
  44 +
44 45 File recordFile = new File(record);
45   - if (!recordFile.exists() || !recordFile.isDirectory()) {
46   - logger.error("[userSettings.record]配置错误,请检查路径是否存在");
47   - System.exit(1);
48   - }
49   - if (!recordFile.canRead()) {
50   - logger.error("[userSettings.record]路径无法读取");
51   - System.exit(1);
52   - }
53   - if (!recordFile.canWrite()) {
54   - logger.error("[userSettings.record]路径无法写入");
55   - System.exit(1);
  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 + }
56 66 }
57   - // 在zlm目录写入assist下载页面
58   - writeAssistDownPage(recordFile);
  67 +
59 68 try {
60 69  
61   -// FFmpegExecUtils.getInstance().ffmpeg = ffmpeg;
62   -// FFmpegExecUtils.getInstance().ffprobe = ffprobe;
63 70 // 对目录进行预整理
64 71 File[] appFiles = recordFile.listFiles();
65 72 if (appFiles != null && appFiles.length > 0) {
66 73 for (File appFile : appFiles) {
  74 + if (appFile.getName().equals("recordTemp")) {
  75 + continue;
  76 + }
67 77 File[] streamFiles = appFile.listFiles();
68 78 if (streamFiles != null && streamFiles.length > 0) {
69 79 for (File streamFile : streamFiles) {
... ... @@ -91,48 +101,48 @@ public class StartConfig implements CommandLineRunner {
91 101 }
92 102 }
93 103  
94   - private void writeAssistDownPage(File recordFile) {
95   - try {
96   - File file = new File(recordFile.getParentFile().getAbsolutePath(), "download.html");
97   - if (file.exists()) {
98   - file.delete();
99   - }
100   - file.createNewFile();
101   - FileOutputStream fs = new FileOutputStream(file);
102   - StringBuffer stringBuffer = new StringBuffer();
103   - String content = "<!DOCTYPE html>\n" +
104   - "<html lang=\"en\">\n" +
105   - "<head>\n" +
106   - " <meta charset=\"UTF-8\">\n" +
107   - " <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n" +
108   - " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n" +
109   - " <title>下载</title>\n" +
110   - "</head>\n" +
111   - "<body>\n" +
112   - " <a id=\"download\" download />\n" +
113   - " <script>\n" +
114   - " (function(){\n" +
115   - " let searchParams = new URLSearchParams(location.search);\n" +
116   - " var download = document.getElementById(\"download\");\n" +
117   - " download.setAttribute(\"href\", searchParams.get(\"url\"))\n" +
118   - " download.click()\n" +
119   - " setTimeout(()=>{\n" +
120   - " window.location.href=\"about:blank\";\n" +
121   - "\t\t\t window.close();\n" +
122   - " },200)\n" +
123   - " })();\n" +
124   - " \n" +
125   - " </script>\n" +
126   - "</body>\n" +
127   - "</html>";
128   - fs.write(content.getBytes(StandardCharsets.UTF_8));
129   - logger.info("已写入html配置页面: " + file.getAbsolutePath());
130   - } catch (FileNotFoundException e) {
131   - logger.error("写入html页面错误", e);
132   - } catch (IOException e) {
133   - logger.error("写入html页面错误", e);
134   - }
135   -
136   -
137   - }
  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 +// }
138 148 }
... ...
src/main/java/top/panll/assist/config/ThreadPoolTaskConfig.java 100644 → 100755
src/main/java/top/panll/assist/controller/DownController.java 0 → 100644
  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/DownloadController.java 0 → 100644
  1 +package top.panll.assist.controller;
  2 +
  3 +
  4 +import io.swagger.v3.oas.annotations.Operation;
  5 +import io.swagger.v3.oas.annotations.Parameter;
  6 +import io.swagger.v3.oas.annotations.tags.Tag;
  7 +import org.apache.catalina.connector.ClientAbortException;
  8 +import org.slf4j.Logger;
  9 +import org.slf4j.LoggerFactory;
  10 +import org.springframework.beans.factory.annotation.Autowired;
  11 +import org.springframework.stereotype.Controller;
  12 +import org.springframework.web.bind.annotation.*;
  13 +import top.panll.assist.dto.UserSettings;
  14 +import top.panll.assist.utils.PageInfo;
  15 +
  16 +import javax.servlet.http.HttpServletRequest;
  17 +import javax.servlet.http.HttpServletResponse;
  18 +import java.io.BufferedOutputStream;
  19 +import java.io.File;
  20 +import java.io.IOException;
  21 +import java.io.RandomAccessFile;
  22 +import java.nio.charset.StandardCharsets;
  23 +import java.util.List;
  24 +import java.util.Map;
  25 +
  26 +@Controller
  27 +@RequestMapping("/download")
  28 +public class DownloadController {
  29 +
  30 + private final static Logger logger = LoggerFactory.getLogger(DownloadController.class);
  31 +
  32 + @Autowired
  33 + private UserSettings userSettings;
  34 +
  35 + /**
  36 + * 获取app+stream列表
  37 + *
  38 + * @return
  39 + */
  40 + @GetMapping(value = "/**")
  41 + @ResponseBody
  42 + public void download(HttpServletRequest request, HttpServletResponse response) {
  43 +
  44 + String resourcePath = request.getServletPath();
  45 + System.out.println(resourcePath);
  46 + resourcePath = resourcePath.substring("/download".length() + 1, resourcePath.length());
  47 + String record = userSettings.getRecord();
  48 +// if (record.endsWith("/")) {
  49 +// record = record.substring(0, record.length() - 1);
  50 +// System.out.println(record);
  51 +// }
  52 + System.out.println(record + resourcePath);
  53 + File file = new File(record + resourcePath);
  54 + if (!file.exists()) {
  55 + response.setStatus(HttpServletResponse.SC_NOT_FOUND);
  56 + return;
  57 + }
  58 +
  59 + /**
  60 + * 参考实现来自: CSDN 进修的CODER SpringBoot Java实现Http方式分片下载断点续传+实现H5大视频渐进式播放
  61 + * https://blog.csdn.net/lovequanquqn/article/details/104562945
  62 + */
  63 + String range = request.getHeader("Range");
  64 + logger.info("current request rang:" + range);
  65 + //开始下载位置
  66 + long startByte = 0;
  67 + //结束下载位置
  68 + long endByte = file.length() - 1;
  69 + logger.info("文件开始位置:{},文件结束位置:{},文件总长度:{}", startByte, endByte, file.length());
  70 +
  71 + //有range的话
  72 + if (range != null && range.contains("bytes=") && range.contains("-")) {
  73 + range = range.substring(range.lastIndexOf("=") + 1).trim();
  74 + String[] ranges = range.split("-");
  75 + try {
  76 + //判断range的类型
  77 + if (ranges.length == 1) {
  78 + // 类型一:bytes=-2343,
  79 + if (range.startsWith("-")) {
  80 + endByte = Long.parseLong(ranges[0]);
  81 + }
  82 + //类型二:bytes=2343-
  83 + else if (range.endsWith("-")) {
  84 + startByte = Long.parseLong(ranges[0]);
  85 + }
  86 + }
  87 + //类型三:bytes=22-2343
  88 + else if (ranges.length == 2) {
  89 + startByte = Long.parseLong(ranges[0]);
  90 + endByte = Long.parseLong(ranges[1]);
  91 + }
  92 +
  93 + } catch (NumberFormatException e) {
  94 + startByte = 0;
  95 + endByte = file.length() - 1;
  96 + logger.error("Range Occur Error,Message:{}", e.getLocalizedMessage());
  97 + }
  98 +
  99 +
  100 + }
  101 +
  102 + // 要下载的长度
  103 + long contentLength = endByte - startByte + 1;
  104 + // 文件名
  105 + String fileName = file.getName();
  106 + // 文件类型
  107 + String contentType = request.getServletContext().getMimeType(fileName);
  108 +
  109 + // 解决下载文件时文件名乱码问题
  110 + byte[] fileNameBytes = fileName.getBytes(StandardCharsets.UTF_8);
  111 + fileName = new String(fileNameBytes, 0, fileNameBytes.length, StandardCharsets.ISO_8859_1);
  112 +
  113 + response.setHeader("Content-Type", contentType);
  114 + response.setHeader("Content-Length", String.valueOf(contentLength));
  115 + //inline表示浏览器直接使用,attachment表示下载,fileName表示下载的文件名
  116 + response.setHeader("Content-Disposition", "inline;filename=" + fileName);
  117 + response.setContentType(contentType);
  118 + if (range != null) {
  119 + //各种响应头设置
  120 + //支持断点续传,获取部分字节内容:
  121 + response.setHeader("Accept-Ranges", "bytes");
  122 + //http状态码要为206:表示获取部分内容
  123 + response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
  124 + // Content-Range,格式为:[要下载的开始位置]-[结束位置]/[文件总大小]
  125 + response.setHeader("Content-Range", "bytes " + startByte + "-" + endByte + "/" + file.length());
  126 + } else {
  127 + response.setStatus(HttpServletResponse.SC_OK);
  128 + }
  129 +
  130 +
  131 + BufferedOutputStream outputStream = null;
  132 + RandomAccessFile randomAccessFile = null;
  133 + //已传送数据大小
  134 + long transmitted = 0;
  135 + try {
  136 + randomAccessFile = new RandomAccessFile(file, "r");
  137 +
  138 + outputStream = new BufferedOutputStream(response.getOutputStream());
  139 + byte[] buff = new byte[4096];
  140 + int len = 0;
  141 + randomAccessFile.seek(startByte);
  142 + //warning:判断是否到了最后不足4096(buff的length)个byte这个逻辑((transmitted + len) <= contentLength)要放前面
  143 + //不然会会先读取randomAccessFile,造成后面读取位置出错;
  144 + while ((transmitted + len) <= contentLength && (len = randomAccessFile.read(buff)) != -1) {
  145 + outputStream.write(buff, 0, len);
  146 + transmitted += len;
  147 + }
  148 + //处理不足buff.length部分
  149 + if (transmitted < contentLength) {
  150 + len = randomAccessFile.read(buff, 0, (int) (contentLength - transmitted));
  151 + outputStream.write(buff, 0, len);
  152 + transmitted += len;
  153 + }
  154 +
  155 + outputStream.flush();
  156 + response.flushBuffer();
  157 + randomAccessFile.close();
  158 + logger.info("下载完毕:" + startByte + "-" + endByte + ":" + transmitted);
  159 + } catch (ClientAbortException e) {
  160 + logger.warn("用户停止下载:" + startByte + "-" + endByte + ":" + transmitted);
  161 + //捕获此异常表示拥护停止下载
  162 + } catch (IOException e) {
  163 + e.printStackTrace();
  164 + logger.error("用户下载IO异常,Message:{}", e.getLocalizedMessage());
  165 + } finally {
  166 + try {
  167 + if (randomAccessFile != null) {
  168 + randomAccessFile.close();
  169 + }
  170 + } catch (IOException e) {
  171 + e.printStackTrace();
  172 + }
  173 + }///end try
  174 + }
  175 +}
... ...
src/main/java/top/panll/assist/controller/RecordController.java 100644 → 100755
... ... @@ -24,10 +24,8 @@ import javax.servlet.http.HttpServletRequest;
24 24 import java.io.File;
25 25 import java.text.ParseException;
26 26 import java.text.SimpleDateFormat;
27   -import java.util.ArrayList;
28   -import java.util.Date;
29   -import java.util.List;
30   -import java.util.Map;
  27 +import java.util.*;
  28 +
31 29 @Tag(name = "录像管理", description = "录像管理")
32 30 @CrossOrigin
33 31 @RestController
... ... @@ -49,6 +47,16 @@ public class RecordController {
49 47  
50 48  
51 49 /**
  50 + * 获取Assist服务配置信息
  51 + */
  52 + @Operation(summary ="获取Assist服务配置信息")
  53 + @GetMapping(value = "/info")
  54 + @ResponseBody
  55 + public UserSettings getInfo(){
  56 + return userSettings;
  57 + }
  58 +
  59 + /**
52 60 * 获取app+stream列表
53 61 * @return
54 62 */
... ... @@ -58,7 +66,7 @@ public class RecordController {
58 66 @GetMapping(value = "/list")
59 67 @ResponseBody
60 68 public PageInfo<Map<String, String>> getList(@RequestParam int page,
61   - @RequestParam int count){
  69 + @RequestParam int count){
62 70 List<Map<String, String>> appList = videoFileService.getList();
63 71  
64 72 PageInfo<Map<String, String>> stringPageInfo = new PageInfo<>(appList);
... ... @@ -84,6 +92,7 @@ public class RecordController {
84 92 resultData.add(file.getName());
85 93 }
86 94 }
  95 + Collections.sort(resultData);
87 96  
88 97 PageInfo<String> stringPageInfo = new PageInfo<>(resultData);
89 98 stringPageInfo.startPage(page, count);
... ... @@ -342,9 +351,10 @@ public class RecordController {
342 351 ret.put("code", 0);
343 352 ret.put("msg", "success");
344 353 String file_path = json.getString("file_path");
  354 +
345 355 String app = json.getString("app");
346 356 String stream = json.getString("stream");
347   - logger.debug("ZLM 录制完成,参数:" + file_path);
  357 + logger.debug("ZLM 录制完成,文件路径:" + file_path);
348 358  
349 359 if (file_path == null) {
350 360 return new ResponseEntity<String>(ret.toString(), HttpStatus.OK);
... ... @@ -356,7 +366,6 @@ public class RecordController {
356 366 videoFileService.handFile(new File(file_path), app, stream);
357 367 }
358 368  
359   -
360 369 return new ResponseEntity<String>(ret.toString(), HttpStatus.OK);
361 370 }
362 371  
... ...
src/main/java/top/panll/assist/controller/bean/ErrorCode.java 100644 → 100755
src/main/java/top/panll/assist/controller/bean/WVPResult.java 100644 → 100755
src/main/java/top/panll/assist/dto/MergeOrCutTaskInfo.java 100644 → 100755
src/main/java/top/panll/assist/dto/SignInfo.java 100644 → 100755
src/main/java/top/panll/assist/dto/SpaceInfo.java 100644 → 100755
src/main/java/top/panll/assist/dto/UserSettings.java 100644 → 100755
src/main/java/top/panll/assist/service/FFmpegExecUtils.java 100644 → 100755
src/main/java/top/panll/assist/service/FileManagerTimer.java 100644 → 100755
... ... @@ -39,6 +39,9 @@ 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) {
  43 + return;
  44 + }
42 45 int recordDay = userSettings.getRecordDay();
43 46 Date lastDate=new Date();
44 47 Calendar lastCalendar = Calendar.getInstance();
... ... @@ -115,7 +118,7 @@ public class FileManagerTimer {
115 118 lastTempCalendar.add(Calendar.DAY_OF_MONTH, 0 - recordTempDay);
116 119 lastTempDate = lastTempCalendar.getTime();
117 120 logger.info("[录像巡查]移除合并任务临时文件 {} 之前的文件", formatter.format(lastTempDate));
118   - File recordTempFile = new File(recordFileDir.getParentFile().getAbsolutePath() + File.separator + "recordTemp");
  121 + File recordTempFile = new File(userSettings.getRecord() + "recordTemp");
119 122 if (recordTempFile.exists() && recordTempFile.isDirectory() && recordTempFile.canWrite()) {
120 123 File[] tempFiles = recordTempFile.listFiles();
121 124 for (File tempFile : tempFiles) {
... ...
src/main/java/top/panll/assist/service/VideoFileService.java 100644 → 100755
... ... @@ -48,7 +48,7 @@ public class VideoFileService {
48 48 if (recordFile.isDirectory()) {
49 49 File[] files = recordFile.listFiles((File dir, String name) -> {
50 50 File currentFile = new File(dir.getAbsolutePath() + File.separator + name);
51   - return currentFile.isDirectory();
  51 + return currentFile.isDirectory() && !name.equals("recordTemp");
52 52 });
53 53 List<File> result = Arrays.asList(files);
54 54 if (sort != null && sort) {
... ... @@ -144,22 +144,17 @@ public class VideoFileService {
144 144  
145 145 String key = AssistConstants.STREAM_CALL_INFO + userSettings.getId() + "_" + app + "_" + stream;
146 146 String callId = (String) redisUtil.get(key);
147   - if (callId != null) {
148   -
149   - File newPath = new File(file.getParentFile().getParent() + "_" + callId + File.separator + file.getParentFile().getName());
150   - if (!newPath.exists()) {
151   - newPath.mkdirs();
152   - }
153   - String newName = newPath.getAbsolutePath() + File.separator+ simpleDateFormat.format(startTime) + "-" + simpleDateFormat.format(endTime) + "-" + durationLong + ".mp4";
154   - file.renameTo(new File(newName));
155   - }else {
156   - String newName = file.getAbsolutePath().replace(file.getName(),
157   - simpleDateFormat.format(startTime) + "-" + simpleDateFormat.format(endTime) + "-" + durationLong + ".mp4");
158 147  
159   - file.renameTo(new File(newName));
  148 + String streamNew = (callId == null? stream : stream + "_" + callId);
  149 + File newPath = new File(userSettings.getRecord() + File.separator + app + File.separator + streamNew + File.separator + DateUtils.getDateStr(new Date(startTime)));
  150 + if (!newPath.exists()) {
  151 + newPath.mkdirs();
160 152 }
161 153  
162   - logger.debug("[处理文件] {}", file.getName());
  154 + String newName = newPath.getAbsolutePath() + File.separator+ simpleDateFormat.format(startTime) + "-" + simpleDateFormat.format(endTime) + "-" + durationLong + ".mp4";
  155 + file.renameTo(new File(newName));
  156 + System.out.println(file.getAbsolutePath());
  157 + logger.info("[处理文件] {}", file.getName());
163 158 } catch (IOException e) {
164 159 logger.warn("文件可能以损坏[{}]", file.getAbsolutePath());
165 160 } catch (ParseException e) {
... ... @@ -339,13 +334,13 @@ public class VideoFileService {
339 334 public String mergeOrCut(String app, String stream, Date startTime, Date endTime, String remoteHost) {
340 335 List<File> filesInTime = this.getFilesInTime(app, stream, startTime, endTime);
341 336 if (filesInTime== null || filesInTime.size() == 0){
342   - logger.info("此时间段未未找到视频文件");
  337 + logger.info("此时间段未未找到视频文件, {}/{} {}->{}", app, stream, DateUtils.getDateTimeStr(startTime), DateUtils.getDateTimeStr(endTime));
343 338 return null;
344 339 }
345 340 String taskId = DigestUtils.md5DigestAsHex(String.valueOf(System.currentTimeMillis()).getBytes());
346 341 logger.info("[录像合并] 开始合并,APP:{}, STREAM: {}, 任务ID:{}", app, stream, taskId);
347 342 String destDir = "recordTemp" + File.separator + taskId + File.separator + app;
348   - File recordFile = new File(new File(userSettings.getRecord()).getParentFile().getAbsolutePath() + File.separator + destDir );
  343 + File recordFile = new File(userSettings.getRecord() + destDir );
349 344 if (!recordFile.exists()) {
350 345 recordFile.mkdirs();
351 346 }
... ... @@ -374,7 +369,7 @@ public class VideoFileService {
374 369 mergeOrCutTaskInfo.setPercentage("1");
375 370 // 处理文件路径
376 371 String recordFileResultPath = recordFile.getAbsolutePath() + File.separator + stream + ".mp4";
377   - Path relativize = Paths.get(userSettings.getRecord()).getParent().relativize(Paths.get(recordFileResultPath));
  372 + Path relativize = Paths.get(userSettings.getRecord()).relativize(Paths.get(recordFileResultPath));
378 373 try {
379 374 Files.copy(filesInTime.get(0).toPath(), Paths.get(recordFileResultPath));
380 375 } catch (IOException e) {
... ... @@ -384,8 +379,8 @@ public class VideoFileService {
384 379 }
385 380 mergeOrCutTaskInfo.setRecordFile(relativize.toString());
386 381 if (remoteHost != null) {
387   - mergeOrCutTaskInfo.setDownloadFile(remoteHost + "/download.html?url=" + relativize);
388   - mergeOrCutTaskInfo.setPlayFile(remoteHost + "/" + relativize);
  382 + mergeOrCutTaskInfo.setDownloadFile(remoteHost + "/download.html?url=download/" + relativize);
  383 + mergeOrCutTaskInfo.setPlayFile(remoteHost + "/download/" + relativize);
389 384 }
390 385 String key = String.format("%S_%S_%S_%S_%S", AssistConstants.MERGEORCUT , userSettings.getId(), mergeOrCutTaskInfo.getApp(), mergeOrCutTaskInfo.getStream(), mergeOrCutTaskInfo.getId());
391 386 redisUtil.set(key, mergeOrCutTaskInfo);
... ... @@ -397,7 +392,7 @@ public class VideoFileService {
397 392 mergeOrCutTaskInfo.setPercentage("1");
398 393  
399 394 // 处理文件路径
400   - Path relativize = Paths.get(userSettings.getRecord()).getParent().relativize(Paths.get(result));
  395 + Path relativize = Paths.get(userSettings.getRecord()).relativize(Paths.get(result));
401 396 mergeOrCutTaskInfo.setRecordFile(relativize.toString());
402 397 if (remoteHost != null) {
403 398 mergeOrCutTaskInfo.setDownloadFile(remoteHost + "/download.html?url=" + relativize);
... ...
src/main/java/top/panll/assist/utils/DateUtils.java 100644 → 100755
1 1 package top.panll.assist.utils;
2 2  
  3 +import java.text.SimpleDateFormat;
3 4 import java.time.Instant;
4 5 import java.time.LocalDateTime;
5 6 import java.time.LocalTime;
6 7 import java.time.ZoneId;
  8 +import java.time.format.DateTimeFormatter;
7 9 import java.util.Date;
  10 +import java.util.Locale;
8 11  
9 12 public class DateUtils {
10 13  
  14 + public static final String PATTERNForDateTime = "yyyy-MM-dd HH:mm:ss";
  15 +
  16 + public static final String PATTERNForDate = "yyyy-MM-dd";
  17 +
  18 + public static final String zoneStr = "Asia/Shanghai";
  19 +
  20 +
  21 +
11 22 // 获得某天最大时间 2020-02-19 23:59:59
12 23 public static Date getEndOfDay(Date date) {
13 24 LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault());;
... ... @@ -22,4 +33,14 @@ public class DateUtils {
22 33 return Date.from(startOfDay.atZone(ZoneId.systemDefault()).toInstant());
23 34 }
24 35  
  36 + public static String getDateStr(Date date) {
  37 + SimpleDateFormat formatter = new SimpleDateFormat(PATTERNForDate);
  38 + return formatter.format(date);
  39 + }
  40 +
  41 + public static String getDateTimeStr(Date date) {
  42 + SimpleDateFormat formatter = new SimpleDateFormat(PATTERNForDateTime);
  43 + return formatter.format(date);
  44 + }
  45 +
25 46 }
... ...
src/main/java/top/panll/assist/utils/PageInfo.java 100644 → 100755
src/main/java/top/panll/assist/utils/RedisUtil.java 100644 → 100755
src/main/resources/all-application.yml 0 → 100755
  1 +spring:
  2 + # REDIS数据库配置
  3 + redis:
  4 + # [必须修改] Redis服务器IP, REDIS安装在本机的,使用127.0.0.1
  5 + host: 127.0.0.1
  6 + # [必须修改] 端口号
  7 + port: 6379
  8 + # [可选] 数据库 DB
  9 + database: 8
  10 + # [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接
  11 + password:
  12 + # [可选] 超时时间
  13 + timeout: 10000
  14 +
  15 +# [必选] WVP监听的HTTP端口, 网页和接口调用都是这个端口
  16 +server:
  17 + port: 18081
  18 + # [可选] HTTPS配置, 默认不开启
  19 + ssl:
  20 + # [可选] 是否开启HTTPS访问
  21 + enabled: false
  22 + # [可选] 证书文件路径,放置在resource/目录下即可,修改xxx为文件名
  23 + key-store: classpath:xxx.jks
  24 + # [可选] 证书密码
  25 + key-password: password
  26 + # [可选] 证书类型, 默认为jks,根据实际修改
  27 + key-store-type: JKS
  28 +
  29 +# [根据业务需求配置]
  30 +user-settings:
  31 + # [可选 ] zlm配置的录像路径,不配置则使用当前目录下的record目录 即: ./record
  32 + record: /media/lin/Server/ZLMediaKit/dev/ZLMediaKit/release/linux/Debug/www/record
  33 + # [可选 ] 录像保存时长(单位: 天)每天晚12点自动对过期文件执行清理, 不配置则不删除
  34 + recordDay: 7
  35 + # [可选 ] 录像下载合成临时文件保存时长, 不配置默认取值recordDay(单位: 天)每天晚12点自动对过期文件执行清理
  36 + # recordTempDay: 7
  37 + # [必选 ] ffmpeg路径
  38 + ffmpeg: /usr/bin/ffmpeg
  39 + # [必选 ] ffprobe路径, 一般安装ffmpeg就会自带, 一般跟ffmpeg在同一目录,用于查询文件的信息
  40 + ffprobe: /usr/bin/ffprobe
  41 + # [可选 ] 限制 ffmpeg 合并文件使用的线程数,间接限制cpu使用率, 默认2 限制到50%
  42 + threads: 2
  43 +
  44 +swagger-ui:
  45 +
  46 +# [可选] 日志配置, 一般不需要改
  47 +logging:
  48 + file:
  49 + name: logs/wvp.log
  50 + max-history: 30
  51 + max-size: 10MB
  52 + total-size-cap: 300MB
  53 + level:
  54 + root: WARN
  55 + top:
  56 + panll:
  57 + assist: info
0 58 \ No newline at end of file
... ...
src/main/resources/application-dev.yml 100644 → 100755
... ... @@ -28,7 +28,7 @@ server:
28 28  
29 29 # [根据业务需求配置]
30 30 userSettings:
31   - # [必选 ] zlm配置的录像路径
  31 + # [可选 ] zlm配置的录像路径,
32 32 record: /media/lin/Server/ZLMediaKit/dev/ZLMediaKit/release/linux/Debug/www/record
33 33 # [可选 ] 录像保存时长(单位: 天)每天晚12点自动对过期文件执行清理
34 34 recordDay: 7
... ...
src/main/resources/application.yml 100644 → 100755
src/main/resources/static/download.html 0 → 100644
  1 +<!DOCTYPE html>
  2 +<html lang="en">
  3 +<head>
  4 + <meta charset="UTF-8">
  5 + <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6 + <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7 + <title>下载</title>
  8 +</head>
  9 +<body>
  10 +<a id="download" download></a>
  11 +<script>
  12 + (function () {
  13 + let searchParams = new URLSearchParams(location.search);
  14 + var download = document.getElementById("download");
  15 + download.setAttribute("href", searchParams.get("url"))
  16 + download.click()
  17 + setTimeout(() => {
  18 + window.location.href = "about:blank";
  19 + window.close();
  20 + }, 200)
  21 + })();
  22 +
  23 +</script>
  24 +</body>
  25 +</html>
0 26 \ No newline at end of file
... ...
src/test/java/top/panll/assist/WvpProAssistApplicationTests.java 100644 → 100755