ssml-builder-js 2.14.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 +10 -4
- package/dist/{chunk-UZSCXPC5.mjs → chunk-F2EMU3HM.mjs} +2 -2
- package/dist/{chunk-QFIBPCO4.mjs → chunk-HI74FTKY.mjs} +197 -21
- package/dist/chunk-HI74FTKY.mjs.map +1 -0
- package/dist/{chunk-AQ55MOPU.mjs → chunk-NKLZGITR.mjs} +354 -177
- package/dist/chunk-NKLZGITR.mjs.map +1 -0
- package/dist/core.d.mts +67 -17
- package/dist/core.d.ts +67 -17
- package/dist/core.js +353 -174
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +5 -1
- package/dist/elements.js +70 -20
- package/dist/elements.js.map +1 -1
- package/dist/elements.mjs +2 -2
- package/dist/{index.d-CUXRwSw0.d.mts → index.d-DR5Qz43p.d.mts} +37 -2
- package/dist/{index.d-CUXRwSw0.d.ts → index.d-DR5Qz43p.d.ts} +37 -2
- package/dist/index.d.mts +162 -19
- package/dist/index.d.ts +162 -19
- package/dist/index.js +2060 -1128
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +752 -174
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +11 -2
- package/dist/react.d.ts +11 -2
- package/dist/react.js +147 -32
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +79 -14
- package/dist/react.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-AQ55MOPU.mjs.map +0 -1
- package/dist/chunk-QFIBPCO4.mjs.map +0 -1
- /package/dist/{chunk-UZSCXPC5.mjs.map → chunk-F2EMU3HM.mjs.map} +0 -0
package/dist/index.mjs
CHANGED
|
@@ -8,18 +8,22 @@ import {
|
|
|
8
8
|
fromPlainTextToSsml,
|
|
9
9
|
getAzureVoiceCatalogMetadata,
|
|
10
10
|
getBuiltInVoiceCatalogMetadata,
|
|
11
|
+
getSsmlSourceMap,
|
|
11
12
|
isValidAzureAudioDuration,
|
|
12
13
|
mapSsmlTextNodes,
|
|
13
14
|
normalizeAzureLanguage,
|
|
14
15
|
parseSsml,
|
|
15
16
|
splitSsmlDocument,
|
|
16
17
|
validateAzureSsml,
|
|
18
|
+
validateAzureSsmlChunks,
|
|
17
19
|
validateSsml,
|
|
18
20
|
validateSsmlStructureIntegrity
|
|
19
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-NKLZGITR.mjs";
|
|
20
22
|
import {
|
|
23
|
+
createAzureUrlValidatorRunner as createAzureUrlValidatorRunner2,
|
|
24
|
+
getSsmlSourceMap as getSsmlSourceMap2,
|
|
21
25
|
validateAzureSsml as validateAzureSsml2
|
|
22
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-HI74FTKY.mjs";
|
|
23
27
|
import {
|
|
24
28
|
__privateAdd,
|
|
25
29
|
__privateGet,
|
|
@@ -30,6 +34,7 @@ import {
|
|
|
30
34
|
var AzureTtsError = class extends Error {
|
|
31
35
|
constructor(status, statusText, responseBody, requestId) {
|
|
32
36
|
super(`Azure TTS request failed: ${status} ${statusText}`);
|
|
37
|
+
this.kind = "azure-api-error";
|
|
33
38
|
this.name = "AzureTtsError";
|
|
34
39
|
this.status = status;
|
|
35
40
|
this.statusText = statusText;
|
|
@@ -45,13 +50,52 @@ var AzureTtsSdkError = class extends AzureTtsError {
|
|
|
45
50
|
this.errorDetails = errorDetails;
|
|
46
51
|
}
|
|
47
52
|
};
|
|
53
|
+
var SynthesisCancelledError = class extends Error {
|
|
54
|
+
constructor(message = "Speech synthesis was cancelled.") {
|
|
55
|
+
super(message);
|
|
56
|
+
this.kind = "cancelled";
|
|
57
|
+
this.name = "SynthesisCancelledError";
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var SynthesisTimeoutError = class extends Error {
|
|
61
|
+
constructor(message) {
|
|
62
|
+
super(message);
|
|
63
|
+
this.kind = "timeout";
|
|
64
|
+
this.name = "SynthesisTimeoutError";
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var MergeError = class extends Error {
|
|
68
|
+
constructor(message, cause) {
|
|
69
|
+
super(message);
|
|
70
|
+
this.kind = "merge-error";
|
|
71
|
+
this.name = "MergeError";
|
|
72
|
+
this.cause = cause;
|
|
73
|
+
}
|
|
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
|
+
};
|
|
48
83
|
var UnsupportedMergeFormatError = class extends Error {
|
|
49
84
|
constructor(format) {
|
|
50
85
|
super(`Audio format "${format}" cannot be safely concatenated; container re-multiplexing is required.`);
|
|
86
|
+
this.kind = "unsupported-format-error";
|
|
51
87
|
this.name = "UnsupportedMergeFormatError";
|
|
52
88
|
this.format = format;
|
|
53
89
|
}
|
|
54
90
|
};
|
|
91
|
+
function toSynthesisError(error) {
|
|
92
|
+
if (error instanceof AzureTtsError || error instanceof MergeError || error instanceof AudioFormatMismatchError || error instanceof UnsupportedMergeFormatError || error instanceof SynthesisCancelledError || error instanceof SynthesisTimeoutError)
|
|
93
|
+
return error;
|
|
94
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
95
|
+
if (/cancel|abort/i.test(message)) return new SynthesisCancelledError(message);
|
|
96
|
+
if (/tim(?:e|ed) ?out/i.test(message)) return new SynthesisTimeoutError(message);
|
|
97
|
+
return createSpeechSdkError(error);
|
|
98
|
+
}
|
|
55
99
|
function createSpeechSdkError(error) {
|
|
56
100
|
const message = error instanceof Error ? error.message : String(error);
|
|
57
101
|
return new AzureTtsSdkError(message);
|
|
@@ -60,9 +104,6 @@ function createSpeechSdkError(error) {
|
|
|
60
104
|
// packages/azure-tts-client/src/synthesis.ts
|
|
61
105
|
import * as SpeechSDK2 from "microsoft-cognitiveservices-speech-sdk";
|
|
62
106
|
|
|
63
|
-
// packages/azure-tts-client/src/speechConfig.ts
|
|
64
|
-
import { SpeechConfig } from "microsoft-cognitiveservices-speech-sdk";
|
|
65
|
-
|
|
66
107
|
// packages/azure-tts-client/src/outputFormats.ts
|
|
67
108
|
import * as SpeechSDK from "microsoft-cognitiveservices-speech-sdk";
|
|
68
109
|
var DEFAULT_OUTPUT_FORMAT = "audio-16khz-128kbitrate-mono-mp3";
|
|
@@ -107,6 +148,14 @@ var OUTPUT_FORMATS = {
|
|
|
107
148
|
"amr-wb-16000hz": SpeechSDK.SpeechSynthesisOutputFormat.AmrWb16000Hz,
|
|
108
149
|
"g722-16khz-64kbps": SpeechSDK.SpeechSynthesisOutputFormat.G72216Khz64Kbps
|
|
109
150
|
};
|
|
151
|
+
function resolveMimeType(outputFormat) {
|
|
152
|
+
if (/(?:wav|wave|riff)/i.test(outputFormat)) return "audio/wav";
|
|
153
|
+
if (/(?:mp3|mpeg)/i.test(outputFormat)) return "audio/mpeg";
|
|
154
|
+
if (/ogg/i.test(outputFormat)) return "audio/ogg";
|
|
155
|
+
if (/webm/i.test(outputFormat)) return "audio/webm";
|
|
156
|
+
if (/raw/i.test(outputFormat)) return "audio/L16";
|
|
157
|
+
return "application/octet-stream";
|
|
158
|
+
}
|
|
110
159
|
function resolveOutputFormat(outputFormat) {
|
|
111
160
|
const resolvedFormat = OUTPUT_FORMATS[outputFormat];
|
|
112
161
|
if (resolvedFormat === void 0) {
|
|
@@ -116,6 +165,7 @@ function resolveOutputFormat(outputFormat) {
|
|
|
116
165
|
}
|
|
117
166
|
|
|
118
167
|
// packages/azure-tts-client/src/speechConfig.ts
|
|
168
|
+
import { SpeechConfig } from "microsoft-cognitiveservices-speech-sdk";
|
|
119
169
|
function resolveEndpoint(config) {
|
|
120
170
|
const endpoint = config.endpoint?.trim() || "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
|
|
121
171
|
return endpoint.replace(/\{region\}/g, encodeURIComponent(config.region));
|
|
@@ -168,6 +218,105 @@ function parseWav(buffer) {
|
|
|
168
218
|
}
|
|
169
219
|
return { chunks, data, format };
|
|
170
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
|
+
}
|
|
171
320
|
function writeUint32(target, offset, value) {
|
|
172
321
|
new DataView(target.buffer).setUint32(offset, value, true);
|
|
173
322
|
}
|
|
@@ -249,28 +398,37 @@ function resolveMergeAudioFormat(format) {
|
|
|
249
398
|
function canMergeAudioFormat(format) {
|
|
250
399
|
return resolveMergeAudioFormat(format) !== void 0;
|
|
251
400
|
}
|
|
252
|
-
function mergeAudioBuffers(buffers,
|
|
253
|
-
|
|
254
|
-
if (
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
401
|
+
function mergeAudioBuffers(buffers, options) {
|
|
402
|
+
const format = typeof options === "string" ? options : options?.format;
|
|
403
|
+
if (!format) throw new UnsupportedMergeFormatError("");
|
|
404
|
+
try {
|
|
405
|
+
validateAudioSpecifications(buffers.map((buffer) => inspectAudioSpecification(buffer, format)));
|
|
406
|
+
if (isWavFormat(format)) return mergeWavBuffers(buffers);
|
|
407
|
+
if (isMp3Format(format)) {
|
|
408
|
+
const parts = buffers.map(stripMp3Tags);
|
|
409
|
+
const output = new Uint8Array(parts.reduce((total, part) => total + part.byteLength, 0));
|
|
410
|
+
let offset = 0;
|
|
411
|
+
for (const part of parts) {
|
|
412
|
+
output.set(part, offset);
|
|
413
|
+
offset += part.byteLength;
|
|
414
|
+
}
|
|
415
|
+
return output.buffer;
|
|
261
416
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
417
|
+
if (isRawFormat(format)) {
|
|
418
|
+
const output = new Uint8Array(buffers.reduce((total, buffer) => total + buffer.byteLength, 0));
|
|
419
|
+
let offset = 0;
|
|
420
|
+
for (const buffer of buffers) {
|
|
421
|
+
output.set(new Uint8Array(buffer), offset);
|
|
422
|
+
offset += buffer.byteLength;
|
|
423
|
+
}
|
|
424
|
+
return output.buffer;
|
|
270
425
|
}
|
|
271
|
-
|
|
426
|
+
throw new UnsupportedMergeFormatError(format);
|
|
427
|
+
} catch (error) {
|
|
428
|
+
if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
|
|
429
|
+
throw error;
|
|
430
|
+
throw new MergeError(`Audio buffers could not be merged for format "${format}".`, error);
|
|
272
431
|
}
|
|
273
|
-
throw new UnsupportedMergeFormatError(format);
|
|
274
432
|
}
|
|
275
433
|
function closeSpeechResources(speechConfig, synthesizer) {
|
|
276
434
|
try {
|
|
@@ -285,7 +443,7 @@ function closeSpeechResources(speechConfig, synthesizer) {
|
|
|
285
443
|
var ticksToMilliseconds = (ticks) => Math.max(0, ticks) / 1e4;
|
|
286
444
|
async function synthesizeSsml(ssml, config) {
|
|
287
445
|
if (config.signal?.aborted) {
|
|
288
|
-
throw
|
|
446
|
+
throw new SynthesisCancelledError();
|
|
289
447
|
}
|
|
290
448
|
const speechConfig = createSpeechConfig(config);
|
|
291
449
|
const synthesizer = new SpeechSDK2.SpeechSynthesizer(speechConfig, null);
|
|
@@ -308,23 +466,104 @@ async function synthesizeSsml(ssml, config) {
|
|
|
308
466
|
settled = true;
|
|
309
467
|
cleanup();
|
|
310
468
|
closeResources();
|
|
311
|
-
reject(
|
|
469
|
+
reject(toSynthesisError(error));
|
|
312
470
|
};
|
|
313
471
|
const boundaries = [];
|
|
314
472
|
const visemes = [];
|
|
315
473
|
const bookmarks = [];
|
|
474
|
+
let sourceEventCursor = 0;
|
|
475
|
+
let generatedSourceMap;
|
|
476
|
+
if (!config.sourceTextSegments && !config.sourceMarkers) {
|
|
477
|
+
try {
|
|
478
|
+
generatedSourceMap = getSsmlSourceMap2(ssml);
|
|
479
|
+
} catch {
|
|
480
|
+
generatedSourceMap = void 0;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
const sourceBaseOffset = config.sourceTextRange?.start ?? 0;
|
|
484
|
+
const sourceSegments = config.sourceTextSegments ?? generatedSourceMap?.segments.map((segment) => ({
|
|
485
|
+
...segment,
|
|
486
|
+
range: {
|
|
487
|
+
start: segment.range.start + sourceBaseOffset,
|
|
488
|
+
end: segment.range.end + sourceBaseOffset
|
|
489
|
+
},
|
|
490
|
+
sourceNodePath: [...segment.sourceNodePath]
|
|
491
|
+
})) ?? [];
|
|
492
|
+
const sourceMarkers = config.sourceMarkers ?? generatedSourceMap?.markers.map((marker) => ({
|
|
493
|
+
...marker,
|
|
494
|
+
originalTextRange: {
|
|
495
|
+
start: marker.originalTextRange.start + sourceBaseOffset,
|
|
496
|
+
end: marker.originalTextRange.end + sourceBaseOffset
|
|
497
|
+
},
|
|
498
|
+
sourceNodePath: [...marker.sourceNodePath]
|
|
499
|
+
})) ?? [];
|
|
500
|
+
const sourceText = sourceSegments.map((segment) => segment.text).join("");
|
|
501
|
+
const mapSourceEvent = (text, offsetHint, markerName) => {
|
|
502
|
+
const marker = markerName ? sourceMarkers.find((candidate) => candidate.name === markerName) : void 0;
|
|
503
|
+
if (marker) {
|
|
504
|
+
return {
|
|
505
|
+
originalTextRange: { ...marker.originalTextRange },
|
|
506
|
+
sourceNodePath: [...marker.sourceNodePath],
|
|
507
|
+
textRange: { ...marker.originalTextRange },
|
|
508
|
+
mappingStatus: "exact"
|
|
509
|
+
};
|
|
510
|
+
}
|
|
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
|
+
}
|
|
516
|
+
const value = text ?? "";
|
|
517
|
+
let localStart = Number.isFinite(offsetHint) && (offsetHint ?? 0) >= 0 ? offsetHint : -1;
|
|
518
|
+
let mappingStatus = "exact";
|
|
519
|
+
if (value && localStart >= 0 && sourceText.slice(localStart, localStart + value.length) !== value) {
|
|
520
|
+
localStart = -1;
|
|
521
|
+
mappingStatus = "fallback";
|
|
522
|
+
}
|
|
523
|
+
if (localStart < 0 || localStart > sourceText.length) {
|
|
524
|
+
localStart = value ? sourceText.indexOf(value, sourceEventCursor) : sourceEventCursor;
|
|
525
|
+
if (localStart < 0) localStart = value ? sourceText.indexOf(value) : sourceEventCursor;
|
|
526
|
+
mappingStatus = "fallback";
|
|
527
|
+
}
|
|
528
|
+
localStart = Math.max(0, localStart);
|
|
529
|
+
const localEnd = Math.min(sourceText.length, localStart + value.length);
|
|
530
|
+
sourceEventCursor = Math.max(sourceEventCursor, localEnd);
|
|
531
|
+
const baseStart = config.sourceTextRange?.start ?? sourceSegments[0]?.range.start ?? 0;
|
|
532
|
+
const fallbackRange = { start: baseStart + localStart, end: baseStart + localEnd };
|
|
533
|
+
const segment = sourceSegments.find(({ range }) => range.start <= fallbackRange.start && range.end > fallbackRange.start) ?? sourceSegments.find(({ range }) => range.end > fallbackRange.start) ?? (value.length === 0 ? sourceSegments.find(({ range }) => range.start <= fallbackRange.start && range.end >= fallbackRange.start) : void 0);
|
|
534
|
+
return {
|
|
535
|
+
originalTextRange: { ...fallbackRange },
|
|
536
|
+
textRange: { ...fallbackRange },
|
|
537
|
+
...segment ? { sourceNodePath: [...segment.sourceNodePath] } : config.sourceNodePath ? { sourceNodePath: [...config.sourceNodePath] } : {},
|
|
538
|
+
mappingStatus: segment || config.sourceTextRange || config.sourceNodePath ? mappingStatus : "unmapped"
|
|
539
|
+
};
|
|
540
|
+
};
|
|
316
541
|
synthesizer.wordBoundary = (_sender, event) => {
|
|
317
542
|
boundaries.push({
|
|
318
543
|
text: event.text,
|
|
319
544
|
audioOffsetMs: ticksToMilliseconds(event.audioOffset),
|
|
320
|
-
durationMs: ticksToMilliseconds(event.duration)
|
|
545
|
+
durationMs: ticksToMilliseconds(event.duration),
|
|
546
|
+
...mapSourceEvent(
|
|
547
|
+
event.text,
|
|
548
|
+
event.textOffset
|
|
549
|
+
)
|
|
321
550
|
});
|
|
322
551
|
};
|
|
323
552
|
synthesizer.visemeReceived = (_sender, event) => {
|
|
324
|
-
|
|
553
|
+
const eventWithOffset = event;
|
|
554
|
+
visemes.push({
|
|
555
|
+
visemeId: event.visemeId,
|
|
556
|
+
audioOffsetMs: ticksToMilliseconds(event.audioOffset),
|
|
557
|
+
...mapSourceEvent(void 0, eventWithOffset.textOffset)
|
|
558
|
+
});
|
|
325
559
|
};
|
|
326
560
|
synthesizer.bookmarkReached = (_sender, event) => {
|
|
327
|
-
|
|
561
|
+
const eventWithOffset = event;
|
|
562
|
+
bookmarks.push({
|
|
563
|
+
name: event.text,
|
|
564
|
+
audioOffsetMs: ticksToMilliseconds(event.audioOffset),
|
|
565
|
+
...mapSourceEvent(void 0, eventWithOffset.textOffset, event.text)
|
|
566
|
+
});
|
|
328
567
|
};
|
|
329
568
|
const cb = (result) => {
|
|
330
569
|
if (settled) return;
|
|
@@ -345,20 +584,27 @@ async function synthesizeSsml(ssml, config) {
|
|
|
345
584
|
);
|
|
346
585
|
const durationMs = result.audioDuration ? ticksToMilliseconds(result.audioDuration) : eventDurationMs;
|
|
347
586
|
const requestId = result.resultId;
|
|
348
|
-
const addSourceMetadata = (event) =>
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
+
};
|
|
356
600
|
const sourceBoundaries = boundaries.map((boundary) => addSourceMetadata(boundary));
|
|
357
601
|
const sourceVisemes = visemes.map((viseme) => addSourceMetadata(viseme));
|
|
358
602
|
const sourceBookmarks = bookmarks.map((bookmark) => addSourceMetadata(bookmark));
|
|
359
603
|
resolve({
|
|
360
604
|
audioData: result.audioData,
|
|
361
605
|
durationMs,
|
|
606
|
+
audioSpec: inspectAudioSpecification(result.audioData, config.outputFormat ?? DEFAULT_OUTPUT_FORMAT),
|
|
607
|
+
mimeType: resolveMimeType(config.outputFormat ?? DEFAULT_OUTPUT_FORMAT),
|
|
362
608
|
...config.sourceTextRange ? { textRange: { ...config.sourceTextRange } } : {},
|
|
363
609
|
...requestId ? { requestId } : {},
|
|
364
610
|
...sourceBoundaries.length > 0 ? { boundaries: sourceBoundaries, wordBoundary: sourceBoundaries, wordBoundaries: sourceBoundaries } : {},
|
|
@@ -368,12 +614,12 @@ async function synthesizeSsml(ssml, config) {
|
|
|
368
614
|
};
|
|
369
615
|
try {
|
|
370
616
|
if (config.signal) {
|
|
371
|
-
abortHandler = () => rejectWithError(
|
|
617
|
+
abortHandler = () => rejectWithError(new SynthesisCancelledError());
|
|
372
618
|
config.signal.addEventListener("abort", abortHandler, { once: true });
|
|
373
619
|
}
|
|
374
620
|
if (config.timeoutMs !== void 0 && config.timeoutMs > 0) {
|
|
375
621
|
timeout = setTimeout(
|
|
376
|
-
() => rejectWithError(`Speech synthesis timed out after ${config.timeoutMs} ms.`),
|
|
622
|
+
() => rejectWithError(new SynthesisTimeoutError(`Speech synthesis timed out after ${config.timeoutMs} ms.`)),
|
|
377
623
|
config.timeoutMs
|
|
378
624
|
);
|
|
379
625
|
}
|
|
@@ -383,8 +629,66 @@ async function synthesizeSsml(ssml, config) {
|
|
|
383
629
|
}
|
|
384
630
|
});
|
|
385
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
|
+
}
|
|
386
690
|
async function synthesizeSsmlChunks(chunks, config) {
|
|
387
|
-
const results =
|
|
691
|
+
const results = new Array(chunks.length);
|
|
388
692
|
const totalChunks = chunks.length;
|
|
389
693
|
const report = (event) => config.onProgress?.(event);
|
|
390
694
|
for (const [index, chunk] of chunks.entries()) {
|
|
@@ -399,71 +703,90 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
399
703
|
durationMs: 0
|
|
400
704
|
});
|
|
401
705
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
durationMs: 0
|
|
412
|
-
});
|
|
413
|
-
const startedAt = Date.now();
|
|
414
|
-
try {
|
|
415
|
-
const result = await synthesizeSsml(input.ssml, {
|
|
416
|
-
...config,
|
|
417
|
-
...input.originalTextRange ? { sourceTextRange: input.originalTextRange } : {},
|
|
418
|
-
...input.sourceNodePath ? { sourceNodePath: input.sourceNodePath } : {},
|
|
419
|
-
chunkIndex: index,
|
|
420
|
-
onProgress: void 0
|
|
421
|
-
});
|
|
422
|
-
results.push(result);
|
|
423
|
-
report({
|
|
424
|
-
currentChunk: index + 1,
|
|
425
|
-
totalChunks,
|
|
426
|
-
percent: totalChunks === 0 ? 100 : Math.round((index + 1) / totalChunks * 100),
|
|
427
|
-
chunkIndex: index,
|
|
428
|
-
originalTextRange: input.originalTextRange,
|
|
429
|
-
status: "success",
|
|
430
|
-
durationMs: Date.now() - startedAt
|
|
431
|
-
});
|
|
432
|
-
} 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;
|
|
433
715
|
report({
|
|
434
|
-
currentChunk:
|
|
716
|
+
currentChunk: completed,
|
|
435
717
|
totalChunks,
|
|
436
|
-
percent: totalChunks === 0 ? 100 : Math.round(
|
|
718
|
+
percent: totalChunks === 0 ? 100 : Math.round(completed / totalChunks * 100),
|
|
437
719
|
chunkIndex: index,
|
|
438
720
|
originalTextRange: input.originalTextRange,
|
|
439
|
-
status: "
|
|
440
|
-
durationMs:
|
|
441
|
-
error
|
|
721
|
+
status: "synthesizing",
|
|
722
|
+
durationMs: 0
|
|
442
723
|
});
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
+
}
|
|
460
775
|
}
|
|
461
|
-
}
|
|
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
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
function createMergedResult(results, audioData, format, audioSpec, outputMimeType) {
|
|
462
785
|
const boundaries = [];
|
|
463
786
|
const visemes = [];
|
|
464
787
|
const bookmarks = [];
|
|
465
788
|
let durationOffset = 0;
|
|
466
|
-
for (const result of results) {
|
|
789
|
+
for (const [resultIndex, result] of results.entries()) {
|
|
467
790
|
const chunkBoundaries = result.boundaries && result.boundaries.length > 0 ? result.boundaries : result.wordBoundary ?? result.wordBoundaries ?? [];
|
|
468
791
|
for (const boundary of chunkBoundaries) {
|
|
469
792
|
const textRange = boundary.textRange ?? result.textRange;
|
|
@@ -473,11 +796,12 @@ function mergeSynthesisResults(results, format) {
|
|
|
473
796
|
...boundary,
|
|
474
797
|
audioOffsetMs: boundary.audioOffsetMs + durationOffset,
|
|
475
798
|
chunkAudioOffsetMs: boundary.chunkAudioOffsetMs ?? boundary.audioOffsetMs,
|
|
476
|
-
...boundary.chunkIndex === void 0 ? { chunkIndex:
|
|
799
|
+
...boundary.chunkIndex === void 0 ? { chunkIndex: resultIndex } : {},
|
|
477
800
|
...boundary.sourceNodePath ? { sourceNodePath: [...boundary.sourceNodePath] } : {},
|
|
478
801
|
...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
|
|
479
802
|
...textRange ? { textRange: { ...textRange } } : {},
|
|
480
|
-
...requestId ? { requestId } : {}
|
|
803
|
+
...requestId ? { requestId } : {},
|
|
804
|
+
mappingStatus: boundary.mappingStatus ?? "unmapped"
|
|
481
805
|
});
|
|
482
806
|
}
|
|
483
807
|
for (const viseme of result.visemes ?? []) {
|
|
@@ -488,11 +812,12 @@ function mergeSynthesisResults(results, format) {
|
|
|
488
812
|
...viseme,
|
|
489
813
|
audioOffsetMs: viseme.audioOffsetMs + durationOffset,
|
|
490
814
|
chunkAudioOffsetMs: viseme.chunkAudioOffsetMs ?? viseme.audioOffsetMs,
|
|
491
|
-
...viseme.chunkIndex === void 0 ? { chunkIndex:
|
|
815
|
+
...viseme.chunkIndex === void 0 ? { chunkIndex: resultIndex } : {},
|
|
492
816
|
...viseme.sourceNodePath ? { sourceNodePath: [...viseme.sourceNodePath] } : {},
|
|
493
817
|
...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
|
|
494
818
|
...textRange ? { textRange: { ...textRange } } : {},
|
|
495
|
-
...requestId ? { requestId } : {}
|
|
819
|
+
...requestId ? { requestId } : {},
|
|
820
|
+
mappingStatus: viseme.mappingStatus ?? "unmapped"
|
|
496
821
|
});
|
|
497
822
|
}
|
|
498
823
|
for (const bookmark of result.bookmarks ?? []) {
|
|
@@ -503,18 +828,22 @@ function mergeSynthesisResults(results, format) {
|
|
|
503
828
|
...bookmark,
|
|
504
829
|
audioOffsetMs: bookmark.audioOffsetMs + durationOffset,
|
|
505
830
|
chunkAudioOffsetMs: bookmark.chunkAudioOffsetMs ?? bookmark.audioOffsetMs,
|
|
506
|
-
...bookmark.chunkIndex === void 0 ? { chunkIndex:
|
|
831
|
+
...bookmark.chunkIndex === void 0 ? { chunkIndex: resultIndex } : {},
|
|
507
832
|
...bookmark.sourceNodePath ? { sourceNodePath: [...bookmark.sourceNodePath] } : {},
|
|
508
833
|
...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
|
|
509
834
|
...textRange ? { textRange: { ...textRange } } : {},
|
|
510
|
-
...requestId ? { requestId } : {}
|
|
835
|
+
...requestId ? { requestId } : {},
|
|
836
|
+
mappingStatus: bookmark.mappingStatus ?? "unmapped"
|
|
511
837
|
});
|
|
512
838
|
}
|
|
513
839
|
durationOffset += Math.max(0, result.durationMs);
|
|
514
840
|
}
|
|
515
841
|
return {
|
|
516
|
-
audioData
|
|
842
|
+
audioData,
|
|
517
843
|
durationMs: durationOffset,
|
|
844
|
+
mimeType: resolveMimeType(format),
|
|
845
|
+
audioSpec: audioSpec ?? formatAudioSpecification(format),
|
|
846
|
+
...outputMimeType ? { mimeType: outputMimeType } : {},
|
|
518
847
|
...boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {},
|
|
519
848
|
...visemes.length > 0 ? { visemes } : {},
|
|
520
849
|
...bookmarks.length > 0 ? { bookmarks } : {},
|
|
@@ -522,6 +851,55 @@ function mergeSynthesisResults(results, format) {
|
|
|
522
851
|
...results.length === 1 && results[0]?.textRange ? { textRange: { ...results[0].textRange } } : {}
|
|
523
852
|
};
|
|
524
853
|
}
|
|
854
|
+
function mergeSynthesisResults(results, options) {
|
|
855
|
+
const resolvedOptions = typeof options === "string" ? { format: options } : options;
|
|
856
|
+
const format = resolvedOptions?.format;
|
|
857
|
+
if (!format) throw new UnsupportedMergeFormatError("");
|
|
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();
|
|
863
|
+
if (resolvedOptions.customMerger) {
|
|
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) => {
|
|
872
|
+
if (!merged) throw new MergeError("The custom audio merger returned no audio buffer.");
|
|
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
|
+
);
|
|
883
|
+
}).catch((error) => {
|
|
884
|
+
if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
|
|
885
|
+
throw error;
|
|
886
|
+
throw new MergeError(`Custom audio merger failed for format "${format}".`, error);
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
try {
|
|
890
|
+
return createMergedResult(
|
|
891
|
+
results,
|
|
892
|
+
mergeAudioBuffers(buffers, { format }),
|
|
893
|
+
format,
|
|
894
|
+
inputSpecs[0],
|
|
895
|
+
resolvedOptions.outputMimeType
|
|
896
|
+
);
|
|
897
|
+
} catch (error) {
|
|
898
|
+
if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
|
|
899
|
+
throw error;
|
|
900
|
+
throw new MergeError(`Audio buffers could not be merged for format "${format}".`, error);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
525
903
|
async function synthesizeSpeech(ssml, config) {
|
|
526
904
|
return (await synthesizeSsml(ssml, config)).audioData;
|
|
527
905
|
}
|
|
@@ -530,37 +908,113 @@ async function synthesizeSpeech(ssml, config) {
|
|
|
530
908
|
var ChunkValidationError = class extends Error {
|
|
531
909
|
constructor(chunkIndex, diagnostics) {
|
|
532
910
|
super(`SSML validation failed for chunk ${chunkIndex}; the Azure Speech API was not called.`);
|
|
533
|
-
this.kind = "
|
|
911
|
+
this.kind = "validation-error";
|
|
534
912
|
this.name = "ChunkValidationError";
|
|
535
913
|
this.chunkIndex = chunkIndex;
|
|
536
914
|
this.diagnostics = diagnostics;
|
|
537
915
|
}
|
|
538
916
|
};
|
|
917
|
+
function failure(error) {
|
|
918
|
+
return { ok: false, success: false, status: error.kind, error };
|
|
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
|
+
}
|
|
539
985
|
async function synthesizeSsmlSafe(client, ssml, options = {}) {
|
|
540
|
-
const validationOptions = options.validation ?? options;
|
|
986
|
+
const validationOptions = sharedValidationOptions(options.validation ?? options, options.signal);
|
|
541
987
|
const diagnostics = await Promise.resolve(validateAzureSsml2(ssml, validationOptions));
|
|
988
|
+
if (options.signal?.aborted) {
|
|
989
|
+
const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
|
|
990
|
+
return failure(error);
|
|
991
|
+
}
|
|
542
992
|
const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
543
993
|
if (errors.length > 0) {
|
|
544
|
-
return {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
kind: "validation",
|
|
550
|
-
message: "SSML validation failed; the Azure Speech API was not called.",
|
|
551
|
-
diagnostics: errors
|
|
552
|
-
}
|
|
553
|
-
};
|
|
994
|
+
return failure({
|
|
995
|
+
kind: "validation-error",
|
|
996
|
+
message: "SSML validation failed; the Azure Speech API was not called.",
|
|
997
|
+
diagnostics: errors
|
|
998
|
+
});
|
|
554
999
|
}
|
|
555
1000
|
try {
|
|
556
|
-
return {
|
|
1001
|
+
return {
|
|
1002
|
+
ok: true,
|
|
1003
|
+
success: true,
|
|
1004
|
+
status: "success",
|
|
1005
|
+
value: await client.synthesizeSsml(ssml, { signal: options.signal })
|
|
1006
|
+
};
|
|
557
1007
|
} catch (error) {
|
|
558
|
-
const
|
|
559
|
-
return
|
|
1008
|
+
const synthesisError = toSynthesisError(error);
|
|
1009
|
+
return failure(synthesisError);
|
|
560
1010
|
}
|
|
561
1011
|
}
|
|
562
1012
|
async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
563
|
-
const validationOptions = options.validation ?? options;
|
|
1013
|
+
const validationOptions = sharedValidationOptions(options.validation ?? options, options.signal);
|
|
1014
|
+
if (options.signal?.aborted) {
|
|
1015
|
+
const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
|
|
1016
|
+
return failure(error);
|
|
1017
|
+
}
|
|
564
1018
|
const pending = (index, status, error) => {
|
|
565
1019
|
options.onProgress?.({
|
|
566
1020
|
currentChunk: status === "success" ? index + 1 : index,
|
|
@@ -577,77 +1031,175 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
577
1031
|
pending(index, "pending");
|
|
578
1032
|
});
|
|
579
1033
|
const validations = await Promise.all(
|
|
580
|
-
chunks.map(async (chunk) => {
|
|
1034
|
+
chunks.map(async (chunk, index) => {
|
|
581
1035
|
const ssml = typeof chunk === "string" ? chunk : chunk.ssml;
|
|
582
|
-
const
|
|
1036
|
+
const sourceNodePath = typeof chunk === "string" ? options.sourceNodePath : chunk.sourceNodePath ?? options.sourceNodePath;
|
|
1037
|
+
const diagnostics = await Promise.resolve(
|
|
1038
|
+
validateAzureSsml2(ssml, {
|
|
1039
|
+
...validationOptions,
|
|
1040
|
+
...sourceNodePath ? { sourceNodePath } : {},
|
|
1041
|
+
chunkIndex: index
|
|
1042
|
+
})
|
|
1043
|
+
);
|
|
583
1044
|
return diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
584
1045
|
})
|
|
585
1046
|
);
|
|
586
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
|
+
}
|
|
587
1052
|
if (firstInvalidIndex >= 0) {
|
|
588
1053
|
const error = new ChunkValidationError(firstInvalidIndex, validations[firstInvalidIndex] ?? []);
|
|
589
1054
|
pending(firstInvalidIndex, "failed", error);
|
|
590
|
-
return
|
|
1055
|
+
return failure(error);
|
|
591
1056
|
}
|
|
592
1057
|
try {
|
|
593
1058
|
if (client.synthesizeChunks) {
|
|
594
|
-
const
|
|
1059
|
+
const normalizedChunks = chunks.map((chunk) => {
|
|
1060
|
+
if (typeof chunk === "string" || chunk.sourceNodePath || !options.sourceNodePath) return chunk;
|
|
1061
|
+
return { ...chunk, sourceNodePath: [...options.sourceNodePath] };
|
|
1062
|
+
});
|
|
1063
|
+
const value = await client.synthesizeChunks(normalizedChunks, {
|
|
1064
|
+
onProgress: options.onProgress,
|
|
1065
|
+
outputFormat: options.outputFormat,
|
|
1066
|
+
signal: options.signal,
|
|
1067
|
+
timeoutMs: options.timeoutMs,
|
|
1068
|
+
sourceNodePath: options.sourceNodePath,
|
|
1069
|
+
concurrency: options.concurrency,
|
|
1070
|
+
retryOptions: options.retryOptions
|
|
1071
|
+
});
|
|
595
1072
|
return { ok: true, success: true, status: "success", value };
|
|
596
1073
|
}
|
|
597
|
-
const results =
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
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
|
+
}
|
|
638
1177
|
}
|
|
639
|
-
}
|
|
1178
|
+
};
|
|
1179
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, chunks.length) }, () => worker()));
|
|
1180
|
+
const orderedResults = results.filter((result) => result !== void 0);
|
|
640
1181
|
return {
|
|
641
1182
|
ok: true,
|
|
642
1183
|
success: true,
|
|
643
1184
|
status: "success",
|
|
644
|
-
value: mergeSynthesisResults(
|
|
1185
|
+
value: mergeSynthesisResults(orderedResults, {
|
|
1186
|
+
format: options.outputFormat ?? "audio-16khz-128kbitrate-mono-mp3",
|
|
1187
|
+
signal: options.signal
|
|
1188
|
+
})
|
|
645
1189
|
};
|
|
646
1190
|
} catch (error) {
|
|
647
|
-
const
|
|
648
|
-
return
|
|
1191
|
+
const synthesisError = toSynthesisError(error);
|
|
1192
|
+
return failure(synthesisError);
|
|
649
1193
|
}
|
|
650
1194
|
}
|
|
1195
|
+
function withValidationSignal(options, signal) {
|
|
1196
|
+
if (!signal) return options;
|
|
1197
|
+
return {
|
|
1198
|
+
...options,
|
|
1199
|
+
urlValidatorSignal: signal,
|
|
1200
|
+
urlValidation: { ...options.urlValidation ?? {}, signal }
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
651
1203
|
|
|
652
1204
|
// packages/azure-tts-client/src/client.ts
|
|
653
1205
|
var ENDPOINT_TEMPLATE = "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
|
|
@@ -664,11 +1216,21 @@ var AzureTtsClient = class {
|
|
|
664
1216
|
const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };
|
|
665
1217
|
return synthesizeSpeech(ssml, config);
|
|
666
1218
|
}
|
|
667
|
-
async synthesizeSsml(ssml) {
|
|
1219
|
+
async synthesizeSsml(ssml, options = {}) {
|
|
668
1220
|
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
669
1221
|
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
670
1222
|
__privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
|
|
671
|
-
return synthesizeSsml(ssml, {
|
|
1223
|
+
return synthesizeSsml(ssml, {
|
|
1224
|
+
endpoint,
|
|
1225
|
+
region,
|
|
1226
|
+
subscriptionKey,
|
|
1227
|
+
outputFormat: options.outputFormat ?? outputFormat,
|
|
1228
|
+
signal: options.signal ?? signal,
|
|
1229
|
+
timeoutMs: options.timeoutMs ?? timeoutMs,
|
|
1230
|
+
sourceNodePath: options.sourceNodePath,
|
|
1231
|
+
sourceTextSegments: options.sourceTextSegments,
|
|
1232
|
+
sourceMarkers: options.sourceMarkers
|
|
1233
|
+
});
|
|
672
1234
|
}
|
|
673
1235
|
async synthesizeChunks(chunks, options = {}) {
|
|
674
1236
|
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
@@ -677,10 +1239,13 @@ var AzureTtsClient = class {
|
|
|
677
1239
|
endpoint,
|
|
678
1240
|
region,
|
|
679
1241
|
subscriptionKey,
|
|
680
|
-
outputFormat,
|
|
681
|
-
signal,
|
|
682
|
-
timeoutMs,
|
|
683
|
-
|
|
1242
|
+
outputFormat: options.outputFormat ?? outputFormat,
|
|
1243
|
+
signal: options.signal ?? signal,
|
|
1244
|
+
timeoutMs: options.timeoutMs ?? timeoutMs,
|
|
1245
|
+
sourceNodePath: options.sourceNodePath,
|
|
1246
|
+
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress,
|
|
1247
|
+
concurrency: options.concurrency ?? __privateGet(this, _options).concurrency,
|
|
1248
|
+
retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions
|
|
684
1249
|
});
|
|
685
1250
|
}
|
|
686
1251
|
async synthesizeSsmlSafe(ssml, options = {}) {
|
|
@@ -690,7 +1255,11 @@ var AzureTtsClient = class {
|
|
|
690
1255
|
return synthesizeSsmlChunksSafe(this, chunks, {
|
|
691
1256
|
...options,
|
|
692
1257
|
outputFormat: options.outputFormat ?? __privateGet(this, _options).outputFormat,
|
|
693
|
-
|
|
1258
|
+
signal: options.signal ?? __privateGet(this, _options).signal,
|
|
1259
|
+
timeoutMs: options.timeoutMs ?? __privateGet(this, _options).timeoutMs,
|
|
1260
|
+
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress,
|
|
1261
|
+
concurrency: options.concurrency ?? __privateGet(this, _options).concurrency,
|
|
1262
|
+
retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions
|
|
694
1263
|
});
|
|
695
1264
|
}
|
|
696
1265
|
async synthesizeSsmlChunksSafe(chunks, options = {}) {
|
|
@@ -786,10 +1355,15 @@ async function fetchAzureVoiceCatalog(options) {
|
|
|
786
1355
|
};
|
|
787
1356
|
}
|
|
788
1357
|
export {
|
|
1358
|
+
AudioFormatMismatchError,
|
|
789
1359
|
AzureTtsClient,
|
|
790
1360
|
AzureTtsError,
|
|
791
1361
|
AzureTtsSdkError,
|
|
792
1362
|
ChunkValidationError,
|
|
1363
|
+
DEFAULT_OUTPUT_FORMAT,
|
|
1364
|
+
MergeError,
|
|
1365
|
+
SynthesisCancelledError,
|
|
1366
|
+
SynthesisTimeoutError,
|
|
793
1367
|
UnsupportedMergeFormatError,
|
|
794
1368
|
areAzureLanguagesEquivalent,
|
|
795
1369
|
buildPartialSsml,
|
|
@@ -802,6 +1376,8 @@ export {
|
|
|
802
1376
|
fromPlainTextToSsml,
|
|
803
1377
|
getAzureVoiceCatalogMetadata,
|
|
804
1378
|
getBuiltInVoiceCatalogMetadata,
|
|
1379
|
+
getSsmlSourceMap,
|
|
1380
|
+
inspectAudioSpecification,
|
|
805
1381
|
isValidAzureAudioDuration,
|
|
806
1382
|
mapSsmlTextNodes,
|
|
807
1383
|
mergeAudioBuffers,
|
|
@@ -809,6 +1385,7 @@ export {
|
|
|
809
1385
|
normalizeAzureLanguage,
|
|
810
1386
|
parseSsml,
|
|
811
1387
|
resolveMergeAudioFormat,
|
|
1388
|
+
resolveMimeType,
|
|
812
1389
|
splitSsmlDocument,
|
|
813
1390
|
synthesizeSpeech,
|
|
814
1391
|
synthesizeSsml,
|
|
@@ -816,6 +1393,7 @@ export {
|
|
|
816
1393
|
synthesizeSsmlChunksSafe,
|
|
817
1394
|
synthesizeSsmlSafe,
|
|
818
1395
|
validateAzureSsml,
|
|
1396
|
+
validateAzureSsmlChunks,
|
|
819
1397
|
validateSsml,
|
|
820
1398
|
validateSsmlStructureIntegrity
|
|
821
1399
|
};
|