package com.doumee.service.business.impl.collection;
|
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.doumee.biz.system.SystemDictDataBiz;
|
import com.doumee.core.constants.ResponseStatus;
|
import com.doumee.core.exception.BusinessException;
|
import com.doumee.core.haikang.isapi.IsapiClient;
|
import com.doumee.core.haikang.isapi.IsapiConstants;
|
import com.doumee.core.haikang.isapi.model.MediaItemDTO;
|
import com.doumee.core.utils.Constants;
|
import com.doumee.core.utils.DateUtil;
|
import com.doumee.core.utils.FtpUtil;
|
import com.doumee.core.utils.VideoTranscodeUtil;
|
import com.doumee.dao.admin.request.CollectionMediaSyncRequest;
|
import com.doumee.dao.business.CollectionMediaMapper;
|
import com.doumee.dao.business.CollectionStationMapper;
|
import com.doumee.dao.business.model.CollectionMedia;
|
import com.doumee.dao.business.model.CollectionStation;
|
import com.doumee.service.business.CollectionMediaSyncService;
|
import com.doumee.service.business.third.model.PageData;
|
import com.doumee.service.business.third.model.PageWrap;
|
import lombok.extern.slf4j.Slf4j;
|
import org.apache.commons.lang3.StringUtils;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.stereotype.Service;
|
|
import javax.annotation.Resource;
|
import java.util.concurrent.Executor;
|
|
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletResponse;
|
import java.io.File;
|
import java.io.FileInputStream;
|
import java.io.IOException;
|
import java.io.InputStream;
|
import java.io.OutputStream;
|
import java.net.URLEncoder;
|
import java.nio.charset.StandardCharsets;
|
import java.nio.file.Files;
|
import java.nio.file.StandardCopyOption;
|
import java.util.Calendar;
|
import java.util.Date;
|
import java.util.List;
|
import java.util.UUID;
|
|
@Service
|
@Slf4j
|
public class CollectionMediaSyncServiceImpl implements CollectionMediaSyncService {
|
|
@Autowired
|
private CollectionMediaMapper collectionMediaMapper;
|
@Autowired
|
private CollectionStationMapper collectionStationMapper;
|
@Autowired
|
private SystemDictDataBiz systemDictDataBiz;
|
@Resource(name = "asyncExecutor")
|
private Executor asyncExecutor;
|
|
private final IsapiClient isapiClient = new IsapiClient();
|
private static FtpUtil ftp;
|
|
@Override
|
public String syncMediaList(CollectionMediaSyncRequest request) {
|
if (Constants.DEALING_HK_COLLECTION_MEDIA) {
|
throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "媒体同步任务正在执行,请稍后");
|
}
|
Constants.DEALING_HK_COLLECTION_MEDIA = true;
|
try {
|
Date endTime = request.getEndTime() != null ? request.getEndTime() : new Date();
|
Date startTime = request.getStartTime();
|
if (startTime == null) {
|
Calendar cal = Calendar.getInstance();
|
cal.setTime(endTime);
|
cal.add(Calendar.DAY_OF_MONTH, -7);
|
startTime = cal.getTime();
|
}
|
String trackId = getTrackId();
|
|
int totalNew = 0;
|
if (request.getStationId() != null) {
|
totalNew += syncStationMedia(request.getStationId(), startTime, endTime, trackId);
|
} else {
|
List<CollectionStation> stations = collectionStationMapper.selectList(new QueryWrapper<CollectionStation>().lambda()
|
.eq(CollectionStation::getIsdeleted, Constants.ZERO)
|
.eq(CollectionStation::getStatus, Constants.ONE));
|
for (CollectionStation station : stations) {
|
try {
|
totalNew += syncStationMedia(station.getId(), startTime, endTime, trackId);
|
} catch (Exception e) {
|
log.error("采集站媒体索引同步失败 stationId={}: {}", station.getId(), e.getMessage());
|
}
|
}
|
}
|
return "同步完成,新增索引【" + totalNew + "】条";
|
} finally {
|
Constants.DEALING_HK_COLLECTION_MEDIA = false;
|
}
|
}
|
|
private int syncStationMedia(Integer stationId, Date startTime, Date endTime, String trackId) {
|
CollectionStation station = collectionStationMapper.selectById(stationId);
|
if (station == null || Constants.equalsInteger(station.getIsdeleted(), Constants.ONE)) {
|
throw new BusinessException(ResponseStatus.DATA_EMPTY);
|
}
|
List<MediaItemDTO> items = isapiClient.searchMediaAll(station, startTime, endTime, trackId, IsapiConstants.MAX_PAGE_RESULTS);
|
log.info("采集站媒体检索 stationId={} ip={} range={}~{} track={} found={}",
|
stationId, station.getIp(), startTime, endTime, trackId, items.size());
|
int count = 0;
|
Date now = new Date();
|
for (MediaItemDTO item : items) {
|
if (StringUtils.isBlank(item.getFileIndex())) {
|
continue;
|
}
|
Long exists = collectionMediaMapper.selectCount(new QueryWrapper<CollectionMedia>().lambda()
|
.eq(CollectionMedia::getStationId, stationId)
|
.eq(CollectionMedia::getFileIndex, item.getFileIndex())
|
.eq(CollectionMedia::getIsdeleted, Constants.ZERO));
|
if (exists != null && exists > 0) {
|
continue;
|
}
|
CollectionMedia media = new CollectionMedia();
|
media.setStationId(stationId);
|
media.setFileIndex(item.getFileIndex());
|
media.setTrackId(item.getTrackId());
|
media.setFileName(item.getFileName());
|
media.setPlaybackUri(item.getPlaybackUri());
|
media.setMediaType(item.getMediaType());
|
media.setContentType(item.getContentType());
|
media.setFileSize(item.getFileSize());
|
media.setStartTime(item.getStartTime());
|
media.setEndTime(item.getEndTime());
|
media.setRecorderSn(item.getRecorderSn());
|
media.setUserName(item.getUserName());
|
media.setDownloadStatus(Constants.ZERO);
|
media.setCreateDate(now);
|
media.setIsdeleted(Constants.ZERO);
|
collectionMediaMapper.insert(media);
|
count++;
|
}
|
return count;
|
}
|
|
@Override
|
public String downloadMedia(Integer mediaId) {
|
CollectionMedia media = collectionMediaMapper.selectById(mediaId);
|
if (media == null || Constants.equalsInteger(media.getIsdeleted(), Constants.ONE)) {
|
throw new BusinessException(ResponseStatus.DATA_EMPTY);
|
}
|
if (Constants.equalsInteger(media.getDownloadStatus(), Constants.ONE)) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "文件已下载,无需重复下载");
|
}
|
if (Constants.equalsInteger(media.getDownloadStatus(), Constants.COLLECTION_MEDIA_DOWNLOADING)) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "文件正在下载中,请稍后刷新");
|
}
|
CollectionStation station = collectionStationMapper.selectById(media.getStationId());
|
if (station == null) {
|
throw new BusinessException(ResponseStatus.DATA_EMPTY);
|
}
|
CollectionMedia downloading = new CollectionMedia();
|
downloading.setDownloadStatus(Constants.COLLECTION_MEDIA_DOWNLOADING);
|
int updated = collectionMediaMapper.update(downloading, new QueryWrapper<CollectionMedia>().lambda()
|
.eq(CollectionMedia::getId, mediaId)
|
.in(CollectionMedia::getDownloadStatus, Constants.ZERO, 2));
|
if (updated <= 0) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "无法提交下载,请刷新后重试");
|
}
|
asyncExecutor.execute(() -> executeDownloadAsync(mediaId));
|
return "已提交下载任务,请稍后刷新查看状态";
|
}
|
|
private void executeDownloadAsync(Integer mediaId) {
|
try {
|
CollectionMedia media = collectionMediaMapper.selectById(mediaId);
|
if (media == null || Constants.equalsInteger(media.getIsdeleted(), Constants.ONE)) {
|
return;
|
}
|
CollectionStation station = collectionStationMapper.selectById(media.getStationId());
|
if (station == null) {
|
markDownloadFailed(mediaId);
|
return;
|
}
|
String path = downloadToFtp(station, media);
|
if (StringUtils.isBlank(path)) {
|
markDownloadFailed(mediaId);
|
log.error("异步下载失败 mediaId={}", mediaId);
|
return;
|
}
|
CollectionMedia update = new CollectionMedia();
|
update.setId(mediaId);
|
update.setFilePathLocal(path);
|
update.setDownloadStatus(Constants.ONE);
|
update.setDownloadTime(new Date());
|
collectionMediaMapper.updateById(update);
|
log.info("异步下载成功 mediaId={} path={}", mediaId, path);
|
} catch (Exception e) {
|
markDownloadFailed(mediaId);
|
log.error("异步下载异常 mediaId={}: {}", mediaId, e.getMessage(), e);
|
}
|
}
|
|
private void markDownloadFailed(Integer mediaId) {
|
CollectionMedia fail = new CollectionMedia();
|
fail.setId(mediaId);
|
fail.setDownloadStatus(2);
|
collectionMediaMapper.updateById(fail);
|
}
|
|
@Override
|
public String batchDownload(CollectionMediaSyncRequest request) {
|
int limit = request.getLimit() != null ? request.getLimit() : getBatchSize();
|
QueryWrapper<CollectionMedia> wrapper = new QueryWrapper<>();
|
wrapper.lambda()
|
.eq(CollectionMedia::getIsdeleted, Constants.ZERO)
|
.eq(CollectionMedia::getDownloadStatus, Constants.ZERO)
|
.eq(request.getStationId() != null, CollectionMedia::getStationId, request.getStationId())
|
.orderByAsc(CollectionMedia::getId)
|
.last("limit " + limit);
|
List<CollectionMedia> list = collectionMediaMapper.selectList(wrapper);
|
int submitted = 0;
|
int skip = 0;
|
for (CollectionMedia media : list) {
|
try {
|
downloadMedia(media.getId());
|
submitted++;
|
} catch (BusinessException e) {
|
skip++;
|
log.warn("批量下载跳过 mediaId={}: {}", media.getId(), e.getMessage());
|
} catch (Exception e) {
|
skip++;
|
log.error("批量下载提交失败 mediaId={}: {}", media.getId(), e.getMessage());
|
}
|
}
|
return "已提交下载【" + submitted + "】条,跳过【" + skip + "】条";
|
}
|
|
@Override
|
public PageData<CollectionMedia> findPage(PageWrap<CollectionMedia> pageWrap) {
|
CollectionMedia model = pageWrap.getModel() != null ? pageWrap.getModel() : new CollectionMedia();
|
QueryWrapper<CollectionMedia> wrapper = new QueryWrapper<>();
|
wrapper.lambda()
|
.eq(CollectionMedia::getIsdeleted, Constants.ZERO)
|
.eq(model.getStationId() != null, CollectionMedia::getStationId, model.getStationId())
|
.eq(model.getDownloadStatus() != null, CollectionMedia::getDownloadStatus, model.getDownloadStatus())
|
.eq(model.getMediaType() != null, CollectionMedia::getMediaType, model.getMediaType())
|
.orderByDesc(CollectionMedia::getId);
|
IPage<CollectionMedia> page = collectionMediaMapper.selectPage(
|
new Page<>(pageWrap.getPage(), pageWrap.getCapacity()), wrapper);
|
page.getRecords().forEach(this::fillMediaAccessUrl);
|
return PageData.from(page);
|
}
|
|
@Override
|
public void previewMedia(Integer mediaId, HttpServletRequest request, HttpServletResponse response) {
|
streamMediaFile(mediaId, request, response, false);
|
}
|
|
@Override
|
public void downloadMediaFile(Integer mediaId, HttpServletRequest request, HttpServletResponse response) {
|
streamMediaFile(mediaId, request, response, true);
|
}
|
|
private void streamMediaFile(Integer mediaId, HttpServletRequest request, HttpServletResponse response, boolean attachment) {
|
CollectionMedia media = collectionMediaMapper.selectById(mediaId);
|
if (media == null || Constants.equalsInteger(media.getIsdeleted(), Constants.ONE)) {
|
throw new BusinessException(ResponseStatus.DATA_EMPTY);
|
}
|
if (!Constants.equalsInteger(media.getDownloadStatus(), Constants.ONE) || StringUtils.isBlank(media.getFilePathLocal())) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "文件尚未下载,无法访问");
|
}
|
String remotePath = getMediaFolder() + media.getFilePathLocal();
|
FtpUtil ftpClient = null;
|
try {
|
ftpClient = createFtpClient();
|
if (!ftpClient.connect()) {
|
throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(), "FTP连接失败");
|
}
|
String contentType = resolvePreviewContentType(media);
|
String fileName = StringUtils.defaultIfBlank(media.getFileName(), "media" + resolveExt(media));
|
long fileSize = ftpClient.getRemoteFileSize(remotePath);
|
if (fileSize <= 0 && media.getFileSize() != null && media.getFileSize() > 0) {
|
fileSize = media.getFileSize();
|
}
|
if (fileSize <= 0) {
|
log.warn("媒体文件无法获取大小 mediaId={} remotePath={}", mediaId, remotePath);
|
}
|
|
response.setContentType(contentType);
|
String disposition = (attachment ? "attachment" : "inline") + ";filename="
|
+ URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
|
response.setHeader("Content-Disposition", disposition);
|
if (attachment) {
|
response.setHeader("eva-download-filename", URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
|
}
|
response.setHeader("Accept-Ranges", "bytes");
|
response.setHeader("eva-opera-type", attachment ? "download" : "preview");
|
response.setHeader("Cache-Control", "private, max-age=3600");
|
|
RangeSpec range = fileSize > 0
|
? parseRangeHeader(request != null ? request.getHeader("Range") : null, fileSize) : null;
|
OutputStream out = response.getOutputStream();
|
boolean streamed;
|
if (range != null) {
|
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
|
response.setHeader("Content-Range", "bytes " + range.start + "-" + range.end + "/" + fileSize);
|
response.setHeader("Content-Length", String.valueOf(range.length()));
|
streamed = ftpClient.streamRemoteFileRange(remotePath, out, range.start, range.length());
|
} else {
|
if (fileSize > 0) {
|
response.setHeader("Content-Length", String.valueOf(fileSize));
|
}
|
streamed = ftpClient.streamRemoteFile(remotePath, out);
|
}
|
if (!streamed) {
|
log.warn("媒体文件 FTP 读取失败 mediaId={} remotePath={}", mediaId, remotePath);
|
if (!response.isCommitted()) {
|
response.resetBuffer();
|
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
response.setContentType("text/plain;charset=UTF-8");
|
out.write("文件读取失败".getBytes(StandardCharsets.UTF_8));
|
}
|
return;
|
}
|
out.flush();
|
} catch (BusinessException e) {
|
throw e;
|
} catch (Exception e) {
|
log.error("媒体文件输出失败 mediaId={}: {}", mediaId, e.getMessage());
|
if (!response.isCommitted()) {
|
try {
|
response.resetBuffer();
|
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
response.setContentType("text/plain;charset=UTF-8");
|
response.getOutputStream().write("文件读取失败".getBytes(StandardCharsets.UTF_8));
|
} catch (Exception ignored) {
|
}
|
}
|
} finally {
|
try {
|
if (ftpClient != null) {
|
ftpClient.disconnect();
|
}
|
} catch (Exception ignored) {
|
}
|
}
|
}
|
|
private void fillMediaAccessUrl(CollectionMedia media) {
|
if (!Constants.equalsInteger(media.getDownloadStatus(), Constants.ONE) || StringUtils.isBlank(media.getFilePathLocal())) {
|
return;
|
}
|
try {
|
String prefix = systemDictDataBiz.queryByCode(Constants.FTP, Constants.FTP_RESOURCE_PATH).getCode();
|
media.setFileUrlFull(prefix + getMediaFolder() + media.getFilePathLocal());
|
} catch (Exception e) {
|
log.warn("构建媒体访问URL失败 mediaId={}: {}", media.getId(), e.getMessage());
|
}
|
}
|
|
private FtpUtil createFtpClient() throws IOException {
|
return new FtpUtil(
|
systemDictDataBiz.queryByCode(Constants.FTP, Constants.FTP_HOST).getCode(),
|
Integer.parseInt(systemDictDataBiz.queryByCode(Constants.FTP, Constants.FTP_PORT).getCode()),
|
systemDictDataBiz.queryByCode(Constants.FTP, Constants.FTP_USERNAME).getCode(),
|
systemDictDataBiz.queryByCode(Constants.FTP, Constants.FTP_PWD).getCode());
|
}
|
|
private String resolvePreviewContentType(CollectionMedia media) {
|
String name = StringUtils.defaultString(media.getFileName()).toLowerCase();
|
if (name.endsWith(".jpg") || name.endsWith(".jpeg")) {
|
return "image/jpeg";
|
}
|
if (name.endsWith(".png")) {
|
return "image/png";
|
}
|
if (name.endsWith(".gif")) {
|
return "image/gif";
|
}
|
if (name.endsWith(".bmp")) {
|
return "image/bmp";
|
}
|
if (name.endsWith(".mp3")) {
|
return "audio/mpeg";
|
}
|
if (name.endsWith(".mp4") || name.endsWith(".m4v")) {
|
return "video/mp4";
|
}
|
if (name.endsWith(".wav")) {
|
return "audio/wav";
|
}
|
if (name.endsWith(".txt") || name.endsWith(".log")) {
|
return "text/plain;charset=UTF-8";
|
}
|
if (media.getMediaType() != null && media.getMediaType() == 1) {
|
return "image/jpeg";
|
}
|
if (media.getMediaType() != null && media.getMediaType() == 2) {
|
return "audio/mpeg";
|
}
|
return "video/mp4";
|
}
|
|
private String downloadToFtp(CollectionStation station, CollectionMedia media) {
|
InputStream is = null;
|
File tempSource = null;
|
File tempTarget = null;
|
try {
|
is = isapiClient.downloadMedia(station, media.getPlaybackUri(), media.getFileIndex(),
|
media.getFileName(), media.getTrackId(), media.getStartTime(), media.getEndTime(),
|
media.getFileSize(), media.getMediaType());
|
if (is == null) {
|
log.error("ISAPI下载无响应 mediaId={} mediaID={} fileName={}",
|
media.getId(), media.getFileIndex(), media.getFileName());
|
return null;
|
}
|
tempSource = File.createTempFile("hk_media_src_", resolveTempSuffix(media));
|
Files.copy(is, tempSource.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
is.close();
|
is = null;
|
|
if (!validateDownloadFile(tempSource, media)) {
|
log.error("ISAPI下载内容无效 mediaId={} fileName={} size={}", media.getId(), media.getFileName(), tempSource.length());
|
return null;
|
}
|
|
File uploadFile = tempSource;
|
String ext = resolveUploadExt(media, tempSource);
|
if (shouldTranscodeMp4(media, tempSource)) {
|
tempTarget = File.createTempFile("hk_media_out_", ".mp4");
|
log.info("开始 MP4 视频转码 mediaId={} fileName={} size={}",
|
media.getId(), media.getFileName(), tempSource.length());
|
if (VideoTranscodeUtil.transcodeToBrowserMp4(getFfmpegPath(), tempSource, tempTarget)) {
|
uploadFile = tempTarget;
|
ext = ".mp4";
|
} else {
|
log.warn("MP4 视频转码未成功,上传原始文件 mediaId={} fileName={} size={}",
|
media.getId(), media.getFileName(), tempSource.length());
|
uploadFile = tempSource;
|
ext = resolveUploadExt(media, tempSource);
|
}
|
}
|
|
if (ftp == null) {
|
ftp = new FtpUtil(
|
systemDictDataBiz.queryByCode(Constants.FTP, Constants.FTP_HOST).getCode(),
|
Integer.parseInt(systemDictDataBiz.queryByCode(Constants.FTP, Constants.FTP_PORT).getCode()),
|
systemDictDataBiz.queryByCode(Constants.FTP, Constants.FTP_USERNAME).getCode(),
|
systemDictDataBiz.queryByCode(Constants.FTP, Constants.FTP_PWD).getCode());
|
} else {
|
ftp.connect();
|
}
|
String folder = getMediaFolder();
|
String date = DateUtil.getNowShortDate();
|
String fName = date + "/" + UUID.randomUUID() + ext;
|
String fileName = folder + fName;
|
try (InputStream uploadStream = new FileInputStream(uploadFile)) {
|
boolean uploaded = ftp.uploadInputstream(uploadStream, fileName);
|
if (uploaded) {
|
log.info("采集站媒体上传FTP成功 stationId={} file={} size={}", station.getId(), fName, uploadFile.length());
|
return fName;
|
}
|
}
|
} catch (Exception e) {
|
log.error("采集站媒体下载上传失败 mediaId={} fileName={}: {}", media.getId(), media.getFileName(), e.getMessage(), e);
|
} finally {
|
if (is != null) {
|
try {
|
is.close();
|
} catch (Exception ignored) {
|
}
|
}
|
deleteQuietly(tempSource);
|
deleteQuietly(tempTarget);
|
}
|
return null;
|
}
|
|
private String resolveTempSuffix(CollectionMedia media) {
|
if (StringUtils.isNotBlank(media.getFileName()) && media.getFileName().contains(".")) {
|
return media.getFileName().substring(media.getFileName().lastIndexOf('.')).toLowerCase();
|
}
|
return resolveExt(media);
|
}
|
|
/** 仅扩展名为 .mp4 的视频走 FFmpeg 转码(海康常见 .mp4 内为 MPEG-PS,仍在此处理) */
|
private boolean shouldTranscodeMp4(CollectionMedia media, File sourceFile) {
|
return isVideoMedia(media) && isMp4FileName(media.getFileName());
|
}
|
|
private static boolean isMp4FileName(String fileName) {
|
if (StringUtils.isBlank(fileName)) {
|
return false;
|
}
|
String lower = fileName.toLowerCase();
|
return lower.endsWith(".mp4");
|
}
|
|
private String resolveUploadExt(CollectionMedia media, File sourceFile) {
|
if (StringUtils.isNotBlank(media.getFileName()) && media.getFileName().contains(".")) {
|
return media.getFileName().substring(media.getFileName().lastIndexOf('.')).toLowerCase();
|
}
|
return resolveExt(media);
|
}
|
|
private void deleteQuietly(File file) {
|
if (file != null && file.exists() && !file.delete()) {
|
log.warn("临时文件删除失败: {}", file.getAbsolutePath());
|
}
|
}
|
|
private String getFfmpegPath() {
|
try {
|
return systemDictDataBiz.queryByCode(Constants.CS_PARAM, Constants.CS_FFMPEG_PATH).getCode();
|
} catch (Exception e) {
|
return "ffmpeg";
|
}
|
}
|
|
private boolean validateDownloadFile(File file, CollectionMedia media) throws IOException {
|
if (file.length() <= 0) {
|
log.warn("ISAPI下载内容为空 mediaId={}", media.getId());
|
return false;
|
}
|
byte[] head = new byte[4096];
|
int n;
|
try (InputStream in = new FileInputStream(file)) {
|
n = in.read(head);
|
}
|
if (n <= 0) {
|
log.warn("ISAPI下载内容为空 mediaId={}", media.getId());
|
return false;
|
}
|
if (isErrorPayload(head, n)) {
|
log.error("ISAPI下载返回错误 mediaId={} snippet={}", media.getId(),
|
new String(head, 0, Math.min(n, 200), StandardCharsets.UTF_8));
|
return false;
|
}
|
if (isVideoMedia(media) && isMpegPs(head, n)) {
|
log.info("检测到MPEG-PS视频流 mediaId={}(非 MP4,不转码)", media.getId());
|
}
|
if (isVideoMedia(media) && !isLikelyPlayableVideo(head, n) && !isMp4FileName(media.getFileName())) {
|
log.info("下载内容非 MP4 视频 mediaId={} fileName={}(不转码)", media.getId(), media.getFileName());
|
}
|
return true;
|
}
|
|
private String getMediaFolder() {
|
try {
|
return systemDictDataBiz.queryByCode(Constants.FTP, Constants.COLLECTION_MEDIA_FOLDER).getCode();
|
} catch (Exception e) {
|
return "/collection_media/";
|
}
|
}
|
|
private String resolveExt(CollectionMedia media) {
|
if (StringUtils.isNotBlank(media.getFileName())) {
|
String lower = media.getFileName().toLowerCase();
|
if (lower.endsWith(".txt") || lower.endsWith(".log")) {
|
return lower.substring(lower.lastIndexOf('.'));
|
}
|
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg") || lower.endsWith(".png")) {
|
return lower.substring(lower.lastIndexOf('.'));
|
}
|
if (lower.endsWith(".mp3") || lower.endsWith(".wav")) {
|
return lower.substring(lower.lastIndexOf('.'));
|
}
|
if (lower.contains(".")) {
|
return lower.substring(lower.lastIndexOf('.'));
|
}
|
}
|
if (media.getMediaType() != null && media.getMediaType() == 1) {
|
return ".jpg";
|
}
|
if (media.getMediaType() != null && media.getMediaType() == 2) {
|
return ".mp3";
|
}
|
return ".dat";
|
}
|
|
private String getTrackId() {
|
try {
|
String val = systemDictDataBiz.queryByCode(Constants.CS_PARAM, Constants.CS_SEARCH_TRACK_ID).getCode();
|
if (StringUtils.isBlank(val) || "auto".equalsIgnoreCase(val.trim()) || "*".equals(val.trim())) {
|
return null;
|
}
|
return val.trim();
|
} catch (Exception e) {
|
return null;
|
}
|
}
|
|
private int getBatchSize() {
|
try {
|
return Integer.parseInt(systemDictDataBiz.queryByCode(Constants.CS_PARAM, Constants.CS_DOWNLOAD_BATCH_SIZE).getCode());
|
} catch (Exception e) {
|
return 10;
|
}
|
}
|
|
private static boolean isVideoMedia(CollectionMedia media) {
|
if (media.getMediaType() != null) {
|
return media.getMediaType() == 0;
|
}
|
String name = StringUtils.defaultString(media.getFileName()).toLowerCase();
|
return name.endsWith(".mp4") || name.endsWith(".mov") || name.endsWith(".avi")
|
|| name.endsWith(".mkv") || name.endsWith(".m4v");
|
}
|
|
private static boolean isErrorPayload(byte[] head, int n) {
|
String snippet = new String(head, 0, Math.min(n, 64), StandardCharsets.UTF_8).trim();
|
return snippet.startsWith("<?xml") || snippet.startsWith("{")
|
|| (snippet.startsWith("<") && snippet.contains("ResponseStatus"));
|
}
|
|
private static boolean isMpegPs(byte[] head, int n) {
|
return n >= 4 && head[0] == 0 && head[1] == 0 && head[2] == 1 && (head[3] & 0xFF) == 0xBA;
|
}
|
|
private static boolean isLikelyPlayableVideo(byte[] head, int n) {
|
if (n >= 8 && head[4] == 'f' && head[5] == 't' && head[6] == 'y' && head[7] == 'p') {
|
return true;
|
}
|
return n >= 12 && head[0] == 'R' && head[1] == 'I' && head[2] == 'F' && head[3] == 'F'
|
&& head[8] == 'A' && head[9] == 'V' && head[10] == 'I';
|
}
|
|
private static RangeSpec parseRangeHeader(String rangeHeader, long fileSize) {
|
if (StringUtils.isBlank(rangeHeader) || fileSize <= 0 || !rangeHeader.startsWith("bytes=")) {
|
return null;
|
}
|
String spec = rangeHeader.substring("bytes=".length()).trim();
|
int dash = spec.indexOf('-');
|
if (dash < 0) {
|
return null;
|
}
|
try {
|
long start = Long.parseLong(spec.substring(0, dash));
|
String endPart = spec.substring(dash + 1).trim();
|
long end = endPart.isEmpty() ? fileSize - 1 : Long.parseLong(endPart);
|
if (start < 0 || end < start || start >= fileSize) {
|
return null;
|
}
|
if (end >= fileSize) {
|
end = fileSize - 1;
|
}
|
return new RangeSpec(start, end);
|
} catch (NumberFormatException e) {
|
return null;
|
}
|
}
|
|
private static final class RangeSpec {
|
private final long start;
|
private final long end;
|
|
private RangeSpec(long start, long end) {
|
this.start = start;
|
this.end = end;
|
}
|
|
private long length() {
|
return end - start + 1;
|
}
|
}
|
}
|