shenrongliang
2025-04-16 0afb9937d0d95f6976ac06ff8a6595a5b9cbd26c
easegen-front/vite.config.ts
@@ -36,7 +36,7 @@
            open: env.VITE_OPEN === 'true',
            proxy: {
              '/admin-api': {
                target: 'http://192.168.3.159:48080',
                target: 'http://192.168.3.190:48080',
                changeOrigin: true,
                // 不要 rewrite,因为要保留完整路径
              }
yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/controller/admin/coursemedia/CourseMediaController.java
@@ -126,9 +126,31 @@
        return courseMediaService.createSubtitlesVideo(courseMediaSubtitlesReqVO);
    }
    /**
     * 更新字幕文件
     * @param updateReqVO
     * @return
     */
    @PutMapping("/updateSubtitles")
    public CommonResult<Boolean> updateSubtitles(@Valid @RequestBody CourseMediaEditSReqVO updateReqVO) {
        courseMediaService.updateSubtitles(updateReqVO);
        return success(true);
    }
    /**
     * 上传片头片尾
     */
    @PostMapping("/createTrailer")
    public CommonResult createTrailer(@RequestBody CourseMediaSubtitlesReqVO courseMediaSubtitlesReqVO){
        return courseMediaService.createTrailer(courseMediaSubtitlesReqVO);
    }
    /**
     * 合成片头片尾
     */
    @PostMapping("/createCompositeVideo")
    public CommonResult createCompositeVideo(@RequestBody CourseMediaSubtitlesReqVO courseMediaSubtitlesReqVO){
        return courseMediaService.createCompositeVideo(courseMediaSubtitlesReqVO);
    }
}
yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/controller/admin/coursemedia/vo/CourseMediaRespVO.java
@@ -86,4 +86,7 @@
    private String lang;
    private Integer pos;
    private String progressVideo;
    private String titles;
    private String trailer;
    private String compositeVideo;
}
yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/controller/admin/coursemedia/vo/CourseMediaSubtitlesReqVO.java
@@ -10,7 +10,7 @@
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12757")
    private Long id;
    private String courseName;
    @Schema(description = "字幕文件地址")
    private String subtitlesUrl;
    @Schema(description = "字幕文件生成状态1合成中,2成功,3失败")
@@ -21,4 +21,12 @@
    private String videoUrl;
    private String lang;
    private String sentenceGap;
    //媒体链接
    private String previewUrl;
    // 片头地址
    private String titles;
    //片尾地址
    private String trailer;
    //合成视频地址
    private String compositeVideo;
}
yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/dal/dataobject/coursemedia/CourseMediaDO.java
@@ -99,4 +99,12 @@
    private Integer pos;
    @TableField(exist = false)
    private String progressVideo;
    //片头地址
    private String titles;
    //片尾地址
    private String trailer;
    //合成视频地址
    private String compositeVideo;
}
yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/service/coursemedia/CourseMediaService.java
@@ -63,4 +63,8 @@
    CommonResult createSubtitlesVideo(CourseMediaSubtitlesReqVO courseMediaSubtitlesReqVO);
    void updateSubtitles(CourseMediaEditSReqVO updateReqVO);
    CommonResult createTrailer(CourseMediaSubtitlesReqVO courseMediaSubtitlesReqVO);
    CommonResult createCompositeVideo(CourseMediaSubtitlesReqVO courseMediaSubtitlesReqVO);
}
yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/service/coursemedia/CourseMediaServiceImpl.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.digitalcourse.service.coursemedia;
import cn.hutool.core.io.FileUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@@ -10,6 +11,7 @@
import cn.iocoder.yudao.module.digitalcourse.manager.MediaTaskManager;
import cn.iocoder.yudao.module.digitalcourse.model.MediaTask;
import cn.iocoder.yudao.module.infra.api.config.ConfigApi;
import cn.iocoder.yudao.module.infra.api.file.FileApi;
import com.alibaba.fastjson.JSON;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
@@ -20,9 +22,10 @@
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import java.io.File;
import java.io.IOException;
import java.io.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import static cn.iocoder.yudao.module.digitalcourse.enums.ErrorCodeConstants.COURSE_MEDIA_NOT_EXISTS;
@@ -42,6 +45,9 @@
    @Resource
    private CourseMediaServiceUtil courseMediaServiceUtil;
    @Resource
    private FileApi fileApi;
    @Override
    public Long createCourseMedia(CourseMediaSaveReqVO createReqVO) {
@@ -222,4 +228,97 @@
        updateObj.setSubtitlesStatus(2);
        courseMediaMapper.updateById(updateObj);
    }
    /**
     * 上传片头片尾
     * @param courseMediaSubtitlesReqVO
     * @return
     */
    @Override
    public CommonResult createTrailer(CourseMediaSubtitlesReqVO courseMediaSubtitlesReqVO) {
        CourseMediaDO courseMediaDO = new CourseMediaDO();
        courseMediaDO.setId(courseMediaSubtitlesReqVO.getId());
        courseMediaDO.setTrailer(courseMediaSubtitlesReqVO.getTrailer());
        courseMediaDO.setTitles(courseMediaSubtitlesReqVO.getTitles());
        int i = courseMediaMapper.updateById(courseMediaDO);
        if (i>0){
            return CommonResult.success("片头片尾上传成功");
        }
        return CommonResult.error(BAD_REQUEST.getCode(),"片头片尾上传失败");
    }
    /**
     * 合成片头片尾视频
     * @param courseMediaSubtitlesReqVO
     * @return
     */
    @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(),"视频合成失败");
    }
}
yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/service/coursemedia/CourseMediaServiceUtil.java
@@ -304,7 +304,7 @@
        //去掉updateReqVO.getName()中的空格和特殊字符
        String newFileName = updateReqVO.getName().replaceAll("[\\s\\p{Punct}]", "");
        ProcessBuilder builder = new ProcessBuilder(
                "ffmpeg", "-f", "concat", "-safe","0", "-i",fileListPath , "-c", "copy", "D:/heygem_data/face2face/temp/"+"111111.mp4"
                "ffmpeg", "-f", "concat", "-safe","0", "-i",fileListPath , "-c", "copy", configApi.getConfigValueByKey(HEYGEM_FACE2FACE)+"/temp/"+"111111.mp4"
//                q
        );
        builder.redirectErrorStream(true);