康鲁杰
2025-04-14 d419a2abed1cd7c8c8cc34db0d7ff4cc894eb5d5
yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/dal/dataobject/voices/VoicesDO.java
@@ -64,7 +64,7 @@
     */
    private Integer quality;
    /**
     * 声音类型
     * 声音类型
     *
     * 枚举 {@link TODO digitalcourse_voices_type 对应的类}
     */
@@ -84,5 +84,7 @@
     * 克隆类型(1:普通,2:高级)
     */
    private Integer type;
    private String referenceAudioText;
    private String asrFormatAudioUrl;
}
}
yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/service/voices/VoicesServiceImpl.java
@@ -55,14 +55,11 @@
        createReqVO.setCode(UUID.fastUUID().toString());
        // 插入
        VoicesDO voices = BeanUtils.toBean(createReqVO, VoicesDO.class);
        voices.setStatus(3);
        voices.setFixAuditionUrl(voices.getAuditionUrl());
        voicesMapper.insert(voices);
        // 判断如果是极速模式,自动开始训练
        if(voices.getType()==1) {
            VoicesSaveReqVO reqVO = BeanUtils.toBean(voices, VoicesSaveReqVO.class);
            reqVO.setStatus(3);
            reqVO.setFixAuditionUrl(voices.getAuditionUrl());
            updateVoices(reqVO);
        }
        voicesServiceUtil.remoteTrain(transferVO(voices.getId()));
        // 返回
        return voices.getId();
    }
yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/service/voices/VoicesServiceUtil.java
@@ -3,6 +3,7 @@
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.iocoder.yudao.module.digitalcourse.controller.admin.voices.vo.VoicesTrailVO;
import cn.iocoder.yudao.module.digitalcourse.dal.dataobject.digitalhumans.DigitalHumansDO;
import cn.iocoder.yudao.module.digitalcourse.dal.dataobject.voices.VoicesDO;
import cn.iocoder.yudao.module.digitalcourse.dal.mysql.voices.VoicesMapper;
import cn.iocoder.yudao.module.infra.api.config.ConfigApi;
@@ -19,6 +20,12 @@
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
@@ -44,20 +51,53 @@
    private ConfigApi configApi;
    @Resource
    private VoicesMapper voicesMapper;
    private static final String HEYGEM_VOICE_DATA = "heygem.voice.data";
    private static final String HEYGEM_FACE2FACE = "heygem.face2face";
    private static final String EASEGEN_URL = "easegen.url";
    private static final String HEYGEM_CORE_URL = "heygem.core.url";
    @Async
    public void remoteTrain(VoicesTrailVO trailVO){
        String origin_audio = configApi.getConfigValueByKey(HEYGEM_VOICE_DATA) + "/origin_audio";
        //训练前校验
        try {
            Files.createDirectories(Path.of(origin_audio));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        String extname = trailVO.getFixAuditionUrl().substring(trailVO.getFixAuditionUrl().lastIndexOf("."));
        String modelFileName = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()) + extname;
        String modelFilePath = Paths.get(origin_audio, modelFileName).toString();
        String substring = configApi.getConfigValueByKey(EASEGEN_URL)+trailVO.getFixAuditionUrl().substring(trailVO.getFixAuditionUrl().lastIndexOf("/"));
        try {
            Files.copy(Path.of(substring), Path.of(modelFilePath), StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        String configValueByKey = configApi.getConfigValueByKey(HEYGEM_VOICE_DATA);
        // 计算相对路径
        Path relativeAudioPath = Path.of(configValueByKey).relativize(Path.of(modelFilePath));
        Map<String, Object> map = Map.of(
                "format", "wav",
                "reference_audio", relativeAudioPath.toString().replace("\\", "/"),
                "lang", "zh"
        );
        // 将路径中的\替换为/  relativeAudioPath.toString()
        int maxRetries = 3; // 最大重试次数
        int retryCount = 0;  // 当前重试次数
        boolean success = false;
        ObjectMapper mapper = new ObjectMapper();
        mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
        while (retryCount < maxRetries && !success) {
            try {
                // 发送POST请求
                HttpResponse execute = HttpRequest.post(configApi.getConfigValueByKey(EASEGEN_CORE_URL) + "/api/clone_voice")
                        .header("X-API-Key", configApi.getConfigValueByKey(EASEGEN_CORE_KEY))
                        .body(mapper.writeValueAsString(trailVO))
                HttpResponse execute = HttpRequest.post(configApi.getConfigValueByKey(HEYGEM_CORE_URL) + "/v1/preprocess_and_tran")
                        .body(JSON.toJSONString(map))
                        .execute();
                String body = execute.body();
@@ -72,20 +112,27 @@
                    }
                    continue; // 重新尝试
                }
                retryCount++;
                if (retryCount >= maxRetries) {
                    voicesMapper.update(new UpdateWrapper<VoicesDO>().lambda().eq(VoicesDO::getCode, trailVO.getCode()).set(VoicesDO::getStatus, ERROR_STATUS));
                    log.error("训练失败:->>>>>>>>>");
                    return;
                }
                // 解析响应,检查是否有错误信息
                JSONObject responseJson = JSON.parseObject(body);
                if (!responseJson.getBoolean("success")) {
                    // 处理业务逻辑错误,更新状态和错误信息
                    String errorDetail = responseJson.getString("detail");
                    retryCount++;
                    if (retryCount >= maxRetries) {
                        voicesMapper.update(new UpdateWrapper<VoicesDO>().lambda().eq(VoicesDO::getCode, trailVO.getCode()).set(VoicesDO::getStatus, ERROR_STATUS));
                        log.error("训练失败:->>>>>>>>>", errorDetail);
                        return;
                    }
                    continue; // 重新尝试
                }
                // 处理业务逻辑错误,更新状态和错误信息
                String referenceAudioText = responseJson.getString("reference_audio_text");
                String asrFormatAudioUrl = responseJson.getString("asr_format_audio_url");
                voicesMapper.update(
                        new UpdateWrapper<VoicesDO>()
                                .lambda()
                                .eq(VoicesDO::getCode, trailVO.getCode())  // 条件:code 等于传入的值
                                .set(VoicesDO::getStatus, 0)  // 更新字段 status 为 0
                                .set(VoicesDO::getAsrFormatAudioUrl,asrFormatAudioUrl)
                                .set(VoicesDO::getReferenceAudioText,referenceAudioText)
                );
                success = true;
            }catch (Exception e){
                retryCount++;
yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/util/PPTUtil.java
@@ -115,7 +115,7 @@
        try {
            // 更新进度为开始
            redisCache.opsForValue().set(ANALYSIS_PPT_KEY + pptId, "0", 1, TimeUnit.DAYS);
            // 下载文件
            log.info("[analysisPptLocal][开始下载] pptId:{}, url:{}", pptId, fileUrl);
            downloadedFile = downloadFile(fileUrl);
@@ -168,6 +168,7 @@
    }
    private File downloadFile(String fileUrl) throws IOException {
        fileUrl = "http://127.0.0.1:48080" + fileUrl;
        log.info("[downloadFile][开始] url:{}", fileUrl);
        URL url = new URL(fileUrl);
        String tempFileName = UUID.randomUUID().toString();