| | |
| | | builder = new ProcessBuilder( |
| | | "ffmpeg", |
| | | "-i", cover1, // 背景图 |
| | | "-i", substring1, // 视频 |
| | | "-i", cover, // PPT内容 |
| | | "-i", cover, // PPT内容 |
| | | "-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(0).getWidth()) + ":" + Math.round(scene.getComponents().get(0).getHeight()) + "[v1];" + |
| | | "[bg][v1]overlay=x=" + Math.round(scene.getComponents().get(0).getMarginLeft()) + ":y=" + Math.round(scene.getComponents().get(0).getTop()) + "[img];" + |
| | | "[2:v]scale=" + Math.round(scene.getComponents().get(1).getWidth()) + ":" + Math.round(scene.getComponents().get(1).getHeight()) + "[v2];" + |
| | | "[img][v2]overlay=x=" + Math.round(scene.getComponents().get(1).getMarginLeft()) + ":y=" + Math.round(scene.getComponents().get(1).getTop()), |
| | | "[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 // 输出文件名 |
| | | ); |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | @Async |
| | | public void 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); |
| | | } else if (previewUrl != null) { |
| | | previewUrl = configApi.getConfigValueByKey("easegen.url") + previewUrl.substring(previewUrl.lastIndexOf("/")); |
| | | videoUrls.add(previewUrl); |
| | | videoUrls.add(trailer); |
| | | } |
| | | //判断文件夹是否存在,如果不存在就创建 |
| | | 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; |
| | | 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(); |
| | | } |
| | | } |
| | | } |
| | | byte[] bytes = FileUtil.readBytes(FileUtil.file(configApi.getConfigValueByKey(HEYGEM_FACE2FACE) +"/compositeVideo/"+timestamp+".mp4")); |
| | | String compositeVideo = fileApi.createFile(bytes); |
| | | CourseMediaDO courseMediaDO = new CourseMediaDO(); |
| | | courseMediaDO.setId(courseMediaSubtitlesReqVO.getId()); |
| | | courseMediaDO.setCompositeVideo(compositeVideo); |
| | | courseMediaMapper.updateById(courseMediaDO); |
| | | FileUtil.del(configApi.getConfigValueByKey(HEYGEM_FACE2FACE) +"/compositeVideo/"+timestamp+".mp4"); |
| | | FileUtil.del(configApi.getConfigValueByKey(HEYGEM_FACE2FACE) +"/compositeVideo/"+timestamp+".txt"); |
| | | System.out.println(); |
| | | } |
| | | } |