Files
tardis-bot/src/tts/azure.ts
2026-05-14 20:06:15 +02:00

78 lines
2.5 KiB
TypeScript

import * as sdk from "microsoft-cognitiveservices-speech-sdk";
import { config } from "../config.js";
import { BaseEngine, type VoiceParams } from "./BaseEngine.js";
interface AzureVoiceMeta {
DisplayName: string;
ShortName: string;
Name: string;
}
export class AzureEngine extends BaseEngine {
protected override voices: Record<string, string> = {};
constructor() {
super("azure", "Microsoft Azure TTS", "wav");
void this.populateVoiceList();
}
override getDefaultVoice(): string {
return "aria";
}
override getSpeechFile(
text: string,
filepath: string,
voice: string = this.getDefaultVoice(),
_params: VoiceParams = {},
): Promise<string> {
return new Promise((resolve, reject) => {
if (!config.AZURE_API_KEY || !config.AZURE_REGION) {
reject(new Error("AZURE_API_KEY and AZURE_REGION must be set"));
return;
}
const speechConfig = sdk.SpeechConfig.fromSubscription(
config.AZURE_API_KEY,
config.AZURE_REGION,
);
speechConfig.speechSynthesisOutputFormat = sdk.SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm;
const internal = this.voices[voice] ?? this.voices[this.getDefaultVoice()];
if (internal) speechConfig.speechSynthesisVoiceName = internal;
const audioConfig = sdk.AudioConfig.fromAudioFileOutput(filepath);
const synthesizer = new sdk.SpeechSynthesizer(speechConfig, audioConfig);
synthesizer.speakTextAsync(
text,
(result) => {
synthesizer.close();
if (result) resolve(filepath);
else reject(new Error("Azure TTS returned no result"));
},
(error: unknown) => {
synthesizer.close();
reject(error instanceof Error ? error : new Error(String(error)));
},
);
});
}
private async populateVoiceList(): Promise<void> {
if (!config.AZURE_LIST_ENDPOINT || !config.AZURE_API_KEY) return;
try {
const res = await fetch(config.AZURE_LIST_ENDPOINT, {
headers: { "Ocp-Apim-Subscription-Key": config.AZURE_API_KEY },
});
const json = (await res.json()) as AzureVoiceMeta[];
for (const voice of json) {
const key = voice.DisplayName.toLowerCase();
if (this.voices[key]) {
if (voice.Name.includes("Neural")) this.voices[key] = voice.ShortName;
} else {
this.voices[key] = voice.ShortName;
}
}
} catch (err) {
console.error("Azure: failed to populate voice list:", err);
}
}
}