| | |
| | | public PageResult<CourseMediaDO> getCourseMediaPage(CourseMediaPageReqVO pageReqVO) { |
| | | PageResult<CourseMediaDO> courseMediaDOPageResult = courseMediaMapper.selectPage(pageReqVO); |
| | | for (CourseMediaDO courseMediaDO : courseMediaDOPageResult.getList()) { |
| | | if (courseMediaDO.getStatus() == 1 || courseMediaDO.getStatus() == 0) { |
| | | //视频合成中 查询排队和合成进度 |
| | | Long id = courseMediaDO.getCourseId(); |
| | | int pos = mediaTaskManager.getQueuePosition(id); |
| | | if (pos == -1) { |
| | | //不在队列中,说明已经合成完成 |
| | | courseMediaDO.setStatus(3); |
| | | courseMediaMapper.updateById(courseMediaDO); |
| | | } |
| | | if (pos == 0) { |
| | | //正在合成中 |
| | | String reqJson = courseMediaDO.getReqJson(); |
| | | CourseMediaMegerVO courseMediaMegerVO = JSON.parseObject(reqJson, CourseMediaMegerVO.class); |
| | | int size = courseMediaMegerVO.getScenes().size(); |
| | | String s = configApi.getConfigValueByKey(HEYGEM_FACE2FACE) + "/temp/"; |
| | | //查询s下面的文件 |
| | | File folder = new File(s); |
| | | int count = 0; |
| | | if (courseMediaDO.getStatus() == 1 || courseMediaDO.getStatus() == 0) { |
| | | //视频合成中 查询排队和合成进度 |
| | | Long id = courseMediaDO.getCourseId(); |
| | | int pos = mediaTaskManager.getQueuePosition(id); |
| | | if (pos == -1) { |
| | | //不在队列中,说明已经合成完成 |
| | | courseMediaDO.setStatus(3); |
| | | courseMediaMapper.updateById(courseMediaDO); |
| | | } |
| | | if (pos == 0) { |
| | | //正在合成中 |
| | | String reqJson = courseMediaDO.getReqJson(); |
| | | CourseMediaMegerVO courseMediaMegerVO = JSON.parseObject(reqJson, CourseMediaMegerVO.class); |
| | | int size = courseMediaMegerVO.getScenes().size(); |
| | | String s = configApi.getConfigValueByKey(HEYGEM_FACE2FACE) + "/temp/"; |
| | | //查询s下面的文件 |
| | | File folder = new File(s); |
| | | int count = 0; |
| | | |
| | | if (folder.exists() && folder.isDirectory()) { |
| | | File[] files = folder.listFiles(); |
| | | if (files != null) { |
| | | for (File file : files) { |
| | | if (file.isFile() && file.getName().endsWith("-r.mp4")) { |
| | | count++; |
| | | System.out.println("匹配文件: " + file.getName()); |
| | | } |
| | | } |
| | | } |
| | | System.out.println("总计匹配 -r.mp4 文件数量: " + count); |
| | | } else { |
| | | System.out.println("路径不存在或不是目录"); |
| | | } |
| | | if (count+1>size){ |
| | | courseMediaDO.setProgressVideo((count) + "/" + size); |
| | | }else{ |
| | | courseMediaDO.setProgressVideo((count)+"/"+size); |
| | | if (folder.exists() && folder.isDirectory()) { |
| | | File[] files = folder.listFiles(); |
| | | if (files != null) { |
| | | for (File file : files) { |
| | | if (file.isFile() && file.getName().endsWith("-r.mp4")) { |
| | | count++; |
| | | System.out.println("匹配文件: " + file.getName()); |
| | | } |
| | | } |
| | | } |
| | | System.out.println("总计匹配 -r.mp4 文件数量: " + count); |
| | | } else { |
| | | System.out.println("路径不存在或不是目录"); |
| | | } |
| | | if (count+1>size){ |
| | | courseMediaDO.setProgressVideo((count) + "/" + size); |
| | | }else{ |
| | | courseMediaDO.setProgressVideo((count)+"/"+size); |
| | | |
| | | } |
| | | } |
| | | courseMediaDO.setPos(pos); |
| | | } |
| | | } |
| | | } |
| | | courseMediaDO.setPos(pos); |
| | | } |
| | | |
| | | } |
| | | return courseMediaDOPageResult; |
| | |
| | | */ |
| | | @Override |
| | | public CommonResult createCompositeVideo(CourseMediaSubtitlesReqVO courseMediaSubtitlesReqVO) { |
| | | //生成时间戳 |
| | | // 生成时间戳 |
| | | String timestamp = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()); |
| | | // 片头地址 |
| | | |
| | | // 片头、片尾和视频地址 |
| | | String titles = courseMediaSubtitlesReqVO.getTitles(); |
| | | titles = configApi.getConfigValueByKey("easegen.url") + titles.substring(titles.lastIndexOf("/")); |
| | | |
| | | // 片尾地址 |
| | | String trailer = courseMediaSubtitlesReqVO.getTrailer(); |
| | | trailer = configApi.getConfigValueByKey("easegen.url") + trailer.substring(trailer.lastIndexOf("/")); |
| | | |
| | | String videoUrl = courseMediaSubtitlesReqVO.getVideoUrl(); |
| | | String previewUrl = courseMediaSubtitlesReqVO.getPreviewUrl(); |
| | | |
| | | List<String> videoUrls = new ArrayList<>(); |
| | | videoUrls.add(titles); |
| | | String mainVideoPath = ""; |
| | | |
| | | // 检查是否有主视频或预览视频 |
| | | if (videoUrl != null) { |
| | | mainVideoPath = configApi.getConfigValueByKey("easegen.url") + videoUrl.substring(videoUrl.lastIndexOf("/")); |
| | | videoUrls.add(mainVideoPath); |
| | | videoUrls.add(mainVideoPath); |
| | | videoUrl = configApi.getConfigValueByKey("easegen.url") + videoUrl.substring(videoUrl.lastIndexOf("/")); |
| | | videoUrls.add(videoUrl); |
| | | videoUrls.add(trailer); |
| | | } else if (previewUrl != null) { |
| | | mainVideoPath = configApi.getConfigValueByKey("easegen.url") + previewUrl.substring(previewUrl.lastIndexOf("/")); |
| | | videoUrls.add(mainVideoPath); |
| | | videoUrls.add(mainVideoPath); |
| | | previewUrl = configApi.getConfigValueByKey("easegen.url") + previewUrl.substring(previewUrl.lastIndexOf("/")); |
| | | videoUrls.add(previewUrl); |
| | | videoUrls.add(trailer); |
| | | } |
| | | // 提取主视频的参数 |
| | | List<String> mainVideoParams = extractMainVideoParams(mainVideoPath); |
| | | if (mainVideoParams.isEmpty()) { |
| | | System.err.println("Failed to extract parameters from main video."); |
| | | return CommonResult.error(BAD_REQUEST.getCode(), "合成失败"); |
| | | } |
| | | // 判断文件夹是否存在,如果不存在就创建 |
| | | |
| | | // 判断文件夹是否存在,如果不存在则创建 |
| | | String filePath = configApi.getConfigValueByKey(HEYGEM_FACE2FACE) + "/compositeVideo/"; |
| | | File file = new File(filePath); |
| | | if (!file.exists()) { |
| | | file.mkdirs(); |
| | | } |
| | | // 创建txt 文件 |
| | | try { |
| | | createVideosFile(titles, mainVideoPath, trailer); |
| | | |
| | | // 生成视频文件列表 |
| | | String fileListPath = configApi.getConfigValueByKey(HEYGEM_FACE2FACE) + "/compositeVideo/" + timestamp + ".txt"; |
| | | try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileListPath))) { |
| | | for (String path : videoUrls) { |
| | | writer.write("file '" + path + "'\n"); |
| | | } |
| | | System.out.println("文件列表已生成:" + fileListPath); |
| | | } catch (IOException e) { |
| | | System.err.println("Error creating videos.txt file: " + e.getMessage()); |
| | | return CommonResult.error(BAD_REQUEST.getCode(), "合成失败"); |
| | | e.printStackTrace(); |
| | | } |
| | | // 输出文件路径 |
| | | String outputFilePath = configApi.getConfigValueByKey(HEYGEM_FACE2FACE) + "/compositeVideo/" + timestamp + ".mp4"; |
| | | // 使用 FFmpeg 合并视频并应用主视频的参数 |
| | | mergeVideos(outputFilePath, mainVideoParams,titles,mainVideoPath,trailer); |
| | | |
| | | System.out.println("Video merging completed."); |
| | | // 去掉 courseMediaSubtitlesReqVO.getCourseName() 中的空格和特殊字符 |
| | | String newFileName = courseMediaSubtitlesReqVO.getCourseName().replaceAll("[\\s\\p{Punct}]", ""); |
| | | |
| | | byte[] bytes = FileUtil.readBytes(FileUtil.file(outputFilePath)); |
| | | // 获取主视频分辨率 |
| | | String videoInfo = getVideoResolution(videoUrls.get(1)); // 使用主视频 URL |
| | | String[] resolution = videoInfo.split("x"); |
| | | String width = resolution[0]; |
| | | String height = resolution[1]; |
| | | boolean hasAudio = checkAudio(titles); |
| | | try { |
| | | ProcessBuilder builder = null; |
| | | if (hasAudio) { |
| | | // 视频包含音频,执行相应的命令 |
| | | builder = new ProcessBuilder( |
| | | "ffmpeg", |
| | | "-i", titles, |
| | | "-vf", "scale="+width+":"+height+":force_original_aspect_ratio=decrease,pad="+width+":"+height+":(ow-iw)/2:(oh-ih)/2,setsar=1", |
| | | "-c:v", "libx264", "-preset", "veryfast", |
| | | "-c:a", "aac", "-shortest", configApi.getConfigValueByKey(HEYGEM_FACE2FACE) + "/compositeVideo/" + timestamp + "_intro.mp4" |
| | | ); |
| | | } else { |
| | | // 视频不包含音频,生成空音频并合成 |
| | | builder = new ProcessBuilder( |
| | | "ffmpeg", |
| | | "-i", titles, |
| | | "-f", "lavfi", "-t", "10", "-i", "anullsrc=channel_layout=stereo:sample_rate=44100", |
| | | "-filter_complex", "[0:v]scale="+width+":"+height+":force_original_aspect_ratio=decrease,pad="+width+":"+height+":(ow-iw)/2:(oh-ih)/2,setsar=1[v]", |
| | | "-map", "[v]", "-map", "1:a", |
| | | "-c:v", "libx264", "-preset", "veryfast", |
| | | "-c:a", "aac", "-shortest", configApi.getConfigValueByKey(HEYGEM_FACE2FACE) + "/compositeVideo/" + timestamp + "_intro.mp4" |
| | | ); |
| | | } |
| | | builder.redirectErrorStream(true); |
| | | Process process = null; |
| | | try { |
| | | process = builder.start(); |
| | | |
| | | // 使用 try-with-resources 确保流关闭 |
| | | try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | System.out.println(line); |
| | | } |
| | | } |
| | | |
| | | // 等待 FFmpeg 进程完成 |
| | | int exitCode = process.waitFor(); |
| | | if (exitCode != 0) { |
| | | throw new RuntimeException("FFmpeg 执行失败,退出码:" + exitCode); |
| | | } |
| | | |
| | | System.out.println("片头和片尾已调整分辨率并添加音轨"); |
| | | |
| | | } catch (IOException | InterruptedException e) { |
| | | throw new RuntimeException("FFmpeg 执行异常", e); |
| | | } finally { |
| | | // 确保 Process 的输入/错误流被关闭 |
| | | if (process != null) { |
| | | try { |
| | | process.getInputStream().close(); |
| | | process.getErrorStream().close(); |
| | | process.getOutputStream().close(); |
| | | } catch (IOException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | |
| | | boolean hasAudio1 = checkAudio(trailer); |
| | | try { |
| | | ProcessBuilder builder = null; |
| | | if (hasAudio1) { |
| | | // 视频包含音频,执行相应的命令 |
| | | builder = new ProcessBuilder( |
| | | "ffmpeg", |
| | | "-i", trailer, |
| | | "-vf", "scale="+width+":"+height+":force_original_aspect_ratio=decrease,pad="+width+":"+height+":(ow-iw)/2:(oh-ih)/2,setsar=1", |
| | | "-c:v", "libx264", "-preset", "veryfast", |
| | | "-c:a", "aac", "-shortest", configApi.getConfigValueByKey(HEYGEM_FACE2FACE) + "/compositeVideo/" + timestamp + "_outro.mp4" |
| | | ); |
| | | } else { |
| | | // 视频不包含音频,生成空音频并合成 |
| | | builder = new ProcessBuilder( |
| | | "ffmpeg", |
| | | "-i", trailer, |
| | | "-f", "lavfi", "-t", "10", "-i", "anullsrc=channel_layout=stereo:sample_rate=44100", |
| | | "-filter_complex", "[0:v]scale="+width+":"+height+":force_original_aspect_ratio=decrease,pad="+width+":"+height+":(ow-iw)/2:(oh-ih)/2,setsar=1[v]", |
| | | "-map", "[v]", "-map", "1:a", |
| | | "-c:v", "libx264", "-preset", "veryfast", |
| | | "-c:a", "aac", "-shortest", configApi.getConfigValueByKey(HEYGEM_FACE2FACE) + "/compositeVideo/" + timestamp + "_outro.mp4" |
| | | ); |
| | | } |
| | | builder.redirectErrorStream(true); |
| | | Process process = null; |
| | | try { |
| | | process = builder.start(); |
| | | |
| | | // 使用 try-with-resources 确保流关闭 |
| | | try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | System.out.println(line); |
| | | } |
| | | } |
| | | |
| | | // 等待 FFmpeg 进程完成 |
| | | int exitCode = process.waitFor(); |
| | | if (exitCode != 0) { |
| | | throw new RuntimeException("FFmpeg 执行失败,退出码:" + exitCode); |
| | | } |
| | | |
| | | System.out.println("片头和片尾已调整分辨率并添加音轨"); |
| | | |
| | | } catch (IOException | InterruptedException e) { |
| | | throw new RuntimeException("FFmpeg 执行异常", e); |
| | | } finally { |
| | | // 确保 Process 的输入/错误流被关闭 |
| | | if (process != null) { |
| | | try { |
| | | process.getInputStream().close(); |
| | | process.getErrorStream().close(); |
| | | process.getOutputStream().close(); |
| | | } catch (IOException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | // 合并三个视频:片头、主视频、片尾 |
| | | ProcessBuilder builder10 = new ProcessBuilder( |
| | | "ffmpeg", |
| | | "-i", filePath + timestamp + "_intro.mp4", |
| | | "-i", videoUrls.get(1), |
| | | "-i", filePath + timestamp + "_outro.mp4", |
| | | "-filter_complex", "[0:v][0:a][1:v][1:a][2:v][2:a]concat=n=3:v=1:a=1[v][a]", |
| | | "-map", "[v]", |
| | | "-map", "[a]", |
| | | "-c:v", "libx264", |
| | | "-preset", "veryfast", |
| | | "-c:a", "aac", |
| | | "-shortest", |
| | | configApi.getConfigValueByKey(HEYGEM_FACE2FACE) + "/compositeVideo/" + timestamp + "_final.mp4" |
| | | ); |
| | | |
| | | Process process10 = null; |
| | | builder10.redirectErrorStream(true); |
| | | try { |
| | | process10 = builder10.start(); |
| | | |
| | | // 使用 try-with-resources 确保流关闭 |
| | | try (BufferedReader reader = new BufferedReader(new InputStreamReader(process10.getInputStream()))) { |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | System.out.println(line); |
| | | } |
| | | } |
| | | |
| | | // 等待 FFmpeg 进程完成 |
| | | int exitCode = process10.waitFor(); |
| | | if (exitCode != 0) { |
| | | throw new RuntimeException("FFmpeg 合成视频失败,退出码:" + exitCode); |
| | | } |
| | | |
| | | System.out.println("视频已成功合成"); |
| | | |
| | | } catch (IOException | InterruptedException e) { |
| | | throw new RuntimeException("FFmpeg 合成视频异常", e); |
| | | } |
| | | |
| | | byte[] bytes = FileUtil.readBytes(FileUtil.file(configApi.getConfigValueByKey(HEYGEM_FACE2FACE) + "/compositeVideo/" + timestamp + "_final.mp4")); |
| | | String compositeVideo = fileApi.createFile(bytes); |
| | | |
| | | // 更新数据库记录 |
| | | CourseMediaDO courseMediaDO = new CourseMediaDO(); |
| | | courseMediaDO.setId(courseMediaSubtitlesReqVO.getId()); |
| | | courseMediaDO.setCompositeVideo(compositeVideo); |
| | | int i = courseMediaMapper.updateById(courseMediaDO); |
| | | //删除文件 |
| | | FileUtil.del(configApi.getConfigValueByKey(HEYGEM_FACE2FACE) + "/compositeVideo/" + timestamp + "_intro.mp4"); |
| | | FileUtil.del(configApi.getConfigValueByKey(HEYGEM_FACE2FACE) + "/compositeVideo/" + timestamp + "_outro.mp4"); |
| | | FileUtil.del(configApi.getConfigValueByKey(HEYGEM_FACE2FACE) + "/compositeVideo/" + timestamp + "_final.mp4"); |
| | | FileUtil.del(configApi.getConfigValueByKey(HEYGEM_FACE2FACE) + "/compositeVideo/" + timestamp + ".txt"); |
| | | |
| | | // 删除临时文件 |
| | | FileUtil.del(outputFilePath); |
| | | System.out.println("临时文件已删除"); |
| | | |
| | | if (i > 0) { |
| | | if (i>0){ |
| | | return CommonResult.success("合成成功"); |
| | | } |
| | | return CommonResult.error(BAD_REQUEST.getCode(), "合成失败"); |
| | | return CommonResult.error(BAD_REQUEST.getCode(),"合成失败"); |
| | | } |
| | | |
| | | private static void createVideosFile(String introVideoPath, String mainVideoPath, String outroVideoPath) throws IOException { |
| | | File videosFile = new File("videos.txt"); |
| | | StringBuilder content = new StringBuilder(); |
| | | content.append("file '").append(introVideoPath).append("'\n"); |
| | | content.append("file '").append(mainVideoPath).append("'\n"); |
| | | content.append("file '").append(outroVideoPath).append("'\n"); |
| | | |
| | | java.nio.file.Files.write(videosFile.toPath(), content.toString().getBytes()); |
| | | } |
| | | |
| | | private static List<String> extractMainVideoParams(String mainVideoPath) { |
| | | List<String> params = new ArrayList<>(); |
| | | ProcessBuilder processBuilder = new ProcessBuilder( |
| | | "ffmpeg", |
| | | "-i", mainVideoPath |
| | | public String getVideoResolution(String videoFilePath) { |
| | | ProcessBuilder builder = new ProcessBuilder( |
| | | "ffprobe", |
| | | "-v", "error", // 仅显示错误 |
| | | "-select_streams", "v:0", // 选择视频流 |
| | | "-show_entries", "stream=width,height", // 仅显示宽度和高度 |
| | | "-of", "default=noprint_wrappers=1:nokey=1", // 格式化输出 |
| | | videoFilePath // 视频文件路径 |
| | | ); |
| | | |
| | | Process process = null; |
| | | String resolution = ""; |
| | | try { |
| | | Process process = processBuilder.start(); |
| | | process = builder.start(); |
| | | |
| | | BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream())); |
| | | String line; |
| | | while ((line = errorReader.readLine()) != null) { |
| | | if (line.contains("Video:")) { |
| | | String videoInfo = line.split(":")[1].trim(); |
| | | String codec = videoInfo.split(", ")[0]; |
| | | params.add("-c:v"); |
| | | params.add(codec); |
| | | } else if (line.contains("Audio:")) { |
| | | String audioInfo = line.split(":")[1].trim(); |
| | | String codec = audioInfo.split(", ")[0]; |
| | | String bitrate = audioInfo.split(", ")[1].split("\\s+")[0]; |
| | | params.add("-c:a"); |
| | | params.add(codec); |
| | | params.add("-b:a"); |
| | | params.add(bitrate); |
| | | // 读取 ffprobe 输出流 |
| | | try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | resolution += line + " "; // 拼接宽度和高度 |
| | | } |
| | | } |
| | | |
| | | // 等待 ffprobe 进程完成 |
| | | int exitCode = process.waitFor(); |
| | | if (exitCode == 0) { |
| | | System.out.println("Parameters extracted successfully."); |
| | | } else { |
| | | System.err.println("Parameter extraction failed with exit code: " + exitCode); |
| | | if (exitCode != 0) { |
| | | throw new RuntimeException("ffprobe 执行失败,退出码:" + exitCode); |
| | | } |
| | | |
| | | } catch (IOException | InterruptedException e) { |
| | | System.err.println("Error during parameter extraction: " + e.getMessage()); |
| | | throw new RuntimeException("ffprobe 执行异常", e); |
| | | } finally { |
| | | if (process != null) { |
| | | try { |
| | | process.getInputStream().close(); |
| | | process.getErrorStream().close(); |
| | | process.getOutputStream().close(); |
| | | } catch (IOException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | return params; |
| | | // 返回分辨率,格式如 "1920x1080" |
| | | return resolution.trim().replaceAll("\\s+", "x"); |
| | | } |
| | | |
| | | private static void mergeVideos(String outputPath, List<String> mainVideoParams, String introVideoPath, String mainVideoPath, String outroVideoPath) { |
| | | List<String> command = new ArrayList<>(); |
| | | command.add("ffmpeg"); |
| | | command.add("-f"); |
| | | command.add("concat"); |
| | | command.add("-safe"); |
| | | command.add("0"); |
| | | command.add("-i"); |
| | | command.add("videos.txt"); |
| | | |
| | | // 添加映射选项以确保音频流的一致性 |
| | | command.add("-map"); |
| | | command.add("[v]"); |
| | | command.add("-map"); |
| | | command.add("[a]"); |
| | | |
| | | // 添加过滤器选项以处理无声音情况 |
| | | double introDuration = getDurationInSeconds(introVideoPath); |
| | | double mainDuration = getDurationInSeconds(mainVideoPath); |
| | | double outroDuration = getDurationInSeconds(outroVideoPath); |
| | | |
| | | StringBuilder filterComplex = new StringBuilder(); |
| | | filterComplex.append("[0:v][0:a?]overlay=enable='between(t,0,"); |
| | | filterComplex.append(introDuration); |
| | | filterComplex.append(")'[v0];"); |
| | | filterComplex.append("[1:v][1:a?]overlay=enable='between(t,"); |
| | | filterComplex.append(introDuration); |
| | | filterComplex.append(","); |
| | | filterComplex.append(introDuration + mainDuration); |
| | | filterComplex.append(")'[v1];"); |
| | | filterComplex.append("[2:v][2:a?]overlay=enable='gt(t,"); |
| | | filterComplex.append(introDuration + mainDuration); |
| | | filterComplex.append(")'[v2];"); |
| | | filterComplex.append("[v0][v1][v2]concat=n=3:v=1:a=0[v];"); |
| | | filterComplex.append("[0:a]aresample=async=1:first_pts=0[a0];"); |
| | | filterComplex.append("[1:a]aresample=async=1:first_pts=0[a1];"); |
| | | filterComplex.append("[2:a]aresample=async=1:first_pts=0[a2];"); |
| | | filterComplex.append("[a0][a1][a2]amerge=inputs=3[a]"); |
| | | |
| | | command.add("-filter_complex"); |
| | | command.add(filterComplex.toString()); |
| | | |
| | | // 添加主视频的参数 |
| | | command.addAll(mainVideoParams); |
| | | command.add(outputPath); |
| | | |
| | | ProcessBuilder processBuilder = new ProcessBuilder(command); |
| | | |
| | | private static boolean checkAudio(String inputFile) { |
| | | try { |
| | | Process process = processBuilder.start(); |
| | | |
| | | ProcessBuilder builder = new ProcessBuilder( |
| | | "ffprobe", "-i", inputFile, |
| | | "-show_streams", "-select_streams", "a", |
| | | "-loglevel", "error" |
| | | ); |
| | | Process process = builder.start(); |
| | | BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | System.out.println(line); |
| | | if (line.contains("codec_type=audio")) { |
| | | return true; // 音频流存在 |
| | | } |
| | | } |
| | | |
| | | int exitCode = process.waitFor(); |
| | | if (exitCode == 0) { |
| | | System.out.println("Video merged successfully."); |
| | | } else { |
| | | System.err.println("Video merging failed with exit code: " + exitCode); |
| | | } |
| | | return exitCode == 0; |
| | | } catch (IOException | InterruptedException e) { |
| | | System.err.println("Error during video merging: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | private static double getDurationInSeconds(String videoPath) { |
| | | ProcessBuilder processBuilder = new ProcessBuilder( |
| | | "ffprobe", |
| | | "-v", "error", |
| | | "-show_entries", "format=duration", |
| | | "-of", "default=noprint_wrappers=1:nokey=1", |
| | | videoPath |
| | | ); |
| | | |
| | | try { |
| | | Process process = processBuilder.start(); |
| | | BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); |
| | | String durationStr = reader.readLine(); |
| | | double duration = Double.parseDouble(durationStr); |
| | | process.waitFor(); |
| | | return duration; |
| | | } catch (IOException | InterruptedException | NumberFormatException e) { |
| | | System.err.println("Error getting duration of video: " + e.getMessage()); |
| | | return 0.0; |
| | | e.printStackTrace(); |
| | | return false; |
| | | } |
| | | } |
| | | } |