From fa990520784100543ea48c87e0e6b271b3fdd398 Mon Sep 17 00:00:00 2001 From: "1378012178@qq.com" <1378012178@qq.com> Date: Wed, 6 May 2026 08:43:44 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81mp4=E5=88=87=E7=89=87=E3=80=81?= =?UTF-8?q?=E6=8B=BC=E6=8E=A5=20=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B=202?= =?UTF-8?q?=E3=80=81=E6=8C=87=E4=BB=A4=E5=B7=A5=E5=8D=95=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E8=A7=86=E9=A2=91=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1-?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=9B=B8=E5=85=B3=E4=BB=A3=E7=A0=81=E8=B0=83?= =?UTF-8?q?=E6=95=B4=EF=BC=88=E9=9D=9E=E6=9C=80=E7=BB=88=E6=AD=A3=E5=BC=8F?= =?UTF-8?q?=E7=89=88=E6=9C=AC=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nursing-unit-services/nu-services-biz/pom.xml | 119 +++++++---- .../biz/order/entity/DirectiveOrder.java | 8 +- .../order/job/DirectiveOrderEndTplinkJob.java | 29 ++- .../nu/modules/biz/order/job/Mp4TestJob.java | 192 ++++++++++++++++++ .../order/mapper/xml/DirectiveOrderMapper.xml | 2 +- 5 files changed, 293 insertions(+), 57 deletions(-) create mode 100644 nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/order/job/Mp4TestJob.java diff --git a/nursing-unit-services/nu-services-biz/pom.xml b/nursing-unit-services/nu-services-biz/pom.xml index cc3ca517..7a22fc94 100644 --- a/nursing-unit-services/nu-services-biz/pom.xml +++ b/nursing-unit-services/nu-services-biz/pom.xml @@ -1,60 +1,60 @@ - - com.nursingunit.boot - nursing-unit-services - 2.0.0 - - 4.0.0 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + com.nursingunit.boot + nursing-unit-services + 2.0.0 + + 4.0.0 - nu-services-biz + nu-services-biz - - - com.nursingunit.boot - nu-services-local-api - ${nursingunit.version} - - - org.hibernate - hibernate-core - - - org.jeecgframework.boot - hibernate-re - + + + com.nursingunit.boot + nu-services-local-api + ${nursingunit.version} + + + org.hibernate + hibernate-core + + + org.jeecgframework.boot + hibernate-re + - - - org.jeecgframework - weixin4j - + + + org.jeecgframework + weixin4j + - - - com.belerweb - pinyin4j - 2.5.0 - + + + com.belerweb + pinyin4j + 2.5.0 + - - - com.nursingunit.boot - nursing-unit-common - ${nursingunit.version} - + + + com.nursingunit.boot + nursing-unit-common + ${nursingunit.version} + com.nursingunit.boot nu-system-local-api 2.0.0 compile - - com.nursingunit.boot - nu-admin-local-api - ${nursingunit.version} - + + com.nursingunit.boot + nu-admin-local-api + ${nursingunit.version} + com.nursingunit.boot nu-admin-biz @@ -73,6 +73,35 @@ 2.0.0 compile + + + + + org.bytedeco + javacv + 1.5.9 + + + org.bytedeco + javacpp + + + + + + + org.bytedeco + ffmpeg + 6.0-1.5.9 + + + + + org.bytedeco + ffmpeg + 6.0-1.5.9 + linux-x86_64 + diff --git a/nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/order/entity/DirectiveOrder.java b/nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/order/entity/DirectiveOrder.java index 9d521c00..e38e30c5 100644 --- a/nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/order/entity/DirectiveOrder.java +++ b/nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/order/entity/DirectiveOrder.java @@ -1,9 +1,6 @@ package com.nu.modules.biz.order.entity; -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.*; import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.annotations.ApiModel; import lombok.Data; @@ -11,6 +8,7 @@ import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; import org.springframework.format.annotation.DateTimeFormat; +import javax.persistence.Column; import java.io.Serializable; import java.math.BigDecimal; import java.util.Date; @@ -212,6 +210,8 @@ public class DirectiveOrder implements Serializable { /** * tplink监控视频上传任务ID */ + @TableField(updateStrategy = FieldStrategy.IGNORED) + @Column(nullable = true, updatable = true) private String tplinkTaskId; /** * tplink切片crc64 diff --git a/nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/order/job/DirectiveOrderEndTplinkJob.java b/nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/order/job/DirectiveOrderEndTplinkJob.java index 9097698f..78fd034b 100644 --- a/nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/order/job/DirectiveOrderEndTplinkJob.java +++ b/nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/order/job/DirectiveOrderEndTplinkJob.java @@ -1,7 +1,6 @@ package com.nu.modules.biz.order.job; import com.alibaba.fastjson.JSONObject; -import com.nu.entity.CosFileInfoEntity; import com.nu.entity.DirectiveOrderEntity; import com.nu.modules.biz.order.entity.DirectiveOrder; import com.nu.modules.biz.order.service.IDirectiveOrderJobService; @@ -9,7 +8,6 @@ import com.nu.modules.sysconfig.ISysConfigApi; import com.nu.modules.tplink.camera.service.ICameraInfoJobService; import com.nu.utils.CosFileUtil; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.compress.utils.Lists; import org.apache.commons.lang.StringUtils; import org.jeecg.common.system.api.ISysBaseAPI; import org.jeecg.common.util.DateUtils; @@ -18,7 +16,6 @@ import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.util.CollectionUtils; import java.util.Date; import java.util.List; @@ -65,10 +62,28 @@ public class DirectiveOrderEndTplinkJob implements Job { { List directiveOrderList = directiveOrderService.getUploadingTplink(); directiveOrderList.forEach(order -> { - Map result = tplinkService.getUploadToServerProcess(order.getTplinkTaskId()); - System.out.println("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓"); - System.out.println(result.toString()); - System.out.println("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"); + try { + Map result = tplinkService.getUploadToServerProcess(order.getTplinkTaskId()); + System.out.println("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓"); + System.out.println(result.toString()); + System.out.println("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"); + + if("success".equals(result.get("result")) ){ + if("1000".equals(result.get("process"))){ + order.setTplinkParams("成功"); + order.setOrderEndTime(new Date()); + order.setTplinkTaskId(null); + directiveOrderService.updateById(order); + } + }else{ + order.setTplinkParams(result.toString()); + order.setOrderEndTime(new Date()); + order.setTplinkTaskId(null); + directiveOrderService.updateById(order); + } + } catch (Exception e) { + e.printStackTrace(); + } }); } // //任务一:COS上传进度查询 diff --git a/nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/order/job/Mp4TestJob.java b/nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/order/job/Mp4TestJob.java new file mode 100644 index 00000000..86230c47 --- /dev/null +++ b/nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/order/job/Mp4TestJob.java @@ -0,0 +1,192 @@ +package com.nu.modules.biz.order.job; + +import lombok.extern.slf4j.Slf4j; +import org.bytedeco.ffmpeg.ffmpeg; +import org.bytedeco.javacpp.Loader; +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.time.Duration; +import java.time.LocalDateTime; + +/** + * 测试mp4视频切割与拼接 + * 策略:完全基于时间轴计算,不依赖视频元数据 + */ +@Slf4j +public class Mp4TestJob implements Job { + + // ================= 常量配置 ================= + // 宿主机映射到容器内的路径 + private static final String BASE_PATH = "/opt/biz"; + private static final String INPUT_FILE_CONTAINER = BASE_PATH + "/upFiles101/2510101004-20260429130857-2039611288698490882.mp4"; + + // 视频元数据(硬编码) + private static final LocalDateTime VIDEO_START_TIME = LocalDateTime.of(2026, 4, 29, 10, 59, 58); + private static final long VIDEO_TOTAL_DURATION = 3601L; // 1小时01秒 + + @Override + public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { + try { + // 确保输出目录存在 + File outputDir = new File(BASE_PATH); + if (!outputDir.exists()) { + outputDir.mkdirs(); + } + + // ================= 任务规划 ================= + // 目标: + // 1. 切 Part1: 11:15:15 -> 11:30:15 + // 2. 切 Part2: 11:30:16 -> 11:45:15 + // 3. 拼接: Part1的后半段(从11:20:15开始) + Part2的前半段(到11:40:15结束) + + // 1. 处理 Part 1 + LocalDateTime p1StartTarget = LocalDateTime.of(2026, 4, 29, 11, 15, 15); + LocalDateTime p1EndTarget = LocalDateTime.of(2026, 4, 29, 11, 30, 15); + String part1File = BASE_PATH + "/part1_111515.mp4"; + + // 计算在源文件中的偏移量和时长 + long p1Offset = getSecondsFromStart(p1StartTarget); + long p1Duration = Duration.between(p1StartTarget, p1EndTarget).getSeconds(); + cutVideo(INPUT_FILE_CONTAINER, part1File, p1Offset, p1Duration); + + // 2. 处理 Part 2 + LocalDateTime p2StartTarget = LocalDateTime.of(2026, 4, 29, 11, 30, 16); + LocalDateTime p2EndTarget = LocalDateTime.of(2026, 4, 29, 11, 45, 15); + String part2File = BASE_PATH + "/part2_113016.mp4"; + + long p2Offset = getSecondsFromStart(p2StartTarget); + long p2Duration = Duration.between(p2StartTarget, p2EndTarget).getSeconds(); + cutVideo(INPUT_FILE_CONTAINER, part2File, p2Offset, p2Duration); + + // 3. 拼接逻辑 + // 需求:取 Part1 中 11:20:15 之后的内容 + Part2 中 11:40:15 之前的内容 + LocalDateTime splicePointP1 = LocalDateTime.of(2026, 4, 29, 11, 20, 15); + LocalDateTime splicePointP2 = LocalDateTime.of(2026, 4, 29, 11, 40, 15); + + String finalFile = BASE_PATH + "/final_112015_114015.mp4"; + spliceVideo(part1File, part2File, finalFile, splicePointP1, p1StartTarget, splicePointP2, p2StartTarget); + + } catch (Exception e) { + log.error("视频处理失败", e); + throw new JobExecutionException(e); + } + } + + /** + * 计算目标时间相对于视频开始时间(2026-04-29 10:59:58)的秒数偏移 + */ + private long getSecondsFromStart(LocalDateTime targetTime) { + return Duration.between(VIDEO_START_TIME, targetTime).getSeconds(); + } + + /** + * 切割视频 + * @param input 输入文件 + * @param output 输出文件 + * @param startTime 在源文件中的开始偏移量(秒) + * @param duration 持续时间(秒) + */ + private void cutVideo(String input, String output, long startTime, long duration) throws IOException, InterruptedException { + log.info("开始切割: [偏移{}秒, 时长{}秒] -> {}", startTime, duration, output); + long start = System.currentTimeMillis(); + + String ffmpeg = Loader.load(ffmpeg.class); + ProcessBuilder pb = new ProcessBuilder( + ffmpeg, + "-ss", String.valueOf(startTime), + "-i", input, + "-t", String.valueOf(duration), + "-c", "copy", + "-avoid_negative_ts", "make_zero", + output + ); + // 继承标准输出和错误流,方便调试 + pb.inheritIO().start().waitFor(); + + long cost = System.currentTimeMillis() - start; + log.info("切割完成: {}, 耗时: {}秒", output, cost / 1000); + } + + /** + * 拼接视频 + * 逻辑: + * 1. 计算 Part1 需要截取的时长 (从 splicePoint 到 Part1 结束) + * 2. 计算 Part2 需要截取的时长 (从 Part2 开始 到 splicePoint) + * 3. 生成临时列表并执行拼接 + */ + private void spliceVideo(String part1, String part2, String output, + LocalDateTime p1SplicePoint, LocalDateTime p1Base, + LocalDateTime p2SplicePoint, LocalDateTime p2Base) throws IOException, InterruptedException { + + log.info("开始拼接: {} + {} -> {}", part1, part2, output); + long start = System.currentTimeMillis(); + + // 1. 计算 Part1 截取时长:目标拼接点 - Part1起始时间 + long p1CutDuration = Duration.between(p1Base, p1SplicePoint).getSeconds(); + + // 2. 计算 Part2 截取时长:目标拼接点 - Part2起始时间 + long p2CutDuration = Duration.between(p2Base, p2SplicePoint).getSeconds(); + + // 3. 创建临时 concat 文件 + // 注意:这里使用 input seeking 方式 (-ss 在 -i 之前) 性能最好 + String concatFile = BASE_PATH + "/concat_list.txt"; + try (FileWriter writer = new FileWriter(concatFile)) { + // 第一部分:从 part1 开头截取 p1CutDuration 秒 + writer.write("file '" + part1 + "'\n"); + // 第二部分:从 part2 开头截取 p2CutDuration 秒 + writer.write("file '" + part2 + "'\n"); + } + + String ffmpeg = Loader.load(ffmpeg.class); + // 使用 -ss 配合 -t 对 concat 流进行切片 + // 这里有个技巧:因为我们要的是 Part1 的后半段,但 concat 只能顺序读。 + // 所以更简单的做法是:分别切出这两段临时文件,再合并。 + // 但为了演示 concat 协议,我们采用“先合并再修剪”或者“分别修剪再合并”。 + // 鉴于 -c copy 的限制,最稳妥的“拼接特定片段”方案是: + // 1. 先切出 Part1 的后半段临时文件 + // 2. 再切出 Part2 的前半段临时文件 + // 3. 合并这两个临时文件 + + // --- 修正方案:三步走 --- + String temp1 = BASE_PATH + "/temp_p1_part.mp4"; + String temp2 = BASE_PATH + "/temp_p2_part.mp4"; + + // 步骤 A: 提取 Part1 的后半段 (从 p1CutDuration 开始,直到文件结束) + // 注意:这里不需要 -t,直接截取到末尾 + ProcessBuilder pb1 = new ProcessBuilder(ffmpeg, "-ss", String.valueOf(p1CutDuration), "-i", part1, "-c", "copy", "-avoid_negative_ts", "make_zero", temp1); + pb1.inheritIO().start().waitFor(); + + // 步骤 B: 提取 Part2 的前半段 (从 0 开始,时长 p2CutDuration) + ProcessBuilder pb2 = new ProcessBuilder(ffmpeg, "-i", part2, "-t", String.valueOf(p2CutDuration), "-c", "copy", temp2); + pb2.inheritIO().start().waitFor(); + + // 步骤 C: 拼接 temp1 和 temp2 + try (FileWriter writer = new FileWriter(concatFile)) { + writer.write("file '" + temp1 + "'\n"); + writer.write("file '" + temp2 + "'\n"); + } + + ProcessBuilder pbConcat = new ProcessBuilder( + ffmpeg, + "-f", "concat", + "-safe", "0", + "-i", concatFile, + "-c", "copy", + output + ); + pbConcat.inheritIO().start().waitFor(); + + // 清理临时文件 + new File(concatFile).delete(); + new File(temp1).delete(); + new File(temp2).delete(); + + long cost = System.currentTimeMillis() - start; + log.info("拼接完成: {}, 耗时: {}秒", output, cost / 1000); + } +} diff --git a/nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/order/mapper/xml/DirectiveOrderMapper.xml b/nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/order/mapper/xml/DirectiveOrderMapper.xml index d6c61968..3d0d8e39 100644 --- a/nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/order/mapper/xml/DirectiveOrderMapper.xml +++ b/nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/order/mapper/xml/DirectiveOrderMapper.xml @@ -751,7 +751,7 @@