This commit is contained in:
fengchuanhn@gmail.com
2026-05-18 10:18:51 +08:00
parent 35944cee39
commit 1b800653ef
2 changed files with 150 additions and 0 deletions

View File

@@ -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,
};