@ssml-builder-js/azure-tts-client 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/CHANGELOG.md +22 -0
- package/dist/index.d.mts +161 -18
- package/dist/index.d.ts +161 -18
- package/dist/index.js +754 -174
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +749 -173
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/client.ts +23 -5
- package/src/errors.ts +85 -0
- package/src/index.ts +25 -2
- package/src/outputFormats.ts +14 -3
- package/src/safe.ts +330 -81
- package/src/synthesis.ts +555 -97
- package/src/types.ts +48 -1
- package/test/synthesis.test.ts +64 -0
- package/test/v213-pipeline.test.ts +23 -16
- package/test/v214-pipeline.test.ts +7 -3
- package/test/v215-pipeline.test.ts +104 -0
- package/test/v216-pipeline.test.ts +110 -0
package/dist/index.js
CHANGED
|
@@ -37,16 +37,23 @@ 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,
|
|
43
44
|
ChunkValidationError: () => ChunkValidationError,
|
|
45
|
+
DEFAULT_OUTPUT_FORMAT: () => DEFAULT_OUTPUT_FORMAT,
|
|
46
|
+
MergeError: () => MergeError,
|
|
47
|
+
SynthesisCancelledError: () => SynthesisCancelledError,
|
|
48
|
+
SynthesisTimeoutError: () => SynthesisTimeoutError,
|
|
44
49
|
UnsupportedMergeFormatError: () => UnsupportedMergeFormatError,
|
|
45
50
|
canMergeAudioFormat: () => canMergeAudioFormat,
|
|
46
51
|
fetchAzureVoiceCatalog: () => fetchAzureVoiceCatalog,
|
|
52
|
+
inspectAudioSpecification: () => inspectAudioSpecification,
|
|
47
53
|
mergeAudioBuffers: () => mergeAudioBuffers,
|
|
48
54
|
mergeSynthesisResults: () => mergeSynthesisResults,
|
|
49
55
|
resolveMergeAudioFormat: () => resolveMergeAudioFormat,
|
|
56
|
+
resolveMimeType: () => resolveMimeType,
|
|
50
57
|
synthesizeSpeech: () => synthesizeSpeech,
|
|
51
58
|
synthesizeSsml: () => synthesizeSsml,
|
|
52
59
|
synthesizeSsmlChunks: () => synthesizeSsmlChunks,
|
|
@@ -59,6 +66,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
59
66
|
var AzureTtsError = class extends Error {
|
|
60
67
|
constructor(status, statusText, responseBody, requestId) {
|
|
61
68
|
super(`Azure TTS request failed: ${status} ${statusText}`);
|
|
69
|
+
this.kind = "azure-api-error";
|
|
62
70
|
this.name = "AzureTtsError";
|
|
63
71
|
this.status = status;
|
|
64
72
|
this.statusText = statusText;
|
|
@@ -74,13 +82,52 @@ var AzureTtsSdkError = class extends AzureTtsError {
|
|
|
74
82
|
this.errorDetails = errorDetails;
|
|
75
83
|
}
|
|
76
84
|
};
|
|
85
|
+
var SynthesisCancelledError = class extends Error {
|
|
86
|
+
constructor(message = "Speech synthesis was cancelled.") {
|
|
87
|
+
super(message);
|
|
88
|
+
this.kind = "cancelled";
|
|
89
|
+
this.name = "SynthesisCancelledError";
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
var SynthesisTimeoutError = class extends Error {
|
|
93
|
+
constructor(message) {
|
|
94
|
+
super(message);
|
|
95
|
+
this.kind = "timeout";
|
|
96
|
+
this.name = "SynthesisTimeoutError";
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
var MergeError = class extends Error {
|
|
100
|
+
constructor(message, cause) {
|
|
101
|
+
super(message);
|
|
102
|
+
this.kind = "merge-error";
|
|
103
|
+
this.name = "MergeError";
|
|
104
|
+
this.cause = cause;
|
|
105
|
+
}
|
|
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
|
+
};
|
|
77
115
|
var UnsupportedMergeFormatError = class extends Error {
|
|
78
116
|
constructor(format) {
|
|
79
117
|
super(`Audio format "${format}" cannot be safely concatenated; container re-multiplexing is required.`);
|
|
118
|
+
this.kind = "unsupported-format-error";
|
|
80
119
|
this.name = "UnsupportedMergeFormatError";
|
|
81
120
|
this.format = format;
|
|
82
121
|
}
|
|
83
122
|
};
|
|
123
|
+
function toSynthesisError(error) {
|
|
124
|
+
if (error instanceof AzureTtsError || error instanceof MergeError || error instanceof AudioFormatMismatchError || error instanceof UnsupportedMergeFormatError || error instanceof SynthesisCancelledError || error instanceof SynthesisTimeoutError)
|
|
125
|
+
return error;
|
|
126
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
127
|
+
if (/cancel|abort/i.test(message)) return new SynthesisCancelledError(message);
|
|
128
|
+
if (/tim(?:e|ed) ?out/i.test(message)) return new SynthesisTimeoutError(message);
|
|
129
|
+
return createSpeechSdkError(error);
|
|
130
|
+
}
|
|
84
131
|
function createSpeechSdkError(error) {
|
|
85
132
|
const message = error instanceof Error ? error.message : String(error);
|
|
86
133
|
return new AzureTtsSdkError(message);
|
|
@@ -88,9 +135,7 @@ function createSpeechSdkError(error) {
|
|
|
88
135
|
|
|
89
136
|
// src/synthesis.ts
|
|
90
137
|
var SpeechSDK2 = __toESM(require("microsoft-cognitiveservices-speech-sdk"));
|
|
91
|
-
|
|
92
|
-
// src/speechConfig.ts
|
|
93
|
-
var import_microsoft_cognitiveservices_speech_sdk = require("microsoft-cognitiveservices-speech-sdk");
|
|
138
|
+
var import_ssml_core = require("@ssml-builder-js/ssml-core");
|
|
94
139
|
|
|
95
140
|
// src/outputFormats.ts
|
|
96
141
|
var SpeechSDK = __toESM(require("microsoft-cognitiveservices-speech-sdk"));
|
|
@@ -136,6 +181,14 @@ var OUTPUT_FORMATS = {
|
|
|
136
181
|
"amr-wb-16000hz": SpeechSDK.SpeechSynthesisOutputFormat.AmrWb16000Hz,
|
|
137
182
|
"g722-16khz-64kbps": SpeechSDK.SpeechSynthesisOutputFormat.G72216Khz64Kbps
|
|
138
183
|
};
|
|
184
|
+
function resolveMimeType(outputFormat) {
|
|
185
|
+
if (/(?:wav|wave|riff)/i.test(outputFormat)) return "audio/wav";
|
|
186
|
+
if (/(?:mp3|mpeg)/i.test(outputFormat)) return "audio/mpeg";
|
|
187
|
+
if (/ogg/i.test(outputFormat)) return "audio/ogg";
|
|
188
|
+
if (/webm/i.test(outputFormat)) return "audio/webm";
|
|
189
|
+
if (/raw/i.test(outputFormat)) return "audio/L16";
|
|
190
|
+
return "application/octet-stream";
|
|
191
|
+
}
|
|
139
192
|
function resolveOutputFormat(outputFormat) {
|
|
140
193
|
const resolvedFormat = OUTPUT_FORMATS[outputFormat];
|
|
141
194
|
if (resolvedFormat === void 0) {
|
|
@@ -145,6 +198,7 @@ function resolveOutputFormat(outputFormat) {
|
|
|
145
198
|
}
|
|
146
199
|
|
|
147
200
|
// src/speechConfig.ts
|
|
201
|
+
var import_microsoft_cognitiveservices_speech_sdk = require("microsoft-cognitiveservices-speech-sdk");
|
|
148
202
|
function resolveEndpoint(config) {
|
|
149
203
|
const endpoint = config.endpoint?.trim() || "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
|
|
150
204
|
return endpoint.replace(/\{region\}/g, encodeURIComponent(config.region));
|
|
@@ -197,6 +251,105 @@ function parseWav(buffer) {
|
|
|
197
251
|
}
|
|
198
252
|
return { chunks, data, format };
|
|
199
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
|
+
}
|
|
200
353
|
function writeUint32(target, offset, value) {
|
|
201
354
|
new DataView(target.buffer).setUint32(offset, value, true);
|
|
202
355
|
}
|
|
@@ -278,28 +431,37 @@ function resolveMergeAudioFormat(format) {
|
|
|
278
431
|
function canMergeAudioFormat(format) {
|
|
279
432
|
return resolveMergeAudioFormat(format) !== void 0;
|
|
280
433
|
}
|
|
281
|
-
function mergeAudioBuffers(buffers,
|
|
282
|
-
|
|
283
|
-
if (
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
434
|
+
function mergeAudioBuffers(buffers, options) {
|
|
435
|
+
const format = typeof options === "string" ? options : options?.format;
|
|
436
|
+
if (!format) throw new UnsupportedMergeFormatError("");
|
|
437
|
+
try {
|
|
438
|
+
validateAudioSpecifications(buffers.map((buffer) => inspectAudioSpecification(buffer, format)));
|
|
439
|
+
if (isWavFormat(format)) return mergeWavBuffers(buffers);
|
|
440
|
+
if (isMp3Format(format)) {
|
|
441
|
+
const parts = buffers.map(stripMp3Tags);
|
|
442
|
+
const output = new Uint8Array(parts.reduce((total, part) => total + part.byteLength, 0));
|
|
443
|
+
let offset = 0;
|
|
444
|
+
for (const part of parts) {
|
|
445
|
+
output.set(part, offset);
|
|
446
|
+
offset += part.byteLength;
|
|
447
|
+
}
|
|
448
|
+
return output.buffer;
|
|
290
449
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
450
|
+
if (isRawFormat(format)) {
|
|
451
|
+
const output = new Uint8Array(buffers.reduce((total, buffer) => total + buffer.byteLength, 0));
|
|
452
|
+
let offset = 0;
|
|
453
|
+
for (const buffer of buffers) {
|
|
454
|
+
output.set(new Uint8Array(buffer), offset);
|
|
455
|
+
offset += buffer.byteLength;
|
|
456
|
+
}
|
|
457
|
+
return output.buffer;
|
|
299
458
|
}
|
|
300
|
-
|
|
459
|
+
throw new UnsupportedMergeFormatError(format);
|
|
460
|
+
} catch (error) {
|
|
461
|
+
if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
|
|
462
|
+
throw error;
|
|
463
|
+
throw new MergeError(`Audio buffers could not be merged for format "${format}".`, error);
|
|
301
464
|
}
|
|
302
|
-
throw new UnsupportedMergeFormatError(format);
|
|
303
465
|
}
|
|
304
466
|
function closeSpeechResources(speechConfig, synthesizer) {
|
|
305
467
|
try {
|
|
@@ -314,7 +476,7 @@ function closeSpeechResources(speechConfig, synthesizer) {
|
|
|
314
476
|
var ticksToMilliseconds = (ticks) => Math.max(0, ticks) / 1e4;
|
|
315
477
|
async function synthesizeSsml(ssml, config) {
|
|
316
478
|
if (config.signal?.aborted) {
|
|
317
|
-
throw
|
|
479
|
+
throw new SynthesisCancelledError();
|
|
318
480
|
}
|
|
319
481
|
const speechConfig = createSpeechConfig(config);
|
|
320
482
|
const synthesizer = new SpeechSDK2.SpeechSynthesizer(speechConfig, null);
|
|
@@ -337,23 +499,104 @@ async function synthesizeSsml(ssml, config) {
|
|
|
337
499
|
settled = true;
|
|
338
500
|
cleanup();
|
|
339
501
|
closeResources();
|
|
340
|
-
reject(
|
|
502
|
+
reject(toSynthesisError(error));
|
|
341
503
|
};
|
|
342
504
|
const boundaries = [];
|
|
343
505
|
const visemes = [];
|
|
344
506
|
const bookmarks = [];
|
|
507
|
+
let sourceEventCursor = 0;
|
|
508
|
+
let generatedSourceMap;
|
|
509
|
+
if (!config.sourceTextSegments && !config.sourceMarkers) {
|
|
510
|
+
try {
|
|
511
|
+
generatedSourceMap = (0, import_ssml_core.getSsmlSourceMap)(ssml);
|
|
512
|
+
} catch {
|
|
513
|
+
generatedSourceMap = void 0;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
const sourceBaseOffset = config.sourceTextRange?.start ?? 0;
|
|
517
|
+
const sourceSegments = config.sourceTextSegments ?? generatedSourceMap?.segments.map((segment) => ({
|
|
518
|
+
...segment,
|
|
519
|
+
range: {
|
|
520
|
+
start: segment.range.start + sourceBaseOffset,
|
|
521
|
+
end: segment.range.end + sourceBaseOffset
|
|
522
|
+
},
|
|
523
|
+
sourceNodePath: [...segment.sourceNodePath]
|
|
524
|
+
})) ?? [];
|
|
525
|
+
const sourceMarkers = config.sourceMarkers ?? generatedSourceMap?.markers.map((marker) => ({
|
|
526
|
+
...marker,
|
|
527
|
+
originalTextRange: {
|
|
528
|
+
start: marker.originalTextRange.start + sourceBaseOffset,
|
|
529
|
+
end: marker.originalTextRange.end + sourceBaseOffset
|
|
530
|
+
},
|
|
531
|
+
sourceNodePath: [...marker.sourceNodePath]
|
|
532
|
+
})) ?? [];
|
|
533
|
+
const sourceText = sourceSegments.map((segment) => segment.text).join("");
|
|
534
|
+
const mapSourceEvent = (text, offsetHint, markerName) => {
|
|
535
|
+
const marker = markerName ? sourceMarkers.find((candidate) => candidate.name === markerName) : void 0;
|
|
536
|
+
if (marker) {
|
|
537
|
+
return {
|
|
538
|
+
originalTextRange: { ...marker.originalTextRange },
|
|
539
|
+
sourceNodePath: [...marker.sourceNodePath],
|
|
540
|
+
textRange: { ...marker.originalTextRange },
|
|
541
|
+
mappingStatus: "exact"
|
|
542
|
+
};
|
|
543
|
+
}
|
|
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
|
+
}
|
|
549
|
+
const value = text ?? "";
|
|
550
|
+
let localStart = Number.isFinite(offsetHint) && (offsetHint ?? 0) >= 0 ? offsetHint : -1;
|
|
551
|
+
let mappingStatus = "exact";
|
|
552
|
+
if (value && localStart >= 0 && sourceText.slice(localStart, localStart + value.length) !== value) {
|
|
553
|
+
localStart = -1;
|
|
554
|
+
mappingStatus = "fallback";
|
|
555
|
+
}
|
|
556
|
+
if (localStart < 0 || localStart > sourceText.length) {
|
|
557
|
+
localStart = value ? sourceText.indexOf(value, sourceEventCursor) : sourceEventCursor;
|
|
558
|
+
if (localStart < 0) localStart = value ? sourceText.indexOf(value) : sourceEventCursor;
|
|
559
|
+
mappingStatus = "fallback";
|
|
560
|
+
}
|
|
561
|
+
localStart = Math.max(0, localStart);
|
|
562
|
+
const localEnd = Math.min(sourceText.length, localStart + value.length);
|
|
563
|
+
sourceEventCursor = Math.max(sourceEventCursor, localEnd);
|
|
564
|
+
const baseStart = config.sourceTextRange?.start ?? sourceSegments[0]?.range.start ?? 0;
|
|
565
|
+
const fallbackRange = { start: baseStart + localStart, end: baseStart + localEnd };
|
|
566
|
+
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);
|
|
567
|
+
return {
|
|
568
|
+
originalTextRange: { ...fallbackRange },
|
|
569
|
+
textRange: { ...fallbackRange },
|
|
570
|
+
...segment ? { sourceNodePath: [...segment.sourceNodePath] } : config.sourceNodePath ? { sourceNodePath: [...config.sourceNodePath] } : {},
|
|
571
|
+
mappingStatus: segment || config.sourceTextRange || config.sourceNodePath ? mappingStatus : "unmapped"
|
|
572
|
+
};
|
|
573
|
+
};
|
|
345
574
|
synthesizer.wordBoundary = (_sender, event) => {
|
|
346
575
|
boundaries.push({
|
|
347
576
|
text: event.text,
|
|
348
577
|
audioOffsetMs: ticksToMilliseconds(event.audioOffset),
|
|
349
|
-
durationMs: ticksToMilliseconds(event.duration)
|
|
578
|
+
durationMs: ticksToMilliseconds(event.duration),
|
|
579
|
+
...mapSourceEvent(
|
|
580
|
+
event.text,
|
|
581
|
+
event.textOffset
|
|
582
|
+
)
|
|
350
583
|
});
|
|
351
584
|
};
|
|
352
585
|
synthesizer.visemeReceived = (_sender, event) => {
|
|
353
|
-
|
|
586
|
+
const eventWithOffset = event;
|
|
587
|
+
visemes.push({
|
|
588
|
+
visemeId: event.visemeId,
|
|
589
|
+
audioOffsetMs: ticksToMilliseconds(event.audioOffset),
|
|
590
|
+
...mapSourceEvent(void 0, eventWithOffset.textOffset)
|
|
591
|
+
});
|
|
354
592
|
};
|
|
355
593
|
synthesizer.bookmarkReached = (_sender, event) => {
|
|
356
|
-
|
|
594
|
+
const eventWithOffset = event;
|
|
595
|
+
bookmarks.push({
|
|
596
|
+
name: event.text,
|
|
597
|
+
audioOffsetMs: ticksToMilliseconds(event.audioOffset),
|
|
598
|
+
...mapSourceEvent(void 0, eventWithOffset.textOffset, event.text)
|
|
599
|
+
});
|
|
357
600
|
};
|
|
358
601
|
const cb = (result) => {
|
|
359
602
|
if (settled) return;
|
|
@@ -374,20 +617,27 @@ async function synthesizeSsml(ssml, config) {
|
|
|
374
617
|
);
|
|
375
618
|
const durationMs = result.audioDuration ? ticksToMilliseconds(result.audioDuration) : eventDurationMs;
|
|
376
619
|
const requestId = result.resultId;
|
|
377
|
-
const addSourceMetadata = (event) =>
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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
|
+
};
|
|
385
633
|
const sourceBoundaries = boundaries.map((boundary) => addSourceMetadata(boundary));
|
|
386
634
|
const sourceVisemes = visemes.map((viseme) => addSourceMetadata(viseme));
|
|
387
635
|
const sourceBookmarks = bookmarks.map((bookmark) => addSourceMetadata(bookmark));
|
|
388
636
|
resolve({
|
|
389
637
|
audioData: result.audioData,
|
|
390
638
|
durationMs,
|
|
639
|
+
audioSpec: inspectAudioSpecification(result.audioData, config.outputFormat ?? DEFAULT_OUTPUT_FORMAT),
|
|
640
|
+
mimeType: resolveMimeType(config.outputFormat ?? DEFAULT_OUTPUT_FORMAT),
|
|
391
641
|
...config.sourceTextRange ? { textRange: { ...config.sourceTextRange } } : {},
|
|
392
642
|
...requestId ? { requestId } : {},
|
|
393
643
|
...sourceBoundaries.length > 0 ? { boundaries: sourceBoundaries, wordBoundary: sourceBoundaries, wordBoundaries: sourceBoundaries } : {},
|
|
@@ -397,12 +647,12 @@ async function synthesizeSsml(ssml, config) {
|
|
|
397
647
|
};
|
|
398
648
|
try {
|
|
399
649
|
if (config.signal) {
|
|
400
|
-
abortHandler = () => rejectWithError(
|
|
650
|
+
abortHandler = () => rejectWithError(new SynthesisCancelledError());
|
|
401
651
|
config.signal.addEventListener("abort", abortHandler, { once: true });
|
|
402
652
|
}
|
|
403
653
|
if (config.timeoutMs !== void 0 && config.timeoutMs > 0) {
|
|
404
654
|
timeout = setTimeout(
|
|
405
|
-
() => rejectWithError(`Speech synthesis timed out after ${config.timeoutMs} ms.`),
|
|
655
|
+
() => rejectWithError(new SynthesisTimeoutError(`Speech synthesis timed out after ${config.timeoutMs} ms.`)),
|
|
406
656
|
config.timeoutMs
|
|
407
657
|
);
|
|
408
658
|
}
|
|
@@ -412,8 +662,66 @@ async function synthesizeSsml(ssml, config) {
|
|
|
412
662
|
}
|
|
413
663
|
});
|
|
414
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
|
+
}
|
|
415
723
|
async function synthesizeSsmlChunks(chunks, config) {
|
|
416
|
-
const results =
|
|
724
|
+
const results = new Array(chunks.length);
|
|
417
725
|
const totalChunks = chunks.length;
|
|
418
726
|
const report = (event) => config.onProgress?.(event);
|
|
419
727
|
for (const [index, chunk] of chunks.entries()) {
|
|
@@ -428,71 +736,90 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
428
736
|
durationMs: 0
|
|
429
737
|
});
|
|
430
738
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
durationMs: 0
|
|
441
|
-
});
|
|
442
|
-
const startedAt = Date.now();
|
|
443
|
-
try {
|
|
444
|
-
const result = await synthesizeSsml(input.ssml, {
|
|
445
|
-
...config,
|
|
446
|
-
...input.originalTextRange ? { sourceTextRange: input.originalTextRange } : {},
|
|
447
|
-
...input.sourceNodePath ? { sourceNodePath: input.sourceNodePath } : {},
|
|
448
|
-
chunkIndex: index,
|
|
449
|
-
onProgress: void 0
|
|
450
|
-
});
|
|
451
|
-
results.push(result);
|
|
452
|
-
report({
|
|
453
|
-
currentChunk: index + 1,
|
|
454
|
-
totalChunks,
|
|
455
|
-
percent: totalChunks === 0 ? 100 : Math.round((index + 1) / totalChunks * 100),
|
|
456
|
-
chunkIndex: index,
|
|
457
|
-
originalTextRange: input.originalTextRange,
|
|
458
|
-
status: "success",
|
|
459
|
-
durationMs: Date.now() - startedAt
|
|
460
|
-
});
|
|
461
|
-
} 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;
|
|
462
748
|
report({
|
|
463
|
-
currentChunk:
|
|
749
|
+
currentChunk: completed,
|
|
464
750
|
totalChunks,
|
|
465
|
-
percent: totalChunks === 0 ? 100 : Math.round(
|
|
751
|
+
percent: totalChunks === 0 ? 100 : Math.round(completed / totalChunks * 100),
|
|
466
752
|
chunkIndex: index,
|
|
467
753
|
originalTextRange: input.originalTextRange,
|
|
468
|
-
status: "
|
|
469
|
-
durationMs:
|
|
470
|
-
error
|
|
754
|
+
status: "synthesizing",
|
|
755
|
+
durationMs: 0
|
|
471
756
|
});
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
+
}
|
|
489
808
|
}
|
|
490
|
-
}
|
|
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
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
function createMergedResult(results, audioData, format, audioSpec, outputMimeType) {
|
|
491
818
|
const boundaries = [];
|
|
492
819
|
const visemes = [];
|
|
493
820
|
const bookmarks = [];
|
|
494
821
|
let durationOffset = 0;
|
|
495
|
-
for (const result of results) {
|
|
822
|
+
for (const [resultIndex, result] of results.entries()) {
|
|
496
823
|
const chunkBoundaries = result.boundaries && result.boundaries.length > 0 ? result.boundaries : result.wordBoundary ?? result.wordBoundaries ?? [];
|
|
497
824
|
for (const boundary of chunkBoundaries) {
|
|
498
825
|
const textRange = boundary.textRange ?? result.textRange;
|
|
@@ -502,11 +829,12 @@ function mergeSynthesisResults(results, format) {
|
|
|
502
829
|
...boundary,
|
|
503
830
|
audioOffsetMs: boundary.audioOffsetMs + durationOffset,
|
|
504
831
|
chunkAudioOffsetMs: boundary.chunkAudioOffsetMs ?? boundary.audioOffsetMs,
|
|
505
|
-
...boundary.chunkIndex === void 0 ? { chunkIndex:
|
|
832
|
+
...boundary.chunkIndex === void 0 ? { chunkIndex: resultIndex } : {},
|
|
506
833
|
...boundary.sourceNodePath ? { sourceNodePath: [...boundary.sourceNodePath] } : {},
|
|
507
834
|
...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
|
|
508
835
|
...textRange ? { textRange: { ...textRange } } : {},
|
|
509
|
-
...requestId ? { requestId } : {}
|
|
836
|
+
...requestId ? { requestId } : {},
|
|
837
|
+
mappingStatus: boundary.mappingStatus ?? "unmapped"
|
|
510
838
|
});
|
|
511
839
|
}
|
|
512
840
|
for (const viseme of result.visemes ?? []) {
|
|
@@ -517,11 +845,12 @@ function mergeSynthesisResults(results, format) {
|
|
|
517
845
|
...viseme,
|
|
518
846
|
audioOffsetMs: viseme.audioOffsetMs + durationOffset,
|
|
519
847
|
chunkAudioOffsetMs: viseme.chunkAudioOffsetMs ?? viseme.audioOffsetMs,
|
|
520
|
-
...viseme.chunkIndex === void 0 ? { chunkIndex:
|
|
848
|
+
...viseme.chunkIndex === void 0 ? { chunkIndex: resultIndex } : {},
|
|
521
849
|
...viseme.sourceNodePath ? { sourceNodePath: [...viseme.sourceNodePath] } : {},
|
|
522
850
|
...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
|
|
523
851
|
...textRange ? { textRange: { ...textRange } } : {},
|
|
524
|
-
...requestId ? { requestId } : {}
|
|
852
|
+
...requestId ? { requestId } : {},
|
|
853
|
+
mappingStatus: viseme.mappingStatus ?? "unmapped"
|
|
525
854
|
});
|
|
526
855
|
}
|
|
527
856
|
for (const bookmark of result.bookmarks ?? []) {
|
|
@@ -532,18 +861,22 @@ function mergeSynthesisResults(results, format) {
|
|
|
532
861
|
...bookmark,
|
|
533
862
|
audioOffsetMs: bookmark.audioOffsetMs + durationOffset,
|
|
534
863
|
chunkAudioOffsetMs: bookmark.chunkAudioOffsetMs ?? bookmark.audioOffsetMs,
|
|
535
|
-
...bookmark.chunkIndex === void 0 ? { chunkIndex:
|
|
864
|
+
...bookmark.chunkIndex === void 0 ? { chunkIndex: resultIndex } : {},
|
|
536
865
|
...bookmark.sourceNodePath ? { sourceNodePath: [...bookmark.sourceNodePath] } : {},
|
|
537
866
|
...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
|
|
538
867
|
...textRange ? { textRange: { ...textRange } } : {},
|
|
539
|
-
...requestId ? { requestId } : {}
|
|
868
|
+
...requestId ? { requestId } : {},
|
|
869
|
+
mappingStatus: bookmark.mappingStatus ?? "unmapped"
|
|
540
870
|
});
|
|
541
871
|
}
|
|
542
872
|
durationOffset += Math.max(0, result.durationMs);
|
|
543
873
|
}
|
|
544
874
|
return {
|
|
545
|
-
audioData
|
|
875
|
+
audioData,
|
|
546
876
|
durationMs: durationOffset,
|
|
877
|
+
mimeType: resolveMimeType(format),
|
|
878
|
+
audioSpec: audioSpec ?? formatAudioSpecification(format),
|
|
879
|
+
...outputMimeType ? { mimeType: outputMimeType } : {},
|
|
547
880
|
...boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {},
|
|
548
881
|
...visemes.length > 0 ? { visemes } : {},
|
|
549
882
|
...bookmarks.length > 0 ? { bookmarks } : {},
|
|
@@ -551,46 +884,171 @@ function mergeSynthesisResults(results, format) {
|
|
|
551
884
|
...results.length === 1 && results[0]?.textRange ? { textRange: { ...results[0].textRange } } : {}
|
|
552
885
|
};
|
|
553
886
|
}
|
|
887
|
+
function mergeSynthesisResults(results, options) {
|
|
888
|
+
const resolvedOptions = typeof options === "string" ? { format: options } : options;
|
|
889
|
+
const format = resolvedOptions?.format;
|
|
890
|
+
if (!format) throw new UnsupportedMergeFormatError("");
|
|
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();
|
|
896
|
+
if (resolvedOptions.customMerger) {
|
|
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) => {
|
|
905
|
+
if (!merged) throw new MergeError("The custom audio merger returned no audio buffer.");
|
|
906
|
+
if (!(merged instanceof ArrayBuffer) || buffers.some((buffer) => buffer.byteLength > 0) && merged.byteLength === 0)
|
|
907
|
+
throw new MergeError("The custom audio merger returned an invalid audio buffer.");
|
|
908
|
+
if (signal.aborted) throw new SynthesisCancelledError();
|
|
909
|
+
return createMergedResult(
|
|
910
|
+
results,
|
|
911
|
+
merged,
|
|
912
|
+
format,
|
|
913
|
+
inspectAudioSpecification(merged, format),
|
|
914
|
+
resolvedOptions.outputMimeType
|
|
915
|
+
);
|
|
916
|
+
}).catch((error) => {
|
|
917
|
+
if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
|
|
918
|
+
throw error;
|
|
919
|
+
throw new MergeError(`Custom audio merger failed for format "${format}".`, error);
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
try {
|
|
923
|
+
return createMergedResult(
|
|
924
|
+
results,
|
|
925
|
+
mergeAudioBuffers(buffers, { format }),
|
|
926
|
+
format,
|
|
927
|
+
inputSpecs[0],
|
|
928
|
+
resolvedOptions.outputMimeType
|
|
929
|
+
);
|
|
930
|
+
} catch (error) {
|
|
931
|
+
if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
|
|
932
|
+
throw error;
|
|
933
|
+
throw new MergeError(`Audio buffers could not be merged for format "${format}".`, error);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
554
936
|
async function synthesizeSpeech(ssml, config) {
|
|
555
937
|
return (await synthesizeSsml(ssml, config)).audioData;
|
|
556
938
|
}
|
|
557
939
|
|
|
558
940
|
// src/safe.ts
|
|
559
|
-
var
|
|
941
|
+
var import_ssml_core2 = require("@ssml-builder-js/ssml-core");
|
|
560
942
|
var ChunkValidationError = class extends Error {
|
|
561
943
|
constructor(chunkIndex, diagnostics) {
|
|
562
944
|
super(`SSML validation failed for chunk ${chunkIndex}; the Azure Speech API was not called.`);
|
|
563
|
-
this.kind = "
|
|
945
|
+
this.kind = "validation-error";
|
|
564
946
|
this.name = "ChunkValidationError";
|
|
565
947
|
this.chunkIndex = chunkIndex;
|
|
566
948
|
this.diagnostics = diagnostics;
|
|
567
949
|
}
|
|
568
950
|
};
|
|
951
|
+
function failure(error) {
|
|
952
|
+
return { ok: false, success: false, status: error.kind, error };
|
|
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
|
+
}
|
|
569
1019
|
async function synthesizeSsmlSafe(client, ssml, options = {}) {
|
|
570
|
-
const validationOptions = options.validation ?? options;
|
|
571
|
-
const diagnostics = await Promise.resolve((0,
|
|
1020
|
+
const validationOptions = sharedValidationOptions(options.validation ?? options, options.signal);
|
|
1021
|
+
const diagnostics = await Promise.resolve((0, import_ssml_core2.validateAzureSsml)(ssml, validationOptions));
|
|
1022
|
+
if (options.signal?.aborted) {
|
|
1023
|
+
const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
|
|
1024
|
+
return failure(error);
|
|
1025
|
+
}
|
|
572
1026
|
const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
573
1027
|
if (errors.length > 0) {
|
|
574
|
-
return {
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
kind: "validation",
|
|
580
|
-
message: "SSML validation failed; the Azure Speech API was not called.",
|
|
581
|
-
diagnostics: errors
|
|
582
|
-
}
|
|
583
|
-
};
|
|
1028
|
+
return failure({
|
|
1029
|
+
kind: "validation-error",
|
|
1030
|
+
message: "SSML validation failed; the Azure Speech API was not called.",
|
|
1031
|
+
diagnostics: errors
|
|
1032
|
+
});
|
|
584
1033
|
}
|
|
585
1034
|
try {
|
|
586
|
-
return {
|
|
1035
|
+
return {
|
|
1036
|
+
ok: true,
|
|
1037
|
+
success: true,
|
|
1038
|
+
status: "success",
|
|
1039
|
+
value: await client.synthesizeSsml(ssml, { signal: options.signal })
|
|
1040
|
+
};
|
|
587
1041
|
} catch (error) {
|
|
588
|
-
const
|
|
589
|
-
return
|
|
1042
|
+
const synthesisError = toSynthesisError(error);
|
|
1043
|
+
return failure(synthesisError);
|
|
590
1044
|
}
|
|
591
1045
|
}
|
|
592
1046
|
async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
593
|
-
const validationOptions = options.validation ?? options;
|
|
1047
|
+
const validationOptions = sharedValidationOptions(options.validation ?? options, options.signal);
|
|
1048
|
+
if (options.signal?.aborted) {
|
|
1049
|
+
const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
|
|
1050
|
+
return failure(error);
|
|
1051
|
+
}
|
|
594
1052
|
const pending = (index, status, error) => {
|
|
595
1053
|
options.onProgress?.({
|
|
596
1054
|
currentChunk: status === "success" ? index + 1 : index,
|
|
@@ -607,77 +1065,175 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
607
1065
|
pending(index, "pending");
|
|
608
1066
|
});
|
|
609
1067
|
const validations = await Promise.all(
|
|
610
|
-
chunks.map(async (chunk) => {
|
|
1068
|
+
chunks.map(async (chunk, index) => {
|
|
611
1069
|
const ssml = typeof chunk === "string" ? chunk : chunk.ssml;
|
|
612
|
-
const
|
|
1070
|
+
const sourceNodePath = typeof chunk === "string" ? options.sourceNodePath : chunk.sourceNodePath ?? options.sourceNodePath;
|
|
1071
|
+
const diagnostics = await Promise.resolve(
|
|
1072
|
+
(0, import_ssml_core2.validateAzureSsml)(ssml, {
|
|
1073
|
+
...validationOptions,
|
|
1074
|
+
...sourceNodePath ? { sourceNodePath } : {},
|
|
1075
|
+
chunkIndex: index
|
|
1076
|
+
})
|
|
1077
|
+
);
|
|
613
1078
|
return diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
614
1079
|
})
|
|
615
1080
|
);
|
|
616
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
|
+
}
|
|
617
1086
|
if (firstInvalidIndex >= 0) {
|
|
618
1087
|
const error = new ChunkValidationError(firstInvalidIndex, validations[firstInvalidIndex] ?? []);
|
|
619
1088
|
pending(firstInvalidIndex, "failed", error);
|
|
620
|
-
return
|
|
1089
|
+
return failure(error);
|
|
621
1090
|
}
|
|
622
1091
|
try {
|
|
623
1092
|
if (client.synthesizeChunks) {
|
|
624
|
-
const
|
|
1093
|
+
const normalizedChunks = chunks.map((chunk) => {
|
|
1094
|
+
if (typeof chunk === "string" || chunk.sourceNodePath || !options.sourceNodePath) return chunk;
|
|
1095
|
+
return { ...chunk, sourceNodePath: [...options.sourceNodePath] };
|
|
1096
|
+
});
|
|
1097
|
+
const value = await client.synthesizeChunks(normalizedChunks, {
|
|
1098
|
+
onProgress: options.onProgress,
|
|
1099
|
+
outputFormat: options.outputFormat,
|
|
1100
|
+
signal: options.signal,
|
|
1101
|
+
timeoutMs: options.timeoutMs,
|
|
1102
|
+
sourceNodePath: options.sourceNodePath,
|
|
1103
|
+
concurrency: options.concurrency,
|
|
1104
|
+
retryOptions: options.retryOptions
|
|
1105
|
+
});
|
|
625
1106
|
return { ok: true, success: true, status: "success", value };
|
|
626
1107
|
}
|
|
627
|
-
const results =
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
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
|
+
}
|
|
668
1211
|
}
|
|
669
|
-
}
|
|
1212
|
+
};
|
|
1213
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, chunks.length) }, () => worker()));
|
|
1214
|
+
const orderedResults = results.filter((result) => result !== void 0);
|
|
670
1215
|
return {
|
|
671
1216
|
ok: true,
|
|
672
1217
|
success: true,
|
|
673
1218
|
status: "success",
|
|
674
|
-
value: mergeSynthesisResults(
|
|
1219
|
+
value: mergeSynthesisResults(orderedResults, {
|
|
1220
|
+
format: options.outputFormat ?? "audio-16khz-128kbitrate-mono-mp3",
|
|
1221
|
+
signal: options.signal
|
|
1222
|
+
})
|
|
675
1223
|
};
|
|
676
1224
|
} catch (error) {
|
|
677
|
-
const
|
|
678
|
-
return
|
|
1225
|
+
const synthesisError = toSynthesisError(error);
|
|
1226
|
+
return failure(synthesisError);
|
|
679
1227
|
}
|
|
680
1228
|
}
|
|
1229
|
+
function withValidationSignal(options, signal) {
|
|
1230
|
+
if (!signal) return options;
|
|
1231
|
+
return {
|
|
1232
|
+
...options,
|
|
1233
|
+
urlValidatorSignal: signal,
|
|
1234
|
+
urlValidation: { ...options.urlValidation ?? {}, signal }
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
681
1237
|
|
|
682
1238
|
// src/client.ts
|
|
683
1239
|
var ENDPOINT_TEMPLATE = "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
|
|
@@ -694,11 +1250,21 @@ var AzureTtsClient = class {
|
|
|
694
1250
|
const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };
|
|
695
1251
|
return synthesizeSpeech(ssml, config);
|
|
696
1252
|
}
|
|
697
|
-
async synthesizeSsml(ssml) {
|
|
1253
|
+
async synthesizeSsml(ssml, options = {}) {
|
|
698
1254
|
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
699
1255
|
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
700
1256
|
__privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
|
|
701
|
-
return synthesizeSsml(ssml, {
|
|
1257
|
+
return synthesizeSsml(ssml, {
|
|
1258
|
+
endpoint,
|
|
1259
|
+
region,
|
|
1260
|
+
subscriptionKey,
|
|
1261
|
+
outputFormat: options.outputFormat ?? outputFormat,
|
|
1262
|
+
signal: options.signal ?? signal,
|
|
1263
|
+
timeoutMs: options.timeoutMs ?? timeoutMs,
|
|
1264
|
+
sourceNodePath: options.sourceNodePath,
|
|
1265
|
+
sourceTextSegments: options.sourceTextSegments,
|
|
1266
|
+
sourceMarkers: options.sourceMarkers
|
|
1267
|
+
});
|
|
702
1268
|
}
|
|
703
1269
|
async synthesizeChunks(chunks, options = {}) {
|
|
704
1270
|
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
@@ -707,10 +1273,13 @@ var AzureTtsClient = class {
|
|
|
707
1273
|
endpoint,
|
|
708
1274
|
region,
|
|
709
1275
|
subscriptionKey,
|
|
710
|
-
outputFormat,
|
|
711
|
-
signal,
|
|
712
|
-
timeoutMs,
|
|
713
|
-
|
|
1276
|
+
outputFormat: options.outputFormat ?? outputFormat,
|
|
1277
|
+
signal: options.signal ?? signal,
|
|
1278
|
+
timeoutMs: options.timeoutMs ?? timeoutMs,
|
|
1279
|
+
sourceNodePath: options.sourceNodePath,
|
|
1280
|
+
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress,
|
|
1281
|
+
concurrency: options.concurrency ?? __privateGet(this, _options).concurrency,
|
|
1282
|
+
retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions
|
|
714
1283
|
});
|
|
715
1284
|
}
|
|
716
1285
|
async synthesizeSsmlSafe(ssml, options = {}) {
|
|
@@ -720,7 +1289,11 @@ var AzureTtsClient = class {
|
|
|
720
1289
|
return synthesizeSsmlChunksSafe(this, chunks, {
|
|
721
1290
|
...options,
|
|
722
1291
|
outputFormat: options.outputFormat ?? __privateGet(this, _options).outputFormat,
|
|
723
|
-
|
|
1292
|
+
signal: options.signal ?? __privateGet(this, _options).signal,
|
|
1293
|
+
timeoutMs: options.timeoutMs ?? __privateGet(this, _options).timeoutMs,
|
|
1294
|
+
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress,
|
|
1295
|
+
concurrency: options.concurrency ?? __privateGet(this, _options).concurrency,
|
|
1296
|
+
retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions
|
|
724
1297
|
});
|
|
725
1298
|
}
|
|
726
1299
|
async synthesizeSsmlChunksSafe(chunks, options = {}) {
|
|
@@ -817,16 +1390,23 @@ async function fetchAzureVoiceCatalog(options) {
|
|
|
817
1390
|
}
|
|
818
1391
|
// Annotate the CommonJS export names for ESM import in node:
|
|
819
1392
|
0 && (module.exports = {
|
|
1393
|
+
AudioFormatMismatchError,
|
|
820
1394
|
AzureTtsClient,
|
|
821
1395
|
AzureTtsError,
|
|
822
1396
|
AzureTtsSdkError,
|
|
823
1397
|
ChunkValidationError,
|
|
1398
|
+
DEFAULT_OUTPUT_FORMAT,
|
|
1399
|
+
MergeError,
|
|
1400
|
+
SynthesisCancelledError,
|
|
1401
|
+
SynthesisTimeoutError,
|
|
824
1402
|
UnsupportedMergeFormatError,
|
|
825
1403
|
canMergeAudioFormat,
|
|
826
1404
|
fetchAzureVoiceCatalog,
|
|
1405
|
+
inspectAudioSpecification,
|
|
827
1406
|
mergeAudioBuffers,
|
|
828
1407
|
mergeSynthesisResults,
|
|
829
1408
|
resolveMergeAudioFormat,
|
|
1409
|
+
resolveMimeType,
|
|
830
1410
|
synthesizeSpeech,
|
|
831
1411
|
synthesizeSsml,
|
|
832
1412
|
synthesizeSsmlChunks,
|