增加语音对话功能
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import { joinPath } from "../../platform/paths";
|
||||
import {
|
||||
defaultVoiceForLang,
|
||||
removeIfExists,
|
||||
resolveExecutable,
|
||||
writeTempTextFile,
|
||||
} from "../../platform/executable";
|
||||
import { runCommand } from "../../platform/subprocess";
|
||||
import type { TtsProvider, TtsSynthesizeOptions, TtsResult } from "../types";
|
||||
import { TtsError } from "../types";
|
||||
import { getPref } from "../../../../../utils/prefs";
|
||||
|
||||
async function estimateWavDurationMs(path: string): Promise<number> {
|
||||
try {
|
||||
const stat = await IOUtils.stat(path);
|
||||
return Math.max(1000, Math.floor((stat.size / 32000) * 1000));
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function createWindowsSapiProvider(): TtsProvider {
|
||||
return {
|
||||
id: "system-win",
|
||||
label: "Windows SAPI",
|
||||
kind: "local",
|
||||
supportsTimestamps: false,
|
||||
async synthesize(options: TtsSynthesizeOptions): Promise<TtsResult> {
|
||||
const powershell = await resolveExecutable("powershell");
|
||||
const voice =
|
||||
options.voice ||
|
||||
(getPref("ttsVoice") as string) ||
|
||||
defaultVoiceForLang(options.lang);
|
||||
|
||||
const outWav = options.outputPath.endsWith(".wav")
|
||||
? options.outputPath
|
||||
: options.outputPath.replace(/\.\w+$/, ".wav");
|
||||
|
||||
const textPath = await writeTempTextFile(options.text, "sapi-text");
|
||||
const scriptPath = joinPath(
|
||||
PathUtils.tempDir,
|
||||
`chatpapers-sapi-${Date.now()}.ps1`,
|
||||
);
|
||||
|
||||
const psScript = [
|
||||
"Add-Type -AssemblyName System.Speech",
|
||||
"$text = Get-Content -LiteralPath $args[0] -Raw -Encoding UTF8",
|
||||
"$out = $args[1]",
|
||||
"$voice = $args[2]",
|
||||
"$synth = New-Object System.Speech.Synthesis.SpeechSynthesizer",
|
||||
"if ($voice) { try { $synth.SelectVoice($voice) } catch {} }",
|
||||
"$synth.SetOutputToWaveFile($out)",
|
||||
"$synth.Speak($text)",
|
||||
"$synth.Dispose()",
|
||||
].join("\n");
|
||||
|
||||
await IOUtils.writeUTF8(scriptPath, psScript);
|
||||
|
||||
try {
|
||||
const result = await runCommand(powershell, [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
scriptPath,
|
||||
textPath,
|
||||
outWav,
|
||||
voice,
|
||||
]);
|
||||
|
||||
if (result.exitCode !== 0) {
|
||||
throw new TtsError(
|
||||
`PowerShell SAPI 退出码 ${result.exitCode}`,
|
||||
"runtime",
|
||||
);
|
||||
}
|
||||
if (!(await IOUtils.exists(outWav))) {
|
||||
throw new TtsError("SAPI 未生成音频文件", "runtime");
|
||||
}
|
||||
|
||||
return {
|
||||
audioPath: outWav,
|
||||
timestamps: [],
|
||||
durationMs: await estimateWavDurationMs(outWav),
|
||||
format: "wav",
|
||||
};
|
||||
} finally {
|
||||
await removeIfExists(textPath);
|
||||
await removeIfExists(scriptPath);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user