du
2025-04-17 ff3f194a07aa86d0e8e1fd1c42dd061fb37db2a6
Merge remote-tracking branch 'origin/master'
已修改2个文件
194 ■■■■ 文件已修改
yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/service/coursemedia/CourseMediaServiceImpl.java 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/service/coursemedia/CourseMediaServiceUtil.java 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/service/coursemedia/CourseMediaServiceImpl.java
@@ -23,8 +23,10 @@
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import java.io.*;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import static cn.iocoder.yudao.module.digitalcourse.enums.ErrorCodeConstants.COURSE_MEDIA_NOT_EXISTS;
@@ -254,8 +256,94 @@
     */
    @Override
    public CommonResult createCompositeVideo(CourseMediaSubtitlesReqVO courseMediaSubtitlesReqVO) {
        //异步合成
        courseMediaServiceUtil.createCompositeVideo(courseMediaSubtitlesReqVO);
        return CommonResult.success("视频合成中,请稍后查看");
        // 生成时间戳
        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);
        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){
            return CommonResult.success("合成成功");
        }
        return CommonResult.error(BAD_REQUEST.getCode(),"合成失败");
    }
}
yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/service/coursemedia/CourseMediaServiceUtil.java
@@ -171,14 +171,14 @@
                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 // 输出文件名
                );
            }
@@ -696,92 +696,4 @@
        }
    }
    @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();
    }
}