From bec32bd085212f945df3984cfb7c7b2ab2c1905a Mon Sep 17 00:00:00 2001 From: hrz <1710360675@qq.com> Date: Mon, 13 Oct 2025 15:26:26 +0800 Subject: [PATCH] =?UTF-8?q?update:=E6=8A=8A=E4=BC=A0=E9=80=92name=E6=94=B9?= =?UTF-8?q?=E6=88=90=E4=BC=A0=E9=80=92speaker=5Fid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/VoiceCloneController.java | 4 +- .../voiceclone/service/VoiceCloneService.java | 12 +- .../service/impl/VoiceCloneServiceImpl.java | 132 ++++++++++-------- 3 files changed, 84 insertions(+), 64 deletions(-) diff --git a/main/manager-api/src/main/java/xiaozhi/modules/voiceclone/controller/VoiceCloneController.java b/main/manager-api/src/main/java/xiaozhi/modules/voiceclone/controller/VoiceCloneController.java index 21c6eda3..90435e11 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/voiceclone/controller/VoiceCloneController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/voiceclone/controller/VoiceCloneController.java @@ -176,8 +176,8 @@ public class VoiceCloneController { String cloneId = params.get("cloneId"); checkPermission(cloneId); // 调用服务层进行语音克隆训练 - String result = voiceCloneService.cloneAudio(cloneId); - return new Result().ok(result); + voiceCloneService.cloneAudio(cloneId); + return new Result(); } private void checkPermission(String id) { diff --git a/main/manager-api/src/main/java/xiaozhi/modules/voiceclone/service/VoiceCloneService.java b/main/manager-api/src/main/java/xiaozhi/modules/voiceclone/service/VoiceCloneService.java index 750f02e6..7968bd73 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/voiceclone/service/VoiceCloneService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/voiceclone/service/VoiceCloneService.java @@ -35,17 +35,17 @@ public interface VoiceCloneService extends BaseService { * 根据用户ID查询声音克隆列表 */ List getByUserId(Long userId); - + /** * 分页查询带模型名称和用户名称的声音克隆列表 */ PageData pageWithNames(Map params); - + /** * 根据ID查询带模型名称和用户名称的声音克隆信息 */ VoiceCloneResponseDTO getByIdWithNames(String id); - + /** * 根据用户ID查询带模型名称的声音克隆列表 */ @@ -65,11 +65,11 @@ public interface VoiceCloneService extends BaseService { * 获取音频数据 */ byte[] getVoiceData(String id); - + /** * 克隆音频,调用火山引擎进行语音复刻训练 + * * @param cloneId 语音克隆记录ID - * @return 训练结果消息 */ - String cloneAudio(String cloneId); + void cloneAudio(String cloneId); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/voiceclone/service/impl/VoiceCloneServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/voiceclone/service/impl/VoiceCloneServiceImpl.java index cf66be20..e1e9877e 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/voiceclone/service/impl/VoiceCloneServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/voiceclone/service/impl/VoiceCloneServiceImpl.java @@ -1,10 +1,9 @@ package xiaozhi.modules.voiceclone.service.impl; -import java.io.IOException; +import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; -import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; @@ -19,16 +18,17 @@ import org.springframework.web.multipart.MultipartFile; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import xiaozhi.common.exception.ErrorCode; +import xiaozhi.common.exception.RenException; import xiaozhi.common.page.PageData; import xiaozhi.common.service.impl.BaseServiceImpl; import xiaozhi.common.utils.ConvertUtils; import xiaozhi.common.utils.DateUtils; -import xiaozhi.common.exception.ErrorCode; -import xiaozhi.common.exception.RenException; import xiaozhi.modules.model.entity.ModelConfigEntity; import xiaozhi.modules.model.service.ModelConfigService; import xiaozhi.modules.sys.service.SysUserService; @@ -221,34 +221,59 @@ public class VoiceCloneServiceImpl extends BaseServiceImpl config = modelConfig.getConfigJson(); + String type = (String) config.get("type"); + if (StringUtils.isBlank(type)) { + throw new RenException("模型类型未找到"); + } + if (type.equals("huoshan_double_stream")) { + huoshanClone(config, entity); + } + } catch (RenException re) { + throw re; + } catch (Exception e) { + e.printStackTrace(); + entity.setTrainStatus(3); + entity.setTrainError(e.getMessage()); + baseDao.updateById(entity); + throw new RenException("语音克隆失败: " + e.getMessage()); + } } - try { - ModelConfigEntity modelConfig = - modelConfigService.getModelByIdFromCache("TTS_HuoshanDoubleStreamTTS"); - if (modelConfig == null || modelConfig.getConfigJson() == null) { - throw new RenException("火山引擎配置未找到"); - } - Map config = modelConfig.getConfigJson(); + /** + * 调用火山引擎进行语音复刻训练 + * + * @param config 模型配置 + * @param entity 语音克隆记录实体 + * @throws Exception + */ + private void huoshanClone(Map config, VoiceCloneEntity entity) throws Exception { String appid = (String) config.get("appid"); String accessToken = (String) config.get("access_token"); if (StringUtils.isAnyBlank(appid, accessToken)) { - throw new RenException("火山引擎配置不完整"); + throw new RenException("火山引擎缺少appid或access_token"); } String audioBase64 = Base64.getEncoder().encodeToString(entity.getVoice()); Map reqBody = new HashMap<>(); reqBody.put("appid", appid); - reqBody.put("name", entity.getName()); List> audios = new ArrayList<>(); Map audioMap = new HashMap<>(); audioMap.put("audio_bytes", audioBase64); @@ -258,7 +283,7 @@ public String cloneAudio(String cloneId) { reqBody.put("source", 2); reqBody.put("language", 0); reqBody.put("model_type", 1); - reqBody.put("speaker_id", entity.getName()); + reqBody.put("speaker_id", entity.getVoiceId()); String apiUrl = "https://openspeech.bytedance.com/api/v1/mega_tts/audio/upload"; @@ -267,52 +292,47 @@ public String cloneAudio(String cloneId) { .uri(URI.create(apiUrl)) .header("Content-Type", "application/json") .header("Authorization", "Bearer;" + accessToken) - .header("Resource-Id", "volc.megatts.voiceclone") + .header("Resource-Id", "seed-icl-1.0") .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(reqBody))) .build(); - // >>> 打印调试信息 - System.out.println(">>> 请求地址 = " + apiUrl); - System.out.println(">>> AppID = " + appid); - System.out.println(">>> Token = " + accessToken); - - - /* ===== 真正发请求 ===== */ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(">>> HTTP status = " + response.statusCode()); System.out.println(">>> response body = " + response.body()); if (response.statusCode() == 200) { - Map rsp = objectMapper.readValue(response.body(), Map.class); - String status = (String) rsp.get("status"); - String spkId = (String) rsp.get("spk_id"); - if ("success".equals(status) && StringUtils.isNotBlank(spkId)) { - entity.setTrainStatus(2); - entity.setVoiceId(spkId); - entity.setTrainError(null); - baseDao.updateById(entity); - return "训练成功,声音ID: " + spkId; + Map rsp = objectMapper.readValue(response.body(), + new TypeReference>() { + }); + + // 获取BaseResp对象 + Map baseResp = objectMapper.convertValue(rsp.get("BaseResp"), + new TypeReference>() { + }); + if (baseResp != null) { + Integer statusCode = objectMapper.convertValue(baseResp.get("StatusCode"), Integer.class); + String statusMessage = objectMapper.convertValue(baseResp.getOrDefault("StatusMessage", ""), + String.class); + + // 获取speaker_id + String speakerId = objectMapper.convertValue(rsp.get("speaker_id"), String.class); + + // StatusCode == 0 表示成功 + if (statusCode != null && statusCode == 0 && StringUtils.isNotBlank(speakerId)) { + entity.setTrainStatus(2); + entity.setVoiceId(speakerId); + entity.setTrainError(null); + baseDao.updateById(entity); + } else { + // 失败时使用StatusMessage作为错误信息 + String errorMsg = StringUtils.isNotBlank(statusMessage) ? statusMessage : "训练失败"; + throw new RenException(errorMsg); + } } else { - String msg = (String) rsp.getOrDefault("message", "训练失败"); - throw new RenException(msg); + throw new RenException("响应格式错误,缺少BaseResp字段"); } } else { - System.out.println(">>> 请求失败 HTTP = " + response.statusCode()); - System.out.println(">>> 失败 body = " + response.body()); throw new RenException("请求失败,状态码: " + response.statusCode()); } - - } catch (RenException re) { - // 自己抛的业务异常继续往外抛 - throw re; - } catch (Exception e) { - /* ===== 任何其他异常(空指针、IO、JSON、404...)全部打印 ===== */ - System.out.println(">>> 语音克隆异常: " + e.getMessage()); - e.printStackTrace(); // 详细堆栈 - entity.setTrainStatus(3); - entity.setTrainError(e.getMessage()); - baseDao.updateById(entity); - throw new RenException("语音克隆失败: " + e.getMessage()); } - } }