From d1d44e475a31ca35c03cb3dbd237f6a5bde216f0 Mon Sep 17 00:00:00 2001
From: troyeguo <13820674+troyeguo@users.noreply.github.com>
Date: Wed, 23 Sep 2026 09:49:03 +0800
Subject: [PATCH] Refactor zhipuTTSVoice.js for improved readability and
consistency in formatting
---
.../plugins/main/voice/amazonPollyVoice.js | 362 +--
.../plugins/main/voice/chatTTSUIVoice.js | 63 +-
src/utils/plugins/main/voice/chatTTSVoice.js | 72 +-
src/utils/plugins/main/voice/coquiTTSVoice.js | 77 +-
.../plugins/main/voice/elevenlabsTTSVoice.js | 160 +-
src/utils/plugins/main/voice/grokTTSVoice.js | 173 +-
src/utils/plugins/main/voice/mimoTTSVoice.js | 225 +-
.../plugins/main/voice/minimaxTTSVoice.js | 338 +--
src/utils/plugins/main/voice/multiTTSVoice.js | 141 +-
.../plugins/main/voice/openaiTTSVoice.js | 139 +-
src/utils/plugins/main/voice/qwenTTSVoice.js | 401 ++--
.../plugins/main/voice/ttsServerVoice.js | 101 +-
.../plugins/main/voice/volcengineTTSVoice.js | 2135 ++++++++++++++---
src/utils/plugins/main/voice/zhipuTTSVoice.js | 176 +-
14 files changed, 3036 insertions(+), 1527 deletions(-)
diff --git a/src/utils/plugins/main/voice/amazonPollyVoice.js b/src/utils/plugins/main/voice/amazonPollyVoice.js
index 948fa657..f459b114 100644
--- a/src/utils/plugins/main/voice/amazonPollyVoice.js
+++ b/src/utils/plugins/main/voice/amazonPollyVoice.js
@@ -3,184 +3,208 @@ const fs = require("fs");
const crypto = require("crypto");
const axios = require("axios");
const getAudioPath = async (text, speed, dirPath, config) => {
- let audioName = new Date().getTime() + ".mp3";
- if (!fs.existsSync(path.join(dirPath, "tts"))) {
- fs.mkdirSync(path.join(dirPath, "tts"));
- fs.writeFileSync(path.join(dirPath, "tts", audioName), await getTTSAudio(text, speed, config));
- }
- else {
- fs.writeFileSync(path.join(dirPath, "tts", audioName), await getTTSAudio(text, speed, config));
- }
- return path.join(dirPath, "tts", audioName);
+ let audioName = new Date().getTime() + ".mp3";
+ if (!fs.existsSync(path.join(dirPath, "tts"))) {
+ fs.mkdirSync(path.join(dirPath, "tts"));
+ fs.writeFileSync(
+ path.join(dirPath, "tts", audioName),
+ await getTTSAudio(text, speed, config)
+ );
+ } else {
+ fs.writeFileSync(
+ path.join(dirPath, "tts", audioName),
+ await getTTSAudio(text, speed, config)
+ );
+ }
+ return path.join(dirPath, "tts", audioName);
};
// AWS Signature Version 4 signing helpers
const getSignatureKey = (crypto, key, dateStamp, regionName, serviceName) => {
- const kDate = crypto
- .createHmac("sha256", "AWS4" + key)
- .update(dateStamp)
- .digest();
- const kRegion = crypto
- .createHmac("sha256", kDate)
- .update(regionName)
- .digest();
- const kService = crypto
- .createHmac("sha256", kRegion)
- .update(serviceName)
- .digest();
- const kSigning = crypto
- .createHmac("sha256", kService)
- .update("aws4_request")
- .digest();
- return kSigning;
+ const kDate = crypto
+ .createHmac("sha256", "AWS4" + key)
+ .update(dateStamp)
+ .digest();
+ const kRegion = crypto
+ .createHmac("sha256", kDate)
+ .update(regionName)
+ .digest();
+ const kService = crypto
+ .createHmac("sha256", kRegion)
+ .update(serviceName)
+ .digest();
+ const kSigning = crypto
+ .createHmac("sha256", kService)
+ .update("aws4_request")
+ .digest();
+ return kSigning;
};
const getAmzDateTime = () => {
- const now = new Date();
- const amzDate = now
- .toISOString()
- .replace(/[:\-]|\.\d{3}/g, "")
- .slice(0, 15) + "Z";
- const dateStamp = amzDate.slice(0, 8);
- return { amzDate, dateStamp };
+ const now = new Date();
+ const amzDate =
+ now
+ .toISOString()
+ .replace(/[:\-]|\.\d{3}/g, "")
+ .slice(0, 15) + "Z";
+ const dateStamp = amzDate.slice(0, 8);
+ return { amzDate, dateStamp };
};
const getTTSAudio = async (text, speed, config) => {
- let region = config.region || "us-east-1";
- let accessKeyId = config.accessKeyId || "";
- let secretAccessKey = config.secretAccessKey || "";
- let voiceId = config.voiceId || "Joanna";
- let engine = config.engine || "neural";
- // speed is a multiplier: 1.0 = normal, e.g. 0.5 = slow, 2.0 = fast
- // Amazon Polly SSML rate accepts: x-slow, slow, medium, fast, x-fast, or N%
- let rate = speed ? `${Math.round(speed * 100)}%` : "100%";
- const service = "polly";
- const host = `polly.${region}.amazonaws.com`;
- const endpoint = `https://${host}/v1/speech`;
- const method = "POST";
- const contentType = "application/json";
- const requestBody = JSON.stringify({
- Engine: engine,
- OutputFormat: "mp3",
- Text: `${text}`,
- TextType: "ssml",
- VoiceId: voiceId,
- });
- const { amzDate, dateStamp } = getAmzDateTime();
- const payloadHash = crypto
- .createHash("sha256")
- .update(requestBody)
- .digest("hex");
- const canonicalHeaders = `content-type:${contentType}\n` +
- `host:${host}\n` +
- `x-amz-date:${amzDate}\n`;
- const signedHeaders = "content-type;host;x-amz-date";
- const canonicalRequest = [
- method,
- "/v1/speech",
- "",
- canonicalHeaders,
- signedHeaders,
- payloadHash,
- ].join("\n");
- const algorithm = "AWS4-HMAC-SHA256";
- const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
- const stringToSign = [
- algorithm,
- amzDate,
- credentialScope,
- crypto.createHash("sha256").update(canonicalRequest).digest("hex"),
- ].join("\n");
- const signingKey = getSignatureKey(crypto, secretAccessKey, dateStamp, region, service);
- const signature = crypto
- .createHmac("sha256", signingKey)
- .update(stringToSign)
- .digest("hex");
- const authorizationHeader = `${algorithm} Credential=${accessKeyId}/${credentialScope}, ` +
- `SignedHeaders=${signedHeaders}, Signature=${signature}`;
- return new Promise((resolve, reject) => {
- axios
- .post(endpoint, requestBody, {
- headers: {
- "Content-Type": contentType,
- "X-Amz-Date": amzDate,
- Authorization: authorizationHeader,
- },
- responseType: "arraybuffer",
- })
- .then((response) => {
- resolve(response.data);
- })
- .catch((error) => {
- reject(error);
- });
- });
+ let region = config.region || "us-east-1";
+ let accessKeyId = config.accessKeyId || "";
+ let secretAccessKey = config.secretAccessKey || "";
+ let voiceId = config.voiceId || "Joanna";
+ let engine = config.engine || "neural";
+ // speed is a multiplier: 1.0 = normal, e.g. 0.5 = slow, 2.0 = fast
+ // Amazon Polly SSML rate accepts: x-slow, slow, medium, fast, x-fast, or N%
+ let rate = speed ? `${Math.round(speed * 100)}%` : "100%";
+ const service = "polly";
+ const host = `polly.${region}.amazonaws.com`;
+ const endpoint = `https://${host}/v1/speech`;
+ const method = "POST";
+ const contentType = "application/json";
+ const requestBody = JSON.stringify({
+ Engine: engine,
+ OutputFormat: "mp3",
+ Text: `${text}`,
+ TextType: "ssml",
+ VoiceId: voiceId,
+ });
+ const { amzDate, dateStamp } = getAmzDateTime();
+ const payloadHash = crypto
+ .createHash("sha256")
+ .update(requestBody)
+ .digest("hex");
+ const canonicalHeaders =
+ `content-type:${contentType}\n` +
+ `host:${host}\n` +
+ `x-amz-date:${amzDate}\n`;
+ const signedHeaders = "content-type;host;x-amz-date";
+ const canonicalRequest = [
+ method,
+ "/v1/speech",
+ "",
+ canonicalHeaders,
+ signedHeaders,
+ payloadHash,
+ ].join("\n");
+ const algorithm = "AWS4-HMAC-SHA256";
+ const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
+ const stringToSign = [
+ algorithm,
+ amzDate,
+ credentialScope,
+ crypto.createHash("sha256").update(canonicalRequest).digest("hex"),
+ ].join("\n");
+ const signingKey = getSignatureKey(
+ crypto,
+ secretAccessKey,
+ dateStamp,
+ region,
+ service
+ );
+ const signature = crypto
+ .createHmac("sha256", signingKey)
+ .update(stringToSign)
+ .digest("hex");
+ const authorizationHeader =
+ `${algorithm} Credential=${accessKeyId}/${credentialScope}, ` +
+ `SignedHeaders=${signedHeaders}, Signature=${signature}`;
+ return new Promise((resolve, reject) => {
+ axios
+ .post(endpoint, requestBody, {
+ headers: {
+ "Content-Type": contentType,
+ "X-Amz-Date": amzDate,
+ Authorization: authorizationHeader,
+ },
+ responseType: "arraybuffer",
+ })
+ .then((response) => {
+ resolve(response.data);
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
};
const getTTSVoice = async (config) => {
- let region = config.region || "us-east-1";
- let accessKeyId = config.accessKeyId || "";
- let secretAccessKey = config.secretAccessKey || "";
- const service = "polly";
- const host = `polly.${region}.amazonaws.com`;
- const method = "GET";
- const path = "/v1/voices";
- const queryString = "Engine=neural";
- const { amzDate, dateStamp } = getAmzDateTime();
- const payloadHash = crypto.createHash("sha256").update("").digest("hex");
- const canonicalHeaders = `host:${host}\n` + `x-amz-date:${amzDate}\n`;
- const signedHeaders = "host;x-amz-date";
- const canonicalRequest = [
- method,
- path,
- queryString,
- canonicalHeaders,
- signedHeaders,
- payloadHash,
- ].join("\n");
- const algorithm = "AWS4-HMAC-SHA256";
- const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
- const stringToSign = [
- algorithm,
- amzDate,
- credentialScope,
- crypto.createHash("sha256").update(canonicalRequest).digest("hex"),
- ].join("\n");
- const signingKey = getSignatureKey(crypto, secretAccessKey, dateStamp, region, service);
- const signature = crypto
- .createHmac("sha256", signingKey)
- .update(stringToSign)
- .digest("hex");
- const authorizationHeader = `${algorithm} Credential=${accessKeyId}/${credentialScope}, ` +
- `SignedHeaders=${signedHeaders}, Signature=${signature}`;
- const url = `https://${host}${path}?${queryString}`;
- return new Promise((resolve, reject) => {
- axios
- .get(url, {
- headers: {
- "X-Amz-Date": amzDate,
- Authorization: authorizationHeader,
- },
- })
- .then((response) => {
- let voices = response.data.Voices;
- resolve(voices.map((voice) => {
- return {
- name: voice.Id,
- gender: voice.Gender.toLowerCase(),
- locale: voice.LanguageCode,
- displayName: `Amazon Polly - ${voice.Name} (${voice.LanguageCode}) - ${voice.Gender}`,
- plugin: "amazon-polly-voice-plugin",
- config: {
- ...config,
- voiceId: voice.Id,
- engine: (voice.SupportedEngines || []).includes("neural")
- ? "neural"
- : "standard",
- },
- };
- }));
- })
- .catch((error) => {
- reject(error);
- });
- });
+ let region = config.region || "us-east-1";
+ let accessKeyId = config.accessKeyId || "";
+ let secretAccessKey = config.secretAccessKey || "";
+ const service = "polly";
+ const host = `polly.${region}.amazonaws.com`;
+ const method = "GET";
+ const path = "/v1/voices";
+ const queryString = "Engine=neural";
+ const { amzDate, dateStamp } = getAmzDateTime();
+ const payloadHash = crypto.createHash("sha256").update("").digest("hex");
+ const canonicalHeaders = `host:${host}\n` + `x-amz-date:${amzDate}\n`;
+ const signedHeaders = "host;x-amz-date";
+ const canonicalRequest = [
+ method,
+ path,
+ queryString,
+ canonicalHeaders,
+ signedHeaders,
+ payloadHash,
+ ].join("\n");
+ const algorithm = "AWS4-HMAC-SHA256";
+ const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
+ const stringToSign = [
+ algorithm,
+ amzDate,
+ credentialScope,
+ crypto.createHash("sha256").update(canonicalRequest).digest("hex"),
+ ].join("\n");
+ const signingKey = getSignatureKey(
+ crypto,
+ secretAccessKey,
+ dateStamp,
+ region,
+ service
+ );
+ const signature = crypto
+ .createHmac("sha256", signingKey)
+ .update(stringToSign)
+ .digest("hex");
+ const authorizationHeader =
+ `${algorithm} Credential=${accessKeyId}/${credentialScope}, ` +
+ `SignedHeaders=${signedHeaders}, Signature=${signature}`;
+ const url = `https://${host}${path}?${queryString}`;
+ return new Promise((resolve, reject) => {
+ axios
+ .get(url, {
+ headers: {
+ "X-Amz-Date": amzDate,
+ Authorization: authorizationHeader,
+ },
+ })
+ .then((response) => {
+ let voices = response.data.Voices;
+ console.log("Amazon Polly voices fetched:", voices);
+ resolve(
+ voices.map((voice) => {
+ return {
+ name: voice.Id,
+ gender: voice.Gender.toLowerCase(),
+ locale: voice.LanguageCode,
+ displayName: `Amazon Polly - ${voice.Name} (${voice.LanguageCode}) - ${voice.Gender}`,
+ plugin: "amazon-polly-voice-plugin",
+ config: {
+ ...config,
+ voiceId: voice.Id,
+ engine: (voice.SupportedEngines || []).includes("neural")
+ ? "neural"
+ : "standard",
+ },
+ };
+ })
+ );
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
};
module.exports = { getAudioPath, getTTSVoice };
diff --git a/src/utils/plugins/main/voice/chatTTSUIVoice.js b/src/utils/plugins/main/voice/chatTTSUIVoice.js
index a62b4ed9..562c5fd5 100644
--- a/src/utils/plugins/main/voice/chatTTSUIVoice.js
+++ b/src/utils/plugins/main/voice/chatTTSUIVoice.js
@@ -1,37 +1,40 @@
const axios = require("axios");
const qs = require("qs");
const getAudioPath = async (text, speed, dirPath, config) => {
- let url = config.url || "http://127.0.0.1:9966/tts";
- let prompt = config.prompt || "";
- let voice = config.voice || "3333";
- let temperature = config.temperature || 0.3;
- let top_p = config.top_p || 0.7;
- let top_k = config.top_k || 20;
- let skip_refine = config.skip_refine || 0;
- let custom_voice = config.custom_voice || 0;
- return new Promise((resolve, reject) => {
- axios.post(url, qs.stringify({
- text: text,
- prompt: prompt,
- voice: voice,
- temperature: temperature,
- top_p: top_p,
- top_k: top_k,
- skip_refine: skip_refine,
- custom_voice: custom_voice
- }))
- .then(response => {
- if (response.data.code == 0) {
- resolve(response.data.audio_files[0].filename);
- }
- else {
- resolve("");
- }
+ let url = config.url || "http://127.0.0.1:9966/tts";
+ let prompt = config.prompt || "";
+ let voice = config.voice || "3333";
+ let temperature = config.temperature || 0.3;
+ let top_p = config.top_p || 0.7;
+ let top_k = config.top_k || 20;
+ let skip_refine = config.skip_refine || 0;
+ let custom_voice = config.custom_voice || 0;
+ return new Promise((resolve, reject) => {
+ axios
+ .post(
+ url,
+ qs.stringify({
+ text: text,
+ prompt: prompt,
+ voice: voice,
+ temperature: temperature,
+ top_p: top_p,
+ top_k: top_k,
+ skip_refine: skip_refine,
+ custom_voice: custom_voice,
})
- .catch(error => {
- reject(error);
- });
- });
+ )
+ .then((response) => {
+ if (response.data.code == 0) {
+ resolve(response.data.audio_files[0].filename);
+ } else {
+ resolve("");
+ }
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
};
module.exports = { getAudioPath };
diff --git a/src/utils/plugins/main/voice/chatTTSVoice.js b/src/utils/plugins/main/voice/chatTTSVoice.js
index da9f7cde..3526cedb 100644
--- a/src/utils/plugins/main/voice/chatTTSVoice.js
+++ b/src/utils/plugins/main/voice/chatTTSVoice.js
@@ -3,41 +3,47 @@ const fs = require("fs");
const axios = require("axios");
const JSZip = require("jszip");
const getAudioPath = async (text, speed, dirPath, config) => {
- let audioName = new Date().getTime() + ".mp3";
- if (!fs.existsSync(path.join(dirPath, "tts"))) {
- fs.mkdirSync(path.join(dirPath, "tts"));
- fs.writeFileSync(path.join(dirPath, "tts", audioName), await getTTSAudio(text, speed, config));
- }
- else {
- fs.writeFileSync(path.join(dirPath, "tts", audioName), await getTTSAudio(text, speed, config));
- }
- return path.join(dirPath, "tts", audioName);
+ let audioName = new Date().getTime() + ".mp3";
+ if (!fs.existsSync(path.join(dirPath, "tts"))) {
+ fs.mkdirSync(path.join(dirPath, "tts"));
+ fs.writeFileSync(
+ path.join(dirPath, "tts", audioName),
+ await getTTSAudio(text, speed, config)
+ );
+ } else {
+ fs.writeFileSync(
+ path.join(dirPath, "tts", audioName),
+ await getTTSAudio(text, speed, config)
+ );
+ }
+ return path.join(dirPath, "tts", audioName);
};
const getTTSAudio = async (text, speed, config) => {
- let url = config.url || "http://127.0.0.1:8000/generate_voice";
- // main infer params
- const body = config.body;
- // refine text params
- const params_refine_text = config.params_refine_text;
- // Add params_refine_text to body
- body.params_refine_text = params_refine_text;
- // infer code params
- const params_infer_code = config.params_infer_code;
- // Add params_infer_code to body
- body.params_infer_code = params_infer_code;
- body.text = [text];
- body.stream = false;
- return new Promise((resolve, reject) => {
- axios.post(url, body, { responseType: "arraybuffer" })
- .then(async (response) => {
- const zip = await JSZip.loadAsync(response.data);
- const firstEntry = Object.values(zip.files)[0];
- resolve(await firstEntry.async("nodebuffer"));
- })
- .catch(error => {
- reject(error);
- });
- });
+ let url = config.url || "http://127.0.0.1:8000/generate_voice";
+ // main infer params
+ const body = config.body;
+ // refine text params
+ const params_refine_text = config.params_refine_text;
+ // Add params_refine_text to body
+ body.params_refine_text = params_refine_text;
+ // infer code params
+ const params_infer_code = config.params_infer_code;
+ // Add params_infer_code to body
+ body.params_infer_code = params_infer_code;
+ body.text = [text];
+ body.stream = false;
+ return new Promise((resolve, reject) => {
+ axios
+ .post(url, body, { responseType: "arraybuffer" })
+ .then(async (response) => {
+ const zip = await JSZip.loadAsync(response.data);
+ const firstEntry = Object.values(zip.files)[0];
+ resolve(await firstEntry.async("nodebuffer"));
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
};
module.exports = { getAudioPath };
diff --git a/src/utils/plugins/main/voice/coquiTTSVoice.js b/src/utils/plugins/main/voice/coquiTTSVoice.js
index 1885b437..f93d320c 100644
--- a/src/utils/plugins/main/voice/coquiTTSVoice.js
+++ b/src/utils/plugins/main/voice/coquiTTSVoice.js
@@ -2,47 +2,58 @@ const path = require("path");
const fs = require("fs");
const axios = require("axios");
const getAudioPath = async (text, speed, dirPath, config) => {
- let audioName = new Date().getTime() + ".wav";
- if (!fs.existsSync(path.join(dirPath, "tts"))) {
- fs.mkdirSync(path.join(dirPath, "tts"));
- fs.writeFileSync(path.join(dirPath, "tts", audioName), await getTTSAudio(text, speed, config));
- }
- else {
- fs.writeFileSync(path.join(dirPath, "tts", audioName), await getTTSAudio(text, speed, config));
- }
- return path.join(dirPath, "tts", audioName);
+ let audioName = new Date().getTime() + ".wav";
+ if (!fs.existsSync(path.join(dirPath, "tts"))) {
+ fs.mkdirSync(path.join(dirPath, "tts"));
+ fs.writeFileSync(
+ path.join(dirPath, "tts", audioName),
+ await getTTSAudio(text, speed, config)
+ );
+ } else {
+ fs.writeFileSync(
+ path.join(dirPath, "tts", audioName),
+ await getTTSAudio(text, speed, config)
+ );
+ }
+ return path.join(dirPath, "tts", audioName);
};
const objectToQueryString = (obj) => {
- const queryParams = [];
- for (let key in obj) {
- if (obj.hasOwnProperty(key)) {
- const value = obj[key];
- const encodedKey = encodeURIComponent(key);
- const encodedValue = encodeURIComponent(value);
- queryParams.push(`${encodedKey}=${encodedValue}`);
- }
+ const queryParams = [];
+ for (let key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ const value = obj[key];
+ const encodedKey = encodeURIComponent(key);
+ const encodedValue = encodeURIComponent(value);
+ queryParams.push(`${encodedKey}=${encodedValue}`);
}
- return queryParams.join("&");
+ }
+ return queryParams.join("&");
};
const getTTSAudio = async (text, speed, config) => {
- let url = config.url || "http://127.0.0.1:5002/api/tts";
- let speaker_id = config.speaker_id || "";
- let language_id = config.language_id || "";
- let style_wav = config.style_wav || "";
- return new Promise((resolve, reject) => {
- axios.get(url + "?" + objectToQueryString({
+ let url = config.url || "http://127.0.0.1:5002/api/tts";
+ let speaker_id = config.speaker_id || "";
+ let language_id = config.language_id || "";
+ let style_wav = config.style_wav || "";
+ return new Promise((resolve, reject) => {
+ axios
+ .get(
+ url +
+ "?" +
+ objectToQueryString({
text,
speaker_id,
language_id,
- style_wav
- }), { responseType: "arraybuffer" })
- .then(response => {
- resolve(response.data);
- })
- .catch(error => {
- reject(error);
- });
- });
+ style_wav,
+ }),
+ { responseType: "arraybuffer" }
+ )
+ .then((response) => {
+ resolve(response.data);
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
};
module.exports = { getAudioPath };
diff --git a/src/utils/plugins/main/voice/elevenlabsTTSVoice.js b/src/utils/plugins/main/voice/elevenlabsTTSVoice.js
index b1d72b1e..0c3569c8 100644
--- a/src/utils/plugins/main/voice/elevenlabsTTSVoice.js
+++ b/src/utils/plugins/main/voice/elevenlabsTTSVoice.js
@@ -2,84 +2,96 @@ const path = require("path");
const fs = require("fs");
const axios = require("axios");
const getAudioPath = async (text, speed, dirPath, config) => {
- let audioName = new Date().getTime() + ".mp3";
- if (!fs.existsSync(path.join(dirPath, "tts"))) {
- fs.mkdirSync(path.join(dirPath, "tts"));
- fs.writeFileSync(path.join(dirPath, "tts", audioName), await getTTSAudio(text, speed, config));
- }
- else {
- fs.writeFileSync(path.join(dirPath, "tts", audioName), await getTTSAudio(text, speed, config));
- }
- return path.join(dirPath, "tts", audioName);
+ let audioName = new Date().getTime() + ".mp3";
+ if (!fs.existsSync(path.join(dirPath, "tts"))) {
+ fs.mkdirSync(path.join(dirPath, "tts"));
+ fs.writeFileSync(
+ path.join(dirPath, "tts", audioName),
+ await getTTSAudio(text, speed, config)
+ );
+ } else {
+ fs.writeFileSync(
+ path.join(dirPath, "tts", audioName),
+ await getTTSAudio(text, speed, config)
+ );
+ }
+ return path.join(dirPath, "tts", audioName);
};
const getTTSAudio = async (text, speed, config) => {
- let apiKey = config.apiKey || "";
- let voiceId = config.voiceId || "JBFqnCBsd6RMkjVDRZzb";
- let modelId = config.modelId || "eleven_multilingual_v2";
- let outputFormat = "mp3_44100_128";
- // speed: 0.7 ~ 1.2, default 1.0
- let voiceSettings = {
- speed: speed || 1.0,
- };
- let url = `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}?output_format=${outputFormat}`;
- return new Promise((resolve, reject) => {
- axios
- .post(url, {
- text: text,
- model_id: modelId,
- voice_settings: voiceSettings,
- }, {
- headers: {
- "xi-api-key": apiKey,
- "Content-Type": "application/json",
- },
- responseType: "arraybuffer",
- })
- .then((response) => {
- resolve(response.data);
- })
- .catch((error) => {
- reject(error);
- });
- });
+ let apiKey = config.apiKey || "";
+ let voiceId = config.voiceId || "JBFqnCBsd6RMkjVDRZzb";
+ let modelId = config.modelId || "eleven_multilingual_v2";
+ let outputFormat = "mp3_44100_128";
+ // speed: 0.7 ~ 1.2, default 1.0
+ let voiceSettings = {
+ speed: speed || 1.0,
+ };
+ let url = `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}?output_format=${outputFormat}`;
+ return new Promise((resolve, reject) => {
+ axios
+ .post(
+ url,
+ {
+ text: text,
+ model_id: modelId,
+ voice_settings: voiceSettings,
+ },
+ {
+ headers: {
+ "xi-api-key": apiKey,
+ "Content-Type": "application/json",
+ },
+ responseType: "arraybuffer",
+ }
+ )
+ .then((response) => {
+ resolve(response.data);
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
};
const getTTSVoice = async (config) => {
- let apiKey = config.apiKey || "";
- let url = `https://api.elevenlabs.io/v2/voices?page_size=100`;
- return new Promise((resolve, reject) => {
- axios
- .get(url, {
- headers: {
- "xi-api-key": apiKey,
- },
- })
- .then((response) => {
- let voices = response.data.voices;
- resolve(voices.map((voice) => {
- let labels = voice.labels || {};
- let gender = labels.gender || "unknown";
- let locale = labels.language ||
- (voice.verified_languages &&
- voice.verified_languages[0] &&
- voice.verified_languages[0].locale) ||
- "";
- return {
- name: voice.voice_id,
- gender: gender.toLowerCase(),
- locale: locale,
- displayName: `ElevenLabs - ${voice.name}${locale ? ` (${locale})` : ""}`,
- plugin: "elevenlabs-tts-voice-plugin",
- config: {
- ...config,
- voiceId: voice.voice_id,
- },
- };
- }));
- })
- .catch((error) => {
- reject(error);
- });
- });
+ let apiKey = config.apiKey || "";
+ let url = `https://api.elevenlabs.io/v2/voices?page_size=100`;
+ return new Promise((resolve, reject) => {
+ axios
+ .get(url, {
+ headers: {
+ "xi-api-key": apiKey,
+ },
+ })
+ .then((response) => {
+ let voices = response.data.voices;
+ resolve(
+ voices.map((voice) => {
+ let labels = voice.labels || {};
+ let gender = labels.gender || "unknown";
+ let locale =
+ labels.language ||
+ (voice.verified_languages &&
+ voice.verified_languages[0] &&
+ voice.verified_languages[0].locale) ||
+ "";
+ return {
+ name: voice.voice_id,
+ gender: gender.toLowerCase(),
+ locale: locale,
+ displayName: `ElevenLabs - ${voice.name}${locale ? ` (${locale})` : ""}`,
+ plugin: "elevenlabs-tts-voice-plugin",
+ config: {
+ ...config,
+ voiceId: voice.voice_id,
+ },
+ };
+ })
+ );
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
};
module.exports = { getAudioPath, getTTSVoice };
diff --git a/src/utils/plugins/main/voice/grokTTSVoice.js b/src/utils/plugins/main/voice/grokTTSVoice.js
index 1b54ea0d..adf85ce0 100644
--- a/src/utils/plugins/main/voice/grokTTSVoice.js
+++ b/src/utils/plugins/main/voice/grokTTSVoice.js
@@ -2,96 +2,101 @@ const path = require("path");
const fs = require("fs");
const axios = require("axios");
const getAudioPath = async (text, speed, dirPath, config) => {
- let audioName = new Date().getTime() + ".mp3";
- if (!fs.existsSync(path.join(dirPath, "tts"))) {
- fs.mkdirSync(path.join(dirPath, "tts"));
- fs.writeFileSync(path.join(dirPath, "tts", audioName), await getTTSAudio(text, speed, config));
- }
- else {
- fs.writeFileSync(path.join(dirPath, "tts", audioName), await getTTSAudio(text, speed, config));
- }
- return path.join(dirPath, "tts", audioName);
+ let audioName = new Date().getTime() + ".mp3";
+ if (!fs.existsSync(path.join(dirPath, "tts"))) {
+ fs.mkdirSync(path.join(dirPath, "tts"));
+ fs.writeFileSync(
+ path.join(dirPath, "tts", audioName),
+ await getTTSAudio(text, speed, config)
+ );
+ } else {
+ fs.writeFileSync(
+ path.join(dirPath, "tts", audioName),
+ await getTTSAudio(text, speed, config)
+ );
+ }
+ return path.join(dirPath, "tts", audioName);
};
const getTTSAudio = async (text, speed, config) => {
- let apiKey = config.apiKey || "";
- let voiceId = config.voiceId || "eve";
- let language = config.language || "auto";
- let url = "https://api.x.ai/v1/tts";
- let body = {
- text: text,
- voice_id: voiceId,
- language: language,
- output_format: {
- codec: "mp3",
- sample_rate: 24000,
- bit_rate: 128000,
+ let apiKey = config.apiKey || "";
+ let voiceId = config.voiceId || "eve";
+ let language = config.language || "auto";
+ let url = "https://api.x.ai/v1/tts";
+ let body = {
+ text: text,
+ voice_id: voiceId,
+ language: language,
+ output_format: {
+ codec: "mp3",
+ sample_rate: 24000,
+ bit_rate: 128000,
+ },
+ };
+ return new Promise((resolve, reject) => {
+ axios
+ .post(url, body, {
+ headers: {
+ Authorization: `Bearer ${apiKey}`,
+ "Content-Type": "application/json",
},
- };
- return new Promise((resolve, reject) => {
- axios
- .post(url, body, {
- headers: {
- Authorization: `Bearer ${apiKey}`,
- "Content-Type": "application/json",
- },
- responseType: "arraybuffer",
- })
- .then((response) => {
- resolve(response.data);
- })
- .catch((error) => {
- reject(error);
- });
- });
+ responseType: "arraybuffer",
+ })
+ .then((response) => {
+ resolve(response.data);
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
};
const getTTSVoice = async (config) => {
- const voiceList = [
- { voiceId: "eve", name: "Eve", gender: "female" },
- { voiceId: "ara", name: "Ara", gender: "female" },
- { voiceId: "rex", name: "Rex", gender: "male" },
- { voiceId: "sal", name: "Sal", gender: "male" },
- { voiceId: "leo", name: "Leo", gender: "male" },
- ];
- const languageList = [
- { code: "en", name: "English" },
- { code: "ar-EG", name: "Arabic (Egypt)" },
- { code: "ar-SA", name: "Arabic (Saudi Arabia)" },
- { code: "ar-AE", name: "Arabic (UAE)" },
- { code: "bn", name: "Bengali" },
- { code: "zh", name: "Chinese (Simplified)" },
- { code: "fr", name: "French" },
- { code: "de", name: "German" },
- { code: "hi", name: "Hindi" },
- { code: "id", name: "Indonesian" },
- { code: "it", name: "Italian" },
- { code: "ja", name: "Japanese" },
- { code: "ko", name: "Korean" },
- { code: "pt-BR", name: "Portuguese (Brazil)" },
- { code: "pt-PT", name: "Portuguese (Portugal)" },
- { code: "ru", name: "Russian" },
- { code: "es-MX", name: "Spanish (Mexico)" },
- { code: "es-ES", name: "Spanish (Spain)" },
- { code: "tr", name: "Turkish" },
- { code: "vi", name: "Vietnamese" },
- ];
- const result = [];
- for (const voice of voiceList) {
- for (const lang of languageList) {
- result.push({
- name: voice.voiceId,
- gender: voice.gender,
- locale: lang.code,
- displayName: `Grok TTS - ${voice.name} (${lang.name})`,
- plugin: "grok-tts-voice-plugin",
- config: {
- ...config,
- voiceId: voice.voiceId,
- language: lang.code,
- },
- });
- }
+ const voiceList = [
+ { voiceId: "eve", name: "Eve", gender: "female" },
+ { voiceId: "ara", name: "Ara", gender: "female" },
+ { voiceId: "rex", name: "Rex", gender: "male" },
+ { voiceId: "sal", name: "Sal", gender: "male" },
+ { voiceId: "leo", name: "Leo", gender: "male" },
+ ];
+ const languageList = [
+ { code: "en", name: "English" },
+ { code: "ar-EG", name: "Arabic (Egypt)" },
+ { code: "ar-SA", name: "Arabic (Saudi Arabia)" },
+ { code: "ar-AE", name: "Arabic (UAE)" },
+ { code: "bn", name: "Bengali" },
+ { code: "zh", name: "Chinese (Simplified)" },
+ { code: "fr", name: "French" },
+ { code: "de", name: "German" },
+ { code: "hi", name: "Hindi" },
+ { code: "id", name: "Indonesian" },
+ { code: "it", name: "Italian" },
+ { code: "ja", name: "Japanese" },
+ { code: "ko", name: "Korean" },
+ { code: "pt-BR", name: "Portuguese (Brazil)" },
+ { code: "pt-PT", name: "Portuguese (Portugal)" },
+ { code: "ru", name: "Russian" },
+ { code: "es-MX", name: "Spanish (Mexico)" },
+ { code: "es-ES", name: "Spanish (Spain)" },
+ { code: "tr", name: "Turkish" },
+ { code: "vi", name: "Vietnamese" },
+ ];
+ const result = [];
+ for (const voice of voiceList) {
+ for (const lang of languageList) {
+ result.push({
+ name: voice.voiceId,
+ gender: voice.gender,
+ locale: lang.code,
+ displayName: `Grok TTS - ${voice.name} (${lang.name})`,
+ plugin: "grok-tts-voice-plugin",
+ config: {
+ ...config,
+ voiceId: voice.voiceId,
+ language: lang.code,
+ },
+ });
}
- return result;
+ }
+ return result;
};
module.exports = { getAudioPath, getTTSVoice };
diff --git a/src/utils/plugins/main/voice/mimoTTSVoice.js b/src/utils/plugins/main/voice/mimoTTSVoice.js
index ccb3ffc5..9aece60b 100644
--- a/src/utils/plugins/main/voice/mimoTTSVoice.js
+++ b/src/utils/plugins/main/voice/mimoTTSVoice.js
@@ -2,118 +2,129 @@ const path = require("path");
const fs = require("fs");
const axios = require("axios");
const getAudioPath = async (text, speed, dirPath, config) => {
- let audioName = new Date().getTime() + ".wav";
- if (!fs.existsSync(path.join(dirPath, "tts"))) {
- fs.mkdirSync(path.join(dirPath, "tts"));
- fs.writeFileSync(path.join(dirPath, "tts", audioName), await getTTSAudio(text, speed, config));
- }
- else {
- fs.writeFileSync(path.join(dirPath, "tts", audioName), await getTTSAudio(text, speed, config));
- }
- return path.join(dirPath, "tts", audioName);
+ let audioName = new Date().getTime() + ".wav";
+ if (!fs.existsSync(path.join(dirPath, "tts"))) {
+ fs.mkdirSync(path.join(dirPath, "tts"));
+ fs.writeFileSync(
+ path.join(dirPath, "tts", audioName),
+ await getTTSAudio(text, speed, config)
+ );
+ } else {
+ fs.writeFileSync(
+ path.join(dirPath, "tts", audioName),
+ await getTTSAudio(text, speed, config)
+ );
+ }
+ return path.join(dirPath, "tts", audioName);
};
const getTTSAudio = async (text, speed, config) => {
- let apiKey = config.apiKey || "";
- let voiceName = config.voiceName || "mimo_default";
- let url = "https://api.xiaomimimo.com/v1/chat/completions";
- return new Promise((resolve, reject) => {
- axios
- .post(url, {
- model: "mimo-v2.5-tts",
- messages: [
- {
- role: "assistant",
- content: text,
- },
- ],
- audio: {
- format: "mp3",
- voice: voiceName,
+ let apiKey = config.apiKey || "";
+ let voiceName = config.voiceName || "mimo_default";
+ let url = "https://api.xiaomimimo.com/v1/chat/completions";
+ return new Promise((resolve, reject) => {
+ axios
+ .post(
+ url,
+ {
+ model: "mimo-v2.5-tts",
+ messages: [
+ {
+ role: "assistant",
+ content: text,
},
- }, {
- headers: {
- "api-key": apiKey,
- "Content-Type": "application/json",
- },
- })
- .then((response) => {
- let audioData = response.data.choices[0].message.audio.data;
- let audioBuffer = Buffer.from(audioData, "base64");
- resolve(audioBuffer);
- })
- .catch((error) => {
- reject(error);
- });
- });
+ ],
+ audio: {
+ format: "mp3",
+ voice: voiceName,
+ },
+ },
+ {
+ headers: {
+ "api-key": apiKey,
+ "Content-Type": "application/json",
+ },
+ }
+ )
+ .then((response) => {
+ let audioData = response.data.choices[0].message.audio.data;
+ let audioBuffer = Buffer.from(audioData, "base64");
+ resolve(audioBuffer);
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
};
const getTTSVoice = async (config) => {
- const voices = [
- {
- name: "mimo_default",
- displayName: "MiMo Default",
- gender: "female",
- locale: "zh-CN",
- },
- {
- name: "冰糖",
- displayName: "冰糖 (Bingtang) - Female",
- gender: "female",
- locale: "zh-CN",
- },
- {
- name: "茉莉",
- displayName: "茉莉 (Moli) - Female",
- gender: "female",
- locale: "zh-CN",
- },
- {
- name: "苏打",
- displayName: "苏打 (Suda) - Male",
- gender: "male",
- locale: "zh-CN",
- },
- {
- name: "白桦",
- displayName: "白桦 (Baihua)- Male",
- gender: "male",
- locale: "zh-CN",
- },
- {
- name: "Mia",
- displayName: "Mia - Female",
- gender: "female",
- locale: "en-US",
- },
- {
- name: "Chloe",
- displayName: "Chloe - Female",
- gender: "female",
- locale: "en-US",
- },
- {
- name: "Milo",
- displayName: "Milo - Male",
- gender: "male",
- locale: "en-US",
- },
- {
- name: "Dean",
- displayName: "Dean - Male",
- gender: "male",
- locale: "en-US",
- },
- ];
- return Promise.resolve(voices.map((voice) => ({
- name: voice.name,
- gender: voice.gender,
- locale: voice.locale,
- displayName: `MiMo TTS - ${voice.displayName} (${voice.locale})`,
- plugin: "mimo-tts-voice-plugin",
- config: {
- ...config,
- voiceName: voice.name,
- },
- })));
+ const voices = [
+ {
+ name: "mimo_default",
+ displayName: "MiMo Default",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "冰糖",
+ displayName: "冰糖 (Bingtang) - Female",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "茉莉",
+ displayName: "茉莉 (Moli) - Female",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "苏打",
+ displayName: "苏打 (Suda) - Male",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "白桦",
+ displayName: "白桦 (Baihua)- Male",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "Mia",
+ displayName: "Mia - Female",
+ gender: "female",
+ locale: "en-US",
+ },
+ {
+ name: "Chloe",
+ displayName: "Chloe - Female",
+ gender: "female",
+ locale: "en-US",
+ },
+ {
+ name: "Milo",
+ displayName: "Milo - Male",
+ gender: "male",
+ locale: "en-US",
+ },
+ {
+ name: "Dean",
+ displayName: "Dean - Male",
+ gender: "male",
+ locale: "en-US",
+ },
+ ];
+ return Promise.resolve(
+ voices.map((voice) => ({
+ name: voice.name,
+ gender: voice.gender,
+ locale: voice.locale,
+ displayName: `MiMo TTS - ${voice.displayName} (${voice.locale})`,
+ plugin: "mimo-tts-voice-plugin",
+ config: {
+ ...config,
+ voiceName: voice.name,
+ },
+ }))
+ );
};
module.exports = { getAudioPath, getTTSVoice };
diff --git a/src/utils/plugins/main/voice/minimaxTTSVoice.js b/src/utils/plugins/main/voice/minimaxTTSVoice.js
index 64ab6e10..fab283cd 100644
--- a/src/utils/plugins/main/voice/minimaxTTSVoice.js
+++ b/src/utils/plugins/main/voice/minimaxTTSVoice.js
@@ -2,185 +2,185 @@ const path = require("path");
const fs = require("fs");
const axios = require("axios");
const getAudioPath = async (text, speed, dirPath, config) => {
- let audioName = new Date().getTime() + ".mp3";
- if (!fs.existsSync(path.join(dirPath, "tts"))) {
- fs.mkdirSync(path.join(dirPath, "tts"));
- fs.writeFileSync(path.join(dirPath, "tts", audioName), await getTTSAudio(text, speed, config));
- }
- else {
- fs.writeFileSync(path.join(dirPath, "tts", audioName), await getTTSAudio(text, speed, config));
- }
- return path.join(dirPath, "tts", audioName);
+ let audioName = new Date().getTime() + ".mp3";
+ if (!fs.existsSync(path.join(dirPath, "tts"))) {
+ fs.mkdirSync(path.join(dirPath, "tts"));
+ fs.writeFileSync(
+ path.join(dirPath, "tts", audioName),
+ await getTTSAudio(text, speed, config)
+ );
+ } else {
+ fs.writeFileSync(
+ path.join(dirPath, "tts", audioName),
+ await getTTSAudio(text, speed, config)
+ );
+ }
+ return path.join(dirPath, "tts", audioName);
};
const getTTSAudio = async (text, speed, config) => {
- let apiKey = config.apiKey || "";
- let model = config.model || "speech-02-hd";
- let voiceName = config.voiceName || "English_expressive_narrator";
- // speed range: 0.5 ~ 2.0, default 1.0
- let speedValue = speed ? Math.min(2.0, Math.max(0.5, speed)) : 1.0;
- let url = config.baseUrl + "/v1/t2a_v2";
- return new Promise((resolve, reject) => {
- axios
- .post(url, {
- model: model,
- text: text,
- stream: false,
- voice_setting: {
- voice_id: voiceName,
- speed: speedValue,
- vol: 1,
- pitch: 0,
- },
- audio_setting: {
- sample_rate: 32000,
- bitrate: 128000,
- format: "mp3",
- channel: 1,
- },
- output_format: "hex",
- }, {
- headers: {
- Authorization: `Bearer ${apiKey}`,
- "Content-Type": "application/json",
- },
- })
- .then((response) => {
- let hexAudio = response.data && response.data.data && response.data.data.audio;
- if (!hexAudio) {
- reject("No audio data in response");
- return;
- }
- resolve(Buffer.from(hexAudio, "hex"));
- })
- .catch((error) => {
- reject(error);
- });
- });
+ let apiKey = config.apiKey || "";
+ let model = config.model || "speech-02-hd";
+ let voiceName = config.voiceName || "English_expressive_narrator";
+ // speed range: 0.5 ~ 2.0, default 1.0
+ let speedValue = speed ? Math.min(2.0, Math.max(0.5, speed)) : 1.0;
+ let url = config.baseUrl + "/v1/t2a_v2";
+ return new Promise((resolve, reject) => {
+ axios
+ .post(
+ url,
+ {
+ model: model,
+ text: text,
+ stream: false,
+ voice_setting: {
+ voice_id: voiceName,
+ speed: speedValue,
+ vol: 1,
+ pitch: 0,
+ },
+ audio_setting: {
+ sample_rate: 32000,
+ bitrate: 128000,
+ format: "mp3",
+ channel: 1,
+ },
+ output_format: "hex",
+ },
+ {
+ headers: {
+ Authorization: `Bearer ${apiKey}`,
+ "Content-Type": "application/json",
+ },
+ }
+ )
+ .then((response) => {
+ let hexAudio =
+ response.data && response.data.data && response.data.data.audio;
+ if (!hexAudio) {
+ reject("No audio data in response");
+ return;
+ }
+ resolve(Buffer.from(hexAudio, "hex"));
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
};
const getLocaleFromVoiceId = (voiceId) => {
- if (voiceId.startsWith("English_") || voiceId.startsWith("English "))
- return "en-US";
- if (voiceId.startsWith("Chinese (Mandarin)") ||
- voiceId.startsWith("Mandarin_"))
- return "zh-CN";
- if (voiceId.startsWith("Cantonese"))
- return "zh-HK";
- if (voiceId.startsWith("Japanese_"))
- return "ja-JP";
- if (voiceId.startsWith("Korean_"))
- return "ko-KR";
- if (voiceId.startsWith("Spanish_"))
- return "es-ES";
- if (voiceId.startsWith("Portuguese_"))
- return "pt-BR";
- if (voiceId.startsWith("French_"))
- return "fr-FR";
- if (voiceId.startsWith("German_"))
- return "de-DE";
- if (voiceId.startsWith("Russian_"))
- return "ru-RU";
- if (voiceId.startsWith("Italian_"))
- return "it-IT";
- if (voiceId.startsWith("Dutch_"))
- return "nl-NL";
- if (voiceId.startsWith("Arabic_"))
- return "ar";
- if (voiceId.startsWith("Turkish_"))
- return "tr-TR";
- if (voiceId.startsWith("Ukrainian_"))
- return "uk-UA";
- if (voiceId.startsWith("Thai_"))
- return "th-TH";
- if (voiceId.startsWith("Polish_"))
- return "pl-PL";
- if (voiceId.startsWith("Romanian_"))
- return "ro-RO";
- if (voiceId.startsWith("Greek_") || voiceId.startsWith("greek_"))
- return "el-GR";
- if (voiceId.startsWith("Czech_") || voiceId.startsWith("czech_"))
- return "cs-CZ";
- if (voiceId.startsWith("Finnish_") || voiceId.startsWith("finnish_"))
- return "fi-FI";
- if (voiceId.startsWith("Hindi_") || voiceId.startsWith("hindi_"))
- return "hi-IN";
- if (voiceId.startsWith("Indonesian_"))
- return "id-ID";
- if (voiceId.startsWith("Vietnamese_"))
- return "vi-VN";
+ if (voiceId.startsWith("English_") || voiceId.startsWith("English "))
return "en-US";
+ if (
+ voiceId.startsWith("Chinese (Mandarin)") ||
+ voiceId.startsWith("Mandarin_")
+ )
+ return "zh-CN";
+ if (voiceId.startsWith("Cantonese")) return "zh-HK";
+ if (voiceId.startsWith("Japanese_")) return "ja-JP";
+ if (voiceId.startsWith("Korean_")) return "ko-KR";
+ if (voiceId.startsWith("Spanish_")) return "es-ES";
+ if (voiceId.startsWith("Portuguese_")) return "pt-BR";
+ if (voiceId.startsWith("French_")) return "fr-FR";
+ if (voiceId.startsWith("German_")) return "de-DE";
+ if (voiceId.startsWith("Russian_")) return "ru-RU";
+ if (voiceId.startsWith("Italian_")) return "it-IT";
+ if (voiceId.startsWith("Dutch_")) return "nl-NL";
+ if (voiceId.startsWith("Arabic_")) return "ar";
+ if (voiceId.startsWith("Turkish_")) return "tr-TR";
+ if (voiceId.startsWith("Ukrainian_")) return "uk-UA";
+ if (voiceId.startsWith("Thai_")) return "th-TH";
+ if (voiceId.startsWith("Polish_")) return "pl-PL";
+ if (voiceId.startsWith("Romanian_")) return "ro-RO";
+ if (voiceId.startsWith("Greek_") || voiceId.startsWith("greek_"))
+ return "el-GR";
+ if (voiceId.startsWith("Czech_") || voiceId.startsWith("czech_"))
+ return "cs-CZ";
+ if (voiceId.startsWith("Finnish_") || voiceId.startsWith("finnish_"))
+ return "fi-FI";
+ if (voiceId.startsWith("Hindi_") || voiceId.startsWith("hindi_"))
+ return "hi-IN";
+ if (voiceId.startsWith("Indonesian_")) return "id-ID";
+ if (voiceId.startsWith("Vietnamese_")) return "vi-VN";
+ return "en-US";
};
const getGenderFromVoiceId = (voiceId, voiceName) => {
- const combined = (voiceId + " " + (voiceName || "")).toLowerCase();
- const femaleKeywords = [
- "female",
- "woman",
- "girl",
- "lady",
- "queen",
- "princess",
- "heroine",
- "sister",
- "aunt",
- "wife",
- "girlfriend",
- "hostess",
- "narrator_f",
- ];
- const maleKeywords = [
- "male",
- "man",
- "boy",
- "gentleman",
- "king",
- "prince",
- "knight",
- "butler",
- "uncle",
- "husband",
- "narrator_m",
- ];
- if (femaleKeywords.some((k) => combined.includes(k)))
- return "female";
- if (maleKeywords.some((k) => combined.includes(k)))
- return "male";
- return "unknown";
+ const combined = (voiceId + " " + (voiceName || "")).toLowerCase();
+ const femaleKeywords = [
+ "female",
+ "woman",
+ "girl",
+ "lady",
+ "queen",
+ "princess",
+ "heroine",
+ "sister",
+ "aunt",
+ "wife",
+ "girlfriend",
+ "hostess",
+ "narrator_f",
+ ];
+ const maleKeywords = [
+ "male",
+ "man",
+ "boy",
+ "gentleman",
+ "king",
+ "prince",
+ "knight",
+ "butler",
+ "uncle",
+ "husband",
+ "narrator_m",
+ ];
+ if (femaleKeywords.some((k) => combined.includes(k))) return "female";
+ if (maleKeywords.some((k) => combined.includes(k))) return "male";
+ return "unknown";
};
const getTTSVoice = async (config) => {
- let apiKey = config.apiKey || "";
- let url = config.baseUrl + "/v1/get_voice";
- return new Promise((resolve, reject) => {
- axios
- .post(url, { voice_type: "all" }, {
- headers: {
- Authorization: `Bearer ${apiKey}`,
- "Content-Type": "application/json",
+ let apiKey = config.apiKey || "";
+ let url = config.baseUrl + "/v1/get_voice";
+ return new Promise((resolve, reject) => {
+ axios
+ .post(
+ url,
+ { voice_type: "all" },
+ {
+ headers: {
+ Authorization: `Bearer ${apiKey}`,
+ "Content-Type": "application/json",
+ },
+ }
+ )
+ .then((response) => {
+ let systemVoices = (response.data && response.data.system_voice) || [];
+ let cloningVoices =
+ (response.data && response.data.voice_cloning) || [];
+ let generationVoices =
+ (response.data && response.data.voice_generation) || [];
+ let allVoices = [
+ ...systemVoices,
+ ...cloningVoices,
+ ...generationVoices,
+ ];
+ resolve(
+ allVoices.map((voice) => ({
+ name: voice.voice_id,
+ gender: getGenderFromVoiceId(voice.voice_id, voice.voice_name),
+ locale: getLocaleFromVoiceId(voice.voice_id),
+ displayName: `MiniMax TTS - ${voice.voice_name || voice.voice_id}`,
+ plugin: "minimax-tts-voice-plugin",
+ config: {
+ ...config,
+ voiceName: voice.voice_id,
},
- })
- .then((response) => {
- let systemVoices = (response.data && response.data.system_voice) || [];
- let cloningVoices = (response.data && response.data.voice_cloning) || [];
- let generationVoices = (response.data && response.data.voice_generation) || [];
- let allVoices = [
- ...systemVoices,
- ...cloningVoices,
- ...generationVoices,
- ];
- resolve(allVoices.map((voice) => ({
- name: voice.voice_id,
- gender: getGenderFromVoiceId(voice.voice_id, voice.voice_name),
- locale: getLocaleFromVoiceId(voice.voice_id),
- displayName: `MiniMax TTS - ${voice.voice_name || voice.voice_id}`,
- plugin: "minimax-tts-voice-plugin",
- config: {
- ...config,
- voiceName: voice.voice_id,
- },
- })));
- })
- .catch((error) => {
- reject(error);
- });
- });
+ }))
+ );
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
};
module.exports = { getAudioPath, getTTSVoice };
diff --git a/src/utils/plugins/main/voice/multiTTSVoice.js b/src/utils/plugins/main/voice/multiTTSVoice.js
index 36877f7c..86bdae21 100644
--- a/src/utils/plugins/main/voice/multiTTSVoice.js
+++ b/src/utils/plugins/main/voice/multiTTSVoice.js
@@ -2,81 +2,94 @@ const path = require("path");
const fs = require("fs");
const axios = require("axios");
const getAudioPath = async (text, speed, dirPath, config) => {
- let audioName = new Date().getTime() + ".wav";
- if (!fs.existsSync(path.join(dirPath, "tts"))) {
- fs.mkdirSync(path.join(dirPath, "tts"));
- fs.writeFileSync(path.join(dirPath, "tts", audioName), await getTTSAudio(text, speed, config));
- }
- else {
- fs.writeFileSync(path.join(dirPath, "tts", audioName), await getTTSAudio(text, speed, config));
- }
- return path.join(dirPath, "tts", audioName);
+ let audioName = new Date().getTime() + ".wav";
+ if (!fs.existsSync(path.join(dirPath, "tts"))) {
+ fs.mkdirSync(path.join(dirPath, "tts"));
+ fs.writeFileSync(
+ path.join(dirPath, "tts", audioName),
+ await getTTSAudio(text, speed, config)
+ );
+ } else {
+ fs.writeFileSync(
+ path.join(dirPath, "tts", audioName),
+ await getTTSAudio(text, speed, config)
+ );
+ }
+ return path.join(dirPath, "tts", audioName);
};
const objectToQueryString = (obj) => {
- const queryParams = [];
- for (let key in obj) {
- if (obj.hasOwnProperty(key)) {
- const value = obj[key];
- const encodedKey = encodeURIComponent(key);
- const encodedValue = encodeURIComponent(value);
- queryParams.push(`${encodedKey}=${encodedValue}`);
- }
+ const queryParams = [];
+ for (let key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ const value = obj[key];
+ const encodedKey = encodeURIComponent(key);
+ const encodedValue = encodeURIComponent(value);
+ queryParams.push(`${encodedKey}=${encodedValue}`);
}
- return queryParams.join("&");
+ }
+ return queryParams.join("&");
};
const getTTSAudio = async (text, rate, config) => {
- let host = config.host || "127.0.0.1";
- let port = config.url || "8774";
- let api = "http://" + host + ":" + port + "/forward";
- let pitch = config.pitch || "50";
- let speed = rate * 50 || "50";
- let volume = config.volume || "50";
- let voice = config.voice || "";
- return new Promise((resolve, reject) => {
- axios.get(api + "?" + objectToQueryString({
+ let host = config.host || "127.0.0.1";
+ let port = config.url || "8774";
+ let api = "http://" + host + ":" + port + "/forward";
+ let pitch = config.pitch || "50";
+ let speed = rate * 50 || "50";
+ let volume = config.volume || "50";
+ let voice = config.voice || "";
+ return new Promise((resolve, reject) => {
+ axios
+ .get(
+ api +
+ "?" +
+ objectToQueryString({
volume,
pitch,
speed,
voice,
- text
- }), { responseType: "arraybuffer" })
- .then(response => {
- resolve(response.data);
- })
- .catch(error => {
- reject(error);
- });
- });
+ text,
+ }),
+ { responseType: "arraybuffer" }
+ )
+ .then((response) => {
+ resolve(response.data);
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
};
const getTTSVoice = async (config) => {
- let host = config.host || "127.0.0.1";
- let port = config.url || "8774";
- let api = "http://" + host + ":" + port + "/voices";
- return new Promise((resolve, reject) => {
- axios.get(api)
- .then(response => {
- let result = response.data;
- if (result.success) {
- let voices = Object.values(result.data.catalog).flat();
- resolve(voices.map(voice => {
- return {
- "name": voice.id,
- "gender": voice.gender,
- "locale": voice.locale,
- "displayName": voice.name,
- "plugin": "multitts-voice-plugin",
- "config": config
- };
- }));
- }
- else {
- resolve([]);
- }
- })
- .catch(error => {
- reject(error);
- });
- });
+ let host = config.host || "127.0.0.1";
+ let port = config.url || "8774";
+ let api = "http://" + host + ":" + port + "/voices";
+ return new Promise((resolve, reject) => {
+ axios
+ .get(api)
+ .then((response) => {
+ let result = response.data;
+ if (result.success) {
+ let voices = Object.values(result.data.catalog).flat();
+ resolve(
+ voices.map((voice) => {
+ return {
+ name: voice.id,
+ gender: voice.gender,
+ locale: voice.locale,
+ displayName: voice.name,
+ plugin: "multitts-voice-plugin",
+ config: config,
+ };
+ })
+ );
+ } else {
+ resolve([]);
+ }
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
};
module.exports = { getAudioPath, getTTSVoice };
diff --git a/src/utils/plugins/main/voice/openaiTTSVoice.js b/src/utils/plugins/main/voice/openaiTTSVoice.js
index 8605686c..a286478d 100644
--- a/src/utils/plugins/main/voice/openaiTTSVoice.js
+++ b/src/utils/plugins/main/voice/openaiTTSVoice.js
@@ -2,74 +2,85 @@ const path = require("path");
const fs = require("fs");
const axios = require("axios");
const getAudioPath = async (text, speed, dirPath, config) => {
- let audioName = new Date().getTime() + ".mp3";
- if (!fs.existsSync(path.join(dirPath, "tts"))) {
- fs.mkdirSync(path.join(dirPath, "tts"));
- fs.writeFileSync(path.join(dirPath, "tts", audioName), await getTTSAudio(text, speed, config));
- }
- else {
- fs.writeFileSync(path.join(dirPath, "tts", audioName), await getTTSAudio(text, speed, config));
- }
- return path.join(dirPath, "tts", audioName);
+ let audioName = new Date().getTime() + ".mp3";
+ if (!fs.existsSync(path.join(dirPath, "tts"))) {
+ fs.mkdirSync(path.join(dirPath, "tts"));
+ fs.writeFileSync(
+ path.join(dirPath, "tts", audioName),
+ await getTTSAudio(text, speed, config)
+ );
+ } else {
+ fs.writeFileSync(
+ path.join(dirPath, "tts", audioName),
+ await getTTSAudio(text, speed, config)
+ );
+ }
+ return path.join(dirPath, "tts", audioName);
};
const getTTSAudio = async (text, speed, config) => {
- let apiKey = config.apiKey || "";
- let baseUrl = config.baseUrl || "https://api.openai.com";
- let model = config.model || "gpt-4o-mini-tts";
- let voiceName = config.voiceName || "coral";
- // speed range: 0.25 ~ 4.0, default 1.0
- let speedValue = speed ? Math.min(4.0, Math.max(0.25, speed)) : 1.0;
- let url = `${baseUrl}/v1/audio/speech`;
- return new Promise((resolve, reject) => {
- axios
- .post(url, {
- model: model,
- input: text,
- voice: voiceName,
- speed: speedValue,
- response_format: "mp3",
- }, {
- headers: {
- Authorization: `Bearer ${apiKey}`,
- "Content-Type": "application/json",
- },
- responseType: "arraybuffer",
- })
- .then((response) => {
- resolve(response.data);
- })
- .catch((error) => {
- reject(error);
- });
- });
+ let apiKey = config.apiKey || "";
+ let baseUrl = config.baseUrl || "https://api.openai.com";
+ let model = config.model || "gpt-4o-mini-tts";
+ let voiceName = config.voiceName || "coral";
+ // speed range: 0.25 ~ 4.0, default 1.0
+ let speedValue = speed ? Math.min(4.0, Math.max(0.25, speed)) : 1.0;
+ let url = `${baseUrl}/v1/audio/speech`;
+ return new Promise((resolve, reject) => {
+ axios
+ .post(
+ url,
+ {
+ model: model,
+ input: text,
+ voice: voiceName,
+ speed: speedValue,
+ response_format: "mp3",
+ },
+ {
+ headers: {
+ Authorization: `Bearer ${apiKey}`,
+ "Content-Type": "application/json",
+ },
+ responseType: "arraybuffer",
+ }
+ )
+ .then((response) => {
+ resolve(response.data);
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
};
const getTTSVoice = async (config) => {
- const voices = [
- { name: "alloy", gender: "female" },
- { name: "ash", gender: "male" },
- { name: "ballad", gender: "male" },
- { name: "coral", gender: "female" },
- { name: "echo", gender: "male" },
- { name: "fable", gender: "male" },
- { name: "nova", gender: "female" },
- { name: "onyx", gender: "male" },
- { name: "sage", gender: "female" },
- { name: "shimmer", gender: "female" },
- { name: "verse", gender: "male" },
- { name: "marin", gender: "female" },
- { name: "cedar", gender: "male" },
- ];
- return Promise.resolve(voices.map((voice) => ({
- name: voice.name,
- gender: voice.gender,
- locale: "en",
- displayName: `OpenAI TTS - ${voice.name}`,
- plugin: "openai-tts-voice-plugin",
- config: {
- ...config,
- voiceName: voice.name,
- },
- })));
+ const voices = [
+ { name: "alloy", gender: "female" },
+ { name: "ash", gender: "male" },
+ { name: "ballad", gender: "male" },
+ { name: "coral", gender: "female" },
+ { name: "echo", gender: "male" },
+ { name: "fable", gender: "male" },
+ { name: "nova", gender: "female" },
+ { name: "onyx", gender: "male" },
+ { name: "sage", gender: "female" },
+ { name: "shimmer", gender: "female" },
+ { name: "verse", gender: "male" },
+ { name: "marin", gender: "female" },
+ { name: "cedar", gender: "male" },
+ ];
+ return Promise.resolve(
+ voices.map((voice) => ({
+ name: voice.name,
+ gender: voice.gender,
+ locale: "en",
+ displayName: `OpenAI TTS - ${voice.name}`,
+ plugin: "openai-tts-voice-plugin",
+ config: {
+ ...config,
+ voiceName: voice.name,
+ },
+ }))
+ );
};
module.exports = { getAudioPath, getTTSVoice };
diff --git a/src/utils/plugins/main/voice/qwenTTSVoice.js b/src/utils/plugins/main/voice/qwenTTSVoice.js
index c90b01b2..e77da757 100644
--- a/src/utils/plugins/main/voice/qwenTTSVoice.js
+++ b/src/utils/plugins/main/voice/qwenTTSVoice.js
@@ -2,212 +2,213 @@ const path = require("path");
const fs = require("fs");
const axios = require("axios");
const getAudioPath = async (text, speed, dirPath, config) => {
- let audioName = new Date().getTime() + ".wav";
- if (!fs.existsSync(path.join(dirPath, "tts"))) {
- fs.mkdirSync(path.join(dirPath, "tts"));
- }
- const audioData = await getTTSAudio(text, speed, config);
- fs.writeFileSync(path.join(dirPath, "tts", audioName), audioData);
- return path.join(dirPath, "tts", audioName);
+ let audioName = new Date().getTime() + ".wav";
+ if (!fs.existsSync(path.join(dirPath, "tts"))) {
+ fs.mkdirSync(path.join(dirPath, "tts"));
+ }
+ const audioData = await getTTSAudio(text, speed, config);
+ fs.writeFileSync(path.join(dirPath, "tts", audioName), audioData);
+ return path.join(dirPath, "tts", audioName);
};
const getTTSAudio = async (text, speed, config) => {
- let apiKey = config.apiKey || "";
- let model = config.model || "qwen3-tts-flash";
- let voice = config.voiceName || "Cherry";
- let baseUrl = config.baseUrl || "https://dashscope.aliyuncs.com/api/v1";
- // Build request body
- const body = {
- model: model,
- input: {
- text: text,
- voice: voice,
+ let apiKey = config.apiKey || "";
+ let model = config.model || "qwen3-tts-flash";
+ let voice = config.voiceName || "Cherry";
+ let baseUrl = config.baseUrl || "https://dashscope.aliyuncs.com/api/v1";
+ // Build request body
+ const body = {
+ model: model,
+ input: {
+ text: text,
+ voice: voice,
+ },
+ parameters: {
+ stream: false,
+ speech_rate: undefined,
+ },
+ };
+ // speed: 1.0 = normal; map to speech_rate (-500 ~ 500), 0 = normal
+ if (speed && speed !== 1.0) {
+ body.parameters.speech_rate = Math.round((speed - 1) * 500);
+ }
+ const url = `${baseUrl}/services/aigc/multimodal-generation/generation`;
+ return new Promise((resolve, reject) => {
+ axios
+ .post(url, body, {
+ headers: {
+ Authorization: `Bearer ${apiKey}`,
+ "Content-Type": "application/json",
+ "X-DashScope-DataInspection": "enable",
},
- parameters: {
- stream: false,
- speech_rate: undefined,
- },
- };
- // speed: 1.0 = normal; map to speech_rate (-500 ~ 500), 0 = normal
- if (speed && speed !== 1.0) {
- body.parameters.speech_rate = Math.round((speed - 1) * 500);
- }
- const url = `${baseUrl}/services/aigc/multimodal-generation/generation`;
- return new Promise((resolve, reject) => {
- axios
- .post(url, body, {
- headers: {
- Authorization: `Bearer ${apiKey}`,
- "Content-Type": "application/json",
- "X-DashScope-DataInspection": "enable",
- },
- })
- .then(async (response) => {
- const audioUrl = response.data &&
- response.data.output &&
- response.data.output.audio &&
- response.data.output.audio.url;
- if (!audioUrl) {
- reject("No audio URL in response");
- return;
- }
- // Download the audio file from the returned URL
- const audioResponse = await axios.get(audioUrl, {
- responseType: "arraybuffer",
- });
- resolve(Buffer.from(audioResponse.data));
- })
- .catch((error) => {
- reject(error);
+ })
+ .then(async (response) => {
+ const audioUrl =
+ response.data &&
+ response.data.output &&
+ response.data.output.audio &&
+ response.data.output.audio.url;
+ if (!audioUrl) {
+ reject("No audio URL in response");
+ return;
+ }
+ // Download the audio file from the returned URL
+ const audioResponse = await axios.get(audioUrl, {
+ responseType: "arraybuffer",
});
- });
+ resolve(Buffer.from(audioResponse.data));
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
};
const getTTSVoice = async (config) => {
- // Qwen TTS uses a fixed set of system voices; return a predefined list
- const voices = [
- {
- name: "Cherry",
- gender: "female",
- locale: "zh-CN",
- displayName: "芊悦 Cherry (zh-CN/en) - Female",
- },
- {
- name: "Serena",
- gender: "female",
- locale: "zh-CN",
- displayName: "苏瑶 Serena (zh-CN/en) - Female",
- },
- {
- name: "Ethan",
- gender: "male",
- locale: "zh-CN",
- displayName: "晨煦 Ethan (zh-CN/en) - Male",
- },
- {
- name: "Chelsie",
- gender: "female",
- locale: "zh-CN",
- displayName: "千雪 Chelsie (zh-CN/en) - Female",
- },
- {
- name: "Momo",
- gender: "female",
- locale: "zh-CN",
- displayName: "茉兔 Momo (zh-CN/en) - Female",
- },
- {
- name: "Vivian",
- gender: "female",
- locale: "zh-CN",
- displayName: "十三 Vivian (zh-CN/en) - Female",
- },
- {
- name: "Moon",
- gender: "male",
- locale: "zh-CN",
- displayName: "月白 Moon (zh-CN/en) - Male",
- },
- {
- name: "Maia",
- gender: "female",
- locale: "zh-CN",
- displayName: "四月 Maia (zh-CN/en) - Female",
- },
- {
- name: "Kai",
- gender: "male",
- locale: "zh-CN",
- displayName: "凯 Kai (zh-CN/en) - Male",
- },
- {
- name: "Nofish",
- gender: "male",
- locale: "zh-CN",
- displayName: "不吃鱼 Nofish (zh-CN/en) - Male",
- },
- {
- name: "Bella",
- gender: "female",
- locale: "zh-CN",
- displayName: "萌宝 Bella (zh-CN/en) - Female",
- },
- {
- name: "Jennifer",
- gender: "female",
- locale: "zh-CN",
- displayName: "詹妮弗 Jennifer (zh-CN/en) - Female",
- },
- {
- name: "Ryan",
- gender: "male",
- locale: "zh-CN",
- displayName: "甜茶 Ryan (zh-CN/en) - Male",
- },
- {
- name: "Katerina",
- gender: "female",
- locale: "zh-CN",
- displayName: "卡捷琳娜 Katerina (zh-CN/en) - Female",
- },
- {
- name: "Aiden",
- gender: "male",
- locale: "zh-CN",
- displayName: "艾登 Aiden (zh-CN/en) - Male",
- },
- {
- name: "Neil",
- gender: "male",
- locale: "zh-CN",
- displayName: "阿闻 Neil (zh-CN/en) - Male",
- },
- {
- name: "Elias",
- gender: "female",
- locale: "zh-CN",
- displayName: "墨讲师 Elias (zh-CN/en) - Female",
- },
- {
- name: "Sunny",
- gender: "female",
- locale: "zh-SC",
- displayName: "四川-晴儿 Sunny (zh-SC) - Female",
- },
- {
- name: "Dylan",
- gender: "male",
- locale: "zh-BJ",
- displayName: "北京-晓东 Dylan (zh-BJ) - Male",
- },
- {
- name: "Jada",
- gender: "female",
- locale: "zh-SH",
- displayName: "上海-阿珍 Jada (zh-SH) - Female",
- },
- {
- name: "Rocky",
- gender: "male",
- locale: "zh-YUE",
- displayName: "粤语-阿强 Rocky (zh-YUE) - Male",
- },
- {
- name: "Kiki",
- gender: "female",
- locale: "zh-YUE",
- displayName: "粤语-阿清 Kiki (zh-YUE) - Female",
- },
- ];
- return voices.map((v) => ({
- name: v.name,
- gender: v.gender,
- locale: v.locale,
- displayName: `Qwen TTS - ${v.displayName}`,
- plugin: "qwen-tts-voice-plugin",
- config: {
- ...config,
- voiceName: v.name,
- },
- }));
+ // Qwen TTS uses a fixed set of system voices; return a predefined list
+ const voices = [
+ {
+ name: "Cherry",
+ gender: "female",
+ locale: "zh-CN",
+ displayName: "芊悦 Cherry (zh-CN/en) - Female",
+ },
+ {
+ name: "Serena",
+ gender: "female",
+ locale: "zh-CN",
+ displayName: "苏瑶 Serena (zh-CN/en) - Female",
+ },
+ {
+ name: "Ethan",
+ gender: "male",
+ locale: "zh-CN",
+ displayName: "晨煦 Ethan (zh-CN/en) - Male",
+ },
+ {
+ name: "Chelsie",
+ gender: "female",
+ locale: "zh-CN",
+ displayName: "千雪 Chelsie (zh-CN/en) - Female",
+ },
+ {
+ name: "Momo",
+ gender: "female",
+ locale: "zh-CN",
+ displayName: "茉兔 Momo (zh-CN/en) - Female",
+ },
+ {
+ name: "Vivian",
+ gender: "female",
+ locale: "zh-CN",
+ displayName: "十三 Vivian (zh-CN/en) - Female",
+ },
+ {
+ name: "Moon",
+ gender: "male",
+ locale: "zh-CN",
+ displayName: "月白 Moon (zh-CN/en) - Male",
+ },
+ {
+ name: "Maia",
+ gender: "female",
+ locale: "zh-CN",
+ displayName: "四月 Maia (zh-CN/en) - Female",
+ },
+ {
+ name: "Kai",
+ gender: "male",
+ locale: "zh-CN",
+ displayName: "凯 Kai (zh-CN/en) - Male",
+ },
+ {
+ name: "Nofish",
+ gender: "male",
+ locale: "zh-CN",
+ displayName: "不吃鱼 Nofish (zh-CN/en) - Male",
+ },
+ {
+ name: "Bella",
+ gender: "female",
+ locale: "zh-CN",
+ displayName: "萌宝 Bella (zh-CN/en) - Female",
+ },
+ {
+ name: "Jennifer",
+ gender: "female",
+ locale: "zh-CN",
+ displayName: "詹妮弗 Jennifer (zh-CN/en) - Female",
+ },
+ {
+ name: "Ryan",
+ gender: "male",
+ locale: "zh-CN",
+ displayName: "甜茶 Ryan (zh-CN/en) - Male",
+ },
+ {
+ name: "Katerina",
+ gender: "female",
+ locale: "zh-CN",
+ displayName: "卡捷琳娜 Katerina (zh-CN/en) - Female",
+ },
+ {
+ name: "Aiden",
+ gender: "male",
+ locale: "zh-CN",
+ displayName: "艾登 Aiden (zh-CN/en) - Male",
+ },
+ {
+ name: "Neil",
+ gender: "male",
+ locale: "zh-CN",
+ displayName: "阿闻 Neil (zh-CN/en) - Male",
+ },
+ {
+ name: "Elias",
+ gender: "female",
+ locale: "zh-CN",
+ displayName: "墨讲师 Elias (zh-CN/en) - Female",
+ },
+ {
+ name: "Sunny",
+ gender: "female",
+ locale: "zh-SC",
+ displayName: "四川-晴儿 Sunny (zh-SC) - Female",
+ },
+ {
+ name: "Dylan",
+ gender: "male",
+ locale: "zh-BJ",
+ displayName: "北京-晓东 Dylan (zh-BJ) - Male",
+ },
+ {
+ name: "Jada",
+ gender: "female",
+ locale: "zh-SH",
+ displayName: "上海-阿珍 Jada (zh-SH) - Female",
+ },
+ {
+ name: "Rocky",
+ gender: "male",
+ locale: "zh-YUE",
+ displayName: "粤语-阿强 Rocky (zh-YUE) - Male",
+ },
+ {
+ name: "Kiki",
+ gender: "female",
+ locale: "zh-YUE",
+ displayName: "粤语-阿清 Kiki (zh-YUE) - Female",
+ },
+ ];
+ return voices.map((v) => ({
+ name: v.name,
+ gender: v.gender,
+ locale: v.locale,
+ displayName: `Qwen TTS - ${v.displayName}`,
+ plugin: "qwen-tts-voice-plugin",
+ config: {
+ ...config,
+ voiceName: v.name,
+ },
+ }));
};
module.exports = { getAudioPath, getTTSVoice };
diff --git a/src/utils/plugins/main/voice/ttsServerVoice.js b/src/utils/plugins/main/voice/ttsServerVoice.js
index 76a2bfa2..d5e45dbb 100644
--- a/src/utils/plugins/main/voice/ttsServerVoice.js
+++ b/src/utils/plugins/main/voice/ttsServerVoice.js
@@ -2,62 +2,73 @@ const path = require("path");
const fs = require("fs");
const axios = require("axios");
const getAudioPath = async (text, speed, dirPath, config) => {
- let audioName = new Date().getTime() + ".wav";
- if (!fs.existsSync(path.join(dirPath, "tts"))) {
- fs.mkdirSync(path.join(dirPath, "tts"));
- fs.writeFileSync(path.join(dirPath, "tts", audioName), await getTTSAudio(text, speed, config));
- }
- else {
- fs.writeFileSync(path.join(dirPath, "tts", audioName), await getTTSAudio(text, speed, config));
- }
- return path.join(dirPath, "tts", audioName);
+ let audioName = new Date().getTime() + ".wav";
+ if (!fs.existsSync(path.join(dirPath, "tts"))) {
+ fs.mkdirSync(path.join(dirPath, "tts"));
+ fs.writeFileSync(
+ path.join(dirPath, "tts", audioName),
+ await getTTSAudio(text, speed, config)
+ );
+ } else {
+ fs.writeFileSync(
+ path.join(dirPath, "tts", audioName),
+ await getTTSAudio(text, speed, config)
+ );
+ }
+ return path.join(dirPath, "tts", audioName);
};
const objectToQueryString = (obj) => {
- const queryParams = [];
- for (let key in obj) {
- if (obj.hasOwnProperty(key)) {
- const value = obj[key];
- const encodedKey = encodeURIComponent(key);
- const encodedValue = encodeURIComponent(value);
- queryParams.push(`${encodedKey}=${encodedValue}`);
- }
+ const queryParams = [];
+ for (let key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ const value = obj[key];
+ const encodedKey = encodeURIComponent(key);
+ const encodedValue = encodeURIComponent(value);
+ queryParams.push(`${encodedKey}=${encodedValue}`);
}
- return queryParams.join("&");
+ }
+ return queryParams.join("&");
};
const getTTSAudio = async (text, speed, config) => {
- let host = config.host || "127.0.0.1";
- let url = config.url || "";
- let params = parseUrlParams(url);
- let api = params.api || "http://localhost:1221/api/tts";
- let engine = params.engine || "com.github.jing332.tts_server_android";
- let pitch = params.pitch || "100";
- let rate = params.rate || "50";
- let voice = params.voice || "";
- api = api.replace("localhost", host);
- return new Promise((resolve, reject) => {
- axios.get(api + "?" + objectToQueryString({
+ let host = config.host || "127.0.0.1";
+ let url = config.url || "";
+ let params = parseUrlParams(url);
+ let api = params.api || "http://localhost:1221/api/tts";
+ let engine = params.engine || "com.github.jing332.tts_server_android";
+ let pitch = params.pitch || "100";
+ let rate = params.rate || "50";
+ let voice = params.voice || "";
+ api = api.replace("localhost", host);
+ return new Promise((resolve, reject) => {
+ axios
+ .get(
+ api +
+ "?" +
+ objectToQueryString({
engine,
pitch,
rate,
voice,
- text
- }), { responseType: "arraybuffer" })
- .then(response => {
- resolve(response.data);
- })
- .catch(error => {
- reject(error);
- });
- });
+ text,
+ }),
+ { responseType: "arraybuffer" }
+ )
+ .then((response) => {
+ resolve(response.data);
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
};
function parseUrlParams(url) {
- const urlObj = new URL(url);
- const params = new URLSearchParams(urlObj.search);
- const result = {};
- for (const [key, value] of params.entries()) {
- result[key] = decodeURIComponent(value); // 解码参数值
- }
- return result;
+ const urlObj = new URL(url);
+ const params = new URLSearchParams(urlObj.search);
+ const result = {};
+ for (const [key, value] of params.entries()) {
+ result[key] = decodeURIComponent(value); // 解码参数值
+ }
+ return result;
}
module.exports = { getAudioPath };
diff --git a/src/utils/plugins/main/voice/volcengineTTSVoice.js b/src/utils/plugins/main/voice/volcengineTTSVoice.js
index 28199f65..c7610aa4 100644
--- a/src/utils/plugins/main/voice/volcengineTTSVoice.js
+++ b/src/utils/plugins/main/voice/volcengineTTSVoice.js
@@ -2,385 +2,1786 @@ const path = require("path");
const fs = require("fs");
const axios = require("axios");
const getAudioPath = async (text, speed, dirPath, config) => {
- let audioName = new Date().getTime() + ".mp3";
- if (!fs.existsSync(path.join(dirPath, "tts"))) {
- fs.mkdirSync(path.join(dirPath, "tts"));
- }
- fs.writeFileSync(path.join(dirPath, "tts", audioName), await getTTSAudio(text, speed, config));
- return path.join(dirPath, "tts", audioName);
+ let audioName = new Date().getTime() + ".mp3";
+ if (!fs.existsSync(path.join(dirPath, "tts"))) {
+ fs.mkdirSync(path.join(dirPath, "tts"));
+ }
+ fs.writeFileSync(
+ path.join(dirPath, "tts", audioName),
+ await getTTSAudio(text, speed, config)
+ );
+ return path.join(dirPath, "tts", audioName);
};
const mapSpeedToSpeechRate = (speed) => {
- if (!speed || speed === 1.0)
- return 0;
- return Math.min(100, Math.max(-50, Math.round((speed - 1) * 100)));
+ if (!speed || speed === 1.0) return 0;
+ return Math.min(100, Math.max(-50, Math.round((speed - 1) * 100)));
};
const parseChunkedResponse = (responseText) => {
- const audioChunks = [];
- const lines = responseText.split("\n");
- for (const line of lines) {
- const trimmed = line.trim();
- if (!trimmed)
- continue;
- try {
- const obj = JSON.parse(trimmed);
- const code = obj.code || 0;
- if (code === 20000000)
- continue;
- if (code !== 0) {
- throw new Error(obj.message || `TTS error code ${code}`);
- }
- if (obj.data) {
- audioChunks.push(Buffer.from(obj.data, "base64"));
- }
- }
- catch (err) {
- if (err instanceof Error && err.message.startsWith("TTS error"))
- throw err;
- }
+ const audioChunks = [];
+ const lines = responseText.split("\n");
+ for (const line of lines) {
+ const trimmed = line.trim();
+ if (!trimmed) continue;
+ try {
+ const obj = JSON.parse(trimmed);
+ const code = obj.code || 0;
+ if (code === 20000000) continue;
+ if (code !== 0) {
+ throw new Error(obj.message || `TTS error code ${code}`);
+ }
+ if (obj.data) {
+ audioChunks.push(Buffer.from(obj.data, "base64"));
+ }
+ } catch (err) {
+ if (err instanceof Error && err.message.startsWith("TTS error"))
+ throw err;
}
- return Buffer.concat(audioChunks);
+ }
+ return Buffer.concat(audioChunks);
};
const getTTSAudio = async (text, speed, config) => {
- const apiKey = config.apiKey || "";
- if (!apiKey) {
- return Promise.reject("Missing API Key");
- }
- const voiceName = config.voiceName || "zh_female_vv_uranus_bigtts";
- const resourceId = config.resourceId || "seed-tts-2.0";
- const url = "https://openspeech.bytedance.com/api/v3/tts/unidirectional";
- const audioParams = {
- format: "mp3",
- sample_rate: 24000,
- };
- const speechRate = mapSpeedToSpeechRate(speed);
- if (speechRate !== 0) {
- audioParams.speech_rate = speechRate;
- }
- const payload = {
- req_params: {
- text: text,
- speaker: voiceName,
- audio_params: audioParams,
+ const apiKey = config.apiKey || "";
+ if (!apiKey) {
+ return Promise.reject("Missing API Key");
+ }
+ const voiceName = config.voiceName || "zh_female_vv_uranus_bigtts";
+ const resourceId = config.resourceId || "seed-tts-2.0";
+ const url = "https://openspeech.bytedance.com/api/v3/tts/unidirectional";
+ const audioParams = {
+ format: "mp3",
+ sample_rate: 24000,
+ };
+ const speechRate = mapSpeedToSpeechRate(speed);
+ if (speechRate !== 0) {
+ audioParams.speech_rate = speechRate;
+ }
+ const payload = {
+ req_params: {
+ text: text,
+ speaker: voiceName,
+ audio_params: audioParams,
+ },
+ };
+ return new Promise((resolve, reject) => {
+ axios
+ .post(url, payload, {
+ headers: {
+ "X-Api-Key": apiKey,
+ "Content-Type": "application/json",
+ "X-Api-Resource-Id": resourceId,
},
- };
- return new Promise((resolve, reject) => {
- axios
- .post(url, payload, {
- headers: {
- "X-Api-Key": apiKey,
- "Content-Type": "application/json",
- "X-Api-Resource-Id": resourceId,
- },
- responseType: "text",
- timeout: 60000,
- })
- .then((response) => {
- const audioBuffer = parseChunkedResponse(response.data);
- if (!audioBuffer.length) {
- reject("No audio data in response");
- return;
- }
- resolve(audioBuffer);
- })
- .catch((error) => {
- reject(error);
- });
- });
+ responseType: "text",
+ timeout: 60000,
+ })
+ .then((response) => {
+ const audioBuffer = parseChunkedResponse(response.data);
+ if (!audioBuffer.length) {
+ reject("No audio data in response");
+ return;
+ }
+ resolve(audioBuffer);
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
};
const VOICES_2_0 = [
- { name: "zh_female_vv_uranus_bigtts", displayName: "Vivi 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_xiaohe_uranus_bigtts", displayName: "小何 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_male_m191_uranus_bigtts", displayName: "云舟 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_male_taocheng_uranus_bigtts", displayName: "小天 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_male_liufei_uranus_bigtts", displayName: "刘飞 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_female_sophie_uranus_bigtts", displayName: "魅力苏菲 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_qingxinnvsheng_uranus_bigtts", displayName: "清新女声 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_cancan_uranus_bigtts", displayName: "知性灿灿 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_sajiaoxuemei_uranus_bigtts", displayName: "撒娇学妹 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_tianmeixiaoyuan_uranus_bigtts", displayName: "甜美小源 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_tianmeitaozi_uranus_bigtts", displayName: "甜美桃子 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_shuangkuaisisi_uranus_bigtts", displayName: "爽快思思 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_peiqi_uranus_bigtts", displayName: "佩奇猪 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_linjianvhai_uranus_bigtts", displayName: "邻家女孩 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_male_shaonianzixin_uranus_bigtts", displayName: "少年梓辛/ Brayan 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_male_sunwukong_uranus_bigtts", displayName: "猴哥 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_female_yingyujiaoxue_uranus_bigtts", displayName: "Tina老师 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_kefunvsheng_uranus_bigtts", displayName: "暖阳女声 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_xiaoxue_uranus_bigtts", displayName: "儿童绘本 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_male_dayi_uranus_bigtts", displayName: "大壹 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_female_mizai_uranus_bigtts", displayName: "黑猫侦探社 咪仔 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_jitangnv_uranus_bigtts", displayName: "鸡汤女 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_meilinvyou_uranus_bigtts", displayName: "魅力女友 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_liuchangnv_uranus_bigtts", displayName: "流畅女声 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_male_ruyayichen_uranus_bigtts", displayName: "儒雅逸辰 2.0", gender: "male", locale: "zh-CN" },
- { name: "en_male_tim_uranus_bigtts", displayName: "Tim", gender: "male", locale: "en-US" },
- { name: "en_female_dacey_uranus_bigtts", displayName: "Dacey", gender: "female", locale: "en-US" },
- { name: "en_female_stokie_uranus_bigtts", displayName: "Stokie", gender: "female", locale: "en-US" },
- { name: "zh_female_wenroumama_uranus_bigtts", displayName: "温柔妈妈 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_male_jieshuoxiaoming_uranus_bigtts", displayName: "解说小明 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_female_tvbnv_uranus_bigtts", displayName: "TVB女声 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_male_yizhipiannan_uranus_bigtts", displayName: "译制片男 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_female_qiaopinv_uranus_bigtts", displayName: "俏皮女声 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_zhishuaiyingzi_uranus_bigtts", displayName: "直率英子 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_male_linjiananhai_uranus_bigtts", displayName: "邻家男孩 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_male_silang_uranus_bigtts", displayName: "四郎 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_male_ruyaqingnian_uranus_bigtts", displayName: "儒雅青年 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_male_qingcang_uranus_bigtts", displayName: "擎苍 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_male_xionger_uranus_bigtts", displayName: "熊二 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_female_yingtaowanzi_uranus_bigtts", displayName: "樱桃丸子 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_male_wennuanahu_uranus_bigtts", displayName: "温暖阿虎/ Alvin 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_male_naiqimengwa_uranus_bigtts", displayName: "奶气萌娃 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_female_popo_uranus_bigtts", displayName: "婆婆 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_gaolengyujie_uranus_bigtts", displayName: "高冷御姐 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_male_aojiaobazong_uranus_bigtts", displayName: "傲娇霸总 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_male_lanyinmianbao_uranus_bigtts", displayName: "懒音绵宝 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_female_wenroushunv_uranus_bigtts", displayName: "温柔淑女 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_gufengshaoyu_uranus_bigtts", displayName: "古风少御 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_male_huolixiaoge_uranus_bigtts", displayName: "活力小哥 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_male_baqiqingshu_uranus_bigtts", displayName: "霸气青叔 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_male_xuanyijieshuo_uranus_bigtts", displayName: "悬疑解说 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_female_mengyatou_uranus_bigtts", displayName: "萌丫头/ Cutey 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_tiexinnvsheng_uranus_bigtts", displayName: "贴心女声/ Candy 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_jitangmei_uranus_bigtts", displayName: "鸡汤妹妹/", gender: "female", locale: "zh-CN" },
- { name: "zh_male_cixingjieshuonan_uranus_bigtts", displayName: "磁性解说男 声/Morgan 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_male_liangsangmengzai_uranus_bigtts", displayName: "亮嗓萌仔 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_female_kailangjiejie_uranus_bigtts", displayName: "开朗姐姐 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_male_gaolengchenwen_uranus_bigtts", displayName: "高冷沉稳 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_male_shenyeboke_uranus_bigtts", displayName: "深夜播客 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_male_lubanqihao_uranus_bigtts", displayName: "鲁班七号 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_female_jiaochuannv_uranus_bigtts", displayName: "娇喘女声 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_linxiao_uranus_bigtts", displayName: "邻萧 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_lingling_uranus_bigtts", displayName: "玲玲姐姐 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_chunribu_uranus_bigtts", displayName: "春日部姐姐 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_male_tangseng_uranus_bigtts", displayName: "唐僧 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_male_zhuangzhou_uranus_bigtts", displayName: "庄周 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_male_kailangdidi_uranus_bigtts", displayName: "开朗弟弟 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_male_zhubajie_uranus_bigtts", displayName: "猪八戒 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_female_ganmaodianyin_uranus_bigtts", displayName: "感冒电音姐 姐 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_chanmeinv_uranus_bigtts", displayName: "谄媚女声 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_nvleishen_uranus_bigtts", displayName: "女雷神 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_qinqienv_uranus_bigtts", displayName: "亲切女声 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_male_kuailexiaodong_uranus_bigtts", displayName: "快乐小东 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_male_kailangxuezhang_uranus_bigtts", displayName: "开朗学长 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_male_youyoujunzi_uranus_bigtts", displayName: "悠悠君子 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_female_wenjingmaomao_uranus_bigtts", displayName: "文静毛毛 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_zhixingnv_uranus_bigtts", displayName: "知性女声 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_male_qingshuangnanda_uranus_bigtts", displayName: "清爽男大 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_male_yuanboxiaoshu_uranus_bigtts", displayName: "渊博小叔 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_male_yangguangqingnian_uranus_bigtts", displayName: "阳光青年 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_female_qingchezizi_uranus_bigtts", displayName: "清澈梓梓 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_tianmeiyueyue_uranus_bigtts", displayName: "甜美悦悦 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_xinlingjitang_uranus_bigtts", displayName: "心灵鸡汤 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_male_wenrouxiaoge_uranus_bigtts", displayName: "温柔小哥 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_female_roumeinvyou_uranus_bigtts", displayName: "柔美女友 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_male_dongfanghaoran_uranus_bigtts", displayName: "东方浩然 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_female_wenrouxiaoya_uranus_bigtts", displayName: "温柔小雅 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_male_tiancaitongsheng_uranus_bigtts", displayName: "天才童声 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_female_wuzetian_uranus_bigtts", displayName: "武则天 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_female_gujie_uranus_bigtts", displayName: "顾姐 2.0", gender: "female", locale: "zh-CN" },
- { name: "zh_male_guanggaojieshuo_uranus_bigtts", displayName: "广告解说 2.0", gender: "male", locale: "zh-CN" },
- { name: "zh_female_shaoergushi_uranus_bigtts", displayName: "少儿故事 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_en_female_charlie_tob", displayName: "Charlie 2.0", gender: "female", locale: "en-US" },
- { name: "ICL_uranus_en_male_ethan_tob", displayName: "Ethan 2.0", gender: "male", locale: "en-US" },
- { name: "ICL_uranus_en_male_alastor_tob", displayName: "Alastor 2.0", gender: "male", locale: "en-US" },
- { name: "ICL_uranus_en_male_chucky_tob", displayName: "Chucky 2.0", gender: "male", locale: "en-US" },
- { name: "ICL_uranus_en_male_noah_tob", displayName: "Noah 2.0", gender: "male", locale: "en-US" },
- { name: "ICL_uranus_en_male_jigsaw_tob", displayName: "Jigsaw 2.0", gender: "male", locale: "en-US" },
- { name: "ICL_uranus_en_male_clown_man_tob", displayName: "Clown Man 2.0", gender: "male", locale: "en-US" },
- { name: "ICL_uranus_en_male_cartoon_chef_tob", displayName: "Cartoon Chef 2.0", gender: "male", locale: "en-US" },
- { name: "ICL_uranus_en_male_frosty_man_tob", displayName: "Frosty Man 2.0", gender: "male", locale: "en-US" },
- { name: "ICL_uranus_en_male_the_grinch_tob", displayName: "The Grinch 2.0", gender: "male", locale: "en-US" },
- { name: "ICL_uranus_en_male_kevin_mccallister_tob", displayName: "Kevin McCallister 2.0", gender: "male", locale: "en-US" },
- { name: "ICL_uranus_en_male_michael_tob", displayName: "Michael 2.0", gender: "male", locale: "en-US" },
- { name: "ICL_uranus_en_male_big_boogie_tob", displayName: "Big Boogie 2.0", gender: "male", locale: "en-US" },
- { name: "ICL_uranus_en_male_xavier_tob", displayName: "Xavier 2.0", gender: "male", locale: "en-US" },
- { name: "ICL_uranus_en_male_zayne_tob", displayName: "Zayne 2.0", gender: "male", locale: "en-US" },
- { name: "ICL_uranus_zh_female_aojiaonvyou_tob", displayName: "傲娇女友 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_aomanjiaosheng_tob", displayName: "傲慢娇声 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_xiemeinvwang_tob", displayName: "邪魅女王 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_bingjiaojiejie_tob", displayName: "病娇姐姐 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_bingjiaomengmei_tob", displayName: "病娇萌妹 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_bingruoshaonv_tob", displayName: "病弱少女 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_chengshuwenrou_tob", displayName: "成熟温柔 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_chengshujiejie_tob", displayName: "成熟姐姐 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_chunzhenshaonv_tob", displayName: "纯真少女 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_chunchenvsheng_tob", displayName: "纯澈女生 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_wumeikeren_tob", displayName: "妩媚可人 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_heainainai_tob", displayName: "乖巧可儿 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_huopodiaoman_tob", displayName: "活泼刁蛮 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_huoponvhai_tob", displayName: "活泼女孩 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_jiaohannvwang_tob", displayName: "娇憨女王 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_jiaoruoluoli_tob", displayName: "娇弱萝莉 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_jiaxiaozi_tob", displayName: "假小子 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_jinglingxiangdao_tob", displayName: "精灵向导 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_kaixinxiaohong_tob", displayName: "开朗婷婷 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_keainvsheng_tob", displayName: "可爱女生 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_lingdongxinxin_tob", displayName: "灵动欣欣 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_linjuayi_tob", displayName: "邻居阿姨 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_tianmeijiaoqiao_tob", displayName: "甜美娇俏 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_qinglenggaoya_tob", displayName: "清冷高雅 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_lixingyuanzi_tob", displayName: "理性圆子 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_xingganmeihuo_tob", displayName: "性感魅惑 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_nuanxinqianqian_tob", displayName: "暖心茜茜 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_nuanxinxuejie_tob", displayName: "暖心学姐 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_qingtianmeimei_tob", displayName: "清甜莓莓 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_qingtiantaotao_tob", displayName: "清甜桃桃 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_qingxixiaoxue_tob", displayName: "清晰小雪 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_qingxinshaonv_tob", displayName: "倾心少女 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_ruanmengtangtang_tob", displayName: "柔骨魂师 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_ruanmengtuanzi_tob", displayName: "软萌团子 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_tianmeihuopo_tob", displayName: "甜美活泼 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_tianmeixiaoju_tob", displayName: "甜美小橘 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_tianmeixiaoyu_tob", displayName: "甜美小雨 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_tiaopigongzhu_tob", displayName: "调皮公主 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_tiexinnvyou_tob", displayName: "贴心女友 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_wenrounvshen_tob", displayName: "温柔女神 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_wenrouwenya_tob", displayName: "温柔文雅 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_zhixinjiejie_tob", displayName: "知心姐姐 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_wumeiyujie_tob", displayName: "妩媚御姐 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_yuanqitianmei_tob", displayName: "元气甜妹 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_xiemeiyujie_tob", displayName: "邪魅御姐 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_xingganyujie_tob", displayName: "性感御姐 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_xiuliqianqian_tob", displayName: "秀丽倩倩 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_tiexinguimi_tob", displayName: "贴心闺蜜 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_tiexinmeimei_tob", displayName: "贴心妹妹 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_wenroubaiyueguang_tob", displayName: "温柔白月光 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_chuliannvyou_tob", displayName: "初恋女友 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_female_zhixingwenwan_tob", displayName: "知性温婉 2.0", gender: "female", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_aoqilingren_tob", displayName: "傲气凌人 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_anrenqinzhu_tob", displayName: "黯刃秦主 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_aojiaogongzi_tob", displayName: "傲娇公子 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_aojiaojingying_tob", displayName: "傲娇精英 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_aomanqingnian_tob", displayName: "傲慢青年 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_aomanshaoye_tob", displayName: "傲慢少爷 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_zhenbiandiyu_tob", displayName: "枕边低语 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_badaoshaoye_tob", displayName: "霸道少爷 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_badaozongcai_tob", displayName: "霸道总裁 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_bingjiaobailian_tob", displayName: "病娇白莲 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_bingjiaodidi_tob", displayName: "病娇弟弟 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_bingjiaogege_tob", displayName: "病娇哥哥 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_bingjiaonanyou_tob", displayName: "病娇男友 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_bingjiaoshaonian_tob", displayName: "病娇少年 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_bingruogongzi_tob", displayName: "病弱公子 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_bingruoshaonian_tob", displayName: "病弱少年 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_bujiqingnian_tob", displayName: "不羁青年 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_chunhoudiyin_tob", displayName: "醇厚低音 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_paoxiaoxiaoge_tob", displayName: "咆哮小哥 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_yangyang_tob", displayName: "炀炀 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_chanruoshaoye_tob", displayName: "孱弱少爷 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_chengshuzongcai_tob", displayName: "成熟总裁 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_chenwenmingzai_tob", displayName: "沉稳明仔 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_qingyisugan_tob", displayName: "清逸苏感 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_chunzhenxuedi_tob", displayName: "纯真学弟 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_cixingnansang_tob", displayName: "磁性男嗓 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_cujingnansheng_tob", displayName: "醋精男生 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_cujingnanyou_tob", displayName: "醋精男友 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_diyinchenyu_tob", displayName: "低音沉郁 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_fengfashaonian_tob", displayName: "风发少年 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_ruyagongzi_tob", displayName: "儒雅公子 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_fuheigongzi_tob", displayName: "腹黑公子 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_ganjingshaonian_tob", displayName: "干净少年 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_gaolengzongcai_tob", displayName: "高冷总裁 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_guaogongzi_tob", displayName: "孤傲公子 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_guiyishenmi_tob", displayName: "aogongzi_to b 诡异神秘 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_guzhibingjiao_tob", displayName: "固执病娇 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_hanhoudunshi_tob", displayName: "憨厚敦实 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_huoliqingnian_tob", displayName: "活力青年 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_huoponanyou_tob", displayName: "活泼男友 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_huoposhuanglang_tob", displayName: "活泼爽朗 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_huzishushu_tob", displayName: "胡子叔叔 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_jijiazhineng_tob", displayName: "机甲智能 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_jingyingqingnian_tob", displayName: "精英青年 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_junyigongzi_tob", displayName: "俊逸公子 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_kailangqingkuai_tob", displayName: "开朗轻快 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_kailangqingnian_tob", displayName: "开朗青年 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_lanyincaohunshi_tob", displayName: "蓝银草魂师 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_lengaozongcai_tob", displayName: "冷傲总裁 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_lengdanshuli_tob", displayName: "冷淡疏离 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_lengjungaozhi_tob", displayName: "冷峻高智 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_lengjunshangsi_tob", displayName: "冷峻上司 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_lengkugege_tob", displayName: "冷酷哥哥 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_lenglianxiongzhang_tob", displayName: "冷脸兄长 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_lenglianxueba_tob", displayName: "冷脸学霸 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_lengmonanyou_tob", displayName: "冷漠男友 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_lengmoxiongzhang_tob", displayName: "冷漠兄长 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_lingyunqingnian_tob", displayName: "凌云青年 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_qinglengjingui_tob", displayName: "清冷矜贵 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_lvchaxiaoge_tob", displayName: "绿茶小哥 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_mengdongqingnian_tob", displayName: "懵懂青年 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_menyoupingxiaoge_tob", displayName: "闷油瓶小哥 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_xiaozhangxiaoge_tob", displayName: "嚣张小哥 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_nianrennanyou_tob", displayName: "粘人男友 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_neiliancaijun_tob", displayName: "内敛才俊 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_nuanxintitie_tob", displayName: "暖心体贴 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_pianpiangongzi_tob", displayName: "翩翩公子 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_chenwenyouya_tob", displayName: "沉稳优雅 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_qingsexiaosheng_tob", displayName: "青涩小生 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_qingseqingnian_tob", displayName: "青涩青年 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_qingshuangshaonian_tob", displayName: "清爽少年 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_qingxinbobo_tob", displayName: "清新波波 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_qinqieqingnian_tob", displayName: "亲切青年 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_qinqiexiaozhuo_tob", displayName: "亲切小卓 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_qinglangwenrun_tob", displayName: "清朗温润 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_rexueshaonian_tob", displayName: "热血少年 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_ruyacaijun_tob", displayName: "儒雅才俊 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_ruyajunzi_tob", displayName: "儒雅君子 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_ruyazongcai_tob", displayName: "儒雅总裁 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_sajiaonansheng_tob", displayName: "撒娇男生 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_sajiaonanyou_tob", displayName: "撒娇男友 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_sajiaonianren_tob", displayName: "撒娇粘人 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_satuoqingnian_tob", displayName: "洒脱青年 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_shaonianjiangjun_tob", displayName: "少年将军 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_shenchenzongcai_tob", displayName: "深沉总裁 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_jilingxiaohuo_tob", displayName: "机灵小伙 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_shenmifashi_tob", displayName: "神秘法师 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_shuaizhenxiaohuo_tob", displayName: "率真小伙 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_shuanglangxiaoyang_tob", displayName: "爽朗小阳 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_dichenqianquan_tob", displayName: "低沉缱绻 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_siwenqingnian_tob", displayName: "斯文青年 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_tianxinanyou_tob", displayName: "甜系男友 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_tiexinnanyou_tob", displayName: "贴心男友 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_wenrounantongzhuo_tob", displayName: "温柔男同桌 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_wenrounanyou_tob", displayName: "温柔男友 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_wenrouxuezhang_tob", displayName: "温柔学长 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_wenrunxuezhe_tob", displayName: "温润学者 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_wenshunshaonian_tob", displayName: "温顺少年 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_guayanxiaoge_tob", displayName: "寡言小哥 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_xiaohouye_tob", displayName: "小侯爷 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_naiqixiaosheng_tob", displayName: "奶气小生 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_xiaosasuixing_tob", displayName: "潇洒随性 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_wenrouneilian_tob", displayName: "温柔内敛 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_xuebanantongzhuo_tob", displayName: "学霸男同桌 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_xuebatongzhuo_tob", displayName: "学霸同桌 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_yangguangyangyang_tob", displayName: "阳光洋洋 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_wennuanshaonian_tob", displayName: "温暖少年 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_yiqishaonian_tob", displayName: "意气少年 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_younidashu_tob", displayName: "油腻大叔 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_youmodaye_tob", displayName: "幽默大爷 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_youmoshushu_tob", displayName: "幽默叔叔 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_youroubangzhu_tob", displayName: "优柔帮主 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_yourougongzi_tob", displayName: "优柔公子 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_yuanqishaonian_tob", displayName: "元气少年 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_zhangjianjunzi_tob", displayName: "仗剑君子 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_zhangjianxiake_tob", displayName: "仗剑侠客 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_zhengzhiqingnian_tob", displayName: "正直青年 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_zhishuaiqingnian_tob", displayName: "直率青年 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_zhongerqingnian_tob", displayName: "中二青年 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_zifuqingnian_tob", displayName: "自负青年 2.0", gender: "male", locale: "zh-CN" },
- { name: "ICL_uranus_zh_male_zixinqingnian_tob", displayName: "自信青年 2.0", gender: "male", locale: "zh-CN" },
+ {
+ name: "zh_female_vv_uranus_bigtts",
+ displayName: "Vivi 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_xiaohe_uranus_bigtts",
+ displayName: "小何 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_m191_uranus_bigtts",
+ displayName: "云舟 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_taocheng_uranus_bigtts",
+ displayName: "小天 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_liufei_uranus_bigtts",
+ displayName: "刘飞 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_sophie_uranus_bigtts",
+ displayName: "魅力苏菲 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_qingxinnvsheng_uranus_bigtts",
+ displayName: "清新女声 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_cancan_uranus_bigtts",
+ displayName: "知性灿灿 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_sajiaoxuemei_uranus_bigtts",
+ displayName: "撒娇学妹 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_tianmeixiaoyuan_uranus_bigtts",
+ displayName: "甜美小源 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_tianmeitaozi_uranus_bigtts",
+ displayName: "甜美桃子 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_shuangkuaisisi_uranus_bigtts",
+ displayName: "爽快思思 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_peiqi_uranus_bigtts",
+ displayName: "佩奇猪 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_linjianvhai_uranus_bigtts",
+ displayName: "邻家女孩 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_shaonianzixin_uranus_bigtts",
+ displayName: "少年梓辛/ Brayan 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_sunwukong_uranus_bigtts",
+ displayName: "猴哥 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_yingyujiaoxue_uranus_bigtts",
+ displayName: "Tina老师 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_kefunvsheng_uranus_bigtts",
+ displayName: "暖阳女声 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_xiaoxue_uranus_bigtts",
+ displayName: "儿童绘本 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_dayi_uranus_bigtts",
+ displayName: "大壹 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_mizai_uranus_bigtts",
+ displayName: "黑猫侦探社 咪仔 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_jitangnv_uranus_bigtts",
+ displayName: "鸡汤女 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_meilinvyou_uranus_bigtts",
+ displayName: "魅力女友 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_liuchangnv_uranus_bigtts",
+ displayName: "流畅女声 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_ruyayichen_uranus_bigtts",
+ displayName: "儒雅逸辰 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "en_male_tim_uranus_bigtts",
+ displayName: "Tim",
+ gender: "male",
+ locale: "en-US",
+ },
+ {
+ name: "en_female_dacey_uranus_bigtts",
+ displayName: "Dacey",
+ gender: "female",
+ locale: "en-US",
+ },
+ {
+ name: "en_female_stokie_uranus_bigtts",
+ displayName: "Stokie",
+ gender: "female",
+ locale: "en-US",
+ },
+ {
+ name: "zh_female_wenroumama_uranus_bigtts",
+ displayName: "温柔妈妈 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_jieshuoxiaoming_uranus_bigtts",
+ displayName: "解说小明 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_tvbnv_uranus_bigtts",
+ displayName: "TVB女声 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_yizhipiannan_uranus_bigtts",
+ displayName: "译制片男 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_qiaopinv_uranus_bigtts",
+ displayName: "俏皮女声 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_zhishuaiyingzi_uranus_bigtts",
+ displayName: "直率英子 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_linjiananhai_uranus_bigtts",
+ displayName: "邻家男孩 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_silang_uranus_bigtts",
+ displayName: "四郎 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_ruyaqingnian_uranus_bigtts",
+ displayName: "儒雅青年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_qingcang_uranus_bigtts",
+ displayName: "擎苍 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_xionger_uranus_bigtts",
+ displayName: "熊二 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_yingtaowanzi_uranus_bigtts",
+ displayName: "樱桃丸子 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_wennuanahu_uranus_bigtts",
+ displayName: "温暖阿虎/ Alvin 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_naiqimengwa_uranus_bigtts",
+ displayName: "奶气萌娃 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_popo_uranus_bigtts",
+ displayName: "婆婆 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_gaolengyujie_uranus_bigtts",
+ displayName: "高冷御姐 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_aojiaobazong_uranus_bigtts",
+ displayName: "傲娇霸总 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_lanyinmianbao_uranus_bigtts",
+ displayName: "懒音绵宝 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_wenroushunv_uranus_bigtts",
+ displayName: "温柔淑女 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_gufengshaoyu_uranus_bigtts",
+ displayName: "古风少御 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_huolixiaoge_uranus_bigtts",
+ displayName: "活力小哥 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_baqiqingshu_uranus_bigtts",
+ displayName: "霸气青叔 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_xuanyijieshuo_uranus_bigtts",
+ displayName: "悬疑解说 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_mengyatou_uranus_bigtts",
+ displayName: "萌丫头/ Cutey 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_tiexinnvsheng_uranus_bigtts",
+ displayName: "贴心女声/ Candy 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_jitangmei_uranus_bigtts",
+ displayName: "鸡汤妹妹/",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_cixingjieshuonan_uranus_bigtts",
+ displayName: "磁性解说男 声/Morgan 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_liangsangmengzai_uranus_bigtts",
+ displayName: "亮嗓萌仔 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_kailangjiejie_uranus_bigtts",
+ displayName: "开朗姐姐 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_gaolengchenwen_uranus_bigtts",
+ displayName: "高冷沉稳 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_shenyeboke_uranus_bigtts",
+ displayName: "深夜播客 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_lubanqihao_uranus_bigtts",
+ displayName: "鲁班七号 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_jiaochuannv_uranus_bigtts",
+ displayName: "娇喘女声 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_linxiao_uranus_bigtts",
+ displayName: "邻萧 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_lingling_uranus_bigtts",
+ displayName: "玲玲姐姐 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_chunribu_uranus_bigtts",
+ displayName: "春日部姐姐 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_tangseng_uranus_bigtts",
+ displayName: "唐僧 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_zhuangzhou_uranus_bigtts",
+ displayName: "庄周 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_kailangdidi_uranus_bigtts",
+ displayName: "开朗弟弟 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_zhubajie_uranus_bigtts",
+ displayName: "猪八戒 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_ganmaodianyin_uranus_bigtts",
+ displayName: "感冒电音姐 姐 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_chanmeinv_uranus_bigtts",
+ displayName: "谄媚女声 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_nvleishen_uranus_bigtts",
+ displayName: "女雷神 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_qinqienv_uranus_bigtts",
+ displayName: "亲切女声 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_kuailexiaodong_uranus_bigtts",
+ displayName: "快乐小东 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_kailangxuezhang_uranus_bigtts",
+ displayName: "开朗学长 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_youyoujunzi_uranus_bigtts",
+ displayName: "悠悠君子 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_wenjingmaomao_uranus_bigtts",
+ displayName: "文静毛毛 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_zhixingnv_uranus_bigtts",
+ displayName: "知性女声 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_qingshuangnanda_uranus_bigtts",
+ displayName: "清爽男大 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_yuanboxiaoshu_uranus_bigtts",
+ displayName: "渊博小叔 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_yangguangqingnian_uranus_bigtts",
+ displayName: "阳光青年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_qingchezizi_uranus_bigtts",
+ displayName: "清澈梓梓 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_tianmeiyueyue_uranus_bigtts",
+ displayName: "甜美悦悦 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_xinlingjitang_uranus_bigtts",
+ displayName: "心灵鸡汤 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_wenrouxiaoge_uranus_bigtts",
+ displayName: "温柔小哥 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_roumeinvyou_uranus_bigtts",
+ displayName: "柔美女友 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_dongfanghaoran_uranus_bigtts",
+ displayName: "东方浩然 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_wenrouxiaoya_uranus_bigtts",
+ displayName: "温柔小雅 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_tiancaitongsheng_uranus_bigtts",
+ displayName: "天才童声 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_wuzetian_uranus_bigtts",
+ displayName: "武则天 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_gujie_uranus_bigtts",
+ displayName: "顾姐 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_male_guanggaojieshuo_uranus_bigtts",
+ displayName: "广告解说 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "zh_female_shaoergushi_uranus_bigtts",
+ displayName: "少儿故事 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_en_female_charlie_tob",
+ displayName: "Charlie 2.0",
+ gender: "female",
+ locale: "en-US",
+ },
+ {
+ name: "ICL_uranus_en_male_ethan_tob",
+ displayName: "Ethan 2.0",
+ gender: "male",
+ locale: "en-US",
+ },
+ {
+ name: "ICL_uranus_en_male_alastor_tob",
+ displayName: "Alastor 2.0",
+ gender: "male",
+ locale: "en-US",
+ },
+ {
+ name: "ICL_uranus_en_male_chucky_tob",
+ displayName: "Chucky 2.0",
+ gender: "male",
+ locale: "en-US",
+ },
+ {
+ name: "ICL_uranus_en_male_noah_tob",
+ displayName: "Noah 2.0",
+ gender: "male",
+ locale: "en-US",
+ },
+ {
+ name: "ICL_uranus_en_male_jigsaw_tob",
+ displayName: "Jigsaw 2.0",
+ gender: "male",
+ locale: "en-US",
+ },
+ {
+ name: "ICL_uranus_en_male_clown_man_tob",
+ displayName: "Clown Man 2.0",
+ gender: "male",
+ locale: "en-US",
+ },
+ {
+ name: "ICL_uranus_en_male_cartoon_chef_tob",
+ displayName: "Cartoon Chef 2.0",
+ gender: "male",
+ locale: "en-US",
+ },
+ {
+ name: "ICL_uranus_en_male_frosty_man_tob",
+ displayName: "Frosty Man 2.0",
+ gender: "male",
+ locale: "en-US",
+ },
+ {
+ name: "ICL_uranus_en_male_the_grinch_tob",
+ displayName: "The Grinch 2.0",
+ gender: "male",
+ locale: "en-US",
+ },
+ {
+ name: "ICL_uranus_en_male_kevin_mccallister_tob",
+ displayName: "Kevin McCallister 2.0",
+ gender: "male",
+ locale: "en-US",
+ },
+ {
+ name: "ICL_uranus_en_male_michael_tob",
+ displayName: "Michael 2.0",
+ gender: "male",
+ locale: "en-US",
+ },
+ {
+ name: "ICL_uranus_en_male_big_boogie_tob",
+ displayName: "Big Boogie 2.0",
+ gender: "male",
+ locale: "en-US",
+ },
+ {
+ name: "ICL_uranus_en_male_xavier_tob",
+ displayName: "Xavier 2.0",
+ gender: "male",
+ locale: "en-US",
+ },
+ {
+ name: "ICL_uranus_en_male_zayne_tob",
+ displayName: "Zayne 2.0",
+ gender: "male",
+ locale: "en-US",
+ },
+ {
+ name: "ICL_uranus_zh_female_aojiaonvyou_tob",
+ displayName: "傲娇女友 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_aomanjiaosheng_tob",
+ displayName: "傲慢娇声 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_xiemeinvwang_tob",
+ displayName: "邪魅女王 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_bingjiaojiejie_tob",
+ displayName: "病娇姐姐 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_bingjiaomengmei_tob",
+ displayName: "病娇萌妹 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_bingruoshaonv_tob",
+ displayName: "病弱少女 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_chengshuwenrou_tob",
+ displayName: "成熟温柔 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_chengshujiejie_tob",
+ displayName: "成熟姐姐 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_chunzhenshaonv_tob",
+ displayName: "纯真少女 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_chunchenvsheng_tob",
+ displayName: "纯澈女生 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_wumeikeren_tob",
+ displayName: "妩媚可人 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_heainainai_tob",
+ displayName: "乖巧可儿 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_huopodiaoman_tob",
+ displayName: "活泼刁蛮 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_huoponvhai_tob",
+ displayName: "活泼女孩 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_jiaohannvwang_tob",
+ displayName: "娇憨女王 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_jiaoruoluoli_tob",
+ displayName: "娇弱萝莉 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_jiaxiaozi_tob",
+ displayName: "假小子 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_jinglingxiangdao_tob",
+ displayName: "精灵向导 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_kaixinxiaohong_tob",
+ displayName: "开朗婷婷 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_keainvsheng_tob",
+ displayName: "可爱女生 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_lingdongxinxin_tob",
+ displayName: "灵动欣欣 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_linjuayi_tob",
+ displayName: "邻居阿姨 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_tianmeijiaoqiao_tob",
+ displayName: "甜美娇俏 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_qinglenggaoya_tob",
+ displayName: "清冷高雅 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_lixingyuanzi_tob",
+ displayName: "理性圆子 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_xingganmeihuo_tob",
+ displayName: "性感魅惑 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_nuanxinqianqian_tob",
+ displayName: "暖心茜茜 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_nuanxinxuejie_tob",
+ displayName: "暖心学姐 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_qingtianmeimei_tob",
+ displayName: "清甜莓莓 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_qingtiantaotao_tob",
+ displayName: "清甜桃桃 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_qingxixiaoxue_tob",
+ displayName: "清晰小雪 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_qingxinshaonv_tob",
+ displayName: "倾心少女 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_ruanmengtangtang_tob",
+ displayName: "柔骨魂师 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_ruanmengtuanzi_tob",
+ displayName: "软萌团子 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_tianmeihuopo_tob",
+ displayName: "甜美活泼 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_tianmeixiaoju_tob",
+ displayName: "甜美小橘 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_tianmeixiaoyu_tob",
+ displayName: "甜美小雨 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_tiaopigongzhu_tob",
+ displayName: "调皮公主 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_tiexinnvyou_tob",
+ displayName: "贴心女友 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_wenrounvshen_tob",
+ displayName: "温柔女神 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_wenrouwenya_tob",
+ displayName: "温柔文雅 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_zhixinjiejie_tob",
+ displayName: "知心姐姐 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_wumeiyujie_tob",
+ displayName: "妩媚御姐 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_yuanqitianmei_tob",
+ displayName: "元气甜妹 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_xiemeiyujie_tob",
+ displayName: "邪魅御姐 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_xingganyujie_tob",
+ displayName: "性感御姐 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_xiuliqianqian_tob",
+ displayName: "秀丽倩倩 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_tiexinguimi_tob",
+ displayName: "贴心闺蜜 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_tiexinmeimei_tob",
+ displayName: "贴心妹妹 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_wenroubaiyueguang_tob",
+ displayName: "温柔白月光 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_chuliannvyou_tob",
+ displayName: "初恋女友 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_female_zhixingwenwan_tob",
+ displayName: "知性温婉 2.0",
+ gender: "female",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_aoqilingren_tob",
+ displayName: "傲气凌人 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_anrenqinzhu_tob",
+ displayName: "黯刃秦主 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_aojiaogongzi_tob",
+ displayName: "傲娇公子 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_aojiaojingying_tob",
+ displayName: "傲娇精英 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_aomanqingnian_tob",
+ displayName: "傲慢青年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_aomanshaoye_tob",
+ displayName: "傲慢少爷 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_zhenbiandiyu_tob",
+ displayName: "枕边低语 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_badaoshaoye_tob",
+ displayName: "霸道少爷 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_badaozongcai_tob",
+ displayName: "霸道总裁 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_bingjiaobailian_tob",
+ displayName: "病娇白莲 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_bingjiaodidi_tob",
+ displayName: "病娇弟弟 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_bingjiaogege_tob",
+ displayName: "病娇哥哥 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_bingjiaonanyou_tob",
+ displayName: "病娇男友 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_bingjiaoshaonian_tob",
+ displayName: "病娇少年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_bingruogongzi_tob",
+ displayName: "病弱公子 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_bingruoshaonian_tob",
+ displayName: "病弱少年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_bujiqingnian_tob",
+ displayName: "不羁青年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_chunhoudiyin_tob",
+ displayName: "醇厚低音 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_paoxiaoxiaoge_tob",
+ displayName: "咆哮小哥 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_yangyang_tob",
+ displayName: "炀炀 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_chanruoshaoye_tob",
+ displayName: "孱弱少爷 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_chengshuzongcai_tob",
+ displayName: "成熟总裁 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_chenwenmingzai_tob",
+ displayName: "沉稳明仔 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_qingyisugan_tob",
+ displayName: "清逸苏感 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_chunzhenxuedi_tob",
+ displayName: "纯真学弟 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_cixingnansang_tob",
+ displayName: "磁性男嗓 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_cujingnansheng_tob",
+ displayName: "醋精男生 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_cujingnanyou_tob",
+ displayName: "醋精男友 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_diyinchenyu_tob",
+ displayName: "低音沉郁 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_fengfashaonian_tob",
+ displayName: "风发少年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_ruyagongzi_tob",
+ displayName: "儒雅公子 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_fuheigongzi_tob",
+ displayName: "腹黑公子 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_ganjingshaonian_tob",
+ displayName: "干净少年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_gaolengzongcai_tob",
+ displayName: "高冷总裁 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_guaogongzi_tob",
+ displayName: "孤傲公子 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_guiyishenmi_tob",
+ displayName: "aogongzi_to b 诡异神秘 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_guzhibingjiao_tob",
+ displayName: "固执病娇 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_hanhoudunshi_tob",
+ displayName: "憨厚敦实 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_huoliqingnian_tob",
+ displayName: "活力青年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_huoponanyou_tob",
+ displayName: "活泼男友 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_huoposhuanglang_tob",
+ displayName: "活泼爽朗 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_huzishushu_tob",
+ displayName: "胡子叔叔 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_jijiazhineng_tob",
+ displayName: "机甲智能 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_jingyingqingnian_tob",
+ displayName: "精英青年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_junyigongzi_tob",
+ displayName: "俊逸公子 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_kailangqingkuai_tob",
+ displayName: "开朗轻快 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_kailangqingnian_tob",
+ displayName: "开朗青年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_lanyincaohunshi_tob",
+ displayName: "蓝银草魂师 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_lengaozongcai_tob",
+ displayName: "冷傲总裁 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_lengdanshuli_tob",
+ displayName: "冷淡疏离 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_lengjungaozhi_tob",
+ displayName: "冷峻高智 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_lengjunshangsi_tob",
+ displayName: "冷峻上司 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_lengkugege_tob",
+ displayName: "冷酷哥哥 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_lenglianxiongzhang_tob",
+ displayName: "冷脸兄长 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_lenglianxueba_tob",
+ displayName: "冷脸学霸 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_lengmonanyou_tob",
+ displayName: "冷漠男友 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_lengmoxiongzhang_tob",
+ displayName: "冷漠兄长 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_lingyunqingnian_tob",
+ displayName: "凌云青年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_qinglengjingui_tob",
+ displayName: "清冷矜贵 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_lvchaxiaoge_tob",
+ displayName: "绿茶小哥 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_mengdongqingnian_tob",
+ displayName: "懵懂青年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_menyoupingxiaoge_tob",
+ displayName: "闷油瓶小哥 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_xiaozhangxiaoge_tob",
+ displayName: "嚣张小哥 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_nianrennanyou_tob",
+ displayName: "粘人男友 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_neiliancaijun_tob",
+ displayName: "内敛才俊 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_nuanxintitie_tob",
+ displayName: "暖心体贴 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_pianpiangongzi_tob",
+ displayName: "翩翩公子 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_chenwenyouya_tob",
+ displayName: "沉稳优雅 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_qingsexiaosheng_tob",
+ displayName: "青涩小生 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_qingseqingnian_tob",
+ displayName: "青涩青年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_qingshuangshaonian_tob",
+ displayName: "清爽少年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_qingxinbobo_tob",
+ displayName: "清新波波 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_qinqieqingnian_tob",
+ displayName: "亲切青年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_qinqiexiaozhuo_tob",
+ displayName: "亲切小卓 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_qinglangwenrun_tob",
+ displayName: "清朗温润 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_rexueshaonian_tob",
+ displayName: "热血少年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_ruyacaijun_tob",
+ displayName: "儒雅才俊 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_ruyajunzi_tob",
+ displayName: "儒雅君子 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_ruyazongcai_tob",
+ displayName: "儒雅总裁 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_sajiaonansheng_tob",
+ displayName: "撒娇男生 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_sajiaonanyou_tob",
+ displayName: "撒娇男友 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_sajiaonianren_tob",
+ displayName: "撒娇粘人 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_satuoqingnian_tob",
+ displayName: "洒脱青年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_shaonianjiangjun_tob",
+ displayName: "少年将军 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_shenchenzongcai_tob",
+ displayName: "深沉总裁 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_jilingxiaohuo_tob",
+ displayName: "机灵小伙 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_shenmifashi_tob",
+ displayName: "神秘法师 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_shuaizhenxiaohuo_tob",
+ displayName: "率真小伙 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_shuanglangxiaoyang_tob",
+ displayName: "爽朗小阳 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_dichenqianquan_tob",
+ displayName: "低沉缱绻 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_siwenqingnian_tob",
+ displayName: "斯文青年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_tianxinanyou_tob",
+ displayName: "甜系男友 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_tiexinnanyou_tob",
+ displayName: "贴心男友 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_wenrounantongzhuo_tob",
+ displayName: "温柔男同桌 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_wenrounanyou_tob",
+ displayName: "温柔男友 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_wenrouxuezhang_tob",
+ displayName: "温柔学长 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_wenrunxuezhe_tob",
+ displayName: "温润学者 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_wenshunshaonian_tob",
+ displayName: "温顺少年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_guayanxiaoge_tob",
+ displayName: "寡言小哥 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_xiaohouye_tob",
+ displayName: "小侯爷 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_naiqixiaosheng_tob",
+ displayName: "奶气小生 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_xiaosasuixing_tob",
+ displayName: "潇洒随性 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_wenrouneilian_tob",
+ displayName: "温柔内敛 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_xuebanantongzhuo_tob",
+ displayName: "学霸男同桌 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_xuebatongzhuo_tob",
+ displayName: "学霸同桌 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_yangguangyangyang_tob",
+ displayName: "阳光洋洋 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_wennuanshaonian_tob",
+ displayName: "温暖少年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_yiqishaonian_tob",
+ displayName: "意气少年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_younidashu_tob",
+ displayName: "油腻大叔 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_youmodaye_tob",
+ displayName: "幽默大爷 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_youmoshushu_tob",
+ displayName: "幽默叔叔 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_youroubangzhu_tob",
+ displayName: "优柔帮主 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_yourougongzi_tob",
+ displayName: "优柔公子 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_yuanqishaonian_tob",
+ displayName: "元气少年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_zhangjianjunzi_tob",
+ displayName: "仗剑君子 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_zhangjianxiake_tob",
+ displayName: "仗剑侠客 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_zhengzhiqingnian_tob",
+ displayName: "正直青年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_zhishuaiqingnian_tob",
+ displayName: "直率青年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_zhongerqingnian_tob",
+ displayName: "中二青年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_zifuqingnian_tob",
+ displayName: "自负青年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
+ {
+ name: "ICL_uranus_zh_male_zixinqingnian_tob",
+ displayName: "自信青年 2.0",
+ gender: "male",
+ locale: "zh-CN",
+ },
];
const getTTSVoice = async (config) => {
- return Promise.resolve(VOICES_2_0.map((voice) => ({
- name: voice.name,
- gender: voice.gender,
- locale: voice.locale,
- displayName: `Volcengine TTS - ${voice.displayName} (${voice.locale})`,
- plugin: "volcengine-tts-voice-plugin",
- config: {
- ...config,
- voiceName: voice.name,
- },
- })));
+ return Promise.resolve(
+ VOICES_2_0.map((voice) => ({
+ name: voice.name,
+ gender: voice.gender,
+ locale: voice.locale,
+ displayName: `Volcengine TTS - ${voice.displayName} (${voice.locale})`,
+ plugin: "volcengine-tts-voice-plugin",
+ config: {
+ ...config,
+ voiceName: voice.name,
+ },
+ }))
+ );
};
module.exports = { getAudioPath, getTTSVoice };
diff --git a/src/utils/plugins/main/voice/zhipuTTSVoice.js b/src/utils/plugins/main/voice/zhipuTTSVoice.js
index cb2d6046..3c56a614 100644
--- a/src/utils/plugins/main/voice/zhipuTTSVoice.js
+++ b/src/utils/plugins/main/voice/zhipuTTSVoice.js
@@ -2,98 +2,98 @@ const path = require("path");
const fs = require("fs");
const axios = require("axios");
const getAudioPath = async (text, speed, dirPath, config) => {
- let audioName = new Date().getTime() + ".wav";
- if (!fs.existsSync(path.join(dirPath, "tts"))) {
- fs.mkdirSync(path.join(dirPath, "tts"));
- }
- const audioData = await getTTSAudio(text, speed, config);
- fs.writeFileSync(path.join(dirPath, "tts", audioName), audioData);
- return path.join(dirPath, "tts", audioName);
+ let audioName = new Date().getTime() + ".wav";
+ if (!fs.existsSync(path.join(dirPath, "tts"))) {
+ fs.mkdirSync(path.join(dirPath, "tts"));
+ }
+ const audioData = await getTTSAudio(text, speed, config);
+ fs.writeFileSync(path.join(dirPath, "tts", audioName), audioData);
+ return path.join(dirPath, "tts", audioName);
};
const getTTSAudio = async (text, speed, config) => {
- let apiKey = config.apiKey || "";
- let voiceName = config.voiceName || "tongtong";
- const body = {
- model: "glm-tts",
- input: text,
- voice: voiceName,
- response_format: "wav",
- speed: speed || 1.0,
- };
- const url = "https://open.bigmodel.cn/api/paas/v4/audio/speech";
- return new Promise((resolve, reject) => {
- axios
- .post(url, body, {
- headers: {
- Authorization: `Bearer ${apiKey}`,
- "Content-Type": "application/json",
- },
- responseType: "arraybuffer",
- })
- .then((response) => {
- resolve(Buffer.from(response.data));
- })
- .catch((error) => {
- reject(error);
- });
- });
+ let apiKey = config.apiKey || "";
+ let voiceName = config.voiceName || "tongtong";
+ const body = {
+ model: "glm-tts",
+ input: text,
+ voice: voiceName,
+ response_format: "wav",
+ speed: speed || 1.0,
+ };
+ const url = "https://open.bigmodel.cn/api/paas/v4/audio/speech";
+ return new Promise((resolve, reject) => {
+ axios
+ .post(url, body, {
+ headers: {
+ Authorization: `Bearer ${apiKey}`,
+ "Content-Type": "application/json",
+ },
+ responseType: "arraybuffer",
+ })
+ .then((response) => {
+ resolve(Buffer.from(response.data));
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
};
const getTTSVoice = async (config) => {
- const voices = [
- {
- name: "tongtong",
- gender: "female",
- locale: "zh-CN",
- displayName: "彤彤 tongtong (zh-CN) - Female",
- },
- {
- name: "chuichui",
- gender: "male",
- locale: "zh-CN",
- displayName: "锤锤 chuichui (zh-CN) - Male",
- },
- {
- name: "xiaochen",
- gender: "male",
- locale: "zh-CN",
- displayName: "小陈 xiaochen (zh-CN) - Male",
- },
- {
- name: "jam",
- gender: "male",
- locale: "zh-CN",
- displayName: "jam (zh-CN) - Male",
- },
- {
- name: "kazi",
- gender: "female",
- locale: "zh-CN",
- displayName: "kazi (zh-CN) - Female",
- },
- {
- name: "douji",
- gender: "male",
- locale: "zh-CN",
- displayName: "douji (zh-CN) - Male",
- },
- {
- name: "luodo",
- gender: "male",
- locale: "zh-CN",
- displayName: "luodo (zh-CN) - Male",
- },
- ];
- return voices.map((voice) => ({
- name: voice.name,
- gender: voice.gender,
- locale: voice.locale,
- displayName: `Zhipu TTS - ${voice.displayName}`,
- plugin: "zhipu-tts-voice-plugin",
- config: {
- ...config,
- voiceName: voice.name,
- },
- }));
+ const voices = [
+ {
+ name: "tongtong",
+ gender: "female",
+ locale: "zh-CN",
+ displayName: "彤彤 tongtong (zh-CN) - Female",
+ },
+ {
+ name: "chuichui",
+ gender: "male",
+ locale: "zh-CN",
+ displayName: "锤锤 chuichui (zh-CN) - Male",
+ },
+ {
+ name: "xiaochen",
+ gender: "male",
+ locale: "zh-CN",
+ displayName: "小陈 xiaochen (zh-CN) - Male",
+ },
+ {
+ name: "jam",
+ gender: "male",
+ locale: "zh-CN",
+ displayName: "jam (zh-CN) - Male",
+ },
+ {
+ name: "kazi",
+ gender: "female",
+ locale: "zh-CN",
+ displayName: "kazi (zh-CN) - Female",
+ },
+ {
+ name: "douji",
+ gender: "male",
+ locale: "zh-CN",
+ displayName: "douji (zh-CN) - Male",
+ },
+ {
+ name: "luodo",
+ gender: "male",
+ locale: "zh-CN",
+ displayName: "luodo (zh-CN) - Male",
+ },
+ ];
+ return voices.map((voice) => ({
+ name: voice.name,
+ gender: voice.gender,
+ locale: voice.locale,
+ displayName: `Zhipu TTS - ${voice.displayName}`,
+ plugin: "zhipu-tts-voice-plugin",
+ config: {
+ ...config,
+ voiceName: voice.name,
+ },
+ }));
};
module.exports = { getAudioPath, getTTSVoice };