From 3c838ec36e4e052deb5e69d882adc7cf1bf73f65 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Mon, 15 Dec 2025 10:14:02 +0800 Subject: [PATCH] =?UTF-8?q?refactor(video):=20=E4=BC=98=E5=8C=96=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E5=88=87=E5=89=B2=E9=80=BB=E8=BE=91=EF=BC=8C=E4=BD=BF?= =?UTF-8?q?=E7=94=A8concat=20demuxer=E6=8F=90=E5=8D=87=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 引入concat demuxer方式替代原有转码流程,提高处理效率 - 新增PROBE_SIZE常量用于控制探测大小,优化文件解析 - 重构runFfmpegForMultipleFile1方法,简化多文件处理逻辑 - 添加quickVideoCutWithConcatDemuxer方法实现无转码快速切割 - 调整ffmpeg命令参数顺序及新增选项,如-probesize、-analyzeduration等 - 在多个ffmpeg调用中统一增加-genpts标志和避免负时间戳处理 - 完善临时文件清理机制,确保执行过程中的资源回收 - 更新相关ffmpeg命令构建逻辑以适配新的处理流程 --- .../com/ycwl/basic/task/VideoPieceGetter.java | 139 +++++++++++------- 1 file changed, 86 insertions(+), 53 deletions(-) diff --git a/src/main/java/com/ycwl/basic/task/VideoPieceGetter.java b/src/main/java/com/ycwl/basic/task/VideoPieceGetter.java index 74557e59..993bad6a 100644 --- a/src/main/java/com/ycwl/basic/task/VideoPieceGetter.java +++ b/src/main/java/com/ycwl/basic/task/VideoPieceGetter.java @@ -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 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 -> { - 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(); - } - return result; - } else { - return true; - } - } catch (IOException e) { - log.warn("转码出错"); - return false; - } - }).anyMatch(b -> !b); - // 转码进程中出现问题 - if (notOk) { - return false; - } - // 步骤二:使用concat协议拼接裁切 - boolean result; + // 使用concat demuxer(文件列表)拼接裁切 + File concatListFile = null; try { - result = quickVideoCut( - "concat:" + task.getFileList().stream().map(FileObject::getUrl).collect(Collectors.joining("|")), - task.getOffsetStart(), task.getDuration(), task.getOutputFile() + // 创建临时文件列表 + 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"); + } + } + + // 使用concat demuxer进行裁切 + return quickVideoCutWithConcatDemuxer( + concatListFile.getAbsolutePath(), + task.getOffsetStart(), + task.getDuration(), + task.getOutputFile() ); } catch (IOException e) { + log.error("使用concat demuxer切割视频失败", e); return false; - } - // 步骤三:删除临时文件 - task.getFileList().stream().map(FileObject::getUrl).forEach(tmpFile -> { - File f = new File(tmpFile); - if (f.exists() && f.isFile()) { - f.delete(); + } finally { + // 清理临时文件 + if (concatListFile != null && concatListFile.exists()) { + concatListFile.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 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; }