@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.js
CHANGED
|
@@ -41,6 +41,7 @@ __export(index_exports, {
|
|
|
41
41
|
AzureTtsClient: () => AzureTtsClient,
|
|
42
42
|
AzureTtsError: () => AzureTtsError,
|
|
43
43
|
AzureTtsSdkError: () => AzureTtsSdkError,
|
|
44
|
+
BatchChunkValidationError: () => BatchChunkValidationError,
|
|
44
45
|
ChunkValidationError: () => ChunkValidationError,
|
|
45
46
|
DEFAULT_OUTPUT_FORMAT: () => DEFAULT_OUTPUT_FORMAT,
|
|
46
47
|
MergeError: () => MergeError,
|
|
@@ -48,7 +49,9 @@ __export(index_exports, {
|
|
|
48
49
|
SynthesisTimeoutError: () => SynthesisTimeoutError,
|
|
49
50
|
UnsupportedMergeFormatError: () => UnsupportedMergeFormatError,
|
|
50
51
|
canMergeAudioFormat: () => canMergeAudioFormat,
|
|
52
|
+
computeChunkFingerprint: () => computeChunkFingerprint,
|
|
51
53
|
fetchAzureVoiceCatalog: () => fetchAzureVoiceCatalog,
|
|
54
|
+
getRetryAfterDelayMs: () => getRetryAfterDelayMs,
|
|
52
55
|
inspectAudioSpecification: () => inspectAudioSpecification,
|
|
53
56
|
mergeAudioBuffers: () => mergeAudioBuffers,
|
|
54
57
|
mergeSynthesisResults: () => mergeSynthesisResults,
|
|
@@ -64,7 +67,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
64
67
|
|
|
65
68
|
// src/errors.ts
|
|
66
69
|
var AzureTtsError = class extends Error {
|
|
67
|
-
constructor(status, statusText, responseBody, requestId) {
|
|
70
|
+
constructor(status, statusText, responseBody, requestId, responseHeaders) {
|
|
68
71
|
super(`Azure TTS request failed: ${status} ${statusText}`);
|
|
69
72
|
this.kind = "azure-api-error";
|
|
70
73
|
this.name = "AzureTtsError";
|
|
@@ -72,8 +75,37 @@ var AzureTtsError = class extends Error {
|
|
|
72
75
|
this.statusText = statusText;
|
|
73
76
|
this.responseBody = responseBody;
|
|
74
77
|
this.requestId = requestId;
|
|
78
|
+
const value = responseHeaders instanceof Headers ? responseHeaders.get("retry-after") : responseHeaders?.["retry-after"] ?? responseHeaders?.["Retry-After"];
|
|
79
|
+
const seconds = value ? Number(value.trim()) : NaN;
|
|
80
|
+
const date = value ? Date.parse(value) : NaN;
|
|
81
|
+
if (Number.isFinite(seconds) && seconds >= 0) this.retryAfterMs = seconds * 1e3;
|
|
82
|
+
else if (Number.isFinite(date)) this.retryAfterMs = Math.max(0, date - Date.now());
|
|
75
83
|
}
|
|
76
84
|
};
|
|
85
|
+
function getRetryAfterDelayMs(error) {
|
|
86
|
+
if (error instanceof AzureTtsError && error.retryAfterMs !== void 0) return error.retryAfterMs;
|
|
87
|
+
if (!error || typeof error !== "object") return void 0;
|
|
88
|
+
const candidate = error;
|
|
89
|
+
if (typeof candidate.retryAfterMs === "number" && candidate.retryAfterMs >= 0) return candidate.retryAfterMs;
|
|
90
|
+
const headers = candidate.headers ?? candidate.response?.headers;
|
|
91
|
+
if (headers instanceof Headers) {
|
|
92
|
+
const value = headers.get("retry-after");
|
|
93
|
+
if (!value) return void 0;
|
|
94
|
+
const seconds = Number(value.trim());
|
|
95
|
+
if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
|
|
96
|
+
const date = Date.parse(value);
|
|
97
|
+
return Number.isFinite(date) ? Math.max(0, date - Date.now()) : void 0;
|
|
98
|
+
}
|
|
99
|
+
if (headers && typeof headers === "object") {
|
|
100
|
+
const value = headers["retry-after"] ?? headers["Retry-After"];
|
|
101
|
+
if (typeof value !== "string") return void 0;
|
|
102
|
+
const seconds = Number(value.trim());
|
|
103
|
+
if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
|
|
104
|
+
const date = Date.parse(value);
|
|
105
|
+
return Number.isFinite(date) ? Math.max(0, date - Date.now()) : void 0;
|
|
106
|
+
}
|
|
107
|
+
return void 0;
|
|
108
|
+
}
|
|
77
109
|
var AzureTtsSdkError = class extends AzureTtsError {
|
|
78
110
|
constructor(errorDetails) {
|
|
79
111
|
super(0, "Speech SDK", errorDetails, null);
|
|
@@ -184,6 +216,9 @@ var OUTPUT_FORMATS = {
|
|
|
184
216
|
function resolveMimeType(outputFormat) {
|
|
185
217
|
if (/(?:wav|wave|riff)/i.test(outputFormat)) return "audio/wav";
|
|
186
218
|
if (/(?:mp3|mpeg)/i.test(outputFormat)) return "audio/mpeg";
|
|
219
|
+
if (/mulaw|mu-law/i.test(outputFormat)) return "audio/basic";
|
|
220
|
+
if (/alaw|a-law/i.test(outputFormat)) return "audio/alaw";
|
|
221
|
+
if (/siren/i.test(outputFormat)) return "audio/siren";
|
|
187
222
|
if (/ogg/i.test(outputFormat)) return "audio/ogg";
|
|
188
223
|
if (/webm/i.test(outputFormat)) return "audio/webm";
|
|
189
224
|
if (/raw/i.test(outputFormat)) return "audio/L16";
|
|
@@ -212,6 +247,27 @@ function createSpeechConfig(config) {
|
|
|
212
247
|
}
|
|
213
248
|
|
|
214
249
|
// src/synthesis.ts
|
|
250
|
+
function computeChunkFingerprint(ssml, outputFormat = DEFAULT_OUTPUT_FORMAT) {
|
|
251
|
+
const readAttribute = (name) => {
|
|
252
|
+
const pattern = new RegExp(`(?:${name})\\s*=\\s*[\\"']([^\\"']*)`, "gi");
|
|
253
|
+
return [...ssml.matchAll(pattern)].map((match) => match[1] ?? "").join("|");
|
|
254
|
+
};
|
|
255
|
+
const payload = JSON.stringify({
|
|
256
|
+
ssml,
|
|
257
|
+
outputFormat,
|
|
258
|
+
voice: readAttribute("(?:name|voice)"),
|
|
259
|
+
language: readAttribute("(?:xml:lang|lang)"),
|
|
260
|
+
rate: readAttribute("rate"),
|
|
261
|
+
pitch: readAttribute("pitch")
|
|
262
|
+
});
|
|
263
|
+
let hash = 0xcbf29ce484222325n;
|
|
264
|
+
const mask = 0xffffffffffffffffn;
|
|
265
|
+
for (let index = 0; index < payload.length; index += 1) {
|
|
266
|
+
hash ^= BigInt(payload.charCodeAt(index));
|
|
267
|
+
hash = hash * 0x100000001b3n & mask;
|
|
268
|
+
}
|
|
269
|
+
return `fnv1a64-${hash.toString(16).padStart(16, "0")}`;
|
|
270
|
+
}
|
|
215
271
|
function ascii(bytes, offset, value) {
|
|
216
272
|
return [...value].every((character, index) => bytes[offset + index] === character.charCodeAt(0));
|
|
217
273
|
}
|
|
@@ -251,9 +307,11 @@ function parseWav(buffer) {
|
|
|
251
307
|
}
|
|
252
308
|
return { chunks, data, format };
|
|
253
309
|
}
|
|
254
|
-
function
|
|
255
|
-
const match =
|
|
256
|
-
|
|
310
|
+
function formatSampleRate(format) {
|
|
311
|
+
const match = /(?:^|-)(\d+)(khz|hz)(?:-|$)/i.exec(format);
|
|
312
|
+
if (!match?.[1] || !match[2]) return 0;
|
|
313
|
+
const value = Number(match[1]);
|
|
314
|
+
return match[2].toLowerCase() === "khz" ? value * 1e3 : value;
|
|
257
315
|
}
|
|
258
316
|
function formatChannels(format, fallback) {
|
|
259
317
|
if (/stereo|2ch|dual/i.test(format)) return 2;
|
|
@@ -261,11 +319,13 @@ function formatChannels(format, fallback) {
|
|
|
261
319
|
return fallback;
|
|
262
320
|
}
|
|
263
321
|
function formatAudioSpecification(format) {
|
|
264
|
-
const sampleRate =
|
|
322
|
+
const sampleRate = formatSampleRate(format);
|
|
265
323
|
const channels = formatChannels(format, 0);
|
|
266
324
|
const bitrateMatch = /(\d+)(?:kbitrate|kbps|kbits?)/i.exec(format);
|
|
267
325
|
const bitrate = bitrateMatch?.[1] ? Number(bitrateMatch[1]) * 1e3 : void 0;
|
|
268
|
-
const codec = /mp3|mpeg/i.test(format) ? "mp3" : /opus/i.test(format) ? "opus" : /silk/i.test(format) ? "silk" : /
|
|
326
|
+
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";
|
|
327
|
+
const bitDepthMatch = /(\d+)bit/i.exec(format);
|
|
328
|
+
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;
|
|
269
329
|
return {
|
|
270
330
|
format,
|
|
271
331
|
mimeType: resolveMimeType(format),
|
|
@@ -273,7 +333,10 @@ function formatAudioSpecification(format) {
|
|
|
273
333
|
sampleRate,
|
|
274
334
|
channels,
|
|
275
335
|
...bitrate ? { bitrate } : {},
|
|
276
|
-
|
|
336
|
+
...bitDepthMatch?.[1] ? { bitDepth: Number(bitDepthMatch[1]) } : {},
|
|
337
|
+
...container ? { container } : {},
|
|
338
|
+
isVbr: /vbr/i.test(format),
|
|
339
|
+
isCompressed: codec !== "pcm" && codec !== "unknown"
|
|
277
340
|
};
|
|
278
341
|
}
|
|
279
342
|
function parseMp3Specification(buffer, format) {
|
|
@@ -308,6 +371,8 @@ function parseMp3Specification(buffer, format) {
|
|
|
308
371
|
sampleRate,
|
|
309
372
|
channels: (bytes[index + 3] ?? 0) >> 6 === 3 ? 1 : 2,
|
|
310
373
|
bitrate: bitrateKbps * 1e3,
|
|
374
|
+
container: "mp3-raw",
|
|
375
|
+
isVbr: false,
|
|
311
376
|
isCompressed: true
|
|
312
377
|
};
|
|
313
378
|
}
|
|
@@ -322,24 +387,43 @@ function inspectAudioSpecification(buffer, format) {
|
|
|
322
387
|
const channels = view.getUint16(2, true);
|
|
323
388
|
const bitsPerSample = parsed.format.byteLength >= 16 ? view.getUint16(14, true) : 0;
|
|
324
389
|
const formatCode = view.getUint16(0, true);
|
|
390
|
+
const namedCodec = formatAudioSpecification(format).codec;
|
|
391
|
+
const codec = formatCode === 1 ? "pcm" : formatCode === 6 ? "alaw" : formatCode === 7 ? "mulaw" : namedCodec === "siren" ? "siren" : "unknown";
|
|
325
392
|
return {
|
|
326
393
|
format,
|
|
327
394
|
mimeType: "audio/wav",
|
|
328
|
-
codec
|
|
395
|
+
codec,
|
|
329
396
|
sampleRate,
|
|
330
397
|
channels,
|
|
331
398
|
...sampleRate && channels && bitsPerSample ? { bitrate: sampleRate * channels * bitsPerSample } : {},
|
|
332
|
-
|
|
399
|
+
bitDepth: bitsPerSample,
|
|
400
|
+
container: "riff-wave",
|
|
401
|
+
isVbr: false,
|
|
402
|
+
isCompressed: codec !== "pcm" && codec !== "unknown"
|
|
333
403
|
};
|
|
334
404
|
}
|
|
335
405
|
if (isMp3Format(format)) return parseMp3Specification(buffer, format) ?? formatAudioSpecification(format);
|
|
336
|
-
|
|
406
|
+
const specification = formatAudioSpecification(format);
|
|
407
|
+
if (specification.container === "raw") validateRawAudioBuffer(buffer, specification);
|
|
408
|
+
return specification;
|
|
409
|
+
}
|
|
410
|
+
function validateRawAudioBuffer(buffer, specification) {
|
|
411
|
+
if (specification.sampleRate <= 0 || specification.channels <= 0 || specification.bitDepth === void 0) {
|
|
412
|
+
throw new Error(`RAW audio format "${specification.format}" does not define a complete audio specification.`);
|
|
413
|
+
}
|
|
414
|
+
if (specification.codec === "siren" || specification.codec === "silk") return;
|
|
415
|
+
const bytesPerFrame = specification.channels * Math.ceil(specification.bitDepth / 8);
|
|
416
|
+
if (bytesPerFrame <= 0 || buffer.byteLength % bytesPerFrame !== 0) {
|
|
417
|
+
throw new Error(
|
|
418
|
+
`RAW audio buffer size ${buffer.byteLength} is not aligned to ${bytesPerFrame}-byte audio frames for "${specification.format}".`
|
|
419
|
+
);
|
|
420
|
+
}
|
|
337
421
|
}
|
|
338
422
|
function validateAudioSpecifications(specs) {
|
|
339
423
|
const first = specs[0];
|
|
340
424
|
if (!first) return;
|
|
341
425
|
const mismatch = specs.find(
|
|
342
|
-
(spec) => spec.sampleRate !== first.sampleRate || spec.channels !== first.channels || first.bitrate !== void 0 && spec.bitrate !== void 0 && spec.bitrate !== first.bitrate
|
|
426
|
+
(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
|
|
343
427
|
);
|
|
344
428
|
if (mismatch)
|
|
345
429
|
throw new AudioFormatMismatchError(
|
|
@@ -422,6 +506,30 @@ function isWavFormat(format) {
|
|
|
422
506
|
function isRawFormat(format) {
|
|
423
507
|
return /^raw(?:-|$)/i.test(format);
|
|
424
508
|
}
|
|
509
|
+
function validateMergedAudioBuffer(merged, format, buffers, inputSpecs, outputMimeType) {
|
|
510
|
+
if (!(merged instanceof ArrayBuffer) || merged.byteLength === 0) {
|
|
511
|
+
throw new MergeError("The custom audio merger returned an empty or invalid audio buffer.");
|
|
512
|
+
}
|
|
513
|
+
const specification = inspectAudioSpecification(merged, format);
|
|
514
|
+
if (!outputMimeType.trim()) throw new MergeError("The custom audio merger output MIME type cannot be empty.");
|
|
515
|
+
const firstInput = inputSpecs[0];
|
|
516
|
+
if (firstInput && (specification.sampleRate !== firstInput.sampleRate || specification.channels !== firstInput.channels || specification.codec !== firstInput.codec || firstInput.bitDepth !== void 0 && specification.bitDepth !== firstInput.bitDepth)) {
|
|
517
|
+
throw new AudioFormatMismatchError("The custom audio merger returned an incompatible audio stream.", [
|
|
518
|
+
...inputSpecs,
|
|
519
|
+
specification
|
|
520
|
+
]);
|
|
521
|
+
}
|
|
522
|
+
if (isRawFormat(format)) {
|
|
523
|
+
const expectedSize = buffers.reduce((total, input) => total + input.byteLength, 0);
|
|
524
|
+
if (merged.byteLength !== expectedSize) {
|
|
525
|
+
throw new MergeError(
|
|
526
|
+
`The custom audio merger returned ${merged.byteLength} bytes; ${expectedSize} were expected.`
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
validateRawAudioBuffer(merged, specification);
|
|
530
|
+
}
|
|
531
|
+
return specification;
|
|
532
|
+
}
|
|
425
533
|
function resolveMergeAudioFormat(format) {
|
|
426
534
|
if (isWavFormat(format)) return "wav";
|
|
427
535
|
if (isMp3Format(format)) return "mp3";
|
|
@@ -474,7 +582,7 @@ function closeSpeechResources(speechConfig, synthesizer) {
|
|
|
474
582
|
}
|
|
475
583
|
}
|
|
476
584
|
var ticksToMilliseconds = (ticks) => Math.max(0, ticks) / 1e4;
|
|
477
|
-
async function
|
|
585
|
+
async function synthesizeSsmlOnce(ssml, config) {
|
|
478
586
|
if (config.signal?.aborted) {
|
|
479
587
|
throw new SynthesisCancelledError();
|
|
480
588
|
}
|
|
@@ -542,9 +650,7 @@ async function synthesizeSsml(ssml, config) {
|
|
|
542
650
|
};
|
|
543
651
|
}
|
|
544
652
|
if (sourceSegments.length === 0 && !config.sourceTextRange && !config.sourceNodePath) {
|
|
545
|
-
|
|
546
|
-
Object.defineProperty(unmapped, "mappingStatus", { value: "unmapped", enumerable: false });
|
|
547
|
-
return unmapped;
|
|
653
|
+
return { mappingStatus: "unmapped" };
|
|
548
654
|
}
|
|
549
655
|
const value = text ?? "";
|
|
550
656
|
let localStart = Number.isFinite(offsetHint) && (offsetHint ?? 0) >= 0 ? offsetHint : -1;
|
|
@@ -606,6 +712,13 @@ async function synthesizeSsml(ssml, config) {
|
|
|
606
712
|
rejectWithError(err);
|
|
607
713
|
return;
|
|
608
714
|
}
|
|
715
|
+
let audioSpec;
|
|
716
|
+
try {
|
|
717
|
+
audioSpec = inspectAudioSpecification(result.audioData, config.outputFormat ?? DEFAULT_OUTPUT_FORMAT);
|
|
718
|
+
} catch (error) {
|
|
719
|
+
rejectWithError(error);
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
609
722
|
settled = true;
|
|
610
723
|
cleanup();
|
|
611
724
|
closeResources();
|
|
@@ -626,8 +739,13 @@ async function synthesizeSsml(ssml, config) {
|
|
|
626
739
|
...config.sourceNodePath ? { sourceNodePath: [...config.sourceNodePath] } : {},
|
|
627
740
|
...requestId ? { requestId } : {}
|
|
628
741
|
};
|
|
629
|
-
if (event.mappingStatus === "unmapped")
|
|
742
|
+
if (event.mappingStatus === "unmapped") {
|
|
630
743
|
Object.defineProperty(mapped, "mappingStatus", { value: "unmapped", enumerable: false });
|
|
744
|
+
Object.defineProperty(mapped, "toJSON", {
|
|
745
|
+
value: () => ({ ...mapped, mappingStatus: "unmapped" }),
|
|
746
|
+
enumerable: false
|
|
747
|
+
});
|
|
748
|
+
}
|
|
631
749
|
return mapped;
|
|
632
750
|
};
|
|
633
751
|
const sourceBoundaries = boundaries.map((boundary) => addSourceMetadata(boundary));
|
|
@@ -636,8 +754,8 @@ async function synthesizeSsml(ssml, config) {
|
|
|
636
754
|
resolve({
|
|
637
755
|
audioData: result.audioData,
|
|
638
756
|
durationMs,
|
|
639
|
-
audioSpec
|
|
640
|
-
mimeType: resolveMimeType(config.outputFormat ?? DEFAULT_OUTPUT_FORMAT),
|
|
757
|
+
audioSpec,
|
|
758
|
+
mimeType: config.outputMimeType ?? resolveMimeType(config.outputFormat ?? DEFAULT_OUTPUT_FORMAT),
|
|
641
759
|
...config.sourceTextRange ? { textRange: { ...config.sourceTextRange } } : {},
|
|
642
760
|
...requestId ? { requestId } : {},
|
|
643
761
|
...sourceBoundaries.length > 0 ? { boundaries: sourceBoundaries, wordBoundary: sourceBoundaries, wordBoundaries: sourceBoundaries } : {},
|
|
@@ -650,10 +768,11 @@ async function synthesizeSsml(ssml, config) {
|
|
|
650
768
|
abortHandler = () => rejectWithError(new SynthesisCancelledError());
|
|
651
769
|
config.signal.addEventListener("abort", abortHandler, { once: true });
|
|
652
770
|
}
|
|
653
|
-
|
|
771
|
+
const timeoutMs = config.timeouts?.perChunkMs ?? config.timeouts?.totalJobMs ?? config.timeoutMs;
|
|
772
|
+
if (timeoutMs !== void 0 && timeoutMs > 0) {
|
|
654
773
|
timeout = setTimeout(
|
|
655
|
-
() => rejectWithError(new SynthesisTimeoutError(`Speech synthesis timed out after ${
|
|
656
|
-
|
|
774
|
+
() => rejectWithError(new SynthesisTimeoutError(`Speech synthesis timed out after ${timeoutMs} ms.`)),
|
|
775
|
+
timeoutMs
|
|
657
776
|
);
|
|
658
777
|
}
|
|
659
778
|
synthesizer.speakSsmlAsync(ssml, cb, rejectWithError);
|
|
@@ -672,7 +791,9 @@ function isRetryableSynthesisError(error) {
|
|
|
672
791
|
if (typeof status === "number") return status === 429 || status >= 500 && status < 600;
|
|
673
792
|
return /network|connection|connect|socket|fetch failed|econn|etimedout|temporar|transient|unavailable/i.test(message);
|
|
674
793
|
}
|
|
675
|
-
function retryDelay(options, retryAttempt) {
|
|
794
|
+
function retryDelay(options, retryAttempt, error) {
|
|
795
|
+
const retryAfterMs = getRetryAfterDelayMs(error);
|
|
796
|
+
if (retryAfterMs !== void 0) return retryAfterMs;
|
|
676
797
|
const base = Math.min(options.maxDelayMs, options.initialDelayMs * 2 ** Math.max(0, retryAttempt - 1));
|
|
677
798
|
return Math.floor(Math.random() * (base + 1));
|
|
678
799
|
}
|
|
@@ -700,32 +821,102 @@ async function waitForRetry(delayMs, signal) {
|
|
|
700
821
|
}
|
|
701
822
|
});
|
|
702
823
|
}
|
|
703
|
-
async function synthesizeWithRetry(ssml, config, retryOptions, onRetry) {
|
|
824
|
+
async function synthesizeWithRetry(ssml, config, retryOptions, onRetry, deadlineAtMs) {
|
|
704
825
|
const options = retryOptions ? {
|
|
705
826
|
maxRetries: Math.max(0, Math.floor(retryOptions.maxRetries)),
|
|
706
827
|
initialDelayMs: Math.max(0, retryOptions.initialDelayMs),
|
|
707
|
-
maxDelayMs: Math.max(0, retryOptions.maxDelayMs)
|
|
828
|
+
maxDelayMs: Math.max(0, retryOptions.maxDelayMs),
|
|
829
|
+
shouldRetry: retryOptions.shouldRetry
|
|
708
830
|
} : void 0;
|
|
709
831
|
let attempt = 0;
|
|
710
832
|
while (true) {
|
|
711
833
|
if (config.signal?.aborted) throw new SynthesisCancelledError();
|
|
712
834
|
try {
|
|
713
|
-
return await
|
|
835
|
+
return await synthesizeSsmlOnce(ssml, config);
|
|
714
836
|
} catch (error) {
|
|
715
|
-
if (!options || attempt >= options.maxRetries || !
|
|
837
|
+
if (!options || attempt >= options.maxRetries || !(options.shouldRetry?.(error, attempt + 1) ?? isRetryableSynthesisError(error)))
|
|
838
|
+
throw error;
|
|
716
839
|
attempt += 1;
|
|
717
|
-
const delayMs = retryDelay(options, attempt);
|
|
840
|
+
const delayMs = retryDelay(options, attempt, error);
|
|
841
|
+
const remainingMs = deadlineAtMs === void 0 ? void 0 : Math.max(0, deadlineAtMs - Date.now());
|
|
842
|
+
if (getRetryAfterDelayMs(error) !== void 0 && (delayMs > options.maxDelayMs || remainingMs !== void 0 && delayMs > remainingMs)) {
|
|
843
|
+
throw new SynthesisTimeoutError(
|
|
844
|
+
remainingMs === void 0 ? `Retry-After exceeded maxDelayMs (${options.maxDelayMs} ms).` : `Retry-After exceeded the remaining total job timeout (${remainingMs} ms).`
|
|
845
|
+
);
|
|
846
|
+
}
|
|
718
847
|
onRetry(attempt, delayMs);
|
|
719
|
-
await waitForRetry(delayMs, config.signal);
|
|
848
|
+
await waitForRetry(Math.min(delayMs, remainingMs ?? delayMs), config.signal);
|
|
720
849
|
}
|
|
721
850
|
}
|
|
722
851
|
}
|
|
852
|
+
async function synthesizeSsml(ssml, config) {
|
|
853
|
+
const totalJobMs = config.timeouts?.totalJobMs;
|
|
854
|
+
const deadlineAtMs = totalJobMs !== void 0 && totalJobMs > 0 ? Date.now() + totalJobMs : void 0;
|
|
855
|
+
if (!config.retryOptions) return synthesizeSsmlOnce(ssml, config);
|
|
856
|
+
return synthesizeWithRetry(ssml, config, config.retryOptions, () => void 0, deadlineAtMs);
|
|
857
|
+
}
|
|
858
|
+
function createAbortScope(parent, timeoutMs) {
|
|
859
|
+
const controller = new AbortController();
|
|
860
|
+
let didTimeout = false;
|
|
861
|
+
const onAbort = () => controller.abort();
|
|
862
|
+
if (parent?.aborted) controller.abort();
|
|
863
|
+
parent?.addEventListener("abort", onAbort, { once: true });
|
|
864
|
+
const timer = timeoutMs !== void 0 && timeoutMs > 0 ? setTimeout(() => {
|
|
865
|
+
didTimeout = true;
|
|
866
|
+
controller.abort();
|
|
867
|
+
}, timeoutMs) : void 0;
|
|
868
|
+
return {
|
|
869
|
+
signal: controller.signal,
|
|
870
|
+
timedOut: () => didTimeout,
|
|
871
|
+
dispose: () => {
|
|
872
|
+
if (timer) clearTimeout(timer);
|
|
873
|
+
parent?.removeEventListener("abort", onAbort);
|
|
874
|
+
},
|
|
875
|
+
abort: () => controller.abort()
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
async function synthesizeChunkWithTimeout(ssml, config, retryOptions, timeoutMs, onRetry, deadlineAtMs) {
|
|
879
|
+
const scope = createAbortScope(config.signal, timeoutMs);
|
|
880
|
+
try {
|
|
881
|
+
return await synthesizeWithRetry(ssml, { ...config, signal: scope.signal }, retryOptions, onRetry, deadlineAtMs);
|
|
882
|
+
} catch (error) {
|
|
883
|
+
if (scope.timedOut()) throw new SynthesisTimeoutError(`Speech synthesis timed out after ${timeoutMs} ms.`);
|
|
884
|
+
throw error;
|
|
885
|
+
} finally {
|
|
886
|
+
scope.dispose();
|
|
887
|
+
}
|
|
888
|
+
}
|
|
723
889
|
async function synthesizeSsmlChunks(chunks, config) {
|
|
724
|
-
const results = new Array(chunks.length);
|
|
725
890
|
const totalChunks = chunks.length;
|
|
891
|
+
const inputs = chunks.map((chunk) => typeof chunk === "string" ? { ssml: chunk } : chunk);
|
|
892
|
+
const fingerprints = inputs.map(
|
|
893
|
+
(chunk) => computeChunkFingerprint(chunk.ssml, config.outputFormat ?? DEFAULT_OUTPUT_FORMAT)
|
|
894
|
+
);
|
|
895
|
+
const results = new Array(totalChunks);
|
|
896
|
+
const cachedChunks = new Map((config.resumeChunks ?? []).map((chunk) => [chunk.chunkIndex, chunk]));
|
|
897
|
+
const invalidCachedIndices = /* @__PURE__ */ new Set();
|
|
898
|
+
const chunkStates = inputs.map((_chunk, chunkIndex) => ({
|
|
899
|
+
chunkIndex,
|
|
900
|
+
status: "pending",
|
|
901
|
+
canResume: true
|
|
902
|
+
}));
|
|
903
|
+
for (const [index, cached] of cachedChunks) {
|
|
904
|
+
if (index < 0 || index >= totalChunks) continue;
|
|
905
|
+
const isValid = config.resumeValidation === "disabled" || cached.fingerprint === fingerprints[index];
|
|
906
|
+
if (isValid) {
|
|
907
|
+
results[index] = { ...cached };
|
|
908
|
+
chunkStates[index] = { chunkIndex: index, status: "succeeded", canResume: true, result: results[index] };
|
|
909
|
+
} else {
|
|
910
|
+
invalidCachedIndices.add(index);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
const requestedIndices = config.resumeChunkIndices ? new Set(config.resumeChunkIndices.filter((index) => index >= 0 && index < totalChunks)) : void 0;
|
|
914
|
+
const shouldSynthesize = (index) => (!cachedChunks.has(index) || invalidCachedIndices.has(index)) && (requestedIndices === void 0 || requestedIndices.has(index) || invalidCachedIndices.has(index));
|
|
915
|
+
const jobStartedAt = Date.now();
|
|
916
|
+
const jobDeadlineAt = config.timeouts?.totalJobMs !== void 0 && config.timeouts.totalJobMs > 0 ? jobStartedAt + config.timeouts.totalJobMs : void 0;
|
|
917
|
+
const scope = createAbortScope(config.signal, config.timeouts?.totalJobMs);
|
|
726
918
|
const report = (event) => config.onProgress?.(event);
|
|
727
|
-
for (const [index,
|
|
728
|
-
const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
|
|
919
|
+
for (const [index, input] of inputs.entries()) {
|
|
729
920
|
report({
|
|
730
921
|
currentChunk: index,
|
|
731
922
|
totalChunks,
|
|
@@ -736,15 +927,18 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
736
927
|
durationMs: 0
|
|
737
928
|
});
|
|
738
929
|
}
|
|
739
|
-
let completed = 0;
|
|
930
|
+
let completed = [...results].filter((result) => result !== void 0).length;
|
|
740
931
|
let nextIndex = 0;
|
|
741
932
|
const concurrency = resolveConcurrency(config.concurrency, chunks.length);
|
|
933
|
+
let firstError;
|
|
934
|
+
const failedIndices = /* @__PURE__ */ new Set();
|
|
742
935
|
const worker = async () => {
|
|
743
936
|
while (true) {
|
|
744
937
|
const index = nextIndex++;
|
|
745
938
|
if (index >= chunks.length) return;
|
|
746
|
-
|
|
747
|
-
|
|
939
|
+
if (!shouldSynthesize(index)) continue;
|
|
940
|
+
if (firstError && config.cancelOnFailure !== false) return;
|
|
941
|
+
const input = inputs[index];
|
|
748
942
|
report({
|
|
749
943
|
currentChunk: completed,
|
|
750
944
|
totalChunks,
|
|
@@ -756,10 +950,11 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
756
950
|
});
|
|
757
951
|
const startedAt = Date.now();
|
|
758
952
|
try {
|
|
759
|
-
const result = await
|
|
953
|
+
const result = await synthesizeChunkWithTimeout(
|
|
760
954
|
input.ssml,
|
|
761
955
|
{
|
|
762
956
|
...config,
|
|
957
|
+
signal: scope.signal,
|
|
763
958
|
...input.originalTextRange ? { sourceTextRange: input.originalTextRange } : {},
|
|
764
959
|
...input.sourceNodePath ?? config.sourceNodePath ? { sourceNodePath: [...input.sourceNodePath ?? config.sourceNodePath ?? []] } : {},
|
|
765
960
|
...input.sourceTextSegments ? { sourceTextSegments: input.sourceTextSegments } : {},
|
|
@@ -768,6 +963,7 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
768
963
|
onProgress: void 0
|
|
769
964
|
},
|
|
770
965
|
config.retryOptions,
|
|
966
|
+
config.timeouts?.chunkWithRetriesMs ?? config.timeouts?.perChunkMs ?? config.timeoutMs,
|
|
771
967
|
(retryAttempt, nextRetryDelayMs) => report({
|
|
772
968
|
currentChunk: completed,
|
|
773
969
|
totalChunks,
|
|
@@ -779,9 +975,11 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
779
975
|
retryAttempt,
|
|
780
976
|
nextRetryDelayMs,
|
|
781
977
|
isRetrying: true
|
|
782
|
-
})
|
|
978
|
+
}),
|
|
979
|
+
jobDeadlineAt
|
|
783
980
|
);
|
|
784
981
|
results[index] = result;
|
|
982
|
+
chunkStates[index] = { chunkIndex: index, status: "succeeded", canResume: true, result };
|
|
785
983
|
completed += 1;
|
|
786
984
|
report({
|
|
787
985
|
currentChunk: completed,
|
|
@@ -793,6 +991,16 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
793
991
|
durationMs: Date.now() - startedAt
|
|
794
992
|
});
|
|
795
993
|
} catch (error) {
|
|
994
|
+
const wasCancelled = firstError !== void 0 || scope.signal.aborted && !scope.timedOut();
|
|
995
|
+
firstError ?? (firstError = scope.timedOut() ? new SynthesisTimeoutError(`Speech synthesis timed out after ${config.timeouts?.totalJobMs} ms.`) : error);
|
|
996
|
+
if (!wasCancelled) failedIndices.add(index);
|
|
997
|
+
chunkStates[index] = {
|
|
998
|
+
chunkIndex: index,
|
|
999
|
+
status: wasCancelled ? "cancelled" : "failed",
|
|
1000
|
+
isOriginalFailure: !wasCancelled,
|
|
1001
|
+
canResume: true,
|
|
1002
|
+
error
|
|
1003
|
+
};
|
|
796
1004
|
report({
|
|
797
1005
|
currentChunk: completed,
|
|
798
1006
|
totalChunks,
|
|
@@ -803,16 +1011,49 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
803
1011
|
durationMs: Date.now() - startedAt,
|
|
804
1012
|
error
|
|
805
1013
|
});
|
|
806
|
-
|
|
1014
|
+
if (config.cancelOnFailure !== false) scope.abort();
|
|
1015
|
+
return;
|
|
807
1016
|
}
|
|
808
1017
|
}
|
|
809
1018
|
};
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
1019
|
+
try {
|
|
1020
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, chunks.length) }, () => worker()));
|
|
1021
|
+
if (firstError) throw firstError;
|
|
1022
|
+
const orderedResults = results.filter((result) => result !== void 0);
|
|
1023
|
+
return await mergeSynthesisResults(orderedResults, {
|
|
1024
|
+
format: config.outputFormat ?? DEFAULT_OUTPUT_FORMAT,
|
|
1025
|
+
signal: scope.signal,
|
|
1026
|
+
customMerger: config.customMerger,
|
|
1027
|
+
outputMimeType: config.outputMimeType,
|
|
1028
|
+
postMergeValidator: config.postMergeValidator
|
|
1029
|
+
});
|
|
1030
|
+
} catch (error) {
|
|
1031
|
+
if (firstError && config.cancelOnFailure !== false) {
|
|
1032
|
+
for (const [chunkIndex, state] of chunkStates.entries()) {
|
|
1033
|
+
if (state.status === "pending" && shouldSynthesize(chunkIndex)) {
|
|
1034
|
+
chunkStates[chunkIndex] = { chunkIndex, status: "cancelled", isOriginalFailure: false, canResume: true };
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
const synthesizedChunks = results.flatMap(
|
|
1039
|
+
(result, chunkIndex) => result ? [{ ...result, chunkIndex, fingerprint: fingerprints[chunkIndex] ?? "" }] : []
|
|
1040
|
+
);
|
|
1041
|
+
const partial = {
|
|
1042
|
+
synthesizedChunks,
|
|
1043
|
+
completedChunks: synthesizedChunks,
|
|
1044
|
+
pendingChunkIndices: chunkStates.flatMap(
|
|
1045
|
+
(state) => state.status === "pending" || state.status === "cancelled" || state.status === "failed" ? [state.chunkIndex] : []
|
|
1046
|
+
),
|
|
1047
|
+
failedChunkIndices: [...failedIndices],
|
|
1048
|
+
cancelledChunkIndices: chunkStates.filter((state) => state.status === "cancelled").map((state) => state.chunkIndex),
|
|
1049
|
+
chunkStates,
|
|
1050
|
+
totalChunks
|
|
1051
|
+
};
|
|
1052
|
+
if (error && typeof error === "object") error.partialResult = partial;
|
|
1053
|
+
throw error;
|
|
1054
|
+
} finally {
|
|
1055
|
+
scope.dispose();
|
|
1056
|
+
}
|
|
816
1057
|
}
|
|
817
1058
|
function createMergedResult(results, audioData, format, audioSpec, outputMimeType) {
|
|
818
1059
|
const boundaries = [];
|
|
@@ -903,16 +1144,26 @@ function mergeSynthesisResults(results, options) {
|
|
|
903
1144
|
})
|
|
904
1145
|
).then((merged) => {
|
|
905
1146
|
if (!merged) throw new MergeError("The custom audio merger returned no audio buffer.");
|
|
906
|
-
if (!(merged instanceof ArrayBuffer) || buffers.some((buffer) => buffer.byteLength > 0) && merged.byteLength === 0)
|
|
907
|
-
throw new MergeError("The custom audio merger returned an invalid audio buffer.");
|
|
908
1147
|
if (signal.aborted) throw new SynthesisCancelledError();
|
|
909
|
-
|
|
910
|
-
results,
|
|
1148
|
+
const mergedSpec = validateMergedAudioBuffer(
|
|
911
1149
|
merged,
|
|
912
1150
|
format,
|
|
913
|
-
|
|
914
|
-
|
|
1151
|
+
buffers,
|
|
1152
|
+
inputSpecs,
|
|
1153
|
+
resolvedOptions.outputMimeType ?? resolveMimeType(format)
|
|
915
1154
|
);
|
|
1155
|
+
const result = createMergedResult(results, merged, format, mergedSpec, resolvedOptions.outputMimeType);
|
|
1156
|
+
return Promise.resolve(
|
|
1157
|
+
resolvedOptions.postMergeValidator?.(result, {
|
|
1158
|
+
format,
|
|
1159
|
+
outputMimeType: resolvedOptions.outputMimeType ?? resolveMimeType(format),
|
|
1160
|
+
inputSpecs,
|
|
1161
|
+
signal
|
|
1162
|
+
})
|
|
1163
|
+
).then((valid) => {
|
|
1164
|
+
if (valid === false) throw new MergeError("The post-merge validator rejected the merged audio.");
|
|
1165
|
+
return result;
|
|
1166
|
+
});
|
|
916
1167
|
}).catch((error) => {
|
|
917
1168
|
if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
|
|
918
1169
|
throw error;
|
|
@@ -920,13 +1171,28 @@ function mergeSynthesisResults(results, options) {
|
|
|
920
1171
|
});
|
|
921
1172
|
}
|
|
922
1173
|
try {
|
|
923
|
-
|
|
1174
|
+
const result = createMergedResult(
|
|
924
1175
|
results,
|
|
925
1176
|
mergeAudioBuffers(buffers, { format }),
|
|
926
1177
|
format,
|
|
927
1178
|
inputSpecs[0],
|
|
928
1179
|
resolvedOptions.outputMimeType
|
|
929
1180
|
);
|
|
1181
|
+
if (resolvedOptions.postMergeValidator) {
|
|
1182
|
+
const validation = resolvedOptions.postMergeValidator(result, {
|
|
1183
|
+
format,
|
|
1184
|
+
outputMimeType: resolvedOptions.outputMimeType ?? resolveMimeType(format),
|
|
1185
|
+
inputSpecs,
|
|
1186
|
+
signal
|
|
1187
|
+
});
|
|
1188
|
+
if (validation instanceof Promise)
|
|
1189
|
+
return validation.then((valid) => {
|
|
1190
|
+
if (valid === false) throw new MergeError("The post-merge validator rejected the merged audio.");
|
|
1191
|
+
return result;
|
|
1192
|
+
});
|
|
1193
|
+
if (validation === false) throw new MergeError("The post-merge validator rejected the merged audio.");
|
|
1194
|
+
}
|
|
1195
|
+
return result;
|
|
930
1196
|
} catch (error) {
|
|
931
1197
|
if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
|
|
932
1198
|
throw error;
|
|
@@ -948,8 +1214,52 @@ var ChunkValidationError = class extends Error {
|
|
|
948
1214
|
this.diagnostics = diagnostics;
|
|
949
1215
|
}
|
|
950
1216
|
};
|
|
951
|
-
|
|
952
|
-
|
|
1217
|
+
var BatchChunkValidationError = class extends ChunkValidationError {
|
|
1218
|
+
constructor(chunkDiagnostics) {
|
|
1219
|
+
const first = chunkDiagnostics[0];
|
|
1220
|
+
super(first?.chunkIndex ?? -1, first?.diagnostics ?? []);
|
|
1221
|
+
this.name = "BatchChunkValidationError";
|
|
1222
|
+
this.message = `SSML validation failed for ${chunkDiagnostics.length} chunk(s); the Azure Speech API was not called.`;
|
|
1223
|
+
this.chunkDiagnostics = chunkDiagnostics;
|
|
1224
|
+
this.totalErrorCount = chunkDiagnostics.reduce((total, chunk) => total + chunk.diagnostics.length, 0);
|
|
1225
|
+
this.errorCount = this.totalErrorCount;
|
|
1226
|
+
this.totalErrors = this.totalErrorCount;
|
|
1227
|
+
}
|
|
1228
|
+
};
|
|
1229
|
+
function failure(error, partialResult) {
|
|
1230
|
+
return {
|
|
1231
|
+
ok: false,
|
|
1232
|
+
success: false,
|
|
1233
|
+
status: error.kind,
|
|
1234
|
+
error,
|
|
1235
|
+
...partialResult ? { partialResult } : {}
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
function partialResultFrom(error) {
|
|
1239
|
+
if (!error || typeof error !== "object") return void 0;
|
|
1240
|
+
const partial = error.partialResult;
|
|
1241
|
+
if (!partial || typeof partial !== "object") return void 0;
|
|
1242
|
+
return partial;
|
|
1243
|
+
}
|
|
1244
|
+
function createSafeAbortScope(parent, timeoutMs) {
|
|
1245
|
+
const controller = new AbortController();
|
|
1246
|
+
let didTimeout = false;
|
|
1247
|
+
const onAbort = () => controller.abort();
|
|
1248
|
+
if (parent?.aborted) controller.abort();
|
|
1249
|
+
parent?.addEventListener("abort", onAbort, { once: true });
|
|
1250
|
+
const timer = timeoutMs !== void 0 && timeoutMs > 0 ? setTimeout(() => {
|
|
1251
|
+
didTimeout = true;
|
|
1252
|
+
controller.abort();
|
|
1253
|
+
}, timeoutMs) : void 0;
|
|
1254
|
+
return {
|
|
1255
|
+
signal: controller.signal,
|
|
1256
|
+
timedOut: () => didTimeout,
|
|
1257
|
+
dispose: () => {
|
|
1258
|
+
if (timer) clearTimeout(timer);
|
|
1259
|
+
parent?.removeEventListener("abort", onAbort);
|
|
1260
|
+
},
|
|
1261
|
+
abort: () => controller.abort()
|
|
1262
|
+
};
|
|
953
1263
|
}
|
|
954
1264
|
function isRetryable(error) {
|
|
955
1265
|
if (error instanceof Error && /cancel|abort|tim(?:e|ed) ?out/i.test(error.message)) return false;
|
|
@@ -964,16 +1274,20 @@ function delayForRetry(options, attempt) {
|
|
|
964
1274
|
const base = Math.min(maxDelay, Math.max(0, options.initialDelayMs) * 2 ** Math.max(0, attempt - 1));
|
|
965
1275
|
return Math.floor(Math.random() * (base + 1));
|
|
966
1276
|
}
|
|
1277
|
+
function retryDelayForError(options, attempt, error) {
|
|
1278
|
+
return getRetryAfterDelayMs(error) ?? delayForRetry(options, attempt);
|
|
1279
|
+
}
|
|
967
1280
|
function resolveConcurrency2(value, total) {
|
|
968
1281
|
if (value === void 0) return 1;
|
|
969
1282
|
if (value === Infinity) return Math.max(1, total);
|
|
970
1283
|
return Number.isFinite(value) ? Math.max(1, Math.floor(value)) : 1;
|
|
971
1284
|
}
|
|
972
|
-
async function retryableSynthesis(synthesize, options, signal, onRetry) {
|
|
1285
|
+
async function retryableSynthesis(synthesize, options, signal, onRetry, deadlineAtMs) {
|
|
973
1286
|
const retry = options ? {
|
|
974
1287
|
maxRetries: Math.max(0, Math.floor(options.maxRetries)),
|
|
975
1288
|
initialDelayMs: options.initialDelayMs,
|
|
976
|
-
maxDelayMs: options.maxDelayMs
|
|
1289
|
+
maxDelayMs: options.maxDelayMs,
|
|
1290
|
+
shouldRetry: options.shouldRetry
|
|
977
1291
|
} : void 0;
|
|
978
1292
|
let attempt = 0;
|
|
979
1293
|
while (true) {
|
|
@@ -981,9 +1295,15 @@ async function retryableSynthesis(synthesize, options, signal, onRetry) {
|
|
|
981
1295
|
try {
|
|
982
1296
|
return await synthesize();
|
|
983
1297
|
} catch (error) {
|
|
984
|
-
if (!retry || attempt >= retry.maxRetries || !
|
|
1298
|
+
if (!retry || attempt >= retry.maxRetries || !(retry.shouldRetry?.(error, attempt + 1) ?? isRetryable(error)))
|
|
1299
|
+
throw error;
|
|
985
1300
|
attempt += 1;
|
|
986
|
-
const delayMs =
|
|
1301
|
+
const delayMs = retryDelayForError(retry, attempt, error);
|
|
1302
|
+
const retryAfterMs = getRetryAfterDelayMs(error);
|
|
1303
|
+
const remainingMs = deadlineAtMs === void 0 ? void 0 : Math.max(0, deadlineAtMs - Date.now());
|
|
1304
|
+
if (retryAfterMs !== void 0 && (retryAfterMs > retry.maxDelayMs || remainingMs !== void 0 && retryAfterMs > remainingMs)) {
|
|
1305
|
+
throw new Error("Speech synthesis timed out because Retry-After exceeded the available retry budget.");
|
|
1306
|
+
}
|
|
987
1307
|
onRetry(attempt, delayMs);
|
|
988
1308
|
if (delayMs > 0)
|
|
989
1309
|
await new Promise((resolve, reject) => {
|
|
@@ -1007,7 +1327,7 @@ function sharedValidationOptions(options, signal) {
|
|
|
1007
1327
|
const runner = (0, import_ssml_core2.createAzureUrlValidatorRunner)(validator, {
|
|
1008
1328
|
...options.urlValidation ?? {},
|
|
1009
1329
|
...options.urlValidatorConcurrency !== void 0 ? { concurrency: options.urlValidatorConcurrency } : {},
|
|
1010
|
-
...options.urlValidatorTimeoutMs !== void 0 ? { timeoutMs: options.urlValidatorTimeoutMs } : {},
|
|
1330
|
+
...options.timeouts?.urlValidationMs !== void 0 ? { timeoutMs: options.timeouts.urlValidationMs } : options.urlValidatorTimeoutMs !== void 0 ? { timeoutMs: options.urlValidatorTimeoutMs } : {},
|
|
1011
1331
|
...signal ? { signal } : {},
|
|
1012
1332
|
...options.urlValidatorCache ? { cache: options.urlValidatorCache } : {}
|
|
1013
1333
|
});
|
|
@@ -1031,20 +1351,31 @@ async function synthesizeSsmlSafe(client, ssml, options = {}) {
|
|
|
1031
1351
|
diagnostics: errors
|
|
1032
1352
|
});
|
|
1033
1353
|
}
|
|
1354
|
+
const jobScope = options.timeouts?.totalJobMs !== void 0 ? createSafeAbortScope(options.signal, options.timeouts.totalJobMs) : void 0;
|
|
1034
1355
|
try {
|
|
1035
1356
|
return {
|
|
1036
1357
|
ok: true,
|
|
1037
1358
|
success: true,
|
|
1038
1359
|
status: "success",
|
|
1039
|
-
value: await client.synthesizeSsml(ssml, {
|
|
1360
|
+
value: await client.synthesizeSsml(ssml, {
|
|
1361
|
+
signal: jobScope?.signal ?? options.signal,
|
|
1362
|
+
timeoutMs: options.timeouts?.perChunkMs,
|
|
1363
|
+
timeouts: options.timeouts
|
|
1364
|
+
})
|
|
1040
1365
|
};
|
|
1041
1366
|
} catch (error) {
|
|
1367
|
+
if (jobScope?.timedOut()) return failure(toSynthesisError(new Error("Speech synthesis timed out.")));
|
|
1042
1368
|
const synthesisError = toSynthesisError(error);
|
|
1043
1369
|
return failure(synthesisError);
|
|
1370
|
+
} finally {
|
|
1371
|
+
jobScope?.dispose();
|
|
1044
1372
|
}
|
|
1045
1373
|
}
|
|
1046
1374
|
async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
1047
|
-
const validationOptions = sharedValidationOptions(
|
|
1375
|
+
const validationOptions = sharedValidationOptions(
|
|
1376
|
+
{ ...options.validation ?? options, timeouts: options.timeouts },
|
|
1377
|
+
options.signal
|
|
1378
|
+
);
|
|
1048
1379
|
if (options.signal?.aborted) {
|
|
1049
1380
|
const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
|
|
1050
1381
|
return failure(error);
|
|
@@ -1078,16 +1409,17 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
1078
1409
|
return diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
1079
1410
|
})
|
|
1080
1411
|
);
|
|
1081
|
-
const firstInvalidIndex = validations.findIndex((diagnostics) => diagnostics.length > 0);
|
|
1082
1412
|
if (options.signal?.aborted) {
|
|
1083
1413
|
const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
|
|
1084
1414
|
return failure(error);
|
|
1085
1415
|
}
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1416
|
+
const chunkDiagnostics = validations.map((diagnostics, chunkIndex) => ({ chunkIndex, diagnostics })).filter((entry) => entry.diagnostics.length > 0);
|
|
1417
|
+
if (chunkDiagnostics.length > 0) {
|
|
1418
|
+
const error = new BatchChunkValidationError(chunkDiagnostics);
|
|
1419
|
+
for (const entry of chunkDiagnostics) pending(entry.chunkIndex, "failed", error);
|
|
1089
1420
|
return failure(error);
|
|
1090
1421
|
}
|
|
1422
|
+
let fallbackJobScope;
|
|
1091
1423
|
try {
|
|
1092
1424
|
if (client.synthesizeChunks) {
|
|
1093
1425
|
const normalizedChunks = chunks.map((chunk) => {
|
|
@@ -1099,20 +1431,57 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
1099
1431
|
outputFormat: options.outputFormat,
|
|
1100
1432
|
signal: options.signal,
|
|
1101
1433
|
timeoutMs: options.timeoutMs,
|
|
1434
|
+
timeouts: options.timeouts,
|
|
1102
1435
|
sourceNodePath: options.sourceNodePath,
|
|
1103
1436
|
concurrency: options.concurrency,
|
|
1104
|
-
retryOptions: options.retryOptions
|
|
1437
|
+
retryOptions: options.retryOptions,
|
|
1438
|
+
cancelOnFailure: options.cancelOnFailure,
|
|
1439
|
+
resumeChunks: options.resumeChunks,
|
|
1440
|
+
resumeChunkIndices: options.resumeChunkIndices,
|
|
1441
|
+
customMerger: options.customMerger,
|
|
1442
|
+
outputMimeType: options.outputMimeType,
|
|
1443
|
+
postMergeValidator: options.postMergeValidator,
|
|
1444
|
+
resumeValidation: options.resumeValidation
|
|
1105
1445
|
});
|
|
1106
1446
|
return { ok: true, success: true, status: "success", value };
|
|
1107
1447
|
}
|
|
1448
|
+
const inputs = chunks.map((chunk) => typeof chunk === "string" ? { ssml: chunk } : chunk);
|
|
1449
|
+
const fingerprints = inputs.map((chunk) => computeChunkFingerprint(chunk.ssml, options.outputFormat));
|
|
1108
1450
|
const results = new Array(chunks.length);
|
|
1109
|
-
|
|
1451
|
+
const chunkStates = inputs.map((_chunk, chunkIndex) => ({
|
|
1452
|
+
chunkIndex,
|
|
1453
|
+
status: "pending",
|
|
1454
|
+
canResume: true
|
|
1455
|
+
}));
|
|
1456
|
+
const cachedChunks = new Map((options.resumeChunks ?? []).map((chunk) => [chunk.chunkIndex, chunk]));
|
|
1457
|
+
const invalidCachedIndices = /* @__PURE__ */ new Set();
|
|
1458
|
+
for (const [index, cached] of cachedChunks) {
|
|
1459
|
+
if (index < 0 || index >= chunks.length) continue;
|
|
1460
|
+
if (options.resumeValidation === "disabled" || cached.fingerprint === fingerprints[index]) {
|
|
1461
|
+
results[index] = cached;
|
|
1462
|
+
chunkStates[index] = { chunkIndex: index, status: "succeeded", canResume: true, result: cached };
|
|
1463
|
+
} else invalidCachedIndices.add(index);
|
|
1464
|
+
}
|
|
1465
|
+
const requestedIndices = options.resumeChunkIndices ? new Set(options.resumeChunkIndices.filter((index) => index >= 0 && index < chunks.length)) : void 0;
|
|
1466
|
+
const shouldSynthesize = (index) => (!cachedChunks.has(index) || invalidCachedIndices.has(index)) && (requestedIndices === void 0 || requestedIndices.has(index) || invalidCachedIndices.has(index));
|
|
1467
|
+
const jobStartedAt = Date.now();
|
|
1468
|
+
const jobDeadlineAt = options.timeouts?.totalJobMs !== void 0 && options.timeouts.totalJobMs > 0 ? jobStartedAt + options.timeouts.totalJobMs : void 0;
|
|
1469
|
+
const jobScope = chunks.length > 1 || options.timeouts?.totalJobMs !== void 0 ? createSafeAbortScope(options.signal, options.timeouts?.totalJobMs) : void 0;
|
|
1470
|
+
fallbackJobScope = jobScope;
|
|
1471
|
+
const failedIndices = /* @__PURE__ */ new Set();
|
|
1472
|
+
let firstError;
|
|
1473
|
+
let completed = [...results].filter((result) => result !== void 0).length;
|
|
1110
1474
|
let nextIndex = 0;
|
|
1111
1475
|
const concurrency = resolveConcurrency2(options.concurrency, chunks.length);
|
|
1112
1476
|
const worker = async () => {
|
|
1113
1477
|
while (true) {
|
|
1114
1478
|
const index = nextIndex++;
|
|
1115
1479
|
if (index >= chunks.length) return;
|
|
1480
|
+
if (!shouldSynthesize(index)) continue;
|
|
1481
|
+
if (firstError && options.cancelOnFailure !== false) {
|
|
1482
|
+
chunkStates[index] = { chunkIndex: index, status: "cancelled", isOriginalFailure: false, canResume: true };
|
|
1483
|
+
return;
|
|
1484
|
+
}
|
|
1116
1485
|
const chunk = chunks[index];
|
|
1117
1486
|
const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
|
|
1118
1487
|
const sourceNodePath = input.sourceNodePath;
|
|
@@ -1120,28 +1489,41 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
1120
1489
|
pending(index, "synthesizing");
|
|
1121
1490
|
const startedAt = Date.now();
|
|
1122
1491
|
try {
|
|
1123
|
-
const
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1492
|
+
const chunkTimeout = options.timeouts?.chunkWithRetriesMs ?? options.timeouts?.perChunkMs;
|
|
1493
|
+
const chunkScope = chunkTimeout !== void 0 || jobScope ? createSafeAbortScope(jobScope?.signal ?? options.signal, chunkTimeout ?? options.timeoutMs) : void 0;
|
|
1494
|
+
const chunkSignal = chunkScope?.signal ?? options.signal;
|
|
1495
|
+
let result;
|
|
1496
|
+
try {
|
|
1497
|
+
result = await retryableSynthesis(
|
|
1498
|
+
() => client.synthesizeSsml(input.ssml, {
|
|
1499
|
+
outputFormat: options.outputFormat,
|
|
1500
|
+
signal: chunkSignal,
|
|
1501
|
+
timeoutMs: options.timeouts?.perChunkMs ?? options.timeoutMs,
|
|
1502
|
+
sourceNodePath: input.sourceNodePath ?? options.sourceNodePath
|
|
1503
|
+
}),
|
|
1504
|
+
options.retryOptions,
|
|
1505
|
+
chunkSignal,
|
|
1506
|
+
(retryAttempt, nextRetryDelayMs) => options.onProgress?.({
|
|
1507
|
+
currentChunk: completed,
|
|
1508
|
+
totalChunks: chunks.length,
|
|
1509
|
+
percent: chunks.length === 0 ? 100 : Math.round(completed / chunks.length * 100),
|
|
1510
|
+
chunkIndex: index,
|
|
1511
|
+
originalTextRange: input.originalTextRange,
|
|
1512
|
+
status: "synthesizing",
|
|
1513
|
+
durationMs: Date.now() - startedAt,
|
|
1514
|
+
retryAttempt,
|
|
1515
|
+
nextRetryDelayMs,
|
|
1516
|
+
isRetrying: true
|
|
1517
|
+
}),
|
|
1518
|
+
jobDeadlineAt
|
|
1519
|
+
);
|
|
1520
|
+
} catch (error) {
|
|
1521
|
+
if (chunkScope?.timedOut())
|
|
1522
|
+
throw new Error(`Speech synthesis timed out after ${chunkTimeout ?? options.timeoutMs} ms.`);
|
|
1523
|
+
throw error;
|
|
1524
|
+
} finally {
|
|
1525
|
+
chunkScope?.dispose();
|
|
1526
|
+
}
|
|
1145
1527
|
results[index] = {
|
|
1146
1528
|
...result,
|
|
1147
1529
|
...input.originalTextRange ? { textRange: { ...input.originalTextRange } } : {},
|
|
@@ -1185,6 +1567,7 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
1185
1567
|
}))
|
|
1186
1568
|
} : {}
|
|
1187
1569
|
};
|
|
1570
|
+
chunkStates[index] = { chunkIndex: index, status: "succeeded", canResume: true, result: results[index] };
|
|
1188
1571
|
completed += 1;
|
|
1189
1572
|
options.onProgress?.({
|
|
1190
1573
|
currentChunk: completed,
|
|
@@ -1196,6 +1579,16 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
1196
1579
|
durationMs: Date.now() - startedAt
|
|
1197
1580
|
});
|
|
1198
1581
|
} catch (error) {
|
|
1582
|
+
const wasCancelled = firstError !== void 0 || Boolean(jobScope?.signal.aborted && !jobScope?.timedOut());
|
|
1583
|
+
firstError ?? (firstError = error);
|
|
1584
|
+
if (!wasCancelled) failedIndices.add(index);
|
|
1585
|
+
chunkStates[index] = {
|
|
1586
|
+
chunkIndex: index,
|
|
1587
|
+
status: wasCancelled ? "cancelled" : "failed",
|
|
1588
|
+
isOriginalFailure: !wasCancelled,
|
|
1589
|
+
canResume: true,
|
|
1590
|
+
error
|
|
1591
|
+
};
|
|
1199
1592
|
options.onProgress?.({
|
|
1200
1593
|
currentChunk: completed,
|
|
1201
1594
|
totalChunks: chunks.length,
|
|
@@ -1206,24 +1599,55 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
1206
1599
|
durationMs: Date.now() - startedAt,
|
|
1207
1600
|
error
|
|
1208
1601
|
});
|
|
1209
|
-
|
|
1602
|
+
if (options.cancelOnFailure !== false) jobScope?.abort();
|
|
1603
|
+
return;
|
|
1210
1604
|
}
|
|
1211
1605
|
}
|
|
1212
1606
|
};
|
|
1213
1607
|
await Promise.all(Array.from({ length: Math.min(concurrency, chunks.length) }, () => worker()));
|
|
1608
|
+
if (firstError && options.cancelOnFailure !== false) {
|
|
1609
|
+
for (const [chunkIndex, state] of chunkStates.entries()) {
|
|
1610
|
+
if (state.status === "pending" && shouldSynthesize(chunkIndex)) {
|
|
1611
|
+
chunkStates[chunkIndex] = { chunkIndex, status: "cancelled", isOriginalFailure: false, canResume: true };
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
if (failedIndices.size > 0) {
|
|
1616
|
+
const error = firstError ?? new Error("One or more SSML chunks failed to synthesize.");
|
|
1617
|
+
const synthesizedChunks = results.flatMap(
|
|
1618
|
+
(result, chunkIndex) => result ? [{ ...result, chunkIndex, fingerprint: fingerprints[chunkIndex] ?? "" }] : []
|
|
1619
|
+
);
|
|
1620
|
+
error.partialResult = {
|
|
1621
|
+
synthesizedChunks,
|
|
1622
|
+
completedChunks: synthesizedChunks,
|
|
1623
|
+
pendingChunkIndices: chunkStates.flatMap(
|
|
1624
|
+
(state) => state.status === "pending" || state.status === "cancelled" || state.status === "failed" ? [state.chunkIndex] : []
|
|
1625
|
+
),
|
|
1626
|
+
failedChunkIndices: [...failedIndices],
|
|
1627
|
+
cancelledChunkIndices: chunkStates.filter((state) => state.status === "cancelled").map((state) => state.chunkIndex),
|
|
1628
|
+
chunkStates,
|
|
1629
|
+
totalChunks: chunks.length
|
|
1630
|
+
};
|
|
1631
|
+
throw error;
|
|
1632
|
+
}
|
|
1214
1633
|
const orderedResults = results.filter((result) => result !== void 0);
|
|
1215
1634
|
return {
|
|
1216
1635
|
ok: true,
|
|
1217
1636
|
success: true,
|
|
1218
1637
|
status: "success",
|
|
1219
|
-
value: mergeSynthesisResults(orderedResults, {
|
|
1638
|
+
value: await mergeSynthesisResults(orderedResults, {
|
|
1220
1639
|
format: options.outputFormat ?? "audio-16khz-128kbitrate-mono-mp3",
|
|
1221
|
-
signal: options.signal
|
|
1640
|
+
signal: jobScope?.signal ?? options.signal,
|
|
1641
|
+
customMerger: options.customMerger,
|
|
1642
|
+
outputMimeType: options.outputMimeType,
|
|
1643
|
+
postMergeValidator: options.postMergeValidator
|
|
1222
1644
|
})
|
|
1223
1645
|
};
|
|
1224
1646
|
} catch (error) {
|
|
1225
1647
|
const synthesisError = toSynthesisError(error);
|
|
1226
|
-
return failure(synthesisError);
|
|
1648
|
+
return failure(synthesisError, partialResultFrom(error));
|
|
1649
|
+
} finally {
|
|
1650
|
+
fallbackJobScope?.dispose();
|
|
1227
1651
|
}
|
|
1228
1652
|
}
|
|
1229
1653
|
function withValidationSignal(options, signal) {
|
|
@@ -1244,14 +1668,23 @@ var AzureTtsClient = class {
|
|
|
1244
1668
|
__privateSet(this, _options, options);
|
|
1245
1669
|
}
|
|
1246
1670
|
async synthesize(ssml) {
|
|
1247
|
-
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
1671
|
+
const { region, subscriptionKey, outputFormat, signal, timeoutMs, timeouts } = __privateGet(this, _options);
|
|
1248
1672
|
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
1249
1673
|
__privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
|
|
1250
|
-
const config = {
|
|
1674
|
+
const config = {
|
|
1675
|
+
endpoint,
|
|
1676
|
+
region,
|
|
1677
|
+
subscriptionKey,
|
|
1678
|
+
outputFormat,
|
|
1679
|
+
signal,
|
|
1680
|
+
timeoutMs,
|
|
1681
|
+
timeouts,
|
|
1682
|
+
retryOptions: __privateGet(this, _options).retryOptions
|
|
1683
|
+
};
|
|
1251
1684
|
return synthesizeSpeech(ssml, config);
|
|
1252
1685
|
}
|
|
1253
1686
|
async synthesizeSsml(ssml, options = {}) {
|
|
1254
|
-
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
1687
|
+
const { region, subscriptionKey, outputFormat, signal, timeoutMs, timeouts } = __privateGet(this, _options);
|
|
1255
1688
|
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
1256
1689
|
__privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
|
|
1257
1690
|
return synthesizeSsml(ssml, {
|
|
@@ -1261,13 +1694,20 @@ var AzureTtsClient = class {
|
|
|
1261
1694
|
outputFormat: options.outputFormat ?? outputFormat,
|
|
1262
1695
|
signal: options.signal ?? signal,
|
|
1263
1696
|
timeoutMs: options.timeoutMs ?? timeoutMs,
|
|
1697
|
+
timeouts: options.timeouts ?? timeouts,
|
|
1264
1698
|
sourceNodePath: options.sourceNodePath,
|
|
1265
1699
|
sourceTextSegments: options.sourceTextSegments,
|
|
1266
|
-
sourceMarkers: options.sourceMarkers
|
|
1700
|
+
sourceMarkers: options.sourceMarkers,
|
|
1701
|
+
retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions,
|
|
1702
|
+
cancelOnFailure: options.cancelOnFailure ?? __privateGet(this, _options).cancelOnFailure,
|
|
1703
|
+
customMerger: options.customMerger ?? __privateGet(this, _options).customMerger,
|
|
1704
|
+
outputMimeType: options.outputMimeType ?? __privateGet(this, _options).outputMimeType,
|
|
1705
|
+
postMergeValidator: options.postMergeValidator ?? __privateGet(this, _options).postMergeValidator,
|
|
1706
|
+
resumeValidation: options.resumeValidation ?? __privateGet(this, _options).resumeValidation
|
|
1267
1707
|
});
|
|
1268
1708
|
}
|
|
1269
1709
|
async synthesizeChunks(chunks, options = {}) {
|
|
1270
|
-
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
1710
|
+
const { region, subscriptionKey, outputFormat, signal, timeoutMs, timeouts } = __privateGet(this, _options);
|
|
1271
1711
|
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
1272
1712
|
return synthesizeSsmlChunks(chunks, {
|
|
1273
1713
|
endpoint,
|
|
@@ -1276,10 +1716,18 @@ var AzureTtsClient = class {
|
|
|
1276
1716
|
outputFormat: options.outputFormat ?? outputFormat,
|
|
1277
1717
|
signal: options.signal ?? signal,
|
|
1278
1718
|
timeoutMs: options.timeoutMs ?? timeoutMs,
|
|
1719
|
+
timeouts: options.timeouts ?? timeouts,
|
|
1279
1720
|
sourceNodePath: options.sourceNodePath,
|
|
1280
1721
|
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress,
|
|
1281
1722
|
concurrency: options.concurrency ?? __privateGet(this, _options).concurrency,
|
|
1282
|
-
retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions
|
|
1723
|
+
retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions,
|
|
1724
|
+
cancelOnFailure: options.cancelOnFailure ?? __privateGet(this, _options).cancelOnFailure,
|
|
1725
|
+
resumeChunks: options.resumeChunks,
|
|
1726
|
+
resumeChunkIndices: options.resumeChunkIndices,
|
|
1727
|
+
customMerger: options.customMerger ?? __privateGet(this, _options).customMerger,
|
|
1728
|
+
outputMimeType: options.outputMimeType ?? __privateGet(this, _options).outputMimeType,
|
|
1729
|
+
postMergeValidator: options.postMergeValidator ?? __privateGet(this, _options).postMergeValidator,
|
|
1730
|
+
resumeValidation: options.resumeValidation ?? __privateGet(this, _options).resumeValidation
|
|
1283
1731
|
});
|
|
1284
1732
|
}
|
|
1285
1733
|
async synthesizeSsmlSafe(ssml, options = {}) {
|
|
@@ -1291,6 +1739,7 @@ var AzureTtsClient = class {
|
|
|
1291
1739
|
outputFormat: options.outputFormat ?? __privateGet(this, _options).outputFormat,
|
|
1292
1740
|
signal: options.signal ?? __privateGet(this, _options).signal,
|
|
1293
1741
|
timeoutMs: options.timeoutMs ?? __privateGet(this, _options).timeoutMs,
|
|
1742
|
+
timeouts: options.timeouts ?? __privateGet(this, _options).timeouts,
|
|
1294
1743
|
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress,
|
|
1295
1744
|
concurrency: options.concurrency ?? __privateGet(this, _options).concurrency,
|
|
1296
1745
|
retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions
|
|
@@ -1384,7 +1833,9 @@ async function fetchAzureVoiceCatalog(options) {
|
|
|
1384
1833
|
voiceCount: sortedVoices.length,
|
|
1385
1834
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1386
1835
|
apiVersion: AZURE_VOICE_API_VERSION,
|
|
1387
|
-
regions
|
|
1836
|
+
regions,
|
|
1837
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3).toISOString(),
|
|
1838
|
+
regionDiffs: {}
|
|
1388
1839
|
}
|
|
1389
1840
|
};
|
|
1390
1841
|
}
|
|
@@ -1394,6 +1845,7 @@ async function fetchAzureVoiceCatalog(options) {
|
|
|
1394
1845
|
AzureTtsClient,
|
|
1395
1846
|
AzureTtsError,
|
|
1396
1847
|
AzureTtsSdkError,
|
|
1848
|
+
BatchChunkValidationError,
|
|
1397
1849
|
ChunkValidationError,
|
|
1398
1850
|
DEFAULT_OUTPUT_FORMAT,
|
|
1399
1851
|
MergeError,
|
|
@@ -1401,7 +1853,9 @@ async function fetchAzureVoiceCatalog(options) {
|
|
|
1401
1853
|
SynthesisTimeoutError,
|
|
1402
1854
|
UnsupportedMergeFormatError,
|
|
1403
1855
|
canMergeAudioFormat,
|
|
1856
|
+
computeChunkFingerprint,
|
|
1404
1857
|
fetchAzureVoiceCatalog,
|
|
1858
|
+
getRetryAfterDelayMs,
|
|
1405
1859
|
inspectAudioSpecification,
|
|
1406
1860
|
mergeAudioBuffers,
|
|
1407
1861
|
mergeSynthesisResults,
|