Refactor zhipuTTSVoice.js for improved readability and consistency in formatting

This commit is contained in:
troyeguo committed 2026-09-23 09:49:03 +08:00
1 parent 4e8f65ad30
commit d1d44e475a
14 files changed
+3036 -1527

No files matched your search

+193 -169
View File
@@ -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: `<speak><prosody rate="${rate}">${text}</prosody></speak>`,
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: `<speak><prosody rate="${rate}">${text}</prosody></speak>`,
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 };
+33 -30
View File
@@ -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 };
+39 -33
View File
@@ -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 };
+44 -33
View File
@@ -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 };
@@ -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 };
+89 -84
View File
@@ -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 };
+118 -107
View File
@@ -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 };
+169 -169
View File
@@ -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 };
+77 -64
View File
@@ -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 };
+75 -64
View File
@@ -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 };
+201 -200
View File
@@ -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 };
+56 -45
View File
@@ -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 };
File diff suppressed because it is too large. Load diff
+88 -88
View File
@@ -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 };