package cn.iocoder.yudao.module.digitalcourse.service.voices; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.UUID; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import cn.iocoder.yudao.module.digitalcourse.controller.admin.voices.vo.VoicesPageReqVO; import cn.iocoder.yudao.module.digitalcourse.controller.admin.voices.vo.VoicesSaveReqVO; 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.*; import cn.iocoder.yudao.module.digitalcourse.dal.mysql.digitalhumans.DigitalHumansMapper; import cn.iocoder.yudao.module.digitalcourse.dal.mysql.voices.VoicesMapper; import cn.iocoder.yudao.module.infra.api.config.ConfigApi; import cn.iocoder.yudao.module.infra.api.file.FileApi; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSON; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import java.util.HashMap; import java.util.Map; import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.digitalcourse.enums.ErrorCodeConstants.VOICES_NOT_EXISTS; /** * 声音管理 Service 实现类 * * @author 芋道源码 */ @Service @Validated public class VoicesServiceImpl implements VoicesService { private static final String EASEGEN_CORE_URL = "easegen.core.url"; static final String EASEGEN_CORE_KEY = "easegen.core.key"; @Resource private VoicesMapper voicesMapper; @Resource private FileApi fileApi; @Resource private ConfigApi configApi; @Resource private VoicesServiceUtil voicesServiceUtil; @Override public Long createVoices(VoicesSaveReqVO createReqVO) { createReqVO.setCode(UUID.fastUUID().toString()); // 插入 VoicesDO voices = BeanUtils.toBean(createReqVO, VoicesDO.class); voices.setStatus(3); voices.setFixAuditionUrl(voices.getAuditionUrl()); voicesMapper.insert(voices); // 判断如果是极速模式,自动开始训练 voicesServiceUtil.remoteTrain(transferVO(voices.getId())); // 返回 return voices.getId(); } @Override public void updateVoices(VoicesSaveReqVO updateReqVO) { // 校验存在 validateVoicesExists(updateReqVO.getId()); // 更新 VoicesDO updateObj = BeanUtils.toBean(updateReqVO, VoicesDO.class); voicesMapper.updateById(updateObj); if (updateReqVO.getStatus() == 3){ voicesServiceUtil.remoteTrain(transferVO(updateReqVO.getId())); } } private VoicesTrailVO transferVO(Long id) { VoicesDO voices = this.getVoices(id); VoicesTrailVO build = VoicesTrailVO.builder().build(); BeanUtils.copyProperties(voices, build); if (StrUtil.isBlank(voices.getFixAuditionUrl())) build.setFixAuditionUrl(voices.getAuditionUrl()); build.setAccountId(voices.getCreator()); return build; } @Override public void deleteVoices(Long id) { // 校验存在 validateVoicesExists(id); // 删除 voicesMapper.deleteById(id); } private void validateVoicesExists(Long id) { if (voicesMapper.selectById(id) == null) { throw exception(VOICES_NOT_EXISTS); } } @Override public VoicesDO getVoices(Long id) { return voicesMapper.selectById(id); } @Override public PageResult getVoicesPage(VoicesPageReqVO pageReqVO) { if(pageReqVO.getVoiceType()==1){ //查询非公共声音,只能查询自己的,公共声音,可以查询所有的 if (WebFrameworkUtils.getLoginUserId() != 1) { pageReqVO.setCreator(String.valueOf(WebFrameworkUtils.getLoginUserId())); pageReqVO.setVoiceType(1); } }else { pageReqVO.setVoiceType(0); } return voicesMapper.selectPage(pageReqVO); } @Override public PageResult getVoicesCommonPage(VoicesPageReqVO pageReqVO) { return voicesMapper.selectPage(pageReqVO); } @Override public Boolean auditing() { // 管理员直接返回true if (WebFrameworkUtils.getLoginUserId() == 1) return true; Integer auditing = voicesMapper.auditing(WebFrameworkUtils.getLoginUserId()); return (auditing == null || auditing == 0); } @Resource private DigitalHumansMapper digitalHumansMapper; private static final String EASEGEN_URL = "easegen.url"; private static final String HEYGEM_CORE_URL = "heygem.core.url"; private static final String HEYGEM_VOICE_DATA = "heygem.voice.data"; public static final Set SUPPORTED_LANGUAGES = Set.of( "en", "es", "fr", "de", "it", "pt", "pl", "tr", "ru", "nl", "cs", "ar", "zh-cn", "hu", "ko", "ja", "hi" ); // 中英文专用模型支持的语言 private static final Set CN_EN_LANGUAGES = Set.of("zh-cn", "en"); @Override public String audition(AuditionVO auditionVO) { String language = auditionVO.getLanguage().toLowerCase(); // 判断是否是支持的语言 if (!SUPPORTED_LANGUAGES.contains(language)) { throw new IllegalArgumentException("不支持的语言类型: " + language); } // 构建参数 InvokeVO invokeVO = new InvokeVO(); invokeVO.setSpeaker(InvokeVO.generateUUID()); invokeVO.setText(auditionVO.getText()); if (auditionVO.getVoiceId() == null) { DigitalHumansDO digitalHumansDO = digitalHumansMapper.selectById(auditionVO.getHumanId()); invokeVO.setReferenceText(digitalHumansDO.getReferenceAudioText()); invokeVO.setReferenceAudio(digitalHumansDO.getAsrFormatAudioUrl()); } else if (auditionVO.getHumanId() == null) { VoicesDO voicesDO = voicesMapper.selectById(auditionVO.getVoiceId()); invokeVO.setReferenceText(voicesDO.getReferenceAudioText()); invokeVO.setReferenceAudio(voicesDO.getAsrFormatAudioUrl()); } String fileName = UUID.randomUUID().toString() + ".wav"; byte[] content; try { if (CN_EN_LANGUAGES.contains(language)) { // 使用中英文模型 String jsonString = new ObjectMapper().writeValueAsString(invokeVO); String coreUrl = configApi.getConfigValueByKey(HEYGEM_CORE_URL) + "/v1/invoke"; HttpResponse response = HttpRequest.post(coreUrl) .body(jsonString) .execute(); if (response.getStatus() != 200) { return null; } content = response.bodyBytes(); } else { // 使用其他语言模型,如 http://127.0.0.1:5002/synthesize String referenceAudio = invokeVO.getReferenceAudio(); String resultName = ""; if (referenceAudio != null) { if (referenceAudio.startsWith("/code/sessions/") || referenceAudio.startsWith("/code/data/")) { System.out.println("路径属于 /code/sessions/ 或 /code/data/"); // 只取第一个路径(以|||分割) String firstPath = referenceAudio.split("\\|\\|\\|")[0]; // 取最后一级文件名 String fileName1 = firstPath.substring(firstPath.lastIndexOf('/') + 1); String coreName; if (referenceAudio.startsWith("/code/sessions/")) { // sessions路径可能有 _partN,去除 _partN 及后面部分 int partIndex = fileName1.indexOf("_part"); if (partIndex != -1) { coreName = fileName1.substring(0, partIndex); } else { // 没有_part,去掉扩展名 int dotIndex = fileName1.lastIndexOf('.'); coreName = (dotIndex != -1) ? fileName1.substring(0, dotIndex) : fileName1; } } else { // data路径直接取完整文件名(即格式名+后缀) coreName = fileName1.substring(0, fileName1.lastIndexOf('.')); } // 获取后缀 int dotIndex = fileName1.lastIndexOf('.'); String suffix = (dotIndex != -1) ? fileName1.substring(dotIndex) : ""; // 最终结果 resultName = coreName + suffix; System.out.println("提取的格式名:" + resultName); } else { // 其他路径 System.out.println("未知路径类型"); throw new IllegalArgumentException("声音模型异常,请联系管理员"); } } //resultName String resultVoiceUrl = configApi.getConfigValueByKey(HEYGEM_VOICE_DATA)+"/origin_audio/" + resultName; Map params = new HashMap<>(); params.put("text", auditionVO.getText()); params.put("speaker_wav", resultVoiceUrl); params.put("language", language); HttpResponse response = HttpRequest.post("http://127.0.0.1:5002/synthesize") .contentType("application/json") .body(new ObjectMapper().writeValueAsString(params)) .execute(); if (response.getStatus() != 200) { return null; } String body = response.body(); JSONObject json = JSON.parseObject(body); Integer code = json.getInteger("code"); String message = json.getString("message"); if (code == null || code != 200) { throw new RuntimeException("语音合成失败:" + message); } JSONObject outputPath = json.getJSONObject("output_path"); String diskPath = outputPath.getString("disk_path"); String url = outputPath.getString("url"); // 使用 diskPath 和 url content = FileUtil.readBytes(diskPath); } // 保存音频文件并返回地址 return fileApi.createFile(fileName, null, content); } catch (Exception e) { throw new RuntimeException("语音合成失败", e); } } }