From 0aa78d876fe4819fb9b7ffc24b9a01e2c9869414 Mon Sep 17 00:00:00 2001 From: shenrongliang <1328040932@qq.com> Date: 星期一, 21 四月 2025 14:42:03 +0800 Subject: [PATCH] 处理合成片头片尾 --- yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/service/coursemedia/CourseMediaServiceImpl.java | 251 +++++++++++++++++++++++++++++++++++++------------- yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/service/coursemedia/CourseMediaServiceUtil.java | 12 +- 2 files changed, 192 insertions(+), 71 deletions(-) diff --git a/yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/service/coursemedia/CourseMediaServiceImpl.java b/yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/service/coursemedia/CourseMediaServiceImpl.java index c52a960..8935f32 100644 --- a/yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/service/coursemedia/CourseMediaServiceImpl.java +++ b/yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/service/coursemedia/CourseMediaServiceImpl.java @@ -256,94 +256,215 @@ */ @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); - if (videoUrl != null){ - videoUrl = configApi.getConfigValueByKey("easegen.url") + videoUrl.substring(videoUrl.lastIndexOf("/")); - videoUrls.add(videoUrl); - videoUrls.add(trailer); + String mainVideoPath = ""; + if (videoUrl != null) { + mainVideoPath = configApi.getConfigValueByKey("easegen.url") + videoUrl.substring(videoUrl.lastIndexOf("/")); + videoUrls.add(mainVideoPath); + videoUrls.add(mainVideoPath); } else if (previewUrl != null) { - previewUrl = configApi.getConfigValueByKey("easegen.url") + previewUrl.substring(previewUrl.lastIndexOf("/")); - videoUrls.add(previewUrl); - videoUrls.add(trailer); + mainVideoPath = configApi.getConfigValueByKey("easegen.url") + previewUrl.substring(previewUrl.lastIndexOf("/")); + videoUrls.add(mainVideoPath); + videoUrls.add(mainVideoPath); } - //鍒ゆ柇鏂囦欢澶规槸鍚﹀瓨鍦紝濡傛灉涓嶅瓨鍦ㄥ氨鍒涘缓 - String filePath = configApi.getConfigValueByKey(HEYGEM_FACE2FACE) +"/compositeVideo/"; + // 鎻愬彇涓昏棰戠殑鍙傛暟 + 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(); } - 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) { - e.printStackTrace(); - } - //鍘绘帀updateReqVO.getName()涓殑绌烘牸鍜岀壒娈婂瓧绗� - String newFileName = courseMediaSubtitlesReqVO.getCourseName().replaceAll("[\\s\\p{Punct}]", ""); - ProcessBuilder builder = new ProcessBuilder( - "ffmpeg", "-f", "concat", "-safe", "0", "-i", fileListPath, "-c", "copy", - configApi.getConfigValueByKey(HEYGEM_FACE2FACE) + "/compositeVideo/" + timestamp + ".mp4" - ); - builder.redirectErrorStream(true); - Process process = null; + // 鍒涘缓txt 鏂囦欢 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(); - } - } + createVideosFile(titles, mainVideoPath, trailer); + } catch (IOException e) { + System.err.println("Error creating videos.txt file: " + e.getMessage()); + return CommonResult.error(BAD_REQUEST.getCode(), "鍚堟垚澶辫触"); } - byte[] bytes = FileUtil.readBytes(FileUtil.file(configApi.getConfigValueByKey(HEYGEM_FACE2FACE) +"/compositeVideo/"+timestamp+".mp4")); + // 杈撳嚭鏂囦欢璺緞 + String outputFilePath = configApi.getConfigValueByKey(HEYGEM_FACE2FACE) + "/compositeVideo/" + timestamp + ".mp4"; + // 浣跨敤 FFmpeg 鍚堝苟瑙嗛骞跺簲鐢ㄤ富瑙嗛鐨勫弬鏁� + mergeVideos(outputFilePath, mainVideoParams,titles,mainVideoPath,trailer); + + System.out.println("Video merging completed."); + + byte[] bytes = FileUtil.readBytes(FileUtil.file(outputFilePath)); 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+".mp4"); - FileUtil.del(configApi.getConfigValueByKey(HEYGEM_FACE2FACE) +"/compositeVideo/"+timestamp+".txt"); - System.out.println(); - if (i>0){ + + // 鍒犻櫎涓存椂鏂囦欢 + FileUtil.del(outputFilePath); + System.out.println("涓存椂鏂囦欢宸插垹闄�"); + + 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 + ); + + try { + Process process = processBuilder.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); + } + } + + int exitCode = process.waitFor(); + if (exitCode == 0) { + System.out.println("Parameters extracted successfully."); + } else { + System.err.println("Parameter extraction failed with exit code: " + exitCode); + } + } catch (IOException | InterruptedException e) { + System.err.println("Error during parameter extraction: " + e.getMessage()); + } + + return params; + } + + 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); + + try { + Process process = processBuilder.start(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while ((line = reader.readLine()) != null) { + System.out.println(line); + } + + int exitCode = process.waitFor(); + if (exitCode == 0) { + System.out.println("Video merged successfully."); + } else { + System.err.println("Video merging failed with exit code: " + exitCode); + } + } 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; + } } } diff --git a/yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/service/coursemedia/CourseMediaServiceUtil.java b/yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/service/coursemedia/CourseMediaServiceUtil.java index 71be55f..9721587 100644 --- a/yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/service/coursemedia/CourseMediaServiceUtil.java +++ b/yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/service/coursemedia/CourseMediaServiceUtil.java @@ -167,7 +167,7 @@ newFileName2 // 杈撳嚭鏂囦欢鍚� ); } else if ("2".equals(scene.getHasPerson())) { - // 褰撴病鏈変汉鍍忔椂锛宻ubstring1鏀惧湪 cover1 鐨勪笅灞� + //cover1鏀惧湪substring1鐨勶紝璁ヽover1鎸′綇substring1,鑳屾櫙鍥緋pt鍐呭鍜屼汉鍍忚棰戜笉鍙橈紝鍙槸灞傜骇鍏崇郴淇敼涓�涓� builder = new ProcessBuilder( "ffmpeg", "-i", cover1, // 鑳屾櫙鍥� @@ -175,11 +175,11 @@ "-i", substring1, // 浜哄儚瑙嗛 "-filter_complex", "[0:v]scale=" + Math.round(scene.getBackground().getWidth()) + ":" + Math.round(scene.getBackground().getHeight()) + "[bg];" + - "[1:v]scale=" + Math.round(scene.getComponents().get(1).getWidth()) + ":" + Math.round(scene.getComponents().get(1).getHeight()) + "[v1];" + - "[bg][v1]overlay=x=" + Math.round(scene.getComponents().get(1).getMarginLeft()) + ":y=" + Math.round(scene.getComponents().get(1).getTop()) + "[img];" + - "[2:v]scale=" + Math.round(scene.getComponents().get(0).getWidth()) + ":" + Math.round(scene.getComponents().get(0).getHeight()) + "[v2];" + - "[img][v2]overlay=x=" + Math.round(scene.getComponents().get(0).getMarginLeft()) + ":y=" + Math.round(scene.getComponents().get(0).getTop()), - newFileName2 // 杈撳嚭鏂囦欢鍚� + "[1:v]scale=" + Math.round(scene.getComponents().get(1).getWidth()) + ":" + Math.round(scene.getComponents().get(1).getHeight()) + "[v1];" + + "[bg][v1]overlay=x=" + Math.round(scene.getComponents().get(1).getMarginLeft()) + ":y=" + Math.round(scene.getComponents().get(1).getTop()) + "[img];" + + "[2:v]scale=" + Math.round(scene.getComponents().get(0).getWidth()) + ":" + Math.round(scene.getComponents().get(0).getHeight()) + "[v2];" + + "[v2][img]overlay=x=" + Math.round(scene.getComponents().get(0).getMarginLeft()) + ":y=" + Math.round(scene.getComponents().get(0).getTop()), + newFileName2 ); } System.out.println(newFileName2); -- Gitblit v1.9.3