@ssml-builder-js/azure-tts-client 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/CHANGELOG.md +11 -0
- package/dist/index.d.mts +52 -5
- package/dist/index.d.ts +52 -5
- package/dist/index.js +510 -154
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +512 -155
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/client.ts +4 -0
- package/src/errors.ts +23 -1
- package/src/index.ts +12 -1
- package/src/safe.ts +238 -111
- package/src/synthesis.ts +361 -66
- package/src/types.ts +31 -0
- package/test/v216-pipeline.test.ts +110 -0
package/dist/index.js
CHANGED
|
@@ -37,6 +37,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
37
37
|
// src/index.ts
|
|
38
38
|
var index_exports = {};
|
|
39
39
|
__export(index_exports, {
|
|
40
|
+
AudioFormatMismatchError: () => AudioFormatMismatchError,
|
|
40
41
|
AzureTtsClient: () => AzureTtsClient,
|
|
41
42
|
AzureTtsError: () => AzureTtsError,
|
|
42
43
|
AzureTtsSdkError: () => AzureTtsSdkError,
|
|
@@ -48,6 +49,7 @@ __export(index_exports, {
|
|
|
48
49
|
UnsupportedMergeFormatError: () => UnsupportedMergeFormatError,
|
|
49
50
|
canMergeAudioFormat: () => canMergeAudioFormat,
|
|
50
51
|
fetchAzureVoiceCatalog: () => fetchAzureVoiceCatalog,
|
|
52
|
+
inspectAudioSpecification: () => inspectAudioSpecification,
|
|
51
53
|
mergeAudioBuffers: () => mergeAudioBuffers,
|
|
52
54
|
mergeSynthesisResults: () => mergeSynthesisResults,
|
|
53
55
|
resolveMergeAudioFormat: () => resolveMergeAudioFormat,
|
|
@@ -102,6 +104,14 @@ var MergeError = class extends Error {
|
|
|
102
104
|
this.cause = cause;
|
|
103
105
|
}
|
|
104
106
|
};
|
|
107
|
+
var AudioFormatMismatchError = class extends Error {
|
|
108
|
+
constructor(message, inputSpecs = []) {
|
|
109
|
+
super(message);
|
|
110
|
+
this.kind = "audio-format-mismatch";
|
|
111
|
+
this.name = "AudioFormatMismatchError";
|
|
112
|
+
this.inputSpecs = inputSpecs;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
105
115
|
var UnsupportedMergeFormatError = class extends Error {
|
|
106
116
|
constructor(format) {
|
|
107
117
|
super(`Audio format "${format}" cannot be safely concatenated; container re-multiplexing is required.`);
|
|
@@ -111,7 +121,7 @@ var UnsupportedMergeFormatError = class extends Error {
|
|
|
111
121
|
}
|
|
112
122
|
};
|
|
113
123
|
function toSynthesisError(error) {
|
|
114
|
-
if (error instanceof AzureTtsError || error instanceof MergeError || error instanceof UnsupportedMergeFormatError || error instanceof SynthesisCancelledError || error instanceof SynthesisTimeoutError)
|
|
124
|
+
if (error instanceof AzureTtsError || error instanceof MergeError || error instanceof AudioFormatMismatchError || error instanceof UnsupportedMergeFormatError || error instanceof SynthesisCancelledError || error instanceof SynthesisTimeoutError)
|
|
115
125
|
return error;
|
|
116
126
|
const message = error instanceof Error ? error.message : String(error);
|
|
117
127
|
if (/cancel|abort/i.test(message)) return new SynthesisCancelledError(message);
|
|
@@ -241,6 +251,105 @@ function parseWav(buffer) {
|
|
|
241
251
|
}
|
|
242
252
|
return { chunks, data, format };
|
|
243
253
|
}
|
|
254
|
+
function formatNumber(format, pattern, fallback) {
|
|
255
|
+
const match = pattern.exec(format);
|
|
256
|
+
return match?.[1] ? Number(match[1]) : fallback;
|
|
257
|
+
}
|
|
258
|
+
function formatChannels(format, fallback) {
|
|
259
|
+
if (/stereo|2ch|dual/i.test(format)) return 2;
|
|
260
|
+
if (/mono|1ch/i.test(format)) return 1;
|
|
261
|
+
return fallback;
|
|
262
|
+
}
|
|
263
|
+
function formatAudioSpecification(format) {
|
|
264
|
+
const sampleRate = formatNumber(format, /(\d+)(?:khz|kHz|hz|Hz)/, 0);
|
|
265
|
+
const channels = formatChannels(format, 0);
|
|
266
|
+
const bitrateMatch = /(\d+)(?:kbitrate|kbps|kbits?)/i.exec(format);
|
|
267
|
+
const bitrate = bitrateMatch?.[1] ? Number(bitrateMatch[1]) * 1e3 : void 0;
|
|
268
|
+
const codec = /mp3|mpeg/i.test(format) ? "mp3" : /opus/i.test(format) ? "opus" : /silk/i.test(format) ? "silk" : /pcm|mulaw|alaw|siren/i.test(format) ? "pcm" : "unknown";
|
|
269
|
+
return {
|
|
270
|
+
format,
|
|
271
|
+
mimeType: resolveMimeType(format),
|
|
272
|
+
codec,
|
|
273
|
+
sampleRate,
|
|
274
|
+
channels,
|
|
275
|
+
...bitrate ? { bitrate } : {},
|
|
276
|
+
isCompressed: codec === "mp3" || codec === "opus" || codec === "silk"
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
function parseMp3Specification(buffer, format) {
|
|
280
|
+
const bytes = stripMp3Tags(buffer);
|
|
281
|
+
const bitrates = [
|
|
282
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
283
|
+
[0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0],
|
|
284
|
+
[0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0]
|
|
285
|
+
];
|
|
286
|
+
const sampleRates = [
|
|
287
|
+
[44100, 48e3, 32e3],
|
|
288
|
+
[22050, 24e3, 16e3],
|
|
289
|
+
[11025, 12e3, 8e3]
|
|
290
|
+
];
|
|
291
|
+
for (let index = 0; index + 4 <= bytes.length; index += 1) {
|
|
292
|
+
if (bytes[index] !== 255 || (bytes[index + 1] ?? 0) < 224) continue;
|
|
293
|
+
const header = bytes[index + 1] ?? 0;
|
|
294
|
+
const versionBits = header >> 3 & 3;
|
|
295
|
+
const layer = header >> 1 & 3;
|
|
296
|
+
const bitrateIndex = (bytes[index + 2] ?? 0) >> 4;
|
|
297
|
+
const sampleIndex = (bytes[index + 2] ?? 0) >> 2 & 3;
|
|
298
|
+
if (versionBits === 1 || layer !== 1 || bitrateIndex === 0 || bitrateIndex === 15 || sampleIndex === 3) continue;
|
|
299
|
+
const versionIndex = versionBits === 3 ? 0 : versionBits === 2 ? 1 : 2;
|
|
300
|
+
const bitrateTable = versionBits === 3 ? bitrates[1] : bitrates[2];
|
|
301
|
+
const sampleRate = sampleRates[versionIndex]?.[sampleIndex] ?? 0;
|
|
302
|
+
const bitrateKbps = bitrateTable?.[bitrateIndex] ?? 0;
|
|
303
|
+
if (!sampleRate || !bitrateKbps) continue;
|
|
304
|
+
return {
|
|
305
|
+
format,
|
|
306
|
+
mimeType: "audio/mpeg",
|
|
307
|
+
codec: "mp3",
|
|
308
|
+
sampleRate,
|
|
309
|
+
channels: (bytes[index + 3] ?? 0) >> 6 === 3 ? 1 : 2,
|
|
310
|
+
bitrate: bitrateKbps * 1e3,
|
|
311
|
+
isCompressed: true
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
return void 0;
|
|
315
|
+
}
|
|
316
|
+
function inspectAudioSpecification(buffer, format) {
|
|
317
|
+
if (isWavFormat(format) || ascii(new Uint8Array(buffer), 0, "RIFF")) {
|
|
318
|
+
const parsed = parseWav(buffer);
|
|
319
|
+
if (parsed.format.byteLength < 16) throw new Error("Invalid WAV fmt chunk.");
|
|
320
|
+
const view = new DataView(parsed.format.buffer, parsed.format.byteOffset, parsed.format.byteLength);
|
|
321
|
+
const sampleRate = view.getUint32(4, true);
|
|
322
|
+
const channels = view.getUint16(2, true);
|
|
323
|
+
const bitsPerSample = parsed.format.byteLength >= 16 ? view.getUint16(14, true) : 0;
|
|
324
|
+
const formatCode = view.getUint16(0, true);
|
|
325
|
+
return {
|
|
326
|
+
format,
|
|
327
|
+
mimeType: "audio/wav",
|
|
328
|
+
codec: formatCode === 1 ? "pcm" : "unknown",
|
|
329
|
+
sampleRate,
|
|
330
|
+
channels,
|
|
331
|
+
...sampleRate && channels && bitsPerSample ? { bitrate: sampleRate * channels * bitsPerSample } : {},
|
|
332
|
+
isCompressed: formatCode !== 1
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
if (isMp3Format(format)) return parseMp3Specification(buffer, format) ?? formatAudioSpecification(format);
|
|
336
|
+
return formatAudioSpecification(format);
|
|
337
|
+
}
|
|
338
|
+
function validateAudioSpecifications(specs) {
|
|
339
|
+
const first = specs[0];
|
|
340
|
+
if (!first) return;
|
|
341
|
+
const mismatch = specs.find(
|
|
342
|
+
(spec) => spec.sampleRate !== first.sampleRate || spec.channels !== first.channels || first.bitrate !== void 0 && spec.bitrate !== void 0 && spec.bitrate !== first.bitrate
|
|
343
|
+
);
|
|
344
|
+
if (mismatch)
|
|
345
|
+
throw new AudioFormatMismatchError(
|
|
346
|
+
`Audio chunks have incompatible specifications: ${first.sampleRate}Hz/${first.channels}ch versus ${mismatch.sampleRate}Hz/${mismatch.channels}ch.`,
|
|
347
|
+
specs
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
function isAudioFormatMismatch(error) {
|
|
351
|
+
return error instanceof AudioFormatMismatchError || error !== null && typeof error === "object" && "kind" in error && error.kind === "audio-format-mismatch";
|
|
352
|
+
}
|
|
244
353
|
function writeUint32(target, offset, value) {
|
|
245
354
|
new DataView(target.buffer).setUint32(offset, value, true);
|
|
246
355
|
}
|
|
@@ -326,6 +435,7 @@ function mergeAudioBuffers(buffers, options) {
|
|
|
326
435
|
const format = typeof options === "string" ? options : options?.format;
|
|
327
436
|
if (!format) throw new UnsupportedMergeFormatError("");
|
|
328
437
|
try {
|
|
438
|
+
validateAudioSpecifications(buffers.map((buffer) => inspectAudioSpecification(buffer, format)));
|
|
329
439
|
if (isWavFormat(format)) return mergeWavBuffers(buffers);
|
|
330
440
|
if (isMp3Format(format)) {
|
|
331
441
|
const parts = buffers.map(stripMp3Tags);
|
|
@@ -348,7 +458,8 @@ function mergeAudioBuffers(buffers, options) {
|
|
|
348
458
|
}
|
|
349
459
|
throw new UnsupportedMergeFormatError(format);
|
|
350
460
|
} catch (error) {
|
|
351
|
-
if (error instanceof UnsupportedMergeFormatError || error instanceof MergeError)
|
|
461
|
+
if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
|
|
462
|
+
throw error;
|
|
352
463
|
throw new MergeError(`Audio buffers could not be merged for format "${format}".`, error);
|
|
353
464
|
}
|
|
354
465
|
}
|
|
@@ -426,17 +537,26 @@ async function synthesizeSsml(ssml, config) {
|
|
|
426
537
|
return {
|
|
427
538
|
originalTextRange: { ...marker.originalTextRange },
|
|
428
539
|
sourceNodePath: [...marker.sourceNodePath],
|
|
429
|
-
textRange: { ...marker.originalTextRange }
|
|
540
|
+
textRange: { ...marker.originalTextRange },
|
|
541
|
+
mappingStatus: "exact"
|
|
430
542
|
};
|
|
431
543
|
}
|
|
432
|
-
if (sourceSegments.length === 0 && !config.sourceTextRange && !config.sourceNodePath)
|
|
544
|
+
if (sourceSegments.length === 0 && !config.sourceTextRange && !config.sourceNodePath) {
|
|
545
|
+
const unmapped = { mappingStatus: "unmapped" };
|
|
546
|
+
Object.defineProperty(unmapped, "mappingStatus", { value: "unmapped", enumerable: false });
|
|
547
|
+
return unmapped;
|
|
548
|
+
}
|
|
433
549
|
const value = text ?? "";
|
|
434
550
|
let localStart = Number.isFinite(offsetHint) && (offsetHint ?? 0) >= 0 ? offsetHint : -1;
|
|
435
|
-
|
|
551
|
+
let mappingStatus = "exact";
|
|
552
|
+
if (value && localStart >= 0 && sourceText.slice(localStart, localStart + value.length) !== value) {
|
|
436
553
|
localStart = -1;
|
|
554
|
+
mappingStatus = "fallback";
|
|
555
|
+
}
|
|
437
556
|
if (localStart < 0 || localStart > sourceText.length) {
|
|
438
557
|
localStart = value ? sourceText.indexOf(value, sourceEventCursor) : sourceEventCursor;
|
|
439
558
|
if (localStart < 0) localStart = value ? sourceText.indexOf(value) : sourceEventCursor;
|
|
559
|
+
mappingStatus = "fallback";
|
|
440
560
|
}
|
|
441
561
|
localStart = Math.max(0, localStart);
|
|
442
562
|
const localEnd = Math.min(sourceText.length, localStart + value.length);
|
|
@@ -447,7 +567,8 @@ async function synthesizeSsml(ssml, config) {
|
|
|
447
567
|
return {
|
|
448
568
|
originalTextRange: { ...fallbackRange },
|
|
449
569
|
textRange: { ...fallbackRange },
|
|
450
|
-
...segment ? { sourceNodePath: [...segment.sourceNodePath] } : config.sourceNodePath ? { sourceNodePath: [...config.sourceNodePath] } : {}
|
|
570
|
+
...segment ? { sourceNodePath: [...segment.sourceNodePath] } : config.sourceNodePath ? { sourceNodePath: [...config.sourceNodePath] } : {},
|
|
571
|
+
mappingStatus: segment || config.sourceTextRange || config.sourceNodePath ? mappingStatus : "unmapped"
|
|
451
572
|
};
|
|
452
573
|
};
|
|
453
574
|
synthesizer.wordBoundary = (_sender, event) => {
|
|
@@ -496,20 +617,27 @@ async function synthesizeSsml(ssml, config) {
|
|
|
496
617
|
);
|
|
497
618
|
const durationMs = result.audioDuration ? ticksToMilliseconds(result.audioDuration) : eventDurationMs;
|
|
498
619
|
const requestId = result.resultId;
|
|
499
|
-
const addSourceMetadata = (event) =>
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
620
|
+
const addSourceMetadata = (event) => {
|
|
621
|
+
const mapped = {
|
|
622
|
+
...event,
|
|
623
|
+
...config.sourceTextRange && !("textRange" in event) ? { textRange: { ...config.sourceTextRange } } : {},
|
|
624
|
+
...config.sourceTextRange && !("originalTextRange" in event) ? { originalTextRange: { ...config.sourceTextRange } } : {},
|
|
625
|
+
...config.chunkIndex !== void 0 ? { chunkIndex: config.chunkIndex } : {},
|
|
626
|
+
...config.sourceNodePath ? { sourceNodePath: [...config.sourceNodePath] } : {},
|
|
627
|
+
...requestId ? { requestId } : {}
|
|
628
|
+
};
|
|
629
|
+
if (event.mappingStatus === "unmapped")
|
|
630
|
+
Object.defineProperty(mapped, "mappingStatus", { value: "unmapped", enumerable: false });
|
|
631
|
+
return mapped;
|
|
632
|
+
};
|
|
507
633
|
const sourceBoundaries = boundaries.map((boundary) => addSourceMetadata(boundary));
|
|
508
634
|
const sourceVisemes = visemes.map((viseme) => addSourceMetadata(viseme));
|
|
509
635
|
const sourceBookmarks = bookmarks.map((bookmark) => addSourceMetadata(bookmark));
|
|
510
636
|
resolve({
|
|
511
637
|
audioData: result.audioData,
|
|
512
638
|
durationMs,
|
|
639
|
+
audioSpec: inspectAudioSpecification(result.audioData, config.outputFormat ?? DEFAULT_OUTPUT_FORMAT),
|
|
640
|
+
mimeType: resolveMimeType(config.outputFormat ?? DEFAULT_OUTPUT_FORMAT),
|
|
513
641
|
...config.sourceTextRange ? { textRange: { ...config.sourceTextRange } } : {},
|
|
514
642
|
...requestId ? { requestId } : {},
|
|
515
643
|
...sourceBoundaries.length > 0 ? { boundaries: sourceBoundaries, wordBoundary: sourceBoundaries, wordBoundaries: sourceBoundaries } : {},
|
|
@@ -534,8 +662,66 @@ async function synthesizeSsml(ssml, config) {
|
|
|
534
662
|
}
|
|
535
663
|
});
|
|
536
664
|
}
|
|
665
|
+
function isRetryableSynthesisError(error) {
|
|
666
|
+
if (error instanceof SynthesisCancelledError || error instanceof SynthesisTimeoutError) return false;
|
|
667
|
+
if (error instanceof AzureTtsError && error.status !== 0)
|
|
668
|
+
return error.status === 429 || error.status >= 500 && error.status < 600;
|
|
669
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
670
|
+
if (/\b4\d{2}\b/.test(message)) return false;
|
|
671
|
+
const status = error && typeof error === "object" && "status" in error ? error.status : void 0;
|
|
672
|
+
if (typeof status === "number") return status === 429 || status >= 500 && status < 600;
|
|
673
|
+
return /network|connection|connect|socket|fetch failed|econn|etimedout|temporar|transient|unavailable/i.test(message);
|
|
674
|
+
}
|
|
675
|
+
function retryDelay(options, retryAttempt) {
|
|
676
|
+
const base = Math.min(options.maxDelayMs, options.initialDelayMs * 2 ** Math.max(0, retryAttempt - 1));
|
|
677
|
+
return Math.floor(Math.random() * (base + 1));
|
|
678
|
+
}
|
|
679
|
+
function resolveConcurrency(value, total) {
|
|
680
|
+
if (value === void 0) return 1;
|
|
681
|
+
if (value === Infinity) return Math.max(1, total);
|
|
682
|
+
return Number.isFinite(value) ? Math.max(1, Math.floor(value)) : 1;
|
|
683
|
+
}
|
|
684
|
+
async function waitForRetry(delayMs, signal) {
|
|
685
|
+
if (signal?.aborted) throw new SynthesisCancelledError();
|
|
686
|
+
if (delayMs <= 0) return;
|
|
687
|
+
await new Promise((resolve, reject) => {
|
|
688
|
+
let timer;
|
|
689
|
+
const abort = () => {
|
|
690
|
+
clearTimeout(timer);
|
|
691
|
+
signal?.removeEventListener("abort", abort);
|
|
692
|
+
reject(new SynthesisCancelledError());
|
|
693
|
+
};
|
|
694
|
+
timer = setTimeout(() => {
|
|
695
|
+
signal?.removeEventListener("abort", abort);
|
|
696
|
+
resolve();
|
|
697
|
+
}, delayMs);
|
|
698
|
+
if (signal) {
|
|
699
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
async function synthesizeWithRetry(ssml, config, retryOptions, onRetry) {
|
|
704
|
+
const options = retryOptions ? {
|
|
705
|
+
maxRetries: Math.max(0, Math.floor(retryOptions.maxRetries)),
|
|
706
|
+
initialDelayMs: Math.max(0, retryOptions.initialDelayMs),
|
|
707
|
+
maxDelayMs: Math.max(0, retryOptions.maxDelayMs)
|
|
708
|
+
} : void 0;
|
|
709
|
+
let attempt = 0;
|
|
710
|
+
while (true) {
|
|
711
|
+
if (config.signal?.aborted) throw new SynthesisCancelledError();
|
|
712
|
+
try {
|
|
713
|
+
return await synthesizeSsml(ssml, config);
|
|
714
|
+
} catch (error) {
|
|
715
|
+
if (!options || attempt >= options.maxRetries || !isRetryableSynthesisError(error)) throw error;
|
|
716
|
+
attempt += 1;
|
|
717
|
+
const delayMs = retryDelay(options, attempt);
|
|
718
|
+
onRetry(attempt, delayMs);
|
|
719
|
+
await waitForRetry(delayMs, config.signal);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
537
723
|
async function synthesizeSsmlChunks(chunks, config) {
|
|
538
|
-
const results =
|
|
724
|
+
const results = new Array(chunks.length);
|
|
539
725
|
const totalChunks = chunks.length;
|
|
540
726
|
const report = (event) => config.onProgress?.(event);
|
|
541
727
|
for (const [index, chunk] of chunks.entries()) {
|
|
@@ -550,57 +736,85 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
550
736
|
durationMs: 0
|
|
551
737
|
});
|
|
552
738
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
durationMs: 0
|
|
563
|
-
});
|
|
564
|
-
const startedAt = Date.now();
|
|
565
|
-
try {
|
|
566
|
-
const result = await synthesizeSsml(input.ssml, {
|
|
567
|
-
...config,
|
|
568
|
-
...input.originalTextRange ? { sourceTextRange: input.originalTextRange } : {},
|
|
569
|
-
...input.sourceNodePath ?? config.sourceNodePath ? { sourceNodePath: [...input.sourceNodePath ?? config.sourceNodePath ?? []] } : {},
|
|
570
|
-
...input.sourceTextSegments ? { sourceTextSegments: input.sourceTextSegments } : {},
|
|
571
|
-
...input.sourceMarkers ? { sourceMarkers: input.sourceMarkers } : {},
|
|
572
|
-
chunkIndex: index,
|
|
573
|
-
onProgress: void 0
|
|
574
|
-
});
|
|
575
|
-
results.push(result);
|
|
576
|
-
report({
|
|
577
|
-
currentChunk: index + 1,
|
|
578
|
-
totalChunks,
|
|
579
|
-
percent: totalChunks === 0 ? 100 : Math.round((index + 1) / totalChunks * 100),
|
|
580
|
-
chunkIndex: index,
|
|
581
|
-
originalTextRange: input.originalTextRange,
|
|
582
|
-
status: "success",
|
|
583
|
-
durationMs: Date.now() - startedAt
|
|
584
|
-
});
|
|
585
|
-
} catch (error) {
|
|
739
|
+
let completed = 0;
|
|
740
|
+
let nextIndex = 0;
|
|
741
|
+
const concurrency = resolveConcurrency(config.concurrency, chunks.length);
|
|
742
|
+
const worker = async () => {
|
|
743
|
+
while (true) {
|
|
744
|
+
const index = nextIndex++;
|
|
745
|
+
if (index >= chunks.length) return;
|
|
746
|
+
const chunk = chunks[index];
|
|
747
|
+
const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
|
|
586
748
|
report({
|
|
587
|
-
currentChunk:
|
|
749
|
+
currentChunk: completed,
|
|
588
750
|
totalChunks,
|
|
589
|
-
percent: totalChunks === 0 ? 100 : Math.round(
|
|
751
|
+
percent: totalChunks === 0 ? 100 : Math.round(completed / totalChunks * 100),
|
|
590
752
|
chunkIndex: index,
|
|
591
753
|
originalTextRange: input.originalTextRange,
|
|
592
|
-
status: "
|
|
593
|
-
durationMs:
|
|
594
|
-
error
|
|
754
|
+
status: "synthesizing",
|
|
755
|
+
durationMs: 0
|
|
595
756
|
});
|
|
596
|
-
|
|
757
|
+
const startedAt = Date.now();
|
|
758
|
+
try {
|
|
759
|
+
const result = await synthesizeWithRetry(
|
|
760
|
+
input.ssml,
|
|
761
|
+
{
|
|
762
|
+
...config,
|
|
763
|
+
...input.originalTextRange ? { sourceTextRange: input.originalTextRange } : {},
|
|
764
|
+
...input.sourceNodePath ?? config.sourceNodePath ? { sourceNodePath: [...input.sourceNodePath ?? config.sourceNodePath ?? []] } : {},
|
|
765
|
+
...input.sourceTextSegments ? { sourceTextSegments: input.sourceTextSegments } : {},
|
|
766
|
+
...input.sourceMarkers ? { sourceMarkers: input.sourceMarkers } : {},
|
|
767
|
+
chunkIndex: index,
|
|
768
|
+
onProgress: void 0
|
|
769
|
+
},
|
|
770
|
+
config.retryOptions,
|
|
771
|
+
(retryAttempt, nextRetryDelayMs) => report({
|
|
772
|
+
currentChunk: completed,
|
|
773
|
+
totalChunks,
|
|
774
|
+
percent: totalChunks === 0 ? 100 : Math.round(completed / totalChunks * 100),
|
|
775
|
+
chunkIndex: index,
|
|
776
|
+
originalTextRange: input.originalTextRange,
|
|
777
|
+
status: "synthesizing",
|
|
778
|
+
durationMs: Date.now() - startedAt,
|
|
779
|
+
retryAttempt,
|
|
780
|
+
nextRetryDelayMs,
|
|
781
|
+
isRetrying: true
|
|
782
|
+
})
|
|
783
|
+
);
|
|
784
|
+
results[index] = result;
|
|
785
|
+
completed += 1;
|
|
786
|
+
report({
|
|
787
|
+
currentChunk: completed,
|
|
788
|
+
totalChunks,
|
|
789
|
+
percent: totalChunks === 0 ? 100 : Math.round(completed / totalChunks * 100),
|
|
790
|
+
chunkIndex: index,
|
|
791
|
+
originalTextRange: input.originalTextRange,
|
|
792
|
+
status: "success",
|
|
793
|
+
durationMs: Date.now() - startedAt
|
|
794
|
+
});
|
|
795
|
+
} catch (error) {
|
|
796
|
+
report({
|
|
797
|
+
currentChunk: completed,
|
|
798
|
+
totalChunks,
|
|
799
|
+
percent: totalChunks === 0 ? 100 : Math.round(completed / totalChunks * 100),
|
|
800
|
+
chunkIndex: index,
|
|
801
|
+
originalTextRange: input.originalTextRange,
|
|
802
|
+
status: "failed",
|
|
803
|
+
durationMs: Date.now() - startedAt,
|
|
804
|
+
error
|
|
805
|
+
});
|
|
806
|
+
throw error;
|
|
807
|
+
}
|
|
597
808
|
}
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
|
|
809
|
+
};
|
|
810
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, chunks.length) }, () => worker()));
|
|
811
|
+
const orderedResults = results.filter((result) => result !== void 0);
|
|
812
|
+
return mergeSynthesisResults(orderedResults, {
|
|
813
|
+
format: config.outputFormat ?? DEFAULT_OUTPUT_FORMAT,
|
|
814
|
+
signal: config.signal
|
|
601
815
|
});
|
|
602
816
|
}
|
|
603
|
-
function createMergedResult(results, audioData, format) {
|
|
817
|
+
function createMergedResult(results, audioData, format, audioSpec, outputMimeType) {
|
|
604
818
|
const boundaries = [];
|
|
605
819
|
const visemes = [];
|
|
606
820
|
const bookmarks = [];
|
|
@@ -619,7 +833,8 @@ function createMergedResult(results, audioData, format) {
|
|
|
619
833
|
...boundary.sourceNodePath ? { sourceNodePath: [...boundary.sourceNodePath] } : {},
|
|
620
834
|
...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
|
|
621
835
|
...textRange ? { textRange: { ...textRange } } : {},
|
|
622
|
-
...requestId ? { requestId } : {}
|
|
836
|
+
...requestId ? { requestId } : {},
|
|
837
|
+
mappingStatus: boundary.mappingStatus ?? "unmapped"
|
|
623
838
|
});
|
|
624
839
|
}
|
|
625
840
|
for (const viseme of result.visemes ?? []) {
|
|
@@ -634,7 +849,8 @@ function createMergedResult(results, audioData, format) {
|
|
|
634
849
|
...viseme.sourceNodePath ? { sourceNodePath: [...viseme.sourceNodePath] } : {},
|
|
635
850
|
...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
|
|
636
851
|
...textRange ? { textRange: { ...textRange } } : {},
|
|
637
|
-
...requestId ? { requestId } : {}
|
|
852
|
+
...requestId ? { requestId } : {},
|
|
853
|
+
mappingStatus: viseme.mappingStatus ?? "unmapped"
|
|
638
854
|
});
|
|
639
855
|
}
|
|
640
856
|
for (const bookmark of result.bookmarks ?? []) {
|
|
@@ -649,7 +865,8 @@ function createMergedResult(results, audioData, format) {
|
|
|
649
865
|
...bookmark.sourceNodePath ? { sourceNodePath: [...bookmark.sourceNodePath] } : {},
|
|
650
866
|
...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
|
|
651
867
|
...textRange ? { textRange: { ...textRange } } : {},
|
|
652
|
-
...requestId ? { requestId } : {}
|
|
868
|
+
...requestId ? { requestId } : {},
|
|
869
|
+
mappingStatus: bookmark.mappingStatus ?? "unmapped"
|
|
653
870
|
});
|
|
654
871
|
}
|
|
655
872
|
durationOffset += Math.max(0, result.durationMs);
|
|
@@ -658,6 +875,8 @@ function createMergedResult(results, audioData, format) {
|
|
|
658
875
|
audioData,
|
|
659
876
|
durationMs: durationOffset,
|
|
660
877
|
mimeType: resolveMimeType(format),
|
|
878
|
+
audioSpec: audioSpec ?? formatAudioSpecification(format),
|
|
879
|
+
...outputMimeType ? { mimeType: outputMimeType } : {},
|
|
661
880
|
...boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {},
|
|
662
881
|
...visemes.length > 0 ? { visemes } : {},
|
|
663
882
|
...bookmarks.length > 0 ? { bookmarks } : {},
|
|
@@ -670,19 +889,47 @@ function mergeSynthesisResults(results, options) {
|
|
|
670
889
|
const format = resolvedOptions?.format;
|
|
671
890
|
if (!format) throw new UnsupportedMergeFormatError("");
|
|
672
891
|
const buffers = results.map((result) => result.audioData);
|
|
892
|
+
const inputSpecs = results.map((result) => result.audioSpec ?? inspectAudioSpecification(result.audioData, format));
|
|
893
|
+
validateAudioSpecifications(inputSpecs);
|
|
894
|
+
const signal = resolvedOptions.signal ?? new AbortController().signal;
|
|
895
|
+
if (signal.aborted) throw new SynthesisCancelledError();
|
|
673
896
|
if (resolvedOptions.customMerger) {
|
|
674
|
-
return Promise.resolve().then(
|
|
897
|
+
return Promise.resolve().then(
|
|
898
|
+
() => resolvedOptions.customMerger?.(buffers, {
|
|
899
|
+
format,
|
|
900
|
+
outputMimeType: resolvedOptions.outputMimeType ?? resolveMimeType(format),
|
|
901
|
+
inputSpecs,
|
|
902
|
+
signal
|
|
903
|
+
})
|
|
904
|
+
).then((merged) => {
|
|
675
905
|
if (!merged) throw new MergeError("The custom audio merger returned no audio buffer.");
|
|
676
|
-
|
|
906
|
+
if (!(merged instanceof ArrayBuffer) || buffers.some((buffer) => buffer.byteLength > 0) && merged.byteLength === 0)
|
|
907
|
+
throw new MergeError("The custom audio merger returned an invalid audio buffer.");
|
|
908
|
+
if (signal.aborted) throw new SynthesisCancelledError();
|
|
909
|
+
return createMergedResult(
|
|
910
|
+
results,
|
|
911
|
+
merged,
|
|
912
|
+
format,
|
|
913
|
+
inspectAudioSpecification(merged, format),
|
|
914
|
+
resolvedOptions.outputMimeType
|
|
915
|
+
);
|
|
677
916
|
}).catch((error) => {
|
|
678
|
-
if (error instanceof UnsupportedMergeFormatError || error instanceof MergeError)
|
|
917
|
+
if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
|
|
918
|
+
throw error;
|
|
679
919
|
throw new MergeError(`Custom audio merger failed for format "${format}".`, error);
|
|
680
920
|
});
|
|
681
921
|
}
|
|
682
922
|
try {
|
|
683
|
-
return createMergedResult(
|
|
923
|
+
return createMergedResult(
|
|
924
|
+
results,
|
|
925
|
+
mergeAudioBuffers(buffers, { format }),
|
|
926
|
+
format,
|
|
927
|
+
inputSpecs[0],
|
|
928
|
+
resolvedOptions.outputMimeType
|
|
929
|
+
);
|
|
684
930
|
} catch (error) {
|
|
685
|
-
if (error instanceof UnsupportedMergeFormatError || error instanceof MergeError)
|
|
931
|
+
if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
|
|
932
|
+
throw error;
|
|
686
933
|
throw new MergeError(`Audio buffers could not be merged for format "${format}".`, error);
|
|
687
934
|
}
|
|
688
935
|
}
|
|
@@ -704,8 +951,73 @@ var ChunkValidationError = class extends Error {
|
|
|
704
951
|
function failure(error) {
|
|
705
952
|
return { ok: false, success: false, status: error.kind, error };
|
|
706
953
|
}
|
|
954
|
+
function isRetryable(error) {
|
|
955
|
+
if (error instanceof Error && /cancel|abort|tim(?:e|ed) ?out/i.test(error.message)) return false;
|
|
956
|
+
const status = error && typeof error === "object" && "status" in error ? error.status : void 0;
|
|
957
|
+
if (typeof status === "number" && status !== 0) return status === 429 || status >= 500 && status < 600;
|
|
958
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
959
|
+
if (/\b4\d{2}\b/.test(message)) return false;
|
|
960
|
+
return /network|connection|connect|socket|fetch failed|econn|etimedout|temporar|transient|unavailable/i.test(message);
|
|
961
|
+
}
|
|
962
|
+
function delayForRetry(options, attempt) {
|
|
963
|
+
const maxDelay = Math.max(0, options.maxDelayMs);
|
|
964
|
+
const base = Math.min(maxDelay, Math.max(0, options.initialDelayMs) * 2 ** Math.max(0, attempt - 1));
|
|
965
|
+
return Math.floor(Math.random() * (base + 1));
|
|
966
|
+
}
|
|
967
|
+
function resolveConcurrency2(value, total) {
|
|
968
|
+
if (value === void 0) return 1;
|
|
969
|
+
if (value === Infinity) return Math.max(1, total);
|
|
970
|
+
return Number.isFinite(value) ? Math.max(1, Math.floor(value)) : 1;
|
|
971
|
+
}
|
|
972
|
+
async function retryableSynthesis(synthesize, options, signal, onRetry) {
|
|
973
|
+
const retry = options ? {
|
|
974
|
+
maxRetries: Math.max(0, Math.floor(options.maxRetries)),
|
|
975
|
+
initialDelayMs: options.initialDelayMs,
|
|
976
|
+
maxDelayMs: options.maxDelayMs
|
|
977
|
+
} : void 0;
|
|
978
|
+
let attempt = 0;
|
|
979
|
+
while (true) {
|
|
980
|
+
if (signal?.aborted) throw new Error("Speech synthesis was cancelled.");
|
|
981
|
+
try {
|
|
982
|
+
return await synthesize();
|
|
983
|
+
} catch (error) {
|
|
984
|
+
if (!retry || attempt >= retry.maxRetries || !isRetryable(error)) throw error;
|
|
985
|
+
attempt += 1;
|
|
986
|
+
const delayMs = delayForRetry(retry, attempt);
|
|
987
|
+
onRetry(attempt, delayMs);
|
|
988
|
+
if (delayMs > 0)
|
|
989
|
+
await new Promise((resolve, reject) => {
|
|
990
|
+
const timer = setTimeout(() => {
|
|
991
|
+
signal?.removeEventListener("abort", abort);
|
|
992
|
+
resolve();
|
|
993
|
+
}, delayMs);
|
|
994
|
+
const abort = () => {
|
|
995
|
+
clearTimeout(timer);
|
|
996
|
+
signal?.removeEventListener("abort", abort);
|
|
997
|
+
reject(new Error("Speech synthesis was cancelled."));
|
|
998
|
+
};
|
|
999
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
function sharedValidationOptions(options, signal) {
|
|
1005
|
+
const validator = options.urlValidator ?? options.customUrlValidator;
|
|
1006
|
+
if (!validator) return signal ? withValidationSignal(options, signal) : options;
|
|
1007
|
+
const runner = (0, import_ssml_core2.createAzureUrlValidatorRunner)(validator, {
|
|
1008
|
+
...options.urlValidation ?? {},
|
|
1009
|
+
...options.urlValidatorConcurrency !== void 0 ? { concurrency: options.urlValidatorConcurrency } : {},
|
|
1010
|
+
...options.urlValidatorTimeoutMs !== void 0 ? { timeoutMs: options.urlValidatorTimeoutMs } : {},
|
|
1011
|
+
...signal ? { signal } : {},
|
|
1012
|
+
...options.urlValidatorCache ? { cache: options.urlValidatorCache } : {}
|
|
1013
|
+
});
|
|
1014
|
+
return {
|
|
1015
|
+
...withValidationSignal(options, signal),
|
|
1016
|
+
urlValidatorRunner: runner
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
707
1019
|
async function synthesizeSsmlSafe(client, ssml, options = {}) {
|
|
708
|
-
const validationOptions =
|
|
1020
|
+
const validationOptions = sharedValidationOptions(options.validation ?? options, options.signal);
|
|
709
1021
|
const diagnostics = await Promise.resolve((0, import_ssml_core2.validateAzureSsml)(ssml, validationOptions));
|
|
710
1022
|
if (options.signal?.aborted) {
|
|
711
1023
|
const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
|
|
@@ -732,7 +1044,7 @@ async function synthesizeSsmlSafe(client, ssml, options = {}) {
|
|
|
732
1044
|
}
|
|
733
1045
|
}
|
|
734
1046
|
async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
735
|
-
const validationOptions =
|
|
1047
|
+
const validationOptions = sharedValidationOptions(options.validation ?? options, options.signal);
|
|
736
1048
|
if (options.signal?.aborted) {
|
|
737
1049
|
const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
|
|
738
1050
|
return failure(error);
|
|
@@ -753,16 +1065,24 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
753
1065
|
pending(index, "pending");
|
|
754
1066
|
});
|
|
755
1067
|
const validations = await Promise.all(
|
|
756
|
-
chunks.map(async (chunk) => {
|
|
1068
|
+
chunks.map(async (chunk, index) => {
|
|
757
1069
|
const ssml = typeof chunk === "string" ? chunk : chunk.ssml;
|
|
758
1070
|
const sourceNodePath = typeof chunk === "string" ? options.sourceNodePath : chunk.sourceNodePath ?? options.sourceNodePath;
|
|
759
1071
|
const diagnostics = await Promise.resolve(
|
|
760
|
-
(0, import_ssml_core2.validateAzureSsml)(ssml, {
|
|
1072
|
+
(0, import_ssml_core2.validateAzureSsml)(ssml, {
|
|
1073
|
+
...validationOptions,
|
|
1074
|
+
...sourceNodePath ? { sourceNodePath } : {},
|
|
1075
|
+
chunkIndex: index
|
|
1076
|
+
})
|
|
761
1077
|
);
|
|
762
1078
|
return diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
763
1079
|
})
|
|
764
1080
|
);
|
|
765
1081
|
const firstInvalidIndex = validations.findIndex((diagnostics) => diagnostics.length > 0);
|
|
1082
|
+
if (options.signal?.aborted) {
|
|
1083
|
+
const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
|
|
1084
|
+
return failure(error);
|
|
1085
|
+
}
|
|
766
1086
|
if (firstInvalidIndex >= 0) {
|
|
767
1087
|
const error = new ChunkValidationError(firstInvalidIndex, validations[firstInvalidIndex] ?? []);
|
|
768
1088
|
pending(firstInvalidIndex, "failed", error);
|
|
@@ -779,96 +1099,126 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
779
1099
|
outputFormat: options.outputFormat,
|
|
780
1100
|
signal: options.signal,
|
|
781
1101
|
timeoutMs: options.timeoutMs,
|
|
782
|
-
sourceNodePath: options.sourceNodePath
|
|
1102
|
+
sourceNodePath: options.sourceNodePath,
|
|
1103
|
+
concurrency: options.concurrency,
|
|
1104
|
+
retryOptions: options.retryOptions
|
|
783
1105
|
});
|
|
784
1106
|
return { ok: true, success: true, status: "success", value };
|
|
785
1107
|
}
|
|
786
|
-
const results =
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
const
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
sourceNodePath:
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
1108
|
+
const results = new Array(chunks.length);
|
|
1109
|
+
let completed = 0;
|
|
1110
|
+
let nextIndex = 0;
|
|
1111
|
+
const concurrency = resolveConcurrency2(options.concurrency, chunks.length);
|
|
1112
|
+
const worker = async () => {
|
|
1113
|
+
while (true) {
|
|
1114
|
+
const index = nextIndex++;
|
|
1115
|
+
if (index >= chunks.length) return;
|
|
1116
|
+
const chunk = chunks[index];
|
|
1117
|
+
const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
|
|
1118
|
+
const sourceNodePath = input.sourceNodePath;
|
|
1119
|
+
const originalTextRange = input.originalTextRange;
|
|
1120
|
+
pending(index, "synthesizing");
|
|
1121
|
+
const startedAt = Date.now();
|
|
1122
|
+
try {
|
|
1123
|
+
const result = await retryableSynthesis(
|
|
1124
|
+
() => client.synthesizeSsml(input.ssml, {
|
|
1125
|
+
outputFormat: options.outputFormat,
|
|
1126
|
+
signal: options.signal,
|
|
1127
|
+
timeoutMs: options.timeoutMs,
|
|
1128
|
+
sourceNodePath: input.sourceNodePath ?? options.sourceNodePath
|
|
1129
|
+
}),
|
|
1130
|
+
options.retryOptions,
|
|
1131
|
+
options.signal,
|
|
1132
|
+
(retryAttempt, nextRetryDelayMs) => options.onProgress?.({
|
|
1133
|
+
currentChunk: completed,
|
|
1134
|
+
totalChunks: chunks.length,
|
|
1135
|
+
percent: chunks.length === 0 ? 100 : Math.round(completed / chunks.length * 100),
|
|
1136
|
+
chunkIndex: index,
|
|
1137
|
+
originalTextRange: input.originalTextRange,
|
|
1138
|
+
status: "synthesizing",
|
|
1139
|
+
durationMs: Date.now() - startedAt,
|
|
1140
|
+
retryAttempt,
|
|
1141
|
+
nextRetryDelayMs,
|
|
1142
|
+
isRetrying: true
|
|
1143
|
+
})
|
|
1144
|
+
);
|
|
1145
|
+
results[index] = {
|
|
1146
|
+
...result,
|
|
1147
|
+
...input.originalTextRange ? { textRange: { ...input.originalTextRange } } : {},
|
|
1148
|
+
...sourceNodePath ? {
|
|
1149
|
+
boundaries: result.boundaries?.map((event) => ({
|
|
1150
|
+
...event,
|
|
1151
|
+
sourceNodePath: [...sourceNodePath],
|
|
1152
|
+
...event.originalTextRange ? { originalTextRange: { ...event.originalTextRange } } : input.originalTextRange ? { originalTextRange: { ...input.originalTextRange } } : {}
|
|
1153
|
+
})),
|
|
1154
|
+
visemes: result.visemes?.map((event) => ({
|
|
1155
|
+
...event,
|
|
1156
|
+
sourceNodePath: [...sourceNodePath],
|
|
1157
|
+
...event.originalTextRange ? { originalTextRange: { ...event.originalTextRange } } : input.originalTextRange ? { originalTextRange: { ...input.originalTextRange } } : {}
|
|
1158
|
+
})),
|
|
1159
|
+
bookmarks: result.bookmarks?.map((event) => ({
|
|
1160
|
+
...event,
|
|
1161
|
+
sourceNodePath: [...sourceNodePath],
|
|
1162
|
+
...event.originalTextRange ? { originalTextRange: { ...event.originalTextRange } } : input.originalTextRange ? { originalTextRange: { ...input.originalTextRange } } : {}
|
|
1163
|
+
}))
|
|
1164
|
+
} : {},
|
|
1165
|
+
...originalTextRange ? {
|
|
1166
|
+
boundaries: result.boundaries?.map((event) => ({
|
|
1167
|
+
...event,
|
|
1168
|
+
originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
|
|
1169
|
+
})),
|
|
1170
|
+
wordBoundary: result.wordBoundary?.map((event) => ({
|
|
1171
|
+
...event,
|
|
1172
|
+
originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
|
|
1173
|
+
})),
|
|
1174
|
+
wordBoundaries: result.wordBoundaries?.map((event) => ({
|
|
1175
|
+
...event,
|
|
1176
|
+
originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
|
|
1177
|
+
})),
|
|
1178
|
+
visemes: result.visemes?.map((event) => ({
|
|
1179
|
+
...event,
|
|
1180
|
+
originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
|
|
1181
|
+
})),
|
|
1182
|
+
bookmarks: result.bookmarks?.map((event) => ({
|
|
1183
|
+
...event,
|
|
1184
|
+
originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
|
|
1185
|
+
}))
|
|
1186
|
+
} : {}
|
|
1187
|
+
};
|
|
1188
|
+
completed += 1;
|
|
1189
|
+
options.onProgress?.({
|
|
1190
|
+
currentChunk: completed,
|
|
1191
|
+
totalChunks: chunks.length,
|
|
1192
|
+
percent: chunks.length === 0 ? 100 : Math.round(completed / chunks.length * 100),
|
|
1193
|
+
chunkIndex: index,
|
|
1194
|
+
originalTextRange: input.originalTextRange,
|
|
1195
|
+
status: "success",
|
|
1196
|
+
durationMs: Date.now() - startedAt
|
|
1197
|
+
});
|
|
1198
|
+
} catch (error) {
|
|
1199
|
+
options.onProgress?.({
|
|
1200
|
+
currentChunk: completed,
|
|
1201
|
+
totalChunks: chunks.length,
|
|
1202
|
+
percent: chunks.length === 0 ? 100 : Math.round(index / chunks.length * 100),
|
|
1203
|
+
chunkIndex: index,
|
|
1204
|
+
originalTextRange: input.originalTextRange,
|
|
1205
|
+
status: "failed",
|
|
1206
|
+
durationMs: Date.now() - startedAt,
|
|
1207
|
+
error
|
|
1208
|
+
});
|
|
1209
|
+
throw error;
|
|
1210
|
+
}
|
|
864
1211
|
}
|
|
865
|
-
}
|
|
1212
|
+
};
|
|
1213
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, chunks.length) }, () => worker()));
|
|
1214
|
+
const orderedResults = results.filter((result) => result !== void 0);
|
|
866
1215
|
return {
|
|
867
1216
|
ok: true,
|
|
868
1217
|
success: true,
|
|
869
1218
|
status: "success",
|
|
870
|
-
value: mergeSynthesisResults(
|
|
871
|
-
format: options.outputFormat ?? "audio-16khz-128kbitrate-mono-mp3"
|
|
1219
|
+
value: mergeSynthesisResults(orderedResults, {
|
|
1220
|
+
format: options.outputFormat ?? "audio-16khz-128kbitrate-mono-mp3",
|
|
1221
|
+
signal: options.signal
|
|
872
1222
|
})
|
|
873
1223
|
};
|
|
874
1224
|
} catch (error) {
|
|
@@ -927,7 +1277,9 @@ var AzureTtsClient = class {
|
|
|
927
1277
|
signal: options.signal ?? signal,
|
|
928
1278
|
timeoutMs: options.timeoutMs ?? timeoutMs,
|
|
929
1279
|
sourceNodePath: options.sourceNodePath,
|
|
930
|
-
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress
|
|
1280
|
+
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress,
|
|
1281
|
+
concurrency: options.concurrency ?? __privateGet(this, _options).concurrency,
|
|
1282
|
+
retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions
|
|
931
1283
|
});
|
|
932
1284
|
}
|
|
933
1285
|
async synthesizeSsmlSafe(ssml, options = {}) {
|
|
@@ -939,7 +1291,9 @@ var AzureTtsClient = class {
|
|
|
939
1291
|
outputFormat: options.outputFormat ?? __privateGet(this, _options).outputFormat,
|
|
940
1292
|
signal: options.signal ?? __privateGet(this, _options).signal,
|
|
941
1293
|
timeoutMs: options.timeoutMs ?? __privateGet(this, _options).timeoutMs,
|
|
942
|
-
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress
|
|
1294
|
+
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress,
|
|
1295
|
+
concurrency: options.concurrency ?? __privateGet(this, _options).concurrency,
|
|
1296
|
+
retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions
|
|
943
1297
|
});
|
|
944
1298
|
}
|
|
945
1299
|
async synthesizeSsmlChunksSafe(chunks, options = {}) {
|
|
@@ -1036,6 +1390,7 @@ async function fetchAzureVoiceCatalog(options) {
|
|
|
1036
1390
|
}
|
|
1037
1391
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1038
1392
|
0 && (module.exports = {
|
|
1393
|
+
AudioFormatMismatchError,
|
|
1039
1394
|
AzureTtsClient,
|
|
1040
1395
|
AzureTtsError,
|
|
1041
1396
|
AzureTtsSdkError,
|
|
@@ -1047,6 +1402,7 @@ async function fetchAzureVoiceCatalog(options) {
|
|
|
1047
1402
|
UnsupportedMergeFormatError,
|
|
1048
1403
|
canMergeAudioFormat,
|
|
1049
1404
|
fetchAzureVoiceCatalog,
|
|
1405
|
+
inspectAudioSpecification,
|
|
1050
1406
|
mergeAudioBuffers,
|
|
1051
1407
|
mergeSynthesisResults,
|
|
1052
1408
|
resolveMergeAudioFormat,
|