ssml-builder-js 2.15.0 → 2.16.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 +5 -1
- package/dist/{chunk-BDY2Q2JL.mjs → chunk-F2EMU3HM.mjs} +2 -2
- package/dist/{chunk-WXFLUCLR.mjs → chunk-HI74FTKY.mjs} +34 -5
- package/dist/chunk-HI74FTKY.mjs.map +1 -0
- package/dist/{chunk-FXUM45ZY.mjs → chunk-NKLZGITR.mjs} +50 -5
- package/dist/chunk-NKLZGITR.mjs.map +1 -0
- package/dist/core.d.mts +20 -1
- package/dist/core.d.ts +20 -1
- package/dist/core.js +50 -4
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +3 -1
- package/dist/elements.js +32 -4
- package/dist/elements.js.map +1 -1
- package/dist/elements.mjs +2 -2
- package/dist/{index.d-8BvkB9gz.d.mts → index.d-DR5Qz43p.d.mts} +15 -0
- package/dist/{index.d-8BvkB9gz.d.ts → index.d-DR5Qz43p.d.ts} +15 -0
- package/dist/index.d.mts +54 -7
- package/dist/index.d.ts +54 -7
- package/dist/index.js +592 -162
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +513 -156
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +32 -4
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +2 -2
- 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-F2EMU3HM.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-NKLZGITR.mjs";
|
|
21
22
|
import {
|
|
23
|
+
createAzureUrlValidatorRunner as createAzureUrlValidatorRunner2,
|
|
22
24
|
getSsmlSourceMap as getSsmlSourceMap2,
|
|
23
25
|
validateAzureSsml as validateAzureSsml2
|
|
24
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-HI74FTKY.mjs";
|
|
25
27
|
import {
|
|
26
28
|
__privateAdd,
|
|
27
29
|
__privateGet,
|
|
@@ -70,6 +72,14 @@ var MergeError = class extends Error {
|
|
|
70
72
|
this.cause = cause;
|
|
71
73
|
}
|
|
72
74
|
};
|
|
75
|
+
var AudioFormatMismatchError = class extends Error {
|
|
76
|
+
constructor(message, inputSpecs = []) {
|
|
77
|
+
super(message);
|
|
78
|
+
this.kind = "audio-format-mismatch";
|
|
79
|
+
this.name = "AudioFormatMismatchError";
|
|
80
|
+
this.inputSpecs = inputSpecs;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
73
83
|
var UnsupportedMergeFormatError = class extends Error {
|
|
74
84
|
constructor(format) {
|
|
75
85
|
super(`Audio format "${format}" cannot be safely concatenated; container re-multiplexing is required.`);
|
|
@@ -79,7 +89,7 @@ var UnsupportedMergeFormatError = class extends Error {
|
|
|
79
89
|
}
|
|
80
90
|
};
|
|
81
91
|
function toSynthesisError(error) {
|
|
82
|
-
if (error instanceof AzureTtsError || error instanceof MergeError || error instanceof UnsupportedMergeFormatError || error instanceof SynthesisCancelledError || error instanceof SynthesisTimeoutError)
|
|
92
|
+
if (error instanceof AzureTtsError || error instanceof MergeError || error instanceof AudioFormatMismatchError || error instanceof UnsupportedMergeFormatError || error instanceof SynthesisCancelledError || error instanceof SynthesisTimeoutError)
|
|
83
93
|
return error;
|
|
84
94
|
const message = error instanceof Error ? error.message : String(error);
|
|
85
95
|
if (/cancel|abort/i.test(message)) return new SynthesisCancelledError(message);
|
|
@@ -208,6 +218,105 @@ function parseWav(buffer) {
|
|
|
208
218
|
}
|
|
209
219
|
return { chunks, data, format };
|
|
210
220
|
}
|
|
221
|
+
function formatNumber(format, pattern, fallback) {
|
|
222
|
+
const match = pattern.exec(format);
|
|
223
|
+
return match?.[1] ? Number(match[1]) : fallback;
|
|
224
|
+
}
|
|
225
|
+
function formatChannels(format, fallback) {
|
|
226
|
+
if (/stereo|2ch|dual/i.test(format)) return 2;
|
|
227
|
+
if (/mono|1ch/i.test(format)) return 1;
|
|
228
|
+
return fallback;
|
|
229
|
+
}
|
|
230
|
+
function formatAudioSpecification(format) {
|
|
231
|
+
const sampleRate = formatNumber(format, /(\d+)(?:khz|kHz|hz|Hz)/, 0);
|
|
232
|
+
const channels = formatChannels(format, 0);
|
|
233
|
+
const bitrateMatch = /(\d+)(?:kbitrate|kbps|kbits?)/i.exec(format);
|
|
234
|
+
const bitrate = bitrateMatch?.[1] ? Number(bitrateMatch[1]) * 1e3 : void 0;
|
|
235
|
+
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";
|
|
236
|
+
return {
|
|
237
|
+
format,
|
|
238
|
+
mimeType: resolveMimeType(format),
|
|
239
|
+
codec,
|
|
240
|
+
sampleRate,
|
|
241
|
+
channels,
|
|
242
|
+
...bitrate ? { bitrate } : {},
|
|
243
|
+
isCompressed: codec === "mp3" || codec === "opus" || codec === "silk"
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function parseMp3Specification(buffer, format) {
|
|
247
|
+
const bytes = stripMp3Tags(buffer);
|
|
248
|
+
const bitrates = [
|
|
249
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
250
|
+
[0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0],
|
|
251
|
+
[0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0]
|
|
252
|
+
];
|
|
253
|
+
const sampleRates = [
|
|
254
|
+
[44100, 48e3, 32e3],
|
|
255
|
+
[22050, 24e3, 16e3],
|
|
256
|
+
[11025, 12e3, 8e3]
|
|
257
|
+
];
|
|
258
|
+
for (let index = 0; index + 4 <= bytes.length; index += 1) {
|
|
259
|
+
if (bytes[index] !== 255 || (bytes[index + 1] ?? 0) < 224) continue;
|
|
260
|
+
const header = bytes[index + 1] ?? 0;
|
|
261
|
+
const versionBits = header >> 3 & 3;
|
|
262
|
+
const layer = header >> 1 & 3;
|
|
263
|
+
const bitrateIndex = (bytes[index + 2] ?? 0) >> 4;
|
|
264
|
+
const sampleIndex = (bytes[index + 2] ?? 0) >> 2 & 3;
|
|
265
|
+
if (versionBits === 1 || layer !== 1 || bitrateIndex === 0 || bitrateIndex === 15 || sampleIndex === 3) continue;
|
|
266
|
+
const versionIndex = versionBits === 3 ? 0 : versionBits === 2 ? 1 : 2;
|
|
267
|
+
const bitrateTable = versionBits === 3 ? bitrates[1] : bitrates[2];
|
|
268
|
+
const sampleRate = sampleRates[versionIndex]?.[sampleIndex] ?? 0;
|
|
269
|
+
const bitrateKbps = bitrateTable?.[bitrateIndex] ?? 0;
|
|
270
|
+
if (!sampleRate || !bitrateKbps) continue;
|
|
271
|
+
return {
|
|
272
|
+
format,
|
|
273
|
+
mimeType: "audio/mpeg",
|
|
274
|
+
codec: "mp3",
|
|
275
|
+
sampleRate,
|
|
276
|
+
channels: (bytes[index + 3] ?? 0) >> 6 === 3 ? 1 : 2,
|
|
277
|
+
bitrate: bitrateKbps * 1e3,
|
|
278
|
+
isCompressed: true
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
return void 0;
|
|
282
|
+
}
|
|
283
|
+
function inspectAudioSpecification(buffer, format) {
|
|
284
|
+
if (isWavFormat(format) || ascii(new Uint8Array(buffer), 0, "RIFF")) {
|
|
285
|
+
const parsed = parseWav(buffer);
|
|
286
|
+
if (parsed.format.byteLength < 16) throw new Error("Invalid WAV fmt chunk.");
|
|
287
|
+
const view = new DataView(parsed.format.buffer, parsed.format.byteOffset, parsed.format.byteLength);
|
|
288
|
+
const sampleRate = view.getUint32(4, true);
|
|
289
|
+
const channels = view.getUint16(2, true);
|
|
290
|
+
const bitsPerSample = parsed.format.byteLength >= 16 ? view.getUint16(14, true) : 0;
|
|
291
|
+
const formatCode = view.getUint16(0, true);
|
|
292
|
+
return {
|
|
293
|
+
format,
|
|
294
|
+
mimeType: "audio/wav",
|
|
295
|
+
codec: formatCode === 1 ? "pcm" : "unknown",
|
|
296
|
+
sampleRate,
|
|
297
|
+
channels,
|
|
298
|
+
...sampleRate && channels && bitsPerSample ? { bitrate: sampleRate * channels * bitsPerSample } : {},
|
|
299
|
+
isCompressed: formatCode !== 1
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
if (isMp3Format(format)) return parseMp3Specification(buffer, format) ?? formatAudioSpecification(format);
|
|
303
|
+
return formatAudioSpecification(format);
|
|
304
|
+
}
|
|
305
|
+
function validateAudioSpecifications(specs) {
|
|
306
|
+
const first = specs[0];
|
|
307
|
+
if (!first) return;
|
|
308
|
+
const mismatch = specs.find(
|
|
309
|
+
(spec) => spec.sampleRate !== first.sampleRate || spec.channels !== first.channels || first.bitrate !== void 0 && spec.bitrate !== void 0 && spec.bitrate !== first.bitrate
|
|
310
|
+
);
|
|
311
|
+
if (mismatch)
|
|
312
|
+
throw new AudioFormatMismatchError(
|
|
313
|
+
`Audio chunks have incompatible specifications: ${first.sampleRate}Hz/${first.channels}ch versus ${mismatch.sampleRate}Hz/${mismatch.channels}ch.`,
|
|
314
|
+
specs
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
function isAudioFormatMismatch(error) {
|
|
318
|
+
return error instanceof AudioFormatMismatchError || error !== null && typeof error === "object" && "kind" in error && error.kind === "audio-format-mismatch";
|
|
319
|
+
}
|
|
211
320
|
function writeUint32(target, offset, value) {
|
|
212
321
|
new DataView(target.buffer).setUint32(offset, value, true);
|
|
213
322
|
}
|
|
@@ -293,6 +402,7 @@ function mergeAudioBuffers(buffers, options) {
|
|
|
293
402
|
const format = typeof options === "string" ? options : options?.format;
|
|
294
403
|
if (!format) throw new UnsupportedMergeFormatError("");
|
|
295
404
|
try {
|
|
405
|
+
validateAudioSpecifications(buffers.map((buffer) => inspectAudioSpecification(buffer, format)));
|
|
296
406
|
if (isWavFormat(format)) return mergeWavBuffers(buffers);
|
|
297
407
|
if (isMp3Format(format)) {
|
|
298
408
|
const parts = buffers.map(stripMp3Tags);
|
|
@@ -315,7 +425,8 @@ function mergeAudioBuffers(buffers, options) {
|
|
|
315
425
|
}
|
|
316
426
|
throw new UnsupportedMergeFormatError(format);
|
|
317
427
|
} catch (error) {
|
|
318
|
-
if (error instanceof UnsupportedMergeFormatError || error instanceof MergeError)
|
|
428
|
+
if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
|
|
429
|
+
throw error;
|
|
319
430
|
throw new MergeError(`Audio buffers could not be merged for format "${format}".`, error);
|
|
320
431
|
}
|
|
321
432
|
}
|
|
@@ -393,17 +504,26 @@ async function synthesizeSsml(ssml, config) {
|
|
|
393
504
|
return {
|
|
394
505
|
originalTextRange: { ...marker.originalTextRange },
|
|
395
506
|
sourceNodePath: [...marker.sourceNodePath],
|
|
396
|
-
textRange: { ...marker.originalTextRange }
|
|
507
|
+
textRange: { ...marker.originalTextRange },
|
|
508
|
+
mappingStatus: "exact"
|
|
397
509
|
};
|
|
398
510
|
}
|
|
399
|
-
if (sourceSegments.length === 0 && !config.sourceTextRange && !config.sourceNodePath)
|
|
511
|
+
if (sourceSegments.length === 0 && !config.sourceTextRange && !config.sourceNodePath) {
|
|
512
|
+
const unmapped = { mappingStatus: "unmapped" };
|
|
513
|
+
Object.defineProperty(unmapped, "mappingStatus", { value: "unmapped", enumerable: false });
|
|
514
|
+
return unmapped;
|
|
515
|
+
}
|
|
400
516
|
const value = text ?? "";
|
|
401
517
|
let localStart = Number.isFinite(offsetHint) && (offsetHint ?? 0) >= 0 ? offsetHint : -1;
|
|
402
|
-
|
|
518
|
+
let mappingStatus = "exact";
|
|
519
|
+
if (value && localStart >= 0 && sourceText.slice(localStart, localStart + value.length) !== value) {
|
|
403
520
|
localStart = -1;
|
|
521
|
+
mappingStatus = "fallback";
|
|
522
|
+
}
|
|
404
523
|
if (localStart < 0 || localStart > sourceText.length) {
|
|
405
524
|
localStart = value ? sourceText.indexOf(value, sourceEventCursor) : sourceEventCursor;
|
|
406
525
|
if (localStart < 0) localStart = value ? sourceText.indexOf(value) : sourceEventCursor;
|
|
526
|
+
mappingStatus = "fallback";
|
|
407
527
|
}
|
|
408
528
|
localStart = Math.max(0, localStart);
|
|
409
529
|
const localEnd = Math.min(sourceText.length, localStart + value.length);
|
|
@@ -414,7 +534,8 @@ async function synthesizeSsml(ssml, config) {
|
|
|
414
534
|
return {
|
|
415
535
|
originalTextRange: { ...fallbackRange },
|
|
416
536
|
textRange: { ...fallbackRange },
|
|
417
|
-
...segment ? { sourceNodePath: [...segment.sourceNodePath] } : config.sourceNodePath ? { sourceNodePath: [...config.sourceNodePath] } : {}
|
|
537
|
+
...segment ? { sourceNodePath: [...segment.sourceNodePath] } : config.sourceNodePath ? { sourceNodePath: [...config.sourceNodePath] } : {},
|
|
538
|
+
mappingStatus: segment || config.sourceTextRange || config.sourceNodePath ? mappingStatus : "unmapped"
|
|
418
539
|
};
|
|
419
540
|
};
|
|
420
541
|
synthesizer.wordBoundary = (_sender, event) => {
|
|
@@ -463,20 +584,27 @@ async function synthesizeSsml(ssml, config) {
|
|
|
463
584
|
);
|
|
464
585
|
const durationMs = result.audioDuration ? ticksToMilliseconds(result.audioDuration) : eventDurationMs;
|
|
465
586
|
const requestId = result.resultId;
|
|
466
|
-
const addSourceMetadata = (event) =>
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
587
|
+
const addSourceMetadata = (event) => {
|
|
588
|
+
const mapped = {
|
|
589
|
+
...event,
|
|
590
|
+
...config.sourceTextRange && !("textRange" in event) ? { textRange: { ...config.sourceTextRange } } : {},
|
|
591
|
+
...config.sourceTextRange && !("originalTextRange" in event) ? { originalTextRange: { ...config.sourceTextRange } } : {},
|
|
592
|
+
...config.chunkIndex !== void 0 ? { chunkIndex: config.chunkIndex } : {},
|
|
593
|
+
...config.sourceNodePath ? { sourceNodePath: [...config.sourceNodePath] } : {},
|
|
594
|
+
...requestId ? { requestId } : {}
|
|
595
|
+
};
|
|
596
|
+
if (event.mappingStatus === "unmapped")
|
|
597
|
+
Object.defineProperty(mapped, "mappingStatus", { value: "unmapped", enumerable: false });
|
|
598
|
+
return mapped;
|
|
599
|
+
};
|
|
474
600
|
const sourceBoundaries = boundaries.map((boundary) => addSourceMetadata(boundary));
|
|
475
601
|
const sourceVisemes = visemes.map((viseme) => addSourceMetadata(viseme));
|
|
476
602
|
const sourceBookmarks = bookmarks.map((bookmark) => addSourceMetadata(bookmark));
|
|
477
603
|
resolve({
|
|
478
604
|
audioData: result.audioData,
|
|
479
605
|
durationMs,
|
|
606
|
+
audioSpec: inspectAudioSpecification(result.audioData, config.outputFormat ?? DEFAULT_OUTPUT_FORMAT),
|
|
607
|
+
mimeType: resolveMimeType(config.outputFormat ?? DEFAULT_OUTPUT_FORMAT),
|
|
480
608
|
...config.sourceTextRange ? { textRange: { ...config.sourceTextRange } } : {},
|
|
481
609
|
...requestId ? { requestId } : {},
|
|
482
610
|
...sourceBoundaries.length > 0 ? { boundaries: sourceBoundaries, wordBoundary: sourceBoundaries, wordBoundaries: sourceBoundaries } : {},
|
|
@@ -501,8 +629,66 @@ async function synthesizeSsml(ssml, config) {
|
|
|
501
629
|
}
|
|
502
630
|
});
|
|
503
631
|
}
|
|
632
|
+
function isRetryableSynthesisError(error) {
|
|
633
|
+
if (error instanceof SynthesisCancelledError || error instanceof SynthesisTimeoutError) return false;
|
|
634
|
+
if (error instanceof AzureTtsError && error.status !== 0)
|
|
635
|
+
return error.status === 429 || error.status >= 500 && error.status < 600;
|
|
636
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
637
|
+
if (/\b4\d{2}\b/.test(message)) return false;
|
|
638
|
+
const status = error && typeof error === "object" && "status" in error ? error.status : void 0;
|
|
639
|
+
if (typeof status === "number") return status === 429 || status >= 500 && status < 600;
|
|
640
|
+
return /network|connection|connect|socket|fetch failed|econn|etimedout|temporar|transient|unavailable/i.test(message);
|
|
641
|
+
}
|
|
642
|
+
function retryDelay(options, retryAttempt) {
|
|
643
|
+
const base = Math.min(options.maxDelayMs, options.initialDelayMs * 2 ** Math.max(0, retryAttempt - 1));
|
|
644
|
+
return Math.floor(Math.random() * (base + 1));
|
|
645
|
+
}
|
|
646
|
+
function resolveConcurrency(value, total) {
|
|
647
|
+
if (value === void 0) return 1;
|
|
648
|
+
if (value === Infinity) return Math.max(1, total);
|
|
649
|
+
return Number.isFinite(value) ? Math.max(1, Math.floor(value)) : 1;
|
|
650
|
+
}
|
|
651
|
+
async function waitForRetry(delayMs, signal) {
|
|
652
|
+
if (signal?.aborted) throw new SynthesisCancelledError();
|
|
653
|
+
if (delayMs <= 0) return;
|
|
654
|
+
await new Promise((resolve, reject) => {
|
|
655
|
+
let timer;
|
|
656
|
+
const abort = () => {
|
|
657
|
+
clearTimeout(timer);
|
|
658
|
+
signal?.removeEventListener("abort", abort);
|
|
659
|
+
reject(new SynthesisCancelledError());
|
|
660
|
+
};
|
|
661
|
+
timer = setTimeout(() => {
|
|
662
|
+
signal?.removeEventListener("abort", abort);
|
|
663
|
+
resolve();
|
|
664
|
+
}, delayMs);
|
|
665
|
+
if (signal) {
|
|
666
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
async function synthesizeWithRetry(ssml, config, retryOptions, onRetry) {
|
|
671
|
+
const options = retryOptions ? {
|
|
672
|
+
maxRetries: Math.max(0, Math.floor(retryOptions.maxRetries)),
|
|
673
|
+
initialDelayMs: Math.max(0, retryOptions.initialDelayMs),
|
|
674
|
+
maxDelayMs: Math.max(0, retryOptions.maxDelayMs)
|
|
675
|
+
} : void 0;
|
|
676
|
+
let attempt = 0;
|
|
677
|
+
while (true) {
|
|
678
|
+
if (config.signal?.aborted) throw new SynthesisCancelledError();
|
|
679
|
+
try {
|
|
680
|
+
return await synthesizeSsml(ssml, config);
|
|
681
|
+
} catch (error) {
|
|
682
|
+
if (!options || attempt >= options.maxRetries || !isRetryableSynthesisError(error)) throw error;
|
|
683
|
+
attempt += 1;
|
|
684
|
+
const delayMs = retryDelay(options, attempt);
|
|
685
|
+
onRetry(attempt, delayMs);
|
|
686
|
+
await waitForRetry(delayMs, config.signal);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
504
690
|
async function synthesizeSsmlChunks(chunks, config) {
|
|
505
|
-
const results =
|
|
691
|
+
const results = new Array(chunks.length);
|
|
506
692
|
const totalChunks = chunks.length;
|
|
507
693
|
const report = (event) => config.onProgress?.(event);
|
|
508
694
|
for (const [index, chunk] of chunks.entries()) {
|
|
@@ -517,57 +703,85 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
517
703
|
durationMs: 0
|
|
518
704
|
});
|
|
519
705
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
durationMs: 0
|
|
530
|
-
});
|
|
531
|
-
const startedAt = Date.now();
|
|
532
|
-
try {
|
|
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) {
|
|
706
|
+
let completed = 0;
|
|
707
|
+
let nextIndex = 0;
|
|
708
|
+
const concurrency = resolveConcurrency(config.concurrency, chunks.length);
|
|
709
|
+
const worker = async () => {
|
|
710
|
+
while (true) {
|
|
711
|
+
const index = nextIndex++;
|
|
712
|
+
if (index >= chunks.length) return;
|
|
713
|
+
const chunk = chunks[index];
|
|
714
|
+
const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
|
|
553
715
|
report({
|
|
554
|
-
currentChunk:
|
|
716
|
+
currentChunk: completed,
|
|
555
717
|
totalChunks,
|
|
556
|
-
percent: totalChunks === 0 ? 100 : Math.round(
|
|
718
|
+
percent: totalChunks === 0 ? 100 : Math.round(completed / totalChunks * 100),
|
|
557
719
|
chunkIndex: index,
|
|
558
720
|
originalTextRange: input.originalTextRange,
|
|
559
|
-
status: "
|
|
560
|
-
durationMs:
|
|
561
|
-
error
|
|
721
|
+
status: "synthesizing",
|
|
722
|
+
durationMs: 0
|
|
562
723
|
});
|
|
563
|
-
|
|
724
|
+
const startedAt = Date.now();
|
|
725
|
+
try {
|
|
726
|
+
const result = await synthesizeWithRetry(
|
|
727
|
+
input.ssml,
|
|
728
|
+
{
|
|
729
|
+
...config,
|
|
730
|
+
...input.originalTextRange ? { sourceTextRange: input.originalTextRange } : {},
|
|
731
|
+
...input.sourceNodePath ?? config.sourceNodePath ? { sourceNodePath: [...input.sourceNodePath ?? config.sourceNodePath ?? []] } : {},
|
|
732
|
+
...input.sourceTextSegments ? { sourceTextSegments: input.sourceTextSegments } : {},
|
|
733
|
+
...input.sourceMarkers ? { sourceMarkers: input.sourceMarkers } : {},
|
|
734
|
+
chunkIndex: index,
|
|
735
|
+
onProgress: void 0
|
|
736
|
+
},
|
|
737
|
+
config.retryOptions,
|
|
738
|
+
(retryAttempt, nextRetryDelayMs) => report({
|
|
739
|
+
currentChunk: completed,
|
|
740
|
+
totalChunks,
|
|
741
|
+
percent: totalChunks === 0 ? 100 : Math.round(completed / totalChunks * 100),
|
|
742
|
+
chunkIndex: index,
|
|
743
|
+
originalTextRange: input.originalTextRange,
|
|
744
|
+
status: "synthesizing",
|
|
745
|
+
durationMs: Date.now() - startedAt,
|
|
746
|
+
retryAttempt,
|
|
747
|
+
nextRetryDelayMs,
|
|
748
|
+
isRetrying: true
|
|
749
|
+
})
|
|
750
|
+
);
|
|
751
|
+
results[index] = result;
|
|
752
|
+
completed += 1;
|
|
753
|
+
report({
|
|
754
|
+
currentChunk: completed,
|
|
755
|
+
totalChunks,
|
|
756
|
+
percent: totalChunks === 0 ? 100 : Math.round(completed / totalChunks * 100),
|
|
757
|
+
chunkIndex: index,
|
|
758
|
+
originalTextRange: input.originalTextRange,
|
|
759
|
+
status: "success",
|
|
760
|
+
durationMs: Date.now() - startedAt
|
|
761
|
+
});
|
|
762
|
+
} catch (error) {
|
|
763
|
+
report({
|
|
764
|
+
currentChunk: completed,
|
|
765
|
+
totalChunks,
|
|
766
|
+
percent: totalChunks === 0 ? 100 : Math.round(completed / totalChunks * 100),
|
|
767
|
+
chunkIndex: index,
|
|
768
|
+
originalTextRange: input.originalTextRange,
|
|
769
|
+
status: "failed",
|
|
770
|
+
durationMs: Date.now() - startedAt,
|
|
771
|
+
error
|
|
772
|
+
});
|
|
773
|
+
throw error;
|
|
774
|
+
}
|
|
564
775
|
}
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
|
|
776
|
+
};
|
|
777
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, chunks.length) }, () => worker()));
|
|
778
|
+
const orderedResults = results.filter((result) => result !== void 0);
|
|
779
|
+
return mergeSynthesisResults(orderedResults, {
|
|
780
|
+
format: config.outputFormat ?? DEFAULT_OUTPUT_FORMAT,
|
|
781
|
+
signal: config.signal
|
|
568
782
|
});
|
|
569
783
|
}
|
|
570
|
-
function createMergedResult(results, audioData, format) {
|
|
784
|
+
function createMergedResult(results, audioData, format, audioSpec, outputMimeType) {
|
|
571
785
|
const boundaries = [];
|
|
572
786
|
const visemes = [];
|
|
573
787
|
const bookmarks = [];
|
|
@@ -586,7 +800,8 @@ function createMergedResult(results, audioData, format) {
|
|
|
586
800
|
...boundary.sourceNodePath ? { sourceNodePath: [...boundary.sourceNodePath] } : {},
|
|
587
801
|
...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
|
|
588
802
|
...textRange ? { textRange: { ...textRange } } : {},
|
|
589
|
-
...requestId ? { requestId } : {}
|
|
803
|
+
...requestId ? { requestId } : {},
|
|
804
|
+
mappingStatus: boundary.mappingStatus ?? "unmapped"
|
|
590
805
|
});
|
|
591
806
|
}
|
|
592
807
|
for (const viseme of result.visemes ?? []) {
|
|
@@ -601,7 +816,8 @@ function createMergedResult(results, audioData, format) {
|
|
|
601
816
|
...viseme.sourceNodePath ? { sourceNodePath: [...viseme.sourceNodePath] } : {},
|
|
602
817
|
...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
|
|
603
818
|
...textRange ? { textRange: { ...textRange } } : {},
|
|
604
|
-
...requestId ? { requestId } : {}
|
|
819
|
+
...requestId ? { requestId } : {},
|
|
820
|
+
mappingStatus: viseme.mappingStatus ?? "unmapped"
|
|
605
821
|
});
|
|
606
822
|
}
|
|
607
823
|
for (const bookmark of result.bookmarks ?? []) {
|
|
@@ -616,7 +832,8 @@ function createMergedResult(results, audioData, format) {
|
|
|
616
832
|
...bookmark.sourceNodePath ? { sourceNodePath: [...bookmark.sourceNodePath] } : {},
|
|
617
833
|
...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
|
|
618
834
|
...textRange ? { textRange: { ...textRange } } : {},
|
|
619
|
-
...requestId ? { requestId } : {}
|
|
835
|
+
...requestId ? { requestId } : {},
|
|
836
|
+
mappingStatus: bookmark.mappingStatus ?? "unmapped"
|
|
620
837
|
});
|
|
621
838
|
}
|
|
622
839
|
durationOffset += Math.max(0, result.durationMs);
|
|
@@ -625,6 +842,8 @@ function createMergedResult(results, audioData, format) {
|
|
|
625
842
|
audioData,
|
|
626
843
|
durationMs: durationOffset,
|
|
627
844
|
mimeType: resolveMimeType(format),
|
|
845
|
+
audioSpec: audioSpec ?? formatAudioSpecification(format),
|
|
846
|
+
...outputMimeType ? { mimeType: outputMimeType } : {},
|
|
628
847
|
...boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {},
|
|
629
848
|
...visemes.length > 0 ? { visemes } : {},
|
|
630
849
|
...bookmarks.length > 0 ? { bookmarks } : {},
|
|
@@ -637,19 +856,47 @@ function mergeSynthesisResults(results, options) {
|
|
|
637
856
|
const format = resolvedOptions?.format;
|
|
638
857
|
if (!format) throw new UnsupportedMergeFormatError("");
|
|
639
858
|
const buffers = results.map((result) => result.audioData);
|
|
859
|
+
const inputSpecs = results.map((result) => result.audioSpec ?? inspectAudioSpecification(result.audioData, format));
|
|
860
|
+
validateAudioSpecifications(inputSpecs);
|
|
861
|
+
const signal = resolvedOptions.signal ?? new AbortController().signal;
|
|
862
|
+
if (signal.aborted) throw new SynthesisCancelledError();
|
|
640
863
|
if (resolvedOptions.customMerger) {
|
|
641
|
-
return Promise.resolve().then(
|
|
864
|
+
return Promise.resolve().then(
|
|
865
|
+
() => resolvedOptions.customMerger?.(buffers, {
|
|
866
|
+
format,
|
|
867
|
+
outputMimeType: resolvedOptions.outputMimeType ?? resolveMimeType(format),
|
|
868
|
+
inputSpecs,
|
|
869
|
+
signal
|
|
870
|
+
})
|
|
871
|
+
).then((merged) => {
|
|
642
872
|
if (!merged) throw new MergeError("The custom audio merger returned no audio buffer.");
|
|
643
|
-
|
|
873
|
+
if (!(merged instanceof ArrayBuffer) || buffers.some((buffer) => buffer.byteLength > 0) && merged.byteLength === 0)
|
|
874
|
+
throw new MergeError("The custom audio merger returned an invalid audio buffer.");
|
|
875
|
+
if (signal.aborted) throw new SynthesisCancelledError();
|
|
876
|
+
return createMergedResult(
|
|
877
|
+
results,
|
|
878
|
+
merged,
|
|
879
|
+
format,
|
|
880
|
+
inspectAudioSpecification(merged, format),
|
|
881
|
+
resolvedOptions.outputMimeType
|
|
882
|
+
);
|
|
644
883
|
}).catch((error) => {
|
|
645
|
-
if (error instanceof UnsupportedMergeFormatError || error instanceof MergeError)
|
|
884
|
+
if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
|
|
885
|
+
throw error;
|
|
646
886
|
throw new MergeError(`Custom audio merger failed for format "${format}".`, error);
|
|
647
887
|
});
|
|
648
888
|
}
|
|
649
889
|
try {
|
|
650
|
-
return createMergedResult(
|
|
890
|
+
return createMergedResult(
|
|
891
|
+
results,
|
|
892
|
+
mergeAudioBuffers(buffers, { format }),
|
|
893
|
+
format,
|
|
894
|
+
inputSpecs[0],
|
|
895
|
+
resolvedOptions.outputMimeType
|
|
896
|
+
);
|
|
651
897
|
} catch (error) {
|
|
652
|
-
if (error instanceof UnsupportedMergeFormatError || error instanceof MergeError)
|
|
898
|
+
if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
|
|
899
|
+
throw error;
|
|
653
900
|
throw new MergeError(`Audio buffers could not be merged for format "${format}".`, error);
|
|
654
901
|
}
|
|
655
902
|
}
|
|
@@ -670,8 +917,73 @@ var ChunkValidationError = class extends Error {
|
|
|
670
917
|
function failure(error) {
|
|
671
918
|
return { ok: false, success: false, status: error.kind, error };
|
|
672
919
|
}
|
|
920
|
+
function isRetryable(error) {
|
|
921
|
+
if (error instanceof Error && /cancel|abort|tim(?:e|ed) ?out/i.test(error.message)) return false;
|
|
922
|
+
const status = error && typeof error === "object" && "status" in error ? error.status : void 0;
|
|
923
|
+
if (typeof status === "number" && status !== 0) return status === 429 || status >= 500 && status < 600;
|
|
924
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
925
|
+
if (/\b4\d{2}\b/.test(message)) return false;
|
|
926
|
+
return /network|connection|connect|socket|fetch failed|econn|etimedout|temporar|transient|unavailable/i.test(message);
|
|
927
|
+
}
|
|
928
|
+
function delayForRetry(options, attempt) {
|
|
929
|
+
const maxDelay = Math.max(0, options.maxDelayMs);
|
|
930
|
+
const base = Math.min(maxDelay, Math.max(0, options.initialDelayMs) * 2 ** Math.max(0, attempt - 1));
|
|
931
|
+
return Math.floor(Math.random() * (base + 1));
|
|
932
|
+
}
|
|
933
|
+
function resolveConcurrency2(value, total) {
|
|
934
|
+
if (value === void 0) return 1;
|
|
935
|
+
if (value === Infinity) return Math.max(1, total);
|
|
936
|
+
return Number.isFinite(value) ? Math.max(1, Math.floor(value)) : 1;
|
|
937
|
+
}
|
|
938
|
+
async function retryableSynthesis(synthesize, options, signal, onRetry) {
|
|
939
|
+
const retry = options ? {
|
|
940
|
+
maxRetries: Math.max(0, Math.floor(options.maxRetries)),
|
|
941
|
+
initialDelayMs: options.initialDelayMs,
|
|
942
|
+
maxDelayMs: options.maxDelayMs
|
|
943
|
+
} : void 0;
|
|
944
|
+
let attempt = 0;
|
|
945
|
+
while (true) {
|
|
946
|
+
if (signal?.aborted) throw new Error("Speech synthesis was cancelled.");
|
|
947
|
+
try {
|
|
948
|
+
return await synthesize();
|
|
949
|
+
} catch (error) {
|
|
950
|
+
if (!retry || attempt >= retry.maxRetries || !isRetryable(error)) throw error;
|
|
951
|
+
attempt += 1;
|
|
952
|
+
const delayMs = delayForRetry(retry, attempt);
|
|
953
|
+
onRetry(attempt, delayMs);
|
|
954
|
+
if (delayMs > 0)
|
|
955
|
+
await new Promise((resolve, reject) => {
|
|
956
|
+
const timer = setTimeout(() => {
|
|
957
|
+
signal?.removeEventListener("abort", abort);
|
|
958
|
+
resolve();
|
|
959
|
+
}, delayMs);
|
|
960
|
+
const abort = () => {
|
|
961
|
+
clearTimeout(timer);
|
|
962
|
+
signal?.removeEventListener("abort", abort);
|
|
963
|
+
reject(new Error("Speech synthesis was cancelled."));
|
|
964
|
+
};
|
|
965
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
function sharedValidationOptions(options, signal) {
|
|
971
|
+
const validator = options.urlValidator ?? options.customUrlValidator;
|
|
972
|
+
if (!validator) return signal ? withValidationSignal(options, signal) : options;
|
|
973
|
+
const runner = createAzureUrlValidatorRunner2(validator, {
|
|
974
|
+
...options.urlValidation ?? {},
|
|
975
|
+
...options.urlValidatorConcurrency !== void 0 ? { concurrency: options.urlValidatorConcurrency } : {},
|
|
976
|
+
...options.urlValidatorTimeoutMs !== void 0 ? { timeoutMs: options.urlValidatorTimeoutMs } : {},
|
|
977
|
+
...signal ? { signal } : {},
|
|
978
|
+
...options.urlValidatorCache ? { cache: options.urlValidatorCache } : {}
|
|
979
|
+
});
|
|
980
|
+
return {
|
|
981
|
+
...withValidationSignal(options, signal),
|
|
982
|
+
urlValidatorRunner: runner
|
|
983
|
+
};
|
|
984
|
+
}
|
|
673
985
|
async function synthesizeSsmlSafe(client, ssml, options = {}) {
|
|
674
|
-
const validationOptions =
|
|
986
|
+
const validationOptions = sharedValidationOptions(options.validation ?? options, options.signal);
|
|
675
987
|
const diagnostics = await Promise.resolve(validateAzureSsml2(ssml, validationOptions));
|
|
676
988
|
if (options.signal?.aborted) {
|
|
677
989
|
const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
|
|
@@ -698,7 +1010,7 @@ async function synthesizeSsmlSafe(client, ssml, options = {}) {
|
|
|
698
1010
|
}
|
|
699
1011
|
}
|
|
700
1012
|
async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
701
|
-
const validationOptions =
|
|
1013
|
+
const validationOptions = sharedValidationOptions(options.validation ?? options, options.signal);
|
|
702
1014
|
if (options.signal?.aborted) {
|
|
703
1015
|
const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
|
|
704
1016
|
return failure(error);
|
|
@@ -719,16 +1031,24 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
719
1031
|
pending(index, "pending");
|
|
720
1032
|
});
|
|
721
1033
|
const validations = await Promise.all(
|
|
722
|
-
chunks.map(async (chunk) => {
|
|
1034
|
+
chunks.map(async (chunk, index) => {
|
|
723
1035
|
const ssml = typeof chunk === "string" ? chunk : chunk.ssml;
|
|
724
1036
|
const sourceNodePath = typeof chunk === "string" ? options.sourceNodePath : chunk.sourceNodePath ?? options.sourceNodePath;
|
|
725
1037
|
const diagnostics = await Promise.resolve(
|
|
726
|
-
validateAzureSsml2(ssml, {
|
|
1038
|
+
validateAzureSsml2(ssml, {
|
|
1039
|
+
...validationOptions,
|
|
1040
|
+
...sourceNodePath ? { sourceNodePath } : {},
|
|
1041
|
+
chunkIndex: index
|
|
1042
|
+
})
|
|
727
1043
|
);
|
|
728
1044
|
return diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
729
1045
|
})
|
|
730
1046
|
);
|
|
731
1047
|
const firstInvalidIndex = validations.findIndex((diagnostics) => diagnostics.length > 0);
|
|
1048
|
+
if (options.signal?.aborted) {
|
|
1049
|
+
const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
|
|
1050
|
+
return failure(error);
|
|
1051
|
+
}
|
|
732
1052
|
if (firstInvalidIndex >= 0) {
|
|
733
1053
|
const error = new ChunkValidationError(firstInvalidIndex, validations[firstInvalidIndex] ?? []);
|
|
734
1054
|
pending(firstInvalidIndex, "failed", error);
|
|
@@ -745,96 +1065,126 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
745
1065
|
outputFormat: options.outputFormat,
|
|
746
1066
|
signal: options.signal,
|
|
747
1067
|
timeoutMs: options.timeoutMs,
|
|
748
|
-
sourceNodePath: options.sourceNodePath
|
|
1068
|
+
sourceNodePath: options.sourceNodePath,
|
|
1069
|
+
concurrency: options.concurrency,
|
|
1070
|
+
retryOptions: options.retryOptions
|
|
749
1071
|
});
|
|
750
1072
|
return { ok: true, success: true, status: "success", value };
|
|
751
1073
|
}
|
|
752
|
-
const results =
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
sourceNodePath:
|
|
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
|
-
|
|
1074
|
+
const results = new Array(chunks.length);
|
|
1075
|
+
let completed = 0;
|
|
1076
|
+
let nextIndex = 0;
|
|
1077
|
+
const concurrency = resolveConcurrency2(options.concurrency, chunks.length);
|
|
1078
|
+
const worker = async () => {
|
|
1079
|
+
while (true) {
|
|
1080
|
+
const index = nextIndex++;
|
|
1081
|
+
if (index >= chunks.length) return;
|
|
1082
|
+
const chunk = chunks[index];
|
|
1083
|
+
const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
|
|
1084
|
+
const sourceNodePath = input.sourceNodePath;
|
|
1085
|
+
const originalTextRange = input.originalTextRange;
|
|
1086
|
+
pending(index, "synthesizing");
|
|
1087
|
+
const startedAt = Date.now();
|
|
1088
|
+
try {
|
|
1089
|
+
const result = await retryableSynthesis(
|
|
1090
|
+
() => client.synthesizeSsml(input.ssml, {
|
|
1091
|
+
outputFormat: options.outputFormat,
|
|
1092
|
+
signal: options.signal,
|
|
1093
|
+
timeoutMs: options.timeoutMs,
|
|
1094
|
+
sourceNodePath: input.sourceNodePath ?? options.sourceNodePath
|
|
1095
|
+
}),
|
|
1096
|
+
options.retryOptions,
|
|
1097
|
+
options.signal,
|
|
1098
|
+
(retryAttempt, nextRetryDelayMs) => options.onProgress?.({
|
|
1099
|
+
currentChunk: completed,
|
|
1100
|
+
totalChunks: chunks.length,
|
|
1101
|
+
percent: chunks.length === 0 ? 100 : Math.round(completed / chunks.length * 100),
|
|
1102
|
+
chunkIndex: index,
|
|
1103
|
+
originalTextRange: input.originalTextRange,
|
|
1104
|
+
status: "synthesizing",
|
|
1105
|
+
durationMs: Date.now() - startedAt,
|
|
1106
|
+
retryAttempt,
|
|
1107
|
+
nextRetryDelayMs,
|
|
1108
|
+
isRetrying: true
|
|
1109
|
+
})
|
|
1110
|
+
);
|
|
1111
|
+
results[index] = {
|
|
1112
|
+
...result,
|
|
1113
|
+
...input.originalTextRange ? { textRange: { ...input.originalTextRange } } : {},
|
|
1114
|
+
...sourceNodePath ? {
|
|
1115
|
+
boundaries: result.boundaries?.map((event) => ({
|
|
1116
|
+
...event,
|
|
1117
|
+
sourceNodePath: [...sourceNodePath],
|
|
1118
|
+
...event.originalTextRange ? { originalTextRange: { ...event.originalTextRange } } : input.originalTextRange ? { originalTextRange: { ...input.originalTextRange } } : {}
|
|
1119
|
+
})),
|
|
1120
|
+
visemes: result.visemes?.map((event) => ({
|
|
1121
|
+
...event,
|
|
1122
|
+
sourceNodePath: [...sourceNodePath],
|
|
1123
|
+
...event.originalTextRange ? { originalTextRange: { ...event.originalTextRange } } : input.originalTextRange ? { originalTextRange: { ...input.originalTextRange } } : {}
|
|
1124
|
+
})),
|
|
1125
|
+
bookmarks: result.bookmarks?.map((event) => ({
|
|
1126
|
+
...event,
|
|
1127
|
+
sourceNodePath: [...sourceNodePath],
|
|
1128
|
+
...event.originalTextRange ? { originalTextRange: { ...event.originalTextRange } } : input.originalTextRange ? { originalTextRange: { ...input.originalTextRange } } : {}
|
|
1129
|
+
}))
|
|
1130
|
+
} : {},
|
|
1131
|
+
...originalTextRange ? {
|
|
1132
|
+
boundaries: result.boundaries?.map((event) => ({
|
|
1133
|
+
...event,
|
|
1134
|
+
originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
|
|
1135
|
+
})),
|
|
1136
|
+
wordBoundary: result.wordBoundary?.map((event) => ({
|
|
1137
|
+
...event,
|
|
1138
|
+
originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
|
|
1139
|
+
})),
|
|
1140
|
+
wordBoundaries: result.wordBoundaries?.map((event) => ({
|
|
1141
|
+
...event,
|
|
1142
|
+
originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
|
|
1143
|
+
})),
|
|
1144
|
+
visemes: result.visemes?.map((event) => ({
|
|
1145
|
+
...event,
|
|
1146
|
+
originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
|
|
1147
|
+
})),
|
|
1148
|
+
bookmarks: result.bookmarks?.map((event) => ({
|
|
1149
|
+
...event,
|
|
1150
|
+
originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
|
|
1151
|
+
}))
|
|
1152
|
+
} : {}
|
|
1153
|
+
};
|
|
1154
|
+
completed += 1;
|
|
1155
|
+
options.onProgress?.({
|
|
1156
|
+
currentChunk: completed,
|
|
1157
|
+
totalChunks: chunks.length,
|
|
1158
|
+
percent: chunks.length === 0 ? 100 : Math.round(completed / chunks.length * 100),
|
|
1159
|
+
chunkIndex: index,
|
|
1160
|
+
originalTextRange: input.originalTextRange,
|
|
1161
|
+
status: "success",
|
|
1162
|
+
durationMs: Date.now() - startedAt
|
|
1163
|
+
});
|
|
1164
|
+
} catch (error) {
|
|
1165
|
+
options.onProgress?.({
|
|
1166
|
+
currentChunk: completed,
|
|
1167
|
+
totalChunks: chunks.length,
|
|
1168
|
+
percent: chunks.length === 0 ? 100 : Math.round(index / chunks.length * 100),
|
|
1169
|
+
chunkIndex: index,
|
|
1170
|
+
originalTextRange: input.originalTextRange,
|
|
1171
|
+
status: "failed",
|
|
1172
|
+
durationMs: Date.now() - startedAt,
|
|
1173
|
+
error
|
|
1174
|
+
});
|
|
1175
|
+
throw error;
|
|
1176
|
+
}
|
|
830
1177
|
}
|
|
831
|
-
}
|
|
1178
|
+
};
|
|
1179
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, chunks.length) }, () => worker()));
|
|
1180
|
+
const orderedResults = results.filter((result) => result !== void 0);
|
|
832
1181
|
return {
|
|
833
1182
|
ok: true,
|
|
834
1183
|
success: true,
|
|
835
1184
|
status: "success",
|
|
836
|
-
value: mergeSynthesisResults(
|
|
837
|
-
format: options.outputFormat ?? "audio-16khz-128kbitrate-mono-mp3"
|
|
1185
|
+
value: mergeSynthesisResults(orderedResults, {
|
|
1186
|
+
format: options.outputFormat ?? "audio-16khz-128kbitrate-mono-mp3",
|
|
1187
|
+
signal: options.signal
|
|
838
1188
|
})
|
|
839
1189
|
};
|
|
840
1190
|
} catch (error) {
|
|
@@ -893,7 +1243,9 @@ var AzureTtsClient = class {
|
|
|
893
1243
|
signal: options.signal ?? signal,
|
|
894
1244
|
timeoutMs: options.timeoutMs ?? timeoutMs,
|
|
895
1245
|
sourceNodePath: options.sourceNodePath,
|
|
896
|
-
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress
|
|
1246
|
+
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress,
|
|
1247
|
+
concurrency: options.concurrency ?? __privateGet(this, _options).concurrency,
|
|
1248
|
+
retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions
|
|
897
1249
|
});
|
|
898
1250
|
}
|
|
899
1251
|
async synthesizeSsmlSafe(ssml, options = {}) {
|
|
@@ -905,7 +1257,9 @@ var AzureTtsClient = class {
|
|
|
905
1257
|
outputFormat: options.outputFormat ?? __privateGet(this, _options).outputFormat,
|
|
906
1258
|
signal: options.signal ?? __privateGet(this, _options).signal,
|
|
907
1259
|
timeoutMs: options.timeoutMs ?? __privateGet(this, _options).timeoutMs,
|
|
908
|
-
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress
|
|
1260
|
+
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress,
|
|
1261
|
+
concurrency: options.concurrency ?? __privateGet(this, _options).concurrency,
|
|
1262
|
+
retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions
|
|
909
1263
|
});
|
|
910
1264
|
}
|
|
911
1265
|
async synthesizeSsmlChunksSafe(chunks, options = {}) {
|
|
@@ -1001,6 +1355,7 @@ async function fetchAzureVoiceCatalog(options) {
|
|
|
1001
1355
|
};
|
|
1002
1356
|
}
|
|
1003
1357
|
export {
|
|
1358
|
+
AudioFormatMismatchError,
|
|
1004
1359
|
AzureTtsClient,
|
|
1005
1360
|
AzureTtsError,
|
|
1006
1361
|
AzureTtsSdkError,
|
|
@@ -1022,6 +1377,7 @@ export {
|
|
|
1022
1377
|
getAzureVoiceCatalogMetadata,
|
|
1023
1378
|
getBuiltInVoiceCatalogMetadata,
|
|
1024
1379
|
getSsmlSourceMap,
|
|
1380
|
+
inspectAudioSpecification,
|
|
1025
1381
|
isValidAzureAudioDuration,
|
|
1026
1382
|
mapSsmlTextNodes,
|
|
1027
1383
|
mergeAudioBuffers,
|
|
@@ -1037,6 +1393,7 @@ export {
|
|
|
1037
1393
|
synthesizeSsmlChunksSafe,
|
|
1038
1394
|
synthesizeSsmlSafe,
|
|
1039
1395
|
validateAzureSsml,
|
|
1396
|
+
validateAzureSsmlChunks,
|
|
1040
1397
|
validateSsml,
|
|
1041
1398
|
validateSsmlStructureIntegrity
|
|
1042
1399
|
};
|