Commit cfc4df108e2652c1c1975e204d57fe290b5e1bc0
1 parent
f3e2bf1e
修改接口bug
Showing
13 changed files
with
359 additions
and
96 deletions
.gitignore
pom.xml
| ... | ... | @@ -81,6 +81,14 @@ |
| 81 | 81 | <groupId>org.springframework.boot</groupId> |
| 82 | 82 | <artifactId>spring-boot-maven-plugin</artifactId> |
| 83 | 83 | </plugin> |
| 84 | + <plugin> | |
| 85 | + <groupId>org.apache.maven.plugins</groupId> | |
| 86 | + <artifactId>maven-compiler-plugin</artifactId> | |
| 87 | + <configuration> | |
| 88 | + <source>1.8</source> | |
| 89 | + <target>1.8</target> | |
| 90 | + </configuration> | |
| 91 | + </plugin> | |
| 84 | 92 | </plugins> |
| 85 | 93 | </build> |
| 86 | 94 | ... | ... |
src/main/java/top/panll/assist/config/FastJsonRedisSerializer.java
0 → 100644
| 1 | +package top.panll.assist.config; | |
| 2 | + | |
| 3 | +import com.alibaba.fastjson.JSON; | |
| 4 | +import com.alibaba.fastjson.parser.ParserConfig; | |
| 5 | +import com.alibaba.fastjson.serializer.SerializerFeature; | |
| 6 | +import org.springframework.data.redis.serializer.RedisSerializer; | |
| 7 | +import org.springframework.data.redis.serializer.SerializationException; | |
| 8 | + | |
| 9 | +import java.nio.charset.Charset; | |
| 10 | + | |
| 11 | +public class FastJsonRedisSerializer<T> implements RedisSerializer<T> { | |
| 12 | + private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); | |
| 13 | + private Class<T> clazz; | |
| 14 | + | |
| 15 | + /** | |
| 16 | + * 添加autotype白名单 | |
| 17 | + * 解决redis反序列化对象时报错 :com.alibaba.fastjson.JSONException: autoType is not support | |
| 18 | + */ | |
| 19 | + static { | |
| 20 | + ParserConfig.getGlobalInstance().addAccept("top.panll.assist"); | |
| 21 | + } | |
| 22 | + | |
| 23 | + public FastJsonRedisSerializer(Class<T> clazz) { | |
| 24 | + super(); | |
| 25 | + this.clazz = clazz; | |
| 26 | + } | |
| 27 | + | |
| 28 | + @Override | |
| 29 | + public byte[] serialize(T t) throws SerializationException { | |
| 30 | + if (null == t) { | |
| 31 | + return new byte[0]; | |
| 32 | + } | |
| 33 | + return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET); | |
| 34 | + } | |
| 35 | + | |
| 36 | + @Override | |
| 37 | + public T deserialize(byte[] bytes) throws SerializationException { | |
| 38 | + if (null == bytes || bytes.length <= 0) { | |
| 39 | + return null; | |
| 40 | + } | |
| 41 | + String str = new String(bytes, DEFAULT_CHARSET); | |
| 42 | + return JSON.parseObject(str, clazz); | |
| 43 | + } | |
| 44 | +} | ... | ... |
src/main/java/top/panll/assist/config/RedisConfig.java
| 1 | 1 | package top.panll.assist.config; |
| 2 | 2 | |
| 3 | 3 | import com.alibaba.fastjson.parser.ParserConfig; |
| 4 | -import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer; | |
| 5 | -import org.springframework.cache.annotation.CachingConfigurerSupport; | |
| 4 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; | |
| 5 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; | |
| 6 | +import org.springframework.boot.autoconfigure.data.redis.RedisProperties; | |
| 7 | +import org.springframework.boot.context.properties.EnableConfigurationProperties; | |
| 6 | 8 | import org.springframework.context.annotation.Bean; |
| 7 | 9 | import org.springframework.context.annotation.Configuration; |
| 8 | 10 | import org.springframework.data.redis.connection.RedisConnectionFactory; |
| 11 | +import org.springframework.data.redis.core.RedisOperations; | |
| 9 | 12 | import org.springframework.data.redis.core.RedisTemplate; |
| 10 | 13 | import org.springframework.data.redis.listener.RedisMessageListenerContainer; |
| 11 | 14 | import org.springframework.data.redis.serializer.StringRedisSerializer; |
| ... | ... | @@ -17,14 +20,21 @@ import org.springframework.data.redis.serializer.StringRedisSerializer; |
| 17 | 20 | * |
| 18 | 21 | */ |
| 19 | 22 | @Configuration |
| 20 | -public class RedisConfig extends CachingConfigurerSupport { | |
| 23 | +@ConditionalOnClass(RedisOperations.class) | |
| 24 | +@EnableConfigurationProperties(RedisProperties.class) | |
| 25 | +public class RedisConfig { | |
| 21 | 26 | |
| 22 | - @Bean("redisTemplate") | |
| 27 | + static { | |
| 28 | + ParserConfig.getGlobalInstance().addAccept("top.panll.assist"); | |
| 29 | + } | |
| 30 | + | |
| 31 | + @Bean | |
| 32 | + @ConditionalOnMissingBean(name = "redisTemplate") | |
| 23 | 33 | public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { |
| 24 | 34 | RedisTemplate<Object, Object> template = new RedisTemplate<>(); |
| 25 | 35 | template.setConnectionFactory(redisConnectionFactory); |
| 26 | 36 | // 使用fastjson进行序列化处理,提高解析效率 |
| 27 | - FastJsonRedisSerializer<Object> serializer = new FastJsonRedisSerializer<Object>(Object.class); | |
| 37 | + FastJsonRedisSerializer<Object> serializer = new FastJsonRedisSerializer<>(Object.class); | |
| 28 | 38 | // value值的序列化采用fastJsonRedisSerializer |
| 29 | 39 | template.setValueSerializer(serializer); |
| 30 | 40 | template.setHashValueSerializer(serializer); |
| ... | ... | @@ -33,8 +43,9 @@ public class RedisConfig extends CachingConfigurerSupport { |
| 33 | 43 | template.setHashKeySerializer(new StringRedisSerializer()); |
| 34 | 44 | template.setConnectionFactory(redisConnectionFactory); |
| 35 | 45 | // 使用fastjson时需设置此项,否则会报异常not support type |
| 36 | - ParserConfig.getGlobalInstance().setAutoTypeSupport(true); | |
| 46 | +// ParserConfig.getGlobalInstance().setAutoTypeSupport(true); | |
| 37 | 47 | return template; |
| 48 | + | |
| 38 | 49 | } |
| 39 | 50 | |
| 40 | 51 | /** | ... | ... |
src/main/java/top/panll/assist/config/StartConfig.java
| ... | ... | @@ -53,7 +53,7 @@ public class StartConfig implements CommandLineRunner { |
| 53 | 53 | String ffprobePath = userSettings.getFfprobe(); |
| 54 | 54 | FFmpeg ffmpeg = new FFmpeg(ffmpegPath); |
| 55 | 55 | FFprobe ffprobe = new FFprobe(ffprobePath); |
| 56 | - logger.info("wvp-pro辅助程序启动成功"); | |
| 56 | + logger.info("wvp-pro辅助程序启动成功。 \n{}\n{} ", ffmpeg.version(), ffprobe.version()); | |
| 57 | 57 | FFmpegExecUtils.getInstance().ffmpeg = ffmpeg; |
| 58 | 58 | FFmpegExecUtils.getInstance().ffprobe = ffprobe; |
| 59 | 59 | // 对目录进行预整理 | ... | ... |
src/main/java/top/panll/assist/config/TaskConfig.java
| ... | ... | @@ -6,6 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired; |
| 6 | 6 | import org.springframework.context.annotation.Configuration; |
| 7 | 7 | import org.springframework.scheduling.annotation.EnableScheduling; |
| 8 | 8 | import org.springframework.scheduling.annotation.Scheduled; |
| 9 | +import org.springframework.stereotype.Component; | |
| 9 | 10 | import top.panll.assist.service.VideoFileService; |
| 10 | 11 | |
| 11 | 12 | import java.io.File; |
| ... | ... | @@ -16,7 +17,7 @@ import java.util.Calendar; |
| 16 | 17 | import java.util.Date; |
| 17 | 18 | import java.util.List; |
| 18 | 19 | |
| 19 | -@Configuration | |
| 20 | +@Component | |
| 20 | 21 | @EnableScheduling |
| 21 | 22 | public class TaskConfig { |
| 22 | 23 | ... | ... |
src/main/java/top/panll/assist/controller/RecordController.java
| 1 | 1 | package top.panll.assist.controller; |
| 2 | 2 | |
| 3 | +import com.alibaba.fastjson.JSONObject; | |
| 3 | 4 | import org.slf4j.Logger; |
| 4 | 5 | import org.slf4j.LoggerFactory; |
| 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; |
| 7 | +import org.springframework.http.HttpStatus; | |
| 8 | +import org.springframework.http.ResponseEntity; | |
| 6 | 9 | import org.springframework.web.bind.annotation.*; |
| 7 | 10 | import top.panll.assist.controller.bean.WVPResult; |
| 11 | +import top.panll.assist.dto.MergeOrCutTaskInfo; | |
| 8 | 12 | import top.panll.assist.service.VideoFileService; |
| 9 | 13 | import top.panll.assist.utils.PageInfo; |
| 10 | 14 | |
| ... | ... | @@ -105,11 +109,11 @@ public class RecordController { |
| 105 | 109 | */ |
| 106 | 110 | @GetMapping(value = "/date/list") |
| 107 | 111 | @ResponseBody |
| 108 | - public WVPResult<PageInfo<String>> getDateList(@RequestParam int page, | |
| 109 | - @RequestParam int count, | |
| 110 | - @RequestParam String app, | |
| 111 | - @RequestParam String stream ){ | |
| 112 | - WVPResult<PageInfo<String>> result = new WVPResult<>(); | |
| 112 | + public WVPResult<List<String>> getDateList( @RequestParam(required = false) Integer year, | |
| 113 | + @RequestParam(required = false) Integer month, | |
| 114 | + @RequestParam String app, | |
| 115 | + @RequestParam String stream ){ | |
| 116 | + WVPResult<List<String>> result = new WVPResult<>(); | |
| 113 | 117 | List<String> resultData = new ArrayList<>(); |
| 114 | 118 | if (app == null) { |
| 115 | 119 | result.setCode(400); |
| ... | ... | @@ -121,15 +125,13 @@ public class RecordController { |
| 121 | 125 | result.setMsg("stream不能为空"); |
| 122 | 126 | return result; |
| 123 | 127 | } |
| 124 | - List<File> dateList = videoFileService.getDateList(app, stream); | |
| 128 | + List<File> dateList = videoFileService.getDateList(app, stream, year, month); | |
| 125 | 129 | for (File file : dateList) { |
| 126 | 130 | resultData.add(file.getName()); |
| 127 | 131 | } |
| 128 | 132 | result.setCode(0); |
| 129 | 133 | result.setMsg("success"); |
| 130 | - PageInfo<String> stringPageInfo = new PageInfo<>(resultData); | |
| 131 | - stringPageInfo.startPage(page, count); | |
| 132 | - result.setData(stringPageInfo); | |
| 134 | + result.setData(resultData); | |
| 133 | 135 | return result; |
| 134 | 136 | } |
| 135 | 137 | |
| ... | ... | @@ -188,7 +190,7 @@ public class RecordController { |
| 188 | 190 | * @param endTime |
| 189 | 191 | * @return |
| 190 | 192 | */ |
| 191 | - @GetMapping(value = "/file/download/task") | |
| 193 | + @GetMapping(value = "/file/download/task/add") | |
| 192 | 194 | @ResponseBody |
| 193 | 195 | public WVPResult<String> addTaskForDownload(@RequestParam String app, |
| 194 | 196 | @RequestParam String stream, |
| ... | ... | @@ -210,35 +212,63 @@ public class RecordController { |
| 210 | 212 | } |
| 211 | 213 | String id = videoFileService.mergeOrCut(app, stream, startTimeDate, endTimeDate); |
| 212 | 214 | result.setCode(0); |
| 213 | - result.setMsg(id!= null?"success":"error"); | |
| 215 | + result.setMsg(id!= null?"success":"error: 可能未找到视频文件"); | |
| 214 | 216 | result.setData(id); |
| 215 | 217 | return result; |
| 216 | 218 | } |
| 217 | 219 | |
| 218 | 220 | /** |
| 219 | - * 录制完成的通知 | |
| 221 | + * 查询视频裁剪合并任务列表 | |
| 220 | 222 | * @return |
| 221 | 223 | */ |
| 222 | - @GetMapping(value = "/end") | |
| 224 | + @GetMapping(value = "/file/download/task/list") | |
| 223 | 225 | @ResponseBody |
| 224 | - public WVPResult<String> recordEnd(@RequestParam String path | |
| 225 | - ){ | |
| 226 | - File file = new File(path); | |
| 227 | - WVPResult<String> result = new WVPResult<>(); | |
| 228 | - if (file.exists()) { | |
| 229 | - try { | |
| 230 | - videoFileService.handFile(file); | |
| 231 | - result.setCode(0); | |
| 232 | - result.setMsg("success"); | |
| 233 | - } catch (ParseException e) { | |
| 234 | - e.printStackTrace(); | |
| 235 | - result.setCode(500); | |
| 236 | - result.setMsg("error"); | |
| 237 | - } | |
| 238 | - }else { | |
| 239 | - result.setCode(400); | |
| 240 | - result.setMsg("路径不存在"); | |
| 226 | + public WVPResult<List<MergeOrCutTaskInfo>> getTaskListForDownload(@RequestParam Boolean isEnd){ | |
| 227 | + if (isEnd == null) { | |
| 228 | + isEnd = false; | |
| 241 | 229 | } |
| 230 | + List<MergeOrCutTaskInfo> taskList = videoFileService.getTaskListForDownload(isEnd); | |
| 231 | + WVPResult<List<MergeOrCutTaskInfo>> result = new WVPResult<>(); | |
| 232 | + result.setCode(0); | |
| 233 | + result.setMsg(taskList != null?"success":"error"); | |
| 234 | + result.setData(taskList); | |
| 242 | 235 | return result; |
| 243 | 236 | } |
| 237 | + | |
| 238 | + /** | |
| 239 | + * TODO 中止视频裁剪合并任务列表 | |
| 240 | + * @return | |
| 241 | + */ | |
| 242 | + @GetMapping(value = "/file/download/task/stop") | |
| 243 | + @ResponseBody | |
| 244 | + public WVPResult<String> stopTaskForDownload(@RequestParam String taskId){ | |
| 245 | +// WVPResult<String> result = new WVPResult<>(); | |
| 246 | +// if (taskId == null) { | |
| 247 | +// result.setCode(400); | |
| 248 | +// result.setMsg("taskId 不能为空"); | |
| 249 | +// return result; | |
| 250 | +// } | |
| 251 | +// boolean stopResult = videoFileService.stopTask(taskId); | |
| 252 | +// result.setCode(0); | |
| 253 | +// result.setMsg(stopResult ? "success": "fail"); | |
| 254 | + return null; | |
| 255 | + } | |
| 256 | + | |
| 257 | + /** | |
| 258 | + * 录制完成的通知, 对用zlm的hook | |
| 259 | + * @return | |
| 260 | + */ | |
| 261 | + @ResponseBody | |
| 262 | + @PostMapping(value = "/on_record_mp4", produces = "application/json;charset=UTF-8") | |
| 263 | + public ResponseEntity<String> onRecordMp4(@RequestBody JSONObject json) { | |
| 264 | + JSONObject ret = new JSONObject(); | |
| 265 | + ret.put("code", 0); | |
| 266 | + ret.put("msg", "success"); | |
| 267 | + String file_path = json.getString("file_path"); | |
| 268 | + logger.debug("ZLM 录制完成,参数:" + file_path); | |
| 269 | + if (file_path == null) return new ResponseEntity<String>(ret.toString(), HttpStatus.OK); | |
| 270 | + videoFileService.handFile(new File(file_path)); | |
| 271 | + | |
| 272 | + return new ResponseEntity<String>(ret.toString(), HttpStatus.OK); | |
| 273 | + } | |
| 244 | 274 | } | ... | ... |
src/main/java/top/panll/assist/dto/MergeOrCutTaskInfo.java
0 → 100644
| 1 | +package top.panll.assist.dto; | |
| 2 | + | |
| 3 | +import java.util.Date; | |
| 4 | + | |
| 5 | +public class MergeOrCutTaskInfo { | |
| 6 | + private String id; | |
| 7 | + private String startTime; | |
| 8 | + private String endTime; | |
| 9 | + private String app; | |
| 10 | + private String stream; | |
| 11 | + | |
| 12 | + private String percentage; | |
| 13 | + | |
| 14 | + private String recordFile; | |
| 15 | + | |
| 16 | + public String getId() { | |
| 17 | + return id; | |
| 18 | + } | |
| 19 | + | |
| 20 | + public void setId(String id) { | |
| 21 | + this.id = id; | |
| 22 | + } | |
| 23 | + | |
| 24 | + public String getStartTime() { | |
| 25 | + return startTime; | |
| 26 | + } | |
| 27 | + | |
| 28 | + public void setStartTime(String startTime) { | |
| 29 | + this.startTime = startTime; | |
| 30 | + } | |
| 31 | + | |
| 32 | + public String getEndTime() { | |
| 33 | + return endTime; | |
| 34 | + } | |
| 35 | + | |
| 36 | + public void setEndTime(String endTime) { | |
| 37 | + this.endTime = endTime; | |
| 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 getPercentage() { | |
| 57 | + return percentage; | |
| 58 | + } | |
| 59 | + | |
| 60 | + public void setPercentage(String percentage) { | |
| 61 | + this.percentage = percentage; | |
| 62 | + } | |
| 63 | + | |
| 64 | + public String getRecordFile() { | |
| 65 | + return recordFile; | |
| 66 | + } | |
| 67 | + | |
| 68 | + public void setRecordFile(String recordFile) { | |
| 69 | + this.recordFile = recordFile; | |
| 70 | + } | |
| 71 | +} | ... | ... |
src/main/java/top/panll/assist/service/FFmpegExecUtils.java
| ... | ... | @@ -51,7 +51,6 @@ public class FFmpegExecUtils { |
| 51 | 51 | return null; |
| 52 | 52 | } |
| 53 | 53 | |
| 54 | - | |
| 55 | 54 | File tempFile = new File(dest.getAbsolutePath() + File.separator + temp); |
| 56 | 55 | if (!tempFile.exists()) { |
| 57 | 56 | tempFile.mkdirs(); |
| ... | ... | @@ -66,14 +65,12 @@ public class FFmpegExecUtils { |
| 66 | 65 | if (split.length != 3) continue; |
| 67 | 66 | String durationStr = split[2].replace(".mp4", ""); |
| 68 | 67 | Double duration = Double.parseDouble(durationStr)/1000; |
| 69 | - System.out.println(duration); | |
| 70 | 68 | bw.write("file " + file.getAbsolutePath()); |
| 71 | 69 | bw.newLine(); |
| 72 | 70 | durationAll += duration; |
| 73 | 71 | } |
| 74 | 72 | bw.flush(); |
| 75 | 73 | bw.close(); |
| 76 | - System.out.println(durationAll); | |
| 77 | 74 | } catch (IOException e) { |
| 78 | 75 | e.printStackTrace(); |
| 79 | 76 | callBack.run("error", 0.0, null); |
| ... | ... | @@ -91,7 +88,6 @@ public class FFmpegExecUtils { |
| 91 | 88 | .setFormat("mp4") |
| 92 | 89 | .done(); |
| 93 | 90 | |
| 94 | - | |
| 95 | 91 | double finalDurationAll = durationAll; |
| 96 | 92 | FFmpegJob job = executor.createJob(builder, (Progress progress) -> { |
| 97 | 93 | final double duration_ns = finalDurationAll * TimeUnit.SECONDS.toNanos(1); |
| ... | ... | @@ -107,6 +103,7 @@ public class FFmpegExecUtils { |
| 107 | 103 | // progress.fps.doubleValue(), |
| 108 | 104 | // progress.speed |
| 109 | 105 | // )); |
| 106 | + | |
| 110 | 107 | if (progress.status.equals(Progress.Status.END)){ |
| 111 | 108 | callBack.run(progress.status.name(), percentage,dest.getName() + File.separator + temp + File.separator + "record.mp4"); |
| 112 | 109 | System.out.println(System.currentTimeMillis() - startTime); |
| ... | ... | @@ -114,7 +111,6 @@ public class FFmpegExecUtils { |
| 114 | 111 | callBack.run(progress.status.name(), percentage, null); |
| 115 | 112 | } |
| 116 | 113 | }); |
| 117 | - | |
| 118 | 114 | job.run(); |
| 119 | 115 | return temp; |
| 120 | 116 | } | ... | ... |
src/main/java/top/panll/assist/service/VideoFileService.java
| 1 | 1 | package top.panll.assist.service; |
| 2 | 2 | |
| 3 | -import com.alibaba.fastjson.JSON; | |
| 3 | +import com.alibaba.fastjson.JSONObject; | |
| 4 | 4 | import net.bramp.ffmpeg.FFprobe; |
| 5 | 5 | import net.bramp.ffmpeg.probe.FFmpegProbeResult; |
| 6 | 6 | import net.bramp.ffmpeg.progress.Progress; |
| ... | ... | @@ -11,12 +11,15 @@ import org.springframework.context.annotation.Bean; |
| 11 | 11 | import org.springframework.data.redis.core.StringRedisTemplate; |
| 12 | 12 | import org.springframework.stereotype.Service; |
| 13 | 13 | import org.springframework.util.DigestUtils; |
| 14 | -import top.panll.assist.config.RedisUtil; | |
| 14 | +import top.panll.assist.utils.RedisUtil; | |
| 15 | +import top.panll.assist.dto.MergeOrCutTaskInfo; | |
| 15 | 16 | import top.panll.assist.dto.UserSettings; |
| 16 | 17 | import top.panll.assist.utils.DateUtils; |
| 17 | 18 | |
| 18 | 19 | import java.io.File; |
| 19 | 20 | import java.io.IOException; |
| 21 | +import java.nio.file.Files; | |
| 22 | +import java.nio.file.attribute.BasicFileAttributes; | |
| 20 | 23 | import java.text.ParseException; |
| 21 | 24 | import java.text.SimpleDateFormat; |
| 22 | 25 | import java.util.*; |
| ... | ... | @@ -41,7 +44,9 @@ public class VideoFileService { |
| 41 | 44 | private ThreadPoolExecutor processThreadPool; |
| 42 | 45 | |
| 43 | 46 | private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); |
| 47 | + private SimpleDateFormat simpleDateFormatForTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); | |
| 44 | 48 | |
| 49 | + private String keyStr = "MERGEORCUT"; | |
| 45 | 50 | |
| 46 | 51 | @Bean("threadPoolExecutor") |
| 47 | 52 | private ThreadPoolExecutor iniThreadPool() { |
| ... | ... | @@ -84,9 +89,9 @@ public class VideoFileService { |
| 84 | 89 | * @param file |
| 85 | 90 | * @throws ParseException |
| 86 | 91 | */ |
| 87 | - public void handFile(File file) throws ParseException { | |
| 92 | + public void handFile(File file) { | |
| 88 | 93 | FFprobe ffprobe = FFmpegExecUtils.getInstance().ffprobe; |
| 89 | - if(file.isFile() && !file.getName().startsWith(".")&& file.getName().endsWith(".mp4") && file.getName().indexOf(":") < 0) { | |
| 94 | + if(file.exists() && file.isFile() && !file.getName().startsWith(".")&& file.getName().endsWith(".mp4") && file.getName().indexOf(":") < 0) { | |
| 90 | 95 | try { |
| 91 | 96 | FFmpegProbeResult in = null; |
| 92 | 97 | in = ffprobe.probe(file.getAbsolutePath()); |
| ... | ... | @@ -107,8 +112,11 @@ public class VideoFileService { |
| 107 | 112 | simpleDateFormat.format(startTime) + "-" + simpleDateFormat.format(endTime) + "-" + durationLong + ".mp4"); |
| 108 | 113 | file.renameTo(new File(newName)); |
| 109 | 114 | System.out.println(newName); |
| 110 | - } catch (IOException exception) { | |
| 111 | - exception.printStackTrace(); | |
| 115 | + } catch (IOException e) { | |
| 116 | + logger.warn("文件可能以损坏[{}]", file.getAbsolutePath()); | |
| 117 | +// e.printStackTrace(); | |
| 118 | + } catch (ParseException e) { | |
| 119 | + logger.error("时间格式化失败", e.getMessage()); | |
| 112 | 120 | } |
| 113 | 121 | } |
| 114 | 122 | } |
| ... | ... | @@ -127,19 +135,30 @@ public class VideoFileService { |
| 127 | 135 | data.put("app", appFile.getName()); |
| 128 | 136 | data.put("stream", streamFile.getName()); |
| 129 | 137 | |
| 130 | -// BasicFileAttributes bAttributes = null; | |
| 131 | -// try { | |
| 132 | -// bAttributes = Files.readAttributes(streamFile.toPath(), | |
| 133 | -// BasicFileAttributes.class); | |
| 134 | -// } catch (IOException e) { | |
| 135 | -// e.printStackTrace(); | |
| 136 | -// } | |
| 137 | -// data.put("time", simpleDateFormat.format(new Date(bAttributes.lastModifiedTime().toMillis()))); | |
| 138 | + BasicFileAttributes bAttributes = null; | |
| 139 | + try { | |
| 140 | + bAttributes = Files.readAttributes(streamFile.toPath(), | |
| 141 | + BasicFileAttributes.class); | |
| 142 | + } catch (IOException e) { | |
| 143 | + e.printStackTrace(); | |
| 144 | + } | |
| 145 | + data.put("time", simpleDateFormatForTime.format(new Date(bAttributes.lastModifiedTime().toMillis()))); | |
| 138 | 146 | result.add(data); |
| 139 | 147 | } |
| 140 | 148 | } |
| 141 | 149 | } |
| 142 | 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.getMessage()); | |
| 159 | + } | |
| 160 | + return time1.compareTo(time2) * -1; | |
| 161 | + }); | |
| 143 | 162 | return result; |
| 144 | 163 | } |
| 145 | 164 | |
| ... | ... | @@ -175,14 +194,16 @@ public class VideoFileService { |
| 175 | 194 | } |
| 176 | 195 | File[] dateFiles = streamFile.listFiles((File dir, String name) -> { |
| 177 | 196 | Date fileDate = null; |
| 197 | + Date startDate = new Date(startTime.getTime() - ((startTime.getTime() + 28800000) % (86400000))); | |
| 198 | + Date endDate = new Date(endTime.getTime() - ((endTime.getTime() + 28800000) % (86400000))); | |
| 178 | 199 | try { |
| 179 | 200 | fileDate = formatterForDate.parse(name); |
| 180 | 201 | } catch (ParseException e) { |
| 181 | 202 | logger.error("过滤日期文件时异常: {}-{}", name, e.getMessage()); |
| 182 | 203 | return false; |
| 183 | 204 | } |
| 184 | - return (DateUtils.getStartOfDay(fileDate).compareTo(startTime) >= 0 | |
| 185 | - && DateUtils.getStartOfDay(fileDate).compareTo(endTime) <= 0) ; | |
| 205 | + return (DateUtils.getStartOfDay(fileDate).compareTo(startDate) <= 0 | |
| 206 | + && DateUtils.getStartOfDay(fileDate).compareTo(endDate) >= 0) ; | |
| 186 | 207 | }); |
| 187 | 208 | |
| 188 | 209 | if (dateFiles != null && dateFiles.length > 0) { |
| ... | ... | @@ -235,50 +256,131 @@ public class VideoFileService { |
| 235 | 256 | |
| 236 | 257 | public String mergeOrCut(String app, String stream, Date startTime, Date endTime) { |
| 237 | 258 | List<File> filesInTime = this.getFilesInTime(app, stream, startTime, endTime); |
| 259 | + if (filesInTime== null || filesInTime.size() == 0){ | |
| 260 | + logger.info("此时间段未未找到视频文件"); | |
| 261 | + return null; | |
| 262 | + } | |
| 238 | 263 | File recordFile = new File(new File(userSettings.getRecord()).getParentFile().getAbsolutePath() + File.separator + "recordTemp"); |
| 239 | 264 | if (!recordFile.exists()) recordFile.mkdirs(); |
| 240 | 265 | |
| 241 | - String temp = DigestUtils.md5DigestAsHex(String.valueOf(System.currentTimeMillis()).getBytes()); | |
| 242 | - processThreadPool.execute(() -> { | |
| 243 | - FFmpegExecUtils.getInstance().mergeOrCutFile(filesInTime, recordFile, temp, (String status, double percentage, String result)->{ | |
| 244 | - Map<String, String> data = new HashMap<>(); | |
| 245 | - data.put("id", temp); | |
| 266 | + String taskId = DigestUtils.md5DigestAsHex(String.valueOf(System.currentTimeMillis()).getBytes()); | |
| 267 | + MergeOrCutTaskInfo mergeOrCutTaskInfo = new MergeOrCutTaskInfo(); | |
| 268 | + mergeOrCutTaskInfo.setId(taskId); | |
| 269 | + mergeOrCutTaskInfo.setApp(app); | |
| 270 | + mergeOrCutTaskInfo.setStream(stream); | |
| 271 | + mergeOrCutTaskInfo.setStartTime(simpleDateFormatForTime.format(startTime)); | |
| 272 | + mergeOrCutTaskInfo.setEndTime(simpleDateFormatForTime.format(endTime)); | |
| 273 | + | |
| 274 | + Runnable task = () -> { | |
| 275 | + FFmpegExecUtils.getInstance().mergeOrCutFile(filesInTime, recordFile, taskId, (String status, double percentage, String result)->{ | |
| 276 | + | |
| 246 | 277 | // 发出redis通知 |
| 247 | 278 | if (status.equals(Progress.Status.END.name())) { |
| 248 | - data.put("percentage", "1"); | |
| 249 | - data.put("recordFile", result); | |
| 250 | - redisUtil.set(app + "_" + stream + "_" + temp, data, 3*60*60); | |
| 251 | - stringRedisTemplate.convertAndSend("topic_mergeorcut_end", JSON.toJSONString(data)); | |
| 279 | + mergeOrCutTaskInfo.setPercentage("1"); | |
| 280 | + mergeOrCutTaskInfo.setRecordFile(result); | |
| 281 | + stringRedisTemplate.convertAndSend("topic_mergeorcut_end", JSONObject.toJSONString(mergeOrCutTaskInfo)); | |
| 252 | 282 | }else { |
| 253 | - data.put("percentage", percentage + ""); | |
| 254 | - redisUtil.set(app + "_" + stream + "_" + temp, data, 3*60*60); | |
| 255 | - stringRedisTemplate.convertAndSend("topic_mergeorcut_continue", JSON.toJSONString(data)); | |
| 283 | + mergeOrCutTaskInfo.setPercentage(percentage + ""); | |
| 284 | + stringRedisTemplate.convertAndSend("topic_mergeorcut_continue", JSONObject.toJSONString(mergeOrCutTaskInfo)); | |
| 256 | 285 | } |
| 286 | + String key = String.format("%S_%S_%S_%S", keyStr, app, stream, taskId); | |
| 287 | + redisUtil.set(key, mergeOrCutTaskInfo); | |
| 257 | 288 | }); |
| 258 | - }); | |
| 259 | - return temp; | |
| 289 | + }; | |
| 290 | + processThreadPool.execute(task); | |
| 291 | + return taskId; | |
| 260 | 292 | } |
| 261 | 293 | |
| 262 | 294 | |
| 263 | - public List<File> getDateList(String app, String stream) { | |
| 295 | + public List<File> getDateList(String app, String stream, Integer year, Integer month) { | |
| 264 | 296 | File recordFile = new File(userSettings.getRecord()); |
| 265 | 297 | File streamFile = new File(recordFile.getAbsolutePath() + File.separator + app + File.separator + stream); |
| 266 | 298 | if (!streamFile.exists()) { |
| 267 | 299 | logger.warn("获取[app: {}, stream: {}]的视频时未找到目录: {}", app, stream, stream); |
| 268 | 300 | return null; |
| 269 | 301 | } |
| 270 | - File[] dateFiles = streamFile.listFiles(); | |
| 302 | + File[] dateFiles = streamFile.listFiles((File dir, String name)->{ | |
| 303 | + Date date = null; | |
| 304 | + try { | |
| 305 | + date = simpleDateFormat.parse(name); | |
| 306 | + } catch (ParseException e) { | |
| 307 | + logger.error("格式化时间{}错误", name); | |
| 308 | + } | |
| 309 | + Calendar c = Calendar.getInstance(); | |
| 310 | + c.setTime(date); | |
| 311 | + int y = c.get(Calendar.YEAR); | |
| 312 | + int m = c.get(Calendar.MONTH); | |
| 313 | + if (year != null) { | |
| 314 | + if (month != null) { | |
| 315 | + return y == year && m == month; | |
| 316 | + }else { | |
| 317 | + return y == year; | |
| 318 | + } | |
| 319 | + }else { | |
| 320 | + return true; | |
| 321 | + } | |
| 322 | + | |
| 323 | + }); | |
| 271 | 324 | List<File> dateFileList = Arrays.asList(dateFiles); |
| 325 | + | |
| 272 | 326 | dateFileList.sort((File f1, File f2)->{ |
| 273 | 327 | int sortResult = 0; |
| 274 | - SimpleDateFormat formatterForDate = new SimpleDateFormat("yyyy-MM-dd"); | |
| 328 | + | |
| 275 | 329 | try { |
| 276 | - sortResult = formatterForDate.parse(f1.getName()).compareTo(formatterForDate.parse(f2.getName())); | |
| 330 | + sortResult = simpleDateFormat.parse(f1.getName()).compareTo(simpleDateFormat.parse(f2.getName())); | |
| 277 | 331 | } catch (ParseException e) { |
| 278 | - e.printStackTrace(); | |
| 332 | + logger.error("格式化时间{}/{}错误", f1.getName(), f2.getName()); | |
| 279 | 333 | } |
| 280 | 334 | return sortResult; |
| 281 | 335 | }); |
| 282 | 336 | return dateFileList; |
| 283 | 337 | } |
| 338 | + | |
| 339 | + public List<MergeOrCutTaskInfo> getTaskListForDownload(boolean idEnd) { | |
| 340 | + ArrayList<MergeOrCutTaskInfo> result = new ArrayList<>(); | |
| 341 | + List<Object> taskCatch = redisUtil.scan(String.format("%S_*_*_*", keyStr)); | |
| 342 | + for (int i = 0; i < taskCatch.size(); i++) { | |
| 343 | + String keyItem = taskCatch.get(i).toString(); | |
| 344 | + MergeOrCutTaskInfo mergeOrCutTaskInfo = (MergeOrCutTaskInfo)redisUtil.get(keyItem); | |
| 345 | + if (mergeOrCutTaskInfo != null){ | |
| 346 | + if (idEnd) { | |
| 347 | + if (Double.parseDouble(mergeOrCutTaskInfo.getPercentage()) == 1){ | |
| 348 | + result.add(mergeOrCutTaskInfo); | |
| 349 | + } | |
| 350 | + }else { | |
| 351 | + if (Double.parseDouble(mergeOrCutTaskInfo.getPercentage()) < 1){ | |
| 352 | + result.add((MergeOrCutTaskInfo)redisUtil.get(keyItem)); | |
| 353 | + } | |
| 354 | + } | |
| 355 | + } | |
| 356 | + } | |
| 357 | + result.sort((MergeOrCutTaskInfo m1, MergeOrCutTaskInfo m2)->{ | |
| 358 | + int sortResult = 0; | |
| 359 | + try { | |
| 360 | + sortResult = simpleDateFormatForTime.parse(m1.getStartTime()).compareTo(simpleDateFormatForTime.parse(m2.getStartTime())); | |
| 361 | + if (sortResult == 0) { | |
| 362 | + sortResult = simpleDateFormatForTime.parse(m1.getEndTime()).compareTo(simpleDateFormatForTime.parse(m2.getEndTime())); | |
| 363 | + } | |
| 364 | + } catch (ParseException e) { | |
| 365 | + e.printStackTrace(); | |
| 366 | + } | |
| 367 | + return sortResult * -1; | |
| 368 | + }); | |
| 369 | + return result; | |
| 370 | + } | |
| 371 | + | |
| 372 | + public boolean stopTask(String taskId) { | |
| 373 | +// Runnable task = taskList.get(taskId); | |
| 374 | +// boolean result = false; | |
| 375 | +// if (task != null) { | |
| 376 | +// processThreadPool.remove(task); | |
| 377 | +// taskList.remove(taskId); | |
| 378 | +// List<Object> taskCatch = redisUtil.scan(String.format("%S_*_*_%S", keyStr, taskId)); | |
| 379 | +// if (taskCatch.size() == 1) { | |
| 380 | +// redisUtil.del((String) taskCatch.get(0)); | |
| 381 | +// result = true; | |
| 382 | +// } | |
| 383 | +// } | |
| 384 | + return false; | |
| 385 | + } | |
| 284 | 386 | } | ... | ... |
src/main/java/top/panll/assist/config/RedisUtil.java renamed to src/main/java/top/panll/assist/utils/RedisUtil.java
| 1 | -package top.panll.assist.config; | |
| 1 | +package top.panll.assist.utils; | |
| 2 | 2 | |
| 3 | 3 | import org.springframework.beans.factory.annotation.Autowired; |
| 4 | 4 | import org.springframework.data.redis.core.*; |
| ... | ... | @@ -18,7 +18,7 @@ import java.util.concurrent.TimeUnit; |
| 18 | 18 | public class RedisUtil { |
| 19 | 19 | |
| 20 | 20 | @Autowired |
| 21 | - private RedisTemplate redisTemplate; | |
| 21 | + private RedisTemplate<Object, Object> redisTemplate; | |
| 22 | 22 | |
| 23 | 23 | /** |
| 24 | 24 | * 指定缓存失效时间 |
| ... | ... | @@ -474,7 +474,7 @@ public class RedisUtil { |
| 474 | 474 | * @param end |
| 475 | 475 | * @return |
| 476 | 476 | */ |
| 477 | - public Set<ZSetOperations.TypedTuple<String>> zRangeWithScore(Object key, int start, int end) { | |
| 477 | + public Set<ZSetOperations.TypedTuple<Object>> zRangeWithScore(Object key, int start, int end) { | |
| 478 | 478 | return redisTemplate.opsForZSet().rangeWithScores(key, start, end); |
| 479 | 479 | } |
| 480 | 480 | /** |
| ... | ... | @@ -487,7 +487,7 @@ public class RedisUtil { |
| 487 | 487 | * @param end |
| 488 | 488 | * @return |
| 489 | 489 | */ |
| 490 | - public Set<String> zRevRange(Object key, int start, int end) { | |
| 490 | + public Set<Object> zRevRange(Object key, int start, int end) { | |
| 491 | 491 | return redisTemplate.opsForZSet().reverseRange(key, start, end); |
| 492 | 492 | } |
| 493 | 493 | /** |
| ... | ... | @@ -498,7 +498,7 @@ public class RedisUtil { |
| 498 | 498 | * @param max |
| 499 | 499 | * @return |
| 500 | 500 | */ |
| 501 | - public Set<String> zSortRange(Object key, int min, int max) { | |
| 501 | + public Set<Object> zSortRange(Object key, int min, int max) { | |
| 502 | 502 | return redisTemplate.opsForZSet().rangeByScore(key, min, max); |
| 503 | 503 | } |
| 504 | 504 | |
| ... | ... | @@ -666,7 +666,7 @@ public class RedisUtil { |
| 666 | 666 | */ |
| 667 | 667 | public List<Object> keys(String key) { |
| 668 | 668 | try { |
| 669 | - Set<String> set = redisTemplate.keys(key); | |
| 669 | + Set<Object> set = redisTemplate.keys(key); | |
| 670 | 670 | return new ArrayList<>(set); |
| 671 | 671 | } catch (Exception e) { |
| 672 | 672 | e.printStackTrace(); | ... | ... |
src/main/resources/application-dev.yml
src/main/resources/application-local.yml
| ... | ... | @@ -6,7 +6,7 @@ spring: |
| 6 | 6 | # [必须修改] 端口号 |
| 7 | 7 | port: 6379 |
| 8 | 8 | # [可选] 数据库 DB |
| 9 | - database: 8 | |
| 9 | + database: 10 | |
| 10 | 10 | # [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接 |
| 11 | 11 | password: |
| 12 | 12 | # [可选] 超时时间 |
| ... | ... | @@ -29,7 +29,7 @@ server: |
| 29 | 29 | # [根据业务需求配置] |
| 30 | 30 | userSettings: |
| 31 | 31 | # [必选 ] zlm配置的录像路径 |
| 32 | - record: /media/lin/Server/ZLMediaKit/dev/ZLMediaKit/release/linux/Debug/www/record | |
| 32 | + record: /home/lin/server/ZLMediaKit/release/linux/Debug/www/record/ | |
| 33 | 33 | # [可选 ] 录像保存时长(单位: 天)每天晚12点自动对过期文件执行清理 |
| 34 | 34 | recordDay: 7 |
| 35 | 35 | # [必选 ] ffmpeg路径 |
| ... | ... | @@ -45,8 +45,7 @@ logging: |
| 45 | 45 | max-size: 10MB |
| 46 | 46 | total-size-cap: 300MB |
| 47 | 47 | level: |
| 48 | - com: | |
| 49 | - genersoft: | |
| 50 | - iot: info | |
| 51 | - net: | |
| 52 | - bramp: warning | |
| 53 | 48 | \ No newline at end of file |
| 49 | + root: WARN | |
| 50 | + top: | |
| 51 | + panll: | |
| 52 | + assist: debug | |
| 54 | 53 | \ No newline at end of file | ... | ... |