ssml-builder-js 2.18.0 → 2.19.1
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 +4 -0
- package/dist/{index.d-B2WddTa4.d.mts → index.d-4kqVuH29.d.mts} +1 -1
- package/dist/{index.d-B2WddTa4.d.ts → index.d-4kqVuH29.d.ts} +1 -1
- package/dist/index.d.mts +156 -105
- package/dist/index.d.ts +156 -105
- package/dist/index.js +453 -63
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +450 -63
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +45 -24
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +45 -24
- package/dist/react.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -93,6 +93,15 @@ var SynthesisTimeoutError = class extends Error {
|
|
|
93
93
|
this.name = "SynthesisTimeoutError";
|
|
94
94
|
}
|
|
95
95
|
};
|
|
96
|
+
var IncompleteChunkSetError = class extends Error {
|
|
97
|
+
constructor(totalChunks, missingChunkIndices) {
|
|
98
|
+
super(`Cannot merge an incomplete chunk set; missing chunk indices: ${missingChunkIndices.join(", ")}.`);
|
|
99
|
+
this.kind = "incomplete-chunk-set";
|
|
100
|
+
this.name = "IncompleteChunkSetError";
|
|
101
|
+
this.totalChunks = totalChunks;
|
|
102
|
+
this.missingChunkIndices = [...missingChunkIndices];
|
|
103
|
+
}
|
|
104
|
+
};
|
|
96
105
|
var MergeError = class extends Error {
|
|
97
106
|
constructor(message, cause) {
|
|
98
107
|
super(message);
|
|
@@ -117,8 +126,32 @@ var UnsupportedMergeFormatError = class extends Error {
|
|
|
117
126
|
this.format = format;
|
|
118
127
|
}
|
|
119
128
|
};
|
|
129
|
+
function serializeChunkError(error, phase, isOriginalFailure) {
|
|
130
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
131
|
+
const status = error instanceof AzureTtsError ? error.status : void 0;
|
|
132
|
+
const kind = error && typeof error === "object" && "kind" in error ? String(error.kind) : "";
|
|
133
|
+
const code = kind === "validation-error" ? "VALIDATION_ERROR" : kind === "timeout" || /tim(?:e|ed) ?out|deadline/i.test(message) ? "TIMEOUT" : kind === "cancelled" || /cancel|abort/i.test(message) ? "CANCELLED" : kind === "audio-format-mismatch" || kind === "unsupported-format-error" ? "FORMAT_MISMATCH" : kind === "merge-error" || phase === "merge" ? "MERGE_ERROR" : "AZURE_API_ERROR";
|
|
134
|
+
const details = {};
|
|
135
|
+
if (error instanceof AzureTtsError) {
|
|
136
|
+
details.statusText = error.statusText;
|
|
137
|
+
if (error.requestId) details.requestId = error.requestId;
|
|
138
|
+
}
|
|
139
|
+
if (error instanceof IncompleteChunkSetError) {
|
|
140
|
+
details.totalChunks = error.totalChunks;
|
|
141
|
+
details.missingChunkIndices = [...error.missingChunkIndices];
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
code,
|
|
145
|
+
phase,
|
|
146
|
+
message,
|
|
147
|
+
isOriginalFailure,
|
|
148
|
+
isRetryable: code === "AZURE_API_ERROR" && (status === 429 || status !== void 0 && status >= 500 || /network|connection|connect|socket|fetch failed|econn|etimedout|temporar|transient|unavailable/i.test(message)),
|
|
149
|
+
...status !== void 0 && status > 0 ? { httpStatus: status } : {},
|
|
150
|
+
...Object.keys(details).length > 0 ? { details } : {}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
120
153
|
function toSynthesisError(error) {
|
|
121
|
-
if (error instanceof AzureTtsError || error instanceof MergeError || error instanceof AudioFormatMismatchError || error instanceof UnsupportedMergeFormatError || error instanceof SynthesisCancelledError || error instanceof SynthesisTimeoutError)
|
|
154
|
+
if (error instanceof AzureTtsError || error instanceof MergeError || error instanceof AudioFormatMismatchError || error instanceof UnsupportedMergeFormatError || error instanceof SynthesisCancelledError || error instanceof SynthesisTimeoutError || error instanceof IncompleteChunkSetError)
|
|
122
155
|
return error;
|
|
123
156
|
const message = error instanceof Error ? error.message : String(error);
|
|
124
157
|
if (/cancel|abort/i.test(message)) return new SynthesisCancelledError(message);
|
|
@@ -130,6 +163,55 @@ function createSpeechSdkError(error) {
|
|
|
130
163
|
return new AzureTtsSdkError(message);
|
|
131
164
|
}
|
|
132
165
|
|
|
166
|
+
// packages/azure-tts-client/src/deadline.ts
|
|
167
|
+
var _controller, _parent, _onParentAbort, _timer, _timedOut;
|
|
168
|
+
var DeadlineController = class {
|
|
169
|
+
constructor(totalJobMs, parent) {
|
|
170
|
+
__privateAdd(this, _controller, new AbortController());
|
|
171
|
+
__privateAdd(this, _parent);
|
|
172
|
+
__privateAdd(this, _onParentAbort);
|
|
173
|
+
__privateAdd(this, _timer);
|
|
174
|
+
__privateAdd(this, _timedOut, false);
|
|
175
|
+
__privateSet(this, _parent, parent);
|
|
176
|
+
__privateSet(this, _onParentAbort, () => __privateGet(this, _controller).abort());
|
|
177
|
+
this.deadlineAtMs = totalJobMs !== void 0 && totalJobMs > 0 ? Date.now() + totalJobMs : void 0;
|
|
178
|
+
this.signal = this.deadlineAtMs === void 0 && parent ? parent : __privateGet(this, _controller).signal;
|
|
179
|
+
if (parent?.aborted) __privateGet(this, _controller).abort();
|
|
180
|
+
parent?.addEventListener("abort", __privateGet(this, _onParentAbort), { once: true });
|
|
181
|
+
if (this.deadlineAtMs !== void 0) {
|
|
182
|
+
__privateSet(this, _timer, setTimeout(
|
|
183
|
+
() => {
|
|
184
|
+
__privateSet(this, _timedOut, true);
|
|
185
|
+
__privateGet(this, _controller).abort();
|
|
186
|
+
},
|
|
187
|
+
Math.max(0, this.deadlineAtMs - Date.now())
|
|
188
|
+
));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
get timedOut() {
|
|
192
|
+
return __privateGet(this, _timedOut) || this.deadlineAtMs !== void 0 && this.remainingMs <= 0;
|
|
193
|
+
}
|
|
194
|
+
get remainingMs() {
|
|
195
|
+
return this.deadlineAtMs === void 0 ? Number.POSITIVE_INFINITY : Math.max(0, this.deadlineAtMs - Date.now());
|
|
196
|
+
}
|
|
197
|
+
throwIfExpired() {
|
|
198
|
+
if (this.timedOut) throw new SynthesisTimeoutError("Speech synthesis exceeded the total job deadline.");
|
|
199
|
+
if (this.signal.aborted) throw new Error("Speech synthesis was cancelled.");
|
|
200
|
+
}
|
|
201
|
+
abort() {
|
|
202
|
+
__privateGet(this, _controller).abort();
|
|
203
|
+
}
|
|
204
|
+
dispose() {
|
|
205
|
+
if (__privateGet(this, _timer)) clearTimeout(__privateGet(this, _timer));
|
|
206
|
+
__privateGet(this, _parent)?.removeEventListener("abort", __privateGet(this, _onParentAbort));
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
_controller = new WeakMap();
|
|
210
|
+
_parent = new WeakMap();
|
|
211
|
+
_onParentAbort = new WeakMap();
|
|
212
|
+
_timer = new WeakMap();
|
|
213
|
+
_timedOut = new WeakMap();
|
|
214
|
+
|
|
133
215
|
// packages/azure-tts-client/src/synthesis.ts
|
|
134
216
|
import * as SpeechSDK2 from "microsoft-cognitiveservices-speech-sdk";
|
|
135
217
|
|
|
@@ -211,18 +293,23 @@ function createSpeechConfig(config) {
|
|
|
211
293
|
}
|
|
212
294
|
|
|
213
295
|
// packages/azure-tts-client/src/synthesis.ts
|
|
214
|
-
function computeChunkFingerprint(ssml, outputFormat = DEFAULT_OUTPUT_FORMAT) {
|
|
296
|
+
function computeChunkFingerprint(ssml, outputFormat = DEFAULT_OUTPUT_FORMAT, options = {}) {
|
|
215
297
|
const readAttribute = (name) => {
|
|
216
298
|
const pattern = new RegExp(`(?:${name})\\s*=\\s*[\\"']([^\\"']*)`, "gi");
|
|
217
299
|
return [...ssml.matchAll(pattern)].map((match) => match[1] ?? "").join("|");
|
|
218
300
|
};
|
|
301
|
+
const headers = Object.fromEntries(
|
|
302
|
+
Object.entries(options.customHeaders ?? {}).sort(([first], [second]) => first.localeCompare(second))
|
|
303
|
+
);
|
|
219
304
|
const payload = JSON.stringify({
|
|
220
305
|
ssml,
|
|
221
306
|
outputFormat,
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
307
|
+
region: options.region ?? "",
|
|
308
|
+
endpoint: options.endpoint ?? "",
|
|
309
|
+
voice: options.voice ?? readAttribute("(?:name|voice)"),
|
|
310
|
+
lang: options.lang ?? readAttribute("(?:xml:lang|lang)"),
|
|
311
|
+
customHeaders: headers,
|
|
312
|
+
fingerprintSchemaVersion: options.fingerprintSchemaVersion ?? options.schemaVersion ?? "2"
|
|
226
313
|
});
|
|
227
314
|
let hash = 0xcbf29ce484222325n;
|
|
228
315
|
const mask = 0xffffffffffffffffn;
|
|
@@ -342,6 +429,129 @@ function parseMp3Specification(buffer, format) {
|
|
|
342
429
|
}
|
|
343
430
|
return void 0;
|
|
344
431
|
}
|
|
432
|
+
function readEbmlVint(bytes, offset, preserveMarker) {
|
|
433
|
+
const first = bytes[offset];
|
|
434
|
+
if (first === void 0) throw new Error("Invalid EBML variable-length integer.");
|
|
435
|
+
let mask = 128;
|
|
436
|
+
let length = 1;
|
|
437
|
+
while (length <= 8 && (first & mask) === 0) {
|
|
438
|
+
mask >>= 1;
|
|
439
|
+
length += 1;
|
|
440
|
+
}
|
|
441
|
+
if (length > 8 || offset + length > bytes.byteLength) throw new Error("Truncated EBML variable-length integer.");
|
|
442
|
+
let value = preserveMarker ? first : first & mask - 1;
|
|
443
|
+
for (let index = 1; index < length; index += 1) value = value * 256 + (bytes[offset + index] ?? 0);
|
|
444
|
+
if (!preserveMarker && value === 2 ** (7 * length) - 1)
|
|
445
|
+
throw new Error("EBML unknown-size elements are not supported.");
|
|
446
|
+
return { value, length };
|
|
447
|
+
}
|
|
448
|
+
function readEbmlElement(bytes, offset) {
|
|
449
|
+
const id = readEbmlVint(bytes, offset, true);
|
|
450
|
+
const size = readEbmlVint(bytes, offset + id.length, false);
|
|
451
|
+
const dataStart = offset + id.length + size.length;
|
|
452
|
+
const dataEnd = dataStart + size.value;
|
|
453
|
+
if (dataEnd > bytes.byteLength) throw new Error("EBML element exceeds the audio buffer.");
|
|
454
|
+
return { id: id.value, dataStart, dataEnd };
|
|
455
|
+
}
|
|
456
|
+
function ebmlText(bytes, element) {
|
|
457
|
+
return new TextDecoder().decode(bytes.slice(element.dataStart, element.dataEnd));
|
|
458
|
+
}
|
|
459
|
+
function findEbmlElement(bytes, start, end, id) {
|
|
460
|
+
let offset = start;
|
|
461
|
+
while (offset < end) {
|
|
462
|
+
const element = readEbmlElement(bytes, offset);
|
|
463
|
+
if (element.id === id) return element;
|
|
464
|
+
offset = element.dataEnd;
|
|
465
|
+
}
|
|
466
|
+
if (offset !== end) throw new Error("Invalid EBML element boundary.");
|
|
467
|
+
return void 0;
|
|
468
|
+
}
|
|
469
|
+
function parseOggSpecification(buffer, format) {
|
|
470
|
+
const bytes = new Uint8Array(buffer);
|
|
471
|
+
let offset = 0;
|
|
472
|
+
let firstPayload;
|
|
473
|
+
let pages = 0;
|
|
474
|
+
while (offset < bytes.byteLength) {
|
|
475
|
+
if (offset + 27 > bytes.byteLength || !ascii(bytes, offset, "OggS")) throw new Error("Invalid Ogg page header.");
|
|
476
|
+
if (bytes[offset + 4] !== 0) throw new Error("Unsupported Ogg bitstream version.");
|
|
477
|
+
const segmentCount = bytes[offset + 26] ?? 0;
|
|
478
|
+
const lacingStart = offset + 27;
|
|
479
|
+
const payloadStart = lacingStart + segmentCount;
|
|
480
|
+
if (payloadStart > bytes.byteLength) throw new Error("Truncated Ogg segment table.");
|
|
481
|
+
const payloadLength = bytes.slice(lacingStart, payloadStart).reduce((total, value) => total + value, 0);
|
|
482
|
+
const pageEnd = payloadStart + payloadLength;
|
|
483
|
+
if (pageEnd > bytes.byteLength) throw new Error("Ogg page payload exceeds the audio buffer.");
|
|
484
|
+
if (pages === 0) firstPayload = bytes.slice(payloadStart, pageEnd);
|
|
485
|
+
offset = pageEnd;
|
|
486
|
+
pages += 1;
|
|
487
|
+
}
|
|
488
|
+
if (pages === 0 || !firstPayload || !ascii(firstPayload, 0, "OpusHead") || firstPayload.byteLength < 19)
|
|
489
|
+
throw new Error("Ogg audio must contain a valid OpusHead packet.");
|
|
490
|
+
const version = firstPayload[8];
|
|
491
|
+
const channels = firstPayload[9] ?? 0;
|
|
492
|
+
const sampleRate = new DataView(firstPayload.buffer, firstPayload.byteOffset, firstPayload.byteLength).getUint32(
|
|
493
|
+
12,
|
|
494
|
+
true
|
|
495
|
+
);
|
|
496
|
+
if (version !== 1 || channels <= 0 || sampleRate <= 0) throw new Error("Invalid Ogg OpusHead stream parameters.");
|
|
497
|
+
return {
|
|
498
|
+
format,
|
|
499
|
+
mimeType: "audio/ogg",
|
|
500
|
+
codec: "opus",
|
|
501
|
+
sampleRate,
|
|
502
|
+
channels,
|
|
503
|
+
container: "ogg",
|
|
504
|
+
isVbr: true,
|
|
505
|
+
isCompressed: true
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
function parseWebmSpecification(buffer, format) {
|
|
509
|
+
const bytes = new Uint8Array(buffer);
|
|
510
|
+
const ebml = readEbmlElement(bytes, 0);
|
|
511
|
+
if (ebml.id !== 440786851) throw new Error("WebM audio must begin with an EBML header.");
|
|
512
|
+
const docType = findEbmlElement(bytes, ebml.dataStart, ebml.dataEnd, 17026);
|
|
513
|
+
if (!docType || ebmlText(bytes, docType).toLowerCase() !== "webm") throw new Error("EBML DocType must be webm.");
|
|
514
|
+
const segment = readEbmlElement(bytes, ebml.dataEnd);
|
|
515
|
+
if (segment.id !== 408125543) throw new Error("WebM audio must contain a Segment element.");
|
|
516
|
+
const tracks = findEbmlElement(bytes, segment.dataStart, segment.dataEnd, 374648427);
|
|
517
|
+
if (!tracks) throw new Error("WebM audio must contain a Tracks element.");
|
|
518
|
+
let offset = tracks.dataStart;
|
|
519
|
+
let opusTrack;
|
|
520
|
+
while (offset < tracks.dataEnd) {
|
|
521
|
+
const track = readEbmlElement(bytes, offset);
|
|
522
|
+
if (track.id === 174) {
|
|
523
|
+
const codec = findEbmlElement(bytes, track.dataStart, track.dataEnd, 134);
|
|
524
|
+
const trackType = findEbmlElement(bytes, track.dataStart, track.dataEnd, 131);
|
|
525
|
+
if (codec && ebmlText(bytes, codec) === "A_OPUS" && trackType && bytes[trackType.dataStart] === 2) {
|
|
526
|
+
opusTrack = track;
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
offset = track.dataEnd;
|
|
531
|
+
}
|
|
532
|
+
if (!opusTrack) throw new Error("WebM tracks do not define an Opus audio track.");
|
|
533
|
+
const audio = findEbmlElement(bytes, opusTrack.dataStart, opusTrack.dataEnd, 225);
|
|
534
|
+
const sampling = audio ? findEbmlElement(bytes, audio.dataStart, audio.dataEnd, 181) : void 0;
|
|
535
|
+
const channels = audio ? findEbmlElement(bytes, audio.dataStart, audio.dataEnd, 159) : void 0;
|
|
536
|
+
const sampleRate = sampling ? new DataView(
|
|
537
|
+
bytes.buffer,
|
|
538
|
+
bytes.byteOffset + sampling.dataStart,
|
|
539
|
+
sampling.dataEnd - sampling.dataStart
|
|
540
|
+
).getFloat64(0, false) : 0;
|
|
541
|
+
const channelCount = channels ? bytes[channels.dataEnd - 1] ?? 0 : 0;
|
|
542
|
+
if (!Number.isFinite(sampleRate) || sampleRate <= 0 || channelCount <= 0)
|
|
543
|
+
throw new Error("WebM Opus audio track has invalid sampling or channel parameters.");
|
|
544
|
+
return {
|
|
545
|
+
format,
|
|
546
|
+
mimeType: "audio/webm",
|
|
547
|
+
codec: "opus",
|
|
548
|
+
sampleRate: Math.round(sampleRate),
|
|
549
|
+
channels: channelCount,
|
|
550
|
+
container: "webm",
|
|
551
|
+
isVbr: true,
|
|
552
|
+
isCompressed: true
|
|
553
|
+
};
|
|
554
|
+
}
|
|
345
555
|
function inspectAudioSpecification(buffer, format) {
|
|
346
556
|
if (isWavFormat(format) || ascii(new Uint8Array(buffer), 0, "RIFF")) {
|
|
347
557
|
const parsed = parseWav(buffer);
|
|
@@ -366,7 +576,20 @@ function inspectAudioSpecification(buffer, format) {
|
|
|
366
576
|
isCompressed: codec !== "pcm" && codec !== "unknown"
|
|
367
577
|
};
|
|
368
578
|
}
|
|
369
|
-
if (isMp3Format(format))
|
|
579
|
+
if (isMp3Format(format)) {
|
|
580
|
+
const specification2 = parseMp3Specification(buffer, format);
|
|
581
|
+
return specification2 ?? formatAudioSpecification(format);
|
|
582
|
+
}
|
|
583
|
+
if (isOggFormat(format) || ascii(new Uint8Array(buffer), 0, "OggS")) {
|
|
584
|
+
const specification2 = parseOggSpecification(buffer, format);
|
|
585
|
+
validateContainerFormat(specification2, format);
|
|
586
|
+
return specification2;
|
|
587
|
+
}
|
|
588
|
+
if (isWebmFormat(format) || new Uint8Array(buffer)[0] === 26) {
|
|
589
|
+
const specification2 = parseWebmSpecification(buffer, format);
|
|
590
|
+
validateContainerFormat(specification2, format);
|
|
591
|
+
return specification2;
|
|
592
|
+
}
|
|
370
593
|
const specification = formatAudioSpecification(format);
|
|
371
594
|
if (specification.container === "raw") validateRawAudioBuffer(buffer, specification);
|
|
372
595
|
return specification;
|
|
@@ -375,7 +598,20 @@ function validateRawAudioBuffer(buffer, specification) {
|
|
|
375
598
|
if (specification.sampleRate <= 0 || specification.channels <= 0 || specification.bitDepth === void 0) {
|
|
376
599
|
throw new Error(`RAW audio format "${specification.format}" does not define a complete audio specification.`);
|
|
377
600
|
}
|
|
378
|
-
if (specification.codec === "siren"
|
|
601
|
+
if (specification.codec === "siren") return;
|
|
602
|
+
if (specification.codec === "silk") {
|
|
603
|
+
if (buffer.byteLength <= 9 || !ascii(new Uint8Array(buffer), 0, "#!SILK_V3"))
|
|
604
|
+
throw new Error("RAW SILK audio must contain a valid #!SILK_V3 payload header.");
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
if (specification.codec === "opus" && buffer.byteLength === 0) throw new Error("RAW Opus audio cannot be empty.");
|
|
608
|
+
if (specification.codec === "opus") {
|
|
609
|
+
const packetCode = new Uint8Array(buffer)[0] ?? 0;
|
|
610
|
+
const frameCountCode = packetCode & 3;
|
|
611
|
+
if (packetCode >> 3 > 31 || buffer.byteLength < (frameCountCode === 3 ? 2 : 2) || frameCountCode === 3 && ((new Uint8Array(buffer)[1] ?? 0) & 63) === 0)
|
|
612
|
+
throw new Error("RAW Opus audio has an invalid packet framing header.");
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
379
615
|
const bytesPerFrame = specification.channels * Math.ceil(specification.bitDepth / 8);
|
|
380
616
|
if (bytesPerFrame <= 0 || buffer.byteLength % bytesPerFrame !== 0) {
|
|
381
617
|
throw new Error(
|
|
@@ -383,6 +619,15 @@ function validateRawAudioBuffer(buffer, specification) {
|
|
|
383
619
|
);
|
|
384
620
|
}
|
|
385
621
|
}
|
|
622
|
+
function validateContainerFormat(specification, format) {
|
|
623
|
+
const expected = formatAudioSpecification(format);
|
|
624
|
+
if (expected.sampleRate > 0 && specification.sampleRate !== expected.sampleRate || expected.channels > 0 && specification.channels !== expected.channels || expected.codec !== "unknown" && specification.codec !== expected.codec) {
|
|
625
|
+
throw new AudioFormatMismatchError(`Audio container does not match the requested format "${format}".`, [
|
|
626
|
+
expected,
|
|
627
|
+
specification
|
|
628
|
+
]);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
386
631
|
function validateAudioSpecifications(specs) {
|
|
387
632
|
const first = specs[0];
|
|
388
633
|
if (!first) return;
|
|
@@ -464,17 +709,29 @@ function stripMp3Tags(buffer) {
|
|
|
464
709
|
function isMp3Format(format) {
|
|
465
710
|
return /(?:mp3|mpeg)/i.test(format);
|
|
466
711
|
}
|
|
712
|
+
function isOggFormat(format) {
|
|
713
|
+
return /ogg/i.test(format);
|
|
714
|
+
}
|
|
715
|
+
function isWebmFormat(format) {
|
|
716
|
+
return /webm/i.test(format);
|
|
717
|
+
}
|
|
467
718
|
function isWavFormat(format) {
|
|
468
719
|
return /(?:wav|wave|riff)/i.test(format);
|
|
469
720
|
}
|
|
470
721
|
function isRawFormat(format) {
|
|
471
722
|
return /^raw(?:-|$)/i.test(format);
|
|
472
723
|
}
|
|
473
|
-
function validateMergedAudioBuffer(merged, format, buffers, inputSpecs, outputMimeType) {
|
|
724
|
+
function validateMergedAudioBuffer(merged, format, buffers, inputSpecs, outputMimeType, allowExternalContainer) {
|
|
474
725
|
if (!(merged instanceof ArrayBuffer) || merged.byteLength === 0) {
|
|
475
726
|
throw new MergeError("The custom audio merger returned an empty or invalid audio buffer.");
|
|
476
727
|
}
|
|
477
|
-
|
|
728
|
+
let specification;
|
|
729
|
+
try {
|
|
730
|
+
specification = inspectAudioSpecification(merged, format);
|
|
731
|
+
} catch (error) {
|
|
732
|
+
if (!allowExternalContainer) throw error;
|
|
733
|
+
specification = inputSpecs[0] ?? formatAudioSpecification(format);
|
|
734
|
+
}
|
|
478
735
|
if (!outputMimeType.trim()) throw new MergeError("The custom audio merger output MIME type cannot be empty.");
|
|
479
736
|
const firstInput = inputSpecs[0];
|
|
480
737
|
if (firstInput && (specification.sampleRate !== firstInput.sampleRate || specification.channels !== firstInput.channels || specification.codec !== firstInput.codec || firstInput.bitDepth !== void 0 && specification.bitDepth !== firstInput.bitDepth)) {
|
|
@@ -494,6 +751,25 @@ function validateMergedAudioBuffer(merged, format, buffers, inputSpecs, outputMi
|
|
|
494
751
|
}
|
|
495
752
|
return specification;
|
|
496
753
|
}
|
|
754
|
+
async function withinDeadline(value, deadline) {
|
|
755
|
+
if (!deadline) return value;
|
|
756
|
+
deadline.throwIfExpired();
|
|
757
|
+
if (!Number.isFinite(deadline.remainingMs)) return value;
|
|
758
|
+
let timer;
|
|
759
|
+
try {
|
|
760
|
+
return await Promise.race([
|
|
761
|
+
Promise.resolve(value),
|
|
762
|
+
new Promise((_resolve, reject) => {
|
|
763
|
+
timer = setTimeout(
|
|
764
|
+
() => reject(new SynthesisTimeoutError("Speech synthesis exceeded the total job deadline.")),
|
|
765
|
+
deadline.remainingMs
|
|
766
|
+
);
|
|
767
|
+
})
|
|
768
|
+
]);
|
|
769
|
+
} finally {
|
|
770
|
+
if (timer) clearTimeout(timer);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
497
773
|
function resolveMergeAudioFormat(format) {
|
|
498
774
|
if (isWavFormat(format)) return "wav";
|
|
499
775
|
if (isMp3Format(format)) return "mp3";
|
|
@@ -506,6 +782,7 @@ function canMergeAudioFormat(format) {
|
|
|
506
782
|
function mergeAudioBuffers(buffers, options) {
|
|
507
783
|
const format = typeof options === "string" ? options : options?.format;
|
|
508
784
|
if (!format) throw new UnsupportedMergeFormatError("");
|
|
785
|
+
if (!canMergeAudioFormat(format)) throw new UnsupportedMergeFormatError(format);
|
|
509
786
|
try {
|
|
510
787
|
validateAudioSpecifications(buffers.map((buffer) => inspectAudioSpecification(buffer, format)));
|
|
511
788
|
if (isWavFormat(format)) return mergeWavBuffers(buffers);
|
|
@@ -814,10 +1091,23 @@ async function synthesizeWithRetry(ssml, config, retryOptions, onRetry, deadline
|
|
|
814
1091
|
}
|
|
815
1092
|
}
|
|
816
1093
|
async function synthesizeSsml(ssml, config) {
|
|
817
|
-
const
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1094
|
+
const deadline = new DeadlineController(config.timeouts?.totalJobMs, config.signal);
|
|
1095
|
+
try {
|
|
1096
|
+
deadline.throwIfExpired();
|
|
1097
|
+
const synthesisConfig = {
|
|
1098
|
+
...config,
|
|
1099
|
+
signal: deadline.signal,
|
|
1100
|
+
timeouts: config.timeouts ? { ...config.timeouts, totalJobMs: void 0 } : void 0
|
|
1101
|
+
};
|
|
1102
|
+
const result = config.retryOptions ? await synthesizeWithRetry(ssml, synthesisConfig, config.retryOptions, () => void 0, deadline.deadlineAtMs) : await synthesizeSsmlOnce(ssml, synthesisConfig);
|
|
1103
|
+
deadline.throwIfExpired();
|
|
1104
|
+
return result;
|
|
1105
|
+
} catch (error) {
|
|
1106
|
+
if (deadline.timedOut) throw new SynthesisTimeoutError("Speech synthesis exceeded the total job deadline.");
|
|
1107
|
+
throw error;
|
|
1108
|
+
} finally {
|
|
1109
|
+
deadline.dispose();
|
|
1110
|
+
}
|
|
821
1111
|
}
|
|
822
1112
|
function createAbortScope(parent, timeoutMs) {
|
|
823
1113
|
const controller = new AbortController();
|
|
@@ -854,7 +1144,12 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
854
1144
|
const totalChunks = chunks.length;
|
|
855
1145
|
const inputs = chunks.map((chunk) => typeof chunk === "string" ? { ssml: chunk } : chunk);
|
|
856
1146
|
const fingerprints = inputs.map(
|
|
857
|
-
(chunk) => computeChunkFingerprint(chunk.ssml, config.outputFormat ?? DEFAULT_OUTPUT_FORMAT
|
|
1147
|
+
(chunk) => computeChunkFingerprint(chunk.ssml, config.outputFormat ?? DEFAULT_OUTPUT_FORMAT, {
|
|
1148
|
+
region: config.region,
|
|
1149
|
+
endpoint: config.endpoint,
|
|
1150
|
+
customHeaders: config.customHeaders,
|
|
1151
|
+
fingerprintSchemaVersion: config.fingerprintSchemaVersion
|
|
1152
|
+
})
|
|
858
1153
|
);
|
|
859
1154
|
const results = new Array(totalChunks);
|
|
860
1155
|
const cachedChunks = new Map((config.resumeChunks ?? []).map((chunk) => [chunk.chunkIndex, chunk]));
|
|
@@ -878,6 +1173,7 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
878
1173
|
const shouldSynthesize = (index) => (!cachedChunks.has(index) || invalidCachedIndices.has(index)) && (requestedIndices === void 0 || requestedIndices.has(index) || invalidCachedIndices.has(index));
|
|
879
1174
|
const jobStartedAt = Date.now();
|
|
880
1175
|
const jobDeadlineAt = config.timeouts?.totalJobMs !== void 0 && config.timeouts.totalJobMs > 0 ? jobStartedAt + config.timeouts.totalJobMs : void 0;
|
|
1176
|
+
const deadline = new DeadlineController(config.timeouts?.totalJobMs, config.signal);
|
|
881
1177
|
const scope = createAbortScope(config.signal, config.timeouts?.totalJobMs);
|
|
882
1178
|
const report = (event) => config.onProgress?.(event);
|
|
883
1179
|
for (const [index, input] of inputs.entries()) {
|
|
@@ -918,7 +1214,7 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
918
1214
|
input.ssml,
|
|
919
1215
|
{
|
|
920
1216
|
...config,
|
|
921
|
-
signal:
|
|
1217
|
+
signal: deadline.signal,
|
|
922
1218
|
...input.originalTextRange ? { sourceTextRange: input.originalTextRange } : {},
|
|
923
1219
|
...input.sourceNodePath ?? config.sourceNodePath ? { sourceNodePath: [...input.sourceNodePath ?? config.sourceNodePath ?? []] } : {},
|
|
924
1220
|
...input.sourceTextSegments ? { sourceTextSegments: input.sourceTextSegments } : {},
|
|
@@ -963,7 +1259,7 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
963
1259
|
status: wasCancelled ? "cancelled" : "failed",
|
|
964
1260
|
isOriginalFailure: !wasCancelled,
|
|
965
1261
|
canResume: true,
|
|
966
|
-
error
|
|
1262
|
+
error: serializeChunkError(error, "synthesis", !wasCancelled)
|
|
967
1263
|
};
|
|
968
1264
|
report({
|
|
969
1265
|
currentChunk: completed,
|
|
@@ -983,13 +1279,19 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
983
1279
|
try {
|
|
984
1280
|
await Promise.all(Array.from({ length: Math.min(concurrency, chunks.length) }, () => worker()));
|
|
985
1281
|
if (firstError) throw firstError;
|
|
1282
|
+
const missingChunkIndices = Array.from(
|
|
1283
|
+
{ length: totalChunks },
|
|
1284
|
+
(_value, index) => results[index] === void 0 ? index : void 0
|
|
1285
|
+
).filter((index) => index !== void 0);
|
|
1286
|
+
if (missingChunkIndices.length > 0) throw new IncompleteChunkSetError(totalChunks, missingChunkIndices);
|
|
986
1287
|
const orderedResults = results.filter((result) => result !== void 0);
|
|
987
1288
|
return await mergeSynthesisResults(orderedResults, {
|
|
988
1289
|
format: config.outputFormat ?? DEFAULT_OUTPUT_FORMAT,
|
|
989
|
-
signal:
|
|
1290
|
+
signal: deadline.signal,
|
|
990
1291
|
customMerger: config.customMerger,
|
|
991
1292
|
outputMimeType: config.outputMimeType,
|
|
992
|
-
postMergeValidator: config.postMergeValidator
|
|
1293
|
+
postMergeValidator: config.postMergeValidator,
|
|
1294
|
+
deadline
|
|
993
1295
|
});
|
|
994
1296
|
} catch (error) {
|
|
995
1297
|
if (firstError && config.cancelOnFailure !== false) {
|
|
@@ -1017,6 +1319,7 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
1017
1319
|
throw error;
|
|
1018
1320
|
} finally {
|
|
1019
1321
|
scope.dispose();
|
|
1322
|
+
deadline.dispose();
|
|
1020
1323
|
}
|
|
1021
1324
|
}
|
|
1022
1325
|
function createMergedResult(results, audioData, format, audioSpec, outputMimeType) {
|
|
@@ -1094,19 +1397,33 @@ function mergeSynthesisResults(results, options) {
|
|
|
1094
1397
|
const format = resolvedOptions?.format;
|
|
1095
1398
|
if (!format) throw new UnsupportedMergeFormatError("");
|
|
1096
1399
|
const buffers = results.map((result) => result.audioData);
|
|
1097
|
-
const inputSpecs = results.map((result) =>
|
|
1400
|
+
const inputSpecs = results.map((result) => {
|
|
1401
|
+
if (result.audioSpec) return result.audioSpec;
|
|
1402
|
+
try {
|
|
1403
|
+
return inspectAudioSpecification(result.audioData, format);
|
|
1404
|
+
} catch (error) {
|
|
1405
|
+
if (resolvedOptions.customMerger) return formatAudioSpecification(format);
|
|
1406
|
+
throw error;
|
|
1407
|
+
}
|
|
1408
|
+
});
|
|
1098
1409
|
validateAudioSpecifications(inputSpecs);
|
|
1099
|
-
const
|
|
1410
|
+
const deadline = resolvedOptions.deadline;
|
|
1411
|
+
deadline?.throwIfExpired();
|
|
1412
|
+
const signal = resolvedOptions.signal ?? deadline?.signal ?? new AbortController().signal;
|
|
1100
1413
|
if (signal.aborted) throw new SynthesisCancelledError();
|
|
1101
1414
|
if (resolvedOptions.customMerger) {
|
|
1102
|
-
return
|
|
1103
|
-
()
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1415
|
+
return withinDeadline(
|
|
1416
|
+
Promise.resolve().then(
|
|
1417
|
+
() => resolvedOptions.customMerger?.(buffers, {
|
|
1418
|
+
format,
|
|
1419
|
+
outputMimeType: resolvedOptions.outputMimeType ?? resolveMimeType(format),
|
|
1420
|
+
inputSpecs,
|
|
1421
|
+
signal
|
|
1422
|
+
})
|
|
1423
|
+
),
|
|
1424
|
+
deadline
|
|
1109
1425
|
).then((merged) => {
|
|
1426
|
+
deadline?.throwIfExpired();
|
|
1110
1427
|
if (!merged) throw new MergeError("The custom audio merger returned no audio buffer.");
|
|
1111
1428
|
if (signal.aborted) throw new SynthesisCancelledError();
|
|
1112
1429
|
const mergedSpec = validateMergedAudioBuffer(
|
|
@@ -1114,7 +1431,8 @@ function mergeSynthesisResults(results, options) {
|
|
|
1114
1431
|
format,
|
|
1115
1432
|
buffers,
|
|
1116
1433
|
inputSpecs,
|
|
1117
|
-
resolvedOptions.outputMimeType ?? resolveMimeType(format)
|
|
1434
|
+
resolvedOptions.outputMimeType ?? resolveMimeType(format),
|
|
1435
|
+
true
|
|
1118
1436
|
);
|
|
1119
1437
|
const result = createMergedResult(results, merged, format, mergedSpec, resolvedOptions.outputMimeType);
|
|
1120
1438
|
return Promise.resolve(
|
|
@@ -1125,6 +1443,7 @@ function mergeSynthesisResults(results, options) {
|
|
|
1125
1443
|
signal
|
|
1126
1444
|
})
|
|
1127
1445
|
).then((valid) => {
|
|
1446
|
+
deadline?.throwIfExpired();
|
|
1128
1447
|
if (valid === false) throw new MergeError("The post-merge validator rejected the merged audio.");
|
|
1129
1448
|
return result;
|
|
1130
1449
|
});
|
|
@@ -1135,6 +1454,7 @@ function mergeSynthesisResults(results, options) {
|
|
|
1135
1454
|
});
|
|
1136
1455
|
}
|
|
1137
1456
|
try {
|
|
1457
|
+
deadline?.throwIfExpired();
|
|
1138
1458
|
const result = createMergedResult(
|
|
1139
1459
|
results,
|
|
1140
1460
|
mergeAudioBuffers(buffers, { format }),
|
|
@@ -1150,12 +1470,14 @@ function mergeSynthesisResults(results, options) {
|
|
|
1150
1470
|
signal
|
|
1151
1471
|
});
|
|
1152
1472
|
if (validation instanceof Promise)
|
|
1153
|
-
return validation.then((valid) => {
|
|
1473
|
+
return withinDeadline(validation, deadline).then((valid) => {
|
|
1474
|
+
deadline?.throwIfExpired();
|
|
1154
1475
|
if (valid === false) throw new MergeError("The post-merge validator rejected the merged audio.");
|
|
1155
1476
|
return result;
|
|
1156
1477
|
});
|
|
1157
1478
|
if (validation === false) throw new MergeError("The post-merge validator rejected the merged audio.");
|
|
1158
1479
|
}
|
|
1480
|
+
deadline?.throwIfExpired();
|
|
1159
1481
|
return result;
|
|
1160
1482
|
} catch (error) {
|
|
1161
1483
|
if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
|
|
@@ -1300,47 +1622,55 @@ function sharedValidationOptions(options, signal) {
|
|
|
1300
1622
|
};
|
|
1301
1623
|
}
|
|
1302
1624
|
async function synthesizeSsmlSafe(client, ssml, options = {}) {
|
|
1303
|
-
const
|
|
1625
|
+
const deadline = new DeadlineController(options.timeouts?.totalJobMs, options.signal);
|
|
1626
|
+
const validationOptions = sharedValidationOptions(options.validation ?? options, deadline.signal);
|
|
1304
1627
|
const diagnostics = await Promise.resolve(validateAzureSsml2(ssml, validationOptions));
|
|
1305
|
-
if (
|
|
1306
|
-
const error = toSynthesisError(
|
|
1628
|
+
if (deadline.signal.aborted) {
|
|
1629
|
+
const error = toSynthesisError(
|
|
1630
|
+
new Error(deadline.timedOut ? "Speech synthesis timed out." : "Speech synthesis was cancelled.")
|
|
1631
|
+
);
|
|
1632
|
+
deadline.dispose();
|
|
1307
1633
|
return failure(error);
|
|
1308
1634
|
}
|
|
1309
1635
|
const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
1310
1636
|
if (errors.length > 0) {
|
|
1637
|
+
deadline.dispose();
|
|
1311
1638
|
return failure({
|
|
1312
1639
|
kind: "validation-error",
|
|
1313
1640
|
message: "SSML validation failed; the Azure Speech API was not called.",
|
|
1314
1641
|
diagnostics: errors
|
|
1315
1642
|
});
|
|
1316
1643
|
}
|
|
1317
|
-
const jobScope = options.timeouts?.totalJobMs !== void 0 ? createSafeAbortScope(options.signal, options.timeouts.totalJobMs) : void 0;
|
|
1318
1644
|
try {
|
|
1319
1645
|
return {
|
|
1320
1646
|
ok: true,
|
|
1321
1647
|
success: true,
|
|
1322
1648
|
status: "success",
|
|
1323
1649
|
value: await client.synthesizeSsml(ssml, {
|
|
1324
|
-
signal:
|
|
1650
|
+
signal: deadline.signal,
|
|
1325
1651
|
timeoutMs: options.timeouts?.perChunkMs,
|
|
1326
|
-
timeouts: options.timeouts
|
|
1652
|
+
timeouts: options.timeouts ? { ...options.timeouts, totalJobMs: void 0 } : void 0
|
|
1327
1653
|
})
|
|
1328
1654
|
};
|
|
1329
1655
|
} catch (error) {
|
|
1330
|
-
if (
|
|
1656
|
+
if (deadline.timedOut) return failure(toSynthesisError(new Error("Speech synthesis timed out.")));
|
|
1331
1657
|
const synthesisError = toSynthesisError(error);
|
|
1332
1658
|
return failure(synthesisError);
|
|
1333
1659
|
} finally {
|
|
1334
|
-
|
|
1660
|
+
deadline.dispose();
|
|
1335
1661
|
}
|
|
1336
1662
|
}
|
|
1337
1663
|
async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
1664
|
+
const deadline = new DeadlineController(options.timeouts?.totalJobMs, options.signal);
|
|
1338
1665
|
const validationOptions = sharedValidationOptions(
|
|
1339
1666
|
{ ...options.validation ?? options, timeouts: options.timeouts },
|
|
1340
|
-
|
|
1667
|
+
deadline.signal
|
|
1341
1668
|
);
|
|
1342
|
-
if (
|
|
1343
|
-
const error = toSynthesisError(
|
|
1669
|
+
if (deadline.signal.aborted) {
|
|
1670
|
+
const error = toSynthesisError(
|
|
1671
|
+
new Error(deadline.timedOut ? "Speech synthesis timed out." : "Speech synthesis was cancelled.")
|
|
1672
|
+
);
|
|
1673
|
+
deadline.dispose();
|
|
1344
1674
|
return failure(error);
|
|
1345
1675
|
}
|
|
1346
1676
|
const pending = (index, status, error) => {
|
|
@@ -1372,14 +1702,16 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
1372
1702
|
return diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
1373
1703
|
})
|
|
1374
1704
|
);
|
|
1375
|
-
if (
|
|
1705
|
+
if (deadline.signal.aborted) {
|
|
1376
1706
|
const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
|
|
1707
|
+
deadline.dispose();
|
|
1377
1708
|
return failure(error);
|
|
1378
1709
|
}
|
|
1379
1710
|
const chunkDiagnostics = validations.map((diagnostics, chunkIndex) => ({ chunkIndex, diagnostics })).filter((entry) => entry.diagnostics.length > 0);
|
|
1380
1711
|
if (chunkDiagnostics.length > 0) {
|
|
1381
1712
|
const error = new BatchChunkValidationError(chunkDiagnostics);
|
|
1382
1713
|
for (const entry of chunkDiagnostics) pending(entry.chunkIndex, "failed", error);
|
|
1714
|
+
deadline.dispose();
|
|
1383
1715
|
return failure(error);
|
|
1384
1716
|
}
|
|
1385
1717
|
let fallbackJobScope;
|
|
@@ -1392,9 +1724,9 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
1392
1724
|
const value = await client.synthesizeChunks(normalizedChunks, {
|
|
1393
1725
|
onProgress: options.onProgress,
|
|
1394
1726
|
outputFormat: options.outputFormat,
|
|
1395
|
-
signal:
|
|
1727
|
+
signal: deadline.signal,
|
|
1396
1728
|
timeoutMs: options.timeoutMs,
|
|
1397
|
-
timeouts: options.timeouts,
|
|
1729
|
+
timeouts: options.timeouts ? { ...options.timeouts, totalJobMs: void 0 } : void 0,
|
|
1398
1730
|
sourceNodePath: options.sourceNodePath,
|
|
1399
1731
|
concurrency: options.concurrency,
|
|
1400
1732
|
retryOptions: options.retryOptions,
|
|
@@ -1404,12 +1736,19 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
1404
1736
|
customMerger: options.customMerger,
|
|
1405
1737
|
outputMimeType: options.outputMimeType,
|
|
1406
1738
|
postMergeValidator: options.postMergeValidator,
|
|
1407
|
-
resumeValidation: options.resumeValidation
|
|
1739
|
+
resumeValidation: options.resumeValidation,
|
|
1740
|
+
customHeaders: options.customHeaders,
|
|
1741
|
+
fingerprintSchemaVersion: options.fingerprintSchemaVersion
|
|
1408
1742
|
});
|
|
1409
1743
|
return { ok: true, success: true, status: "success", value };
|
|
1410
1744
|
}
|
|
1411
1745
|
const inputs = chunks.map((chunk) => typeof chunk === "string" ? { ssml: chunk } : chunk);
|
|
1412
|
-
const fingerprints = inputs.map(
|
|
1746
|
+
const fingerprints = inputs.map(
|
|
1747
|
+
(chunk) => computeChunkFingerprint(chunk.ssml, options.outputFormat, {
|
|
1748
|
+
customHeaders: options.customHeaders,
|
|
1749
|
+
fingerprintSchemaVersion: options.fingerprintSchemaVersion
|
|
1750
|
+
})
|
|
1751
|
+
);
|
|
1413
1752
|
const results = new Array(chunks.length);
|
|
1414
1753
|
const chunkStates = inputs.map((_chunk, chunkIndex) => ({
|
|
1415
1754
|
chunkIndex,
|
|
@@ -1427,9 +1766,8 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
1427
1766
|
}
|
|
1428
1767
|
const requestedIndices = options.resumeChunkIndices ? new Set(options.resumeChunkIndices.filter((index) => index >= 0 && index < chunks.length)) : void 0;
|
|
1429
1768
|
const shouldSynthesize = (index) => (!cachedChunks.has(index) || invalidCachedIndices.has(index)) && (requestedIndices === void 0 || requestedIndices.has(index) || invalidCachedIndices.has(index));
|
|
1430
|
-
const
|
|
1431
|
-
const
|
|
1432
|
-
const jobScope = chunks.length > 1 || options.timeouts?.totalJobMs !== void 0 ? createSafeAbortScope(options.signal, options.timeouts?.totalJobMs) : void 0;
|
|
1769
|
+
const jobDeadlineAt = deadline.deadlineAtMs;
|
|
1770
|
+
const jobScope = chunks.length > 1 || options.timeouts?.totalJobMs !== void 0 ? createSafeAbortScope(deadline.signal, void 0) : void 0;
|
|
1433
1771
|
fallbackJobScope = jobScope;
|
|
1434
1772
|
const failedIndices = /* @__PURE__ */ new Set();
|
|
1435
1773
|
let firstError;
|
|
@@ -1453,8 +1791,8 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
1453
1791
|
const startedAt = Date.now();
|
|
1454
1792
|
try {
|
|
1455
1793
|
const chunkTimeout = options.timeouts?.chunkWithRetriesMs ?? options.timeouts?.perChunkMs;
|
|
1456
|
-
const chunkScope = chunkTimeout !== void 0 || jobScope ? createSafeAbortScope(jobScope?.signal ??
|
|
1457
|
-
const chunkSignal = chunkScope?.signal ??
|
|
1794
|
+
const chunkScope = chunkTimeout !== void 0 || jobScope ? createSafeAbortScope(jobScope?.signal ?? deadline.signal, chunkTimeout ?? options.timeoutMs) : void 0;
|
|
1795
|
+
const chunkSignal = chunkScope?.signal ?? deadline.signal;
|
|
1458
1796
|
let result;
|
|
1459
1797
|
try {
|
|
1460
1798
|
result = await retryableSynthesis(
|
|
@@ -1481,7 +1819,7 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
1481
1819
|
jobDeadlineAt
|
|
1482
1820
|
);
|
|
1483
1821
|
} catch (error) {
|
|
1484
|
-
if (chunkScope?.timedOut())
|
|
1822
|
+
if (chunkScope?.timedOut() || deadline.timedOut)
|
|
1485
1823
|
throw new Error(`Speech synthesis timed out after ${chunkTimeout ?? options.timeoutMs} ms.`);
|
|
1486
1824
|
throw error;
|
|
1487
1825
|
} finally {
|
|
@@ -1542,15 +1880,15 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
1542
1880
|
durationMs: Date.now() - startedAt
|
|
1543
1881
|
});
|
|
1544
1882
|
} catch (error) {
|
|
1545
|
-
const wasCancelled = firstError !== void 0 || Boolean(jobScope?.signal.aborted && !jobScope?.timedOut());
|
|
1546
|
-
firstError ?? (firstError = error);
|
|
1883
|
+
const wasCancelled = firstError !== void 0 || !deadline.timedOut && Boolean(jobScope?.signal.aborted && !jobScope?.timedOut());
|
|
1884
|
+
firstError ?? (firstError = deadline.timedOut ? new Error("Speech synthesis timed out.") : error);
|
|
1547
1885
|
if (!wasCancelled) failedIndices.add(index);
|
|
1548
1886
|
chunkStates[index] = {
|
|
1549
1887
|
chunkIndex: index,
|
|
1550
1888
|
status: wasCancelled ? "cancelled" : "failed",
|
|
1551
1889
|
isOriginalFailure: !wasCancelled,
|
|
1552
1890
|
canResume: true,
|
|
1553
|
-
error
|
|
1891
|
+
error: serializeChunkError(error, "synthesis", !wasCancelled)
|
|
1554
1892
|
};
|
|
1555
1893
|
options.onProgress?.({
|
|
1556
1894
|
currentChunk: completed,
|
|
@@ -1593,6 +1931,11 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
1593
1931
|
};
|
|
1594
1932
|
throw error;
|
|
1595
1933
|
}
|
|
1934
|
+
const missingChunkIndices = Array.from(
|
|
1935
|
+
{ length: chunks.length },
|
|
1936
|
+
(_value, index) => results[index] === void 0 ? index : void 0
|
|
1937
|
+
).filter((index) => index !== void 0);
|
|
1938
|
+
if (missingChunkIndices.length > 0) throw new IncompleteChunkSetError(chunks.length, missingChunkIndices);
|
|
1596
1939
|
const orderedResults = results.filter((result) => result !== void 0);
|
|
1597
1940
|
return {
|
|
1598
1941
|
ok: true,
|
|
@@ -1600,7 +1943,7 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
1600
1943
|
status: "success",
|
|
1601
1944
|
value: await mergeSynthesisResults(orderedResults, {
|
|
1602
1945
|
format: options.outputFormat ?? "audio-16khz-128kbitrate-mono-mp3",
|
|
1603
|
-
signal: jobScope?.signal ??
|
|
1946
|
+
signal: jobScope?.signal ?? deadline.signal,
|
|
1604
1947
|
customMerger: options.customMerger,
|
|
1605
1948
|
outputMimeType: options.outputMimeType,
|
|
1606
1949
|
postMergeValidator: options.postMergeValidator
|
|
@@ -1611,6 +1954,7 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
1611
1954
|
return failure(synthesisError, partialResultFrom(error));
|
|
1612
1955
|
} finally {
|
|
1613
1956
|
fallbackJobScope?.dispose();
|
|
1957
|
+
deadline.dispose();
|
|
1614
1958
|
}
|
|
1615
1959
|
}
|
|
1616
1960
|
function withValidationSignal(options, signal) {
|
|
@@ -1631,7 +1975,16 @@ var AzureTtsClient = class {
|
|
|
1631
1975
|
__privateSet(this, _options, options);
|
|
1632
1976
|
}
|
|
1633
1977
|
async synthesize(ssml) {
|
|
1634
|
-
const {
|
|
1978
|
+
const {
|
|
1979
|
+
region,
|
|
1980
|
+
subscriptionKey,
|
|
1981
|
+
outputFormat,
|
|
1982
|
+
signal,
|
|
1983
|
+
timeoutMs,
|
|
1984
|
+
timeouts,
|
|
1985
|
+
customHeaders,
|
|
1986
|
+
fingerprintSchemaVersion
|
|
1987
|
+
} = __privateGet(this, _options);
|
|
1635
1988
|
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
1636
1989
|
__privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
|
|
1637
1990
|
const config = {
|
|
@@ -1642,12 +1995,23 @@ var AzureTtsClient = class {
|
|
|
1642
1995
|
signal,
|
|
1643
1996
|
timeoutMs,
|
|
1644
1997
|
timeouts,
|
|
1645
|
-
retryOptions: __privateGet(this, _options).retryOptions
|
|
1998
|
+
retryOptions: __privateGet(this, _options).retryOptions,
|
|
1999
|
+
customHeaders,
|
|
2000
|
+
fingerprintSchemaVersion
|
|
1646
2001
|
};
|
|
1647
2002
|
return synthesizeSpeech(ssml, config);
|
|
1648
2003
|
}
|
|
1649
2004
|
async synthesizeSsml(ssml, options = {}) {
|
|
1650
|
-
const {
|
|
2005
|
+
const {
|
|
2006
|
+
region,
|
|
2007
|
+
subscriptionKey,
|
|
2008
|
+
outputFormat,
|
|
2009
|
+
signal,
|
|
2010
|
+
timeoutMs,
|
|
2011
|
+
timeouts,
|
|
2012
|
+
customHeaders,
|
|
2013
|
+
fingerprintSchemaVersion
|
|
2014
|
+
} = __privateGet(this, _options);
|
|
1651
2015
|
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
1652
2016
|
__privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
|
|
1653
2017
|
return synthesizeSsml(ssml, {
|
|
@@ -1666,11 +2030,22 @@ var AzureTtsClient = class {
|
|
|
1666
2030
|
customMerger: options.customMerger ?? __privateGet(this, _options).customMerger,
|
|
1667
2031
|
outputMimeType: options.outputMimeType ?? __privateGet(this, _options).outputMimeType,
|
|
1668
2032
|
postMergeValidator: options.postMergeValidator ?? __privateGet(this, _options).postMergeValidator,
|
|
1669
|
-
resumeValidation: options.resumeValidation ?? __privateGet(this, _options).resumeValidation
|
|
2033
|
+
resumeValidation: options.resumeValidation ?? __privateGet(this, _options).resumeValidation,
|
|
2034
|
+
customHeaders: options.customHeaders ?? customHeaders,
|
|
2035
|
+
fingerprintSchemaVersion: options.fingerprintSchemaVersion ?? fingerprintSchemaVersion
|
|
1670
2036
|
});
|
|
1671
2037
|
}
|
|
1672
2038
|
async synthesizeChunks(chunks, options = {}) {
|
|
1673
|
-
const {
|
|
2039
|
+
const {
|
|
2040
|
+
region,
|
|
2041
|
+
subscriptionKey,
|
|
2042
|
+
outputFormat,
|
|
2043
|
+
signal,
|
|
2044
|
+
timeoutMs,
|
|
2045
|
+
timeouts,
|
|
2046
|
+
customHeaders,
|
|
2047
|
+
fingerprintSchemaVersion
|
|
2048
|
+
} = __privateGet(this, _options);
|
|
1674
2049
|
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
1675
2050
|
return synthesizeSsmlChunks(chunks, {
|
|
1676
2051
|
endpoint,
|
|
@@ -1690,7 +2065,9 @@ var AzureTtsClient = class {
|
|
|
1690
2065
|
customMerger: options.customMerger ?? __privateGet(this, _options).customMerger,
|
|
1691
2066
|
outputMimeType: options.outputMimeType ?? __privateGet(this, _options).outputMimeType,
|
|
1692
2067
|
postMergeValidator: options.postMergeValidator ?? __privateGet(this, _options).postMergeValidator,
|
|
1693
|
-
resumeValidation: options.resumeValidation ?? __privateGet(this, _options).resumeValidation
|
|
2068
|
+
resumeValidation: options.resumeValidation ?? __privateGet(this, _options).resumeValidation,
|
|
2069
|
+
customHeaders: options.customHeaders ?? customHeaders,
|
|
2070
|
+
fingerprintSchemaVersion: options.fingerprintSchemaVersion ?? fingerprintSchemaVersion
|
|
1694
2071
|
});
|
|
1695
2072
|
}
|
|
1696
2073
|
async synthesizeSsmlSafe(ssml, options = {}) {
|
|
@@ -1705,7 +2082,14 @@ var AzureTtsClient = class {
|
|
|
1705
2082
|
timeouts: options.timeouts ?? __privateGet(this, _options).timeouts,
|
|
1706
2083
|
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress,
|
|
1707
2084
|
concurrency: options.concurrency ?? __privateGet(this, _options).concurrency,
|
|
1708
|
-
retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions
|
|
2085
|
+
retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions,
|
|
2086
|
+
cancelOnFailure: options.cancelOnFailure ?? __privateGet(this, _options).cancelOnFailure,
|
|
2087
|
+
customMerger: options.customMerger ?? __privateGet(this, _options).customMerger,
|
|
2088
|
+
outputMimeType: options.outputMimeType ?? __privateGet(this, _options).outputMimeType,
|
|
2089
|
+
postMergeValidator: options.postMergeValidator ?? __privateGet(this, _options).postMergeValidator,
|
|
2090
|
+
resumeValidation: options.resumeValidation ?? __privateGet(this, _options).resumeValidation,
|
|
2091
|
+
customHeaders: options.customHeaders ?? __privateGet(this, _options).customHeaders,
|
|
2092
|
+
fingerprintSchemaVersion: options.fingerprintSchemaVersion ?? __privateGet(this, _options).fingerprintSchemaVersion
|
|
1709
2093
|
});
|
|
1710
2094
|
}
|
|
1711
2095
|
async synthesizeSsmlChunksSafe(chunks, options = {}) {
|
|
@@ -1810,6 +2194,8 @@ export {
|
|
|
1810
2194
|
BatchChunkValidationError,
|
|
1811
2195
|
ChunkValidationError,
|
|
1812
2196
|
DEFAULT_OUTPUT_FORMAT,
|
|
2197
|
+
DeadlineController,
|
|
2198
|
+
IncompleteChunkSetError,
|
|
1813
2199
|
MergeError,
|
|
1814
2200
|
SynthesisCancelledError,
|
|
1815
2201
|
SynthesisTimeoutError,
|
|
@@ -1837,6 +2223,7 @@ export {
|
|
|
1837
2223
|
parseSsml,
|
|
1838
2224
|
resolveMergeAudioFormat,
|
|
1839
2225
|
resolveMimeType,
|
|
2226
|
+
serializeChunkError,
|
|
1840
2227
|
splitSsmlDocument,
|
|
1841
2228
|
synthesizeSpeech,
|
|
1842
2229
|
synthesizeSsml,
|