xf 维语转语音

This commit is contained in:
ran.shang
2025-08-22 15:52:49 +08:00
parent cc8c388925
commit 5effd9bcf4
5 changed files with 76 additions and 17 deletions

View File

@@ -3,6 +3,7 @@ package com.newyetai.ai.resume.controller;
import com.newyetai.ai.resume.dto.AiResumeReqDTO; import com.newyetai.ai.resume.dto.AiResumeReqDTO;
import com.newyetai.ai.resume.service.AiResumeService; import com.newyetai.ai.resume.service.AiResumeService;
import com.newyetai.common.AlitaResult; import com.newyetai.common.AlitaResult;
import com.newyetai.sdk.xf.dto.TtsReqDTO;
import com.newyetai.sdk.xf.util.XfUtils; import com.newyetai.sdk.xf.util.XfUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -48,8 +49,8 @@ public class AiResumeController {
@PostMapping("/tts") @PostMapping("/tts")
@ApiOperation("ai简历-语音合成") @ApiOperation("ai简历-语音合成")
public AlitaResult<String> tts(@RequestParam("text") String text) { public AlitaResult<String> tts(@RequestBody @Validated TtsReqDTO ttsReq) {
return AlitaResult.success(XfUtils.tts(text)); return AlitaResult.success(XfUtils.tts(ttsReq));
} }
@PostMapping("/analyseResume") @PostMapping("/analyseResume")

View File

@@ -16,6 +16,10 @@ public class RtasrReqDTO implements Serializable {
@NotBlank(message = "语音文件不能为空") @NotBlank(message = "语音文件不能为空")
private String fileCode; private String fileCode;
/**
* 中文:cn
* 维语:cn_uyghur
*/
@Default @Default
private String from = "cn_uyghur"; private String from = "cn_uyghur";

View File

@@ -0,0 +1,40 @@
package com.newyetai.sdk.xf.dto;
import com.newyetai.sdk.xf.util.WebTtsWS;
import java.io.Serializable;
import javax.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Builder.Default;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class TtsReqDTO implements Serializable {
@NotBlank(message = "合成文本不能为空")
private String text;
/**
* 发音人
* x4_yezi(中文)/x2_ugsp_dilare(维语)
*/
@Default
private String vcn = "xiaoyan";
/**
* 文本编码格式
* GB2312
* GBK
* BIG5
* UNICODE(小语种)
* GB18030
* UTF8(小语种推荐使用UTF8编码)
* */
@Default
private String tte = WebTtsWS.TTL_UTF8;
}

View File

@@ -5,6 +5,7 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL; import java.net.URL;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@@ -22,6 +23,7 @@ import org.apache.tomcat.util.http.fileupload.ByteArrayOutputStream;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.newyetai.common.AlitaResult; import com.newyetai.common.AlitaResult;
import com.newyetai.sdk.xf.config.XfProperties; import com.newyetai.sdk.xf.config.XfProperties;
import com.newyetai.sdk.xf.dto.TtsReqDTO;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
@@ -44,6 +46,9 @@ public class WebTtsWS extends WebSocketListener {
public static final int StatusFirstFrame = 0; public static final int StatusFirstFrame = 0;
public static final int StatusContinueFrame = 1; public static final int StatusContinueFrame = 1;
public static final int StatusLastFrame = 2; public static final int StatusLastFrame = 2;
public static final String TTL_UTF8 = "UTF8";
public static final String TTL_UNICODE = "UNICODE";
public static final Gson json = new Gson(); public static final Gson json = new Gson();
// mp3格式 // mp3格式
private String encoding = "lame"; private String encoding = "lame";
@@ -52,14 +57,16 @@ public class WebTtsWS extends WebSocketListener {
private String text; private String text;
private String appId; private String appId;
OutputStream outputStream; OutputStream outputStream;
// 合成文本编码格式 // 小语种必须使用UNICODE编码作为值
public static final String TTE = "UTF8"; // 小语种必须使用UNICODE编码作为值 private String tte;
// 发音人参数。到控制台-我的应用-语音合成-添加试用或购买发音人,添加后即显示该发音人参数值,若试用未添加的发音人会报错11200 // 发音人参数。到控制台-我的应用-语音合成-添加试用或购买发音人,添加后即显示该发音人参数值,若试用未添加的发音人会报错11200
public static final String VCN = "xiaoyan"; private String vcn;
WebTtsWS(String text, String appId, OutputStream outputStream){ WebTtsWS(String text, String appId, String vcn, String tte, OutputStream outputStream){
this.text = text; this.text = text;
this.appId = appId; this.appId = appId;
this.vcn = vcn;
this.tte = tte;
this.outputStream = outputStream; this.outputStream = outputStream;
} }
@@ -73,6 +80,16 @@ public class WebTtsWS extends WebSocketListener {
super.onOpen(webSocket, response); super.onOpen(webSocket, response);
log.info("ws建立连接成功..."); log.info("ws建立连接成功...");
String requestJson;//请求参数json串 String requestJson;//请求参数json串
String encodedText ;
if (WebTtsWS.TTL_UNICODE.equals(tte)) {
try {
encodedText = Base64.getEncoder().encodeToString(text.getBytes("UTF-16LE"));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
} else {
encodedText = Base64.getEncoder().encodeToString(text.getBytes(StandardCharsets.UTF_8));
}
requestJson = "{\n" + requestJson = "{\n" +
" \"common\": {\n" + " \"common\": {\n" +
" \"app_id\": \"" + appId + "\"\n" + " \"app_id\": \"" + appId + "\"\n" +
@@ -80,21 +97,19 @@ public class WebTtsWS extends WebSocketListener {
" \"business\": {\n" + " \"business\": {\n" +
" \"aue\": \"lame\",\n" + " \"aue\": \"lame\",\n" +
" \"sfl\": 1,\n" + " \"sfl\": 1,\n" +
" \"tte\": \"" + TTE + "\",\n" + " \"tte\": \"" + tte + "\",\n" +
" \"ent\": \"intp65\",\n" + " \"ent\": \"intp65\",\n" +
" \"vcn\": \"" + VCN + "\",\n" + " \"vcn\": \"" + vcn + "\",\n" +
" \"pitch\": 50,\n" + " \"pitch\": 50,\n" +
" \"speed\": 50\n" + " \"speed\": 50\n" +
" },\n" + " },\n" +
" \"data\": {\n" + " \"data\": {\n" +
" \"status\": 2,\n" + " \"status\": 2,\n" +
" \"text\": \"" " \"text\": \""
+ Base64.getEncoder().encodeToString(text.getBytes(StandardCharsets.UTF_8)) + "\"\n" + + encodedText + "\"\n" +
//" \"text\": \"" + Base64.getEncoder().encodeToString(TEXT.getBytes("UTF-16LE")) + "\"\n" +
" }\n" + " }\n" +
"}"; "}";
webSocket.send(requestJson); webSocket.send(requestJson);
} }
@Override @Override
@@ -145,7 +160,7 @@ public class WebTtsWS extends WebSocketListener {
} }
} }
public static AlitaResult<Void> execute(String text, XfProperties xfProperties, OutputStream outputStream) { public static AlitaResult<Void> execute(TtsReqDTO ttsReq, XfProperties xfProperties, OutputStream outputStream) {
try { try {
// 构建鉴权url // 构建鉴权url
String authUrl = getAuthUrl(xfProperties.getTtsHostUrl(), xfProperties.getApiKey(), String authUrl = getAuthUrl(xfProperties.getTtsHostUrl(), xfProperties.getApiKey(),
@@ -154,7 +169,7 @@ public class WebTtsWS extends WebSocketListener {
//将url中的 schema http://和https://分别替换为ws:// 和 wss:// //将url中的 schema http://和https://分别替换为ws:// 和 wss://
String url = authUrl.toString().replace("http://", "ws://").replace("https://", "wss://"); String url = authUrl.toString().replace("http://", "ws://").replace("https://", "wss://");
Request request = new Request.Builder().url(url).build(); Request request = new Request.Builder().url(url).build();
WebTtsWS webIATWS = new WebTtsWS(text, xfProperties.getAppId(), outputStream); WebTtsWS webIATWS = new WebTtsWS(ttsReq.getText(), xfProperties.getAppId(), ttsReq.getVcn(), ttsReq.getTte(), outputStream);
WebSocket webSocket = client.newWebSocket(request, webIATWS); WebSocket webSocket = client.newWebSocket(request, webIATWS);
while (webIATWS.result == null) { while (webIATWS.result == null) {
// 未拿到结果等待 // 未拿到结果等待
@@ -165,7 +180,6 @@ public class WebTtsWS extends WebSocketListener {
log.info("err ", e); log.info("err ", e);
return AlitaResult.error("语音合成失败:{}", e.getMessage()); return AlitaResult.error("语音合成失败:{}", e.getMessage());
} }
} }
public static String getAuthUrl(String hostUrl, String apiKey, String apiSecret) public static String getAuthUrl(String hostUrl, String apiKey, String apiSecret)

View File

@@ -22,6 +22,7 @@ import com.newyetai.component.file.util.FileUtils;
import com.newyetai.config.service.ConfigService; import com.newyetai.config.service.ConfigService;
import com.newyetai.sdk.xf.config.XfProperties; import com.newyetai.sdk.xf.config.XfProperties;
import com.newyetai.sdk.xf.dto.InterviewContentDTO; import com.newyetai.sdk.xf.dto.InterviewContentDTO;
import com.newyetai.sdk.xf.dto.TtsReqDTO;
import com.newyetai.sdk.xf.model.ChatReq; import com.newyetai.sdk.xf.model.ChatReq;
import com.newyetai.sdk.xf.model.MessageDTO; import com.newyetai.sdk.xf.model.MessageDTO;
import com.newyetai.sdk.xf.service.XfSparkProvider; import com.newyetai.sdk.xf.service.XfSparkProvider;
@@ -123,15 +124,14 @@ public class XfUtils {
} }
/** /**
* 语音合成 * 语音合成
* @return * @return
* @author sr on 2025-01-09 * @author sr on 2025-01-09
*/ */
public static String tts(String text) { public static String tts(TtsReqDTO ttsReq) {
try(ByteArrayOutputStream outputStream = new ByteArrayOutputStream()){ try(ByteArrayOutputStream outputStream = new ByteArrayOutputStream()){
AlitaResult<Void> result = WebTtsWS.execute(text, xfProperties, outputStream); AlitaResult<Void> result = WebTtsWS.execute(ttsReq, xfProperties, outputStream);
Assert.isTrue(result.getSuccess(), result.getMsg()); Assert.isTrue(result.getSuccess(), result.getMsg());
FileRecord fileRecord = fileService.upload("语音合成.mp3", outputStream.toByteArray()); FileRecord fileRecord = fileService.upload("语音合成.mp3", outputStream.toByteArray());
return fileRecord.getFileCode(); return fileRecord.getFileCode();