@loonylabs/tts-middleware 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +201 -0
  3. package/dist/index.d.ts +22 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +39 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/middleware/services/tts/index.d.ts +30 -0
  8. package/dist/middleware/services/tts/index.d.ts.map +1 -0
  9. package/dist/middleware/services/tts/index.js +69 -0
  10. package/dist/middleware/services/tts/index.js.map +1 -0
  11. package/dist/middleware/services/tts/providers/azure-provider.d.ts +131 -0
  12. package/dist/middleware/services/tts/providers/azure-provider.d.ts.map +1 -0
  13. package/dist/middleware/services/tts/providers/azure-provider.js +375 -0
  14. package/dist/middleware/services/tts/providers/azure-provider.js.map +1 -0
  15. package/dist/middleware/services/tts/providers/base-tts-provider.d.ts +204 -0
  16. package/dist/middleware/services/tts/providers/base-tts-provider.d.ts.map +1 -0
  17. package/dist/middleware/services/tts/providers/base-tts-provider.js +267 -0
  18. package/dist/middleware/services/tts/providers/base-tts-provider.js.map +1 -0
  19. package/dist/middleware/services/tts/providers/edenai-provider.d.ts +112 -0
  20. package/dist/middleware/services/tts/providers/edenai-provider.d.ts.map +1 -0
  21. package/dist/middleware/services/tts/providers/edenai-provider.js +289 -0
  22. package/dist/middleware/services/tts/providers/edenai-provider.js.map +1 -0
  23. package/dist/middleware/services/tts/providers/index.d.ts +9 -0
  24. package/dist/middleware/services/tts/providers/index.d.ts.map +1 -0
  25. package/dist/middleware/services/tts/providers/index.js +29 -0
  26. package/dist/middleware/services/tts/providers/index.js.map +1 -0
  27. package/dist/middleware/services/tts/tts.service.d.ts +175 -0
  28. package/dist/middleware/services/tts/tts.service.d.ts.map +1 -0
  29. package/dist/middleware/services/tts/tts.service.js +287 -0
  30. package/dist/middleware/services/tts/tts.service.js.map +1 -0
  31. package/dist/middleware/services/tts/types/common.types.d.ts +303 -0
  32. package/dist/middleware/services/tts/types/common.types.d.ts.map +1 -0
  33. package/dist/middleware/services/tts/types/common.types.js +42 -0
  34. package/dist/middleware/services/tts/types/common.types.js.map +1 -0
  35. package/dist/middleware/services/tts/types/index.d.ts +22 -0
  36. package/dist/middleware/services/tts/types/index.d.ts.map +1 -0
  37. package/dist/middleware/services/tts/types/index.js +46 -0
  38. package/dist/middleware/services/tts/types/index.js.map +1 -0
  39. package/dist/middleware/services/tts/types/provider-options.types.d.ts +414 -0
  40. package/dist/middleware/services/tts/types/provider-options.types.d.ts.map +1 -0
  41. package/dist/middleware/services/tts/types/provider-options.types.js +71 -0
  42. package/dist/middleware/services/tts/types/provider-options.types.js.map +1 -0
  43. package/dist/middleware/services/tts/utils/character-counter.utils.d.ts +160 -0
  44. package/dist/middleware/services/tts/utils/character-counter.utils.d.ts.map +1 -0
  45. package/dist/middleware/services/tts/utils/character-counter.utils.js +205 -0
  46. package/dist/middleware/services/tts/utils/character-counter.utils.js.map +1 -0
  47. package/dist/middleware/services/tts/utils/index.d.ts +9 -0
  48. package/dist/middleware/services/tts/utils/index.d.ts.map +1 -0
  49. package/dist/middleware/services/tts/utils/index.js +25 -0
  50. package/dist/middleware/services/tts/utils/index.js.map +1 -0
  51. package/dist/middleware/services/tts/utils/logger.utils.d.ts +116 -0
  52. package/dist/middleware/services/tts/utils/logger.utils.d.ts.map +1 -0
  53. package/dist/middleware/services/tts/utils/logger.utils.js +186 -0
  54. package/dist/middleware/services/tts/utils/logger.utils.js.map +1 -0
  55. package/dist/middleware/shared/config/tts.config.d.ts +147 -0
  56. package/dist/middleware/shared/config/tts.config.d.ts.map +1 -0
  57. package/dist/middleware/shared/config/tts.config.js +162 -0
  58. package/dist/middleware/shared/config/tts.config.js.map +1 -0
  59. package/package.json +94 -0
