@stacksjs/ts-cloud 0.1.8 → 0.1.9
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.
- package/dist/bin/cli.js +1 -1
- package/package.json +18 -16
- package/src/aws/acm.ts +768 -0
- package/src/aws/application-autoscaling.ts +845 -0
- package/src/aws/bedrock.ts +4074 -0
- package/src/aws/client.ts +891 -0
- package/src/aws/cloudformation.ts +896 -0
- package/src/aws/cloudfront.ts +1531 -0
- package/src/aws/cloudwatch-logs.ts +154 -0
- package/src/aws/comprehend.ts +839 -0
- package/src/aws/connect.ts +1056 -0
- package/src/aws/deploy-imap.ts +384 -0
- package/src/aws/dynamodb.ts +340 -0
- package/src/aws/ec2.ts +1385 -0
- package/src/aws/ecr.ts +621 -0
- package/src/aws/ecs.ts +615 -0
- package/src/aws/elasticache.ts +301 -0
- package/src/aws/elbv2.ts +942 -0
- package/src/aws/email.ts +928 -0
- package/src/aws/eventbridge.ts +248 -0
- package/src/aws/iam.ts +1689 -0
- package/src/aws/imap-server.ts +2100 -0
- package/src/aws/index.ts +213 -0
- package/src/aws/kendra.ts +1097 -0
- package/src/aws/lambda.ts +786 -0
- package/src/aws/opensearch.ts +158 -0
- package/src/aws/personalize.ts +977 -0
- package/src/aws/polly.ts +559 -0
- package/src/aws/rds.ts +888 -0
- package/src/aws/rekognition.ts +846 -0
- package/src/aws/route53-domains.ts +359 -0
- package/src/aws/route53.ts +1046 -0
- package/src/aws/s3.ts +2334 -0
- package/src/aws/scheduler.ts +571 -0
- package/src/aws/secrets-manager.ts +769 -0
- package/src/aws/ses.ts +1081 -0
- package/src/aws/setup-phone.ts +104 -0
- package/src/aws/setup-sms.ts +580 -0
- package/src/aws/sms.ts +1735 -0
- package/src/aws/smtp-server.ts +531 -0
- package/src/aws/sns.ts +758 -0
- package/src/aws/sqs.ts +382 -0
- package/src/aws/ssm.ts +807 -0
- package/src/aws/sts.ts +92 -0
- package/src/aws/support.ts +391 -0
- package/src/aws/test-imap.ts +86 -0
- package/src/aws/textract.ts +780 -0
- package/src/aws/transcribe.ts +108 -0
- package/src/aws/translate.ts +641 -0
- package/src/aws/voice.ts +1379 -0
- package/src/config.ts +35 -0
- package/src/deploy/index.ts +7 -0
- package/src/deploy/static-site-external-dns.ts +945 -0
- package/src/deploy/static-site.ts +1175 -0
- package/src/dns/cloudflare.ts +548 -0
- package/src/dns/godaddy.ts +412 -0
- package/src/dns/index.ts +205 -0
- package/src/dns/porkbun.ts +362 -0
- package/src/dns/route53-adapter.ts +414 -0
- package/src/dns/types.ts +119 -0
- package/src/dns/validator.ts +369 -0
- package/src/generators/index.ts +5 -0
- package/src/generators/infrastructure.ts +1660 -0
- package/src/index.ts +163 -0
- package/src/push/apns.ts +452 -0
- package/src/push/fcm.ts +506 -0
- package/src/push/index.ts +58 -0
- package/src/security/pre-deploy-scanner.ts +655 -0
- package/src/ssl/acme-client.ts +478 -0
- package/src/ssl/index.ts +7 -0
- package/src/ssl/letsencrypt.ts +747 -0
- package/src/types.ts +2 -0
- package/src/utils/cli.ts +398 -0
- package/src/validation/index.ts +5 -0
- package/src/validation/template.ts +405 -0
package/src/aws/polly.ts
ADDED
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AWS Polly Client
|
|
3
|
+
* Text-to-Speech service
|
|
4
|
+
* No external SDK dependencies - implements AWS Signature V4 directly
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { AWSClient } from './client'
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Types
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
export type VoiceId =
|
|
14
|
+
| 'Aditi' | 'Amy' | 'Aria' | 'Arlet' | 'Arthur' | 'Astrid'
|
|
15
|
+
| 'Ayanda' | 'Bianca' | 'Brian' | 'Camila' | 'Carla' | 'Carmen'
|
|
16
|
+
| 'Celine' | 'Chantal' | 'Conchita' | 'Cristiano' | 'Daniel' | 'Dora'
|
|
17
|
+
| 'Elin' | 'Emma' | 'Enrique' | 'Ewa' | 'Filiz' | 'Gabrielle'
|
|
18
|
+
| 'Geraint' | 'Giorgio' | 'Gwyneth' | 'Hala' | 'Hannah' | 'Hans'
|
|
19
|
+
| 'Hiujin' | 'Ida' | 'Ines' | 'Ivy' | 'Jacek' | 'Jan'
|
|
20
|
+
| 'Joanna' | 'Joey' | 'Justin' | 'Kajal' | 'Karl' | 'Kazuha'
|
|
21
|
+
| 'Kendra' | 'Kevin' | 'Kimberly' | 'Laura' | 'Lea' | 'Liam'
|
|
22
|
+
| 'Lisa' | 'Liv' | 'Lotte' | 'Lucia' | 'Lupe' | 'Mads'
|
|
23
|
+
| 'Maja' | 'Marlene' | 'Mathieu' | 'Matthew' | 'Maxim' | 'Mia'
|
|
24
|
+
| 'Miguel' | 'Mizuki' | 'Naja' | 'Niamh' | 'Nicole' | 'Ola'
|
|
25
|
+
| 'Olivia' | 'Pedro' | 'Penelope' | 'Raveena' | 'Remi' | 'Ricardo'
|
|
26
|
+
| 'Ruben' | 'Russell' | 'Ruth' | 'Salli' | 'Seoyeon' | 'Sergio'
|
|
27
|
+
| 'Sofie' | 'Stephen' | 'Suvi' | 'Takumi' | 'Tatyana' | 'Thiago'
|
|
28
|
+
| 'Tomoko' | 'Vicki' | 'Vitoria' | 'Zayd' | 'Zeina' | 'Zhiyu'
|
|
29
|
+
|
|
30
|
+
export type LanguageCode =
|
|
31
|
+
| 'arb' | 'ca-ES' | 'cmn-CN' | 'cy-GB' | 'da-DK' | 'de-AT' | 'de-DE'
|
|
32
|
+
| 'en-AU' | 'en-GB' | 'en-GB-WLS' | 'en-IE' | 'en-IN' | 'en-NZ' | 'en-US' | 'en-ZA'
|
|
33
|
+
| 'es-ES' | 'es-MX' | 'es-US' | 'fi-FI' | 'fr-BE' | 'fr-CA' | 'fr-FR'
|
|
34
|
+
| 'hi-IN' | 'is-IS' | 'it-IT' | 'ja-JP' | 'ko-KR' | 'nb-NO' | 'nl-BE' | 'nl-NL'
|
|
35
|
+
| 'pl-PL' | 'pt-BR' | 'pt-PT' | 'ro-RO' | 'ru-RU' | 'sv-SE' | 'tr-TR' | 'yue-CN'
|
|
36
|
+
|
|
37
|
+
export type Engine = 'standard' | 'neural' | 'long-form' | 'generative'
|
|
38
|
+
|
|
39
|
+
export type OutputFormat = 'json' | 'mp3' | 'ogg_vorbis' | 'pcm'
|
|
40
|
+
|
|
41
|
+
export type TextType = 'ssml' | 'text'
|
|
42
|
+
|
|
43
|
+
export type SpeechMarkType = 'sentence' | 'ssml' | 'viseme' | 'word'
|
|
44
|
+
|
|
45
|
+
export interface Voice {
|
|
46
|
+
Gender?: 'Female' | 'Male'
|
|
47
|
+
Id?: VoiceId
|
|
48
|
+
LanguageCode?: LanguageCode
|
|
49
|
+
LanguageName?: string
|
|
50
|
+
Name?: string
|
|
51
|
+
AdditionalLanguageCodes?: LanguageCode[]
|
|
52
|
+
SupportedEngines?: Engine[]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface SynthesizeSpeechCommandInput {
|
|
56
|
+
Engine?: Engine
|
|
57
|
+
LanguageCode?: LanguageCode
|
|
58
|
+
LexiconNames?: string[]
|
|
59
|
+
OutputFormat: OutputFormat
|
|
60
|
+
SampleRate?: string
|
|
61
|
+
SpeechMarkTypes?: SpeechMarkType[]
|
|
62
|
+
Text: string
|
|
63
|
+
TextType?: TextType
|
|
64
|
+
VoiceId: VoiceId
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface SynthesizeSpeechCommandOutput {
|
|
68
|
+
AudioStream?: Uint8Array
|
|
69
|
+
ContentType?: string
|
|
70
|
+
RequestCharacters?: number
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface DescribeVoicesCommandInput {
|
|
74
|
+
Engine?: Engine
|
|
75
|
+
LanguageCode?: LanguageCode
|
|
76
|
+
IncludeAdditionalLanguageCodes?: boolean
|
|
77
|
+
NextToken?: string
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface DescribeVoicesCommandOutput {
|
|
81
|
+
Voices?: Voice[]
|
|
82
|
+
NextToken?: string
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface StartSpeechSynthesisTaskCommandInput {
|
|
86
|
+
Engine?: Engine
|
|
87
|
+
LanguageCode?: LanguageCode
|
|
88
|
+
LexiconNames?: string[]
|
|
89
|
+
OutputFormat: OutputFormat
|
|
90
|
+
OutputS3BucketName: string
|
|
91
|
+
OutputS3KeyPrefix?: string
|
|
92
|
+
SampleRate?: string
|
|
93
|
+
SnsTopicArn?: string
|
|
94
|
+
SpeechMarkTypes?: SpeechMarkType[]
|
|
95
|
+
Text: string
|
|
96
|
+
TextType?: TextType
|
|
97
|
+
VoiceId: VoiceId
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface SynthesisTask {
|
|
101
|
+
Engine?: Engine
|
|
102
|
+
TaskId?: string
|
|
103
|
+
TaskStatus?: 'scheduled' | 'inProgress' | 'completed' | 'failed'
|
|
104
|
+
TaskStatusReason?: string
|
|
105
|
+
OutputUri?: string
|
|
106
|
+
CreationTime?: string
|
|
107
|
+
RequestCharacters?: number
|
|
108
|
+
SnsTopicArn?: string
|
|
109
|
+
LexiconNames?: string[]
|
|
110
|
+
OutputFormat?: OutputFormat
|
|
111
|
+
SampleRate?: string
|
|
112
|
+
SpeechMarkTypes?: SpeechMarkType[]
|
|
113
|
+
TextType?: TextType
|
|
114
|
+
VoiceId?: VoiceId
|
|
115
|
+
LanguageCode?: LanguageCode
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface StartSpeechSynthesisTaskCommandOutput {
|
|
119
|
+
SynthesisTask?: SynthesisTask
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface GetSpeechSynthesisTaskCommandInput {
|
|
123
|
+
TaskId: string
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface GetSpeechSynthesisTaskCommandOutput {
|
|
127
|
+
SynthesisTask?: SynthesisTask
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface ListSpeechSynthesisTasksCommandInput {
|
|
131
|
+
MaxResults?: number
|
|
132
|
+
NextToken?: string
|
|
133
|
+
Status?: 'scheduled' | 'inProgress' | 'completed' | 'failed'
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface ListSpeechSynthesisTasksCommandOutput {
|
|
137
|
+
NextToken?: string
|
|
138
|
+
SynthesisTasks?: SynthesisTask[]
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface PutLexiconCommandInput {
|
|
142
|
+
Name: string
|
|
143
|
+
Content: string
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface PutLexiconCommandOutput {
|
|
147
|
+
// Empty
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface GetLexiconCommandInput {
|
|
151
|
+
Name: string
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface Lexicon {
|
|
155
|
+
Content?: string
|
|
156
|
+
Name?: string
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface LexiconAttributes {
|
|
160
|
+
Alphabet?: string
|
|
161
|
+
LanguageCode?: LanguageCode
|
|
162
|
+
LastModified?: string
|
|
163
|
+
LexemesCount?: number
|
|
164
|
+
LexiconArn?: string
|
|
165
|
+
Size?: number
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface GetLexiconCommandOutput {
|
|
169
|
+
Lexicon?: Lexicon
|
|
170
|
+
LexiconAttributes?: LexiconAttributes
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface DeleteLexiconCommandInput {
|
|
174
|
+
Name: string
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface DeleteLexiconCommandOutput {
|
|
178
|
+
// Empty
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface ListLexiconsCommandInput {
|
|
182
|
+
NextToken?: string
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface ListLexiconsCommandOutput {
|
|
186
|
+
Lexicons?: Array<{
|
|
187
|
+
Name?: string
|
|
188
|
+
Attributes?: LexiconAttributes
|
|
189
|
+
}>
|
|
190
|
+
NextToken?: string
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ============================================================================
|
|
194
|
+
// Polly Client
|
|
195
|
+
// ============================================================================
|
|
196
|
+
|
|
197
|
+
export class PollyClient {
|
|
198
|
+
private client: AWSClient
|
|
199
|
+
private region: string
|
|
200
|
+
|
|
201
|
+
constructor(region: string = 'us-east-1') {
|
|
202
|
+
this.region = region
|
|
203
|
+
this.client = new AWSClient()
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// -------------------------------------------------------------------------
|
|
207
|
+
// Speech Synthesis
|
|
208
|
+
// -------------------------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Synthesize speech from text (synchronous)
|
|
212
|
+
*/
|
|
213
|
+
async synthesizeSpeech(params: SynthesizeSpeechCommandInput): Promise<SynthesizeSpeechCommandOutput> {
|
|
214
|
+
const result = await this.client.request({
|
|
215
|
+
service: 'polly',
|
|
216
|
+
region: this.region,
|
|
217
|
+
method: 'POST',
|
|
218
|
+
path: '/v1/speech',
|
|
219
|
+
headers: {
|
|
220
|
+
'Content-Type': 'application/json',
|
|
221
|
+
},
|
|
222
|
+
body: JSON.stringify({
|
|
223
|
+
Engine: params.Engine,
|
|
224
|
+
LanguageCode: params.LanguageCode,
|
|
225
|
+
LexiconNames: params.LexiconNames,
|
|
226
|
+
OutputFormat: params.OutputFormat,
|
|
227
|
+
SampleRate: params.SampleRate,
|
|
228
|
+
SpeechMarkTypes: params.SpeechMarkTypes,
|
|
229
|
+
Text: params.Text,
|
|
230
|
+
TextType: params.TextType,
|
|
231
|
+
VoiceId: params.VoiceId,
|
|
232
|
+
}),
|
|
233
|
+
rawResponse: true,
|
|
234
|
+
returnHeaders: true,
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
AudioStream: new TextEncoder().encode(result.body),
|
|
239
|
+
ContentType: result.headers?.['content-type'],
|
|
240
|
+
RequestCharacters: result.headers?.['x-amzn-requestcharacters']
|
|
241
|
+
? Number.parseInt(result.headers['x-amzn-requestcharacters'])
|
|
242
|
+
: undefined,
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Start async speech synthesis task
|
|
248
|
+
*/
|
|
249
|
+
async startSpeechSynthesisTask(params: StartSpeechSynthesisTaskCommandInput): Promise<StartSpeechSynthesisTaskCommandOutput> {
|
|
250
|
+
return this.client.request({
|
|
251
|
+
service: 'polly',
|
|
252
|
+
region: this.region,
|
|
253
|
+
method: 'POST',
|
|
254
|
+
path: '/v1/synthesisTasks',
|
|
255
|
+
headers: {
|
|
256
|
+
'Content-Type': 'application/json',
|
|
257
|
+
},
|
|
258
|
+
body: JSON.stringify(params),
|
|
259
|
+
})
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Get speech synthesis task status
|
|
264
|
+
*/
|
|
265
|
+
async getSpeechSynthesisTask(params: GetSpeechSynthesisTaskCommandInput): Promise<GetSpeechSynthesisTaskCommandOutput> {
|
|
266
|
+
return this.client.request({
|
|
267
|
+
service: 'polly',
|
|
268
|
+
region: this.region,
|
|
269
|
+
method: 'GET',
|
|
270
|
+
path: `/v1/synthesisTasks/${encodeURIComponent(params.TaskId)}`,
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* List speech synthesis tasks
|
|
276
|
+
*/
|
|
277
|
+
async listSpeechSynthesisTasks(params?: ListSpeechSynthesisTasksCommandInput): Promise<ListSpeechSynthesisTasksCommandOutput> {
|
|
278
|
+
const queryParams: Record<string, string> = {}
|
|
279
|
+
if (params?.MaxResults) queryParams.MaxResults = params.MaxResults.toString()
|
|
280
|
+
if (params?.NextToken) queryParams.NextToken = params.NextToken
|
|
281
|
+
if (params?.Status) queryParams.Status = params.Status
|
|
282
|
+
|
|
283
|
+
return this.client.request({
|
|
284
|
+
service: 'polly',
|
|
285
|
+
region: this.region,
|
|
286
|
+
method: 'GET',
|
|
287
|
+
path: '/v1/synthesisTasks',
|
|
288
|
+
queryParams: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// -------------------------------------------------------------------------
|
|
293
|
+
// Voices
|
|
294
|
+
// -------------------------------------------------------------------------
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* List available voices
|
|
298
|
+
*/
|
|
299
|
+
async describeVoices(params?: DescribeVoicesCommandInput): Promise<DescribeVoicesCommandOutput> {
|
|
300
|
+
const queryParams: Record<string, string> = {}
|
|
301
|
+
if (params?.Engine) queryParams.Engine = params.Engine
|
|
302
|
+
if (params?.LanguageCode) queryParams.LanguageCode = params.LanguageCode
|
|
303
|
+
if (params?.IncludeAdditionalLanguageCodes !== undefined) {
|
|
304
|
+
queryParams.IncludeAdditionalLanguageCodes = params.IncludeAdditionalLanguageCodes.toString()
|
|
305
|
+
}
|
|
306
|
+
if (params?.NextToken) queryParams.NextToken = params.NextToken
|
|
307
|
+
|
|
308
|
+
return this.client.request({
|
|
309
|
+
service: 'polly',
|
|
310
|
+
region: this.region,
|
|
311
|
+
method: 'GET',
|
|
312
|
+
path: '/v1/voices',
|
|
313
|
+
queryParams: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
314
|
+
})
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// -------------------------------------------------------------------------
|
|
318
|
+
// Lexicons
|
|
319
|
+
// -------------------------------------------------------------------------
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Store a pronunciation lexicon
|
|
323
|
+
*/
|
|
324
|
+
async putLexicon(params: PutLexiconCommandInput): Promise<PutLexiconCommandOutput> {
|
|
325
|
+
return this.client.request({
|
|
326
|
+
service: 'polly',
|
|
327
|
+
region: this.region,
|
|
328
|
+
method: 'PUT',
|
|
329
|
+
path: `/v1/lexicons/${encodeURIComponent(params.Name)}`,
|
|
330
|
+
headers: {
|
|
331
|
+
'Content-Type': 'application/pls+xml',
|
|
332
|
+
},
|
|
333
|
+
body: params.Content,
|
|
334
|
+
})
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Get a lexicon
|
|
339
|
+
*/
|
|
340
|
+
async getLexicon(params: GetLexiconCommandInput): Promise<GetLexiconCommandOutput> {
|
|
341
|
+
return this.client.request({
|
|
342
|
+
service: 'polly',
|
|
343
|
+
region: this.region,
|
|
344
|
+
method: 'GET',
|
|
345
|
+
path: `/v1/lexicons/${encodeURIComponent(params.Name)}`,
|
|
346
|
+
})
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Delete a lexicon
|
|
351
|
+
*/
|
|
352
|
+
async deleteLexicon(params: DeleteLexiconCommandInput): Promise<DeleteLexiconCommandOutput> {
|
|
353
|
+
return this.client.request({
|
|
354
|
+
service: 'polly',
|
|
355
|
+
region: this.region,
|
|
356
|
+
method: 'DELETE',
|
|
357
|
+
path: `/v1/lexicons/${encodeURIComponent(params.Name)}`,
|
|
358
|
+
})
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* List lexicons
|
|
363
|
+
*/
|
|
364
|
+
async listLexicons(params?: ListLexiconsCommandInput): Promise<ListLexiconsCommandOutput> {
|
|
365
|
+
const queryParams: Record<string, string> = {}
|
|
366
|
+
if (params?.NextToken) queryParams.NextToken = params.NextToken
|
|
367
|
+
|
|
368
|
+
return this.client.request({
|
|
369
|
+
service: 'polly',
|
|
370
|
+
region: this.region,
|
|
371
|
+
method: 'GET',
|
|
372
|
+
path: '/v1/lexicons',
|
|
373
|
+
queryParams: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
374
|
+
})
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// -------------------------------------------------------------------------
|
|
378
|
+
// Convenience Methods
|
|
379
|
+
// -------------------------------------------------------------------------
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Simple text to speech - returns MP3 audio bytes
|
|
383
|
+
*/
|
|
384
|
+
async textToSpeech(
|
|
385
|
+
text: string,
|
|
386
|
+
options?: {
|
|
387
|
+
voiceId?: VoiceId
|
|
388
|
+
engine?: Engine
|
|
389
|
+
languageCode?: LanguageCode
|
|
390
|
+
},
|
|
391
|
+
): Promise<Uint8Array> {
|
|
392
|
+
const result = await this.synthesizeSpeech({
|
|
393
|
+
Text: text,
|
|
394
|
+
VoiceId: options?.voiceId || 'Joanna',
|
|
395
|
+
Engine: options?.engine || 'neural',
|
|
396
|
+
LanguageCode: options?.languageCode,
|
|
397
|
+
OutputFormat: 'mp3',
|
|
398
|
+
})
|
|
399
|
+
return result.AudioStream || new Uint8Array()
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Text to speech with SSML support
|
|
404
|
+
*/
|
|
405
|
+
async ssmlToSpeech(
|
|
406
|
+
ssml: string,
|
|
407
|
+
options?: {
|
|
408
|
+
voiceId?: VoiceId
|
|
409
|
+
engine?: Engine
|
|
410
|
+
languageCode?: LanguageCode
|
|
411
|
+
},
|
|
412
|
+
): Promise<Uint8Array> {
|
|
413
|
+
const result = await this.synthesizeSpeech({
|
|
414
|
+
Text: ssml,
|
|
415
|
+
TextType: 'ssml',
|
|
416
|
+
VoiceId: options?.voiceId || 'Joanna',
|
|
417
|
+
Engine: options?.engine || 'neural',
|
|
418
|
+
LanguageCode: options?.languageCode,
|
|
419
|
+
OutputFormat: 'mp3',
|
|
420
|
+
})
|
|
421
|
+
return result.AudioStream || new Uint8Array()
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Save speech to S3 (for longer texts)
|
|
426
|
+
*/
|
|
427
|
+
async saveToS3(
|
|
428
|
+
text: string,
|
|
429
|
+
bucket: string,
|
|
430
|
+
keyPrefix: string,
|
|
431
|
+
options?: {
|
|
432
|
+
voiceId?: VoiceId
|
|
433
|
+
engine?: Engine
|
|
434
|
+
languageCode?: LanguageCode
|
|
435
|
+
textType?: TextType
|
|
436
|
+
},
|
|
437
|
+
): Promise<{ taskId: string; outputUri: string }> {
|
|
438
|
+
const result = await this.startSpeechSynthesisTask({
|
|
439
|
+
Text: text,
|
|
440
|
+
TextType: options?.textType || 'text',
|
|
441
|
+
VoiceId: options?.voiceId || 'Joanna',
|
|
442
|
+
Engine: options?.engine || 'neural',
|
|
443
|
+
LanguageCode: options?.languageCode,
|
|
444
|
+
OutputFormat: 'mp3',
|
|
445
|
+
OutputS3BucketName: bucket,
|
|
446
|
+
OutputS3KeyPrefix: keyPrefix,
|
|
447
|
+
})
|
|
448
|
+
|
|
449
|
+
return {
|
|
450
|
+
taskId: result.SynthesisTask?.TaskId || '',
|
|
451
|
+
outputUri: result.SynthesisTask?.OutputUri || '',
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* List voices for a specific language
|
|
457
|
+
*/
|
|
458
|
+
async listVoicesForLanguage(languageCode: LanguageCode): Promise<Voice[]> {
|
|
459
|
+
const result = await this.describeVoices({ LanguageCode: languageCode })
|
|
460
|
+
return result.Voices || []
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* List neural voices
|
|
465
|
+
*/
|
|
466
|
+
async listNeuralVoices(): Promise<Voice[]> {
|
|
467
|
+
const result = await this.describeVoices({ Engine: 'neural' })
|
|
468
|
+
return result.Voices || []
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Wait for synthesis task to complete
|
|
473
|
+
*/
|
|
474
|
+
async waitForTask(
|
|
475
|
+
taskId: string,
|
|
476
|
+
options?: { maxWaitMs?: number; pollIntervalMs?: number },
|
|
477
|
+
): Promise<SynthesisTask> {
|
|
478
|
+
const maxWaitMs = options?.maxWaitMs ?? 300000 // 5 minutes
|
|
479
|
+
const pollIntervalMs = options?.pollIntervalMs ?? 5000
|
|
480
|
+
const startTime = Date.now()
|
|
481
|
+
|
|
482
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
483
|
+
const result = await this.getSpeechSynthesisTask({ TaskId: taskId })
|
|
484
|
+
const task = result.SynthesisTask
|
|
485
|
+
|
|
486
|
+
if (task?.TaskStatus === 'completed') {
|
|
487
|
+
return task
|
|
488
|
+
}
|
|
489
|
+
if (task?.TaskStatus === 'failed') {
|
|
490
|
+
throw new Error(`Polly task ${taskId} failed: ${task.TaskStatusReason}`)
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
await new Promise(resolve => setTimeout(resolve, pollIntervalMs))
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
throw new Error(`Timeout waiting for Polly task ${taskId}`)
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// ============================================================================
|
|
501
|
+
// Helper Functions
|
|
502
|
+
// ============================================================================
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Quick text to speech
|
|
506
|
+
*/
|
|
507
|
+
export async function textToSpeech(
|
|
508
|
+
text: string,
|
|
509
|
+
options?: {
|
|
510
|
+
voiceId?: VoiceId
|
|
511
|
+
engine?: Engine
|
|
512
|
+
region?: string
|
|
513
|
+
},
|
|
514
|
+
): Promise<Uint8Array> {
|
|
515
|
+
const client = new PollyClient(options?.region || 'us-east-1')
|
|
516
|
+
return client.textToSpeech(text, options)
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* List available voices
|
|
521
|
+
*/
|
|
522
|
+
export async function listVoices(
|
|
523
|
+
options?: {
|
|
524
|
+
languageCode?: LanguageCode
|
|
525
|
+
engine?: Engine
|
|
526
|
+
region?: string
|
|
527
|
+
},
|
|
528
|
+
): Promise<Voice[]> {
|
|
529
|
+
const client = new PollyClient(options?.region || 'us-east-1')
|
|
530
|
+
const result = await client.describeVoices({
|
|
531
|
+
LanguageCode: options?.languageCode,
|
|
532
|
+
Engine: options?.engine,
|
|
533
|
+
})
|
|
534
|
+
return result.Voices || []
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Create SSML with speech marks (pauses, emphasis, etc.)
|
|
539
|
+
*/
|
|
540
|
+
export function createSSML(text: string, options?: {
|
|
541
|
+
rate?: 'x-slow' | 'slow' | 'medium' | 'fast' | 'x-fast'
|
|
542
|
+
pitch?: 'x-low' | 'low' | 'medium' | 'high' | 'x-high'
|
|
543
|
+
volume?: 'silent' | 'x-soft' | 'soft' | 'medium' | 'loud' | 'x-loud'
|
|
544
|
+
}): string {
|
|
545
|
+
let ssml = '<speak>'
|
|
546
|
+
|
|
547
|
+
if (options?.rate || options?.pitch || options?.volume) {
|
|
548
|
+
ssml += '<prosody'
|
|
549
|
+
if (options.rate) ssml += ` rate="${options.rate}"`
|
|
550
|
+
if (options.pitch) ssml += ` pitch="${options.pitch}"`
|
|
551
|
+
if (options.volume) ssml += ` volume="${options.volume}"`
|
|
552
|
+
ssml += `>${text}</prosody>`
|
|
553
|
+
} else {
|
|
554
|
+
ssml += text
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
ssml += '</speak>'
|
|
558
|
+
return ssml
|
|
559
|
+
}
|