1
This commit is contained in:
@@ -274,6 +274,80 @@ async function fetchJson(url, options = {}) {
|
||||
return data;
|
||||
}
|
||||
|
||||
async function fetchArrayBuffer(url, options = {}) {
|
||||
const res = await fetch(url, options);
|
||||
if (!res.ok) {
|
||||
const text = await res.text().catch(() => "");
|
||||
throw new Error(`HTTP ${res.status}: ${text.slice(0, 300)}`);
|
||||
}
|
||||
return Buffer.from(await res.arrayBuffer());
|
||||
}
|
||||
|
||||
const QWEN_TTS_URL =
|
||||
"https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation";
|
||||
|
||||
/** 对齐 Electron Ka:根据 voiceId 选择 TTS 模型 */
|
||||
function resolveTtsModel(voiceId) {
|
||||
const v = String(voiceId || "").trim();
|
||||
if (!v || v === "Cherry") return "qwen3-tts-flash";
|
||||
if (v.startsWith("qwen-tts-vc-")) return "qwen3-tts-vc-2026-01-22";
|
||||
return "qwen3-tts-flash";
|
||||
}
|
||||
|
||||
/**
|
||||
* 通义千问 TTS 合成(对齐 Electron synthesizeOneSegment)
|
||||
* @param {{ apiKey: string, text: string, voice: string, model?: string, instruction?: string, outputPath: string }} opts
|
||||
*/
|
||||
async function qwenTtsSynthesize(opts) {
|
||||
const apiKey = opts.apiKey;
|
||||
const text = String(opts.text || "").trim();
|
||||
const voice = String(opts.voice || "").trim();
|
||||
const outputPath = opts.outputPath;
|
||||
const model = opts.model || resolveTtsModel(voice);
|
||||
const instruction = String(opts.instruction || "").trim();
|
||||
|
||||
if (!apiKey) return { success: false, error: "缺少 DashScope apiKey" };
|
||||
if (!text) return { success: false, error: "缺少合成文本" };
|
||||
if (!voice) return { success: false, error: "缺少 voice" };
|
||||
if (!outputPath) return { success: false, error: "缺少 outputPath" };
|
||||
|
||||
try {
|
||||
const data = await fetchJson(QWEN_TTS_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model,
|
||||
input: {
|
||||
text,
|
||||
voice,
|
||||
...(instruction
|
||||
? { instructions: instruction, optimize_instructions: true }
|
||||
: {}),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const audioUrl = data?.output?.audio?.url;
|
||||
if (!audioUrl) {
|
||||
return {
|
||||
success: false,
|
||||
error: data?.message || data?.code || "未获取到音频 URL",
|
||||
};
|
||||
}
|
||||
|
||||
const buf = await fetchArrayBuffer(audioUrl);
|
||||
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
||||
fs.writeFileSync(outputPath, buf);
|
||||
log("info", "qwen tts saved", { outputPath, voice, model });
|
||||
return { success: true, audioPath: outputPath };
|
||||
} catch (e) {
|
||||
return { success: false, error: e.message || String(e) };
|
||||
}
|
||||
}
|
||||
|
||||
async function aliyunAsrFiletrans(audioUrl, opts) {
|
||||
const apiKey = opts.apiKey || opts.api_key;
|
||||
const model = opts.model || "qwen3-asr-flash-filetrans";
|
||||
@@ -454,4 +528,6 @@ module.exports = {
|
||||
aliyunAsrFiletrans,
|
||||
localAsr,
|
||||
openaiChat,
|
||||
resolveTtsModel,
|
||||
qwenTtsSynthesize,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user