du
2025-04-17 48aa89ef8c7ceedb51e320ab34638a09a33da22a
Merge remote-tracking branch 'origin/master'
已修改3个文件
194 ■■■■■ 文件已修改
yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/service/coursemedia/CourseMediaServiceImpl.java 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/service/coursemedia/CourseMediaServiceUtil.java 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/utils/FileTypeUtils.java 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/service/coursemedia/CourseMediaServiceImpl.java
@@ -212,7 +212,7 @@
            return CommonResult.error(BAD_REQUEST.getCode(), "字幕文件不存在或未生成");
        }
        CourseMediaDO courseMediaDO = new CourseMediaDO();
        courseMediaDO.setCourseId(courseMediaSubtitlesReqVO.getId());
        courseMediaDO.setId(courseMediaSubtitlesReqVO.getId());
        courseMediaDO.setSubtitlesAddStatus(1);
        courseMediaMapper.updateById(courseMediaDO);
        courseMediaServiceUtil.createSubtitlesVideo(courseMediaDO1);
@@ -254,71 +254,8 @@
     */
    @Override
    public CommonResult createCompositeVideo(CourseMediaSubtitlesReqVO courseMediaSubtitlesReqVO) {
        //片头地址
        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/filelist.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/"+newFileName+".mp4"
//                q
        );
        builder.redirectErrorStream(true);
        Process process = null;
        try {
            process = builder.start();
            // 读取 FFmpeg 输出(可选)
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            System.out.println(builder.command());
            System.out.println("最终视频已生成");
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        byte[] bytes = FileUtil.readBytes(FileUtil.file(configApi.getConfigValueByKey(HEYGEM_FACE2FACE) +"/compositeVideo/"+newFileName+".mp4"));
        String compositeVideo = fileApi.createFile(bytes);
        CourseMediaDO courseMediaDO = new CourseMediaDO();
        courseMediaDO.setId(courseMediaSubtitlesReqVO.getId());
        courseMediaDO.setCompositeVideo(compositeVideo);
        int i = courseMediaMapper.updateById(courseMediaDO);
        if (i>0){
            return CommonResult.success("视频合成成功");
        }
        return CommonResult.error(BAD_REQUEST.getCode(),"视频合成失败");
        //异步合成
        courseMediaServiceUtil.createCompositeVideo(courseMediaSubtitlesReqVO);
        return CommonResult.success("视频合成中,请稍后查看");
    }
}
yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/service/coursemedia/CourseMediaServiceUtil.java
@@ -6,6 +6,7 @@
import cn.hutool.http.HttpException;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
import cn.iocoder.yudao.module.digitalcourse.controller.admin.coursemedia.vo.CourseMediaMegerVO;
import cn.iocoder.yudao.module.digitalcourse.controller.admin.coursemedia.vo.CourseMediaSubtitlesReqVO;
@@ -42,8 +43,11 @@
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
@Service
@Validated
@@ -163,7 +167,7 @@
                        newFileName2 // 输出文件名
                );
            } else if ("2".equals(scene.getHasPerson())) {
                // 当没有人像时,视频放在 cover 的下层
                // 当没有人像时,substring1放在 cover1 的下层
                builder = new ProcessBuilder(
                        "ffmpeg",
                        "-i", cover1, // 背景图
@@ -691,4 +695,93 @@
            throw new RuntimeException(e);
        }
    }
    @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();
    }
}
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/utils/FileTypeUtils.java
@@ -8,6 +8,7 @@
import org.apache.tika.Tika;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
/**
@@ -65,12 +66,33 @@
        response.setContentType(contentType);
        // 针对 video 的特殊处理,解决视频地址在移动端播放的兼容性问题
        if (StrUtil.containsIgnoreCase(contentType, "video")) {
            response.setHeader("Content-Length", String.valueOf(content.length - 1));
            response.setHeader("Content-Range", String.valueOf(content.length - 1));
            long contentLength = content.length;
            response.setHeader("Content-Length", String.valueOf(contentLength));
            response.setHeader("Content-Range", "bytes 0-" + (contentLength - 1) + "/" + contentLength);
            response.setHeader("Accept-Ranges", "bytes");
        }
        // 输出附件
        IoUtil.write(response.getOutputStream(), false, content);
        OutputStream outputStream = null;
        try {
            outputStream = response.getOutputStream();
            IoUtil.write(outputStream, false, content);
        } catch (IOException e) {
            if ("Connection reset by peer".equals(e.getMessage())) {
                System.out.println("客户端中断连接: {}");
                System.out.println(e.getMessage());
            } else {
                throw e;
            }
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    System.out.println("关闭输出流失败: {}");
                    System.out.println(e.getMessage());
                }
            }
        }
    }
}