You've already forked FrameTour-BE
refactor(video): 优化视频切割逻辑,使用concat demuxer提升性能
- 引入concat demuxer方式替代原有转码流程,提高处理效率 - 新增PROBE_SIZE常量用于控制探测大小,优化文件解析 - 重构runFfmpegForMultipleFile1方法,简化多文件处理逻辑 - 添加quickVideoCutWithConcatDemuxer方法实现无转码快速切割 - 调整ffmpeg命令参数顺序及新增选项,如-probesize、-analyzeduration等 - 在多个ffmpeg调用中统一增加-genpts标志和避免负时间戳处理 - 完善临时文件清理机制,确保执行过程中的资源回收 - 更新相关ffmpeg命令构建逻辑以适配新的处理流程
This commit is contained in:
@@ -84,6 +84,8 @@ public class VideoPieceGetter {
|
||||
@Autowired
|
||||
private MemberRelationRepository memberRelationRepository;
|
||||
|
||||
public static final String PROBE_SIZE = "32M";
|
||||
|
||||
@Data
|
||||
public static class Task {
|
||||
public List<Long> faceSampleIds = new ArrayList<>();
|
||||
@@ -472,63 +474,40 @@ public class VideoPieceGetter {
|
||||
// 多个文件切割,用速度快的
|
||||
result = runFfmpegForMultipleFile1(task);
|
||||
}
|
||||
// 先尝试方法1
|
||||
if (result) {
|
||||
return true;
|
||||
}
|
||||
log.warn("FFMPEG简易方法失败,尝试复杂方法转码");
|
||||
// 不行再尝试方法二
|
||||
return runFfmpegForMultipleFile2(task);
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean runFfmpegForMultipleFile1(FfmpegTask task) {
|
||||
// 多文件,方法一:先转换成ts,然后合并切割
|
||||
// 步骤一:先转换成ts,并行转换
|
||||
boolean notOk = task.getFileList().stream().map(file -> {
|
||||
// 使用concat demuxer(文件列表)拼接裁切
|
||||
File concatListFile = null;
|
||||
try {
|
||||
if (file.isNeedDownload() || (!file.getName().endsWith(".ts"))) {
|
||||
// 使用时间戳和线程ID确保临时文件名唯一性,避免并发冲突
|
||||
String uniqueSuffix = System.currentTimeMillis() + "_" + Thread.currentThread().getId();
|
||||
String tmpFile = file.getName() + "_" + uniqueSuffix + ".ts";
|
||||
boolean result = convertMp4ToTs(file, tmpFile);
|
||||
// 因为是并行转换,没法保证顺序,就直接存里面
|
||||
if (result) {
|
||||
file.setUrl(tmpFile);
|
||||
} else {
|
||||
// 失败了,务必删除临时文件
|
||||
(new File(tmpFile)).delete();
|
||||
// 创建临时文件列表
|
||||
concatListFile = File.createTempFile("concat_list_", ".txt");
|
||||
try (java.io.FileWriter writer = new java.io.FileWriter(concatListFile)) {
|
||||
for (FileObject file : task.getFileList()) {
|
||||
// concat demuxer格式:file 'path'
|
||||
// 对单引号进行转义
|
||||
String escapedUrl = file.getUrl().replace("'", "'\\''");
|
||||
writer.write("file '" + escapedUrl + "'\n");
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.warn("转码出错");
|
||||
return false;
|
||||
}
|
||||
}).anyMatch(b -> !b);
|
||||
// 转码进程中出现问题
|
||||
if (notOk) {
|
||||
return false;
|
||||
}
|
||||
// 步骤二:使用concat协议拼接裁切
|
||||
boolean result;
|
||||
try {
|
||||
result = quickVideoCut(
|
||||
"concat:" + task.getFileList().stream().map(FileObject::getUrl).collect(Collectors.joining("|")),
|
||||
task.getOffsetStart(), task.getDuration(), task.getOutputFile()
|
||||
|
||||
// 使用concat demuxer进行裁切
|
||||
return quickVideoCutWithConcatDemuxer(
|
||||
concatListFile.getAbsolutePath(),
|
||||
task.getOffsetStart(),
|
||||
task.getDuration(),
|
||||
task.getOutputFile()
|
||||
);
|
||||
} catch (IOException e) {
|
||||
log.error("使用concat demuxer切割视频失败", e);
|
||||
return false;
|
||||
} finally {
|
||||
// 清理临时文件
|
||||
if (concatListFile != null && concatListFile.exists()) {
|
||||
concatListFile.delete();
|
||||
}
|
||||
// 步骤三:删除临时文件
|
||||
task.getFileList().stream().map(FileObject::getUrl).forEach(tmpFile -> {
|
||||
File f = new File(tmpFile);
|
||||
if (f.exists() && f.isFile()) {
|
||||
f.delete();
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean runFfmpegForMultipleFile2(FfmpegTask task) {
|
||||
@@ -589,6 +568,48 @@ public class VideoPieceGetter {
|
||||
return handleFfmpegProcess(ffmpegCmd);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用concat demuxer快速切割,不产生转码,速度快
|
||||
*
|
||||
* @param concatListFile concat列表文件路径
|
||||
* @param offset 离输入文件开始的偏移
|
||||
* @param length 输出文件时长
|
||||
* @param outputFile 输出文件名称
|
||||
* @return 是否成功
|
||||
* @throws IOException 奇奇怪怪的报错
|
||||
*/
|
||||
private boolean quickVideoCutWithConcatDemuxer(String concatListFile, BigDecimal offset, BigDecimal length, String outputFile) throws IOException {
|
||||
List<String> ffmpegCmd = new ArrayList<>();
|
||||
ffmpegCmd.add("ffmpeg");
|
||||
ffmpegCmd.add("-hide_banner");
|
||||
ffmpegCmd.add("-y");
|
||||
ffmpegCmd.add("-f");
|
||||
ffmpegCmd.add("concat");
|
||||
ffmpegCmd.add("-safe");
|
||||
ffmpegCmd.add("0");
|
||||
ffmpegCmd.add("-probesize");
|
||||
ffmpegCmd.add(PROBE_SIZE);
|
||||
ffmpegCmd.add("-analyzeduration");
|
||||
ffmpegCmd.add("0");
|
||||
ffmpegCmd.add("-ss");
|
||||
ffmpegCmd.add(offset.toPlainString());
|
||||
ffmpegCmd.add("-i");
|
||||
ffmpegCmd.add(concatListFile);
|
||||
ffmpegCmd.add("-fflags");
|
||||
ffmpegCmd.add("+genpts");
|
||||
ffmpegCmd.add("-c:v");
|
||||
ffmpegCmd.add("copy");
|
||||
ffmpegCmd.add("-an");
|
||||
ffmpegCmd.add("-t");
|
||||
ffmpegCmd.add(length.toPlainString());
|
||||
ffmpegCmd.add("-avoid_negative_ts");
|
||||
ffmpegCmd.add("make_zero");
|
||||
ffmpegCmd.add("-f");
|
||||
ffmpegCmd.add("mp4");
|
||||
ffmpegCmd.add(outputFile);
|
||||
return handleFfmpegProcess(ffmpegCmd);
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速切割,不产生转码,速度快,但可能会出现:第一帧数据不是I帧导致前面的数据无法使用
|
||||
*
|
||||
@@ -604,15 +625,23 @@ public class VideoPieceGetter {
|
||||
ffmpegCmd.add("ffmpeg");
|
||||
ffmpegCmd.add("-hide_banner");
|
||||
ffmpegCmd.add("-y");
|
||||
ffmpegCmd.add("-probesize");
|
||||
ffmpegCmd.add(PROBE_SIZE);
|
||||
ffmpegCmd.add("-analyzeduration");
|
||||
ffmpegCmd.add("0");
|
||||
ffmpegCmd.add("-ss");
|
||||
ffmpegCmd.add(offset.toPlainString());
|
||||
ffmpegCmd.add("-i");
|
||||
ffmpegCmd.add(inputFile);
|
||||
ffmpegCmd.add("-fflags");
|
||||
ffmpegCmd.add("+genpts");
|
||||
ffmpegCmd.add("-c:v");
|
||||
ffmpegCmd.add("copy");
|
||||
ffmpegCmd.add("-an");
|
||||
ffmpegCmd.add("-ss");
|
||||
ffmpegCmd.add(offset.toPlainString());
|
||||
ffmpegCmd.add("-t");
|
||||
ffmpegCmd.add(length.toPlainString());
|
||||
ffmpegCmd.add("-avoid_negative_ts");
|
||||
ffmpegCmd.add("make_zero");
|
||||
ffmpegCmd.add("-f");
|
||||
ffmpegCmd.add("mp4");
|
||||
ffmpegCmd.add(outputFile);
|
||||
@@ -648,12 +677,16 @@ public class VideoPieceGetter {
|
||||
ffmpegCmd.add("[v]");
|
||||
ffmpegCmd.add("-preset:v");
|
||||
ffmpegCmd.add("fast");
|
||||
ffmpegCmd.add("-fflags");
|
||||
ffmpegCmd.add("+genpts");
|
||||
ffmpegCmd.add("-an");
|
||||
// 没有使用copy,因为使用了filter_complex
|
||||
ffmpegCmd.add("-ss");
|
||||
ffmpegCmd.add(offset.toPlainString());
|
||||
ffmpegCmd.add("-t");
|
||||
ffmpegCmd.add(length.toPlainString());
|
||||
ffmpegCmd.add("-avoid_negative_ts");
|
||||
ffmpegCmd.add("make_zero");
|
||||
ffmpegCmd.add("-f");
|
||||
ffmpegCmd.add("mp4");
|
||||
ffmpegCmd.add(outputFile);
|
||||
@@ -683,14 +716,14 @@ public class VideoPieceGetter {
|
||||
}
|
||||
try {
|
||||
// 最长1分钟
|
||||
boolean exited = ffmpegProcess.waitFor(1, TimeUnit.MINUTES);
|
||||
boolean exited = ffmpegProcess.waitFor(90, TimeUnit.SECONDS);
|
||||
if (exited) {
|
||||
int code = ffmpegProcess.exitValue();
|
||||
Date _endDt = new Date();
|
||||
log.info("FFMPEG执行命令结束,Code:【{}】,耗费时间:【{}ms】,命令:【{}】", code, _endDt.getTime() - _startDt.getTime(), String.join(" ", ffmpegCmd));
|
||||
return 0 == code;
|
||||
} else {
|
||||
log.error("FFMPEG执行命令没有在1分钟内退出,命令:【{}】", String.join(" ", ffmpegCmd));
|
||||
log.error("FFMPEG执行命令没有在90秒内退出,命令:【{}】", String.join(" ", ffmpegCmd));
|
||||
ffmpegProcess.destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user