CameraUtil.java 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111
  1. package com.ozs.utils;
  2. import com.alibaba.fastjson2.JSON;
  3. import com.alibaba.fastjson2.JSONArray;
  4. import com.alibaba.fastjson2.JSONObject;
  5. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  6. import com.hikvision.artemis.sdk.ArtemisHttpUtil;
  7. import com.hikvision.artemis.sdk.config.ArtemisConfig;
  8. import com.ozs.common.config.BaseConfig;
  9. import com.ozs.common.constant.Constants;
  10. import com.ozs.common.core.domain.AjaxResult;
  11. import com.ozs.common.core.domain.entity.SysDictData;
  12. import com.ozs.common.core.redis.RedisCache;
  13. import com.ozs.common.exception.base.BaseException;
  14. import com.ozs.common.utils.DateUtils;
  15. import com.ozs.common.utils.HttpClientUtil;
  16. import com.ozs.common.utils.http.HttpUtils;
  17. import com.ozs.entity.BaseTerminal;
  18. import com.ozs.entity.MsgHeartbeatAlarmMessage;
  19. import com.ozs.entity.vo.CamerasVo;
  20. import com.ozs.entity.vo.PlaybackVo;
  21. import com.ozs.framework.config.ServerConfig;
  22. import com.ozs.service.BaseTerminalService;
  23. import com.ozs.service.MsgHeartbeatAlarmMessageService;
  24. import com.ozs.system.mapper.SysDictDataMapper;
  25. import com.ozs.system.service.ISysDictTypeService;
  26. import lombok.SneakyThrows;
  27. import lombok.extern.slf4j.Slf4j;
  28. import org.apache.commons.lang3.StringUtils;
  29. import org.springframework.beans.factory.annotation.Autowired;
  30. import org.springframework.context.annotation.Configuration;
  31. import org.springframework.util.ObjectUtils;
  32. import javax.annotation.PostConstruct;
  33. import java.io.BufferedReader;
  34. import java.io.BufferedWriter;
  35. import java.io.File;
  36. import java.io.FileFilter;
  37. import java.io.IOException;
  38. import java.io.InputStreamReader;
  39. import java.io.OutputStreamWriter;
  40. import java.io.PrintWriter;
  41. import java.text.ParseException;
  42. import java.text.SimpleDateFormat;
  43. import java.time.LocalDateTime;
  44. import java.time.OffsetDateTime;
  45. import java.time.format.DateTimeFormatter;
  46. import java.util.ArrayList;
  47. import java.util.Calendar;
  48. import java.util.Date;
  49. import java.util.GregorianCalendar;
  50. import java.util.HashMap;
  51. import java.util.List;
  52. import java.util.Map;
  53. import java.util.Random;
  54. import java.util.Set;
  55. import java.util.UUID;
  56. import java.util.concurrent.Callable;
  57. import java.util.concurrent.CompletableFuture;
  58. import java.util.concurrent.ExecutionException;
  59. import java.util.concurrent.ExecutorService;
  60. import java.util.concurrent.Executors;
  61. import java.util.concurrent.TimeUnit;
  62. import java.util.concurrent.TimeoutException;
  63. import java.util.stream.Collectors;
  64. /**
  65. * 相机工具
  66. */
  67. @Configuration
  68. @Slf4j
  69. public class CameraUtil {
  70. private static final ExecutorService executor = Executors.newFixedThreadPool(20);
  71. private static String historyUrl;
  72. private static String ffmpegPath;
  73. private static String filePath;
  74. private static String transcribeFilePath;
  75. private static String webUrl;
  76. private static String bakUrl;
  77. private static String bakUrlRtsp;
  78. private static String wsUrl;
  79. private static String httpUrl;
  80. private static CmdCameraUtil cUtil;
  81. private static RedisCache rc;
  82. private static ServerConfig sc;
  83. private static String recordUrl;
  84. private static String hkUrl;
  85. private static String host;
  86. private static String appKey;
  87. private static String appSecret;
  88. private static String rtmpUrl;
  89. @Autowired
  90. private CaneraConfig caneraConfig;
  91. @Autowired
  92. private CmdCameraUtil cmdCameraUtil;
  93. @Autowired
  94. private SysDictDataMapper dictDataMapper;
  95. @Autowired
  96. private RedisCache redisCache;
  97. @Autowired
  98. private ServerConfig serverConfig;
  99. @Autowired
  100. private BaseTerminalService baseTerminalService;
  101. @Autowired
  102. private MsgHeartbeatAlarmMessageService msgHeartbeatAlarmMessageService;
  103. @Autowired
  104. private ISysDictTypeService dictTypeService;
  105. @Autowired
  106. private RtspToMP4 rtspToMP4;
  107. private Map<String, Process> map = new HashMap<>();
  108. public final static String tsFilekey = "mergeVideoTsFile";
  109. /**
  110. * 历史回放
  111. *
  112. * @param fromVideoFileList
  113. * @param ph
  114. * @return
  115. * @throws IOException
  116. */
  117. public static String historyPlay(List<String> fromVideoFileList, String ph, boolean flay) {
  118. // 视频服务映射路径
  119. String NewfilePath = BaseConfig.getProfile() + "/" + ph;
  120. log.info("NewfilePath:{}", NewfilePath);
  121. log.info("fromVideoFileList:{}", fromVideoFileList);
  122. if (ObjectUtils.isEmpty(fromVideoFileList) || fromVideoFileList.size() <= 0) {
  123. throw new BaseException("当前相机无视频录像");
  124. }
  125. executor.submit(new Runnable() {
  126. @Override
  127. public void run() {
  128. try {
  129. txConvetor(fromVideoFileList, NewfilePath, flay);
  130. } catch (IOException e) {
  131. log.error(e.getMessage());
  132. e.printStackTrace();
  133. }
  134. }
  135. });
  136. // executor.shutdown();
  137. return Constants.RESOURCE_PREFIX + "/" + ph;
  138. }
  139. @SneakyThrows
  140. public static void txConvetor(List<String> fromVideoFileList,
  141. String newfilePath,
  142. boolean fly) throws IOException {
  143. /*
  144. * ffmpeg -i 20230411_155847_155947-d4c2265d-d83e-11ed-8e7f-fa163e4e1e9f.flv -c:v copy 1.ts
  145. ffmpeg -i 20230411_155948_160048-f91fea03-d83e-11ed-8e7f-fa163e4e1e9f.flv -c:v copy 2.ts
  146. ffmpeg -i "concat:1.ts|2.ts" -c copy output.mp4
  147. * */
  148. File file = new File(newfilePath);
  149. boolean flay = false;
  150. if (!file.getParentFile().exists()) {
  151. boolean mkdirs = file.getParentFile().mkdirs();
  152. log.info("创建文件夹:{}", file.getParentFile().getPath());
  153. log.info("创建文件夹结果:{}", mkdirs);
  154. flay = true;
  155. }
  156. cUtil.cmd("chomd -R 777 " + file.getParentFile().getPath());
  157. log.info("newfilePath:{}", newfilePath);
  158. StringBuffer sm = new StringBuffer(ffmpegPath + " -i \"concat:");
  159. List<String> fileTs = new ArrayList<>();
  160. for (int t = 0; t < fromVideoFileList.size(); t++) {
  161. File ft = new File(fromVideoFileList.get(t));
  162. if (ft.exists()) {
  163. log.info("file:{}", fromVideoFileList.get(t));
  164. String substring = fromVideoFileList.get(t).substring(0, fromVideoFileList.get(t).lastIndexOf("."));
  165. int x;//定义两变量
  166. Random ne = new Random();//实例化一个random的对象ne
  167. x = ne.nextInt(9999 - 1000 + 1) + 1000;//为变量赋随机值1000-9999
  168. substring = substring + x;//定义两变量
  169. String cmdstr = ffmpegPath + " -i " + fromVideoFileList.get(t) + " -c:v copy " + substring + ".ts";
  170. log.info("转换命令:{}", cmdstr);
  171. cUtil.cmd(cmdstr);
  172. fileTs.add(substring + ".ts");
  173. if (t != fromVideoFileList.size() - 1) {
  174. sm.append(substring + ".ts|");
  175. } else {
  176. sm.append(substring + ".ts\" ");
  177. }
  178. }
  179. }
  180. if (fileTs.size() > 0) {
  181. Map<String, Object> mergeVideoTsFile = rc.getCacheMap(tsFilekey);
  182. if (ObjectUtils.isEmpty(mergeVideoTsFile)) {
  183. mergeVideoTsFile = new HashMap<>();
  184. }
  185. if (!flay) {
  186. // 如果没有解除,把生成的文件放入要删除的定时任务 redis key 中
  187. fileTs.add(newfilePath);
  188. }
  189. mergeVideoTsFile.put(System.currentTimeMillis() + "", fileTs);
  190. rc.deleteObject(tsFilekey);
  191. if (mergeVideoTsFile.size() > 0) {
  192. rc.setCacheMap(tsFilekey, mergeVideoTsFile);
  193. }
  194. sm.append("-c copy " + newfilePath);
  195. log.info("合并命令:{}", sm.toString());
  196. cUtil.cmd(sm.toString());
  197. }
  198. }
  199. /**
  200. * 实时播放的拼接流
  201. *
  202. * @param cameraCode 相机编码
  203. * @param channel 相机通道
  204. * @return
  205. */
  206. public static String getPlayFlv(String cameraCode, String channel, boolean flay) {
  207. if (!flay) {
  208. return bakUrl + "/hdl/" + cameraCode + "/" + channel + ".flv";
  209. }
  210. return httpUrl + "/hdl/" + cameraCode + "/" + channel + ".flv";
  211. // if (!flay) {
  212. // return bakUrl + "/ws/" + channel + "/" + cameraCode + ".flv";
  213. // }
  214. // return webUrl + "/ws/" + channel + "/" + cameraCode + ".flv";
  215. }
  216. /**
  217. * Rtsp实时播放的拼接流
  218. *
  219. * @param cameraCode 相机编码
  220. * @param channel 相机通道
  221. * @return
  222. */
  223. public static String getPlayFlvRtsp(String cameraCode, String channel, boolean flay) {
  224. if (!flay) {
  225. return bakUrlRtsp + "/" + channel + "/" + cameraCode;
  226. }
  227. return bakUrlRtsp + "/" + channel + "/" + cameraCode;
  228. // if (!flay) {
  229. // return bakUrl + "/ws/" + channel + "/" + cameraCode + ".flv";
  230. // }
  231. // return webUrl + "/ws/" + channel + "/" + cameraCode + ".flv";
  232. }
  233. /**
  234. * web页面实时流接口
  235. *
  236. * @param cameraCode
  237. * @param channel
  238. * @return
  239. */
  240. public static String getPlayFlv(String cameraCode, String channel) {
  241. List<String> pathList = new ArrayList<>();
  242. String data = HttpUtils.sendGet(webUrl + "/rtsp/api/list");
  243. if (StringUtils.isNotEmpty(data)) {
  244. JSONArray jsonArray = JSONArray.parseArray(data);
  245. for (int i = 0; i < jsonArray.size(); i++) {
  246. JSONObject jsonObject = jsonArray.getJSONObject(i);
  247. String name = jsonObject.getString("Path");
  248. pathList.add(name);
  249. }
  250. boolean contains = pathList.contains(cameraCode + "/" + channel);
  251. if (contains) {
  252. return getPlayFlv(cameraCode, channel, true);
  253. } else {
  254. /**
  255. * jsonBody.put("cameraIndexCode", "01ea43e6676f4e47bd6c5cd9e02aa006");
  256. * jsonBody.put("streamType", 0);
  257. * jsonBody.put("protocol","rtsp");
  258. * jsonBody.put("transmode", 1);
  259. * jsonBody.put("expand","streamform=rtp");
  260. */
  261. CamerasVo camerasVo = new CamerasVo();
  262. camerasVo.setCameraIndexCode(cameraCode);
  263. previewURLs(camerasVo, channel);
  264. return getPlayFlv(cameraCode, channel, true);
  265. }
  266. } else {
  267. CamerasVo camerasVo = new CamerasVo();
  268. camerasVo.setCameraIndexCode(cameraCode);
  269. previewURLs(camerasVo, channel);
  270. return getPlayFlv(cameraCode, channel, true);
  271. }
  272. // return getPlayFlv(cameraCode, channel, true);
  273. }
  274. public static String invite(String cameraCode, String channel) {
  275. String result = null;
  276. String url = bakUrl + "/api/gb28181/invite?id=" + cameraCode + "&channel=" + channel;
  277. try {
  278. result = HttpClientUtil.get(url);
  279. log.info("result:{}", result);
  280. } catch (Exception e) {
  281. log.info(e.getMessage());
  282. e.printStackTrace();
  283. }
  284. return result;
  285. }
  286. /**
  287. * 开启录制功能
  288. *
  289. * @param cameraCode 相机编码
  290. * @param channel 相机通道
  291. * @return
  292. */
  293. public static String startRecording(String cameraCode, String channel) {
  294. return historyUrl + "/recordpro/api/start?streamPath=" + channel + "/" + cameraCode;
  295. }
  296. /**
  297. * 关闭录制功能
  298. *
  299. * @param taskId 录像接口返回的任务ID
  300. * @return
  301. */
  302. public static String endRecording(String taskId) {
  303. return historyUrl + "/recordpro/api/stop?id=" + taskId;
  304. }
  305. /**
  306. * 历史回放流(获取)
  307. *
  308. * @param channel 相机通道
  309. * @param startTm 开始时间
  310. * @param endTm 结束时间
  311. * @return
  312. */
  313. public static String historyPlayListStr(String channel, Date startTm, Date endTm, boolean flay) {
  314. List<String> list = filterPlayList(channel, startTm, endTm, filePath);
  315. String uuid = UUID.randomUUID().toString();
  316. String ph = "record/flv/" + DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, new Date())
  317. + "/" + channel + "/"
  318. + uuid + ".mp4";
  319. if (!ObjectUtils.isEmpty(list)) {
  320. try {
  321. return historyPlay(list, ph, flay);
  322. } catch (Exception e) {
  323. log.error(e.getMessage());
  324. e.printStackTrace();
  325. }
  326. } else {
  327. throw new BaseException("当前相机无视频录像");
  328. }
  329. return null;
  330. }
  331. /**
  332. * 过滤符合条件的视频
  333. *
  334. * @param channel
  335. * @param startTm
  336. * @param endTm
  337. * @param mappingUrl
  338. * @return
  339. */
  340. public static List<String> filterPlayList(String channel, Date startTm, Date endTm, String mappingUrl) {
  341. if (StringUtils.isBlank(channel)
  342. || ObjectUtils.isEmpty(startTm)
  343. || ObjectUtils.isEmpty(endTm)) {
  344. return null;
  345. }
  346. List<String> ls = new ArrayList<>();
  347. Map<Date, String> m = new HashMap<>();
  348. // 调用视频服务返回参数
  349. String startTime = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, startTm);
  350. String endTime = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, endTm);
  351. String param = "channel=" + channel + "&startTime=" + startTime + "&endTime=" + endTime;
  352. String s = HttpUtils.sendGet(historyUrl + "/api/record/flv/list", param);
  353. // 视频拼接
  354. if (!StringUtils.isBlank(s) && !"null".equals(s) && !s.startsWith("<!DOCTYPE html>")) {
  355. List<Map<String, Object>> maps = JSON.parseArray(s, Map.class);
  356. if (ObjectUtils.isEmpty(maps)) {
  357. return null;
  358. }
  359. for (Map<String, Object> map : maps) {
  360. Object path = map.get("Path");
  361. if (!ObjectUtils.isEmpty(path)) {
  362. String s1 = path.toString();
  363. String substring = s1.substring(s1.lastIndexOf("/") + 1, s1.length());
  364. String substring1 = substring.substring(0, substring.indexOf("-"));
  365. String[] s2 = substring1.split("_");
  366. if (!ObjectUtils.isEmpty(s2)) {
  367. String s3 = s2[0] + s2[1];
  368. String s4 = s2[0] + s2[2];
  369. Date sdate = DateUtils.dateTime(DateUtils.YYYYMMDDHHMMSS, s3);
  370. if (s2[1].startsWith("23") && s2[2].startsWith("00")) {
  371. sdate = DateUtils.addDays(sdate, -1);
  372. }
  373. Date edate = DateUtils.dateTime(DateUtils.YYYYMMDDHHMMSS, s4);
  374. /* sdate |startTm| edate |endTm| */
  375. if (startTm.compareTo(sdate) >= 0
  376. && startTm.compareTo(edate) <= 0
  377. && endTm.compareTo(edate) >= 0) {
  378. m.put(sdate, mappingUrl + path.toString());
  379. /* |startTm| sdate edate |endTm| */
  380. } else if (startTm.compareTo(sdate) <= 0 && endTm.compareTo(edate) >= 0) {
  381. m.put(sdate, mappingUrl + path.toString());
  382. /* |startTm| sdate |endTm| edate */
  383. } else if (startTm.compareTo(sdate) <= 0
  384. && endTm.compareTo(sdate) >= 0
  385. && endTm.compareTo(edate) <= 0) {
  386. m.put(sdate, mappingUrl + path.toString());
  387. /* sdate |startTm| |endTm| edate */
  388. } else if (startTm.compareTo(sdate) >= 0 && endTm.compareTo(edate) <= 0) {
  389. m.put(sdate, mappingUrl + path.toString());
  390. }
  391. }
  392. }
  393. }
  394. }
  395. if (!ObjectUtils.isEmpty(m) && m.size() > 0) {
  396. Set<Date> dates = m.keySet();
  397. // 排序
  398. dates.stream().parallel().collect(Collectors.toList()).stream().sorted().forEach(d -> {
  399. ls.add(m.get(d));
  400. });
  401. return ls;
  402. }
  403. return null;
  404. }
  405. /**
  406. * 合并视频(转化文件)
  407. *
  408. * @param fromVideoFileList 视频路径
  409. * @param newfilePath 生产新的视频文件路径
  410. * @throws IOException
  411. */
  412. public static Map<String, String> myConvetor(List<String> fromVideoFileList,
  413. String newfilePath,
  414. String uuid) throws IOException {
  415. /*
  416. * for f in *.flv; do echo "file '$f'" >> mylist.txt; done
  417. ffmpeg -f concat -i mylist.txt -c copy output.flv
  418. * */
  419. File file = new File(newfilePath);
  420. boolean flay = false;
  421. if (!file.getParentFile().exists()) {
  422. file.getParentFile().mkdirs();
  423. log.info("创建文件夹:{}", file.getParentFile().getPath());
  424. flay = true;
  425. }
  426. log.info("newfilePath:{}", newfilePath);
  427. StringBuffer sm = new StringBuffer("for f in ");
  428. String pathStr = null;
  429. for (int t = 0; t < fromVideoFileList.size(); t++) {
  430. File ft = new File(fromVideoFileList.get(t));
  431. if (ft.exists()) {
  432. if (t != fromVideoFileList.size() - 1) {
  433. // sm.append(fromVideoFileList.get(t) + " ");
  434. sm.append(fromVideoFileList.get(t).substring(fromVideoFileList.get(t).lastIndexOf("/") + 1, fromVideoFileList.get(t).length()) + " ");
  435. } else {
  436. // sm.append(fromVideoFileList.get(t));
  437. sm.append(fromVideoFileList.get(t).substring(fromVideoFileList.get(t).lastIndexOf("/") + 1, fromVideoFileList.get(t).length()));
  438. pathStr = fromVideoFileList.get(t).substring(0, fromVideoFileList.get(t).lastIndexOf("/"));
  439. }
  440. }
  441. }
  442. // String substring = newfilePath.substring(0, newfilePath.lastIndexOf("."));
  443. // substring = substring + ".txt";
  444. String substring = uuid + ".txt";
  445. log.info("临时转化的文件:{}", substring);
  446. sm.append("; do echo \"file '$f'\" >> " + substring + "; done");
  447. String commit = sm.toString();
  448. log.info("合并转化文件的命令:{}", commit);
  449. Process proc = Runtime.getRuntime().exec("/bin/bash", null, null);
  450. BufferedReader in = new BufferedReader(new InputStreamReader(proc.getInputStream()));
  451. PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(proc.getOutputStream())), true);
  452. try {
  453. String commit1 = ffmpegPath + " -f concat -safe 0 -i " + pathStr + "/" + substring + " -c copy " + newfilePath;
  454. log.info("合并视频命令:{}", commit1);
  455. List<String> commands = new ArrayList<>();
  456. if (flay) {
  457. commands.add("chomd -R 777 " + file.getParentFile().getPath());
  458. }
  459. //
  460. commands.add("cd " + pathStr);
  461. // 合并成一个临时文件
  462. commands.add(commit);
  463. // 临时文件转化为 flv
  464. // commands.add(commit1);
  465. log.info("录像视频命令:{}", commands);
  466. for (String line : commands) {
  467. log.info("命令:{}", line);
  468. out.println(line);
  469. }
  470. out.println("exit");// 这个命令必须执行,否则in流不结束。
  471. log.info("命令:{}", "exit");
  472. String rspLine = "";
  473. while ((rspLine = in.readLine()) != null) {
  474. log.info("*****:{}", rspLine);
  475. }
  476. proc.waitFor();
  477. Map<String, String> map = new HashMap<>();
  478. map.put("cmd", commit1);
  479. map.put("path", pathStr + "/" + substring);
  480. return map;
  481. } catch (InterruptedException e) {
  482. log.error(e.getMessage());
  483. e.printStackTrace();
  484. } finally {
  485. in.close();
  486. out.close();
  487. proc.destroy();
  488. }
  489. return null;
  490. }
  491. @PostConstruct
  492. public void init() {
  493. historyUrl = caneraConfig.getHistoryUrl();
  494. ffmpegPath = caneraConfig.getFfmpegPath();
  495. filePath = caneraConfig.getFilePath();
  496. transcribeFilePath = caneraConfig.getTranscribeFilePath();
  497. webUrl = caneraConfig.getWebUrl();
  498. bakUrl = caneraConfig.getBakUrl();
  499. bakUrlRtsp = caneraConfig.getBakUrlRtsp();
  500. rc = redisCache;
  501. cUtil = cmdCameraUtil;
  502. sc = serverConfig;
  503. wsUrl = caneraConfig.getWsUrl();
  504. httpUrl=caneraConfig.getHttpUrl();
  505. recordUrl = caneraConfig.getRecordUrl();
  506. hkUrl = caneraConfig.getHkUrl();
  507. host = caneraConfig.getHost();
  508. appKey = caneraConfig.getAppKey();
  509. appSecret = caneraConfig.getAppSecret();
  510. rtmpUrl = caneraConfig.getRtmpUrl();
  511. }
  512. /**
  513. * 定时任务参数flv文件
  514. *
  515. * @throws IOException
  516. * @throws InterruptedException
  517. */
  518. public void deleteFlv() throws IOException, InterruptedException {
  519. SimpleDateFormat s = new SimpleDateFormat("yyyy-MM-dd");
  520. Date date = new Date();
  521. Calendar calendar = new GregorianCalendar();
  522. calendar.setTime(date);
  523. calendar.add(Calendar.DATE, -1); //把日期往后增加一天,整数 往后推,负数往前移动
  524. date = calendar.getTime(); //这个时间就是日期往后推一天的结果
  525. String path = BaseConfig.getProfile() + "/flv/" + DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, date);
  526. File file = new File(path);
  527. if (file.exists()) {
  528. List<String> rspList = new ArrayList<String>();
  529. Process proc = Runtime.getRuntime().exec("/bin/bash", null, null);
  530. BufferedReader in = new BufferedReader(new InputStreamReader(proc.getInputStream()));
  531. PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(proc.getOutputStream())), true);
  532. String commit = " rm -rf " + path;
  533. List<String> commands = new ArrayList<>();
  534. // 删除
  535. commands.add(commit);
  536. log.info("删除昨天的录像视频命令:{}", commands);
  537. for (String line : commands) {
  538. out.println(line);
  539. }
  540. out.println("exit");// 这个命令必须执行,否则in流不结束。
  541. String rspLine = "";
  542. while ((rspLine = in.readLine()) != null) {
  543. System.out.println(rspLine);
  544. rspList.add(rspLine);
  545. }
  546. int i = proc.waitFor();
  547. log.info("执行结果:{}", i);
  548. in.close();
  549. out.close();
  550. proc.destroy();
  551. }
  552. }
  553. /**
  554. * 定时任务:删除超过配置时长的录制视频
  555. *
  556. * @throws IOException
  557. * @throws InterruptedException
  558. */
  559. public void deleteFlvExceed() throws IOException, InterruptedException, ParseException {
  560. //字典中设置的值
  561. List<SysDictData> sysCameraRecordTime = dictDataMapper.selectDictDataByType("sys_camera_record_time");
  562. SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
  563. Date date = new Date();
  564. Calendar calendar = new GregorianCalendar();
  565. calendar.setTime(date);
  566. //正数,日期天数加
  567. //负数,日期天数减 提前45天
  568. if (!ObjectUtils.isEmpty(sysCameraRecordTime)) {
  569. String dictValue = sysCameraRecordTime.get(0).getDictValue();
  570. calendar.add(Calendar.DAY_OF_YEAR, Integer.parseInt("-" + dictValue));
  571. } else {
  572. calendar.add(Calendar.DAY_OF_YEAR, -45);
  573. }
  574. date = calendar.getTime();
  575. //遍历每个进行视频录制的摄像头
  576. File file = new File(transcribeFilePath);
  577. log.info("file.Name()======================================" + file.getName());
  578. log.info("file.exists()======================================" + file.exists());
  579. if (file.exists() && file.isDirectory()) {
  580. //获取文件夹中所有的子文件夹和文件
  581. File[] files = file.listFiles();
  582. if (!ObjectUtils.isEmpty(files) && files.length > 0) {
  583. for (File file1 : files) {
  584. // file1=/opt/streams/record/flv/34020000001320000167
  585. log.info("file1.Name()======================================" + file1.getName());
  586. if (file1.exists() && file1.isDirectory()) {
  587. //相机文件
  588. File[] fs = file1.listFiles();
  589. if (!ObjectUtils.isEmpty(fs) && fs.length > 0) {
  590. for (File f : fs) {
  591. // f=/opt/streams/record/flv/34020000001320000167/20230328_192033_192037-91509173-cd5a-11ed-8a42-fa163e4e1e9f.flv
  592. //fName=20230328_192033_192037-91509173-cd5a-11ed-8a42-fa163e4e1e9f.flv
  593. String fName = f.getName();
  594. log.info("fName======================================" + fName);
  595. String[] split = fName.split("_");
  596. if (split.length > 0) {
  597. //20230328
  598. String s = split[0];
  599. Date parse = sdf.parse(s);
  600. long fTime = parse.getTime();
  601. long dateTime = date.getTime();
  602. //删除过期文件
  603. if (fTime < dateTime) {
  604. f.delete();
  605. }
  606. }
  607. }
  608. }
  609. }
  610. }
  611. }
  612. }
  613. }
  614. public static File[] getCurFilesList(String filePath) {
  615. File path = new File(filePath);
  616. File[] listFiles = path.listFiles(new FileFilter() {
  617. @Override
  618. public boolean accept(File pathname) {
  619. if (pathname.isFile()) {
  620. return true;
  621. } else {
  622. return false;
  623. }
  624. }
  625. });
  626. return listFiles;
  627. }
  628. public static void execute(String command) {
  629. try {
  630. ProcessBuilder process = new ProcessBuilder(command);
  631. process.inheritIO().start().waitFor();
  632. } catch (Exception e) {
  633. e.printStackTrace();
  634. }
  635. }
  636. /**
  637. * web页面视频回放接口
  638. *
  639. * @param code
  640. * @param startTm
  641. * @param endTm
  642. * @return
  643. */
  644. public static String getRecordList(String code, Date startTm, Date endTm) {
  645. PlaybackVo playbackVo = new PlaybackVo();
  646. playbackVo.setCameraIndexCode(code);
  647. // 格式化时间
  648. SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
  649. playbackVo.setBeginTime(dateFormat.format(startTm));
  650. playbackVo.setEndTime(dateFormat.format(endTm));
  651. playbackVo.setProtocol("ws");
  652. //对象转换json字符串
  653. String body = JSONObject.toJSONString(playbackVo);
  654. //调用天网接口
  655. String playbackURLs = getPreviewURLs("/api/video/v1/cameras/playbackURLs", body);
  656. JSONObject outJson = (JSONObject) JSONObject.parse(playbackURLs);
  657. log.info("outJson-------->" + outJson);
  658. if ("0".equals(outJson.getString("code"))) {
  659. JSONObject data = outJson.getJSONObject("data");
  660. String urls = data.getString("url");
  661. return urls;
  662. } else {
  663. return "调用天网接口失败";
  664. }
  665. // return filterRecordList(channel, startTm, endTm, filePath, recordUrl + "profile/");
  666. }
  667. /**
  668. * HTTP流转RTSP流
  669. */
  670. public String streamConversion(String code) {
  671. ExecutorService executor = Executors.newSingleThreadExecutor();
  672. Callable<String> task = new Callable<String>() {
  673. @Override
  674. public String call() throws Exception {
  675. // 执行网络请求...
  676. log.info(ffmpegPath + " -re -i /opt/streams/compress/70b0bd685b0d4df1b4faf74ff5c1e7fd.mp4 -c:v copy -c:a copy -f flv rtmp://10.48.36.47:1935/live/70b0bd685b0d4df1b4faf74ff5c1e7fd");
  677. cmdCameraUtil.cmd(ffmpegPath + " -re -i /opt/streams/compress/" + code + ".mp4 -c:v copy -c:a copy -f flv " + rtmpUrl + "/live/" + code);
  678. return "Response";
  679. }
  680. };
  681. try {
  682. String result = executor.submit(task).get(1, TimeUnit.SECONDS); // 设置5秒超时时间
  683. System.out.println("Response: " + result);
  684. } catch (InterruptedException | ExecutionException | TimeoutException e) {
  685. // 请求超时处理逻辑
  686. System.out.println("Request timeout");
  687. }
  688. executor.shutdown();
  689. return bakUrlRtsp + "/live/" + code;
  690. }
  691. /**
  692. * RTSP流视频压缩
  693. */
  694. public void videoCompression(String code) {
  695. // 执行网络请求...
  696. /**
  697. * /usr/bin/ffmpeg -i /opt/streams/map/70b0bd685b0d4df1b4faf74ff5c1e7fd.mp4 -c:v libx264 -s 640x480 -c:a aac -ar 44100 /opt/streams/compress/70b0bd685b0d4df1b4faf74ff5c1e7fd.mp4
  698. */
  699. log.info(ffmpegPath + " -i /opt/streams/map/" + code + ".mp4 -c:v libx264 -s 640x480 -c:a aac -ar 44100 /opt/streams/compress/" + code + ".mp4");
  700. cmdCameraUtil.cmd(ffmpegPath + " -i /opt/streams/map/" + code + ".mp4 -c:v libx264 -s 640x480 -c:a aac -ar 44100 /opt/streams/compress/" + code + ".mp4");
  701. }
  702. public static List<Map<String, Object>> filterRecordList(String channel,
  703. Date startTm,
  704. Date endTm,
  705. String mappingUrl,
  706. String wUrl) {
  707. List<Map<String, Object>> rmaps = new ArrayList<>();
  708. if (StringUtils.isBlank(channel)
  709. || ObjectUtils.isEmpty(startTm)
  710. || ObjectUtils.isEmpty(endTm)) {
  711. return null;
  712. }
  713. Map<Date, Map<String, Object>> m = new HashMap<>();
  714. // 调用视频服务返回参数
  715. String startTime = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, startTm);
  716. String endTime = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, endTm);
  717. String param = "channel=" + channel + "&startTime=" + startTime + "&endTime=" + endTime;
  718. // /api/record/flv/list
  719. String s = HttpUtils.sendGet(webUrl + "/recordpro/api/list", param);
  720. // 视频拼接
  721. if (!StringUtils.isBlank(s) || "null".equals(s)) {
  722. List<Map<String, Object>> maps = JSON.parseArray(s, Map.class);
  723. if (ObjectUtils.isEmpty(maps)) {
  724. return null;
  725. }
  726. for (Map<String, Object> map : maps) {
  727. Object path = map.get("Path");
  728. Object size = map.get("Size");
  729. Object duration = map.get("Duration");
  730. if (!ObjectUtils.isEmpty(path)) {
  731. String s1 = path.toString();
  732. String substring = s1.substring(s1.lastIndexOf("/") + 1, s1.length());
  733. String substring1 = substring.substring(0, substring.indexOf("-"));
  734. String[] s2 = substring1.split("_");
  735. if (!ObjectUtils.isEmpty(s2)) {
  736. Map<String, Object> mo = new HashMap<>();
  737. String s3 = s2[0] + s2[1];
  738. String s4 = s2[0] + s2[2];
  739. Date sdate = DateUtils.dateTime(DateUtils.YYYYMMDDHHMMSS, s3);
  740. if (s2[1].startsWith("23") && s2[2].startsWith("00")) {
  741. sdate = DateUtils.addDays(sdate, -1);
  742. }
  743. Date edate = DateUtils.dateTime(DateUtils.YYYYMMDDHHMMSS, s4);
  744. mo.put("startTime", DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, sdate));
  745. mo.put("entTime", DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, edate));
  746. mo.put("url", wUrl + path.toString());
  747. mo.put("path", mappingUrl + path.toString());
  748. mo.put("fileName", substring);
  749. mo.put("size", size);
  750. mo.put("duration", duration);
  751. /* sdate |startTm| edate |endTm| */
  752. if (startTm.compareTo(sdate) >= 0
  753. && startTm.compareTo(edate) <= 0
  754. && endTm.compareTo(edate) >= 0) {
  755. m.put(sdate, mo);
  756. /* |startTm| sdate edate |endTm| */
  757. } else if (startTm.compareTo(sdate) <= 0 && endTm.compareTo(edate) >= 0) {
  758. m.put(sdate, mo);
  759. /* |startTm| sdate |endTm| edate */
  760. } else if (startTm.compareTo(sdate) <= 0
  761. && endTm.compareTo(sdate) >= 0
  762. && endTm.compareTo(edate) <= 0) {
  763. m.put(sdate, mo);
  764. /* sdate |startTm| |endTm| edate */
  765. } else if (startTm.compareTo(sdate) >= 0 && endTm.compareTo(edate) <= 0) {
  766. m.put(sdate, mo);
  767. }
  768. }
  769. }
  770. }
  771. }
  772. if (!ObjectUtils.isEmpty(m) && m.size() > 0) {
  773. Set<Date> dates = m.keySet();
  774. // 排序
  775. dates.stream().parallel().collect(Collectors.toList()).stream().sorted().forEach(d -> {
  776. rmaps.add(m.get(d));
  777. });
  778. log.info("rmaps:{}", rmaps);
  779. return rmaps;
  780. }
  781. return null;
  782. }
  783. /**
  784. * 机车状态修改
  785. */
  786. public void heartbeat() {
  787. List<BaseTerminal> list = baseTerminalService.list();
  788. for (BaseTerminal baseTerminal : list) {
  789. QueryWrapper<MsgHeartbeatAlarmMessage> wrapper = new QueryWrapper<>();
  790. wrapper.eq("terminal_code", wrapper);
  791. wrapper.orderByDesc("create_time");
  792. wrapper.last("limit 1");
  793. MsgHeartbeatAlarmMessage msgHeartbeatAlarmMessage = msgHeartbeatAlarmMessageService.getOne(wrapper);
  794. if (ObjectUtils.isEmpty(msgHeartbeatAlarmMessage)) {
  795. baseTerminal.setStatus(2);
  796. baseTerminalService.updateById(baseTerminal);
  797. } else {
  798. List<SysDictData> heartbeat = dictTypeService.selectDictDataByType("heartbeat");
  799. long timeNow = System.currentTimeMillis();
  800. long createTime = msgHeartbeatAlarmMessage.getCreateTime().getTime();
  801. int times = Integer.parseInt(heartbeat.get(0).getDictValue());
  802. long thereHourMillis = 60 * 1000 * 3 * times;
  803. long timeNew = timeNow - thereHourMillis;
  804. if (timeNew > createTime) {
  805. baseTerminal.setStatus(2);
  806. baseTerminalService.updateById(baseTerminal);
  807. } else {
  808. baseTerminal.setStatus(1);
  809. baseTerminalService.updateById(baseTerminal);
  810. }
  811. }
  812. }
  813. }
  814. /**
  815. * 天网接口
  816. *
  817. * @return
  818. */
  819. public static String getPreviewURLs(String url, String body) {
  820. /**
  821. * STEP1:设置平台参数,根据实际情况,设置host appkey appsecret 三个参数.
  822. */
  823. ArtemisConfig.host = host; // 平台的ip端口
  824. ArtemisConfig.appKey = appKey; // 密钥appkey
  825. ArtemisConfig.appSecret = appSecret;// 密钥appSecret
  826. /**
  827. * STEP2:设置OpenAPI接口的上下文
  828. */
  829. final String ARTEMIS_PATH = "/artemis";
  830. /**
  831. * STEP3:设置接口的URI地址
  832. */
  833. final String previewURLsApi = ARTEMIS_PATH + url;
  834. Map<String, String> path = new HashMap<String, String>(2) {
  835. {
  836. put("https://", previewURLsApi);//根据现场环境部署确认是http还是https
  837. }
  838. };
  839. /**
  840. * STEP4:设置参数提交方式
  841. */
  842. String contentType = "application/json";
  843. /**
  844. * STEP6:调用接口
  845. */
  846. String result = ArtemisHttpUtil.doPostStringArtemis(path, body, null, null, contentType, null);// post请求application/json类型参数
  847. log.info("GetCameraPreviewURL----->" + result);
  848. return result;
  849. }
  850. /**
  851. * 车载终端实时流调用的远程天网接口
  852. *
  853. * @param camerasVo
  854. * @param channel
  855. */
  856. public String apiPreviewURLs(CamerasVo camerasVo) {
  857. /**
  858. * jsonBody.put("cameraIndexCode", "01ea43e6676f4e47bd6c5cd9e02aa006");
  859. * jsonBody.put("streamType", 0);
  860. * jsonBody.put("protocol","rtsp");
  861. * jsonBody.put("transmode", 1);
  862. * jsonBody.put("expand","streamform=rtp");
  863. */
  864. camerasVo.setProtocol("rtsp");
  865. camerasVo.setTransmode(1);
  866. camerasVo.setExpand("streamform=rtp");
  867. String body = JSONObject.toJSONString(camerasVo);
  868. String previewURLs = getPreviewURLs("/api/video/v1/cameras/previewURLs", body);
  869. log.info("-------------------------------->>>>>previewURLs" + previewURLs);
  870. JSONObject outJson = JSONObject.parse(previewURLs);
  871. if ("0".equals(outJson.getString("code"))) {
  872. log.info("outJson----->" + outJson);
  873. JSONObject data = outJson.getJSONObject("data");
  874. String urls = data.getString("url");
  875. return urls;
  876. } else {
  877. return "1";
  878. }
  879. }
  880. /**
  881. * web页面实时流调用的远程天网接口
  882. *
  883. * @param camerasVo
  884. * @param channel
  885. */
  886. public static void previewURLs(CamerasVo camerasVo, String channel) {
  887. /**
  888. * jsonBody.put("cameraIndexCode", "01ea43e6676f4e47bd6c5cd9e02aa006");
  889. * jsonBody.put("streamType", 0);
  890. * jsonBody.put("protocol","rtsp");
  891. * jsonBody.put("transmode", 1);
  892. * jsonBody.put("expand","streamform=rtp");
  893. */
  894. camerasVo.setStreamType(0);
  895. camerasVo.setProtocol("rtsp");
  896. camerasVo.setTransmode(1);
  897. camerasVo.setExpand("streamform=rtp");
  898. String body = JSONObject.toJSONString(camerasVo);
  899. String previewURLs = getPreviewURLs("/api/video/v1/cameras/previewURLs", body);
  900. log.info("-------------------------------->>>>>previewURLs" + previewURLs);
  901. JSONObject outJson = JSONObject.parse(previewURLs);
  902. if ("0".equals(outJson.getString("code"))) {
  903. log.info("outJson----->" + outJson);
  904. JSONObject data = outJson.getJSONObject("data");
  905. String urls = data.getString("url");
  906. log.info("urls----->" + urls);
  907. try {
  908. TimeUnit.SECONDS.sleep(5);
  909. String msg = HttpUtils.sendGet(httpUrl + "/rtsp/api/pull?target=" + urls + "&streamPath=" + camerasVo.getCameraIndexCode() + "/" + channel + "&save=0");
  910. log.info("msg---------->"+msg);
  911. } catch (InterruptedException e) {
  912. e.printStackTrace();
  913. }
  914. }else {
  915. log.info("流媒体获取流失败----->");
  916. }
  917. }
  918. /**
  919. * 报警回放本地测试
  920. * 从天网拉回放流,然后根据url把流下载下来变成文件,然后进行视频压缩
  921. *
  922. * @param list
  923. * @param alarmPlayTimeValue
  924. */
  925. public void playbackURLs(List<String> list, String alarmPlayTimeValue) {
  926. log.info("--------------->playbackURLs");
  927. CompletableFuture future = CompletableFuture.supplyAsync(() -> {
  928. log.info("异步任务开始-----》");
  929. // for (String code : list) {
  930. String code = "70b0bd685b0d4df1b4faf74ff5c1e7fd";
  931. PlaybackVo playbackVo = new PlaybackVo();
  932. playbackVo.setCameraIndexCode(code);
  933. // 获取当前时间
  934. OffsetDateTime currentTime = OffsetDateTime.now();
  935. // 获取当前时间的前10分钟时间
  936. OffsetDateTime beforeTenMinutes = currentTime.minusMinutes(Long.parseLong(alarmPlayTimeValue));
  937. // 获取当前时间的后10分钟时间
  938. OffsetDateTime afterTenMinutes = currentTime.plusMinutes(Long.parseLong(alarmPlayTimeValue));
  939. // 格式化时间
  940. DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
  941. playbackVo.setBeginTime(beforeTenMinutes.format(formatter));
  942. //现在当前时间:后续需要修改成报警时间的后几分钟
  943. playbackVo.setEndTime(currentTime.format(formatter));
  944. // jsonBody.put("protocol","rtsp");
  945. // jsonBody.put("expand","streamform=rtp");
  946. playbackVo.setProtocol("rtsp");
  947. playbackVo.setExpand("streamform=rtp");
  948. //对象转换json字符串
  949. String body = JSONObject.toJSONString(playbackVo);
  950. //调用天网接口
  951. log.info("body----->" + body);
  952. String playbackURLs = getPreviewURLs("/api/video/v1/cameras/playbackURLs", body);
  953. log.info("playbackURLs----->" + playbackURLs);
  954. JSONObject outJson = (JSONObject) JSONObject.parse(playbackURLs);
  955. if ("0".equals(outJson.getString("code"))) {
  956. log.info("outJson----->" + outJson);
  957. JSONObject data = outJson.getJSONObject("data");
  958. String urls = data.getString("url");
  959. log.info("--------------->urls::" + urls);
  960. String FilePath = "/opt/streams/map/" + code + ".mp4";
  961. // String FilePath = "/opt/streams/map/01ea43e6676f4e47bd6c5cd9e02aa006.mp4";
  962. // try {
  963. // TimeUnit.SECONDS.sleep(62*Integer.parseInt(alarmPlayTimeValue));
  964. // } catch (InterruptedException e) {
  965. // e.printStackTrace();
  966. // }
  967. Process process = rtspToMP4.StartRecord(ffmpegPath, urls, FilePath);
  968. log.info("------playbackURLs----->>>>:" + process);
  969. if (null != process) {
  970. map.put(code, process);
  971. }
  972. try {
  973. TimeUnit.SECONDS.sleep(70 * Integer.parseInt(alarmPlayTimeValue));
  974. } catch (InterruptedException e) {
  975. e.printStackTrace();
  976. }
  977. log.info("------videoCompression----->>>>:");
  978. videoCompression(code);
  979. }
  980. return 1;
  981. });
  982. future.join();
  983. }
  984. public AjaxResult stop(String id) {
  985. if (map.containsKey(id)) {
  986. Process process = map.get(id);
  987. log.info("-----stop------>>>" + process);
  988. if (null != process) {
  989. rtspToMP4.stopRecord(process);
  990. return AjaxResult.success();
  991. }
  992. }
  993. return AjaxResult.error();
  994. }
  995. public static void main(String[] args) throws InterruptedException, ParseException, IOException {
  996. // CameraUtil cameraUtil = new CameraUtil();
  997. // cameraUtil.closeRecording();
  998. // String s = "/opt/streams/record/flv/42010001541320001116/20230403_235506_000506-22e1523b-d170-11ed-8a42-fa163e4e1e9f.flv";
  999. // String fileStr = s.substring(s.lastIndexOf("/") + 1, s.length());
  1000. // String pathStr = s.substring(0, s.lastIndexOf("/"));
  1001. // System.out.println(fileStr);
  1002. // System.out.println(pathStr);
  1003. String s = "20230403235512";
  1004. Date sdate = DateUtils.dateTime(DateUtils.YYYYMMDDHHMMSS, s);
  1005. sdate = DateUtils.addDays(sdate, -1);
  1006. System.out.println(DateUtils.parseDateToStr(DateUtils.YYYYMMDDHHMMSS, sdate));
  1007. }
  1008. }