content: package top.panll.assist.service;
  
  import net.bramp.ffmpeg.FFprobe;
  import net.bramp.ffmpeg.probe.FFmpegProbeResult;
  import net.bramp.ffmpeg.progress.Progress;
  import org.apache.commons.io.FileUtils;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.data.redis.core.RedisTemplate;
  import org.springframework.stereotype.Service;
  import org.springframework.util.DigestUtils;
  import org.springframework.util.ObjectUtils;
  import top.panll.assist.controller.bean.ControllerException;
  import top.panll.assist.controller.bean.ErrorCode;
  import top.panll.assist.dto.*;
  import top.panll.assist.utils.RedisUtil;
  import top.panll.assist.utils.DateUtils;
  
  import java.io.File;
  import java.io.IOException;
  import java.nio.file.Files;
  import java.nio.file.Path;
  import java.nio.file.Paths;
  import java.nio.file.attribute.BasicFileAttributes;
  import java.text.ParseException;
  import java.text.SimpleDateFormat;
  import java.util.*;
  
  @Service
  public class VideoFileService {
  
      private final static Logger logger = LoggerFactory.getLogger(VideoFileService.class);
  
      @Autowired
      private UserSettings userSettings;
  
      @Autowired
      private RedisUtil redisUtil;
  
      @Autowired
      private FFmpegExecUtils ffmpegExecUtils;
  
  
  
      private final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
      private final SimpleDateFormat simpleDateFormatForTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  
      public List<File> getAppList(Boolean sort) {
          File recordFile = new File(userSettings.getRecordTempPath());
          if (recordFile.isDirectory()) {
              File[] files = recordFile.listFiles((File dir, String name) -> {
                  File currentFile = new File(dir.getAbsolutePath() + File.separator + name);
                  return  currentFile.isDirectory() && !name.equals("recordTemp");
              });
              List<File> result = Arrays.asList(files);
              if (sort != null && sort) {
                  Collections.sort(result);
              }
              return result;
          }else {
              return null;
          }
      }
  
      public SpaceInfo getSpaceInfo(){
          File recordFile = new File(userSettings.getRecordTempPath());
          SpaceInfo spaceInfo = new SpaceInfo();
          spaceInfo.setFree(recordFile.getFreeSpace());
          spaceInfo.setTotal(recordFile.getTotalSpace());
          return spaceInfo;
      }
  
  
      public List<File> getStreamList(File appFile, Boolean sort) {
          if (appFile != null && appFile.isDirectory()) {
              File[] files = appFile.listFiles((File dir, String name) -> {
                  File currentFile = new File(dir.getAbsolutePath() + File.separator + name);
                  return  currentFile.isDirectory();
              });
              List<File> result = Arrays.asList(files);
              if (sort != null && sort) {
                  Collections.sort(result);
              }
              return result;
          }else {
              return null;
          }
      }
  
      /**
       * 获取制定推流的指定时间段内的推流
       * @param app
       * @param stream
       * @param startTime
       * @param endTime
       * @return
       */
      public List<File> getFilesInTime(String app, String stream, Date startTime, Date endTime){
  
          List<File> result = new ArrayList<>();
          if (app == null || stream == null) {
              return result;
          }
  
          SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HHmmss");
          SimpleDateFormat formatterForDate = new SimpleDateFormat("yyyy-MM-dd");
          String startTimeStr = null;
          String endTimeStr = null;
          if (startTime != null) {
              startTimeStr = formatter.format(startTime);
          }
          if (endTime != null) {
              endTimeStr = formatter.format(endTime);
          }
  
          logger.debug("获取[app: {}, stream: {}, statime: {}, endTime: {}]的视频", app, stream,
                  startTimeStr, endTimeStr);
  
          File recordFile = new File(userSettings.getRecordTempPath());
          File streamFile = new File(recordFile.getAbsolutePath() + File.separator + app + File.separator + stream + File.separator);
          if (!streamFile.exists()) {
              logger.warn("获取[app: {}, stream: {}, statime: {}, endTime: {}]的视频时未找到目录： {}", app, stream,
                      startTimeStr, endTimeStr, stream);
              return null;
          }
  
          File[] dateFiles = streamFile.listFiles((File dir, String name) -> {
              Date fileDate = null;
              Date startDate = null;
              Date endDate = null;
              if (new File(dir + File.separator + name).isFile()) {
                  return false;
              }
              if (startTime != null) {
                  startDate = new Date(startTime.getTime() - ((startTime.getTime() + 28800000) % (86400000)));
              }
              if (endTime != null) {
                  endDate = new Date(endTime.getTime() - ((endTime.getTime() + 28800000) % (86400000)));
              }
              try {
                  fileDate = formatterForDate.parse(name);
              } catch (ParseException e) {
                  logger.error("过滤日期文件时异常： {}-{}", name, e.getMessage());
                  return false;
              }
              boolean filterResult = true;
  
              if (startDate != null) {
                  filterResult = filterResult &&  DateUtils.getStartOfDay(startDate).compareTo(fileDate) <= 0;
              }
  
              if (endDate != null) {
                  filterResult = filterResult &&  DateUtils.getEndOfDay(endDate).compareTo(fileDate) >= 0;
              }
  
              return filterResult ;
          });
  
          if (dateFiles != null && dateFiles.length > 0) {
              for (File dateFile : dateFiles) {
                  File[] files = dateFile.listFiles((File dir, String name) ->{
                      File currentFile = new File(dir + File.separator + name);
                      VideoFile videoFile = VideoFileFactory.createFile(ffmpegExecUtils, currentFile);
                      if (videoFile == null ) {
                          return false;
                      }else {
                          if (!videoFile.isTargetFormat()) {
                              return false;
                          }
                          if (startTime == null && endTime == null) {
                             return true;
                          }else if (startTime == null && endTime != null) {
                              return videoFile.getEndTime().before(endTime)
                                      || videoFile.getEndTime().equals(endTime)
                                      || (videoFile.getEndTime().after(endTime) && videoFile.getStartTime().before(endTime));
                          }else if (startTime != null && endTime == null) {
                              return videoFile.getStartTime().after(startTime)
                                      || videoFile.getStartTime().equals(startTime)
                                      || (videoFile.getStartTime().before(startTime) && videoFile.getEndTime().after(startTime));
                          }else {
                              return videoFile.getStartTime().after(startTime)
                                      || videoFile.getStartTime().equals(startTime)
                                      || (videoFile.getStartTime().before(startTime) && videoFile.getEndTime().after(startTime))
                                      || videoFile.getEndTime().before(endTime)
                                      || videoFile.getEndTime().equals(endTime)
                                      || (videoFile.getEndTime().after(endTime) && videoFile.getStartTime().before(endTime));
                          }
                      }
                  });
                  if (files != null && files.length > 0) {
                      result.addAll(Arrays.asList(files));
                  }
              }
          }
          if (!result.isEmpty()) {
              result.sort((File f1, File f2) -> {
                  VideoFile videoFile1 = VideoFileFactory.createFile(ffmpegExecUtils, f1);
                  VideoFile videoFile2 = VideoFileFactory.createFile(ffmpegExecUtils, f2);
                  if (videoFile1 == null || !videoFile1.isTargetFormat() || videoFile2 == null || !videoFile2.isTargetFormat()) {
                      logger.warn("[根据时间获取视频文件] 排序错误，文件错误： {}/{}", f1.getName(), f2.getName());
                      return 0;
                  }
                  return videoFile1.getStartTime().compareTo(videoFile2.getStartTime());
              });
          }
          return result;
      }
  
  
      public String mergeOrCut(VideoTaskInfo videoTaskInfo) {
          assert videoTaskInfo.getFilePathList() != null;
          assert !videoTaskInfo.getFilePathList().isEmpty();
          String taskId = DigestUtils.md5DigestAsHex(String.valueOf(System.currentTimeMillis()).getBytes());
          String logInfo = String.format("app: %S, stream: %S, callId: %S,  任务ID：%S",
                  videoTaskInfo.getApp(), videoTaskInfo.getStream(), videoTaskInfo.getCallId(), taskId);
          logger.info("[录像合并] 开始合并，{} ", logInfo);
          List<File> fileList = new ArrayList<>();
          for (String filePath : videoTaskInfo.getFilePathList()) {
              File file = new File(filePath);
              if (!file.exists()) {
                  logger.info("[录像合并] 失败，{} ", logInfo);
                  throw new ControllerException(ErrorCode.ERROR100.getCode(), filePath + "文件不存在");
              }
              logger.info("[录像合并] 添加文件，{}, 文件: {}", logInfo, filePath);
              fileList.add(file);
          }
  
          File recordFile = new File(userSettings.getRecordTempPath() );
          if (!recordFile.exists()) {
              if (!recordFile.mkdirs()) {
                  logger.info("[录像合并] 失败，{}, 创建临时目录失败", logInfo);
                  throw new ControllerException(ErrorCode.ERROR100.getCode(), "创建临时目录失败");
              }
          }
          MergeOrCutTaskInfo mergeOrCutTaskInfo = new MergeOrCutTaskInfo();
          mergeOrCutTaskInfo.setId(taskId);
          mergeOrCutTaskInfo.setApp(videoTaskInfo.getApp());
          mergeOrCutTaskInfo.setStream(videoTaskInfo.getStream());
          mergeOrCutTaskInfo.setCallId(videoTaskInfo.getCallId());
          mergeOrCutTaskInfo.setStartTime(videoTaskInfo.getStartTime());
          mergeOrCutTaskInfo.setEndTime(videoTaskInfo.getEndTime());
          mergeOrCutTaskInfo.setCreateTime(simpleDateFormatForTime.format(System.currentTimeMillis()));
          String destFileName = videoTaskInfo.getStream() + "_" + videoTaskInfo.getCallId();
          if (fileList.size() == 1) {
  
              // 文件只有一个则不合并，直接复制过去
              mergeOrCutTaskInfo.setPercentage("1");
              // 处理文件路径
              String recordFileResultPath = recordFile.getAbsolutePath() + File.separator + destFileName + ".mp4";
              File destFile = new File(recordFileResultPath);
              destFile.deleteOnExit();
              try {
                  Files.copy(fileList.get(0).toPath(), Paths.get(recordFileResultPath));
              } catch (IOException e) {
                  logger.info("[录像合并] 失败， {}", logInfo, e);
                  throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage());
              }
              mergeOrCutTaskInfo.setRecordFile("/download/" + destFileName + ".mp4");
              if (videoTaskInfo.getRemoteHost() != null) {
                  mergeOrCutTaskInfo.setDownloadFile(videoTaskInfo.getRemoteHost() + "/download.html?url=download/" + destFileName + ".mp4");
                  mergeOrCutTaskInfo.setPlayFile(videoTaskInfo.getRemoteHost() + "/download/" + destFileName + ".mp4");
              }
              String key = String.format("%S_%S_%S", AssistConstants.MERGEORCUT , userSettings.getId(), mergeOrCutTaskInfo.getId());
              redisUtil.set(key, mergeOrCutTaskInfo);
              logger.info("[录像合并] 成功， 任务ID：{}", taskId);
          }else {
              ffmpegExecUtils.mergeOrCutFile(fileList, recordFile, destFileName, (status, percentage, result)->{
                  // 发出redis通知
                  if (status.equals(Progress.Status.END.name())) {
                      mergeOrCutTaskInfo.setPercentage("1");
  
                      // 处理文件路径
                      String relativize = new File(result).getName();
                      mergeOrCutTaskInfo.setRecordFile(relativize.toString());
                      if (videoTaskInfo.getRemoteHost() != null) {
                          mergeOrCutTaskInfo.setDownloadFile(videoTaskInfo.getRemoteHost() + "/download.html?url=download/" + relativize);
                          mergeOrCutTaskInfo.setPlayFile(videoTaskInfo.getRemoteHost() + "/download/" + relativize);
                      }
                      logger.info("[录像合并] 成功， {}", logInfo);
                  }else {
                      mergeOrCutTaskInfo.setPercentage(percentage + "");
                  }
                  String key = String.format("%S_%S_%S", AssistConstants.MERGEORCUT, userSettings.getId(), mergeOrCutTaskInfo.getId());
                  redisUtil.set(key, mergeOrCutTaskInfo);
              });
          }
  
          return taskId;
      }
  
      public List<File> getDateList(File streamFile, Integer year, Integer month, Boolean sort) {
          if (!streamFile.exists() && streamFile.isDirectory()) {
              logger.warn("获取[]的视频时未找到目录： {}",streamFile.getName());
              return null;
          }
          File[] dateFiles = streamFile.listFiles((File dir, String name)->{
              File currentFile = new File(dir.getAbsolutePath() + File.separator + name);
              if (!currentFile.isDirectory()){
                  return false;
              }
              Date date = null;
              try {
                  date = simpleDateFormat.parse(name);
              } catch (ParseException e) {
                  logger.error("格式化时间{}错误", name);
                  return false;
              }
              Calendar c = Calendar.getInstance();
              c.setTime(date);
              int y = c.get(Calendar.YEAR);
              int m = c.get(Calendar.MONTH) + 1;
              if (year != null) {
                  if (month != null) {
                      return  y == year && m == month;
                  }else {
                      return  y == year;
                  }
              }else {
                  return true;
              }
  
          });
          if (dateFiles == null) {
              return new ArrayList<>();
          }
          List<File> dateFileList = Arrays.asList(dateFiles);
          if (sort != null && sort) {
              dateFileList.sort((File f1, File f2)->{
                  int sortResult = 0;
  
                  try {
                      sortResult = simpleDateFormat.parse(f1.getName()).compareTo(simpleDateFormat.parse(f2.getName()));
                  } catch (ParseException e) {
                      logger.error("格式化时间{}/{}错误", f1.getName(), f2.getName());
                  }
                  return sortResult;
              });
          }
  
          return dateFileList;
      }
  
      public List<MergeOrCutTaskInfo> getTaskListForDownload(String app, String stream, String callId, Boolean isEnd, String taskId) {
          logger.info("[查询录像合成列表] app： {}， stream： {}， callId： {}, isEnd: {}, taskId: {}",
                  app, stream, callId, isEnd, taskId);
          ArrayList<MergeOrCutTaskInfo> result = new ArrayList<>();
          if (taskId == null) {
              taskId = "*";
          }
          List<Object> taskCatch = redisUtil.scan(String.format("%S_%S_%S", AssistConstants.MERGEORCUT,
                  userSettings.getId(), taskId));
          for (int i = 0; i < taskCatch.size(); i++) {
              String keyItem = taskCatch.get(i).toString();
              MergeOrCutTaskInfo mergeOrCutTaskInfo = (MergeOrCutTaskInfo)redisUtil.get(keyItem);
              if (mergeOrCutTaskInfo != null){
                  if ((!ObjectUtils.isEmpty(app) && !mergeOrCutTaskInfo.getApp().equals(app))
                          || (!ObjectUtils.isEmpty(stream) && !mergeOrCutTaskInfo.getStream().equals(stream))
                          || (!ObjectUtils.isEmpty(callId) && !mergeOrCutTaskInfo.getCallId().equals(callId))
                  ) {
                      continue;
                  }
                  if (mergeOrCutTaskInfo.getPercentage() != null){
                      if (isEnd != null ) {
                          if (isEnd) {
                              if (Double.parseDouble(mergeOrCutTaskInfo.getPercentage()) == 1){
                                  result.add(mergeOrCutTaskInfo);
                              }
                          }else {
                              if (Double.parseDouble(mergeOrCutTaskInfo.getPercentage()) < 1){
                                  result.add((MergeOrCutTaskInfo)redisUtil.get(keyItem));
                              }
                          }
                      }else {
                          result.add((MergeOrCutTaskInfo)redisUtil.get(keyItem));
                      }
                  }
              }
          }
          result.sort((MergeOrCutTaskInfo m1, MergeOrCutTaskInfo m2)->{
              int sortResult = 0;
              try {
                  sortResult = simpleDateFormatForTime.parse(m1.getCreateTime()).compareTo(simpleDateFormatForTime.parse(m2.getCreateTime()));
                  if (sortResult == 0) {
                      sortResult = simpleDateFormatForTime.parse(m1.getCreateTime()).compareTo(simpleDateFormatForTime.parse(m2.getCreateTime()));
                  }
                  if (sortResult == 0) {
                      sortResult = simpleDateFormatForTime.parse(m1.getCreateTime()).compareTo(simpleDateFormatForTime.parse(m2.getCreateTime()));
                  }
              } catch (ParseException e) {
                  e.printStackTrace();
              }
              return sortResult * -1;
          });
  
          return result;
      }
  
      public boolean collection(String app, String stream, String type) {
          File streamFile = new File(userSettings.getRecordTempPath() + File.separator + app + File.separator + stream);
          boolean result = false;
          if (streamFile.exists() && streamFile.isDirectory() && streamFile.canWrite()) {
              File signFile = new File(streamFile.getAbsolutePath() + File.separator + type + ".sign");
              try {
                  result = signFile.createNewFile();
              } catch (IOException e) {
                  logger.error("[收藏文件]失败，{}/{}", app, stream);
              }
          }
          return result;
      }
  
      public boolean removeCollection(String app, String stream, String type) {
          File signFile = new File(userSettings.getRecordTempPath() + File.separator + app + File.separator + stream + File.separator + type + ".sign");
          boolean result = false;
          if (signFile.exists() && signFile.isFile()) {
              result = signFile.delete();
          }
          return result;
      }
  
      public List<SignInfo> getCollectionList(String app, String stream, String type) {
          List<File> appList = this.getAppList(true);
          List<SignInfo> result = new ArrayList<>();
          if (appList.size() > 0) {
              for (File appFile : appList) {
                  if (app != null) {
                      if (!app.equals(appFile.getName())) {
                          continue;
                      }
                  }
                  List<File> streamList = getStreamList(appFile, true);
                  if (streamList.size() > 0) {
                      for (File streamFile : streamList) {
                          if (stream != null) {
                              if (!stream.equals(streamFile.getName())) {
                                  continue;
                              }
                          }
  
                          if (type != null) {
                              File signFile = new File(streamFile.getAbsolutePath() + File.separator + type + ".sign");
                              if (signFile.exists()) {
                                  SignInfo signInfo = new SignInfo();
                                  signInfo.setApp(appFile.getName());
                                  signInfo.setStream(streamFile.getName());
                                  signInfo.setType(type);
                                  result.add(signInfo);
                              }
                          }else {
                              streamFile.listFiles((File dir, String name) -> {
                                  File currentFile = new File(dir.getAbsolutePath() + File.separator + name);
                                  if (currentFile.isFile() && name.endsWith(".sign")){
                                      String currentType = name.substring(0, name.length() - ".sign".length());
                                      SignInfo signInfo = new SignInfo();
                                      signInfo.setApp(appFile.getName());
                                      signInfo.setStream(streamFile.getName());
                                      signInfo.setType(currentType);
                                      result.add(signInfo);
                                  }
                                  return false;
                              });
                          }
                      }
                  }
              }
          }
          return result;
      }
  
      public long fileDuration(String app, String stream) {
          List<File> allFiles = getFilesInTime(app, stream, null, null);
          long durationResult = 0;
          if (allFiles != null && allFiles.size() > 0) {
              for (File file : allFiles) {
                  try {
                      durationResult += ffmpegExecUtils.duration(file);
                  } catch (IOException e) {
                      logger.error("获取{}视频时长错误：{}", file.getAbsolutePath(), e.getMessage());
                  }
              }
          }
          return durationResult;
      }
  
      public int deleteFile(List<String> filePathList) {
          assert filePathList != null;
          assert filePathList.isEmpty();
          int deleteResult = 0;
          for (String filePath : filePathList) {
              File file = new File(filePath);
              if (file.exists()) {
                  if (file.delete()) {
                      deleteResult ++;
                  }
              }else {
                  logger.warn("[删除文件] 文件不存在，{}", filePath);
              }
          }
          if (deleteResult == 0) {
              throw new ControllerException(ErrorCode.ERROR100.getCode(), "未删除任何文件");
          }
          return deleteResult;
      }
  }
  