@ssml-builder-js/azure-tts-client 2.16.0 → 2.18.0
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/CHANGELOG.md +21 -0
- package/dist/index.d.mts +223 -127
- package/dist/index.d.ts +223 -127
- package/dist/index.js +552 -98
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +549 -98
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/client.ts +29 -4
- package/src/errors.ts +44 -2
- package/src/index.ts +19 -2
- package/src/outputFormats.ts +3 -0
- package/src/safe.ts +280 -42
- package/src/synthesis.ts +380 -57
- package/src/types.ts +85 -1
- package/src/voiceCatalog.ts +4 -0
- package/test/v217-pipeline.test.ts +125 -0
- package/test/v218-pipeline.test.ts +91 -0
package/dist/index.mjs
CHANGED
|
@@ -8,7 +8,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
8
8
|
|
|
9
9
|
// src/errors.ts
|
|
10
10
|
var AzureTtsError = class extends Error {
|
|
11
|
-
constructor(status, statusText, responseBody, requestId) {
|
|
11
|
+
constructor(status, statusText, responseBody, requestId, responseHeaders) {
|
|
12
12
|
super(`Azure TTS request failed: ${status} ${statusText}`);
|
|
13
13
|
this.kind = "azure-api-error";
|
|
14
14
|
this.name = "AzureTtsError";
|
|
@@ -16,8 +16,37 @@ var AzureTtsError = class extends Error {
|
|
|
16
16
|
this.statusText = statusText;
|
|
17
17
|
this.responseBody = responseBody;
|
|
18
18
|
this.requestId = requestId;
|
|
19
|
+
const value = responseHeaders instanceof Headers ? responseHeaders.get("retry-after") : responseHeaders?.["retry-after"] ?? responseHeaders?.["Retry-After"];
|
|
20
|
+
const seconds = value ? Number(value.trim()) : NaN;
|
|
21
|
+
const date = value ? Date.parse(value) : NaN;
|
|
22
|
+
if (Number.isFinite(seconds) && seconds >= 0) this.retryAfterMs = seconds * 1e3;
|
|
23
|
+
else if (Number.isFinite(date)) this.retryAfterMs = Math.max(0, date - Date.now());
|
|
19
24
|
}
|
|
20
25
|
};
|
|
26
|
+
function getRetryAfterDelayMs(error) {
|
|
27
|
+
if (error instanceof AzureTtsError && error.retryAfterMs !== void 0) return error.retryAfterMs;
|
|
28
|
+
if (!error || typeof error !== "object") return void 0;
|
|
29
|
+
const candidate = error;
|
|
30
|
+
if (typeof candidate.retryAfterMs === "number" && candidate.retryAfterMs >= 0) return candidate.retryAfterMs;
|
|
31
|
+
const headers = candidate.headers ?? candidate.response?.headers;
|
|
32
|
+
if (headers instanceof Headers) {
|
|
33
|
+
const value = headers.get("retry-after");
|
|
34
|
+
if (!value) return void 0;
|
|
35
|
+
const seconds = Number(value.trim());
|
|
36
|
+
if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
|
|
37
|
+
const date = Date.parse(value);
|
|
38
|
+
return Number.isFinite(date) ? Math.max(0, date - Date.now()) : void 0;
|
|
39
|
+
}
|
|
40
|
+
if (headers && typeof headers === "object") {
|
|
41
|
+
const value = headers["retry-after"] ?? headers["Retry-After"];
|
|
42
|
+
if (typeof value !== "string") return void 0;
|
|
43
|
+
const seconds = Number(value.trim());
|
|
44
|
+
if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
|
|
45
|
+
const date = Date.parse(value);
|
|
46
|
+
return Number.isFinite(date) ? Math.max(0, date - Date.now()) : void 0;
|
|
47
|
+
}
|
|
48
|
+
return void 0;
|
|
49
|
+
}
|
|
21
50
|
var AzureTtsSdkError = class extends AzureTtsError {
|
|
22
51
|
constructor(errorDetails) {
|
|
23
52
|
super(0, "Speech SDK", errorDetails, null);
|
|
@@ -128,6 +157,9 @@ var OUTPUT_FORMATS = {
|
|
|
128
157
|
function resolveMimeType(outputFormat) {
|
|
129
158
|
if (/(?:wav|wave|riff)/i.test(outputFormat)) return "audio/wav";
|
|
130
159
|
if (/(?:mp3|mpeg)/i.test(outputFormat)) return "audio/mpeg";
|
|
160
|
+
if (/mulaw|mu-law/i.test(outputFormat)) return "audio/basic";
|
|
161
|
+
if (/alaw|a-law/i.test(outputFormat)) return "audio/alaw";
|
|
162
|
+
if (/siren/i.test(outputFormat)) return "audio/siren";
|
|
131
163
|
if (/ogg/i.test(outputFormat)) return "audio/ogg";
|
|
132
164
|
if (/webm/i.test(outputFormat)) return "audio/webm";
|
|
133
165
|
if (/raw/i.test(outputFormat)) return "audio/L16";
|
|
@@ -156,6 +188,27 @@ function createSpeechConfig(config) {
|
|
|
156
188
|
}
|
|
157
189
|
|
|
158
190
|
// src/synthesis.ts
|
|
191
|
+
function computeChunkFingerprint(ssml, outputFormat = DEFAULT_OUTPUT_FORMAT) {
|
|
192
|
+
const readAttribute = (name) => {
|
|
193
|
+
const pattern = new RegExp(`(?:${name})\\s*=\\s*[\\"']([^\\"']*)`, "gi");
|
|
194
|
+
return [...ssml.matchAll(pattern)].map((match) => match[1] ?? "").join("|");
|
|
195
|
+
};
|
|
196
|
+
const payload = JSON.stringify({
|
|
197
|
+
ssml,
|
|
198
|
+
outputFormat,
|
|
199
|
+
voice: readAttribute("(?:name|voice)"),
|
|
200
|
+
language: readAttribute("(?:xml:lang|lang)"),
|
|
201
|
+
rate: readAttribute("rate"),
|
|
202
|
+
pitch: readAttribute("pitch")
|
|
203
|
+
});
|
|
204
|
+
let hash = 0xcbf29ce484222325n;
|
|
205
|
+
const mask = 0xffffffffffffffffn;
|
|
206
|
+
for (let index = 0; index < payload.length; index += 1) {
|
|
207
|
+
hash ^= BigInt(payload.charCodeAt(index));
|
|
208
|
+
hash = hash * 0x100000001b3n & mask;
|
|
209
|
+
}
|
|
210
|
+
return `fnv1a64-${hash.toString(16).padStart(16, "0")}`;
|
|
211
|
+
}
|
|
159
212
|
function ascii(bytes, offset, value) {
|
|
160
213
|
return [...value].every((character, index) => bytes[offset + index] === character.charCodeAt(0));
|
|
161
214
|
}
|
|
@@ -195,9 +248,11 @@ function parseWav(buffer) {
|
|
|
195
248
|
}
|
|
196
249
|
return { chunks, data, format };
|
|
197
250
|
}
|
|
198
|
-
function
|
|
199
|
-
const match =
|
|
200
|
-
|
|
251
|
+
function formatSampleRate(format) {
|
|
252
|
+
const match = /(?:^|-)(\d+)(khz|hz)(?:-|$)/i.exec(format);
|
|
253
|
+
if (!match?.[1] || !match[2]) return 0;
|
|
254
|
+
const value = Number(match[1]);
|
|
255
|
+
return match[2].toLowerCase() === "khz" ? value * 1e3 : value;
|
|
201
256
|
}
|
|
202
257
|
function formatChannels(format, fallback) {
|
|
203
258
|
if (/stereo|2ch|dual/i.test(format)) return 2;
|
|
@@ -205,11 +260,13 @@ function formatChannels(format, fallback) {
|
|
|
205
260
|
return fallback;
|
|
206
261
|
}
|
|
207
262
|
function formatAudioSpecification(format) {
|
|
208
|
-
const sampleRate =
|
|
263
|
+
const sampleRate = formatSampleRate(format);
|
|
209
264
|
const channels = formatChannels(format, 0);
|
|
210
265
|
const bitrateMatch = /(\d+)(?:kbitrate|kbps|kbits?)/i.exec(format);
|
|
211
266
|
const bitrate = bitrateMatch?.[1] ? Number(bitrateMatch[1]) * 1e3 : void 0;
|
|
212
|
-
const codec = /mp3|mpeg/i.test(format) ? "mp3" : /opus/i.test(format) ? "opus" : /silk/i.test(format) ? "silk" : /
|
|
267
|
+
const codec = /mp3|mpeg/i.test(format) ? "mp3" : /opus/i.test(format) ? "opus" : /silk/i.test(format) ? "silk" : /mulaw|mu-law/i.test(format) ? "mulaw" : /alaw|a-law/i.test(format) ? "alaw" : /siren/i.test(format) ? "siren" : /pcm/i.test(format) ? "pcm" : "unknown";
|
|
268
|
+
const bitDepthMatch = /(\d+)bit/i.exec(format);
|
|
269
|
+
const container = /(?:wav|wave|riff)/i.test(format) ? "riff-wave" : /mp3|mpeg/i.test(format) ? "mp3-raw" : /ogg/i.test(format) ? "ogg" : /webm/i.test(format) ? "webm" : /raw/i.test(format) ? "raw" : void 0;
|
|
213
270
|
return {
|
|
214
271
|
format,
|
|
215
272
|
mimeType: resolveMimeType(format),
|
|
@@ -217,7 +274,10 @@ function formatAudioSpecification(format) {
|
|
|
217
274
|
sampleRate,
|
|
218
275
|
channels,
|
|
219
276
|
...bitrate ? { bitrate } : {},
|
|
220
|
-
|
|
277
|
+
...bitDepthMatch?.[1] ? { bitDepth: Number(bitDepthMatch[1]) } : {},
|
|
278
|
+
...container ? { container } : {},
|
|
279
|
+
isVbr: /vbr/i.test(format),
|
|
280
|
+
isCompressed: codec !== "pcm" && codec !== "unknown"
|
|
221
281
|
};
|
|
222
282
|
}
|
|
223
283
|
function parseMp3Specification(buffer, format) {
|
|
@@ -252,6 +312,8 @@ function parseMp3Specification(buffer, format) {
|
|
|
252
312
|
sampleRate,
|
|
253
313
|
channels: (bytes[index + 3] ?? 0) >> 6 === 3 ? 1 : 2,
|
|
254
314
|
bitrate: bitrateKbps * 1e3,
|
|
315
|
+
container: "mp3-raw",
|
|
316
|
+
isVbr: false,
|
|
255
317
|
isCompressed: true
|
|
256
318
|
};
|
|
257
319
|
}
|
|
@@ -266,24 +328,43 @@ function inspectAudioSpecification(buffer, format) {
|
|
|
266
328
|
const channels = view.getUint16(2, true);
|
|
267
329
|
const bitsPerSample = parsed.format.byteLength >= 16 ? view.getUint16(14, true) : 0;
|
|
268
330
|
const formatCode = view.getUint16(0, true);
|
|
331
|
+
const namedCodec = formatAudioSpecification(format).codec;
|
|
332
|
+
const codec = formatCode === 1 ? "pcm" : formatCode === 6 ? "alaw" : formatCode === 7 ? "mulaw" : namedCodec === "siren" ? "siren" : "unknown";
|
|
269
333
|
return {
|
|
270
334
|
format,
|
|
271
335
|
mimeType: "audio/wav",
|
|
272
|
-
codec
|
|
336
|
+
codec,
|
|
273
337
|
sampleRate,
|
|
274
338
|
channels,
|
|
275
339
|
...sampleRate && channels && bitsPerSample ? { bitrate: sampleRate * channels * bitsPerSample } : {},
|
|
276
|
-
|
|
340
|
+
bitDepth: bitsPerSample,
|
|
341
|
+
container: "riff-wave",
|
|
342
|
+
isVbr: false,
|
|
343
|
+
isCompressed: codec !== "pcm" && codec !== "unknown"
|
|
277
344
|
};
|
|
278
345
|
}
|
|
279
346
|
if (isMp3Format(format)) return parseMp3Specification(buffer, format) ?? formatAudioSpecification(format);
|
|
280
|
-
|
|
347
|
+
const specification = formatAudioSpecification(format);
|
|
348
|
+
if (specification.container === "raw") validateRawAudioBuffer(buffer, specification);
|
|
349
|
+
return specification;
|
|
350
|
+
}
|
|
351
|
+
function validateRawAudioBuffer(buffer, specification) {
|
|
352
|
+
if (specification.sampleRate <= 0 || specification.channels <= 0 || specification.bitDepth === void 0) {
|
|
353
|
+
throw new Error(`RAW audio format "${specification.format}" does not define a complete audio specification.`);
|
|
354
|
+
}
|
|
355
|
+
if (specification.codec === "siren" || specification.codec === "silk") return;
|
|
356
|
+
const bytesPerFrame = specification.channels * Math.ceil(specification.bitDepth / 8);
|
|
357
|
+
if (bytesPerFrame <= 0 || buffer.byteLength % bytesPerFrame !== 0) {
|
|
358
|
+
throw new Error(
|
|
359
|
+
`RAW audio buffer size ${buffer.byteLength} is not aligned to ${bytesPerFrame}-byte audio frames for "${specification.format}".`
|
|
360
|
+
);
|
|
361
|
+
}
|
|
281
362
|
}
|
|
282
363
|
function validateAudioSpecifications(specs) {
|
|
283
364
|
const first = specs[0];
|
|
284
365
|
if (!first) return;
|
|
285
366
|
const mismatch = specs.find(
|
|
286
|
-
(spec) => spec.sampleRate !== first.sampleRate || spec.channels !== first.channels || first.bitrate !== void 0 && spec.bitrate !== void 0 && spec.bitrate !== first.bitrate
|
|
367
|
+
(spec) => spec.sampleRate !== first.sampleRate || spec.channels !== first.channels || spec.codec !== first.codec || first.bitrate !== void 0 && spec.bitrate !== void 0 && spec.bitrate !== first.bitrate || first.bitDepth !== void 0 && spec.bitDepth !== void 0 && spec.bitDepth !== first.bitDepth || first.container !== void 0 && spec.container !== void 0 && spec.container !== first.container || first.isVbr !== void 0 && spec.isVbr !== void 0 && spec.isVbr !== first.isVbr
|
|
287
368
|
);
|
|
288
369
|
if (mismatch)
|
|
289
370
|
throw new AudioFormatMismatchError(
|
|
@@ -366,6 +447,30 @@ function isWavFormat(format) {
|
|
|
366
447
|
function isRawFormat(format) {
|
|
367
448
|
return /^raw(?:-|$)/i.test(format);
|
|
368
449
|
}
|
|
450
|
+
function validateMergedAudioBuffer(merged, format, buffers, inputSpecs, outputMimeType) {
|
|
451
|
+
if (!(merged instanceof ArrayBuffer) || merged.byteLength === 0) {
|
|
452
|
+
throw new MergeError("The custom audio merger returned an empty or invalid audio buffer.");
|
|
453
|
+
}
|
|
454
|
+
const specification = inspectAudioSpecification(merged, format);
|
|
455
|
+
if (!outputMimeType.trim()) throw new MergeError("The custom audio merger output MIME type cannot be empty.");
|
|
456
|
+
const firstInput = inputSpecs[0];
|
|
457
|
+
if (firstInput && (specification.sampleRate !== firstInput.sampleRate || specification.channels !== firstInput.channels || specification.codec !== firstInput.codec || firstInput.bitDepth !== void 0 && specification.bitDepth !== firstInput.bitDepth)) {
|
|
458
|
+
throw new AudioFormatMismatchError("The custom audio merger returned an incompatible audio stream.", [
|
|
459
|
+
...inputSpecs,
|
|
460
|
+
specification
|
|
461
|
+
]);
|
|
462
|
+
}
|
|
463
|
+
if (isRawFormat(format)) {
|
|
464
|
+
const expectedSize = buffers.reduce((total, input) => total + input.byteLength, 0);
|
|
465
|
+
if (merged.byteLength !== expectedSize) {
|
|
466
|
+
throw new MergeError(
|
|
467
|
+
`The custom audio merger returned ${merged.byteLength} bytes; ${expectedSize} were expected.`
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
validateRawAudioBuffer(merged, specification);
|
|
471
|
+
}
|
|
472
|
+
return specification;
|
|
473
|
+
}
|
|
369
474
|
function resolveMergeAudioFormat(format) {
|
|
370
475
|
if (isWavFormat(format)) return "wav";
|
|
371
476
|
if (isMp3Format(format)) return "mp3";
|
|
@@ -418,7 +523,7 @@ function closeSpeechResources(speechConfig, synthesizer) {
|
|
|
418
523
|
}
|
|
419
524
|
}
|
|
420
525
|
var ticksToMilliseconds = (ticks) => Math.max(0, ticks) / 1e4;
|
|
421
|
-
async function
|
|
526
|
+
async function synthesizeSsmlOnce(ssml, config) {
|
|
422
527
|
if (config.signal?.aborted) {
|
|
423
528
|
throw new SynthesisCancelledError();
|
|
424
529
|
}
|
|
@@ -486,9 +591,7 @@ async function synthesizeSsml(ssml, config) {
|
|
|
486
591
|
};
|
|
487
592
|
}
|
|
488
593
|
if (sourceSegments.length === 0 && !config.sourceTextRange && !config.sourceNodePath) {
|
|
489
|
-
|
|
490
|
-
Object.defineProperty(unmapped, "mappingStatus", { value: "unmapped", enumerable: false });
|
|
491
|
-
return unmapped;
|
|
594
|
+
return { mappingStatus: "unmapped" };
|
|
492
595
|
}
|
|
493
596
|
const value = text ?? "";
|
|
494
597
|
let localStart = Number.isFinite(offsetHint) && (offsetHint ?? 0) >= 0 ? offsetHint : -1;
|
|
@@ -550,6 +653,13 @@ async function synthesizeSsml(ssml, config) {
|
|
|
550
653
|
rejectWithError(err);
|
|
551
654
|
return;
|
|
552
655
|
}
|
|
656
|
+
let audioSpec;
|
|
657
|
+
try {
|
|
658
|
+
audioSpec = inspectAudioSpecification(result.audioData, config.outputFormat ?? DEFAULT_OUTPUT_FORMAT);
|
|
659
|
+
} catch (error) {
|
|
660
|
+
rejectWithError(error);
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
553
663
|
settled = true;
|
|
554
664
|
cleanup();
|
|
555
665
|
closeResources();
|
|
@@ -570,8 +680,13 @@ async function synthesizeSsml(ssml, config) {
|
|
|
570
680
|
...config.sourceNodePath ? { sourceNodePath: [...config.sourceNodePath] } : {},
|
|
571
681
|
...requestId ? { requestId } : {}
|
|
572
682
|
};
|
|
573
|
-
if (event.mappingStatus === "unmapped")
|
|
683
|
+
if (event.mappingStatus === "unmapped") {
|
|
574
684
|
Object.defineProperty(mapped, "mappingStatus", { value: "unmapped", enumerable: false });
|
|
685
|
+
Object.defineProperty(mapped, "toJSON", {
|
|
686
|
+
value: () => ({ ...mapped, mappingStatus: "unmapped" }),
|
|
687
|
+
enumerable: false
|
|
688
|
+
});
|
|
689
|
+
}
|
|
575
690
|
return mapped;
|
|
576
691
|
};
|
|
577
692
|
const sourceBoundaries = boundaries.map((boundary) => addSourceMetadata(boundary));
|
|
@@ -580,8 +695,8 @@ async function synthesizeSsml(ssml, config) {
|
|
|
580
695
|
resolve({
|
|
581
696
|
audioData: result.audioData,
|
|
582
697
|
durationMs,
|
|
583
|
-
audioSpec
|
|
584
|
-
mimeType: resolveMimeType(config.outputFormat ?? DEFAULT_OUTPUT_FORMAT),
|
|
698
|
+
audioSpec,
|
|
699
|
+
mimeType: config.outputMimeType ?? resolveMimeType(config.outputFormat ?? DEFAULT_OUTPUT_FORMAT),
|
|
585
700
|
...config.sourceTextRange ? { textRange: { ...config.sourceTextRange } } : {},
|
|
586
701
|
...requestId ? { requestId } : {},
|
|
587
702
|
...sourceBoundaries.length > 0 ? { boundaries: sourceBoundaries, wordBoundary: sourceBoundaries, wordBoundaries: sourceBoundaries } : {},
|
|
@@ -594,10 +709,11 @@ async function synthesizeSsml(ssml, config) {
|
|
|
594
709
|
abortHandler = () => rejectWithError(new SynthesisCancelledError());
|
|
595
710
|
config.signal.addEventListener("abort", abortHandler, { once: true });
|
|
596
711
|
}
|
|
597
|
-
|
|
712
|
+
const timeoutMs = config.timeouts?.perChunkMs ?? config.timeouts?.totalJobMs ?? config.timeoutMs;
|
|
713
|
+
if (timeoutMs !== void 0 && timeoutMs > 0) {
|
|
598
714
|
timeout = setTimeout(
|
|
599
|
-
() => rejectWithError(new SynthesisTimeoutError(`Speech synthesis timed out after ${
|
|
600
|
-
|
|
715
|
+
() => rejectWithError(new SynthesisTimeoutError(`Speech synthesis timed out after ${timeoutMs} ms.`)),
|
|
716
|
+
timeoutMs
|
|
601
717
|
);
|
|
602
718
|
}
|
|
603
719
|
synthesizer.speakSsmlAsync(ssml, cb, rejectWithError);
|
|
@@ -616,7 +732,9 @@ function isRetryableSynthesisError(error) {
|
|
|
616
732
|
if (typeof status === "number") return status === 429 || status >= 500 && status < 600;
|
|
617
733
|
return /network|connection|connect|socket|fetch failed|econn|etimedout|temporar|transient|unavailable/i.test(message);
|
|
618
734
|
}
|
|
619
|
-
function retryDelay(options, retryAttempt) {
|
|
735
|
+
function retryDelay(options, retryAttempt, error) {
|
|
736
|
+
const retryAfterMs = getRetryAfterDelayMs(error);
|
|
737
|
+
if (retryAfterMs !== void 0) return retryAfterMs;
|
|
620
738
|
const base = Math.min(options.maxDelayMs, options.initialDelayMs * 2 ** Math.max(0, retryAttempt - 1));
|
|
621
739
|
return Math.floor(Math.random() * (base + 1));
|
|
622
740
|
}
|
|
@@ -644,32 +762,102 @@ async function waitForRetry(delayMs, signal) {
|
|
|
644
762
|
}
|
|
645
763
|
});
|
|
646
764
|
}
|
|
647
|
-
async function synthesizeWithRetry(ssml, config, retryOptions, onRetry) {
|
|
765
|
+
async function synthesizeWithRetry(ssml, config, retryOptions, onRetry, deadlineAtMs) {
|
|
648
766
|
const options = retryOptions ? {
|
|
649
767
|
maxRetries: Math.max(0, Math.floor(retryOptions.maxRetries)),
|
|
650
768
|
initialDelayMs: Math.max(0, retryOptions.initialDelayMs),
|
|
651
|
-
maxDelayMs: Math.max(0, retryOptions.maxDelayMs)
|
|
769
|
+
maxDelayMs: Math.max(0, retryOptions.maxDelayMs),
|
|
770
|
+
shouldRetry: retryOptions.shouldRetry
|
|
652
771
|
} : void 0;
|
|
653
772
|
let attempt = 0;
|
|
654
773
|
while (true) {
|
|
655
774
|
if (config.signal?.aborted) throw new SynthesisCancelledError();
|
|
656
775
|
try {
|
|
657
|
-
return await
|
|
776
|
+
return await synthesizeSsmlOnce(ssml, config);
|
|
658
777
|
} catch (error) {
|
|
659
|
-
if (!options || attempt >= options.maxRetries || !
|
|
778
|
+
if (!options || attempt >= options.maxRetries || !(options.shouldRetry?.(error, attempt + 1) ?? isRetryableSynthesisError(error)))
|
|
779
|
+
throw error;
|
|
660
780
|
attempt += 1;
|
|
661
|
-
const delayMs = retryDelay(options, attempt);
|
|
781
|
+
const delayMs = retryDelay(options, attempt, error);
|
|
782
|
+
const remainingMs = deadlineAtMs === void 0 ? void 0 : Math.max(0, deadlineAtMs - Date.now());
|
|
783
|
+
if (getRetryAfterDelayMs(error) !== void 0 && (delayMs > options.maxDelayMs || remainingMs !== void 0 && delayMs > remainingMs)) {
|
|
784
|
+
throw new SynthesisTimeoutError(
|
|
785
|
+
remainingMs === void 0 ? `Retry-After exceeded maxDelayMs (${options.maxDelayMs} ms).` : `Retry-After exceeded the remaining total job timeout (${remainingMs} ms).`
|
|
786
|
+
);
|
|
787
|
+
}
|
|
662
788
|
onRetry(attempt, delayMs);
|
|
663
|
-
await waitForRetry(delayMs, config.signal);
|
|
789
|
+
await waitForRetry(Math.min(delayMs, remainingMs ?? delayMs), config.signal);
|
|
664
790
|
}
|
|
665
791
|
}
|
|
666
792
|
}
|
|
793
|
+
async function synthesizeSsml(ssml, config) {
|
|
794
|
+
const totalJobMs = config.timeouts?.totalJobMs;
|
|
795
|
+
const deadlineAtMs = totalJobMs !== void 0 && totalJobMs > 0 ? Date.now() + totalJobMs : void 0;
|
|
796
|
+
if (!config.retryOptions) return synthesizeSsmlOnce(ssml, config);
|
|
797
|
+
return synthesizeWithRetry(ssml, config, config.retryOptions, () => void 0, deadlineAtMs);
|
|
798
|
+
}
|
|
799
|
+
function createAbortScope(parent, timeoutMs) {
|
|
800
|
+
const controller = new AbortController();
|
|
801
|
+
let didTimeout = false;
|
|
802
|
+
const onAbort = () => controller.abort();
|
|
803
|
+
if (parent?.aborted) controller.abort();
|
|
804
|
+
parent?.addEventListener("abort", onAbort, { once: true });
|
|
805
|
+
const timer = timeoutMs !== void 0 && timeoutMs > 0 ? setTimeout(() => {
|
|
806
|
+
didTimeout = true;
|
|
807
|
+
controller.abort();
|
|
808
|
+
}, timeoutMs) : void 0;
|
|
809
|
+
return {
|
|
810
|
+
signal: controller.signal,
|
|
811
|
+
timedOut: () => didTimeout,
|
|
812
|
+
dispose: () => {
|
|
813
|
+
if (timer) clearTimeout(timer);
|
|
814
|
+
parent?.removeEventListener("abort", onAbort);
|
|
815
|
+
},
|
|
816
|
+
abort: () => controller.abort()
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
async function synthesizeChunkWithTimeout(ssml, config, retryOptions, timeoutMs, onRetry, deadlineAtMs) {
|
|
820
|
+
const scope = createAbortScope(config.signal, timeoutMs);
|
|
821
|
+
try {
|
|
822
|
+
return await synthesizeWithRetry(ssml, { ...config, signal: scope.signal }, retryOptions, onRetry, deadlineAtMs);
|
|
823
|
+
} catch (error) {
|
|
824
|
+
if (scope.timedOut()) throw new SynthesisTimeoutError(`Speech synthesis timed out after ${timeoutMs} ms.`);
|
|
825
|
+
throw error;
|
|
826
|
+
} finally {
|
|
827
|
+
scope.dispose();
|
|
828
|
+
}
|
|
829
|
+
}
|
|
667
830
|
async function synthesizeSsmlChunks(chunks, config) {
|
|
668
|
-
const results = new Array(chunks.length);
|
|
669
831
|
const totalChunks = chunks.length;
|
|
832
|
+
const inputs = chunks.map((chunk) => typeof chunk === "string" ? { ssml: chunk } : chunk);
|
|
833
|
+
const fingerprints = inputs.map(
|
|
834
|
+
(chunk) => computeChunkFingerprint(chunk.ssml, config.outputFormat ?? DEFAULT_OUTPUT_FORMAT)
|
|
835
|
+
);
|
|
836
|
+
const results = new Array(totalChunks);
|
|
837
|
+
const cachedChunks = new Map((config.resumeChunks ?? []).map((chunk) => [chunk.chunkIndex, chunk]));
|
|
838
|
+
const invalidCachedIndices = /* @__PURE__ */ new Set();
|
|
839
|
+
const chunkStates = inputs.map((_chunk, chunkIndex) => ({
|
|
840
|
+
chunkIndex,
|
|
841
|
+
status: "pending",
|
|
842
|
+
canResume: true
|
|
843
|
+
}));
|
|
844
|
+
for (const [index, cached] of cachedChunks) {
|
|
845
|
+
if (index < 0 || index >= totalChunks) continue;
|
|
846
|
+
const isValid = config.resumeValidation === "disabled" || cached.fingerprint === fingerprints[index];
|
|
847
|
+
if (isValid) {
|
|
848
|
+
results[index] = { ...cached };
|
|
849
|
+
chunkStates[index] = { chunkIndex: index, status: "succeeded", canResume: true, result: results[index] };
|
|
850
|
+
} else {
|
|
851
|
+
invalidCachedIndices.add(index);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
const requestedIndices = config.resumeChunkIndices ? new Set(config.resumeChunkIndices.filter((index) => index >= 0 && index < totalChunks)) : void 0;
|
|
855
|
+
const shouldSynthesize = (index) => (!cachedChunks.has(index) || invalidCachedIndices.has(index)) && (requestedIndices === void 0 || requestedIndices.has(index) || invalidCachedIndices.has(index));
|
|
856
|
+
const jobStartedAt = Date.now();
|
|
857
|
+
const jobDeadlineAt = config.timeouts?.totalJobMs !== void 0 && config.timeouts.totalJobMs > 0 ? jobStartedAt + config.timeouts.totalJobMs : void 0;
|
|
858
|
+
const scope = createAbortScope(config.signal, config.timeouts?.totalJobMs);
|
|
670
859
|
const report = (event) => config.onProgress?.(event);
|
|
671
|
-
for (const [index,
|
|
672
|
-
const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
|
|
860
|
+
for (const [index, input] of inputs.entries()) {
|
|
673
861
|
report({
|
|
674
862
|
currentChunk: index,
|
|
675
863
|
totalChunks,
|
|
@@ -680,15 +868,18 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
680
868
|
durationMs: 0
|
|
681
869
|
});
|
|
682
870
|
}
|
|
683
|
-
let completed = 0;
|
|
871
|
+
let completed = [...results].filter((result) => result !== void 0).length;
|
|
684
872
|
let nextIndex = 0;
|
|
685
873
|
const concurrency = resolveConcurrency(config.concurrency, chunks.length);
|
|
874
|
+
let firstError;
|
|
875
|
+
const failedIndices = /* @__PURE__ */ new Set();
|
|
686
876
|
const worker = async () => {
|
|
687
877
|
while (true) {
|
|
688
878
|
const index = nextIndex++;
|
|
689
879
|
if (index >= chunks.length) return;
|
|
690
|
-
|
|
691
|
-
|
|
880
|
+
if (!shouldSynthesize(index)) continue;
|
|
881
|
+
if (firstError && config.cancelOnFailure !== false) return;
|
|
882
|
+
const input = inputs[index];
|
|
692
883
|
report({
|
|
693
884
|
currentChunk: completed,
|
|
694
885
|
totalChunks,
|
|
@@ -700,10 +891,11 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
700
891
|
});
|
|
701
892
|
const startedAt = Date.now();
|
|
702
893
|
try {
|
|
703
|
-
const result = await
|
|
894
|
+
const result = await synthesizeChunkWithTimeout(
|
|
704
895
|
input.ssml,
|
|
705
896
|
{
|
|
706
897
|
...config,
|
|
898
|
+
signal: scope.signal,
|
|
707
899
|
...input.originalTextRange ? { sourceTextRange: input.originalTextRange } : {},
|
|
708
900
|
...input.sourceNodePath ?? config.sourceNodePath ? { sourceNodePath: [...input.sourceNodePath ?? config.sourceNodePath ?? []] } : {},
|
|
709
901
|
...input.sourceTextSegments ? { sourceTextSegments: input.sourceTextSegments } : {},
|
|
@@ -712,6 +904,7 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
712
904
|
onProgress: void 0
|
|
713
905
|
},
|
|
714
906
|
config.retryOptions,
|
|
907
|
+
config.timeouts?.chunkWithRetriesMs ?? config.timeouts?.perChunkMs ?? config.timeoutMs,
|
|
715
908
|
(retryAttempt, nextRetryDelayMs) => report({
|
|
716
909
|
currentChunk: completed,
|
|
717
910
|
totalChunks,
|
|
@@ -723,9 +916,11 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
723
916
|
retryAttempt,
|
|
724
917
|
nextRetryDelayMs,
|
|
725
918
|
isRetrying: true
|
|
726
|
-
})
|
|
919
|
+
}),
|
|
920
|
+
jobDeadlineAt
|
|
727
921
|
);
|
|
728
922
|
results[index] = result;
|
|
923
|
+
chunkStates[index] = { chunkIndex: index, status: "succeeded", canResume: true, result };
|
|
729
924
|
completed += 1;
|
|
730
925
|
report({
|
|
731
926
|
currentChunk: completed,
|
|
@@ -737,6 +932,16 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
737
932
|
durationMs: Date.now() - startedAt
|
|
738
933
|
});
|
|
739
934
|
} catch (error) {
|
|
935
|
+
const wasCancelled = firstError !== void 0 || scope.signal.aborted && !scope.timedOut();
|
|
936
|
+
firstError ?? (firstError = scope.timedOut() ? new SynthesisTimeoutError(`Speech synthesis timed out after ${config.timeouts?.totalJobMs} ms.`) : error);
|
|
937
|
+
if (!wasCancelled) failedIndices.add(index);
|
|
938
|
+
chunkStates[index] = {
|
|
939
|
+
chunkIndex: index,
|
|
940
|
+
status: wasCancelled ? "cancelled" : "failed",
|
|
941
|
+
isOriginalFailure: !wasCancelled,
|
|
942
|
+
canResume: true,
|
|
943
|
+
error
|
|
944
|
+
};
|
|
740
945
|
report({
|
|
741
946
|
currentChunk: completed,
|
|
742
947
|
totalChunks,
|
|
@@ -747,16 +952,49 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
747
952
|
durationMs: Date.now() - startedAt,
|
|
748
953
|
error
|
|
749
954
|
});
|
|
750
|
-
|
|
955
|
+
if (config.cancelOnFailure !== false) scope.abort();
|
|
956
|
+
return;
|
|
751
957
|
}
|
|
752
958
|
}
|
|
753
959
|
};
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
960
|
+
try {
|
|
961
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, chunks.length) }, () => worker()));
|
|
962
|
+
if (firstError) throw firstError;
|
|
963
|
+
const orderedResults = results.filter((result) => result !== void 0);
|
|
964
|
+
return await mergeSynthesisResults(orderedResults, {
|
|
965
|
+
format: config.outputFormat ?? DEFAULT_OUTPUT_FORMAT,
|
|
966
|
+
signal: scope.signal,
|
|
967
|
+
customMerger: config.customMerger,
|
|
968
|
+
outputMimeType: config.outputMimeType,
|
|
969
|
+
postMergeValidator: config.postMergeValidator
|
|
970
|
+
});
|
|
971
|
+
} catch (error) {
|
|
972
|
+
if (firstError && config.cancelOnFailure !== false) {
|
|
973
|
+
for (const [chunkIndex, state] of chunkStates.entries()) {
|
|
974
|
+
if (state.status === "pending" && shouldSynthesize(chunkIndex)) {
|
|
975
|
+
chunkStates[chunkIndex] = { chunkIndex, status: "cancelled", isOriginalFailure: false, canResume: true };
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
const synthesizedChunks = results.flatMap(
|
|
980
|
+
(result, chunkIndex) => result ? [{ ...result, chunkIndex, fingerprint: fingerprints[chunkIndex] ?? "" }] : []
|
|
981
|
+
);
|
|
982
|
+
const partial = {
|
|
983
|
+
synthesizedChunks,
|
|
984
|
+
completedChunks: synthesizedChunks,
|
|
985
|
+
pendingChunkIndices: chunkStates.flatMap(
|
|
986
|
+
(state) => state.status === "pending" || state.status === "cancelled" || state.status === "failed" ? [state.chunkIndex] : []
|
|
987
|
+
),
|
|
988
|
+
failedChunkIndices: [...failedIndices],
|
|
989
|
+
cancelledChunkIndices: chunkStates.filter((state) => state.status === "cancelled").map((state) => state.chunkIndex),
|
|
990
|
+
chunkStates,
|
|
991
|
+
totalChunks
|
|
992
|
+
};
|
|
993
|
+
if (error && typeof error === "object") error.partialResult = partial;
|
|
994
|
+
throw error;
|
|
995
|
+
} finally {
|
|
996
|
+
scope.dispose();
|
|
997
|
+
}
|
|
760
998
|
}
|
|
761
999
|
function createMergedResult(results, audioData, format, audioSpec, outputMimeType) {
|
|
762
1000
|
const boundaries = [];
|
|
@@ -847,16 +1085,26 @@ function mergeSynthesisResults(results, options) {
|
|
|
847
1085
|
})
|
|
848
1086
|
).then((merged) => {
|
|
849
1087
|
if (!merged) throw new MergeError("The custom audio merger returned no audio buffer.");
|
|
850
|
-
if (!(merged instanceof ArrayBuffer) || buffers.some((buffer) => buffer.byteLength > 0) && merged.byteLength === 0)
|
|
851
|
-
throw new MergeError("The custom audio merger returned an invalid audio buffer.");
|
|
852
1088
|
if (signal.aborted) throw new SynthesisCancelledError();
|
|
853
|
-
|
|
854
|
-
results,
|
|
1089
|
+
const mergedSpec = validateMergedAudioBuffer(
|
|
855
1090
|
merged,
|
|
856
1091
|
format,
|
|
857
|
-
|
|
858
|
-
|
|
1092
|
+
buffers,
|
|
1093
|
+
inputSpecs,
|
|
1094
|
+
resolvedOptions.outputMimeType ?? resolveMimeType(format)
|
|
859
1095
|
);
|
|
1096
|
+
const result = createMergedResult(results, merged, format, mergedSpec, resolvedOptions.outputMimeType);
|
|
1097
|
+
return Promise.resolve(
|
|
1098
|
+
resolvedOptions.postMergeValidator?.(result, {
|
|
1099
|
+
format,
|
|
1100
|
+
outputMimeType: resolvedOptions.outputMimeType ?? resolveMimeType(format),
|
|
1101
|
+
inputSpecs,
|
|
1102
|
+
signal
|
|
1103
|
+
})
|
|
1104
|
+
).then((valid) => {
|
|
1105
|
+
if (valid === false) throw new MergeError("The post-merge validator rejected the merged audio.");
|
|
1106
|
+
return result;
|
|
1107
|
+
});
|
|
860
1108
|
}).catch((error) => {
|
|
861
1109
|
if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
|
|
862
1110
|
throw error;
|
|
@@ -864,13 +1112,28 @@ function mergeSynthesisResults(results, options) {
|
|
|
864
1112
|
});
|
|
865
1113
|
}
|
|
866
1114
|
try {
|
|
867
|
-
|
|
1115
|
+
const result = createMergedResult(
|
|
868
1116
|
results,
|
|
869
1117
|
mergeAudioBuffers(buffers, { format }),
|
|
870
1118
|
format,
|
|
871
1119
|
inputSpecs[0],
|
|
872
1120
|
resolvedOptions.outputMimeType
|
|
873
1121
|
);
|
|
1122
|
+
if (resolvedOptions.postMergeValidator) {
|
|
1123
|
+
const validation = resolvedOptions.postMergeValidator(result, {
|
|
1124
|
+
format,
|
|
1125
|
+
outputMimeType: resolvedOptions.outputMimeType ?? resolveMimeType(format),
|
|
1126
|
+
inputSpecs,
|
|
1127
|
+
signal
|
|
1128
|
+
});
|
|
1129
|
+
if (validation instanceof Promise)
|
|
1130
|
+
return validation.then((valid) => {
|
|
1131
|
+
if (valid === false) throw new MergeError("The post-merge validator rejected the merged audio.");
|
|
1132
|
+
return result;
|
|
1133
|
+
});
|
|
1134
|
+
if (validation === false) throw new MergeError("The post-merge validator rejected the merged audio.");
|
|
1135
|
+
}
|
|
1136
|
+
return result;
|
|
874
1137
|
} catch (error) {
|
|
875
1138
|
if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
|
|
876
1139
|
throw error;
|
|
@@ -895,8 +1158,52 @@ var ChunkValidationError = class extends Error {
|
|
|
895
1158
|
this.diagnostics = diagnostics;
|
|
896
1159
|
}
|
|
897
1160
|
};
|
|
898
|
-
|
|
899
|
-
|
|
1161
|
+
var BatchChunkValidationError = class extends ChunkValidationError {
|
|
1162
|
+
constructor(chunkDiagnostics) {
|
|
1163
|
+
const first = chunkDiagnostics[0];
|
|
1164
|
+
super(first?.chunkIndex ?? -1, first?.diagnostics ?? []);
|
|
1165
|
+
this.name = "BatchChunkValidationError";
|
|
1166
|
+
this.message = `SSML validation failed for ${chunkDiagnostics.length} chunk(s); the Azure Speech API was not called.`;
|
|
1167
|
+
this.chunkDiagnostics = chunkDiagnostics;
|
|
1168
|
+
this.totalErrorCount = chunkDiagnostics.reduce((total, chunk) => total + chunk.diagnostics.length, 0);
|
|
1169
|
+
this.errorCount = this.totalErrorCount;
|
|
1170
|
+
this.totalErrors = this.totalErrorCount;
|
|
1171
|
+
}
|
|
1172
|
+
};
|
|
1173
|
+
function failure(error, partialResult) {
|
|
1174
|
+
return {
|
|
1175
|
+
ok: false,
|
|
1176
|
+
success: false,
|
|
1177
|
+
status: error.kind,
|
|
1178
|
+
error,
|
|
1179
|
+
...partialResult ? { partialResult } : {}
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
function partialResultFrom(error) {
|
|
1183
|
+
if (!error || typeof error !== "object") return void 0;
|
|
1184
|
+
const partial = error.partialResult;
|
|
1185
|
+
if (!partial || typeof partial !== "object") return void 0;
|
|
1186
|
+
return partial;
|
|
1187
|
+
}
|
|
1188
|
+
function createSafeAbortScope(parent, timeoutMs) {
|
|
1189
|
+
const controller = new AbortController();
|
|
1190
|
+
let didTimeout = false;
|
|
1191
|
+
const onAbort = () => controller.abort();
|
|
1192
|
+
if (parent?.aborted) controller.abort();
|
|
1193
|
+
parent?.addEventListener("abort", onAbort, { once: true });
|
|
1194
|
+
const timer = timeoutMs !== void 0 && timeoutMs > 0 ? setTimeout(() => {
|
|
1195
|
+
didTimeout = true;
|
|
1196
|
+
controller.abort();
|
|
1197
|
+
}, timeoutMs) : void 0;
|
|
1198
|
+
return {
|
|
1199
|
+
signal: controller.signal,
|
|
1200
|
+
timedOut: () => didTimeout,
|
|
1201
|
+
dispose: () => {
|
|
1202
|
+
if (timer) clearTimeout(timer);
|
|
1203
|
+
parent?.removeEventListener("abort", onAbort);
|
|
1204
|
+
},
|
|
1205
|
+
abort: () => controller.abort()
|
|
1206
|
+
};
|
|
900
1207
|
}
|
|
901
1208
|
function isRetryable(error) {
|
|
902
1209
|
if (error instanceof Error && /cancel|abort|tim(?:e|ed) ?out/i.test(error.message)) return false;
|
|
@@ -911,16 +1218,20 @@ function delayForRetry(options, attempt) {
|
|
|
911
1218
|
const base = Math.min(maxDelay, Math.max(0, options.initialDelayMs) * 2 ** Math.max(0, attempt - 1));
|
|
912
1219
|
return Math.floor(Math.random() * (base + 1));
|
|
913
1220
|
}
|
|
1221
|
+
function retryDelayForError(options, attempt, error) {
|
|
1222
|
+
return getRetryAfterDelayMs(error) ?? delayForRetry(options, attempt);
|
|
1223
|
+
}
|
|
914
1224
|
function resolveConcurrency2(value, total) {
|
|
915
1225
|
if (value === void 0) return 1;
|
|
916
1226
|
if (value === Infinity) return Math.max(1, total);
|
|
917
1227
|
return Number.isFinite(value) ? Math.max(1, Math.floor(value)) : 1;
|
|
918
1228
|
}
|
|
919
|
-
async function retryableSynthesis(synthesize, options, signal, onRetry) {
|
|
1229
|
+
async function retryableSynthesis(synthesize, options, signal, onRetry, deadlineAtMs) {
|
|
920
1230
|
const retry = options ? {
|
|
921
1231
|
maxRetries: Math.max(0, Math.floor(options.maxRetries)),
|
|
922
1232
|
initialDelayMs: options.initialDelayMs,
|
|
923
|
-
maxDelayMs: options.maxDelayMs
|
|
1233
|
+
maxDelayMs: options.maxDelayMs,
|
|
1234
|
+
shouldRetry: options.shouldRetry
|
|
924
1235
|
} : void 0;
|
|
925
1236
|
let attempt = 0;
|
|
926
1237
|
while (true) {
|
|
@@ -928,9 +1239,15 @@ async function retryableSynthesis(synthesize, options, signal, onRetry) {
|
|
|
928
1239
|
try {
|
|
929
1240
|
return await synthesize();
|
|
930
1241
|
} catch (error) {
|
|
931
|
-
if (!retry || attempt >= retry.maxRetries || !
|
|
1242
|
+
if (!retry || attempt >= retry.maxRetries || !(retry.shouldRetry?.(error, attempt + 1) ?? isRetryable(error)))
|
|
1243
|
+
throw error;
|
|
932
1244
|
attempt += 1;
|
|
933
|
-
const delayMs =
|
|
1245
|
+
const delayMs = retryDelayForError(retry, attempt, error);
|
|
1246
|
+
const retryAfterMs = getRetryAfterDelayMs(error);
|
|
1247
|
+
const remainingMs = deadlineAtMs === void 0 ? void 0 : Math.max(0, deadlineAtMs - Date.now());
|
|
1248
|
+
if (retryAfterMs !== void 0 && (retryAfterMs > retry.maxDelayMs || remainingMs !== void 0 && retryAfterMs > remainingMs)) {
|
|
1249
|
+
throw new Error("Speech synthesis timed out because Retry-After exceeded the available retry budget.");
|
|
1250
|
+
}
|
|
934
1251
|
onRetry(attempt, delayMs);
|
|
935
1252
|
if (delayMs > 0)
|
|
936
1253
|
await new Promise((resolve, reject) => {
|
|
@@ -954,7 +1271,7 @@ function sharedValidationOptions(options, signal) {
|
|
|
954
1271
|
const runner = createAzureUrlValidatorRunner(validator, {
|
|
955
1272
|
...options.urlValidation ?? {},
|
|
956
1273
|
...options.urlValidatorConcurrency !== void 0 ? { concurrency: options.urlValidatorConcurrency } : {},
|
|
957
|
-
...options.urlValidatorTimeoutMs !== void 0 ? { timeoutMs: options.urlValidatorTimeoutMs } : {},
|
|
1274
|
+
...options.timeouts?.urlValidationMs !== void 0 ? { timeoutMs: options.timeouts.urlValidationMs } : options.urlValidatorTimeoutMs !== void 0 ? { timeoutMs: options.urlValidatorTimeoutMs } : {},
|
|
958
1275
|
...signal ? { signal } : {},
|
|
959
1276
|
...options.urlValidatorCache ? { cache: options.urlValidatorCache } : {}
|
|
960
1277
|
});
|
|
@@ -978,20 +1295,31 @@ async function synthesizeSsmlSafe(client, ssml, options = {}) {
|
|
|
978
1295
|
diagnostics: errors
|
|
979
1296
|
});
|
|
980
1297
|
}
|
|
1298
|
+
const jobScope = options.timeouts?.totalJobMs !== void 0 ? createSafeAbortScope(options.signal, options.timeouts.totalJobMs) : void 0;
|
|
981
1299
|
try {
|
|
982
1300
|
return {
|
|
983
1301
|
ok: true,
|
|
984
1302
|
success: true,
|
|
985
1303
|
status: "success",
|
|
986
|
-
value: await client.synthesizeSsml(ssml, {
|
|
1304
|
+
value: await client.synthesizeSsml(ssml, {
|
|
1305
|
+
signal: jobScope?.signal ?? options.signal,
|
|
1306
|
+
timeoutMs: options.timeouts?.perChunkMs,
|
|
1307
|
+
timeouts: options.timeouts
|
|
1308
|
+
})
|
|
987
1309
|
};
|
|
988
1310
|
} catch (error) {
|
|
1311
|
+
if (jobScope?.timedOut()) return failure(toSynthesisError(new Error("Speech synthesis timed out.")));
|
|
989
1312
|
const synthesisError = toSynthesisError(error);
|
|
990
1313
|
return failure(synthesisError);
|
|
1314
|
+
} finally {
|
|
1315
|
+
jobScope?.dispose();
|
|
991
1316
|
}
|
|
992
1317
|
}
|
|
993
1318
|
async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
994
|
-
const validationOptions = sharedValidationOptions(
|
|
1319
|
+
const validationOptions = sharedValidationOptions(
|
|
1320
|
+
{ ...options.validation ?? options, timeouts: options.timeouts },
|
|
1321
|
+
options.signal
|
|
1322
|
+
);
|
|
995
1323
|
if (options.signal?.aborted) {
|
|
996
1324
|
const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
|
|
997
1325
|
return failure(error);
|
|
@@ -1025,16 +1353,17 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
1025
1353
|
return diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
1026
1354
|
})
|
|
1027
1355
|
);
|
|
1028
|
-
const firstInvalidIndex = validations.findIndex((diagnostics) => diagnostics.length > 0);
|
|
1029
1356
|
if (options.signal?.aborted) {
|
|
1030
1357
|
const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
|
|
1031
1358
|
return failure(error);
|
|
1032
1359
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1360
|
+
const chunkDiagnostics = validations.map((diagnostics, chunkIndex) => ({ chunkIndex, diagnostics })).filter((entry) => entry.diagnostics.length > 0);
|
|
1361
|
+
if (chunkDiagnostics.length > 0) {
|
|
1362
|
+
const error = new BatchChunkValidationError(chunkDiagnostics);
|
|
1363
|
+
for (const entry of chunkDiagnostics) pending(entry.chunkIndex, "failed", error);
|
|
1036
1364
|
return failure(error);
|
|
1037
1365
|
}
|
|
1366
|
+
let fallbackJobScope;
|
|
1038
1367
|
try {
|
|
1039
1368
|
if (client.synthesizeChunks) {
|
|
1040
1369
|
const normalizedChunks = chunks.map((chunk) => {
|
|
@@ -1046,20 +1375,57 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
1046
1375
|
outputFormat: options.outputFormat,
|
|
1047
1376
|
signal: options.signal,
|
|
1048
1377
|
timeoutMs: options.timeoutMs,
|
|
1378
|
+
timeouts: options.timeouts,
|
|
1049
1379
|
sourceNodePath: options.sourceNodePath,
|
|
1050
1380
|
concurrency: options.concurrency,
|
|
1051
|
-
retryOptions: options.retryOptions
|
|
1381
|
+
retryOptions: options.retryOptions,
|
|
1382
|
+
cancelOnFailure: options.cancelOnFailure,
|
|
1383
|
+
resumeChunks: options.resumeChunks,
|
|
1384
|
+
resumeChunkIndices: options.resumeChunkIndices,
|
|
1385
|
+
customMerger: options.customMerger,
|
|
1386
|
+
outputMimeType: options.outputMimeType,
|
|
1387
|
+
postMergeValidator: options.postMergeValidator,
|
|
1388
|
+
resumeValidation: options.resumeValidation
|
|
1052
1389
|
});
|
|
1053
1390
|
return { ok: true, success: true, status: "success", value };
|
|
1054
1391
|
}
|
|
1392
|
+
const inputs = chunks.map((chunk) => typeof chunk === "string" ? { ssml: chunk } : chunk);
|
|
1393
|
+
const fingerprints = inputs.map((chunk) => computeChunkFingerprint(chunk.ssml, options.outputFormat));
|
|
1055
1394
|
const results = new Array(chunks.length);
|
|
1056
|
-
|
|
1395
|
+
const chunkStates = inputs.map((_chunk, chunkIndex) => ({
|
|
1396
|
+
chunkIndex,
|
|
1397
|
+
status: "pending",
|
|
1398
|
+
canResume: true
|
|
1399
|
+
}));
|
|
1400
|
+
const cachedChunks = new Map((options.resumeChunks ?? []).map((chunk) => [chunk.chunkIndex, chunk]));
|
|
1401
|
+
const invalidCachedIndices = /* @__PURE__ */ new Set();
|
|
1402
|
+
for (const [index, cached] of cachedChunks) {
|
|
1403
|
+
if (index < 0 || index >= chunks.length) continue;
|
|
1404
|
+
if (options.resumeValidation === "disabled" || cached.fingerprint === fingerprints[index]) {
|
|
1405
|
+
results[index] = cached;
|
|
1406
|
+
chunkStates[index] = { chunkIndex: index, status: "succeeded", canResume: true, result: cached };
|
|
1407
|
+
} else invalidCachedIndices.add(index);
|
|
1408
|
+
}
|
|
1409
|
+
const requestedIndices = options.resumeChunkIndices ? new Set(options.resumeChunkIndices.filter((index) => index >= 0 && index < chunks.length)) : void 0;
|
|
1410
|
+
const shouldSynthesize = (index) => (!cachedChunks.has(index) || invalidCachedIndices.has(index)) && (requestedIndices === void 0 || requestedIndices.has(index) || invalidCachedIndices.has(index));
|
|
1411
|
+
const jobStartedAt = Date.now();
|
|
1412
|
+
const jobDeadlineAt = options.timeouts?.totalJobMs !== void 0 && options.timeouts.totalJobMs > 0 ? jobStartedAt + options.timeouts.totalJobMs : void 0;
|
|
1413
|
+
const jobScope = chunks.length > 1 || options.timeouts?.totalJobMs !== void 0 ? createSafeAbortScope(options.signal, options.timeouts?.totalJobMs) : void 0;
|
|
1414
|
+
fallbackJobScope = jobScope;
|
|
1415
|
+
const failedIndices = /* @__PURE__ */ new Set();
|
|
1416
|
+
let firstError;
|
|
1417
|
+
let completed = [...results].filter((result) => result !== void 0).length;
|
|
1057
1418
|
let nextIndex = 0;
|
|
1058
1419
|
const concurrency = resolveConcurrency2(options.concurrency, chunks.length);
|
|
1059
1420
|
const worker = async () => {
|
|
1060
1421
|
while (true) {
|
|
1061
1422
|
const index = nextIndex++;
|
|
1062
1423
|
if (index >= chunks.length) return;
|
|
1424
|
+
if (!shouldSynthesize(index)) continue;
|
|
1425
|
+
if (firstError && options.cancelOnFailure !== false) {
|
|
1426
|
+
chunkStates[index] = { chunkIndex: index, status: "cancelled", isOriginalFailure: false, canResume: true };
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1063
1429
|
const chunk = chunks[index];
|
|
1064
1430
|
const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
|
|
1065
1431
|
const sourceNodePath = input.sourceNodePath;
|
|
@@ -1067,28 +1433,41 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
1067
1433
|
pending(index, "synthesizing");
|
|
1068
1434
|
const startedAt = Date.now();
|
|
1069
1435
|
try {
|
|
1070
|
-
const
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1436
|
+
const chunkTimeout = options.timeouts?.chunkWithRetriesMs ?? options.timeouts?.perChunkMs;
|
|
1437
|
+
const chunkScope = chunkTimeout !== void 0 || jobScope ? createSafeAbortScope(jobScope?.signal ?? options.signal, chunkTimeout ?? options.timeoutMs) : void 0;
|
|
1438
|
+
const chunkSignal = chunkScope?.signal ?? options.signal;
|
|
1439
|
+
let result;
|
|
1440
|
+
try {
|
|
1441
|
+
result = await retryableSynthesis(
|
|
1442
|
+
() => client.synthesizeSsml(input.ssml, {
|
|
1443
|
+
outputFormat: options.outputFormat,
|
|
1444
|
+
signal: chunkSignal,
|
|
1445
|
+
timeoutMs: options.timeouts?.perChunkMs ?? options.timeoutMs,
|
|
1446
|
+
sourceNodePath: input.sourceNodePath ?? options.sourceNodePath
|
|
1447
|
+
}),
|
|
1448
|
+
options.retryOptions,
|
|
1449
|
+
chunkSignal,
|
|
1450
|
+
(retryAttempt, nextRetryDelayMs) => options.onProgress?.({
|
|
1451
|
+
currentChunk: completed,
|
|
1452
|
+
totalChunks: chunks.length,
|
|
1453
|
+
percent: chunks.length === 0 ? 100 : Math.round(completed / chunks.length * 100),
|
|
1454
|
+
chunkIndex: index,
|
|
1455
|
+
originalTextRange: input.originalTextRange,
|
|
1456
|
+
status: "synthesizing",
|
|
1457
|
+
durationMs: Date.now() - startedAt,
|
|
1458
|
+
retryAttempt,
|
|
1459
|
+
nextRetryDelayMs,
|
|
1460
|
+
isRetrying: true
|
|
1461
|
+
}),
|
|
1462
|
+
jobDeadlineAt
|
|
1463
|
+
);
|
|
1464
|
+
} catch (error) {
|
|
1465
|
+
if (chunkScope?.timedOut())
|
|
1466
|
+
throw new Error(`Speech synthesis timed out after ${chunkTimeout ?? options.timeoutMs} ms.`);
|
|
1467
|
+
throw error;
|
|
1468
|
+
} finally {
|
|
1469
|
+
chunkScope?.dispose();
|
|
1470
|
+
}
|
|
1092
1471
|
results[index] = {
|
|
1093
1472
|
...result,
|
|
1094
1473
|
...input.originalTextRange ? { textRange: { ...input.originalTextRange } } : {},
|
|
@@ -1132,6 +1511,7 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
1132
1511
|
}))
|
|
1133
1512
|
} : {}
|
|
1134
1513
|
};
|
|
1514
|
+
chunkStates[index] = { chunkIndex: index, status: "succeeded", canResume: true, result: results[index] };
|
|
1135
1515
|
completed += 1;
|
|
1136
1516
|
options.onProgress?.({
|
|
1137
1517
|
currentChunk: completed,
|
|
@@ -1143,6 +1523,16 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
1143
1523
|
durationMs: Date.now() - startedAt
|
|
1144
1524
|
});
|
|
1145
1525
|
} catch (error) {
|
|
1526
|
+
const wasCancelled = firstError !== void 0 || Boolean(jobScope?.signal.aborted && !jobScope?.timedOut());
|
|
1527
|
+
firstError ?? (firstError = error);
|
|
1528
|
+
if (!wasCancelled) failedIndices.add(index);
|
|
1529
|
+
chunkStates[index] = {
|
|
1530
|
+
chunkIndex: index,
|
|
1531
|
+
status: wasCancelled ? "cancelled" : "failed",
|
|
1532
|
+
isOriginalFailure: !wasCancelled,
|
|
1533
|
+
canResume: true,
|
|
1534
|
+
error
|
|
1535
|
+
};
|
|
1146
1536
|
options.onProgress?.({
|
|
1147
1537
|
currentChunk: completed,
|
|
1148
1538
|
totalChunks: chunks.length,
|
|
@@ -1153,24 +1543,55 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
1153
1543
|
durationMs: Date.now() - startedAt,
|
|
1154
1544
|
error
|
|
1155
1545
|
});
|
|
1156
|
-
|
|
1546
|
+
if (options.cancelOnFailure !== false) jobScope?.abort();
|
|
1547
|
+
return;
|
|
1157
1548
|
}
|
|
1158
1549
|
}
|
|
1159
1550
|
};
|
|
1160
1551
|
await Promise.all(Array.from({ length: Math.min(concurrency, chunks.length) }, () => worker()));
|
|
1552
|
+
if (firstError && options.cancelOnFailure !== false) {
|
|
1553
|
+
for (const [chunkIndex, state] of chunkStates.entries()) {
|
|
1554
|
+
if (state.status === "pending" && shouldSynthesize(chunkIndex)) {
|
|
1555
|
+
chunkStates[chunkIndex] = { chunkIndex, status: "cancelled", isOriginalFailure: false, canResume: true };
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
if (failedIndices.size > 0) {
|
|
1560
|
+
const error = firstError ?? new Error("One or more SSML chunks failed to synthesize.");
|
|
1561
|
+
const synthesizedChunks = results.flatMap(
|
|
1562
|
+
(result, chunkIndex) => result ? [{ ...result, chunkIndex, fingerprint: fingerprints[chunkIndex] ?? "" }] : []
|
|
1563
|
+
);
|
|
1564
|
+
error.partialResult = {
|
|
1565
|
+
synthesizedChunks,
|
|
1566
|
+
completedChunks: synthesizedChunks,
|
|
1567
|
+
pendingChunkIndices: chunkStates.flatMap(
|
|
1568
|
+
(state) => state.status === "pending" || state.status === "cancelled" || state.status === "failed" ? [state.chunkIndex] : []
|
|
1569
|
+
),
|
|
1570
|
+
failedChunkIndices: [...failedIndices],
|
|
1571
|
+
cancelledChunkIndices: chunkStates.filter((state) => state.status === "cancelled").map((state) => state.chunkIndex),
|
|
1572
|
+
chunkStates,
|
|
1573
|
+
totalChunks: chunks.length
|
|
1574
|
+
};
|
|
1575
|
+
throw error;
|
|
1576
|
+
}
|
|
1161
1577
|
const orderedResults = results.filter((result) => result !== void 0);
|
|
1162
1578
|
return {
|
|
1163
1579
|
ok: true,
|
|
1164
1580
|
success: true,
|
|
1165
1581
|
status: "success",
|
|
1166
|
-
value: mergeSynthesisResults(orderedResults, {
|
|
1582
|
+
value: await mergeSynthesisResults(orderedResults, {
|
|
1167
1583
|
format: options.outputFormat ?? "audio-16khz-128kbitrate-mono-mp3",
|
|
1168
|
-
signal: options.signal
|
|
1584
|
+
signal: jobScope?.signal ?? options.signal,
|
|
1585
|
+
customMerger: options.customMerger,
|
|
1586
|
+
outputMimeType: options.outputMimeType,
|
|
1587
|
+
postMergeValidator: options.postMergeValidator
|
|
1169
1588
|
})
|
|
1170
1589
|
};
|
|
1171
1590
|
} catch (error) {
|
|
1172
1591
|
const synthesisError = toSynthesisError(error);
|
|
1173
|
-
return failure(synthesisError);
|
|
1592
|
+
return failure(synthesisError, partialResultFrom(error));
|
|
1593
|
+
} finally {
|
|
1594
|
+
fallbackJobScope?.dispose();
|
|
1174
1595
|
}
|
|
1175
1596
|
}
|
|
1176
1597
|
function withValidationSignal(options, signal) {
|
|
@@ -1191,14 +1612,23 @@ var AzureTtsClient = class {
|
|
|
1191
1612
|
__privateSet(this, _options, options);
|
|
1192
1613
|
}
|
|
1193
1614
|
async synthesize(ssml) {
|
|
1194
|
-
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
1615
|
+
const { region, subscriptionKey, outputFormat, signal, timeoutMs, timeouts } = __privateGet(this, _options);
|
|
1195
1616
|
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
1196
1617
|
__privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
|
|
1197
|
-
const config = {
|
|
1618
|
+
const config = {
|
|
1619
|
+
endpoint,
|
|
1620
|
+
region,
|
|
1621
|
+
subscriptionKey,
|
|
1622
|
+
outputFormat,
|
|
1623
|
+
signal,
|
|
1624
|
+
timeoutMs,
|
|
1625
|
+
timeouts,
|
|
1626
|
+
retryOptions: __privateGet(this, _options).retryOptions
|
|
1627
|
+
};
|
|
1198
1628
|
return synthesizeSpeech(ssml, config);
|
|
1199
1629
|
}
|
|
1200
1630
|
async synthesizeSsml(ssml, options = {}) {
|
|
1201
|
-
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
1631
|
+
const { region, subscriptionKey, outputFormat, signal, timeoutMs, timeouts } = __privateGet(this, _options);
|
|
1202
1632
|
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
1203
1633
|
__privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
|
|
1204
1634
|
return synthesizeSsml(ssml, {
|
|
@@ -1208,13 +1638,20 @@ var AzureTtsClient = class {
|
|
|
1208
1638
|
outputFormat: options.outputFormat ?? outputFormat,
|
|
1209
1639
|
signal: options.signal ?? signal,
|
|
1210
1640
|
timeoutMs: options.timeoutMs ?? timeoutMs,
|
|
1641
|
+
timeouts: options.timeouts ?? timeouts,
|
|
1211
1642
|
sourceNodePath: options.sourceNodePath,
|
|
1212
1643
|
sourceTextSegments: options.sourceTextSegments,
|
|
1213
|
-
sourceMarkers: options.sourceMarkers
|
|
1644
|
+
sourceMarkers: options.sourceMarkers,
|
|
1645
|
+
retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions,
|
|
1646
|
+
cancelOnFailure: options.cancelOnFailure ?? __privateGet(this, _options).cancelOnFailure,
|
|
1647
|
+
customMerger: options.customMerger ?? __privateGet(this, _options).customMerger,
|
|
1648
|
+
outputMimeType: options.outputMimeType ?? __privateGet(this, _options).outputMimeType,
|
|
1649
|
+
postMergeValidator: options.postMergeValidator ?? __privateGet(this, _options).postMergeValidator,
|
|
1650
|
+
resumeValidation: options.resumeValidation ?? __privateGet(this, _options).resumeValidation
|
|
1214
1651
|
});
|
|
1215
1652
|
}
|
|
1216
1653
|
async synthesizeChunks(chunks, options = {}) {
|
|
1217
|
-
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
1654
|
+
const { region, subscriptionKey, outputFormat, signal, timeoutMs, timeouts } = __privateGet(this, _options);
|
|
1218
1655
|
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
1219
1656
|
return synthesizeSsmlChunks(chunks, {
|
|
1220
1657
|
endpoint,
|
|
@@ -1223,10 +1660,18 @@ var AzureTtsClient = class {
|
|
|
1223
1660
|
outputFormat: options.outputFormat ?? outputFormat,
|
|
1224
1661
|
signal: options.signal ?? signal,
|
|
1225
1662
|
timeoutMs: options.timeoutMs ?? timeoutMs,
|
|
1663
|
+
timeouts: options.timeouts ?? timeouts,
|
|
1226
1664
|
sourceNodePath: options.sourceNodePath,
|
|
1227
1665
|
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress,
|
|
1228
1666
|
concurrency: options.concurrency ?? __privateGet(this, _options).concurrency,
|
|
1229
|
-
retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions
|
|
1667
|
+
retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions,
|
|
1668
|
+
cancelOnFailure: options.cancelOnFailure ?? __privateGet(this, _options).cancelOnFailure,
|
|
1669
|
+
resumeChunks: options.resumeChunks,
|
|
1670
|
+
resumeChunkIndices: options.resumeChunkIndices,
|
|
1671
|
+
customMerger: options.customMerger ?? __privateGet(this, _options).customMerger,
|
|
1672
|
+
outputMimeType: options.outputMimeType ?? __privateGet(this, _options).outputMimeType,
|
|
1673
|
+
postMergeValidator: options.postMergeValidator ?? __privateGet(this, _options).postMergeValidator,
|
|
1674
|
+
resumeValidation: options.resumeValidation ?? __privateGet(this, _options).resumeValidation
|
|
1230
1675
|
});
|
|
1231
1676
|
}
|
|
1232
1677
|
async synthesizeSsmlSafe(ssml, options = {}) {
|
|
@@ -1238,6 +1683,7 @@ var AzureTtsClient = class {
|
|
|
1238
1683
|
outputFormat: options.outputFormat ?? __privateGet(this, _options).outputFormat,
|
|
1239
1684
|
signal: options.signal ?? __privateGet(this, _options).signal,
|
|
1240
1685
|
timeoutMs: options.timeoutMs ?? __privateGet(this, _options).timeoutMs,
|
|
1686
|
+
timeouts: options.timeouts ?? __privateGet(this, _options).timeouts,
|
|
1241
1687
|
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress,
|
|
1242
1688
|
concurrency: options.concurrency ?? __privateGet(this, _options).concurrency,
|
|
1243
1689
|
retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions
|
|
@@ -1331,7 +1777,9 @@ async function fetchAzureVoiceCatalog(options) {
|
|
|
1331
1777
|
voiceCount: sortedVoices.length,
|
|
1332
1778
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1333
1779
|
apiVersion: AZURE_VOICE_API_VERSION,
|
|
1334
|
-
regions
|
|
1780
|
+
regions,
|
|
1781
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3).toISOString(),
|
|
1782
|
+
regionDiffs: {}
|
|
1335
1783
|
}
|
|
1336
1784
|
};
|
|
1337
1785
|
}
|
|
@@ -1340,6 +1788,7 @@ export {
|
|
|
1340
1788
|
AzureTtsClient,
|
|
1341
1789
|
AzureTtsError,
|
|
1342
1790
|
AzureTtsSdkError,
|
|
1791
|
+
BatchChunkValidationError,
|
|
1343
1792
|
ChunkValidationError,
|
|
1344
1793
|
DEFAULT_OUTPUT_FORMAT,
|
|
1345
1794
|
MergeError,
|
|
@@ -1347,7 +1796,9 @@ export {
|
|
|
1347
1796
|
SynthesisTimeoutError,
|
|
1348
1797
|
UnsupportedMergeFormatError,
|
|
1349
1798
|
canMergeAudioFormat,
|
|
1799
|
+
computeChunkFingerprint,
|
|
1350
1800
|
fetchAzureVoiceCatalog,
|
|
1801
|
+
getRetryAfterDelayMs,
|
|
1351
1802
|
inspectAudioSpecification,
|
|
1352
1803
|
mergeAudioBuffers,
|
|
1353
1804
|
mergeSynthesisResults,
|