mirror of
https://github.com/koodo-reader/koodo-reader.git
synced 2026-10-05 20:37:15 -04:00
Refactor zhipuTTSVoice.js for improved readability and consistency in formatting
This commit is contained in:
1 parent
4e8f65ad30
commit
d1d44e475a
14 files changed
+3036
-1527
No files matched your search
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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
@@ -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 };
|
||||
Reference in new issue
Block a user