package com.ard.sample.media.service.impl; import com.ard.common.config.OssConfiguration; import com.ard.oss.service.impl.OssServiceContext; import com.ard.sample.utils.TimestampUtils; import com.ard.sample.utils.VideoDurationUtilFFmpeg; import com.ard.sample.wayline.model.dto.WaylineJobDTO; import com.ard.sample.wayline.service.IWaylineJobService; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ard.sample.common.model.Pagination; import com.ard.sample.common.model.PaginationData; import com.ard.sample.manage.model.dto.DeviceDictionaryDTO; import com.ard.sample.manage.model.enums.DeviceDomainEnum; import com.ard.sample.manage.service.IDeviceDictionaryService; import com.ard.sample.media.mapper.IFileMapper; import com.ard.sample.media.model.FileUploadDTO; import com.ard.sample.media.model.MediaFileDTO; import com.ard.sample.media.model.MediaFileEntity; import com.ard.sample.media.service.IFileService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.math.BigDecimal; import java.net.URL; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; /** * @author sean * @version 0.2 * @date 2021/12/9 */ @Slf4j @Service @Transactional public class FileServiceImpl implements IFileService { // 定义全局的日期时间格式化器(复用避免重复创建) private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); @Resource private IFileMapper mapper; @Autowired private IDeviceDictionaryService deviceDictionaryService; @Autowired private IWaylineJobService _waylineJobService; @Autowired private OssServiceContext ossService; @Autowired private VideoDurationUtilFFmpeg _videoDurationUtil; @Autowired private TimestampUtils _timestampUtils; private Optional getMediaByFingerprint(String fingerprint) { MediaFileEntity fileEntity = mapper.selectOne(new LambdaQueryWrapper() .eq(MediaFileEntity::getFingerprint, fingerprint)); return Optional.ofNullable(fileEntity); } private Optional getMediaByFileId(String fileId) { MediaFileEntity fileEntity = mapper.selectOne(new LambdaQueryWrapper() .eq(MediaFileEntity::getFileId, fileId)); return Optional.ofNullable(fileEntity); } @Override public Boolean checkExist(String fingerprint) { return this.getMediaByFingerprint(fingerprint).isPresent(); } @Override public Integer saveFile(FileUploadDTO file) { MediaFileEntity fileEntity = this.fileUploadConvertToEntity(file); fileEntity.setFileId(UUID.randomUUID().toString()); if(fileEntity.getFileName().contains(".mp4")) { log.info("mp4文件入库时读取视频时长,文件名:{},文件地址:{}",fileEntity.getFileName(),fileEntity.getFilePath()); double duration = _videoDurationUtil.getRemoteMp4Duration(file.getPath()); log.info("mp4视频文件时长为:{}",duration); fileEntity.setDuration(duration); log.info("视频创建时间:{}",fileEntity.getStartTime()); long endTime=_timestampUtils.plusSecondsByMillis(fileEntity.getStartTime(), duration); log.info("视频创建时间+时长=结束时间:{}",endTime); fileEntity.setEndTime(endTime); } return mapper.insert(fileEntity); } @Override public List getAllFiles() { return mapper.selectList(new LambdaQueryWrapper()) .stream() .map(this::entityConvertToDto) .collect(Collectors.toList()); } /** * 根据任务id获取全部媒体库文件 * * @param jobId 任务id * @return List 媒体库文件列表 */ @Override public PaginationData getJobFiles(long page, long pageSize,String jobId, String isType) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() .eq(Objects.nonNull(jobId), MediaFileEntity::getJobId, jobId); // 按文件扩展名区分图片/视频,不再写死镜头前缀: // Dock2 可见光文件名为 _V.jpeg/_V.mp4;机场1(M30T)三光相机为 _W/_Z/_T.jpeg(广角/变焦/红外)及 _W.mp4。 // 原实现写死 V.jpeg/V.mp4,导致机场1的媒体文件在计划库中查询不到。 if ("1".equals(isType)) { queryWrapper.and(w -> w.like(MediaFileEntity::getFileName, ".jpeg") .or().like(MediaFileEntity::getFileName, ".jpg")); } else if ("0".equals(isType)) { queryWrapper.like(MediaFileEntity::getFileName, ".mp4"); } // 排序:按 start_time 正序(升序)排列 queryWrapper.orderByAsc(MediaFileEntity::getStartTime); Page pageData = mapper.selectPage(new Page(page, pageSize), queryWrapper); List records = pageData.getRecords() .stream() .map(this::entityConvertToDto) .collect(Collectors.toList()); return new PaginationData(records, new Pagination(pageData)); } /** * 分页获取媒体文件列表 * 增加筛选条件 * @param page 当前页码 * @param pageSize 每页大小 * @return 分页数据对象,包含媒体文件列表和分页信息 */ @Override public PaginationData getMediaFilesPagination(long page, long pageSize, String jobId, Long startTime, Long endTime) { Page pageData = mapper.selectPage( new Page(page, pageSize), new LambdaQueryWrapper() .eq(jobId != null, MediaFileEntity::getJobId, jobId) .ge(startTime != null, MediaFileEntity::getStartTime, startTime) .le(endTime != null, MediaFileEntity::getStartTime, endTime) .orderByDesc(MediaFileEntity::getId)); List records = pageData.getRecords() .stream() .map(this::entityConvertToDto) .collect(Collectors.toList()); return new PaginationData(records, new Pagination(pageData)); } @Override public URL getObjectUrl(String fileId) { Optional mediaFileOpt = getMediaByFileId(fileId); if (mediaFileOpt.isEmpty()) { throw new IllegalArgumentException("{} doesn't exist."); } return ossService.getObjectUrl(OssConfiguration.bucket, mediaFileOpt.get().getObjectKey()); } @Override public List getFilesByWorkspaceAndJobId(String jobId) { return mapper.selectList(new LambdaQueryWrapper() .eq(MediaFileEntity::getJobId, jobId)) .stream() .map(this::entityConvertToDto).collect(Collectors.toList()); } /** * 将接收到的文件对象转换为数据库实体对象。 * @param file * @return */ private MediaFileEntity fileUploadConvertToEntity(FileUploadDTO file) { MediaFileEntity.MediaFileEntityBuilder builder = MediaFileEntity.builder(); if (file != null) { long creaeTime = 0; if(file.getMetadata().getCreatedTime() != null) { creaeTime = file.getMetadata().getCreatedTime().getTime(); } builder.fileName(file.getName()) .filePath(file.getPath()) .fingerprint(file.getFingerprint()) .objectKey(file.getObjectKey()) .subFileType(file.getSubFileType()) .isOriginal(file.getExt().getIsOriginal()) .jobId(file.getExt().getFlightId()) .drone(file.getExt().getSn()) .startTime(creaeTime) .tinnyFingerprint(file.getExt().getTinnyFingerprint()); // domain-type-subType int[] payloadModel = Arrays.stream(file.getExt().getPayloadModelKey().split("-")) .map(Integer::valueOf) .mapToInt(Integer::intValue) .toArray(); Optional payloadDict = deviceDictionaryService .getOneDictionaryInfoByTypeSubType(DeviceDomainEnum.PAYLOAD.getVal(), payloadModel[1], payloadModel[2]); payloadDict.ifPresent(payload -> builder.payload(payload.getDeviceName())); } return builder.build(); } /** * 将数据库实体对象转换为文件数据传输对象。 * @param entity * @return */ private MediaFileDTO entityConvertToDto(MediaFileEntity entity) { MediaFileDTO.MediaFileDTOBuilder builder = MediaFileDTO.builder(); if (entity != null) { builder.fileName(entity.getFileName()) .fileId(entity.getFileId()) .filePath(entity.getFilePath()) .isOriginal(entity.getIsOriginal()) .fingerprint(entity.getFingerprint()) .objectKey(entity.getObjectKey()) .tinnyFingerprint(entity.getTinnyFingerprint()) .payload(entity.getPayload()) .createTime(LocalDateTime.ofInstant( Instant.ofEpochMilli(entity.getCreateTime()), ZoneId.systemDefault()).format(DATE_TIME_FORMATTER)) .updateTime(LocalDateTime.ofInstant( Instant.ofEpochMilli(entity.getUpdateTime()), ZoneId.systemDefault()).format(DATE_TIME_FORMATTER)) .startTime(LocalDateTime.ofInstant( Instant.ofEpochMilli(entity.getStartTime()), ZoneId.systemDefault()).format(DATE_TIME_FORMATTER)) .endTime(LocalDateTime.ofInstant( Instant.ofEpochMilli(entity.getEndTime()), ZoneId.systemDefault()).format(DATE_TIME_FORMATTER)) .duration(entity.getDuration()) .drone(entity.getDrone()) .jobId(entity.getJobId()) .jobName(entity.getJobName()); } return builder.build(); } }