diff --git a/nursing-unit-common/src/main/java/com/nu/entity/Mp4HandleEntity.java b/nursing-unit-common/src/main/java/com/nu/entity/Mp4HandleEntity.java new file mode 100644 index 00000000..94ad06a7 --- /dev/null +++ b/nursing-unit-common/src/main/java/com/nu/entity/Mp4HandleEntity.java @@ -0,0 +1,23 @@ +package com.nu.entity; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * @Description: mp4视频处理信息 + * @Author: jeecg-boot + * @Date: 2026-05-09 + * @Version: V1.0 + */ +@Data +public class Mp4HandleEntity implements Serializable { + private Date startTime; + private Date endTime; + private String orgCode; + private String orderId; + private List taskList; +} + diff --git a/nursing-unit-common/src/main/java/com/nu/entity/TplinkInfoEntity.java b/nursing-unit-common/src/main/java/com/nu/entity/TplinkInfoEntity.java new file mode 100644 index 00000000..bc59aba8 --- /dev/null +++ b/nursing-unit-common/src/main/java/com/nu/entity/TplinkInfoEntity.java @@ -0,0 +1,38 @@ +package com.nu.entity; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * @Description: 视频切片信息 + * @Author: jeecg-boot + * @Date: 2026-05-09 + * @Version: V1.0 + */ +@Data +public class TplinkInfoEntity implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 设备标识 + */ + private String sn; + /** + * 开始时间 + */ + private Date startTime; + /** + * 结束时间 + */ + private Date endTime; + /** + * tplink网络地址 + */ + private String tplinkPath; + /** + * 机构编码 + */ + private String orgCode; +} diff --git a/nursing-unit-iot/nu-iot-biz/src/main/java/com/nu/modules/tplink/uploadlog/controller/TplinkUploadLogController.java b/nursing-unit-iot/nu-iot-biz/src/main/java/com/nu/modules/tplink/uploadlog/controller/TplinkUploadLogController.java index dc8f3098..51a3e238 100644 --- a/nursing-unit-iot/nu-iot-biz/src/main/java/com/nu/modules/tplink/uploadlog/controller/TplinkUploadLogController.java +++ b/nursing-unit-iot/nu-iot-biz/src/main/java/com/nu/modules/tplink/uploadlog/controller/TplinkUploadLogController.java @@ -1,163 +1,162 @@ package com.nu.modules.tplink.uploadlog.controller; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.jeecg.common.api.vo.Result; -import org.jeecg.common.system.query.QueryGenerator; -import org.jeecg.common.system.query.QueryRuleEnum; -import org.jeecg.common.util.oConvertUtils; -import com.nu.modules.tplink.uploadlog.entity.TplinkUploadLog; -import com.nu.modules.tplink.uploadlog.service.ITplinkUploadLogService; - +import cn.hutool.core.bean.BeanUtil; +import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import lombok.extern.slf4j.Slf4j; - -import org.jeecgframework.poi.excel.ExcelImportUtil; -import org.jeecgframework.poi.excel.def.NormalExcelConstants; -import org.jeecgframework.poi.excel.entity.ExportParams; -import org.jeecgframework.poi.excel.entity.ImportParams; -import org.jeecgframework.poi.excel.view.JeecgEntityExcelView; -import org.jeecg.common.system.base.controller.JeecgController; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.multipart.MultipartHttpServletRequest; -import org.springframework.web.servlet.ModelAndView; -import com.alibaba.fastjson.JSON; +import com.nu.entity.Mp4HandleEntity; +import com.nu.entity.TplinkInfoEntity; +import com.nu.modules.sysconfig.ISysConfigApi; +import com.nu.modules.tplink.uploadlog.entity.TplinkUploadLog; +import com.nu.modules.tplink.uploadlog.service.ITplinkUploadLogService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; -import org.jeecg.common.aspect.annotation.AutoLog; +import lombok.extern.slf4j.Slf4j; import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.jeecg.common.api.vo.Result; +import org.jeecg.common.aspect.annotation.AutoLog; +import org.jeecg.common.system.base.controller.JeecgController; +import org.jeecg.common.system.query.QueryGenerator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.util.CollectionUtils; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.servlet.ModelAndView; - /** +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +/** * @Description: tplink切片上传日志 * @Author: jeecg-boot - * @Date: 2026-05-09 + * @Date: 2026-05-09 * @Version: V1.0 */ -@Api(tags="tplink切片上传日志") +@Api(tags = "tplink切片上传日志") @RestController @RequestMapping("/tplink/uploadlog/tplinkUploadLog") @Slf4j public class TplinkUploadLogController extends JeecgController { - @Autowired - private ITplinkUploadLogService tplinkUploadLogService; - - /** - * 分页列表查询 - * - * @param tplinkUploadLog - * @param pageNo - * @param pageSize - * @param req - * @return - */ - //@AutoLog(value = "tplink切片上传日志-分页列表查询") - @ApiOperation(value="tplink切片上传日志-分页列表查询", notes="tplink切片上传日志-分页列表查询") - @GetMapping(value = "/list") - public Result> queryPageList(TplinkUploadLog tplinkUploadLog, - @RequestParam(name="pageNo", defaultValue="1") Integer pageNo, - @RequestParam(name="pageSize", defaultValue="10") Integer pageSize, - HttpServletRequest req) { - QueryWrapper queryWrapper = QueryGenerator.initQueryWrapper(tplinkUploadLog, req.getParameterMap()); - Page page = new Page(pageNo, pageSize); - IPage pageList = tplinkUploadLogService.page(page, queryWrapper); - return Result.OK(pageList); - } - - /** - * 添加 - * - * @param tplinkUploadLog - * @return - */ - @AutoLog(value = "tplink切片上传日志-添加") - @ApiOperation(value="tplink切片上传日志-添加", notes="tplink切片上传日志-添加") - @RequiresPermissions("tplink.uploadlog:nu_iot_tplink_upload_log:add") - @PostMapping(value = "/add") - public Result add(@RequestBody TplinkUploadLog tplinkUploadLog) { - tplinkUploadLogService.save(tplinkUploadLog); - return Result.OK("添加成功!"); - } - - /** - * 编辑 - * - * @param tplinkUploadLog - * @return - */ - @AutoLog(value = "tplink切片上传日志-编辑") - @ApiOperation(value="tplink切片上传日志-编辑", notes="tplink切片上传日志-编辑") - @RequiresPermissions("tplink.uploadlog:nu_iot_tplink_upload_log:edit") - @RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST}) - public Result edit(@RequestBody TplinkUploadLog tplinkUploadLog) { - tplinkUploadLogService.updateById(tplinkUploadLog); - return Result.OK("编辑成功!"); - } - - /** - * 通过id删除 - * - * @param id - * @return - */ - @AutoLog(value = "tplink切片上传日志-通过id删除") - @ApiOperation(value="tplink切片上传日志-通过id删除", notes="tplink切片上传日志-通过id删除") - @RequiresPermissions("tplink.uploadlog:nu_iot_tplink_upload_log:delete") - @DeleteMapping(value = "/delete") - public Result delete(@RequestParam(name="id",required=true) String id) { - tplinkUploadLogService.removeById(id); - return Result.OK("删除成功!"); - } - - /** - * 批量删除 - * - * @param ids - * @return - */ - @AutoLog(value = "tplink切片上传日志-批量删除") - @ApiOperation(value="tplink切片上传日志-批量删除", notes="tplink切片上传日志-批量删除") - @RequiresPermissions("tplink.uploadlog:nu_iot_tplink_upload_log:deleteBatch") - @DeleteMapping(value = "/deleteBatch") - public Result deleteBatch(@RequestParam(name="ids",required=true) String ids) { - this.tplinkUploadLogService.removeByIds(Arrays.asList(ids.split(","))); - return Result.OK("批量删除成功!"); - } - - /** - * 通过id查询 - * - * @param id - * @return - */ - //@AutoLog(value = "tplink切片上传日志-通过id查询") - @ApiOperation(value="tplink切片上传日志-通过id查询", notes="tplink切片上传日志-通过id查询") - @GetMapping(value = "/queryById") - public Result queryById(@RequestParam(name="id",required=true) String id) { - TplinkUploadLog tplinkUploadLog = tplinkUploadLogService.getById(id); - if(tplinkUploadLog==null) { - return Result.error("未找到对应数据"); - } - return Result.OK(tplinkUploadLog); - } + @Autowired + private ITplinkUploadLogService tplinkUploadLogService; + @Autowired + private ISysConfigApi sysConfigApi; /** - * 导出excel - * - * @param request - * @param tplinkUploadLog - */ + * 分页列表查询 + * + * @param tplinkUploadLog + * @param pageNo + * @param pageSize + * @param req + * @return + */ + //@AutoLog(value = "tplink切片上传日志-分页列表查询") + @ApiOperation(value = "tplink切片上传日志-分页列表查询", notes = "tplink切片上传日志-分页列表查询") + @GetMapping(value = "/list") + public Result> queryPageList(TplinkUploadLog tplinkUploadLog, + @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo, + @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, + HttpServletRequest req) { + QueryWrapper queryWrapper = QueryGenerator.initQueryWrapper(tplinkUploadLog, req.getParameterMap()); + Page page = new Page(pageNo, pageSize); + IPage pageList = tplinkUploadLogService.page(page, queryWrapper); + return Result.OK(pageList); + } + + /** + * 添加 + * + * @param tplinkUploadLog + * @return + */ + @AutoLog(value = "tplink切片上传日志-添加") + @ApiOperation(value = "tplink切片上传日志-添加", notes = "tplink切片上传日志-添加") + @RequiresPermissions("tplink.uploadlog:nu_iot_tplink_upload_log:add") + @PostMapping(value = "/add") + public Result add(@RequestBody TplinkUploadLog tplinkUploadLog) { + tplinkUploadLogService.save(tplinkUploadLog); + return Result.OK("添加成功!"); + } + + /** + * 编辑 + * + * @param tplinkUploadLog + * @return + */ + @AutoLog(value = "tplink切片上传日志-编辑") + @ApiOperation(value = "tplink切片上传日志-编辑", notes = "tplink切片上传日志-编辑") + @RequiresPermissions("tplink.uploadlog:nu_iot_tplink_upload_log:edit") + @RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST}) + public Result edit(@RequestBody TplinkUploadLog tplinkUploadLog) { + tplinkUploadLogService.updateById(tplinkUploadLog); + return Result.OK("编辑成功!"); + } + + /** + * 通过id删除 + * + * @param id + * @return + */ + @AutoLog(value = "tplink切片上传日志-通过id删除") + @ApiOperation(value = "tplink切片上传日志-通过id删除", notes = "tplink切片上传日志-通过id删除") + @RequiresPermissions("tplink.uploadlog:nu_iot_tplink_upload_log:delete") + @DeleteMapping(value = "/delete") + public Result delete(@RequestParam(name = "id", required = true) String id) { + tplinkUploadLogService.removeById(id); + return Result.OK("删除成功!"); + } + + /** + * 批量删除 + * + * @param ids + * @return + */ + @AutoLog(value = "tplink切片上传日志-批量删除") + @ApiOperation(value = "tplink切片上传日志-批量删除", notes = "tplink切片上传日志-批量删除") + @RequiresPermissions("tplink.uploadlog:nu_iot_tplink_upload_log:deleteBatch") + @DeleteMapping(value = "/deleteBatch") + public Result deleteBatch(@RequestParam(name = "ids", required = true) String ids) { + this.tplinkUploadLogService.removeByIds(Arrays.asList(ids.split(","))); + return Result.OK("批量删除成功!"); + } + + /** + * 通过id查询 + * + * @param id + * @return + */ + //@AutoLog(value = "tplink切片上传日志-通过id查询") + @ApiOperation(value = "tplink切片上传日志-通过id查询", notes = "tplink切片上传日志-通过id查询") + @GetMapping(value = "/queryById") + public Result queryById(@RequestParam(name = "id", required = true) String id) { + TplinkUploadLog tplinkUploadLog = tplinkUploadLogService.getById(id); + if (tplinkUploadLog == null) { + return Result.error("未找到对应数据"); + } + return Result.OK(tplinkUploadLog); + } + + /** + * 导出excel + * + * @param request + * @param tplinkUploadLog + */ @RequiresPermissions("tplink.uploadlog:nu_iot_tplink_upload_log:exportXls") @RequestMapping(value = "/exportXls") public ModelAndView exportXls(HttpServletRequest request, TplinkUploadLog tplinkUploadLog) { @@ -165,16 +164,80 @@ public class TplinkUploadLogController extends JeecgController importExcel(HttpServletRequest request, HttpServletResponse response) { return super.importExcel(request, response, TplinkUploadLog.class); } + /** + * 获取tplink回放 + * + * @param dto + * @return + */ + @AutoLog(value = "获取tplink回放") + @ApiOperation(value = "获取tplink回放", notes = "获取tplink回放") + @PostMapping(value = "/tplinkPlayBack") + public Result tplinkPlayBack(@RequestBody Mp4HandleEntity dto) { + JSONObject mediaservOpenUrl = sysConfigApi.getByKeyByDS("master", "mediaserv_open_url"); + String mediaservApiAddress = mediaservOpenUrl.getString("configValue"); + + //获取对应日期所有源视频(这个多查出来一些不会影响处理效率) + Date startTime = dto.getStartTime(); + Date endTime = dto.getEndTime(); + LocalDateTime startLdt = startTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); + LocalDateTime endLdt = endTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); + LocalDateTime normalizedStart = startLdt.toLocalDate().atStartOfDay(); + LocalDateTime normalizedEnd = endLdt.toLocalDate().atTime(23, 59, 59); + QueryWrapper qw = new QueryWrapper<>(); + qw.ge("start_time", normalizedStart) + .le("start_time", normalizedEnd); + List sourceMp4List = tplinkUploadLogService.list(qw); + + if (CollectionUtils.isEmpty(sourceMp4List)) { + return Result.error("无回放文件"); + } + + dto.setOrgCode(sourceMp4List.get(0).getOrgCode()); + dto.setTaskList(BeanUtil.copyToList(sourceMp4List, TplinkInfoEntity.class)); + + + try { + //完整请求地址 + String targetUrl = mediaservApiAddress + "/api/tplink/tplinkPlayBack"; + + RestTemplate restTemplate = new RestTemplate(); + ResponseEntity> response = restTemplate.exchange( + targetUrl, + HttpMethod.POST, + new HttpEntity<>(dto), + new ParameterizedTypeReference>() { + } + ); + + //处理返回结果 + if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) { + Result remoteResult = response.getBody(); + if (remoteResult.getCode() == 200) { + String videoPath = remoteResult.getResult(); + return Result.OK(videoPath); + } else { + return Result.error("无回放文件-api500"); + } + } else { + return Result.error("无回放文件-api404"); + } + + } catch (Exception e) { + e.printStackTrace(); + return Result.error("无回放文件-请求异常"); + } + } } diff --git a/nursing-unit-services/nu-services-biz/pom.xml b/nursing-unit-services/nu-services-biz/pom.xml index 7a22fc94..f8ca57cf 100644 --- a/nursing-unit-services/nu-services-biz/pom.xml +++ b/nursing-unit-services/nu-services-biz/pom.xml @@ -74,34 +74,6 @@ 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/job/Mp4TestJob.java b/nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/order/job/Mp4TestJob.java deleted file mode 100644 index 86230c47..00000000 --- a/nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/order/job/Mp4TestJob.java +++ /dev/null @@ -1,192 +0,0 @@ -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); - } -}