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 = {}; 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 { 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 { 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); } } }