ssml-builder-js 2.15.0 → 2.17.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/README.md +11 -3
- package/dist/{chunk-FXUM45ZY.mjs → chunk-2SYLELUT.mjs} +53 -6
- package/dist/chunk-2SYLELUT.mjs.map +1 -0
- package/dist/{chunk-WXFLUCLR.mjs → chunk-5AZWURHW.mjs} +37 -6
- package/dist/chunk-5AZWURHW.mjs.map +1 -0
- package/dist/{chunk-BDY2Q2JL.mjs → chunk-SLZ7PE6W.mjs} +2 -2
- package/dist/core.d.mts +24 -1
- package/dist/core.d.ts +24 -1
- package/dist/core.js +53 -5
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +3 -1
- package/dist/elements.js +35 -5
- package/dist/elements.js.map +1 -1
- package/dist/elements.mjs +2 -2
- package/dist/{index.d-8BvkB9gz.d.mts → index.d-tpKoP1jl.d.mts} +27 -1
- package/dist/{index.d-8BvkB9gz.d.ts → index.d-tpKoP1jl.d.ts} +27 -1
- package/dist/index.d.mts +123 -9
- package/dist/index.d.ts +123 -9
- package/dist/index.js +874 -180
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +787 -172
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +5 -2
- package/dist/react.d.ts +5 -2
- package/dist/react.js +143 -29
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +110 -26
- package/dist/react.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-FXUM45ZY.mjs.map +0 -1
- package/dist/chunk-WXFLUCLR.mjs.map +0 -1
- /package/dist/{chunk-BDY2Q2JL.mjs.map → chunk-SLZ7PE6W.mjs.map} +0 -0
package/dist/index.mjs
CHANGED
|
@@ -15,13 +15,15 @@ import {
|
|
|
15
15
|
parseSsml,
|
|
16
16
|
splitSsmlDocument,
|
|
17
17
|
validateAzureSsml,
|
|
18
|
+
validateAzureSsmlChunks,
|
|
18
19
|
validateSsml,
|
|
19
20
|
validateSsmlStructureIntegrity
|
|
20
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-2SYLELUT.mjs";
|
|
21
22
|
import {
|
|
23
|
+
createAzureUrlValidatorRunner as createAzureUrlValidatorRunner2,
|
|
22
24
|
getSsmlSourceMap as getSsmlSourceMap2,
|
|
23
25
|
validateAzureSsml as validateAzureSsml2
|
|
24
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-5AZWURHW.mjs";
|
|
25
27
|
import {
|
|
26
28
|
__privateAdd,
|
|
27
29
|
__privateGet,
|
|
@@ -30,7 +32,7 @@ import {
|
|
|
30
32
|
|
|
31
33
|
// packages/azure-tts-client/src/errors.ts
|
|
32
34
|
var AzureTtsError = class extends Error {
|
|
33
|
-
constructor(status, statusText, responseBody, requestId) {
|
|
35
|
+
constructor(status, statusText, responseBody, requestId, responseHeaders) {
|
|
34
36
|
super(`Azure TTS request failed: ${status} ${statusText}`);
|
|
35
37
|
this.kind = "azure-api-error";
|
|
36
38
|
this.name = "AzureTtsError";
|
|
@@ -38,8 +40,37 @@ var AzureTtsError = class extends Error {
|
|
|
38
40
|
this.statusText = statusText;
|
|
39
41
|
this.responseBody = responseBody;
|
|
40
42
|
this.requestId = requestId;
|
|
43
|
+
const value = responseHeaders instanceof Headers ? responseHeaders.get("retry-after") : responseHeaders?.["retry-after"] ?? responseHeaders?.["Retry-After"];
|
|
44
|
+
const seconds = value ? Number(value.trim()) : NaN;
|
|
45
|
+
const date = value ? Date.parse(value) : NaN;
|
|
46
|
+
if (Number.isFinite(seconds) && seconds >= 0) this.retryAfterMs = seconds * 1e3;
|
|
47
|
+
else if (Number.isFinite(date)) this.retryAfterMs = Math.max(0, date - Date.now());
|
|
41
48
|
}
|
|
42
49
|
};
|
|
50
|
+
function getRetryAfterDelayMs(error) {
|
|
51
|
+
if (error instanceof AzureTtsError && error.retryAfterMs !== void 0) return error.retryAfterMs;
|
|
52
|
+
if (!error || typeof error !== "object") return void 0;
|
|
53
|
+
const candidate = error;
|
|
54
|
+
if (typeof candidate.retryAfterMs === "number" && candidate.retryAfterMs >= 0) return candidate.retryAfterMs;
|
|
55
|
+
const headers = candidate.headers ?? candidate.response?.headers;
|
|
56
|
+
if (headers instanceof Headers) {
|
|
57
|
+
const value = headers.get("retry-after");
|
|
58
|
+
if (!value) return void 0;
|
|
59
|
+
const seconds = Number(value.trim());
|
|
60
|
+
if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
|
|
61
|
+
const date = Date.parse(value);
|
|
62
|
+
return Number.isFinite(date) ? Math.max(0, date - Date.now()) : void 0;
|
|
63
|
+
}
|
|
64
|
+
if (headers && typeof headers === "object") {
|
|
65
|
+
const value = headers["retry-after"] ?? headers["Retry-After"];
|
|
66
|
+
if (typeof value !== "string") return void 0;
|
|
67
|
+
const seconds = Number(value.trim());
|
|
68
|
+
if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
|
|
69
|
+
const date = Date.parse(value);
|
|
70
|
+
return Number.isFinite(date) ? Math.max(0, date - Date.now()) : void 0;
|
|
71
|
+
}
|
|
72
|
+
return void 0;
|
|
73
|
+
}
|
|
43
74
|
var AzureTtsSdkError = class extends AzureTtsError {
|
|
44
75
|
constructor(errorDetails) {
|
|
45
76
|
super(0, "Speech SDK", errorDetails, null);
|
|
@@ -70,6 +101,14 @@ var MergeError = class extends Error {
|
|
|
70
101
|
this.cause = cause;
|
|
71
102
|
}
|
|
72
103
|
};
|
|
104
|
+
var AudioFormatMismatchError = class extends Error {
|
|
105
|
+
constructor(message, inputSpecs = []) {
|
|
106
|
+
super(message);
|
|
107
|
+
this.kind = "audio-format-mismatch";
|
|
108
|
+
this.name = "AudioFormatMismatchError";
|
|
109
|
+
this.inputSpecs = inputSpecs;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
73
112
|
var UnsupportedMergeFormatError = class extends Error {
|
|
74
113
|
constructor(format) {
|
|
75
114
|
super(`Audio format "${format}" cannot be safely concatenated; container re-multiplexing is required.`);
|
|
@@ -79,7 +118,7 @@ var UnsupportedMergeFormatError = class extends Error {
|
|
|
79
118
|
}
|
|
80
119
|
};
|
|
81
120
|
function toSynthesisError(error) {
|
|
82
|
-
if (error instanceof AzureTtsError || error instanceof MergeError || error instanceof UnsupportedMergeFormatError || error instanceof SynthesisCancelledError || error instanceof SynthesisTimeoutError)
|
|
121
|
+
if (error instanceof AzureTtsError || error instanceof MergeError || error instanceof AudioFormatMismatchError || error instanceof UnsupportedMergeFormatError || error instanceof SynthesisCancelledError || error instanceof SynthesisTimeoutError)
|
|
83
122
|
return error;
|
|
84
123
|
const message = error instanceof Error ? error.message : String(error);
|
|
85
124
|
if (/cancel|abort/i.test(message)) return new SynthesisCancelledError(message);
|
|
@@ -208,6 +247,115 @@ function parseWav(buffer) {
|
|
|
208
247
|
}
|
|
209
248
|
return { chunks, data, format };
|
|
210
249
|
}
|
|
250
|
+
function formatNumber(format, pattern, fallback) {
|
|
251
|
+
const match = pattern.exec(format);
|
|
252
|
+
return match?.[1] ? Number(match[1]) : fallback;
|
|
253
|
+
}
|
|
254
|
+
function formatChannels(format, fallback) {
|
|
255
|
+
if (/stereo|2ch|dual/i.test(format)) return 2;
|
|
256
|
+
if (/mono|1ch/i.test(format)) return 1;
|
|
257
|
+
return fallback;
|
|
258
|
+
}
|
|
259
|
+
function formatAudioSpecification(format) {
|
|
260
|
+
const sampleRate = formatNumber(format, /(\d+)(?:khz|kHz|hz|Hz)/, 0);
|
|
261
|
+
const channels = formatChannels(format, 0);
|
|
262
|
+
const bitrateMatch = /(\d+)(?:kbitrate|kbps|kbits?)/i.exec(format);
|
|
263
|
+
const bitrate = bitrateMatch?.[1] ? Number(bitrateMatch[1]) * 1e3 : void 0;
|
|
264
|
+
const codec = /mp3|mpeg/i.test(format) ? "mp3" : /opus/i.test(format) ? "opus" : /silk/i.test(format) ? "silk" : /pcm|mulaw|alaw|siren/i.test(format) ? "pcm" : "unknown";
|
|
265
|
+
const bitDepthMatch = /(\d+)bit/i.exec(format);
|
|
266
|
+
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;
|
|
267
|
+
return {
|
|
268
|
+
format,
|
|
269
|
+
mimeType: resolveMimeType(format),
|
|
270
|
+
codec,
|
|
271
|
+
sampleRate,
|
|
272
|
+
channels,
|
|
273
|
+
...bitrate ? { bitrate } : {},
|
|
274
|
+
...bitDepthMatch?.[1] ? { bitDepth: Number(bitDepthMatch[1]) } : {},
|
|
275
|
+
...container ? { container } : {},
|
|
276
|
+
isVbr: /vbr/i.test(format),
|
|
277
|
+
isCompressed: codec === "mp3" || codec === "opus" || codec === "silk"
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function parseMp3Specification(buffer, format) {
|
|
281
|
+
const bytes = stripMp3Tags(buffer);
|
|
282
|
+
const bitrates = [
|
|
283
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
284
|
+
[0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0],
|
|
285
|
+
[0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0]
|
|
286
|
+
];
|
|
287
|
+
const sampleRates = [
|
|
288
|
+
[44100, 48e3, 32e3],
|
|
289
|
+
[22050, 24e3, 16e3],
|
|
290
|
+
[11025, 12e3, 8e3]
|
|
291
|
+
];
|
|
292
|
+
for (let index = 0; index + 4 <= bytes.length; index += 1) {
|
|
293
|
+
if (bytes[index] !== 255 || (bytes[index + 1] ?? 0) < 224) continue;
|
|
294
|
+
const header = bytes[index + 1] ?? 0;
|
|
295
|
+
const versionBits = header >> 3 & 3;
|
|
296
|
+
const layer = header >> 1 & 3;
|
|
297
|
+
const bitrateIndex = (bytes[index + 2] ?? 0) >> 4;
|
|
298
|
+
const sampleIndex = (bytes[index + 2] ?? 0) >> 2 & 3;
|
|
299
|
+
if (versionBits === 1 || layer !== 1 || bitrateIndex === 0 || bitrateIndex === 15 || sampleIndex === 3) continue;
|
|
300
|
+
const versionIndex = versionBits === 3 ? 0 : versionBits === 2 ? 1 : 2;
|
|
301
|
+
const bitrateTable = versionBits === 3 ? bitrates[1] : bitrates[2];
|
|
302
|
+
const sampleRate = sampleRates[versionIndex]?.[sampleIndex] ?? 0;
|
|
303
|
+
const bitrateKbps = bitrateTable?.[bitrateIndex] ?? 0;
|
|
304
|
+
if (!sampleRate || !bitrateKbps) continue;
|
|
305
|
+
return {
|
|
306
|
+
format,
|
|
307
|
+
mimeType: "audio/mpeg",
|
|
308
|
+
codec: "mp3",
|
|
309
|
+
sampleRate,
|
|
310
|
+
channels: (bytes[index + 3] ?? 0) >> 6 === 3 ? 1 : 2,
|
|
311
|
+
bitrate: bitrateKbps * 1e3,
|
|
312
|
+
container: "mp3-raw",
|
|
313
|
+
isVbr: false,
|
|
314
|
+
isCompressed: true
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
return void 0;
|
|
318
|
+
}
|
|
319
|
+
function inspectAudioSpecification(buffer, format) {
|
|
320
|
+
if (isWavFormat(format) || ascii(new Uint8Array(buffer), 0, "RIFF")) {
|
|
321
|
+
const parsed = parseWav(buffer);
|
|
322
|
+
if (parsed.format.byteLength < 16) throw new Error("Invalid WAV fmt chunk.");
|
|
323
|
+
const view = new DataView(parsed.format.buffer, parsed.format.byteOffset, parsed.format.byteLength);
|
|
324
|
+
const sampleRate = view.getUint32(4, true);
|
|
325
|
+
const channels = view.getUint16(2, true);
|
|
326
|
+
const bitsPerSample = parsed.format.byteLength >= 16 ? view.getUint16(14, true) : 0;
|
|
327
|
+
const formatCode = view.getUint16(0, true);
|
|
328
|
+
return {
|
|
329
|
+
format,
|
|
330
|
+
mimeType: "audio/wav",
|
|
331
|
+
codec: formatCode === 1 ? "pcm" : "unknown",
|
|
332
|
+
sampleRate,
|
|
333
|
+
channels,
|
|
334
|
+
...sampleRate && channels && bitsPerSample ? { bitrate: sampleRate * channels * bitsPerSample } : {},
|
|
335
|
+
bitDepth: bitsPerSample,
|
|
336
|
+
container: "riff-wave",
|
|
337
|
+
isVbr: false,
|
|
338
|
+
isCompressed: formatCode !== 1
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
if (isMp3Format(format)) return parseMp3Specification(buffer, format) ?? formatAudioSpecification(format);
|
|
342
|
+
return formatAudioSpecification(format);
|
|
343
|
+
}
|
|
344
|
+
function validateAudioSpecifications(specs) {
|
|
345
|
+
const first = specs[0];
|
|
346
|
+
if (!first) return;
|
|
347
|
+
const mismatch = specs.find(
|
|
348
|
+
(spec) => spec.sampleRate !== first.sampleRate || spec.channels !== first.channels || 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
|
|
349
|
+
);
|
|
350
|
+
if (mismatch)
|
|
351
|
+
throw new AudioFormatMismatchError(
|
|
352
|
+
`Audio chunks have incompatible specifications: ${first.sampleRate}Hz/${first.channels}ch versus ${mismatch.sampleRate}Hz/${mismatch.channels}ch.`,
|
|
353
|
+
specs
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
function isAudioFormatMismatch(error) {
|
|
357
|
+
return error instanceof AudioFormatMismatchError || error !== null && typeof error === "object" && "kind" in error && error.kind === "audio-format-mismatch";
|
|
358
|
+
}
|
|
211
359
|
function writeUint32(target, offset, value) {
|
|
212
360
|
new DataView(target.buffer).setUint32(offset, value, true);
|
|
213
361
|
}
|
|
@@ -293,6 +441,7 @@ function mergeAudioBuffers(buffers, options) {
|
|
|
293
441
|
const format = typeof options === "string" ? options : options?.format;
|
|
294
442
|
if (!format) throw new UnsupportedMergeFormatError("");
|
|
295
443
|
try {
|
|
444
|
+
validateAudioSpecifications(buffers.map((buffer) => inspectAudioSpecification(buffer, format)));
|
|
296
445
|
if (isWavFormat(format)) return mergeWavBuffers(buffers);
|
|
297
446
|
if (isMp3Format(format)) {
|
|
298
447
|
const parts = buffers.map(stripMp3Tags);
|
|
@@ -315,7 +464,8 @@ function mergeAudioBuffers(buffers, options) {
|
|
|
315
464
|
}
|
|
316
465
|
throw new UnsupportedMergeFormatError(format);
|
|
317
466
|
} catch (error) {
|
|
318
|
-
if (error instanceof UnsupportedMergeFormatError || error instanceof MergeError)
|
|
467
|
+
if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
|
|
468
|
+
throw error;
|
|
319
469
|
throw new MergeError(`Audio buffers could not be merged for format "${format}".`, error);
|
|
320
470
|
}
|
|
321
471
|
}
|
|
@@ -393,17 +543,24 @@ async function synthesizeSsml(ssml, config) {
|
|
|
393
543
|
return {
|
|
394
544
|
originalTextRange: { ...marker.originalTextRange },
|
|
395
545
|
sourceNodePath: [...marker.sourceNodePath],
|
|
396
|
-
textRange: { ...marker.originalTextRange }
|
|
546
|
+
textRange: { ...marker.originalTextRange },
|
|
547
|
+
mappingStatus: "exact"
|
|
397
548
|
};
|
|
398
549
|
}
|
|
399
|
-
if (sourceSegments.length === 0 && !config.sourceTextRange && !config.sourceNodePath)
|
|
550
|
+
if (sourceSegments.length === 0 && !config.sourceTextRange && !config.sourceNodePath) {
|
|
551
|
+
return { mappingStatus: "unmapped" };
|
|
552
|
+
}
|
|
400
553
|
const value = text ?? "";
|
|
401
554
|
let localStart = Number.isFinite(offsetHint) && (offsetHint ?? 0) >= 0 ? offsetHint : -1;
|
|
402
|
-
|
|
555
|
+
let mappingStatus = "exact";
|
|
556
|
+
if (value && localStart >= 0 && sourceText.slice(localStart, localStart + value.length) !== value) {
|
|
403
557
|
localStart = -1;
|
|
558
|
+
mappingStatus = "fallback";
|
|
559
|
+
}
|
|
404
560
|
if (localStart < 0 || localStart > sourceText.length) {
|
|
405
561
|
localStart = value ? sourceText.indexOf(value, sourceEventCursor) : sourceEventCursor;
|
|
406
562
|
if (localStart < 0) localStart = value ? sourceText.indexOf(value) : sourceEventCursor;
|
|
563
|
+
mappingStatus = "fallback";
|
|
407
564
|
}
|
|
408
565
|
localStart = Math.max(0, localStart);
|
|
409
566
|
const localEnd = Math.min(sourceText.length, localStart + value.length);
|
|
@@ -414,7 +571,8 @@ async function synthesizeSsml(ssml, config) {
|
|
|
414
571
|
return {
|
|
415
572
|
originalTextRange: { ...fallbackRange },
|
|
416
573
|
textRange: { ...fallbackRange },
|
|
417
|
-
...segment ? { sourceNodePath: [...segment.sourceNodePath] } : config.sourceNodePath ? { sourceNodePath: [...config.sourceNodePath] } : {}
|
|
574
|
+
...segment ? { sourceNodePath: [...segment.sourceNodePath] } : config.sourceNodePath ? { sourceNodePath: [...config.sourceNodePath] } : {},
|
|
575
|
+
mappingStatus: segment || config.sourceTextRange || config.sourceNodePath ? mappingStatus : "unmapped"
|
|
418
576
|
};
|
|
419
577
|
};
|
|
420
578
|
synthesizer.wordBoundary = (_sender, event) => {
|
|
@@ -463,20 +621,32 @@ async function synthesizeSsml(ssml, config) {
|
|
|
463
621
|
);
|
|
464
622
|
const durationMs = result.audioDuration ? ticksToMilliseconds(result.audioDuration) : eventDurationMs;
|
|
465
623
|
const requestId = result.resultId;
|
|
466
|
-
const addSourceMetadata = (event) =>
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
624
|
+
const addSourceMetadata = (event) => {
|
|
625
|
+
const mapped = {
|
|
626
|
+
...event,
|
|
627
|
+
...config.sourceTextRange && !("textRange" in event) ? { textRange: { ...config.sourceTextRange } } : {},
|
|
628
|
+
...config.sourceTextRange && !("originalTextRange" in event) ? { originalTextRange: { ...config.sourceTextRange } } : {},
|
|
629
|
+
...config.chunkIndex !== void 0 ? { chunkIndex: config.chunkIndex } : {},
|
|
630
|
+
...config.sourceNodePath ? { sourceNodePath: [...config.sourceNodePath] } : {},
|
|
631
|
+
...requestId ? { requestId } : {}
|
|
632
|
+
};
|
|
633
|
+
if (event.mappingStatus === "unmapped") {
|
|
634
|
+
Object.defineProperty(mapped, "mappingStatus", { value: "unmapped", enumerable: false });
|
|
635
|
+
Object.defineProperty(mapped, "toJSON", {
|
|
636
|
+
value: () => ({ ...mapped, mappingStatus: "unmapped" }),
|
|
637
|
+
enumerable: false
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
return mapped;
|
|
641
|
+
};
|
|
474
642
|
const sourceBoundaries = boundaries.map((boundary) => addSourceMetadata(boundary));
|
|
475
643
|
const sourceVisemes = visemes.map((viseme) => addSourceMetadata(viseme));
|
|
476
644
|
const sourceBookmarks = bookmarks.map((bookmark) => addSourceMetadata(bookmark));
|
|
477
645
|
resolve({
|
|
478
646
|
audioData: result.audioData,
|
|
479
647
|
durationMs,
|
|
648
|
+
audioSpec: inspectAudioSpecification(result.audioData, config.outputFormat ?? DEFAULT_OUTPUT_FORMAT),
|
|
649
|
+
mimeType: resolveMimeType(config.outputFormat ?? DEFAULT_OUTPUT_FORMAT),
|
|
480
650
|
...config.sourceTextRange ? { textRange: { ...config.sourceTextRange } } : {},
|
|
481
651
|
...requestId ? { requestId } : {},
|
|
482
652
|
...sourceBoundaries.length > 0 ? { boundaries: sourceBoundaries, wordBoundary: sourceBoundaries, wordBoundaries: sourceBoundaries } : {},
|
|
@@ -489,10 +659,11 @@ async function synthesizeSsml(ssml, config) {
|
|
|
489
659
|
abortHandler = () => rejectWithError(new SynthesisCancelledError());
|
|
490
660
|
config.signal.addEventListener("abort", abortHandler, { once: true });
|
|
491
661
|
}
|
|
492
|
-
|
|
662
|
+
const timeoutMs = config.timeouts?.perChunkMs ?? config.timeoutMs;
|
|
663
|
+
if (timeoutMs !== void 0 && timeoutMs > 0) {
|
|
493
664
|
timeout = setTimeout(
|
|
494
|
-
() => rejectWithError(new SynthesisTimeoutError(`Speech synthesis timed out after ${
|
|
495
|
-
|
|
665
|
+
() => rejectWithError(new SynthesisTimeoutError(`Speech synthesis timed out after ${timeoutMs} ms.`)),
|
|
666
|
+
timeoutMs
|
|
496
667
|
);
|
|
497
668
|
}
|
|
498
669
|
synthesizer.speakSsmlAsync(ssml, cb, rejectWithError);
|
|
@@ -501,9 +672,109 @@ async function synthesizeSsml(ssml, config) {
|
|
|
501
672
|
}
|
|
502
673
|
});
|
|
503
674
|
}
|
|
675
|
+
function isRetryableSynthesisError(error) {
|
|
676
|
+
if (error instanceof SynthesisCancelledError || error instanceof SynthesisTimeoutError) return false;
|
|
677
|
+
if (error instanceof AzureTtsError && error.status !== 0)
|
|
678
|
+
return error.status === 429 || error.status >= 500 && error.status < 600;
|
|
679
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
680
|
+
if (/\b4\d{2}\b/.test(message)) return false;
|
|
681
|
+
const status = error && typeof error === "object" && "status" in error ? error.status : void 0;
|
|
682
|
+
if (typeof status === "number") return status === 429 || status >= 500 && status < 600;
|
|
683
|
+
return /network|connection|connect|socket|fetch failed|econn|etimedout|temporar|transient|unavailable/i.test(message);
|
|
684
|
+
}
|
|
685
|
+
function retryDelay(options, retryAttempt, error) {
|
|
686
|
+
const retryAfterMs = getRetryAfterDelayMs(error);
|
|
687
|
+
if (retryAfterMs !== void 0) return retryAfterMs;
|
|
688
|
+
const base = Math.min(options.maxDelayMs, options.initialDelayMs * 2 ** Math.max(0, retryAttempt - 1));
|
|
689
|
+
return Math.floor(Math.random() * (base + 1));
|
|
690
|
+
}
|
|
691
|
+
function resolveConcurrency(value, total) {
|
|
692
|
+
if (value === void 0) return 1;
|
|
693
|
+
if (value === Infinity) return Math.max(1, total);
|
|
694
|
+
return Number.isFinite(value) ? Math.max(1, Math.floor(value)) : 1;
|
|
695
|
+
}
|
|
696
|
+
async function waitForRetry(delayMs, signal) {
|
|
697
|
+
if (signal?.aborted) throw new SynthesisCancelledError();
|
|
698
|
+
if (delayMs <= 0) return;
|
|
699
|
+
await new Promise((resolve, reject) => {
|
|
700
|
+
let timer;
|
|
701
|
+
const abort = () => {
|
|
702
|
+
clearTimeout(timer);
|
|
703
|
+
signal?.removeEventListener("abort", abort);
|
|
704
|
+
reject(new SynthesisCancelledError());
|
|
705
|
+
};
|
|
706
|
+
timer = setTimeout(() => {
|
|
707
|
+
signal?.removeEventListener("abort", abort);
|
|
708
|
+
resolve();
|
|
709
|
+
}, delayMs);
|
|
710
|
+
if (signal) {
|
|
711
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
async function synthesizeWithRetry(ssml, config, retryOptions, onRetry) {
|
|
716
|
+
const options = retryOptions ? {
|
|
717
|
+
maxRetries: Math.max(0, Math.floor(retryOptions.maxRetries)),
|
|
718
|
+
initialDelayMs: Math.max(0, retryOptions.initialDelayMs),
|
|
719
|
+
maxDelayMs: Math.max(0, retryOptions.maxDelayMs),
|
|
720
|
+
shouldRetry: retryOptions.shouldRetry
|
|
721
|
+
} : void 0;
|
|
722
|
+
let attempt = 0;
|
|
723
|
+
while (true) {
|
|
724
|
+
if (config.signal?.aborted) throw new SynthesisCancelledError();
|
|
725
|
+
try {
|
|
726
|
+
return await synthesizeSsml(ssml, config);
|
|
727
|
+
} catch (error) {
|
|
728
|
+
if (!options || attempt >= options.maxRetries || !(options.shouldRetry?.(error, attempt + 1) ?? isRetryableSynthesisError(error)))
|
|
729
|
+
throw error;
|
|
730
|
+
attempt += 1;
|
|
731
|
+
const delayMs = retryDelay(options, attempt, error);
|
|
732
|
+
onRetry(attempt, delayMs);
|
|
733
|
+
await waitForRetry(delayMs, config.signal);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
function createAbortScope(parent, timeoutMs) {
|
|
738
|
+
const controller = new AbortController();
|
|
739
|
+
let didTimeout = false;
|
|
740
|
+
const onAbort = () => controller.abort();
|
|
741
|
+
if (parent?.aborted) controller.abort();
|
|
742
|
+
parent?.addEventListener("abort", onAbort, { once: true });
|
|
743
|
+
const timer = timeoutMs !== void 0 && timeoutMs > 0 ? setTimeout(() => {
|
|
744
|
+
didTimeout = true;
|
|
745
|
+
controller.abort();
|
|
746
|
+
}, timeoutMs) : void 0;
|
|
747
|
+
return {
|
|
748
|
+
signal: controller.signal,
|
|
749
|
+
timedOut: () => didTimeout,
|
|
750
|
+
dispose: () => {
|
|
751
|
+
if (timer) clearTimeout(timer);
|
|
752
|
+
parent?.removeEventListener("abort", onAbort);
|
|
753
|
+
},
|
|
754
|
+
abort: () => controller.abort()
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
async function synthesizeChunkWithTimeout(ssml, config, retryOptions, timeoutMs, onRetry) {
|
|
758
|
+
const scope = createAbortScope(config.signal, timeoutMs);
|
|
759
|
+
try {
|
|
760
|
+
return await synthesizeWithRetry(ssml, { ...config, signal: scope.signal }, retryOptions, onRetry);
|
|
761
|
+
} catch (error) {
|
|
762
|
+
if (scope.timedOut()) throw new SynthesisTimeoutError(`Speech synthesis timed out after ${timeoutMs} ms.`);
|
|
763
|
+
throw error;
|
|
764
|
+
} finally {
|
|
765
|
+
scope.dispose();
|
|
766
|
+
}
|
|
767
|
+
}
|
|
504
768
|
async function synthesizeSsmlChunks(chunks, config) {
|
|
505
|
-
const results =
|
|
769
|
+
const results = new Array(chunks.length);
|
|
506
770
|
const totalChunks = chunks.length;
|
|
771
|
+
const cachedChunks = new Map((config.resumeChunks ?? []).map((chunk) => [chunk.chunkIndex, chunk]));
|
|
772
|
+
for (const [index, cached] of cachedChunks) {
|
|
773
|
+
if (index >= 0 && index < totalChunks) results[index] = cached;
|
|
774
|
+
}
|
|
775
|
+
const requestedIndices = config.resumeChunkIndices ? new Set(config.resumeChunkIndices.filter((index) => index >= 0 && index < totalChunks)) : void 0;
|
|
776
|
+
const shouldSynthesize = (index) => !cachedChunks.has(index) && (requestedIndices === void 0 || requestedIndices.has(index));
|
|
777
|
+
const scope = createAbortScope(config.signal, config.timeouts?.totalJobMs);
|
|
507
778
|
const report = (event) => config.onProgress?.(event);
|
|
508
779
|
for (const [index, chunk] of chunks.entries()) {
|
|
509
780
|
const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
|
|
@@ -517,57 +788,112 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
517
788
|
durationMs: 0
|
|
518
789
|
});
|
|
519
790
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
const result = await synthesizeSsml(input.ssml, {
|
|
534
|
-
...config,
|
|
535
|
-
...input.originalTextRange ? { sourceTextRange: input.originalTextRange } : {},
|
|
536
|
-
...input.sourceNodePath ?? config.sourceNodePath ? { sourceNodePath: [...input.sourceNodePath ?? config.sourceNodePath ?? []] } : {},
|
|
537
|
-
...input.sourceTextSegments ? { sourceTextSegments: input.sourceTextSegments } : {},
|
|
538
|
-
...input.sourceMarkers ? { sourceMarkers: input.sourceMarkers } : {},
|
|
539
|
-
chunkIndex: index,
|
|
540
|
-
onProgress: void 0
|
|
541
|
-
});
|
|
542
|
-
results.push(result);
|
|
543
|
-
report({
|
|
544
|
-
currentChunk: index + 1,
|
|
545
|
-
totalChunks,
|
|
546
|
-
percent: totalChunks === 0 ? 100 : Math.round((index + 1) / totalChunks * 100),
|
|
547
|
-
chunkIndex: index,
|
|
548
|
-
originalTextRange: input.originalTextRange,
|
|
549
|
-
status: "success",
|
|
550
|
-
durationMs: Date.now() - startedAt
|
|
551
|
-
});
|
|
552
|
-
} catch (error) {
|
|
791
|
+
let completed = [...results].filter((result) => result !== void 0).length;
|
|
792
|
+
let nextIndex = 0;
|
|
793
|
+
const concurrency = resolveConcurrency(config.concurrency, chunks.length);
|
|
794
|
+
let firstError;
|
|
795
|
+
const failedIndices = /* @__PURE__ */ new Set();
|
|
796
|
+
const worker = async () => {
|
|
797
|
+
while (true) {
|
|
798
|
+
const index = nextIndex++;
|
|
799
|
+
if (index >= chunks.length) return;
|
|
800
|
+
if (!shouldSynthesize(index)) continue;
|
|
801
|
+
if (firstError && config.cancelOnFailure !== false) return;
|
|
802
|
+
const chunk = chunks[index];
|
|
803
|
+
const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
|
|
553
804
|
report({
|
|
554
|
-
currentChunk:
|
|
805
|
+
currentChunk: completed,
|
|
555
806
|
totalChunks,
|
|
556
|
-
percent: totalChunks === 0 ? 100 : Math.round(
|
|
807
|
+
percent: totalChunks === 0 ? 100 : Math.round(completed / totalChunks * 100),
|
|
557
808
|
chunkIndex: index,
|
|
558
809
|
originalTextRange: input.originalTextRange,
|
|
559
|
-
status: "
|
|
560
|
-
durationMs:
|
|
561
|
-
error
|
|
810
|
+
status: "synthesizing",
|
|
811
|
+
durationMs: 0
|
|
562
812
|
});
|
|
563
|
-
|
|
813
|
+
const startedAt = Date.now();
|
|
814
|
+
try {
|
|
815
|
+
const result = await synthesizeChunkWithTimeout(
|
|
816
|
+
input.ssml,
|
|
817
|
+
{
|
|
818
|
+
...config,
|
|
819
|
+
signal: scope.signal,
|
|
820
|
+
...input.originalTextRange ? { sourceTextRange: input.originalTextRange } : {},
|
|
821
|
+
...input.sourceNodePath ?? config.sourceNodePath ? { sourceNodePath: [...input.sourceNodePath ?? config.sourceNodePath ?? []] } : {},
|
|
822
|
+
...input.sourceTextSegments ? { sourceTextSegments: input.sourceTextSegments } : {},
|
|
823
|
+
...input.sourceMarkers ? { sourceMarkers: input.sourceMarkers } : {},
|
|
824
|
+
chunkIndex: index,
|
|
825
|
+
onProgress: void 0
|
|
826
|
+
},
|
|
827
|
+
config.retryOptions,
|
|
828
|
+
config.timeouts?.chunkWithRetriesMs ?? config.timeouts?.perChunkMs ?? config.timeoutMs,
|
|
829
|
+
(retryAttempt, nextRetryDelayMs) => report({
|
|
830
|
+
currentChunk: completed,
|
|
831
|
+
totalChunks,
|
|
832
|
+
percent: totalChunks === 0 ? 100 : Math.round(completed / totalChunks * 100),
|
|
833
|
+
chunkIndex: index,
|
|
834
|
+
originalTextRange: input.originalTextRange,
|
|
835
|
+
status: "synthesizing",
|
|
836
|
+
durationMs: Date.now() - startedAt,
|
|
837
|
+
retryAttempt,
|
|
838
|
+
nextRetryDelayMs,
|
|
839
|
+
isRetrying: true
|
|
840
|
+
})
|
|
841
|
+
);
|
|
842
|
+
results[index] = result;
|
|
843
|
+
completed += 1;
|
|
844
|
+
report({
|
|
845
|
+
currentChunk: completed,
|
|
846
|
+
totalChunks,
|
|
847
|
+
percent: totalChunks === 0 ? 100 : Math.round(completed / totalChunks * 100),
|
|
848
|
+
chunkIndex: index,
|
|
849
|
+
originalTextRange: input.originalTextRange,
|
|
850
|
+
status: "success",
|
|
851
|
+
durationMs: Date.now() - startedAt
|
|
852
|
+
});
|
|
853
|
+
} catch (error) {
|
|
854
|
+
failedIndices.add(index);
|
|
855
|
+
report({
|
|
856
|
+
currentChunk: completed,
|
|
857
|
+
totalChunks,
|
|
858
|
+
percent: totalChunks === 0 ? 100 : Math.round(completed / totalChunks * 100),
|
|
859
|
+
chunkIndex: index,
|
|
860
|
+
originalTextRange: input.originalTextRange,
|
|
861
|
+
status: "failed",
|
|
862
|
+
durationMs: Date.now() - startedAt,
|
|
863
|
+
error
|
|
864
|
+
});
|
|
865
|
+
firstError ?? (firstError = scope.timedOut() ? new SynthesisTimeoutError(`Speech synthesis timed out after ${config.timeouts?.totalJobMs} ms.`) : error);
|
|
866
|
+
if (config.cancelOnFailure !== false) scope.abort();
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
564
869
|
}
|
|
870
|
+
};
|
|
871
|
+
try {
|
|
872
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, chunks.length) }, () => worker()));
|
|
873
|
+
if (firstError) throw firstError;
|
|
874
|
+
const orderedResults = results.filter((result) => result !== void 0);
|
|
875
|
+
return await mergeSynthesisResults(orderedResults, {
|
|
876
|
+
format: config.outputFormat ?? DEFAULT_OUTPUT_FORMAT,
|
|
877
|
+
signal: scope.signal,
|
|
878
|
+
customMerger: config.customMerger,
|
|
879
|
+
outputMimeType: config.outputMimeType,
|
|
880
|
+
postMergeValidator: config.postMergeValidator
|
|
881
|
+
});
|
|
882
|
+
} catch (error) {
|
|
883
|
+
const partial = {
|
|
884
|
+
synthesizedChunks: results.flatMap((result, chunkIndex) => result ? [{ ...result, chunkIndex }] : []),
|
|
885
|
+
completedChunks: results.flatMap((result, chunkIndex) => result ? [{ ...result, chunkIndex }] : []),
|
|
886
|
+
pendingChunkIndices: chunks.flatMap((_chunk, chunkIndex) => results[chunkIndex] ? [] : [chunkIndex]),
|
|
887
|
+
failedChunkIndices: [...failedIndices],
|
|
888
|
+
totalChunks
|
|
889
|
+
};
|
|
890
|
+
if (error && typeof error === "object") error.partialResult = partial;
|
|
891
|
+
throw error;
|
|
892
|
+
} finally {
|
|
893
|
+
scope.dispose();
|
|
565
894
|
}
|
|
566
|
-
return mergeSynthesisResults(results, {
|
|
567
|
-
format: config.outputFormat ?? "audio-16khz-128kbitrate-mono-mp3"
|
|
568
|
-
});
|
|
569
895
|
}
|
|
570
|
-
function createMergedResult(results, audioData, format) {
|
|
896
|
+
function createMergedResult(results, audioData, format, audioSpec, outputMimeType) {
|
|
571
897
|
const boundaries = [];
|
|
572
898
|
const visemes = [];
|
|
573
899
|
const bookmarks = [];
|
|
@@ -586,7 +912,8 @@ function createMergedResult(results, audioData, format) {
|
|
|
586
912
|
...boundary.sourceNodePath ? { sourceNodePath: [...boundary.sourceNodePath] } : {},
|
|
587
913
|
...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
|
|
588
914
|
...textRange ? { textRange: { ...textRange } } : {},
|
|
589
|
-
...requestId ? { requestId } : {}
|
|
915
|
+
...requestId ? { requestId } : {},
|
|
916
|
+
mappingStatus: boundary.mappingStatus ?? "unmapped"
|
|
590
917
|
});
|
|
591
918
|
}
|
|
592
919
|
for (const viseme of result.visemes ?? []) {
|
|
@@ -601,7 +928,8 @@ function createMergedResult(results, audioData, format) {
|
|
|
601
928
|
...viseme.sourceNodePath ? { sourceNodePath: [...viseme.sourceNodePath] } : {},
|
|
602
929
|
...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
|
|
603
930
|
...textRange ? { textRange: { ...textRange } } : {},
|
|
604
|
-
...requestId ? { requestId } : {}
|
|
931
|
+
...requestId ? { requestId } : {},
|
|
932
|
+
mappingStatus: viseme.mappingStatus ?? "unmapped"
|
|
605
933
|
});
|
|
606
934
|
}
|
|
607
935
|
for (const bookmark of result.bookmarks ?? []) {
|
|
@@ -616,7 +944,8 @@ function createMergedResult(results, audioData, format) {
|
|
|
616
944
|
...bookmark.sourceNodePath ? { sourceNodePath: [...bookmark.sourceNodePath] } : {},
|
|
617
945
|
...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
|
|
618
946
|
...textRange ? { textRange: { ...textRange } } : {},
|
|
619
|
-
...requestId ? { requestId } : {}
|
|
947
|
+
...requestId ? { requestId } : {},
|
|
948
|
+
mappingStatus: bookmark.mappingStatus ?? "unmapped"
|
|
620
949
|
});
|
|
621
950
|
}
|
|
622
951
|
durationOffset += Math.max(0, result.durationMs);
|
|
@@ -625,6 +954,8 @@ function createMergedResult(results, audioData, format) {
|
|
|
625
954
|
audioData,
|
|
626
955
|
durationMs: durationOffset,
|
|
627
956
|
mimeType: resolveMimeType(format),
|
|
957
|
+
audioSpec: audioSpec ?? formatAudioSpecification(format),
|
|
958
|
+
...outputMimeType ? { mimeType: outputMimeType } : {},
|
|
628
959
|
...boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {},
|
|
629
960
|
...visemes.length > 0 ? { visemes } : {},
|
|
630
961
|
...bookmarks.length > 0 ? { bookmarks } : {},
|
|
@@ -637,19 +968,73 @@ function mergeSynthesisResults(results, options) {
|
|
|
637
968
|
const format = resolvedOptions?.format;
|
|
638
969
|
if (!format) throw new UnsupportedMergeFormatError("");
|
|
639
970
|
const buffers = results.map((result) => result.audioData);
|
|
971
|
+
const inputSpecs = results.map((result) => result.audioSpec ?? inspectAudioSpecification(result.audioData, format));
|
|
972
|
+
validateAudioSpecifications(inputSpecs);
|
|
973
|
+
const signal = resolvedOptions.signal ?? new AbortController().signal;
|
|
974
|
+
if (signal.aborted) throw new SynthesisCancelledError();
|
|
640
975
|
if (resolvedOptions.customMerger) {
|
|
641
|
-
return Promise.resolve().then(
|
|
976
|
+
return Promise.resolve().then(
|
|
977
|
+
() => resolvedOptions.customMerger?.(buffers, {
|
|
978
|
+
format,
|
|
979
|
+
outputMimeType: resolvedOptions.outputMimeType ?? resolveMimeType(format),
|
|
980
|
+
inputSpecs,
|
|
981
|
+
signal
|
|
982
|
+
})
|
|
983
|
+
).then((merged) => {
|
|
642
984
|
if (!merged) throw new MergeError("The custom audio merger returned no audio buffer.");
|
|
643
|
-
|
|
985
|
+
if (!(merged instanceof ArrayBuffer) || buffers.some((buffer) => buffer.byteLength > 0) && merged.byteLength === 0)
|
|
986
|
+
throw new MergeError("The custom audio merger returned an invalid audio buffer.");
|
|
987
|
+
if (signal.aborted) throw new SynthesisCancelledError();
|
|
988
|
+
const result = createMergedResult(
|
|
989
|
+
results,
|
|
990
|
+
merged,
|
|
991
|
+
format,
|
|
992
|
+
inspectAudioSpecification(merged, format),
|
|
993
|
+
resolvedOptions.outputMimeType
|
|
994
|
+
);
|
|
995
|
+
return Promise.resolve(
|
|
996
|
+
resolvedOptions.postMergeValidator?.(result, {
|
|
997
|
+
format,
|
|
998
|
+
outputMimeType: resolvedOptions.outputMimeType ?? resolveMimeType(format),
|
|
999
|
+
inputSpecs,
|
|
1000
|
+
signal
|
|
1001
|
+
})
|
|
1002
|
+
).then((valid) => {
|
|
1003
|
+
if (valid === false) throw new MergeError("The post-merge validator rejected the merged audio.");
|
|
1004
|
+
return result;
|
|
1005
|
+
});
|
|
644
1006
|
}).catch((error) => {
|
|
645
|
-
if (error instanceof UnsupportedMergeFormatError || error instanceof MergeError)
|
|
1007
|
+
if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
|
|
1008
|
+
throw error;
|
|
646
1009
|
throw new MergeError(`Custom audio merger failed for format "${format}".`, error);
|
|
647
1010
|
});
|
|
648
1011
|
}
|
|
649
1012
|
try {
|
|
650
|
-
|
|
1013
|
+
const result = createMergedResult(
|
|
1014
|
+
results,
|
|
1015
|
+
mergeAudioBuffers(buffers, { format }),
|
|
1016
|
+
format,
|
|
1017
|
+
inputSpecs[0],
|
|
1018
|
+
resolvedOptions.outputMimeType
|
|
1019
|
+
);
|
|
1020
|
+
if (resolvedOptions.postMergeValidator) {
|
|
1021
|
+
const validation = resolvedOptions.postMergeValidator(result, {
|
|
1022
|
+
format,
|
|
1023
|
+
outputMimeType: resolvedOptions.outputMimeType ?? resolveMimeType(format),
|
|
1024
|
+
inputSpecs,
|
|
1025
|
+
signal
|
|
1026
|
+
});
|
|
1027
|
+
if (validation instanceof Promise)
|
|
1028
|
+
return validation.then((valid) => {
|
|
1029
|
+
if (valid === false) throw new MergeError("The post-merge validator rejected the merged audio.");
|
|
1030
|
+
return result;
|
|
1031
|
+
});
|
|
1032
|
+
if (validation === false) throw new MergeError("The post-merge validator rejected the merged audio.");
|
|
1033
|
+
}
|
|
1034
|
+
return result;
|
|
651
1035
|
} catch (error) {
|
|
652
|
-
if (error instanceof UnsupportedMergeFormatError || error instanceof MergeError)
|
|
1036
|
+
if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
|
|
1037
|
+
throw error;
|
|
653
1038
|
throw new MergeError(`Audio buffers could not be merged for format "${format}".`, error);
|
|
654
1039
|
}
|
|
655
1040
|
}
|
|
@@ -667,11 +1052,125 @@ var ChunkValidationError = class extends Error {
|
|
|
667
1052
|
this.diagnostics = diagnostics;
|
|
668
1053
|
}
|
|
669
1054
|
};
|
|
670
|
-
|
|
671
|
-
|
|
1055
|
+
var BatchChunkValidationError = class extends ChunkValidationError {
|
|
1056
|
+
constructor(chunkDiagnostics) {
|
|
1057
|
+
const first = chunkDiagnostics[0];
|
|
1058
|
+
super(first?.chunkIndex ?? -1, first?.diagnostics ?? []);
|
|
1059
|
+
this.name = "BatchChunkValidationError";
|
|
1060
|
+
this.message = `SSML validation failed for ${chunkDiagnostics.length} chunk(s); the Azure Speech API was not called.`;
|
|
1061
|
+
this.chunkDiagnostics = chunkDiagnostics;
|
|
1062
|
+
this.totalErrorCount = chunkDiagnostics.reduce((total, chunk) => total + chunk.diagnostics.length, 0);
|
|
1063
|
+
this.errorCount = this.totalErrorCount;
|
|
1064
|
+
this.totalErrors = this.totalErrorCount;
|
|
1065
|
+
}
|
|
1066
|
+
};
|
|
1067
|
+
function failure(error, partialResult) {
|
|
1068
|
+
return {
|
|
1069
|
+
ok: false,
|
|
1070
|
+
success: false,
|
|
1071
|
+
status: error.kind,
|
|
1072
|
+
error,
|
|
1073
|
+
...partialResult ? { partialResult } : {}
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
function partialResultFrom(error) {
|
|
1077
|
+
if (!error || typeof error !== "object") return void 0;
|
|
1078
|
+
const partial = error.partialResult;
|
|
1079
|
+
if (!partial || typeof partial !== "object") return void 0;
|
|
1080
|
+
return partial;
|
|
1081
|
+
}
|
|
1082
|
+
function createSafeAbortScope(parent, timeoutMs) {
|
|
1083
|
+
const controller = new AbortController();
|
|
1084
|
+
let didTimeout = false;
|
|
1085
|
+
const onAbort = () => controller.abort();
|
|
1086
|
+
if (parent?.aborted) controller.abort();
|
|
1087
|
+
parent?.addEventListener("abort", onAbort, { once: true });
|
|
1088
|
+
const timer = timeoutMs !== void 0 && timeoutMs > 0 ? setTimeout(() => {
|
|
1089
|
+
didTimeout = true;
|
|
1090
|
+
controller.abort();
|
|
1091
|
+
}, timeoutMs) : void 0;
|
|
1092
|
+
return {
|
|
1093
|
+
signal: controller.signal,
|
|
1094
|
+
timedOut: () => didTimeout,
|
|
1095
|
+
dispose: () => {
|
|
1096
|
+
if (timer) clearTimeout(timer);
|
|
1097
|
+
parent?.removeEventListener("abort", onAbort);
|
|
1098
|
+
},
|
|
1099
|
+
abort: () => controller.abort()
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
function isRetryable(error) {
|
|
1103
|
+
if (error instanceof Error && /cancel|abort|tim(?:e|ed) ?out/i.test(error.message)) return false;
|
|
1104
|
+
const status = error && typeof error === "object" && "status" in error ? error.status : void 0;
|
|
1105
|
+
if (typeof status === "number" && status !== 0) return status === 429 || status >= 500 && status < 600;
|
|
1106
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1107
|
+
if (/\b4\d{2}\b/.test(message)) return false;
|
|
1108
|
+
return /network|connection|connect|socket|fetch failed|econn|etimedout|temporar|transient|unavailable/i.test(message);
|
|
1109
|
+
}
|
|
1110
|
+
function delayForRetry(options, attempt) {
|
|
1111
|
+
const maxDelay = Math.max(0, options.maxDelayMs);
|
|
1112
|
+
const base = Math.min(maxDelay, Math.max(0, options.initialDelayMs) * 2 ** Math.max(0, attempt - 1));
|
|
1113
|
+
return Math.floor(Math.random() * (base + 1));
|
|
1114
|
+
}
|
|
1115
|
+
function retryDelayForError(options, attempt, error) {
|
|
1116
|
+
return getRetryAfterDelayMs(error) ?? delayForRetry(options, attempt);
|
|
1117
|
+
}
|
|
1118
|
+
function resolveConcurrency2(value, total) {
|
|
1119
|
+
if (value === void 0) return 1;
|
|
1120
|
+
if (value === Infinity) return Math.max(1, total);
|
|
1121
|
+
return Number.isFinite(value) ? Math.max(1, Math.floor(value)) : 1;
|
|
1122
|
+
}
|
|
1123
|
+
async function retryableSynthesis(synthesize, options, signal, onRetry) {
|
|
1124
|
+
const retry = options ? {
|
|
1125
|
+
maxRetries: Math.max(0, Math.floor(options.maxRetries)),
|
|
1126
|
+
initialDelayMs: options.initialDelayMs,
|
|
1127
|
+
maxDelayMs: options.maxDelayMs,
|
|
1128
|
+
shouldRetry: options.shouldRetry
|
|
1129
|
+
} : void 0;
|
|
1130
|
+
let attempt = 0;
|
|
1131
|
+
while (true) {
|
|
1132
|
+
if (signal?.aborted) throw new Error("Speech synthesis was cancelled.");
|
|
1133
|
+
try {
|
|
1134
|
+
return await synthesize();
|
|
1135
|
+
} catch (error) {
|
|
1136
|
+
if (!retry || attempt >= retry.maxRetries || !(retry.shouldRetry?.(error, attempt + 1) ?? isRetryable(error)))
|
|
1137
|
+
throw error;
|
|
1138
|
+
attempt += 1;
|
|
1139
|
+
const delayMs = retryDelayForError(retry, attempt, error);
|
|
1140
|
+
onRetry(attempt, delayMs);
|
|
1141
|
+
if (delayMs > 0)
|
|
1142
|
+
await new Promise((resolve, reject) => {
|
|
1143
|
+
const timer = setTimeout(() => {
|
|
1144
|
+
signal?.removeEventListener("abort", abort);
|
|
1145
|
+
resolve();
|
|
1146
|
+
}, delayMs);
|
|
1147
|
+
const abort = () => {
|
|
1148
|
+
clearTimeout(timer);
|
|
1149
|
+
signal?.removeEventListener("abort", abort);
|
|
1150
|
+
reject(new Error("Speech synthesis was cancelled."));
|
|
1151
|
+
};
|
|
1152
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
function sharedValidationOptions(options, signal) {
|
|
1158
|
+
const validator = options.urlValidator ?? options.customUrlValidator;
|
|
1159
|
+
if (!validator) return signal ? withValidationSignal(options, signal) : options;
|
|
1160
|
+
const runner = createAzureUrlValidatorRunner2(validator, {
|
|
1161
|
+
...options.urlValidation ?? {},
|
|
1162
|
+
...options.urlValidatorConcurrency !== void 0 ? { concurrency: options.urlValidatorConcurrency } : {},
|
|
1163
|
+
...options.timeouts?.urlValidationMs !== void 0 ? { timeoutMs: options.timeouts.urlValidationMs } : options.urlValidatorTimeoutMs !== void 0 ? { timeoutMs: options.urlValidatorTimeoutMs } : {},
|
|
1164
|
+
...signal ? { signal } : {},
|
|
1165
|
+
...options.urlValidatorCache ? { cache: options.urlValidatorCache } : {}
|
|
1166
|
+
});
|
|
1167
|
+
return {
|
|
1168
|
+
...withValidationSignal(options, signal),
|
|
1169
|
+
urlValidatorRunner: runner
|
|
1170
|
+
};
|
|
672
1171
|
}
|
|
673
1172
|
async function synthesizeSsmlSafe(client, ssml, options = {}) {
|
|
674
|
-
const validationOptions =
|
|
1173
|
+
const validationOptions = sharedValidationOptions(options.validation ?? options, options.signal);
|
|
675
1174
|
const diagnostics = await Promise.resolve(validateAzureSsml2(ssml, validationOptions));
|
|
676
1175
|
if (options.signal?.aborted) {
|
|
677
1176
|
const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
|
|
@@ -690,7 +1189,11 @@ async function synthesizeSsmlSafe(client, ssml, options = {}) {
|
|
|
690
1189
|
ok: true,
|
|
691
1190
|
success: true,
|
|
692
1191
|
status: "success",
|
|
693
|
-
value: await client.synthesizeSsml(ssml, {
|
|
1192
|
+
value: await client.synthesizeSsml(ssml, {
|
|
1193
|
+
signal: options.signal,
|
|
1194
|
+
timeoutMs: options.timeouts?.perChunkMs,
|
|
1195
|
+
timeouts: options.timeouts
|
|
1196
|
+
})
|
|
694
1197
|
};
|
|
695
1198
|
} catch (error) {
|
|
696
1199
|
const synthesisError = toSynthesisError(error);
|
|
@@ -698,7 +1201,10 @@ async function synthesizeSsmlSafe(client, ssml, options = {}) {
|
|
|
698
1201
|
}
|
|
699
1202
|
}
|
|
700
1203
|
async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
701
|
-
const validationOptions =
|
|
1204
|
+
const validationOptions = sharedValidationOptions(
|
|
1205
|
+
{ ...options.validation ?? options, timeouts: options.timeouts },
|
|
1206
|
+
options.signal
|
|
1207
|
+
);
|
|
702
1208
|
if (options.signal?.aborted) {
|
|
703
1209
|
const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
|
|
704
1210
|
return failure(error);
|
|
@@ -719,21 +1225,30 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
719
1225
|
pending(index, "pending");
|
|
720
1226
|
});
|
|
721
1227
|
const validations = await Promise.all(
|
|
722
|
-
chunks.map(async (chunk) => {
|
|
1228
|
+
chunks.map(async (chunk, index) => {
|
|
723
1229
|
const ssml = typeof chunk === "string" ? chunk : chunk.ssml;
|
|
724
1230
|
const sourceNodePath = typeof chunk === "string" ? options.sourceNodePath : chunk.sourceNodePath ?? options.sourceNodePath;
|
|
725
1231
|
const diagnostics = await Promise.resolve(
|
|
726
|
-
validateAzureSsml2(ssml, {
|
|
1232
|
+
validateAzureSsml2(ssml, {
|
|
1233
|
+
...validationOptions,
|
|
1234
|
+
...sourceNodePath ? { sourceNodePath } : {},
|
|
1235
|
+
chunkIndex: index
|
|
1236
|
+
})
|
|
727
1237
|
);
|
|
728
1238
|
return diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
729
1239
|
})
|
|
730
1240
|
);
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
const error = new ChunkValidationError(firstInvalidIndex, validations[firstInvalidIndex] ?? []);
|
|
734
|
-
pending(firstInvalidIndex, "failed", error);
|
|
1241
|
+
if (options.signal?.aborted) {
|
|
1242
|
+
const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
|
|
735
1243
|
return failure(error);
|
|
736
1244
|
}
|
|
1245
|
+
const chunkDiagnostics = validations.map((diagnostics, chunkIndex) => ({ chunkIndex, diagnostics })).filter((entry) => entry.diagnostics.length > 0);
|
|
1246
|
+
if (chunkDiagnostics.length > 0) {
|
|
1247
|
+
const error = new BatchChunkValidationError(chunkDiagnostics);
|
|
1248
|
+
for (const entry of chunkDiagnostics) pending(entry.chunkIndex, "failed", error);
|
|
1249
|
+
return failure(error);
|
|
1250
|
+
}
|
|
1251
|
+
let fallbackJobScope;
|
|
737
1252
|
try {
|
|
738
1253
|
if (client.synthesizeChunks) {
|
|
739
1254
|
const normalizedChunks = chunks.map((chunk) => {
|
|
@@ -745,101 +1260,181 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
745
1260
|
outputFormat: options.outputFormat,
|
|
746
1261
|
signal: options.signal,
|
|
747
1262
|
timeoutMs: options.timeoutMs,
|
|
748
|
-
|
|
1263
|
+
timeouts: options.timeouts,
|
|
1264
|
+
sourceNodePath: options.sourceNodePath,
|
|
1265
|
+
concurrency: options.concurrency,
|
|
1266
|
+
retryOptions: options.retryOptions,
|
|
1267
|
+
cancelOnFailure: options.cancelOnFailure,
|
|
1268
|
+
resumeChunks: options.resumeChunks,
|
|
1269
|
+
resumeChunkIndices: options.resumeChunkIndices,
|
|
1270
|
+
customMerger: options.customMerger,
|
|
1271
|
+
outputMimeType: options.outputMimeType,
|
|
1272
|
+
postMergeValidator: options.postMergeValidator
|
|
749
1273
|
});
|
|
750
1274
|
return { ok: true, success: true, status: "success", value };
|
|
751
1275
|
}
|
|
752
|
-
const results =
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
1276
|
+
const results = new Array(chunks.length);
|
|
1277
|
+
const cachedChunks = new Map((options.resumeChunks ?? []).map((chunk) => [chunk.chunkIndex, chunk]));
|
|
1278
|
+
for (const [index, cached] of cachedChunks) {
|
|
1279
|
+
if (index >= 0 && index < chunks.length) results[index] = cached;
|
|
1280
|
+
}
|
|
1281
|
+
const requestedIndices = options.resumeChunkIndices ? new Set(options.resumeChunkIndices.filter((index) => index >= 0 && index < chunks.length)) : void 0;
|
|
1282
|
+
const shouldSynthesize = (index) => !cachedChunks.has(index) && (requestedIndices === void 0 || requestedIndices.has(index));
|
|
1283
|
+
const jobScope = chunks.length > 1 || options.timeouts?.totalJobMs !== void 0 ? createSafeAbortScope(options.signal, options.timeouts?.totalJobMs) : void 0;
|
|
1284
|
+
fallbackJobScope = jobScope;
|
|
1285
|
+
const failedIndices = /* @__PURE__ */ new Set();
|
|
1286
|
+
let firstError;
|
|
1287
|
+
let completed = [...results].filter((result) => result !== void 0).length;
|
|
1288
|
+
let nextIndex = 0;
|
|
1289
|
+
const concurrency = resolveConcurrency2(options.concurrency, chunks.length);
|
|
1290
|
+
const worker = async () => {
|
|
1291
|
+
while (true) {
|
|
1292
|
+
const index = nextIndex++;
|
|
1293
|
+
if (index >= chunks.length) return;
|
|
1294
|
+
if (!shouldSynthesize(index)) continue;
|
|
1295
|
+
if (failedIndices.size > 0 && options.cancelOnFailure !== false) return;
|
|
1296
|
+
const chunk = chunks[index];
|
|
1297
|
+
const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
|
|
1298
|
+
const sourceNodePath = input.sourceNodePath;
|
|
1299
|
+
const originalTextRange = input.originalTextRange;
|
|
1300
|
+
pending(index, "synthesizing");
|
|
1301
|
+
const startedAt = Date.now();
|
|
1302
|
+
try {
|
|
1303
|
+
const chunkTimeout = options.timeouts?.chunkWithRetriesMs ?? options.timeouts?.perChunkMs;
|
|
1304
|
+
const chunkScope = chunkTimeout !== void 0 || jobScope ? createSafeAbortScope(jobScope?.signal ?? options.signal, chunkTimeout ?? options.timeoutMs) : void 0;
|
|
1305
|
+
const chunkSignal = chunkScope?.signal ?? options.signal;
|
|
1306
|
+
let result;
|
|
1307
|
+
try {
|
|
1308
|
+
result = await retryableSynthesis(
|
|
1309
|
+
() => client.synthesizeSsml(input.ssml, {
|
|
1310
|
+
outputFormat: options.outputFormat,
|
|
1311
|
+
signal: chunkSignal,
|
|
1312
|
+
timeoutMs: options.timeouts?.perChunkMs ?? options.timeoutMs,
|
|
1313
|
+
sourceNodePath: input.sourceNodePath ?? options.sourceNodePath
|
|
1314
|
+
}),
|
|
1315
|
+
options.retryOptions,
|
|
1316
|
+
chunkSignal,
|
|
1317
|
+
(retryAttempt, nextRetryDelayMs) => options.onProgress?.({
|
|
1318
|
+
currentChunk: completed,
|
|
1319
|
+
totalChunks: chunks.length,
|
|
1320
|
+
percent: chunks.length === 0 ? 100 : Math.round(completed / chunks.length * 100),
|
|
1321
|
+
chunkIndex: index,
|
|
1322
|
+
originalTextRange: input.originalTextRange,
|
|
1323
|
+
status: "synthesizing",
|
|
1324
|
+
durationMs: Date.now() - startedAt,
|
|
1325
|
+
retryAttempt,
|
|
1326
|
+
nextRetryDelayMs,
|
|
1327
|
+
isRetrying: true
|
|
1328
|
+
})
|
|
1329
|
+
);
|
|
1330
|
+
} catch (error) {
|
|
1331
|
+
if (chunkScope?.timedOut())
|
|
1332
|
+
throw new Error(`Speech synthesis timed out after ${chunkTimeout ?? options.timeoutMs} ms.`);
|
|
1333
|
+
throw error;
|
|
1334
|
+
} finally {
|
|
1335
|
+
chunkScope?.dispose();
|
|
1336
|
+
}
|
|
1337
|
+
results[index] = {
|
|
1338
|
+
...result,
|
|
1339
|
+
...input.originalTextRange ? { textRange: { ...input.originalTextRange } } : {},
|
|
1340
|
+
...sourceNodePath ? {
|
|
1341
|
+
boundaries: result.boundaries?.map((event) => ({
|
|
1342
|
+
...event,
|
|
1343
|
+
sourceNodePath: [...sourceNodePath],
|
|
1344
|
+
...event.originalTextRange ? { originalTextRange: { ...event.originalTextRange } } : input.originalTextRange ? { originalTextRange: { ...input.originalTextRange } } : {}
|
|
1345
|
+
})),
|
|
1346
|
+
visemes: result.visemes?.map((event) => ({
|
|
1347
|
+
...event,
|
|
1348
|
+
sourceNodePath: [...sourceNodePath],
|
|
1349
|
+
...event.originalTextRange ? { originalTextRange: { ...event.originalTextRange } } : input.originalTextRange ? { originalTextRange: { ...input.originalTextRange } } : {}
|
|
1350
|
+
})),
|
|
1351
|
+
bookmarks: result.bookmarks?.map((event) => ({
|
|
1352
|
+
...event,
|
|
1353
|
+
sourceNodePath: [...sourceNodePath],
|
|
1354
|
+
...event.originalTextRange ? { originalTextRange: { ...event.originalTextRange } } : input.originalTextRange ? { originalTextRange: { ...input.originalTextRange } } : {}
|
|
1355
|
+
}))
|
|
1356
|
+
} : {},
|
|
1357
|
+
...originalTextRange ? {
|
|
1358
|
+
boundaries: result.boundaries?.map((event) => ({
|
|
1359
|
+
...event,
|
|
1360
|
+
originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
|
|
1361
|
+
})),
|
|
1362
|
+
wordBoundary: result.wordBoundary?.map((event) => ({
|
|
1363
|
+
...event,
|
|
1364
|
+
originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
|
|
1365
|
+
})),
|
|
1366
|
+
wordBoundaries: result.wordBoundaries?.map((event) => ({
|
|
1367
|
+
...event,
|
|
1368
|
+
originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
|
|
1369
|
+
})),
|
|
1370
|
+
visemes: result.visemes?.map((event) => ({
|
|
1371
|
+
...event,
|
|
1372
|
+
originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
|
|
1373
|
+
})),
|
|
1374
|
+
bookmarks: result.bookmarks?.map((event) => ({
|
|
1375
|
+
...event,
|
|
1376
|
+
originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
|
|
1377
|
+
}))
|
|
1378
|
+
} : {}
|
|
1379
|
+
};
|
|
1380
|
+
completed += 1;
|
|
1381
|
+
options.onProgress?.({
|
|
1382
|
+
currentChunk: completed,
|
|
1383
|
+
totalChunks: chunks.length,
|
|
1384
|
+
percent: chunks.length === 0 ? 100 : Math.round(completed / chunks.length * 100),
|
|
1385
|
+
chunkIndex: index,
|
|
1386
|
+
originalTextRange: input.originalTextRange,
|
|
1387
|
+
status: "success",
|
|
1388
|
+
durationMs: Date.now() - startedAt
|
|
1389
|
+
});
|
|
1390
|
+
} catch (error) {
|
|
1391
|
+
failedIndices.add(index);
|
|
1392
|
+
options.onProgress?.({
|
|
1393
|
+
currentChunk: completed,
|
|
1394
|
+
totalChunks: chunks.length,
|
|
1395
|
+
percent: chunks.length === 0 ? 100 : Math.round(index / chunks.length * 100),
|
|
1396
|
+
chunkIndex: index,
|
|
1397
|
+
originalTextRange: input.originalTextRange,
|
|
1398
|
+
status: "failed",
|
|
1399
|
+
durationMs: Date.now() - startedAt,
|
|
1400
|
+
error
|
|
1401
|
+
});
|
|
1402
|
+
if (options.cancelOnFailure !== false) jobScope?.abort();
|
|
1403
|
+
firstError ?? (firstError = error);
|
|
1404
|
+
return;
|
|
1405
|
+
}
|
|
830
1406
|
}
|
|
1407
|
+
};
|
|
1408
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, chunks.length) }, () => worker()));
|
|
1409
|
+
if (failedIndices.size > 0) {
|
|
1410
|
+
const error = firstError ?? new Error("One or more SSML chunks failed to synthesize.");
|
|
1411
|
+
error.partialResult = {
|
|
1412
|
+
synthesizedChunks: results.flatMap((result, chunkIndex) => result ? [{ ...result, chunkIndex }] : []),
|
|
1413
|
+
completedChunks: results.flatMap((result, chunkIndex) => result ? [{ ...result, chunkIndex }] : []),
|
|
1414
|
+
pendingChunkIndices: chunks.flatMap((_chunk, chunkIndex) => results[chunkIndex] ? [] : [chunkIndex]),
|
|
1415
|
+
failedChunkIndices: [...failedIndices],
|
|
1416
|
+
totalChunks: chunks.length
|
|
1417
|
+
};
|
|
1418
|
+
throw error;
|
|
831
1419
|
}
|
|
1420
|
+
const orderedResults = results.filter((result) => result !== void 0);
|
|
832
1421
|
return {
|
|
833
1422
|
ok: true,
|
|
834
1423
|
success: true,
|
|
835
1424
|
status: "success",
|
|
836
|
-
value: mergeSynthesisResults(
|
|
837
|
-
format: options.outputFormat ?? "audio-16khz-128kbitrate-mono-mp3"
|
|
1425
|
+
value: await mergeSynthesisResults(orderedResults, {
|
|
1426
|
+
format: options.outputFormat ?? "audio-16khz-128kbitrate-mono-mp3",
|
|
1427
|
+
signal: jobScope?.signal ?? options.signal,
|
|
1428
|
+
customMerger: options.customMerger,
|
|
1429
|
+
outputMimeType: options.outputMimeType,
|
|
1430
|
+
postMergeValidator: options.postMergeValidator
|
|
838
1431
|
})
|
|
839
1432
|
};
|
|
840
1433
|
} catch (error) {
|
|
841
1434
|
const synthesisError = toSynthesisError(error);
|
|
842
|
-
return failure(synthesisError);
|
|
1435
|
+
return failure(synthesisError, partialResultFrom(error));
|
|
1436
|
+
} finally {
|
|
1437
|
+
fallbackJobScope?.dispose();
|
|
843
1438
|
}
|
|
844
1439
|
}
|
|
845
1440
|
function withValidationSignal(options, signal) {
|
|
@@ -860,14 +1455,14 @@ var AzureTtsClient = class {
|
|
|
860
1455
|
__privateSet(this, _options, options);
|
|
861
1456
|
}
|
|
862
1457
|
async synthesize(ssml) {
|
|
863
|
-
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
1458
|
+
const { region, subscriptionKey, outputFormat, signal, timeoutMs, timeouts } = __privateGet(this, _options);
|
|
864
1459
|
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
865
1460
|
__privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
|
|
866
|
-
const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };
|
|
1461
|
+
const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs, timeouts };
|
|
867
1462
|
return synthesizeSpeech(ssml, config);
|
|
868
1463
|
}
|
|
869
1464
|
async synthesizeSsml(ssml, options = {}) {
|
|
870
|
-
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
1465
|
+
const { region, subscriptionKey, outputFormat, signal, timeoutMs, timeouts } = __privateGet(this, _options);
|
|
871
1466
|
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
872
1467
|
__privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
|
|
873
1468
|
return synthesizeSsml(ssml, {
|
|
@@ -877,13 +1472,14 @@ var AzureTtsClient = class {
|
|
|
877
1472
|
outputFormat: options.outputFormat ?? outputFormat,
|
|
878
1473
|
signal: options.signal ?? signal,
|
|
879
1474
|
timeoutMs: options.timeoutMs ?? timeoutMs,
|
|
1475
|
+
timeouts: options.timeouts ?? timeouts,
|
|
880
1476
|
sourceNodePath: options.sourceNodePath,
|
|
881
1477
|
sourceTextSegments: options.sourceTextSegments,
|
|
882
1478
|
sourceMarkers: options.sourceMarkers
|
|
883
1479
|
});
|
|
884
1480
|
}
|
|
885
1481
|
async synthesizeChunks(chunks, options = {}) {
|
|
886
|
-
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
1482
|
+
const { region, subscriptionKey, outputFormat, signal, timeoutMs, timeouts } = __privateGet(this, _options);
|
|
887
1483
|
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
888
1484
|
return synthesizeSsmlChunks(chunks, {
|
|
889
1485
|
endpoint,
|
|
@@ -892,8 +1488,17 @@ var AzureTtsClient = class {
|
|
|
892
1488
|
outputFormat: options.outputFormat ?? outputFormat,
|
|
893
1489
|
signal: options.signal ?? signal,
|
|
894
1490
|
timeoutMs: options.timeoutMs ?? timeoutMs,
|
|
1491
|
+
timeouts: options.timeouts ?? timeouts,
|
|
895
1492
|
sourceNodePath: options.sourceNodePath,
|
|
896
|
-
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress
|
|
1493
|
+
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress,
|
|
1494
|
+
concurrency: options.concurrency ?? __privateGet(this, _options).concurrency,
|
|
1495
|
+
retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions,
|
|
1496
|
+
cancelOnFailure: options.cancelOnFailure,
|
|
1497
|
+
resumeChunks: options.resumeChunks,
|
|
1498
|
+
resumeChunkIndices: options.resumeChunkIndices,
|
|
1499
|
+
customMerger: options.customMerger,
|
|
1500
|
+
outputMimeType: options.outputMimeType,
|
|
1501
|
+
postMergeValidator: options.postMergeValidator
|
|
897
1502
|
});
|
|
898
1503
|
}
|
|
899
1504
|
async synthesizeSsmlSafe(ssml, options = {}) {
|
|
@@ -905,7 +1510,10 @@ var AzureTtsClient = class {
|
|
|
905
1510
|
outputFormat: options.outputFormat ?? __privateGet(this, _options).outputFormat,
|
|
906
1511
|
signal: options.signal ?? __privateGet(this, _options).signal,
|
|
907
1512
|
timeoutMs: options.timeoutMs ?? __privateGet(this, _options).timeoutMs,
|
|
908
|
-
|
|
1513
|
+
timeouts: options.timeouts ?? __privateGet(this, _options).timeouts,
|
|
1514
|
+
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress,
|
|
1515
|
+
concurrency: options.concurrency ?? __privateGet(this, _options).concurrency,
|
|
1516
|
+
retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions
|
|
909
1517
|
});
|
|
910
1518
|
}
|
|
911
1519
|
async synthesizeSsmlChunksSafe(chunks, options = {}) {
|
|
@@ -996,14 +1604,18 @@ async function fetchAzureVoiceCatalog(options) {
|
|
|
996
1604
|
voiceCount: sortedVoices.length,
|
|
997
1605
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
998
1606
|
apiVersion: AZURE_VOICE_API_VERSION,
|
|
999
|
-
regions
|
|
1607
|
+
regions,
|
|
1608
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3).toISOString(),
|
|
1609
|
+
regionDiffs: {}
|
|
1000
1610
|
}
|
|
1001
1611
|
};
|
|
1002
1612
|
}
|
|
1003
1613
|
export {
|
|
1614
|
+
AudioFormatMismatchError,
|
|
1004
1615
|
AzureTtsClient,
|
|
1005
1616
|
AzureTtsError,
|
|
1006
1617
|
AzureTtsSdkError,
|
|
1618
|
+
BatchChunkValidationError,
|
|
1007
1619
|
ChunkValidationError,
|
|
1008
1620
|
DEFAULT_OUTPUT_FORMAT,
|
|
1009
1621
|
MergeError,
|
|
@@ -1021,7 +1633,9 @@ export {
|
|
|
1021
1633
|
fromPlainTextToSsml,
|
|
1022
1634
|
getAzureVoiceCatalogMetadata,
|
|
1023
1635
|
getBuiltInVoiceCatalogMetadata,
|
|
1636
|
+
getRetryAfterDelayMs,
|
|
1024
1637
|
getSsmlSourceMap,
|
|
1638
|
+
inspectAudioSpecification,
|
|
1025
1639
|
isValidAzureAudioDuration,
|
|
1026
1640
|
mapSsmlTextNodes,
|
|
1027
1641
|
mergeAudioBuffers,
|
|
@@ -1037,6 +1651,7 @@ export {
|
|
|
1037
1651
|
synthesizeSsmlChunksSafe,
|
|
1038
1652
|
synthesizeSsmlSafe,
|
|
1039
1653
|
validateAzureSsml,
|
|
1654
|
+
validateAzureSsmlChunks,
|
|
1040
1655
|
validateSsml,
|
|
1041
1656
|
validateSsmlStructureIntegrity
|
|
1042
1657
|
};
|