Compare commits

...

4 Commits

Author SHA1 Message Date
neru c1d355993e chore: add polly 2026-01-14 20:39:25 -03:00
neru a401fdab15 feat: add (untested) polly module 2026-01-14 20:38:58 -03:00
neru c5e6395b89 feat: add canBeUsed to TTSModule 2026-01-14 20:38:10 -03:00
neru 0c394bdcbe style: change fn def type 2026-01-14 20:37:33 -03:00
7 changed files with 1388 additions and 3 deletions
+1270
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -34,6 +34,7 @@
"typescript-eslint": "^8.52.0" "typescript-eslint": "^8.52.0"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-polly": "^3.968.0",
"@discordjs/opus": "^0.10.0", "@discordjs/opus": "^0.10.0",
"@discordjs/voice": "^0.19.0", "@discordjs/voice": "^0.19.0",
"@snazzah/davey": "^0.1.9", "@snazzah/davey": "^0.1.9",
+4
View File
@@ -32,6 +32,10 @@ class AzureTTS implements TTSModule {
return { data: Buffer.concat(buffers) }; return { data: Buffer.concat(buffers) };
} }
async canBeUsed(): Promise<boolean> {
return true;
}
} }
export default new AzureTTS(); export default new AzureTTS();
+6 -2
View File
@@ -9,9 +9,9 @@ const USER_AGENT =
const ttsGoogle: TTSModule = { const ttsGoogle: TTSModule = {
name: 'Google', name: 'Google',
getVoices: async (): Promise<string[]> => GOOGLE_TTS_VOICES.voices, async getVoices(): Promise<string[]> { return GOOGLE_TTS_VOICES.voices },
generate: async (voice: string, text: string): Promise<TTSResponse> => { async generate(voice: string, text: string): Promise<TTSResponse> {
const query = new URLSearchParams({ const query = new URLSearchParams({
ie: 'UTF-8', ie: 'UTF-8',
q: text, q: text,
@@ -41,6 +41,10 @@ const ttsGoogle: TTSModule = {
resolve({ error: 'timed out' }); resolve({ error: 'timed out' });
}); });
}); });
},
async canBeUsed(): Promise<boolean> {
return true;
} }
}; };
+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();
+1
View File
@@ -13,6 +13,7 @@ export interface TTSModule {
name: string; name: string;
getVoices: () => Promise<Array<string> | undefined>; getVoices: () => Promise<Array<string> | undefined>;
generate: (voice: string, text: string) => Promise<TTSResponse>; generate: (voice: string, text: string) => Promise<TTSResponse>;
canBeUsed: () => Promise<boolean>;
} }
export class TTSManager { export class TTSManager {
+6 -1
View File
@@ -10,6 +10,9 @@ export interface Config {
tts_elevenlabs_key: string | undefined; tts_elevenlabs_key: string | undefined;
steam_webapi_key: string | undefined; steam_webapi_key: string | undefined;
aws_access_id: string | undefined;
aws_access_key: string | undefined;
} }
function loadConfig(): Config { function loadConfig(): Config {
@@ -28,7 +31,9 @@ function loadConfig(): Config {
tts_default_voice: process.env.DEFAULT_TTS_VOICE, tts_default_voice: process.env.DEFAULT_TTS_VOICE,
tts_azure_key: process.env.TTS_AZURE_KEY, tts_azure_key: process.env.TTS_AZURE_KEY,
tts_elevenlabs_key: process.env.TTS_ELEVENLABS_KEY, tts_elevenlabs_key: process.env.TTS_ELEVENLABS_KEY,
steam_webapi_key: process.env.STEAM_WEBAPI_KEY steam_webapi_key: process.env.STEAM_WEBAPI_KEY,
aws_access_id: process.env.AWS_ACCESS_ID,
aws_access_key: process.env.AWS_ACCESS_KEY
}; };
} }