@@ -0,0 +1,267 @@
1
+ "use strict";
2
+ /**
3
+ * Base TTS Provider Abstract Class
4
+ *
5
+ * All TTS providers must extend this class and implement the abstract methods.
6
+ * This ensures consistency across all providers.
7
+ *
8
+ * @abstract
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.BaseTTSProvider = exports.NetworkError = exports.SynthesisFailedError = exports.ProviderUnavailableError = exports.QuotaExceededError = exports.InvalidVoiceError = exports.InvalidConfigError = exports.TTSError = void 0;
12
+ const logger_utils_1 = require("../utils/logger.utils");
13
+ /**
14
+ * Base error class for all TTS errors
15
+ */
16
+ class TTSError extends Error {
17
+ /**
18
+ * Creates a new TTS error
19
+ *
20
+ * @param provider - The provider that threw the error
21
+ * @param code - Error code for categorization
22
+ * @param message - Human-readable error message
23
+ * @param cause - Optional underlying error that caused this error
24
+ */
25
+ constructor(provider, code, message, cause) {
26
+ super(message);
27
+ this.provider = provider;
28
+ this.code = code;
29
+ this.cause = cause;
30
+ this.name = 'TTSError';
31
+ // Maintains proper stack trace for where our error was thrown (only available on V8)
32
+ if (Error.captureStackTrace) {
33
+ Error.captureStackTrace(this, TTSError);
34
+ }
35
+ }
36
+ /**
37
+ * Returns a formatted error message with context
38
+ */
39
+ toString() {
40
+ return `[${this.provider}] ${this.code}: ${this.message}${this.cause ? ` (caused by: ${this.cause.message})` : ''}`;
41
+ }
42
+ }
43
+ exports.TTSError = TTSError;
44
+ /**
45
+ * Error thrown when TTS configuration is invalid or missing
46
+ */
47
+ class InvalidConfigError extends TTSError {
48
+ constructor(provider, message, cause) {
49
+ super(provider, 'INVALID_CONFIG', message, cause);
50
+ this.name = 'InvalidConfigError';
51
+ }
52
+ }
53
+ exports.InvalidConfigError = InvalidConfigError;
54
+ /**
55
+ * Error thrown when voice ID is invalid or not found
56
+ */
57
+ class InvalidVoiceError extends TTSError {
58
+ constructor(provider, voiceId, message, cause) {
59
+ super(provider, 'INVALID_VOICE', message || `Voice not found: ${voiceId}`, cause);
60
+ this.name = 'InvalidVoiceError';
61
+ }
62
+ }
63
+ exports.InvalidVoiceError = InvalidVoiceError;
64
+ /**
65
+ * Error thrown when provider quota/rate limit is exceeded
66
+ */
67
+ class QuotaExceededError extends TTSError {
68
+ constructor(provider, message, cause) {
69
+ super(provider, 'QUOTA_EXCEEDED', message || 'Provider quota or rate limit exceeded', cause);
70
+ this.name = 'QuotaExceededError';
71
+ }
72
+ }
73
+ exports.QuotaExceededError = QuotaExceededError;
74
+ /**
75
+ * Error thrown when provider service is unavailable
76
+ */
77
+ class ProviderUnavailableError extends TTSError {
78
+ constructor(provider, message, cause) {
79
+ super(provider, 'PROVIDER_UNAVAILABLE', message || 'Provider service is temporarily unavailable', cause);
80
+ this.name = 'ProviderUnavailableError';
81
+ }
82
+ }
83
+ exports.ProviderUnavailableError = ProviderUnavailableError;
84
+ /**
85
+ * Error thrown when synthesis fails for unknown reasons
86
+ */
87
+ class SynthesisFailedError extends TTSError {
88
+ constructor(provider, message, cause) {
89
+ super(provider, 'SYNTHESIS_FAILED', message, cause);
90
+ this.name = 'SynthesisFailedError';
91
+ }
92
+ }
93
+ exports.SynthesisFailedError = SynthesisFailedError;
94
+ /**
95
+ * Error thrown when network operation fails
96
+ */
97
+ class NetworkError extends TTSError {
98
+ constructor(provider, message, cause) {
99
+ super(provider, 'NETWORK_ERROR', message, cause);
100
+ this.name = 'NetworkError';
101
+ }
102
+ }
103
+ exports.NetworkError = NetworkError;
104
+ /**
105
+ * Abstract base class for all TTS providers
106
+ *
107
+ * @abstract
108
+ *
109
+ * @description All provider implementations must:
110
+ * - Extend this class
111
+ * - Implement the abstract synthesize() method
112
+ * - Call super() in constructor with the provider name
113
+ * - Use the provided error classes for consistent error handling
114
+ *
115
+ * @example
116
+ * ```typescript
117
+ * class AzureProvider extends BaseTTSProvider {
118
+ * constructor() {
119
+ * super(TTSProvider.AZURE);
120
+ * }
121
+ *
122
+ * async synthesize(text: string, voiceId: string, request: TTSSynthesizeRequest): Promise<TTSResponse> {
123
+ * this.validateConfig(request);
124
+ * // ... implementation
125
+ * }
126
+ * }
127
+ * ```
128
+ */
129
+ class BaseTTSProvider {
130
+ /**
131
+ * Creates a new TTS provider
132
+ *
133
+ * @param providerName - The provider identifier
134
+ */
135
+ constructor(providerName) {
136
+ this.providerName = providerName;
137
+ }
138
+ /**
139
+ * Get the provider name
140
+ *
141
+ * @returns The provider identifier
142
+ */
143
+ getProviderName() {
144
+ return this.providerName;
145
+ }
146
+ /**
147
+ * Validate provider configuration
148
+ *
149
+ * @protected
150
+ * @param request - The synthesis request to validate
151
+ * @throws {InvalidConfigError} If configuration is invalid
152
+ *
153
+ * @description Subclasses should override this to validate provider-specific
154
+ * configuration (e.g., API keys, region settings)
155
+ */
156
+ validateConfig(request) {
157
+ if (!request.text || request.text.trim().length === 0) {
158
+ throw new InvalidConfigError(this.providerName, 'Text cannot be empty');
159
+ }
160
+ if (!request.voice || !request.voice.id) {
161
+ throw new InvalidConfigError(this.providerName, 'Voice ID is required');
162
+ }
163
+ }
164
+ /**
165
+ * Count characters in text for billing purposes
166
+ *
167
+ * @protected
168
+ * @param text - The input text to count
169
+ * @returns The number of characters (including spaces and punctuation)
170
+ *
171
+ * @description This is the base implementation that counts all characters.
172
+ * Providers that use SSML or have special character counting rules
173
+ * should override this method.
174
+ *
175
+ * @example
176
+ * ```typescript
177
+ * const count = this.countCharacters("Hello World!"); // returns 12
178
+ * ```
179
+ */
180
+ countCharacters(text) {
181
+ return text.length;
182
+ }
183
+ /**
184
+ * Validate that a voice exists
185
+ *
186
+ * @protected
187
+ * @param _voiceId - The voice identifier to validate (unused in base implementation)
188
+ * @returns Promise resolving to true if voice exists
189
+ * @throws {InvalidVoiceError} If voice does not exist
190
+ *
191
+ * @description Base implementation always returns true. Providers should
192
+ * override this to validate voice IDs against their voice catalogs.
193
+ */
194
+ async validateVoiceExists(_voiceId) {
195
+ // Base implementation: assume voice exists
196
+ // Subclasses should override to validate against provider's voice catalog
197
+ return true;
198
+ }
199
+ /**
200
+ * Create a standardized error from a provider-specific error
201
+ *
202
+ * @protected
203
+ * @param error - The original error
204
+ * @param context - Additional context for debugging
205
+ * @returns A standardized TTS error
206
+ *
207
+ * @description Wraps provider-specific errors in our error classes
208
+ * for consistent error handling across providers.
209
+ */
210
+ handleError(error, context) {
211
+ // If it's already a TTSError, return it
212
+ if (error instanceof TTSError) {
213
+ return error;
214
+ }
215
+ // Check for common HTTP error codes
216
+ const errorMessage = error.message.toLowerCase();
217
+ if (errorMessage.includes('401') || errorMessage.includes('403')) {
218
+ return new InvalidConfigError(this.providerName, `Authentication failed${context ? `: ${context}` : ''}`, error);
219
+ }
220
+ if (errorMessage.includes('429')) {
221
+ return new QuotaExceededError(this.providerName, `Rate limit exceeded${context ? `: ${context}` : ''}`, error);
222
+ }
223
+ if (errorMessage.includes('503') ||
224
+ errorMessage.includes('504') ||
225
+ errorMessage.includes('502')) {
226
+ return new ProviderUnavailableError(this.providerName, `Service temporarily unavailable${context ? `: ${context}` : ''}`, error);
227
+ }
228
+ if (errorMessage.includes('timeout') ||
229
+ errorMessage.includes('econnrefused') ||
230
+ errorMessage.includes('enotfound')) {
231
+ return new NetworkError(this.providerName, `Network error${context ? `: ${context}` : ''}`, error);
232
+ }
233
+ // Default to SynthesisFailedError for unknown errors
234
+ return new SynthesisFailedError(this.providerName, `Synthesis failed${context ? `: ${context}` : ''}: ${error.message}`, error);
235
+ }
236
+ /**
237
+ * Log a message using the pluggable logger
238
+ *
239
+ * @protected
240
+ * @param level - Log level
241
+ * @param message - Log message
242
+ * @param meta - Optional metadata
243
+ *
244
+ * @description Uses the global TTS logger which can be customized via setLogger().
245
+ * By default uses console logging, but can be replaced with any logger (Winston, Pino, etc.)
246
+ *
247
+ * @example
248
+ * ```typescript
249
+ * // In application code:
250
+ * import { setLogger, silentLogger } from '@loonylabs/tts-middleware';
251
+ *
252
+ * // Disable all logging
253
+ * setLogger(silentLogger);
254
+ *
255
+ * // Use custom logger
256
+ * setLogger({
257
+ * info: (msg, meta) => myLogger.info(msg, meta),
258
+ * // ...
259
+ * });
260
+ * ```
261
+ */
262
+ log(level, message, meta) {
263
+ (0, logger_utils_1.log)(this.providerName, level, message, meta);
264
+ }
265
+ }
266
+ exports.BaseTTSProvider = BaseTTSProvider;
267
+ //# sourceMappingURL=base-tts-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base-tts-provider.js","sourceRoot":"","sources":["../../../../../src/middleware/services/tts/providers/base-tts-provider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * EdenAI TTS Provider
3
+ *
4
+ * @description Provider for EdenAI TTS API (multi-provider aggregator)
5
+ * EdenAI acts as an aggregator, providing access to multiple TTS providers
6
+ * (Amazon, Google, IBM, Microsoft, OpenAI, ElevenLabs) through a single API.
7
+ *
8
+ * @see https://docs.edenai.co/reference/text_to_speech_create
9
+ */
10
+ import type { TTSSynthesizeRequest, TTSResponse } from '../types';
11
+ import { BaseTTSProvider } from './base-tts-provider';
12
+ /**
13
+ * EdenAI-specific configuration
14
+ */
15
+ interface EdenAIConfig {
16
+ apiKey: string;
17
+ apiUrl?: string;
18
+ }
19
+ /**
20
+ * EdenAI provider implementation
21
+ *
22
+ * @description Provides TTS synthesis using EdenAI's multi-provider aggregator API.
23
+ * EdenAI routes requests to underlying providers (Amazon, Google, IBM, Microsoft, OpenAI, ElevenLabs).
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * const provider = new EdenAIProvider();
28
+ * const response = await provider.synthesize(
29
+ * "Hello World",
30
+ * "en-US",
31
+ * {
32
+ * text: "Hello World",
33
+ * voice: { id: "en-US-JennyNeural" },
34
+ * audio: { speed: 1.0, format: "mp3" },
35
+ * providerOptions: { provider: "google" }
36
+ * }
37
+ * );
38
+ * ```
39
+ */
40
+ export declare class EdenAIProvider extends BaseTTSProvider {
41
+ private config;
42
+ private readonly apiUrl;
43
+ /**
44
+ * Creates a new EdenAI TTS provider
45
+ *
46
+ * @param config - Optional EdenAI configuration (uses env vars if not provided)
47
+ * @throws {InvalidConfigError} If API key is missing
48
+ */
49
+ constructor(config?: Partial<EdenAIConfig>);
50
+ /**
51
+ * Validate EdenAI-specific configuration
52
+ *
53
+ * @private
54
+ * @throws {InvalidConfigError} If configuration is invalid
55
+ */
56
+ private validateEdenAIConfig;
57
+ /**
58
+ * Synthesize text to speech using EdenAI
59
+ *
60
+ * @param text - The input text to synthesize
61
+ * @param voiceId - The voice identifier (language code or voice name)
62
+ * @param request - The full synthesis request with options
63
+ * @returns Promise resolving to the synthesis response
64
+ * @throws {InvalidConfigError} If configuration is invalid
65
+ * @throws {SynthesisFailedError} If synthesis fails
66
+ */
67
+ synthesize(text: string, voiceId: string, request: TTSSynthesizeRequest): Promise<TTSResponse>;
68
+ /**
69
+ * Build EdenAI API request payload
70
+ *
71
+ * @private
72
+ * @param text - The input text
73
+ * @param voiceId - The voice identifier
74
+ * @param request - The synthesis request
75
+ * @param options - EdenAI-specific options
76
+ * @returns Request payload for EdenAI API
77
+ */
78
+ private buildEdenAIRequest;
79
+ /**
80
+ * Call EdenAI API
81
+ *
82
+ * @private
83
+ * @param requestBody - The request payload
84
+ * @returns Promise resolving to audio buffer
85
+ * @throws {SynthesisFailedError} If API call fails
86
+ */
87
+ private callEdenAIAPI;
88
+ /**
89
+ * Extract audio buffer from EdenAI response
90
+ *
91
+ * @private
92
+ * @param data - The EdenAI API response
93
+ * @returns Audio buffer or null
94
+ */
95
+ private extractAudioFromResponse;
96
+ /**
97
+ * Extract language code from voice ID
98
+ *
99
+ * @private
100
+ * @param voiceId - The voice identifier
101
+ * @returns Language code (e.g., 'en-US', 'de-DE')
102
+ *
103
+ * @description Extracts language code from voice IDs like:
104
+ * - 'en-US-JennyNeural' -> 'en-US'
105
+ * - 'de-DE-KatjaNeural' -> 'de-DE'
106
+ * - 'en-US' -> 'en-US'
107
+ * - 'en' -> 'en'
108
+ */
109
+ private extractLanguage;
110
+ }
111
+ export {};
112
+ //# sourceMappingURL=edenai-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edenai-provider.d.ts","sourceRoot":"","sources":["../../../../../src/middleware/services/tts/providers/edenai-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAElE,OAAO,EACL,eAAe,EAEhB,MAAM,qBAAqB,CAAC;AAG7B;;GAEG;AACH,UAAU,YAAY;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAeD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,cAAe,SAAQ,eAAe;IACjD,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC;;;;;OAKG;gBACS,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC;IAqB1C;;;;;OAKG;IACH,OAAO,CAAC,oBAAoB;IAS5B;;;;;;;;;OASG;IACG,UAAU,CACd,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,WAAW,CAAC;IA2DvB;;;;;;;;;OASG;IACH,OAAO,CAAC,kBAAkB;IAgE1B;;;;;;;OAOG;YACW,aAAa;IAmC3B;;;;;;OAMG;YACW,wBAAwB;IA2CtC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,eAAe;CAWxB"}
@@ -0,0 +1,289 @@
1
+ "use strict";
2
+ /**
3
+ * EdenAI TTS Provider
4
+ *
5
+ * @description Provider for EdenAI TTS API (multi-provider aggregator)
6
+ * EdenAI acts as an aggregator, providing access to multiple TTS providers
7
+ * (Amazon, Google, IBM, Microsoft, OpenAI, ElevenLabs) through a single API.
8
+ *
9
+ * @see https://docs.edenai.co/reference/text_to_speech_create
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.EdenAIProvider = void 0;
13
+ const types_1 = require("../types");
14
+ const base_tts_provider_1 = require("./base-tts-provider");
15
+ /**
16
+ * EdenAI provider implementation
17
+ *
18
+ * @description Provides TTS synthesis using EdenAI's multi-provider aggregator API.
19
+ * EdenAI routes requests to underlying providers (Amazon, Google, IBM, Microsoft, OpenAI, ElevenLabs).
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const provider = new EdenAIProvider();
24
+ * const response = await provider.synthesize(
25
+ * "Hello World",
26
+ * "en-US",
27
+ * {
28
+ * text: "Hello World",
29
+ * voice: { id: "en-US-JennyNeural" },
30
+ * audio: { speed: 1.0, format: "mp3" },
31
+ * providerOptions: { provider: "google" }
32
+ * }
33
+ * );
34
+ * ```
35
+ */
36
+ class EdenAIProvider extends base_tts_provider_1.BaseTTSProvider {
37
+ /**
38
+ * Creates a new EdenAI TTS provider
39
+ *
40
+ * @param config - Optional EdenAI configuration (uses env vars if not provided)
41
+ * @throws {InvalidConfigError} If API key is missing
42
+ */
43
+ constructor(config) {
44
+ super(types_1.TTSProvider.EDENAI);
45
+ // Load configuration from environment or provided config
46
+ this.config = {
47
+ apiKey: config?.apiKey || process.env.EDENAI_API_KEY || '',
48
+ apiUrl: config?.apiUrl,
49
+ };
50
+ this.apiUrl =
51
+ this.config.apiUrl || 'https://api.edenai.run/v2/audio/text_to_speech';
52
+ // Validate configuration
53
+ this.validateEdenAIConfig();
54
+ this.log('info', 'EdenAI provider initialized', {
55
+ hasApiKey: !!this.config.apiKey,
56
+ apiUrl: this.apiUrl,
57
+ });
58
+ }
59
+ /**
60
+ * Validate EdenAI-specific configuration
61
+ *
62
+ * @private
63
+ * @throws {InvalidConfigError} If configuration is invalid
64
+ */
65
+ validateEdenAIConfig() {
66
+ if (!this.config.apiKey) {
67
+ throw new base_tts_provider_1.InvalidConfigError(this.providerName, 'EdenAI API key is required (EDENAI_API_KEY)');
68
+ }
69
+ }
70
+ /**
71
+ * Synthesize text to speech using EdenAI
72
+ *
73
+ * @param text - The input text to synthesize
74
+ * @param voiceId - The voice identifier (language code or voice name)
75
+ * @param request - The full synthesis request with options
76
+ * @returns Promise resolving to the synthesis response
77
+ * @throws {InvalidConfigError} If configuration is invalid
78
+ * @throws {SynthesisFailedError} If synthesis fails
79
+ */
80
+ async synthesize(text, voiceId, request) {
81
+ // Validate configuration
82
+ this.validateConfig(request);
83
+ const startTime = Date.now();
84
+ // Extract options (use as EdenAIProviderOptions if it looks like it, otherwise empty)
85
+ const options = (request.providerOptions || {});
86
+ // Build EdenAI request
87
+ const edenaiRequest = this.buildEdenAIRequest(text, voiceId, request, options);
88
+ this.log('debug', 'Synthesizing with EdenAI', {
89
+ voiceId,
90
+ provider: options.provider || 'auto',
91
+ textLength: text.length,
92
+ });
93
+ try {
94
+ // Call EdenAI API
95
+ const audioBuffer = await this.callEdenAIAPI(edenaiRequest);
96
+ // Calculate duration
97
+ const duration = Date.now() - startTime;
98
+ // Count billable characters (original text)
99
+ const characters = this.countCharacters(text);
100
+ this.log('info', 'Synthesis successful', {
101
+ voiceId,
102
+ characters,
103
+ duration,
104
+ audioSize: audioBuffer.length,
105
+ });
106
+ // Return response
107
+ return {
108
+ audio: audioBuffer,
109
+ metadata: {
110
+ provider: this.providerName,
111
+ voice: voiceId,
112
+ duration,
113
+ audioFormat: request.audio?.format || options.audio_format || 'mp3',
114
+ sampleRate: request.audio?.sampleRate || options.sampling_rate || 24000,
115
+ },
116
+ billing: {
117
+ characters,
118
+ },
119
+ };
120
+ }
121
+ catch (error) {
122
+ this.log('error', 'Synthesis failed', {
123
+ voiceId,
124
+ error: error.message,
125
+ });
126
+ throw this.handleError(error, 'during EdenAI API call');
127
+ }
128
+ }
129
+ /**
130
+ * Build EdenAI API request payload
131
+ *
132
+ * @private
133
+ * @param text - The input text
134
+ * @param voiceId - The voice identifier
135
+ * @param request - The synthesis request
136
+ * @param options - EdenAI-specific options
137
+ * @returns Request payload for EdenAI API
138
+ */
139
+ buildEdenAIRequest(text, voiceId, request, options) {
140
+ // Extract language from voice ID (e.g., 'en-US-JennyNeural' -> 'en-US', 'en-US' -> 'en-US')
141
+ const language = this.extractLanguage(voiceId);
142
+ // Base request
143
+ const edenaiRequest = {
144
+ text,
145
+ language,
146
+ option: voiceId, // Use voiceId as option for voice selection
147
+ providers: options.provider || 'google', // Top-level providers field
148
+ };
149
+ // Build settings object for provider-specific options
150
+ const settings = {};
151
+ // Speaking rate (maps to speed)
152
+ const speakingRate = options.speaking_rate ?? request.audio?.speed;
153
+ if (speakingRate !== undefined) {
154
+ settings.speaking_rate = speakingRate;
155
+ }
156
+ // Speaking pitch
157
+ if (options.speaking_pitch !== undefined) {
158
+ settings.speaking_pitch = options.speaking_pitch;
159
+ }
160
+ // Speaking volume
161
+ if (options.speaking_volume !== undefined) {
162
+ settings.speaking_volume = options.speaking_volume;
163
+ }
164
+ // Audio format
165
+ if (options.audio_format !== undefined) {
166
+ settings.audio_format = options.audio_format;
167
+ }
168
+ // Sampling rate
169
+ if (options.sampling_rate !== undefined) {
170
+ settings.sampling_rate = options.sampling_rate;
171
+ }
172
+ // Add providers to settings if provider is explicitly specified
173
+ if (options.provider) {
174
+ settings.providers = options.provider;
175
+ }
176
+ // Add settings to request if any settings were specified
177
+ if (Object.keys(settings).length > 0) {
178
+ edenaiRequest.settings = settings;
179
+ }
180
+ // Fallback providers (top-level)
181
+ if (options.fallback_providers && options.fallback_providers.length > 0) {
182
+ edenaiRequest.fallback_providers = options.fallback_providers;
183
+ }
184
+ return edenaiRequest;
185
+ }
186
+ /**
187
+ * Call EdenAI API
188
+ *
189
+ * @private
190
+ * @param requestBody - The request payload
191
+ * @returns Promise resolving to audio buffer
192
+ * @throws {SynthesisFailedError} If API call fails
193
+ */
194
+ async callEdenAIAPI(requestBody) {
195
+ try {
196
+ const response = await fetch(this.apiUrl, {
197
+ method: 'POST',
198
+ headers: {
199
+ Authorization: `Bearer ${this.config.apiKey}`,
200
+ 'Content-Type': 'application/json',
201
+ },
202
+ body: JSON.stringify(requestBody),
203
+ });
204
+ if (!response.ok) {
205
+ const errorText = await response.text();
206
+ throw new Error(`EdenAI API error (${response.status}): ${errorText}`);
207
+ }
208
+ const data = (await response.json());
209
+ // Extract audio from response
210
+ // EdenAI returns provider-specific results
211
+ const audioBuffer = await this.extractAudioFromResponse(data);
212
+ if (!audioBuffer) {
213
+ throw new Error('No audio data in EdenAI response');
214
+ }
215
+ return audioBuffer;
216
+ }
217
+ catch (error) {
218
+ if (error instanceof Error) {
219
+ throw error;
220
+ }
221
+ throw new Error(`EdenAI API call failed: ${error}`);
222
+ }
223
+ }
224
+ /**
225
+ * Extract audio buffer from EdenAI response
226
+ *
227
+ * @private
228
+ * @param data - The EdenAI API response
229
+ * @returns Audio buffer or null
230
+ */
231
+ async extractAudioFromResponse(data) {
232
+ // EdenAI returns results keyed by provider name
233
+ // Find the first successful provider response
234
+ for (const providerName of Object.keys(data)) {
235
+ const providerResult = data[providerName];
236
+ // Check for errors
237
+ if (providerResult.error || providerResult.status === 'fail') {
238
+ this.log('warn', `Provider ${providerName} failed`, {
239
+ error: providerResult.error,
240
+ });
241
+ continue;
242
+ }
243
+ // Check for base64-encoded audio
244
+ if (providerResult.audio) {
245
+ this.log('debug', `Using audio from provider: ${providerName}`);
246
+ return Buffer.from(providerResult.audio, 'base64');
247
+ }
248
+ // Check for audio URL
249
+ if (providerResult.audio_resource_url) {
250
+ this.log('debug', `Downloading audio from URL (provider: ${providerName})`);
251
+ const audioResponse = await fetch(providerResult.audio_resource_url);
252
+ if (!audioResponse.ok) {
253
+ this.log('warn', `Failed to download audio from URL`, {
254
+ provider: providerName,
255
+ url: providerResult.audio_resource_url,
256
+ });
257
+ continue;
258
+ }
259
+ const arrayBuffer = await audioResponse.arrayBuffer();
260
+ return Buffer.from(arrayBuffer);
261
+ }
262
+ }
263
+ return null;
264
+ }
265
+ /**
266
+ * Extract language code from voice ID
267
+ *
268
+ * @private
269
+ * @param voiceId - The voice identifier
270
+ * @returns Language code (e.g., 'en-US', 'de-DE')
271
+ *
272
+ * @description Extracts language code from voice IDs like:
273
+ * - 'en-US-JennyNeural' -> 'en-US'
274
+ * - 'de-DE-KatjaNeural' -> 'de-DE'
275
+ * - 'en-US' -> 'en-US'
276
+ * - 'en' -> 'en'
277
+ */
278
+ extractLanguage(voiceId) {
279
+ // Match language code patterns: en-US, de-DE, etc.
280
+ const match = voiceId.match(/^([a-z]{2}(-[A-Z]{2})?)/);
281
+ if (match) {
282
+ return match[1];
283
+ }
284
+ // If no match, assume voice ID is already a language code
285
+ return voiceId;
286
+ }
287
+ }
288
+ exports.EdenAIProvider = EdenAIProvider;
289
+ //# sourceMappingURL=edenai-provider.js.map