@pie-players/tts-server-polly 0.3.66 → 0.3.68

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.
@@ -46,19 +46,6 @@ export interface PollyProviderConfig extends TTSServerConfig {
46
46
  */
47
47
  defaultVoice?: string;
48
48
  }
49
- /**
50
- * AWS Polly Server Provider
51
- *
52
- * Provides high-quality neural text-to-speech with precise word-level timing
53
- * through AWS Polly speech marks.
54
- *
55
- * Features:
56
- * - Native speech marks support (millisecond precision)
57
- * - Neural and standard voices
58
- * - 25+ languages
59
- * - Full SSML support
60
- * - Parallel audio + speech marks requests
61
- */
62
49
  export declare class PollyServerProvider extends BaseTTSProvider {
63
50
  readonly providerId = "aws-polly";
64
51
  readonly providerName = "AWS Polly";
@@ -100,6 +87,14 @@ export declare class PollyServerProvider extends BaseTTSProvider {
100
87
  * Clearly documents what features are W3C-standard vs AWS-specific.
101
88
  */
102
89
  getCapabilities(): ServerProviderCapabilities;
90
+ /**
91
+ * Map an AWS SDK error to a TTSError, matching the granularity
92
+ * GoogleCloudTTSProvider gives its own vendor errors: a bare
93
+ * `PROVIDER_ERROR` for everything hides cases callers (Host A maps
94
+ * `RATE_LIMIT_EXCEEDED` / `AUTHENTICATION_ERROR` / `INVALID_REQUEST` /
95
+ * `TEXT_TOO_LONG` onto HTTP status codes) already know how to handle.
96
+ */
97
+ private mapPollyErrorToTTSError;
103
98
  /**
104
99
  * Clean up AWS Polly client
105
100
  */
@@ -17,6 +17,34 @@ import { BaseTTSProvider, TTSError, TTSErrorCode, } from "@pie-players/tts-serve
17
17
  * - Full SSML support
18
18
  * - Parallel audio + speech marks requests
19
19
  */
20
+ /**
21
+ * Polly's own SSML vocabulary, on top of the standard elements the base provider
22
+ * knows. `<amazon:effect>` and the `<aws-*>` family are Polly-only.
23
+ */
24
+ const POLLY_SSML_TAGS = ["<amazon:", "<aws-"];
25
+ /**
26
+ * AWS SDK exception names (`error.name`) that mean the request itself was
27
+ * invalid or unsupported, as opposed to a transient/provider-side failure.
28
+ * @see https://docs.aws.amazon.com/polly/latest/dg/API_Reference.html
29
+ */
30
+ const POLLY_INVALID_REQUEST_EXCEPTIONS = new Set([
31
+ "InvalidSampleRateException",
32
+ "InvalidSsmlException",
33
+ "LanguageNotSupportedException",
34
+ "ValidationException",
35
+ "EngineNotSupportedException",
36
+ "MarksNotSupportedForFormatException",
37
+ "SsmlMarksNotSupportedForTextTypeException",
38
+ "UnsupportedPlsAlphabetException",
39
+ "UnsupportedPlsLanguageException",
40
+ "InvalidS3BucketException",
41
+ "InvalidS3KeyException",
42
+ "InvalidSnsTopicArnException",
43
+ ]);
44
+ const POLLY_RATE_LIMIT_EXCEPTIONS = new Set([
45
+ "ThrottlingException",
46
+ "ServiceQuotaExceededException",
47
+ ]);
20
48
  export class PollyServerProvider extends BaseTTSProvider {
21
49
  providerId = "aws-polly";
22
50
  providerName = "AWS Polly";
@@ -120,29 +148,22 @@ export class PollyServerProvider extends BaseTTSProvider {
120
148
  };
121
149
  }
122
150
  catch (error) {
123
- throw new TTSError(TTSErrorCode.PROVIDER_ERROR, `AWS Polly synthesis failed: ${error instanceof Error ? error.message : String(error)}`, { error, request }, this.providerId);
151
+ throw this.mapPollyErrorToTTSError(error, { request });
124
152
  }
125
153
  }
126
154
  /**
127
155
  * Synthesize audio stream
128
156
  */
129
157
  async synthesizeAudio(request, voice) {
130
- // Detect if text contains SSML tags
131
- const isSsml = request.text.includes("<speak") ||
132
- request.text.includes("<emphasis") ||
133
- request.text.includes("<break") ||
134
- request.text.includes("<prosody") ||
135
- request.text.includes("<phoneme") ||
136
- request.text.includes("<amazon:") ||
137
- request.text.includes("<aws-");
138
- const textType = isSsml ? "ssml" : "text";
139
- if (isSsml) {
158
+ if (this.detectSSML(request.text, POLLY_SSML_TAGS)) {
140
159
  console.log("[PollyServerProvider] Detected SSML content, using TextType: ssml");
141
160
  }
161
+ const { text, isSsml } = this.applyProsody(request.text, request, POLLY_SSML_TAGS);
162
+ const textType = isSsml ? "ssml" : "text";
142
163
  const command = new SynthesizeSpeechCommand({
143
164
  Engine: this.engine === "neural" ? Engine.NEURAL : Engine.STANDARD,
144
165
  OutputFormat: this.resolveOutputFormat(request),
145
- Text: request.text,
166
+ Text: text,
146
167
  TextType: textType,
147
168
  VoiceId: voice,
148
169
  SampleRate: String(request.sampleRate || 24000),
@@ -172,19 +193,12 @@ export class PollyServerProvider extends BaseTTSProvider {
172
193
  * Synthesize speech marks
173
194
  */
174
195
  async synthesizeSpeechMarks(request, voice) {
175
- // Detect if text contains SSML tags (same logic as audio synthesis)
176
- const isSsml = request.text.includes("<speak") ||
177
- request.text.includes("<emphasis") ||
178
- request.text.includes("<break") ||
179
- request.text.includes("<prosody") ||
180
- request.text.includes("<phoneme") ||
181
- request.text.includes("<amazon:") ||
182
- request.text.includes("<aws-");
196
+ const { text, isSsml } = this.applyProsody(request.text, request, POLLY_SSML_TAGS);
183
197
  const textType = isSsml ? "ssml" : "text";
184
198
  const command = new SynthesizeSpeechCommand({
185
199
  Engine: this.engine === "neural" ? Engine.NEURAL : Engine.STANDARD,
186
200
  OutputFormat: "json",
187
- Text: request.text,
201
+ Text: text,
188
202
  TextType: textType,
189
203
  VoiceId: voice,
190
204
  SpeechMarkTypes: this.resolveSpeechMarkTypes(request),
@@ -273,7 +287,7 @@ export class PollyServerProvider extends BaseTTSProvider {
273
287
  });
274
288
  }
275
289
  catch (error) {
276
- throw new TTSError(TTSErrorCode.PROVIDER_ERROR, `Failed to get voices: ${error instanceof Error ? error.message : String(error)}`, { error }, this.providerId);
290
+ throw this.mapPollyErrorToTTSError(error);
277
291
  }
278
292
  }
279
293
  /**
@@ -310,6 +324,33 @@ export class PollyServerProvider extends BaseTTSProvider {
310
324
  },
311
325
  };
312
326
  }
327
+ /**
328
+ * Map an AWS SDK error to a TTSError, matching the granularity
329
+ * GoogleCloudTTSProvider gives its own vendor errors: a bare
330
+ * `PROVIDER_ERROR` for everything hides cases callers (Host A maps
331
+ * `RATE_LIMIT_EXCEEDED` / `AUTHENTICATION_ERROR` / `INVALID_REQUEST` /
332
+ * `TEXT_TOO_LONG` onto HTTP status codes) already know how to handle.
333
+ */
334
+ mapPollyErrorToTTSError(error, extraDetails) {
335
+ const err = error;
336
+ const message = err?.message || String(error);
337
+ const statusCode = err?.$metadata?.httpStatusCode;
338
+ const details = { error, ...extraDetails };
339
+ if (err?.name === "TextLengthExceededException") {
340
+ return new TTSError(TTSErrorCode.TEXT_TOO_LONG, `AWS Polly text length exceeded: ${message}`, details, this.providerId);
341
+ }
342
+ if (err?.name && POLLY_INVALID_REQUEST_EXCEPTIONS.has(err.name)) {
343
+ return new TTSError(TTSErrorCode.INVALID_REQUEST, `Invalid request to AWS Polly: ${message}`, details, this.providerId);
344
+ }
345
+ if ((err?.name && POLLY_RATE_LIMIT_EXCEPTIONS.has(err.name)) ||
346
+ statusCode === 429) {
347
+ return new TTSError(TTSErrorCode.RATE_LIMIT_EXCEEDED, `AWS Polly rate limit exceeded: ${message}`, details, this.providerId);
348
+ }
349
+ if (statusCode === 401 || statusCode === 403) {
350
+ return new TTSError(TTSErrorCode.AUTHENTICATION_ERROR, `AWS Polly authentication failed: ${message}`, details, this.providerId);
351
+ }
352
+ return new TTSError(TTSErrorCode.PROVIDER_ERROR, `AWS Polly error: ${message}`, details, this.providerId);
353
+ }
313
354
  /**
314
355
  * Clean up AWS Polly client
315
356
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pie-players/tts-server-polly",
3
- "version": "0.3.66",
3
+ "version": "0.3.68",
4
4
  "description": "AWS Polly provider for server-side TTS with speech marks support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@aws-sdk/client-polly": "^3.1033.0",
42
- "@pie-players/tts-server-core": "0.3.66"
42
+ "@pie-players/tts-server-core": "0.3.68"
43
43
  },
44
44
  "devDependencies": {
45
45
  "typescript": "^5.9.3",