feat: add (untested) polly module

This commit is contained in:
2026-01-14 20:38:58 -03:00
parent c5e6395b89
commit a401fdab15
2 changed files with 106 additions and 1 deletions
+100
View File
@@ -0,0 +1,100 @@
import {
PollyClient,
DescribeVoicesCommand,
Voice,
SynthesizeSpeechCommand,
Engine
} from '@aws-sdk/client-polly';
import { TTSModule, TTSResponse } from '../tts';
import { config } from '../../utils/config';
const ENGINE_PRIORITY: Engine[] = [
'generative',
'neural',
'standard',
'long-form'
];
class PollyTTS implements TTSModule {
private client: PollyClient | undefined = undefined;
private voices: Array<Voice> | undefined = undefined;
public name: string = 'AWS Polly';
constructor() {
if (!config.aws_access_id || !config.aws_access_key) return;
this.client = new PollyClient({
credentials: {
accessKeyId: config.aws_access_id,
secretAccessKey: config.aws_access_key
}
});
}
async getVoices(): Promise<Array<string> | undefined> {
if (!this.client) return [];
if (!this.voices) {
const cmd = new DescribeVoicesCommand({});
try {
const res = await this.client.send(cmd);
if (res.Voices) this.voices = res.Voices;
} catch (err) {
console.error('AWS Polly getVoices error:', err);
}
}
if (this.voices)
return this.voices.map((voice) => `${voice.LanguageCode} ${voice.Id}`);
}
async generate(voice: string, text: string): Promise<TTSResponse> {
if (!this.client || !this.voices) return { data: Buffer.from([]) };
voice = voice.split(' ').slice(1).join(' ');
const voiceData = this.voices.find((voiceDesc) => voiceDesc.Name == voice);
if (!voiceData) return {};
const bestEngine = this.getBestEngine(voiceData);
if (!bestEngine) return {};
const cmd = new SynthesizeSpeechCommand({
Engine: bestEngine,
LanguageCode: voiceData.LanguageCode,
OutputFormat: 'mp3',
Text: text,
VoiceId: voiceData.Id
});
try {
const res = await this.client.send(cmd);
if (!res.AudioStream) return {};
const buffer = Buffer.from(await res.AudioStream.transformToByteArray());
return { data: buffer };
} catch (err) {
console.error('AWS Polly gen error:', err);
}
return {};
}
async canBeUsed(): Promise<boolean> {
if (!config.aws_access_id || !config.aws_access_key)
return false;
return true;
}
private getBestEngine(voice: Voice): Engine | null {
if (!voice.SupportedEngines || voice.SupportedEngines.length === 0) {
return null;
}
const supportedSet = new Set(voice.SupportedEngines);
return ENGINE_PRIORITY.find((engine) => supportedSet.has(engine)) || null;
}
}
export default new PollyTTS();