@ssml-builder-js/azure-tts-client 2.17.0 → 2.19.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/dist/index.js CHANGED
@@ -44,11 +44,14 @@ __export(index_exports, {
44
44
  BatchChunkValidationError: () => BatchChunkValidationError,
45
45
  ChunkValidationError: () => ChunkValidationError,
46
46
  DEFAULT_OUTPUT_FORMAT: () => DEFAULT_OUTPUT_FORMAT,
47
+ DeadlineController: () => DeadlineController,
48
+ IncompleteChunkSetError: () => IncompleteChunkSetError,
47
49
  MergeError: () => MergeError,
48
50
  SynthesisCancelledError: () => SynthesisCancelledError,
49
51
  SynthesisTimeoutError: () => SynthesisTimeoutError,
50
52
  UnsupportedMergeFormatError: () => UnsupportedMergeFormatError,
51
53
  canMergeAudioFormat: () => canMergeAudioFormat,
54
+ computeChunkFingerprint: () => computeChunkFingerprint,
52
55
  fetchAzureVoiceCatalog: () => fetchAzureVoiceCatalog,
53
56
  getRetryAfterDelayMs: () => getRetryAfterDelayMs,
54
57
  inspectAudioSpecification: () => inspectAudioSpecification,
@@ -56,6 +59,7 @@ __export(index_exports, {
56
59
  mergeSynthesisResults: () => mergeSynthesisResults,
57
60
  resolveMergeAudioFormat: () => resolveMergeAudioFormat,
58
61
  resolveMimeType: () => resolveMimeType,
62
+ serializeChunkError: () => serializeChunkError,
59
63
  synthesizeSpeech: () => synthesizeSpeech,
60
64
  synthesizeSsml: () => synthesizeSsml,
61
65
  synthesizeSsmlChunks: () => synthesizeSsmlChunks,
@@ -127,6 +131,15 @@ var SynthesisTimeoutError = class extends Error {
127
131
  this.name = "SynthesisTimeoutError";
128
132
  }
129
133
  };
134
+ var IncompleteChunkSetError = class extends Error {
135
+ constructor(totalChunks, missingChunkIndices) {
136
+ super(`Cannot merge an incomplete chunk set; missing chunk indices: ${missingChunkIndices.join(", ")}.`);
137
+ this.kind = "incomplete-chunk-set";
138
+ this.name = "IncompleteChunkSetError";
139
+ this.totalChunks = totalChunks;
140
+ this.missingChunkIndices = [...missingChunkIndices];
141
+ }
142
+ };
130
143
  var MergeError = class extends Error {
131
144
  constructor(message, cause) {
132
145
  super(message);
@@ -151,8 +164,32 @@ var UnsupportedMergeFormatError = class extends Error {
151
164
  this.format = format;
152
165
  }
153
166
  };
167
+ function serializeChunkError(error, phase, isOriginalFailure) {
168
+ const message = error instanceof Error ? error.message : String(error);
169
+ const status = error instanceof AzureTtsError ? error.status : void 0;
170
+ const kind = error && typeof error === "object" && "kind" in error ? String(error.kind) : "";
171
+ 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";
172
+ const details = {};
173
+ if (error instanceof AzureTtsError) {
174
+ details.statusText = error.statusText;
175
+ if (error.requestId) details.requestId = error.requestId;
176
+ }
177
+ if (error instanceof IncompleteChunkSetError) {
178
+ details.totalChunks = error.totalChunks;
179
+ details.missingChunkIndices = [...error.missingChunkIndices];
180
+ }
181
+ return {
182
+ code,
183
+ phase,
184
+ message,
185
+ isOriginalFailure,
186
+ 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)),
187
+ ...status !== void 0 && status > 0 ? { httpStatus: status } : {},
188
+ ...Object.keys(details).length > 0 ? { details } : {}
189
+ };
190
+ }
154
191
  function toSynthesisError(error) {
155
- if (error instanceof AzureTtsError || error instanceof MergeError || error instanceof AudioFormatMismatchError || error instanceof UnsupportedMergeFormatError || error instanceof SynthesisCancelledError || error instanceof SynthesisTimeoutError)
192
+ if (error instanceof AzureTtsError || error instanceof MergeError || error instanceof AudioFormatMismatchError || error instanceof UnsupportedMergeFormatError || error instanceof SynthesisCancelledError || error instanceof SynthesisTimeoutError || error instanceof IncompleteChunkSetError)
156
193
  return error;
157
194
  const message = error instanceof Error ? error.message : String(error);
158
195
  if (/cancel|abort/i.test(message)) return new SynthesisCancelledError(message);
@@ -164,6 +201,55 @@ function createSpeechSdkError(error) {
164
201
  return new AzureTtsSdkError(message);
165
202
  }
166
203
 
204
+ // src/deadline.ts
205
+ var _controller, _parent, _onParentAbort, _timer, _timedOut;
206
+ var DeadlineController = class {
207
+ constructor(totalJobMs, parent) {
208
+ __privateAdd(this, _controller, new AbortController());
209
+ __privateAdd(this, _parent);
210
+ __privateAdd(this, _onParentAbort);
211
+ __privateAdd(this, _timer);
212
+ __privateAdd(this, _timedOut, false);
213
+ __privateSet(this, _parent, parent);
214
+ __privateSet(this, _onParentAbort, () => __privateGet(this, _controller).abort());
215
+ this.deadlineAtMs = totalJobMs !== void 0 && totalJobMs > 0 ? Date.now() + totalJobMs : void 0;
216
+ this.signal = this.deadlineAtMs === void 0 && parent ? parent : __privateGet(this, _controller).signal;
217
+ if (parent?.aborted) __privateGet(this, _controller).abort();
218
+ parent?.addEventListener("abort", __privateGet(this, _onParentAbort), { once: true });
219
+ if (this.deadlineAtMs !== void 0) {
220
+ __privateSet(this, _timer, setTimeout(
221
+ () => {
222
+ __privateSet(this, _timedOut, true);
223
+ __privateGet(this, _controller).abort();
224
+ },
225
+ Math.max(0, this.deadlineAtMs - Date.now())
226
+ ));
227
+ }
228
+ }
229
+ get timedOut() {
230
+ return __privateGet(this, _timedOut) || this.deadlineAtMs !== void 0 && this.remainingMs <= 0;
231
+ }
232
+ get remainingMs() {
233
+ return this.deadlineAtMs === void 0 ? Number.POSITIVE_INFINITY : Math.max(0, this.deadlineAtMs - Date.now());
234
+ }
235
+ throwIfExpired() {
236
+ if (this.timedOut) throw new SynthesisTimeoutError("Speech synthesis exceeded the total job deadline.");
237
+ if (this.signal.aborted) throw new Error("Speech synthesis was cancelled.");
238
+ }
239
+ abort() {
240
+ __privateGet(this, _controller).abort();
241
+ }
242
+ dispose() {
243
+ if (__privateGet(this, _timer)) clearTimeout(__privateGet(this, _timer));
244
+ __privateGet(this, _parent)?.removeEventListener("abort", __privateGet(this, _onParentAbort));
245
+ }
246
+ };
247
+ _controller = new WeakMap();
248
+ _parent = new WeakMap();
249
+ _onParentAbort = new WeakMap();
250
+ _timer = new WeakMap();
251
+ _timedOut = new WeakMap();
252
+
167
253
  // src/synthesis.ts
168
254
  var SpeechSDK2 = __toESM(require("microsoft-cognitiveservices-speech-sdk"));
169
255
  var import_ssml_core = require("@ssml-builder-js/ssml-core");
@@ -215,6 +301,9 @@ var OUTPUT_FORMATS = {
215
301
  function resolveMimeType(outputFormat) {
216
302
  if (/(?:wav|wave|riff)/i.test(outputFormat)) return "audio/wav";
217
303
  if (/(?:mp3|mpeg)/i.test(outputFormat)) return "audio/mpeg";
304
+ if (/mulaw|mu-law/i.test(outputFormat)) return "audio/basic";
305
+ if (/alaw|a-law/i.test(outputFormat)) return "audio/alaw";
306
+ if (/siren/i.test(outputFormat)) return "audio/siren";
218
307
  if (/ogg/i.test(outputFormat)) return "audio/ogg";
219
308
  if (/webm/i.test(outputFormat)) return "audio/webm";
220
309
  if (/raw/i.test(outputFormat)) return "audio/L16";
@@ -243,6 +332,32 @@ function createSpeechConfig(config) {
243
332
  }
244
333
 
245
334
  // src/synthesis.ts
335
+ function computeChunkFingerprint(ssml, outputFormat = DEFAULT_OUTPUT_FORMAT, options = {}) {
336
+ const readAttribute = (name) => {
337
+ const pattern = new RegExp(`(?:${name})\\s*=\\s*[\\"']([^\\"']*)`, "gi");
338
+ return [...ssml.matchAll(pattern)].map((match) => match[1] ?? "").join("|");
339
+ };
340
+ const headers = Object.fromEntries(
341
+ Object.entries(options.customHeaders ?? {}).sort(([first], [second]) => first.localeCompare(second))
342
+ );
343
+ const payload = JSON.stringify({
344
+ ssml,
345
+ outputFormat,
346
+ region: options.region ?? "",
347
+ endpoint: options.endpoint ?? "",
348
+ voice: options.voice ?? readAttribute("(?:name|voice)"),
349
+ lang: options.lang ?? readAttribute("(?:xml:lang|lang)"),
350
+ customHeaders: headers,
351
+ fingerprintSchemaVersion: options.fingerprintSchemaVersion ?? options.schemaVersion ?? "2"
352
+ });
353
+ let hash = 0xcbf29ce484222325n;
354
+ const mask = 0xffffffffffffffffn;
355
+ for (let index = 0; index < payload.length; index += 1) {
356
+ hash ^= BigInt(payload.charCodeAt(index));
357
+ hash = hash * 0x100000001b3n & mask;
358
+ }
359
+ return `fnv1a64-${hash.toString(16).padStart(16, "0")}`;
360
+ }
246
361
  function ascii(bytes, offset, value) {
247
362
  return [...value].every((character, index) => bytes[offset + index] === character.charCodeAt(0));
248
363
  }
@@ -282,9 +397,11 @@ function parseWav(buffer) {
282
397
  }
283
398
  return { chunks, data, format };
284
399
  }
285
- function formatNumber(format, pattern, fallback) {
286
- const match = pattern.exec(format);
287
- return match?.[1] ? Number(match[1]) : fallback;
400
+ function formatSampleRate(format) {
401
+ const match = /(?:^|-)(\d+)(khz|hz)(?:-|$)/i.exec(format);
402
+ if (!match?.[1] || !match[2]) return 0;
403
+ const value = Number(match[1]);
404
+ return match[2].toLowerCase() === "khz" ? value * 1e3 : value;
288
405
  }
289
406
  function formatChannels(format, fallback) {
290
407
  if (/stereo|2ch|dual/i.test(format)) return 2;
@@ -292,11 +409,11 @@ function formatChannels(format, fallback) {
292
409
  return fallback;
293
410
  }
294
411
  function formatAudioSpecification(format) {
295
- const sampleRate = formatNumber(format, /(\d+)(?:khz|kHz|hz|Hz)/, 0);
412
+ const sampleRate = formatSampleRate(format);
296
413
  const channels = formatChannels(format, 0);
297
414
  const bitrateMatch = /(\d+)(?:kbitrate|kbps|kbits?)/i.exec(format);
298
415
  const bitrate = bitrateMatch?.[1] ? Number(bitrateMatch[1]) * 1e3 : void 0;
299
- 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";
416
+ const codec = /mp3|mpeg/i.test(format) ? "mp3" : /opus/i.test(format) ? "opus" : /silk/i.test(format) ? "silk" : /mulaw|mu-law/i.test(format) ? "mulaw" : /alaw|a-law/i.test(format) ? "alaw" : /siren/i.test(format) ? "siren" : /pcm/i.test(format) ? "pcm" : "unknown";
300
417
  const bitDepthMatch = /(\d+)bit/i.exec(format);
301
418
  const container = /(?:wav|wave|riff)/i.test(format) ? "riff-wave" : /mp3|mpeg/i.test(format) ? "mp3-raw" : /ogg/i.test(format) ? "ogg" : /webm/i.test(format) ? "webm" : /raw/i.test(format) ? "raw" : void 0;
302
419
  return {
@@ -309,7 +426,7 @@ function formatAudioSpecification(format) {
309
426
  ...bitDepthMatch?.[1] ? { bitDepth: Number(bitDepthMatch[1]) } : {},
310
427
  ...container ? { container } : {},
311
428
  isVbr: /vbr/i.test(format),
312
- isCompressed: codec === "mp3" || codec === "opus" || codec === "silk"
429
+ isCompressed: codec !== "pcm" && codec !== "unknown"
313
430
  };
314
431
  }
315
432
  function parseMp3Specification(buffer, format) {
@@ -351,6 +468,129 @@ function parseMp3Specification(buffer, format) {
351
468
  }
352
469
  return void 0;
353
470
  }
471
+ function readEbmlVint(bytes, offset, preserveMarker) {
472
+ const first = bytes[offset];
473
+ if (first === void 0) throw new Error("Invalid EBML variable-length integer.");
474
+ let mask = 128;
475
+ let length = 1;
476
+ while (length <= 8 && (first & mask) === 0) {
477
+ mask >>= 1;
478
+ length += 1;
479
+ }
480
+ if (length > 8 || offset + length > bytes.byteLength) throw new Error("Truncated EBML variable-length integer.");
481
+ let value = preserveMarker ? first : first & mask - 1;
482
+ for (let index = 1; index < length; index += 1) value = value * 256 + (bytes[offset + index] ?? 0);
483
+ if (!preserveMarker && value === 2 ** (7 * length) - 1)
484
+ throw new Error("EBML unknown-size elements are not supported.");
485
+ return { value, length };
486
+ }
487
+ function readEbmlElement(bytes, offset) {
488
+ const id = readEbmlVint(bytes, offset, true);
489
+ const size = readEbmlVint(bytes, offset + id.length, false);
490
+ const dataStart = offset + id.length + size.length;
491
+ const dataEnd = dataStart + size.value;
492
+ if (dataEnd > bytes.byteLength) throw new Error("EBML element exceeds the audio buffer.");
493
+ return { id: id.value, dataStart, dataEnd };
494
+ }
495
+ function ebmlText(bytes, element) {
496
+ return new TextDecoder().decode(bytes.slice(element.dataStart, element.dataEnd));
497
+ }
498
+ function findEbmlElement(bytes, start, end, id) {
499
+ let offset = start;
500
+ while (offset < end) {
501
+ const element = readEbmlElement(bytes, offset);
502
+ if (element.id === id) return element;
503
+ offset = element.dataEnd;
504
+ }
505
+ if (offset !== end) throw new Error("Invalid EBML element boundary.");
506
+ return void 0;
507
+ }
508
+ function parseOggSpecification(buffer, format) {
509
+ const bytes = new Uint8Array(buffer);
510
+ let offset = 0;
511
+ let firstPayload;
512
+ let pages = 0;
513
+ while (offset < bytes.byteLength) {
514
+ if (offset + 27 > bytes.byteLength || !ascii(bytes, offset, "OggS")) throw new Error("Invalid Ogg page header.");
515
+ if (bytes[offset + 4] !== 0) throw new Error("Unsupported Ogg bitstream version.");
516
+ const segmentCount = bytes[offset + 26] ?? 0;
517
+ const lacingStart = offset + 27;
518
+ const payloadStart = lacingStart + segmentCount;
519
+ if (payloadStart > bytes.byteLength) throw new Error("Truncated Ogg segment table.");
520
+ const payloadLength = bytes.slice(lacingStart, payloadStart).reduce((total, value) => total + value, 0);
521
+ const pageEnd = payloadStart + payloadLength;
522
+ if (pageEnd > bytes.byteLength) throw new Error("Ogg page payload exceeds the audio buffer.");
523
+ if (pages === 0) firstPayload = bytes.slice(payloadStart, pageEnd);
524
+ offset = pageEnd;
525
+ pages += 1;
526
+ }
527
+ if (pages === 0 || !firstPayload || !ascii(firstPayload, 0, "OpusHead") || firstPayload.byteLength < 19)
528
+ throw new Error("Ogg audio must contain a valid OpusHead packet.");
529
+ const version = firstPayload[8];
530
+ const channels = firstPayload[9] ?? 0;
531
+ const sampleRate = new DataView(firstPayload.buffer, firstPayload.byteOffset, firstPayload.byteLength).getUint32(
532
+ 12,
533
+ true
534
+ );
535
+ if (version !== 1 || channels <= 0 || sampleRate <= 0) throw new Error("Invalid Ogg OpusHead stream parameters.");
536
+ return {
537
+ format,
538
+ mimeType: "audio/ogg",
539
+ codec: "opus",
540
+ sampleRate,
541
+ channels,
542
+ container: "ogg",
543
+ isVbr: true,
544
+ isCompressed: true
545
+ };
546
+ }
547
+ function parseWebmSpecification(buffer, format) {
548
+ const bytes = new Uint8Array(buffer);
549
+ const ebml = readEbmlElement(bytes, 0);
550
+ if (ebml.id !== 440786851) throw new Error("WebM audio must begin with an EBML header.");
551
+ const docType = findEbmlElement(bytes, ebml.dataStart, ebml.dataEnd, 17026);
552
+ if (!docType || ebmlText(bytes, docType).toLowerCase() !== "webm") throw new Error("EBML DocType must be webm.");
553
+ const segment = readEbmlElement(bytes, ebml.dataEnd);
554
+ if (segment.id !== 408125543) throw new Error("WebM audio must contain a Segment element.");
555
+ const tracks = findEbmlElement(bytes, segment.dataStart, segment.dataEnd, 374648427);
556
+ if (!tracks) throw new Error("WebM audio must contain a Tracks element.");
557
+ let offset = tracks.dataStart;
558
+ let opusTrack;
559
+ while (offset < tracks.dataEnd) {
560
+ const track = readEbmlElement(bytes, offset);
561
+ if (track.id === 174) {
562
+ const codec = findEbmlElement(bytes, track.dataStart, track.dataEnd, 134);
563
+ const trackType = findEbmlElement(bytes, track.dataStart, track.dataEnd, 131);
564
+ if (codec && ebmlText(bytes, codec) === "A_OPUS" && trackType && bytes[trackType.dataStart] === 2) {
565
+ opusTrack = track;
566
+ break;
567
+ }
568
+ }
569
+ offset = track.dataEnd;
570
+ }
571
+ if (!opusTrack) throw new Error("WebM tracks do not define an Opus audio track.");
572
+ const audio = findEbmlElement(bytes, opusTrack.dataStart, opusTrack.dataEnd, 225);
573
+ const sampling = audio ? findEbmlElement(bytes, audio.dataStart, audio.dataEnd, 181) : void 0;
574
+ const channels = audio ? findEbmlElement(bytes, audio.dataStart, audio.dataEnd, 159) : void 0;
575
+ const sampleRate = sampling ? new DataView(
576
+ bytes.buffer,
577
+ bytes.byteOffset + sampling.dataStart,
578
+ sampling.dataEnd - sampling.dataStart
579
+ ).getFloat64(0, false) : 0;
580
+ const channelCount = channels ? bytes[channels.dataEnd - 1] ?? 0 : 0;
581
+ if (!Number.isFinite(sampleRate) || sampleRate <= 0 || channelCount <= 0)
582
+ throw new Error("WebM Opus audio track has invalid sampling or channel parameters.");
583
+ return {
584
+ format,
585
+ mimeType: "audio/webm",
586
+ codec: "opus",
587
+ sampleRate: Math.round(sampleRate),
588
+ channels: channelCount,
589
+ container: "webm",
590
+ isVbr: true,
591
+ isCompressed: true
592
+ };
593
+ }
354
594
  function inspectAudioSpecification(buffer, format) {
355
595
  if (isWavFormat(format) || ascii(new Uint8Array(buffer), 0, "RIFF")) {
356
596
  const parsed = parseWav(buffer);
@@ -360,27 +600,78 @@ function inspectAudioSpecification(buffer, format) {
360
600
  const channels = view.getUint16(2, true);
361
601
  const bitsPerSample = parsed.format.byteLength >= 16 ? view.getUint16(14, true) : 0;
362
602
  const formatCode = view.getUint16(0, true);
603
+ const namedCodec = formatAudioSpecification(format).codec;
604
+ const codec = formatCode === 1 ? "pcm" : formatCode === 6 ? "alaw" : formatCode === 7 ? "mulaw" : namedCodec === "siren" ? "siren" : "unknown";
363
605
  return {
364
606
  format,
365
607
  mimeType: "audio/wav",
366
- codec: formatCode === 1 ? "pcm" : "unknown",
608
+ codec,
367
609
  sampleRate,
368
610
  channels,
369
611
  ...sampleRate && channels && bitsPerSample ? { bitrate: sampleRate * channels * bitsPerSample } : {},
370
612
  bitDepth: bitsPerSample,
371
613
  container: "riff-wave",
372
614
  isVbr: false,
373
- isCompressed: formatCode !== 1
615
+ isCompressed: codec !== "pcm" && codec !== "unknown"
374
616
  };
375
617
  }
376
- if (isMp3Format(format)) return parseMp3Specification(buffer, format) ?? formatAudioSpecification(format);
377
- return formatAudioSpecification(format);
618
+ if (isMp3Format(format)) {
619
+ const specification2 = parseMp3Specification(buffer, format);
620
+ return specification2 ?? formatAudioSpecification(format);
621
+ }
622
+ if (isOggFormat(format) || ascii(new Uint8Array(buffer), 0, "OggS")) {
623
+ const specification2 = parseOggSpecification(buffer, format);
624
+ validateContainerFormat(specification2, format);
625
+ return specification2;
626
+ }
627
+ if (isWebmFormat(format) || new Uint8Array(buffer)[0] === 26) {
628
+ const specification2 = parseWebmSpecification(buffer, format);
629
+ validateContainerFormat(specification2, format);
630
+ return specification2;
631
+ }
632
+ const specification = formatAudioSpecification(format);
633
+ if (specification.container === "raw") validateRawAudioBuffer(buffer, specification);
634
+ return specification;
635
+ }
636
+ function validateRawAudioBuffer(buffer, specification) {
637
+ if (specification.sampleRate <= 0 || specification.channels <= 0 || specification.bitDepth === void 0) {
638
+ throw new Error(`RAW audio format "${specification.format}" does not define a complete audio specification.`);
639
+ }
640
+ if (specification.codec === "siren") return;
641
+ if (specification.codec === "silk") {
642
+ if (buffer.byteLength <= 9 || !ascii(new Uint8Array(buffer), 0, "#!SILK_V3"))
643
+ throw new Error("RAW SILK audio must contain a valid #!SILK_V3 payload header.");
644
+ return;
645
+ }
646
+ if (specification.codec === "opus" && buffer.byteLength === 0) throw new Error("RAW Opus audio cannot be empty.");
647
+ if (specification.codec === "opus") {
648
+ const packetCode = new Uint8Array(buffer)[0] ?? 0;
649
+ const frameCountCode = packetCode & 3;
650
+ if (packetCode >> 3 > 31 || buffer.byteLength < (frameCountCode === 3 ? 2 : 2) || frameCountCode === 3 && ((new Uint8Array(buffer)[1] ?? 0) & 63) === 0)
651
+ throw new Error("RAW Opus audio has an invalid packet framing header.");
652
+ return;
653
+ }
654
+ const bytesPerFrame = specification.channels * Math.ceil(specification.bitDepth / 8);
655
+ if (bytesPerFrame <= 0 || buffer.byteLength % bytesPerFrame !== 0) {
656
+ throw new Error(
657
+ `RAW audio buffer size ${buffer.byteLength} is not aligned to ${bytesPerFrame}-byte audio frames for "${specification.format}".`
658
+ );
659
+ }
660
+ }
661
+ function validateContainerFormat(specification, format) {
662
+ const expected = formatAudioSpecification(format);
663
+ if (expected.sampleRate > 0 && specification.sampleRate !== expected.sampleRate || expected.channels > 0 && specification.channels !== expected.channels || expected.codec !== "unknown" && specification.codec !== expected.codec) {
664
+ throw new AudioFormatMismatchError(`Audio container does not match the requested format "${format}".`, [
665
+ expected,
666
+ specification
667
+ ]);
668
+ }
378
669
  }
379
670
  function validateAudioSpecifications(specs) {
380
671
  const first = specs[0];
381
672
  if (!first) return;
382
673
  const mismatch = specs.find(
383
- (spec) => spec.sampleRate !== first.sampleRate || spec.channels !== first.channels || first.bitrate !== void 0 && spec.bitrate !== void 0 && spec.bitrate !== first.bitrate || first.bitDepth !== void 0 && spec.bitDepth !== void 0 && spec.bitDepth !== first.bitDepth || first.container !== void 0 && spec.container !== void 0 && spec.container !== first.container || first.isVbr !== void 0 && spec.isVbr !== void 0 && spec.isVbr !== first.isVbr
674
+ (spec) => spec.sampleRate !== first.sampleRate || spec.channels !== first.channels || spec.codec !== first.codec || first.bitrate !== void 0 && spec.bitrate !== void 0 && spec.bitrate !== first.bitrate || first.bitDepth !== void 0 && spec.bitDepth !== void 0 && spec.bitDepth !== first.bitDepth || first.container !== void 0 && spec.container !== void 0 && spec.container !== first.container || first.isVbr !== void 0 && spec.isVbr !== void 0 && spec.isVbr !== first.isVbr
384
675
  );
385
676
  if (mismatch)
386
677
  throw new AudioFormatMismatchError(
@@ -457,12 +748,67 @@ function stripMp3Tags(buffer) {
457
748
  function isMp3Format(format) {
458
749
  return /(?:mp3|mpeg)/i.test(format);
459
750
  }
751
+ function isOggFormat(format) {
752
+ return /ogg/i.test(format);
753
+ }
754
+ function isWebmFormat(format) {
755
+ return /webm/i.test(format);
756
+ }
460
757
  function isWavFormat(format) {
461
758
  return /(?:wav|wave|riff)/i.test(format);
462
759
  }
463
760
  function isRawFormat(format) {
464
761
  return /^raw(?:-|$)/i.test(format);
465
762
  }
763
+ function validateMergedAudioBuffer(merged, format, buffers, inputSpecs, outputMimeType, allowExternalContainer) {
764
+ if (!(merged instanceof ArrayBuffer) || merged.byteLength === 0) {
765
+ throw new MergeError("The custom audio merger returned an empty or invalid audio buffer.");
766
+ }
767
+ let specification;
768
+ try {
769
+ specification = inspectAudioSpecification(merged, format);
770
+ } catch (error) {
771
+ if (!allowExternalContainer) throw error;
772
+ specification = inputSpecs[0] ?? formatAudioSpecification(format);
773
+ }
774
+ if (!outputMimeType.trim()) throw new MergeError("The custom audio merger output MIME type cannot be empty.");
775
+ const firstInput = inputSpecs[0];
776
+ if (firstInput && (specification.sampleRate !== firstInput.sampleRate || specification.channels !== firstInput.channels || specification.codec !== firstInput.codec || firstInput.bitDepth !== void 0 && specification.bitDepth !== firstInput.bitDepth)) {
777
+ throw new AudioFormatMismatchError("The custom audio merger returned an incompatible audio stream.", [
778
+ ...inputSpecs,
779
+ specification
780
+ ]);
781
+ }
782
+ if (isRawFormat(format)) {
783
+ const expectedSize = buffers.reduce((total, input) => total + input.byteLength, 0);
784
+ if (merged.byteLength !== expectedSize) {
785
+ throw new MergeError(
786
+ `The custom audio merger returned ${merged.byteLength} bytes; ${expectedSize} were expected.`
787
+ );
788
+ }
789
+ validateRawAudioBuffer(merged, specification);
790
+ }
791
+ return specification;
792
+ }
793
+ async function withinDeadline(value, deadline) {
794
+ if (!deadline) return value;
795
+ deadline.throwIfExpired();
796
+ if (!Number.isFinite(deadline.remainingMs)) return value;
797
+ let timer;
798
+ try {
799
+ return await Promise.race([
800
+ Promise.resolve(value),
801
+ new Promise((_resolve, reject) => {
802
+ timer = setTimeout(
803
+ () => reject(new SynthesisTimeoutError("Speech synthesis exceeded the total job deadline.")),
804
+ deadline.remainingMs
805
+ );
806
+ })
807
+ ]);
808
+ } finally {
809
+ if (timer) clearTimeout(timer);
810
+ }
811
+ }
466
812
  function resolveMergeAudioFormat(format) {
467
813
  if (isWavFormat(format)) return "wav";
468
814
  if (isMp3Format(format)) return "mp3";
@@ -475,6 +821,7 @@ function canMergeAudioFormat(format) {
475
821
  function mergeAudioBuffers(buffers, options) {
476
822
  const format = typeof options === "string" ? options : options?.format;
477
823
  if (!format) throw new UnsupportedMergeFormatError("");
824
+ if (!canMergeAudioFormat(format)) throw new UnsupportedMergeFormatError(format);
478
825
  try {
479
826
  validateAudioSpecifications(buffers.map((buffer) => inspectAudioSpecification(buffer, format)));
480
827
  if (isWavFormat(format)) return mergeWavBuffers(buffers);
@@ -515,7 +862,7 @@ function closeSpeechResources(speechConfig, synthesizer) {
515
862
  }
516
863
  }
517
864
  var ticksToMilliseconds = (ticks) => Math.max(0, ticks) / 1e4;
518
- async function synthesizeSsml(ssml, config) {
865
+ async function synthesizeSsmlOnce(ssml, config) {
519
866
  if (config.signal?.aborted) {
520
867
  throw new SynthesisCancelledError();
521
868
  }
@@ -645,6 +992,13 @@ async function synthesizeSsml(ssml, config) {
645
992
  rejectWithError(err);
646
993
  return;
647
994
  }
995
+ let audioSpec;
996
+ try {
997
+ audioSpec = inspectAudioSpecification(result.audioData, config.outputFormat ?? DEFAULT_OUTPUT_FORMAT);
998
+ } catch (error) {
999
+ rejectWithError(error);
1000
+ return;
1001
+ }
648
1002
  settled = true;
649
1003
  cleanup();
650
1004
  closeResources();
@@ -680,8 +1034,8 @@ async function synthesizeSsml(ssml, config) {
680
1034
  resolve({
681
1035
  audioData: result.audioData,
682
1036
  durationMs,
683
- audioSpec: inspectAudioSpecification(result.audioData, config.outputFormat ?? DEFAULT_OUTPUT_FORMAT),
684
- mimeType: resolveMimeType(config.outputFormat ?? DEFAULT_OUTPUT_FORMAT),
1037
+ audioSpec,
1038
+ mimeType: config.outputMimeType ?? resolveMimeType(config.outputFormat ?? DEFAULT_OUTPUT_FORMAT),
685
1039
  ...config.sourceTextRange ? { textRange: { ...config.sourceTextRange } } : {},
686
1040
  ...requestId ? { requestId } : {},
687
1041
  ...sourceBoundaries.length > 0 ? { boundaries: sourceBoundaries, wordBoundary: sourceBoundaries, wordBoundaries: sourceBoundaries } : {},
@@ -694,7 +1048,7 @@ async function synthesizeSsml(ssml, config) {
694
1048
  abortHandler = () => rejectWithError(new SynthesisCancelledError());
695
1049
  config.signal.addEventListener("abort", abortHandler, { once: true });
696
1050
  }
697
- const timeoutMs = config.timeouts?.perChunkMs ?? config.timeoutMs;
1051
+ const timeoutMs = config.timeouts?.perChunkMs ?? config.timeouts?.totalJobMs ?? config.timeoutMs;
698
1052
  if (timeoutMs !== void 0 && timeoutMs > 0) {
699
1053
  timeout = setTimeout(
700
1054
  () => rejectWithError(new SynthesisTimeoutError(`Speech synthesis timed out after ${timeoutMs} ms.`)),
@@ -747,7 +1101,7 @@ async function waitForRetry(delayMs, signal) {
747
1101
  }
748
1102
  });
749
1103
  }
750
- async function synthesizeWithRetry(ssml, config, retryOptions, onRetry) {
1104
+ async function synthesizeWithRetry(ssml, config, retryOptions, onRetry, deadlineAtMs) {
751
1105
  const options = retryOptions ? {
752
1106
  maxRetries: Math.max(0, Math.floor(retryOptions.maxRetries)),
753
1107
  initialDelayMs: Math.max(0, retryOptions.initialDelayMs),
@@ -758,17 +1112,42 @@ async function synthesizeWithRetry(ssml, config, retryOptions, onRetry) {
758
1112
  while (true) {
759
1113
  if (config.signal?.aborted) throw new SynthesisCancelledError();
760
1114
  try {
761
- return await synthesizeSsml(ssml, config);
1115
+ return await synthesizeSsmlOnce(ssml, config);
762
1116
  } catch (error) {
763
1117
  if (!options || attempt >= options.maxRetries || !(options.shouldRetry?.(error, attempt + 1) ?? isRetryableSynthesisError(error)))
764
1118
  throw error;
765
1119
  attempt += 1;
766
1120
  const delayMs = retryDelay(options, attempt, error);
1121
+ const remainingMs = deadlineAtMs === void 0 ? void 0 : Math.max(0, deadlineAtMs - Date.now());
1122
+ if (getRetryAfterDelayMs(error) !== void 0 && (delayMs > options.maxDelayMs || remainingMs !== void 0 && delayMs > remainingMs)) {
1123
+ throw new SynthesisTimeoutError(
1124
+ remainingMs === void 0 ? `Retry-After exceeded maxDelayMs (${options.maxDelayMs} ms).` : `Retry-After exceeded the remaining total job timeout (${remainingMs} ms).`
1125
+ );
1126
+ }
767
1127
  onRetry(attempt, delayMs);
768
- await waitForRetry(delayMs, config.signal);
1128
+ await waitForRetry(Math.min(delayMs, remainingMs ?? delayMs), config.signal);
769
1129
  }
770
1130
  }
771
1131
  }
1132
+ async function synthesizeSsml(ssml, config) {
1133
+ const deadline = new DeadlineController(config.timeouts?.totalJobMs, config.signal);
1134
+ try {
1135
+ deadline.throwIfExpired();
1136
+ const synthesisConfig = {
1137
+ ...config,
1138
+ signal: deadline.signal,
1139
+ timeouts: config.timeouts ? { ...config.timeouts, totalJobMs: void 0 } : void 0
1140
+ };
1141
+ const result = config.retryOptions ? await synthesizeWithRetry(ssml, synthesisConfig, config.retryOptions, () => void 0, deadline.deadlineAtMs) : await synthesizeSsmlOnce(ssml, synthesisConfig);
1142
+ deadline.throwIfExpired();
1143
+ return result;
1144
+ } catch (error) {
1145
+ if (deadline.timedOut) throw new SynthesisTimeoutError("Speech synthesis exceeded the total job deadline.");
1146
+ throw error;
1147
+ } finally {
1148
+ deadline.dispose();
1149
+ }
1150
+ }
772
1151
  function createAbortScope(parent, timeoutMs) {
773
1152
  const controller = new AbortController();
774
1153
  let didTimeout = false;
@@ -789,10 +1168,10 @@ function createAbortScope(parent, timeoutMs) {
789
1168
  abort: () => controller.abort()
790
1169
  };
791
1170
  }
792
- async function synthesizeChunkWithTimeout(ssml, config, retryOptions, timeoutMs, onRetry) {
1171
+ async function synthesizeChunkWithTimeout(ssml, config, retryOptions, timeoutMs, onRetry, deadlineAtMs) {
793
1172
  const scope = createAbortScope(config.signal, timeoutMs);
794
1173
  try {
795
- return await synthesizeWithRetry(ssml, { ...config, signal: scope.signal }, retryOptions, onRetry);
1174
+ return await synthesizeWithRetry(ssml, { ...config, signal: scope.signal }, retryOptions, onRetry, deadlineAtMs);
796
1175
  } catch (error) {
797
1176
  if (scope.timedOut()) throw new SynthesisTimeoutError(`Speech synthesis timed out after ${timeoutMs} ms.`);
798
1177
  throw error;
@@ -801,18 +1180,42 @@ async function synthesizeChunkWithTimeout(ssml, config, retryOptions, timeoutMs,
801
1180
  }
802
1181
  }
803
1182
  async function synthesizeSsmlChunks(chunks, config) {
804
- const results = new Array(chunks.length);
805
1183
  const totalChunks = chunks.length;
1184
+ const inputs = chunks.map((chunk) => typeof chunk === "string" ? { ssml: chunk } : chunk);
1185
+ const fingerprints = inputs.map(
1186
+ (chunk) => computeChunkFingerprint(chunk.ssml, config.outputFormat ?? DEFAULT_OUTPUT_FORMAT, {
1187
+ region: config.region,
1188
+ endpoint: config.endpoint,
1189
+ customHeaders: config.customHeaders,
1190
+ fingerprintSchemaVersion: config.fingerprintSchemaVersion
1191
+ })
1192
+ );
1193
+ const results = new Array(totalChunks);
806
1194
  const cachedChunks = new Map((config.resumeChunks ?? []).map((chunk) => [chunk.chunkIndex, chunk]));
1195
+ const invalidCachedIndices = /* @__PURE__ */ new Set();
1196
+ const chunkStates = inputs.map((_chunk, chunkIndex) => ({
1197
+ chunkIndex,
1198
+ status: "pending",
1199
+ canResume: true
1200
+ }));
807
1201
  for (const [index, cached] of cachedChunks) {
808
- if (index >= 0 && index < totalChunks) results[index] = cached;
1202
+ if (index < 0 || index >= totalChunks) continue;
1203
+ const isValid = config.resumeValidation === "disabled" || cached.fingerprint === fingerprints[index];
1204
+ if (isValid) {
1205
+ results[index] = { ...cached };
1206
+ chunkStates[index] = { chunkIndex: index, status: "succeeded", canResume: true, result: results[index] };
1207
+ } else {
1208
+ invalidCachedIndices.add(index);
1209
+ }
809
1210
  }
810
1211
  const requestedIndices = config.resumeChunkIndices ? new Set(config.resumeChunkIndices.filter((index) => index >= 0 && index < totalChunks)) : void 0;
811
- const shouldSynthesize = (index) => !cachedChunks.has(index) && (requestedIndices === void 0 || requestedIndices.has(index));
1212
+ const shouldSynthesize = (index) => (!cachedChunks.has(index) || invalidCachedIndices.has(index)) && (requestedIndices === void 0 || requestedIndices.has(index) || invalidCachedIndices.has(index));
1213
+ const jobStartedAt = Date.now();
1214
+ const jobDeadlineAt = config.timeouts?.totalJobMs !== void 0 && config.timeouts.totalJobMs > 0 ? jobStartedAt + config.timeouts.totalJobMs : void 0;
1215
+ const deadline = new DeadlineController(config.timeouts?.totalJobMs, config.signal);
812
1216
  const scope = createAbortScope(config.signal, config.timeouts?.totalJobMs);
813
1217
  const report = (event) => config.onProgress?.(event);
814
- for (const [index, chunk] of chunks.entries()) {
815
- const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
1218
+ for (const [index, input] of inputs.entries()) {
816
1219
  report({
817
1220
  currentChunk: index,
818
1221
  totalChunks,
@@ -834,8 +1237,7 @@ async function synthesizeSsmlChunks(chunks, config) {
834
1237
  if (index >= chunks.length) return;
835
1238
  if (!shouldSynthesize(index)) continue;
836
1239
  if (firstError && config.cancelOnFailure !== false) return;
837
- const chunk = chunks[index];
838
- const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
1240
+ const input = inputs[index];
839
1241
  report({
840
1242
  currentChunk: completed,
841
1243
  totalChunks,
@@ -851,7 +1253,7 @@ async function synthesizeSsmlChunks(chunks, config) {
851
1253
  input.ssml,
852
1254
  {
853
1255
  ...config,
854
- signal: scope.signal,
1256
+ signal: deadline.signal,
855
1257
  ...input.originalTextRange ? { sourceTextRange: input.originalTextRange } : {},
856
1258
  ...input.sourceNodePath ?? config.sourceNodePath ? { sourceNodePath: [...input.sourceNodePath ?? config.sourceNodePath ?? []] } : {},
857
1259
  ...input.sourceTextSegments ? { sourceTextSegments: input.sourceTextSegments } : {},
@@ -872,9 +1274,11 @@ async function synthesizeSsmlChunks(chunks, config) {
872
1274
  retryAttempt,
873
1275
  nextRetryDelayMs,
874
1276
  isRetrying: true
875
- })
1277
+ }),
1278
+ jobDeadlineAt
876
1279
  );
877
1280
  results[index] = result;
1281
+ chunkStates[index] = { chunkIndex: index, status: "succeeded", canResume: true, result };
878
1282
  completed += 1;
879
1283
  report({
880
1284
  currentChunk: completed,
@@ -886,7 +1290,16 @@ async function synthesizeSsmlChunks(chunks, config) {
886
1290
  durationMs: Date.now() - startedAt
887
1291
  });
888
1292
  } catch (error) {
889
- failedIndices.add(index);
1293
+ const wasCancelled = firstError !== void 0 || scope.signal.aborted && !scope.timedOut();
1294
+ firstError ?? (firstError = scope.timedOut() ? new SynthesisTimeoutError(`Speech synthesis timed out after ${config.timeouts?.totalJobMs} ms.`) : error);
1295
+ if (!wasCancelled) failedIndices.add(index);
1296
+ chunkStates[index] = {
1297
+ chunkIndex: index,
1298
+ status: wasCancelled ? "cancelled" : "failed",
1299
+ isOriginalFailure: !wasCancelled,
1300
+ canResume: true,
1301
+ error: serializeChunkError(error, "synthesis", !wasCancelled)
1302
+ };
890
1303
  report({
891
1304
  currentChunk: completed,
892
1305
  totalChunks,
@@ -897,7 +1310,6 @@ async function synthesizeSsmlChunks(chunks, config) {
897
1310
  durationMs: Date.now() - startedAt,
898
1311
  error
899
1312
  });
900
- firstError ?? (firstError = scope.timedOut() ? new SynthesisTimeoutError(`Speech synthesis timed out after ${config.timeouts?.totalJobMs} ms.`) : error);
901
1313
  if (config.cancelOnFailure !== false) scope.abort();
902
1314
  return;
903
1315
  }
@@ -906,26 +1318,47 @@ async function synthesizeSsmlChunks(chunks, config) {
906
1318
  try {
907
1319
  await Promise.all(Array.from({ length: Math.min(concurrency, chunks.length) }, () => worker()));
908
1320
  if (firstError) throw firstError;
1321
+ const missingChunkIndices = Array.from(
1322
+ { length: totalChunks },
1323
+ (_value, index) => results[index] === void 0 ? index : void 0
1324
+ ).filter((index) => index !== void 0);
1325
+ if (missingChunkIndices.length > 0) throw new IncompleteChunkSetError(totalChunks, missingChunkIndices);
909
1326
  const orderedResults = results.filter((result) => result !== void 0);
910
1327
  return await mergeSynthesisResults(orderedResults, {
911
1328
  format: config.outputFormat ?? DEFAULT_OUTPUT_FORMAT,
912
- signal: scope.signal,
1329
+ signal: deadline.signal,
913
1330
  customMerger: config.customMerger,
914
1331
  outputMimeType: config.outputMimeType,
915
- postMergeValidator: config.postMergeValidator
1332
+ postMergeValidator: config.postMergeValidator,
1333
+ deadline
916
1334
  });
917
1335
  } catch (error) {
1336
+ if (firstError && config.cancelOnFailure !== false) {
1337
+ for (const [chunkIndex, state] of chunkStates.entries()) {
1338
+ if (state.status === "pending" && shouldSynthesize(chunkIndex)) {
1339
+ chunkStates[chunkIndex] = { chunkIndex, status: "cancelled", isOriginalFailure: false, canResume: true };
1340
+ }
1341
+ }
1342
+ }
1343
+ const synthesizedChunks = results.flatMap(
1344
+ (result, chunkIndex) => result ? [{ ...result, chunkIndex, fingerprint: fingerprints[chunkIndex] ?? "" }] : []
1345
+ );
918
1346
  const partial = {
919
- synthesizedChunks: results.flatMap((result, chunkIndex) => result ? [{ ...result, chunkIndex }] : []),
920
- completedChunks: results.flatMap((result, chunkIndex) => result ? [{ ...result, chunkIndex }] : []),
921
- pendingChunkIndices: chunks.flatMap((_chunk, chunkIndex) => results[chunkIndex] ? [] : [chunkIndex]),
1347
+ synthesizedChunks,
1348
+ completedChunks: synthesizedChunks,
1349
+ pendingChunkIndices: chunkStates.flatMap(
1350
+ (state) => state.status === "pending" || state.status === "cancelled" || state.status === "failed" ? [state.chunkIndex] : []
1351
+ ),
922
1352
  failedChunkIndices: [...failedIndices],
1353
+ cancelledChunkIndices: chunkStates.filter((state) => state.status === "cancelled").map((state) => state.chunkIndex),
1354
+ chunkStates,
923
1355
  totalChunks
924
1356
  };
925
1357
  if (error && typeof error === "object") error.partialResult = partial;
926
1358
  throw error;
927
1359
  } finally {
928
1360
  scope.dispose();
1361
+ deadline.dispose();
929
1362
  }
930
1363
  }
931
1364
  function createMergedResult(results, audioData, format, audioSpec, outputMimeType) {
@@ -1003,30 +1436,44 @@ function mergeSynthesisResults(results, options) {
1003
1436
  const format = resolvedOptions?.format;
1004
1437
  if (!format) throw new UnsupportedMergeFormatError("");
1005
1438
  const buffers = results.map((result) => result.audioData);
1006
- const inputSpecs = results.map((result) => result.audioSpec ?? inspectAudioSpecification(result.audioData, format));
1439
+ const inputSpecs = results.map((result) => {
1440
+ if (result.audioSpec) return result.audioSpec;
1441
+ try {
1442
+ return inspectAudioSpecification(result.audioData, format);
1443
+ } catch (error) {
1444
+ if (resolvedOptions.customMerger) return formatAudioSpecification(format);
1445
+ throw error;
1446
+ }
1447
+ });
1007
1448
  validateAudioSpecifications(inputSpecs);
1008
- const signal = resolvedOptions.signal ?? new AbortController().signal;
1449
+ const deadline = resolvedOptions.deadline;
1450
+ deadline?.throwIfExpired();
1451
+ const signal = resolvedOptions.signal ?? deadline?.signal ?? new AbortController().signal;
1009
1452
  if (signal.aborted) throw new SynthesisCancelledError();
1010
1453
  if (resolvedOptions.customMerger) {
1011
- return Promise.resolve().then(
1012
- () => resolvedOptions.customMerger?.(buffers, {
1013
- format,
1014
- outputMimeType: resolvedOptions.outputMimeType ?? resolveMimeType(format),
1015
- inputSpecs,
1016
- signal
1017
- })
1454
+ return withinDeadline(
1455
+ Promise.resolve().then(
1456
+ () => resolvedOptions.customMerger?.(buffers, {
1457
+ format,
1458
+ outputMimeType: resolvedOptions.outputMimeType ?? resolveMimeType(format),
1459
+ inputSpecs,
1460
+ signal
1461
+ })
1462
+ ),
1463
+ deadline
1018
1464
  ).then((merged) => {
1465
+ deadline?.throwIfExpired();
1019
1466
  if (!merged) throw new MergeError("The custom audio merger returned no audio buffer.");
1020
- if (!(merged instanceof ArrayBuffer) || buffers.some((buffer) => buffer.byteLength > 0) && merged.byteLength === 0)
1021
- throw new MergeError("The custom audio merger returned an invalid audio buffer.");
1022
1467
  if (signal.aborted) throw new SynthesisCancelledError();
1023
- const result = createMergedResult(
1024
- results,
1468
+ const mergedSpec = validateMergedAudioBuffer(
1025
1469
  merged,
1026
1470
  format,
1027
- inspectAudioSpecification(merged, format),
1028
- resolvedOptions.outputMimeType
1471
+ buffers,
1472
+ inputSpecs,
1473
+ resolvedOptions.outputMimeType ?? resolveMimeType(format),
1474
+ true
1029
1475
  );
1476
+ const result = createMergedResult(results, merged, format, mergedSpec, resolvedOptions.outputMimeType);
1030
1477
  return Promise.resolve(
1031
1478
  resolvedOptions.postMergeValidator?.(result, {
1032
1479
  format,
@@ -1035,6 +1482,7 @@ function mergeSynthesisResults(results, options) {
1035
1482
  signal
1036
1483
  })
1037
1484
  ).then((valid) => {
1485
+ deadline?.throwIfExpired();
1038
1486
  if (valid === false) throw new MergeError("The post-merge validator rejected the merged audio.");
1039
1487
  return result;
1040
1488
  });
@@ -1045,6 +1493,7 @@ function mergeSynthesisResults(results, options) {
1045
1493
  });
1046
1494
  }
1047
1495
  try {
1496
+ deadline?.throwIfExpired();
1048
1497
  const result = createMergedResult(
1049
1498
  results,
1050
1499
  mergeAudioBuffers(buffers, { format }),
@@ -1060,12 +1509,14 @@ function mergeSynthesisResults(results, options) {
1060
1509
  signal
1061
1510
  });
1062
1511
  if (validation instanceof Promise)
1063
- return validation.then((valid) => {
1512
+ return withinDeadline(validation, deadline).then((valid) => {
1513
+ deadline?.throwIfExpired();
1064
1514
  if (valid === false) throw new MergeError("The post-merge validator rejected the merged audio.");
1065
1515
  return result;
1066
1516
  });
1067
1517
  if (validation === false) throw new MergeError("The post-merge validator rejected the merged audio.");
1068
1518
  }
1519
+ deadline?.throwIfExpired();
1069
1520
  return result;
1070
1521
  } catch (error) {
1071
1522
  if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
@@ -1156,7 +1607,7 @@ function resolveConcurrency2(value, total) {
1156
1607
  if (value === Infinity) return Math.max(1, total);
1157
1608
  return Number.isFinite(value) ? Math.max(1, Math.floor(value)) : 1;
1158
1609
  }
1159
- async function retryableSynthesis(synthesize, options, signal, onRetry) {
1610
+ async function retryableSynthesis(synthesize, options, signal, onRetry, deadlineAtMs) {
1160
1611
  const retry = options ? {
1161
1612
  maxRetries: Math.max(0, Math.floor(options.maxRetries)),
1162
1613
  initialDelayMs: options.initialDelayMs,
@@ -1173,6 +1624,11 @@ async function retryableSynthesis(synthesize, options, signal, onRetry) {
1173
1624
  throw error;
1174
1625
  attempt += 1;
1175
1626
  const delayMs = retryDelayForError(retry, attempt, error);
1627
+ const retryAfterMs = getRetryAfterDelayMs(error);
1628
+ const remainingMs = deadlineAtMs === void 0 ? void 0 : Math.max(0, deadlineAtMs - Date.now());
1629
+ if (retryAfterMs !== void 0 && (retryAfterMs > retry.maxDelayMs || remainingMs !== void 0 && retryAfterMs > remainingMs)) {
1630
+ throw new Error("Speech synthesis timed out because Retry-After exceeded the available retry budget.");
1631
+ }
1176
1632
  onRetry(attempt, delayMs);
1177
1633
  if (delayMs > 0)
1178
1634
  await new Promise((resolve, reject) => {
@@ -1206,14 +1662,19 @@ function sharedValidationOptions(options, signal) {
1206
1662
  };
1207
1663
  }
1208
1664
  async function synthesizeSsmlSafe(client, ssml, options = {}) {
1209
- const validationOptions = sharedValidationOptions(options.validation ?? options, options.signal);
1665
+ const deadline = new DeadlineController(options.timeouts?.totalJobMs, options.signal);
1666
+ const validationOptions = sharedValidationOptions(options.validation ?? options, deadline.signal);
1210
1667
  const diagnostics = await Promise.resolve((0, import_ssml_core2.validateAzureSsml)(ssml, validationOptions));
1211
- if (options.signal?.aborted) {
1212
- const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
1668
+ if (deadline.signal.aborted) {
1669
+ const error = toSynthesisError(
1670
+ new Error(deadline.timedOut ? "Speech synthesis timed out." : "Speech synthesis was cancelled.")
1671
+ );
1672
+ deadline.dispose();
1213
1673
  return failure(error);
1214
1674
  }
1215
1675
  const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
1216
1676
  if (errors.length > 0) {
1677
+ deadline.dispose();
1217
1678
  return failure({
1218
1679
  kind: "validation-error",
1219
1680
  message: "SSML validation failed; the Azure Speech API was not called.",
@@ -1226,23 +1687,30 @@ async function synthesizeSsmlSafe(client, ssml, options = {}) {
1226
1687
  success: true,
1227
1688
  status: "success",
1228
1689
  value: await client.synthesizeSsml(ssml, {
1229
- signal: options.signal,
1690
+ signal: deadline.signal,
1230
1691
  timeoutMs: options.timeouts?.perChunkMs,
1231
- timeouts: options.timeouts
1692
+ timeouts: options.timeouts ? { ...options.timeouts, totalJobMs: void 0 } : void 0
1232
1693
  })
1233
1694
  };
1234
1695
  } catch (error) {
1696
+ if (deadline.timedOut) return failure(toSynthesisError(new Error("Speech synthesis timed out.")));
1235
1697
  const synthesisError = toSynthesisError(error);
1236
1698
  return failure(synthesisError);
1699
+ } finally {
1700
+ deadline.dispose();
1237
1701
  }
1238
1702
  }
1239
1703
  async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
1704
+ const deadline = new DeadlineController(options.timeouts?.totalJobMs, options.signal);
1240
1705
  const validationOptions = sharedValidationOptions(
1241
1706
  { ...options.validation ?? options, timeouts: options.timeouts },
1242
- options.signal
1707
+ deadline.signal
1243
1708
  );
1244
- if (options.signal?.aborted) {
1245
- const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
1709
+ if (deadline.signal.aborted) {
1710
+ const error = toSynthesisError(
1711
+ new Error(deadline.timedOut ? "Speech synthesis timed out." : "Speech synthesis was cancelled.")
1712
+ );
1713
+ deadline.dispose();
1246
1714
  return failure(error);
1247
1715
  }
1248
1716
  const pending = (index, status, error) => {
@@ -1274,14 +1742,16 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
1274
1742
  return diagnostics.filter((diagnostic) => diagnostic.severity === "error");
1275
1743
  })
1276
1744
  );
1277
- if (options.signal?.aborted) {
1745
+ if (deadline.signal.aborted) {
1278
1746
  const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
1747
+ deadline.dispose();
1279
1748
  return failure(error);
1280
1749
  }
1281
1750
  const chunkDiagnostics = validations.map((diagnostics, chunkIndex) => ({ chunkIndex, diagnostics })).filter((entry) => entry.diagnostics.length > 0);
1282
1751
  if (chunkDiagnostics.length > 0) {
1283
1752
  const error = new BatchChunkValidationError(chunkDiagnostics);
1284
1753
  for (const entry of chunkDiagnostics) pending(entry.chunkIndex, "failed", error);
1754
+ deadline.dispose();
1285
1755
  return failure(error);
1286
1756
  }
1287
1757
  let fallbackJobScope;
@@ -1294,9 +1764,9 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
1294
1764
  const value = await client.synthesizeChunks(normalizedChunks, {
1295
1765
  onProgress: options.onProgress,
1296
1766
  outputFormat: options.outputFormat,
1297
- signal: options.signal,
1767
+ signal: deadline.signal,
1298
1768
  timeoutMs: options.timeoutMs,
1299
- timeouts: options.timeouts,
1769
+ timeouts: options.timeouts ? { ...options.timeouts, totalJobMs: void 0 } : void 0,
1300
1770
  sourceNodePath: options.sourceNodePath,
1301
1771
  concurrency: options.concurrency,
1302
1772
  retryOptions: options.retryOptions,
@@ -1305,18 +1775,39 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
1305
1775
  resumeChunkIndices: options.resumeChunkIndices,
1306
1776
  customMerger: options.customMerger,
1307
1777
  outputMimeType: options.outputMimeType,
1308
- postMergeValidator: options.postMergeValidator
1778
+ postMergeValidator: options.postMergeValidator,
1779
+ resumeValidation: options.resumeValidation,
1780
+ customHeaders: options.customHeaders,
1781
+ fingerprintSchemaVersion: options.fingerprintSchemaVersion
1309
1782
  });
1310
1783
  return { ok: true, success: true, status: "success", value };
1311
1784
  }
1785
+ const inputs = chunks.map((chunk) => typeof chunk === "string" ? { ssml: chunk } : chunk);
1786
+ const fingerprints = inputs.map(
1787
+ (chunk) => computeChunkFingerprint(chunk.ssml, options.outputFormat, {
1788
+ customHeaders: options.customHeaders,
1789
+ fingerprintSchemaVersion: options.fingerprintSchemaVersion
1790
+ })
1791
+ );
1312
1792
  const results = new Array(chunks.length);
1793
+ const chunkStates = inputs.map((_chunk, chunkIndex) => ({
1794
+ chunkIndex,
1795
+ status: "pending",
1796
+ canResume: true
1797
+ }));
1313
1798
  const cachedChunks = new Map((options.resumeChunks ?? []).map((chunk) => [chunk.chunkIndex, chunk]));
1799
+ const invalidCachedIndices = /* @__PURE__ */ new Set();
1314
1800
  for (const [index, cached] of cachedChunks) {
1315
- if (index >= 0 && index < chunks.length) results[index] = cached;
1801
+ if (index < 0 || index >= chunks.length) continue;
1802
+ if (options.resumeValidation === "disabled" || cached.fingerprint === fingerprints[index]) {
1803
+ results[index] = cached;
1804
+ chunkStates[index] = { chunkIndex: index, status: "succeeded", canResume: true, result: cached };
1805
+ } else invalidCachedIndices.add(index);
1316
1806
  }
1317
1807
  const requestedIndices = options.resumeChunkIndices ? new Set(options.resumeChunkIndices.filter((index) => index >= 0 && index < chunks.length)) : void 0;
1318
- const shouldSynthesize = (index) => !cachedChunks.has(index) && (requestedIndices === void 0 || requestedIndices.has(index));
1319
- const jobScope = chunks.length > 1 || options.timeouts?.totalJobMs !== void 0 ? createSafeAbortScope(options.signal, options.timeouts?.totalJobMs) : void 0;
1808
+ const shouldSynthesize = (index) => (!cachedChunks.has(index) || invalidCachedIndices.has(index)) && (requestedIndices === void 0 || requestedIndices.has(index) || invalidCachedIndices.has(index));
1809
+ const jobDeadlineAt = deadline.deadlineAtMs;
1810
+ const jobScope = chunks.length > 1 || options.timeouts?.totalJobMs !== void 0 ? createSafeAbortScope(deadline.signal, void 0) : void 0;
1320
1811
  fallbackJobScope = jobScope;
1321
1812
  const failedIndices = /* @__PURE__ */ new Set();
1322
1813
  let firstError;
@@ -1328,7 +1819,10 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
1328
1819
  const index = nextIndex++;
1329
1820
  if (index >= chunks.length) return;
1330
1821
  if (!shouldSynthesize(index)) continue;
1331
- if (failedIndices.size > 0 && options.cancelOnFailure !== false) return;
1822
+ if (firstError && options.cancelOnFailure !== false) {
1823
+ chunkStates[index] = { chunkIndex: index, status: "cancelled", isOriginalFailure: false, canResume: true };
1824
+ return;
1825
+ }
1332
1826
  const chunk = chunks[index];
1333
1827
  const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
1334
1828
  const sourceNodePath = input.sourceNodePath;
@@ -1337,8 +1831,8 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
1337
1831
  const startedAt = Date.now();
1338
1832
  try {
1339
1833
  const chunkTimeout = options.timeouts?.chunkWithRetriesMs ?? options.timeouts?.perChunkMs;
1340
- const chunkScope = chunkTimeout !== void 0 || jobScope ? createSafeAbortScope(jobScope?.signal ?? options.signal, chunkTimeout ?? options.timeoutMs) : void 0;
1341
- const chunkSignal = chunkScope?.signal ?? options.signal;
1834
+ const chunkScope = chunkTimeout !== void 0 || jobScope ? createSafeAbortScope(jobScope?.signal ?? deadline.signal, chunkTimeout ?? options.timeoutMs) : void 0;
1835
+ const chunkSignal = chunkScope?.signal ?? deadline.signal;
1342
1836
  let result;
1343
1837
  try {
1344
1838
  result = await retryableSynthesis(
@@ -1361,10 +1855,11 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
1361
1855
  retryAttempt,
1362
1856
  nextRetryDelayMs,
1363
1857
  isRetrying: true
1364
- })
1858
+ }),
1859
+ jobDeadlineAt
1365
1860
  );
1366
1861
  } catch (error) {
1367
- if (chunkScope?.timedOut())
1862
+ if (chunkScope?.timedOut() || deadline.timedOut)
1368
1863
  throw new Error(`Speech synthesis timed out after ${chunkTimeout ?? options.timeoutMs} ms.`);
1369
1864
  throw error;
1370
1865
  } finally {
@@ -1413,6 +1908,7 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
1413
1908
  }))
1414
1909
  } : {}
1415
1910
  };
1911
+ chunkStates[index] = { chunkIndex: index, status: "succeeded", canResume: true, result: results[index] };
1416
1912
  completed += 1;
1417
1913
  options.onProgress?.({
1418
1914
  currentChunk: completed,
@@ -1424,7 +1920,16 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
1424
1920
  durationMs: Date.now() - startedAt
1425
1921
  });
1426
1922
  } catch (error) {
1427
- failedIndices.add(index);
1923
+ const wasCancelled = firstError !== void 0 || !deadline.timedOut && Boolean(jobScope?.signal.aborted && !jobScope?.timedOut());
1924
+ firstError ?? (firstError = deadline.timedOut ? new Error("Speech synthesis timed out.") : error);
1925
+ if (!wasCancelled) failedIndices.add(index);
1926
+ chunkStates[index] = {
1927
+ chunkIndex: index,
1928
+ status: wasCancelled ? "cancelled" : "failed",
1929
+ isOriginalFailure: !wasCancelled,
1930
+ canResume: true,
1931
+ error: serializeChunkError(error, "synthesis", !wasCancelled)
1932
+ };
1428
1933
  options.onProgress?.({
1429
1934
  currentChunk: completed,
1430
1935
  totalChunks: chunks.length,
@@ -1436,23 +1941,41 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
1436
1941
  error
1437
1942
  });
1438
1943
  if (options.cancelOnFailure !== false) jobScope?.abort();
1439
- firstError ?? (firstError = error);
1440
1944
  return;
1441
1945
  }
1442
1946
  }
1443
1947
  };
1444
1948
  await Promise.all(Array.from({ length: Math.min(concurrency, chunks.length) }, () => worker()));
1949
+ if (firstError && options.cancelOnFailure !== false) {
1950
+ for (const [chunkIndex, state] of chunkStates.entries()) {
1951
+ if (state.status === "pending" && shouldSynthesize(chunkIndex)) {
1952
+ chunkStates[chunkIndex] = { chunkIndex, status: "cancelled", isOriginalFailure: false, canResume: true };
1953
+ }
1954
+ }
1955
+ }
1445
1956
  if (failedIndices.size > 0) {
1446
1957
  const error = firstError ?? new Error("One or more SSML chunks failed to synthesize.");
1958
+ const synthesizedChunks = results.flatMap(
1959
+ (result, chunkIndex) => result ? [{ ...result, chunkIndex, fingerprint: fingerprints[chunkIndex] ?? "" }] : []
1960
+ );
1447
1961
  error.partialResult = {
1448
- synthesizedChunks: results.flatMap((result, chunkIndex) => result ? [{ ...result, chunkIndex }] : []),
1449
- completedChunks: results.flatMap((result, chunkIndex) => result ? [{ ...result, chunkIndex }] : []),
1450
- pendingChunkIndices: chunks.flatMap((_chunk, chunkIndex) => results[chunkIndex] ? [] : [chunkIndex]),
1962
+ synthesizedChunks,
1963
+ completedChunks: synthesizedChunks,
1964
+ pendingChunkIndices: chunkStates.flatMap(
1965
+ (state) => state.status === "pending" || state.status === "cancelled" || state.status === "failed" ? [state.chunkIndex] : []
1966
+ ),
1451
1967
  failedChunkIndices: [...failedIndices],
1968
+ cancelledChunkIndices: chunkStates.filter((state) => state.status === "cancelled").map((state) => state.chunkIndex),
1969
+ chunkStates,
1452
1970
  totalChunks: chunks.length
1453
1971
  };
1454
1972
  throw error;
1455
1973
  }
1974
+ const missingChunkIndices = Array.from(
1975
+ { length: chunks.length },
1976
+ (_value, index) => results[index] === void 0 ? index : void 0
1977
+ ).filter((index) => index !== void 0);
1978
+ if (missingChunkIndices.length > 0) throw new IncompleteChunkSetError(chunks.length, missingChunkIndices);
1456
1979
  const orderedResults = results.filter((result) => result !== void 0);
1457
1980
  return {
1458
1981
  ok: true,
@@ -1460,7 +1983,7 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
1460
1983
  status: "success",
1461
1984
  value: await mergeSynthesisResults(orderedResults, {
1462
1985
  format: options.outputFormat ?? "audio-16khz-128kbitrate-mono-mp3",
1463
- signal: jobScope?.signal ?? options.signal,
1986
+ signal: jobScope?.signal ?? deadline.signal,
1464
1987
  customMerger: options.customMerger,
1465
1988
  outputMimeType: options.outputMimeType,
1466
1989
  postMergeValidator: options.postMergeValidator
@@ -1471,6 +1994,7 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
1471
1994
  return failure(synthesisError, partialResultFrom(error));
1472
1995
  } finally {
1473
1996
  fallbackJobScope?.dispose();
1997
+ deadline.dispose();
1474
1998
  }
1475
1999
  }
1476
2000
  function withValidationSignal(options, signal) {
@@ -1491,14 +2015,43 @@ var AzureTtsClient = class {
1491
2015
  __privateSet(this, _options, options);
1492
2016
  }
1493
2017
  async synthesize(ssml) {
1494
- const { region, subscriptionKey, outputFormat, signal, timeoutMs, timeouts } = __privateGet(this, _options);
2018
+ const {
2019
+ region,
2020
+ subscriptionKey,
2021
+ outputFormat,
2022
+ signal,
2023
+ timeoutMs,
2024
+ timeouts,
2025
+ customHeaders,
2026
+ fingerprintSchemaVersion
2027
+ } = __privateGet(this, _options);
1495
2028
  const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
1496
2029
  __privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
1497
- const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs, timeouts };
2030
+ const config = {
2031
+ endpoint,
2032
+ region,
2033
+ subscriptionKey,
2034
+ outputFormat,
2035
+ signal,
2036
+ timeoutMs,
2037
+ timeouts,
2038
+ retryOptions: __privateGet(this, _options).retryOptions,
2039
+ customHeaders,
2040
+ fingerprintSchemaVersion
2041
+ };
1498
2042
  return synthesizeSpeech(ssml, config);
1499
2043
  }
1500
2044
  async synthesizeSsml(ssml, options = {}) {
1501
- const { region, subscriptionKey, outputFormat, signal, timeoutMs, timeouts } = __privateGet(this, _options);
2045
+ const {
2046
+ region,
2047
+ subscriptionKey,
2048
+ outputFormat,
2049
+ signal,
2050
+ timeoutMs,
2051
+ timeouts,
2052
+ customHeaders,
2053
+ fingerprintSchemaVersion
2054
+ } = __privateGet(this, _options);
1502
2055
  const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
1503
2056
  __privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
1504
2057
  return synthesizeSsml(ssml, {
@@ -1511,11 +2064,28 @@ var AzureTtsClient = class {
1511
2064
  timeouts: options.timeouts ?? timeouts,
1512
2065
  sourceNodePath: options.sourceNodePath,
1513
2066
  sourceTextSegments: options.sourceTextSegments,
1514
- sourceMarkers: options.sourceMarkers
2067
+ sourceMarkers: options.sourceMarkers,
2068
+ retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions,
2069
+ cancelOnFailure: options.cancelOnFailure ?? __privateGet(this, _options).cancelOnFailure,
2070
+ customMerger: options.customMerger ?? __privateGet(this, _options).customMerger,
2071
+ outputMimeType: options.outputMimeType ?? __privateGet(this, _options).outputMimeType,
2072
+ postMergeValidator: options.postMergeValidator ?? __privateGet(this, _options).postMergeValidator,
2073
+ resumeValidation: options.resumeValidation ?? __privateGet(this, _options).resumeValidation,
2074
+ customHeaders: options.customHeaders ?? customHeaders,
2075
+ fingerprintSchemaVersion: options.fingerprintSchemaVersion ?? fingerprintSchemaVersion
1515
2076
  });
1516
2077
  }
1517
2078
  async synthesizeChunks(chunks, options = {}) {
1518
- const { region, subscriptionKey, outputFormat, signal, timeoutMs, timeouts } = __privateGet(this, _options);
2079
+ const {
2080
+ region,
2081
+ subscriptionKey,
2082
+ outputFormat,
2083
+ signal,
2084
+ timeoutMs,
2085
+ timeouts,
2086
+ customHeaders,
2087
+ fingerprintSchemaVersion
2088
+ } = __privateGet(this, _options);
1519
2089
  const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
1520
2090
  return synthesizeSsmlChunks(chunks, {
1521
2091
  endpoint,
@@ -1529,12 +2099,15 @@ var AzureTtsClient = class {
1529
2099
  onProgress: options.onProgress ?? __privateGet(this, _options).onProgress,
1530
2100
  concurrency: options.concurrency ?? __privateGet(this, _options).concurrency,
1531
2101
  retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions,
1532
- cancelOnFailure: options.cancelOnFailure,
2102
+ cancelOnFailure: options.cancelOnFailure ?? __privateGet(this, _options).cancelOnFailure,
1533
2103
  resumeChunks: options.resumeChunks,
1534
2104
  resumeChunkIndices: options.resumeChunkIndices,
1535
- customMerger: options.customMerger,
1536
- outputMimeType: options.outputMimeType,
1537
- postMergeValidator: options.postMergeValidator
2105
+ customMerger: options.customMerger ?? __privateGet(this, _options).customMerger,
2106
+ outputMimeType: options.outputMimeType ?? __privateGet(this, _options).outputMimeType,
2107
+ postMergeValidator: options.postMergeValidator ?? __privateGet(this, _options).postMergeValidator,
2108
+ resumeValidation: options.resumeValidation ?? __privateGet(this, _options).resumeValidation,
2109
+ customHeaders: options.customHeaders ?? customHeaders,
2110
+ fingerprintSchemaVersion: options.fingerprintSchemaVersion ?? fingerprintSchemaVersion
1538
2111
  });
1539
2112
  }
1540
2113
  async synthesizeSsmlSafe(ssml, options = {}) {
@@ -1549,7 +2122,14 @@ var AzureTtsClient = class {
1549
2122
  timeouts: options.timeouts ?? __privateGet(this, _options).timeouts,
1550
2123
  onProgress: options.onProgress ?? __privateGet(this, _options).onProgress,
1551
2124
  concurrency: options.concurrency ?? __privateGet(this, _options).concurrency,
1552
- retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions
2125
+ retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions,
2126
+ cancelOnFailure: options.cancelOnFailure ?? __privateGet(this, _options).cancelOnFailure,
2127
+ customMerger: options.customMerger ?? __privateGet(this, _options).customMerger,
2128
+ outputMimeType: options.outputMimeType ?? __privateGet(this, _options).outputMimeType,
2129
+ postMergeValidator: options.postMergeValidator ?? __privateGet(this, _options).postMergeValidator,
2130
+ resumeValidation: options.resumeValidation ?? __privateGet(this, _options).resumeValidation,
2131
+ customHeaders: options.customHeaders ?? __privateGet(this, _options).customHeaders,
2132
+ fingerprintSchemaVersion: options.fingerprintSchemaVersion ?? __privateGet(this, _options).fingerprintSchemaVersion
1553
2133
  });
1554
2134
  }
1555
2135
  async synthesizeSsmlChunksSafe(chunks, options = {}) {
@@ -1655,11 +2235,14 @@ async function fetchAzureVoiceCatalog(options) {
1655
2235
  BatchChunkValidationError,
1656
2236
  ChunkValidationError,
1657
2237
  DEFAULT_OUTPUT_FORMAT,
2238
+ DeadlineController,
2239
+ IncompleteChunkSetError,
1658
2240
  MergeError,
1659
2241
  SynthesisCancelledError,
1660
2242
  SynthesisTimeoutError,
1661
2243
  UnsupportedMergeFormatError,
1662
2244
  canMergeAudioFormat,
2245
+ computeChunkFingerprint,
1663
2246
  fetchAzureVoiceCatalog,
1664
2247
  getRetryAfterDelayMs,
1665
2248
  inspectAudioSpecification,
@@ -1667,6 +2250,7 @@ async function fetchAzureVoiceCatalog(options) {
1667
2250
  mergeSynthesisResults,
1668
2251
  resolveMergeAudioFormat,
1669
2252
  resolveMimeType,
2253
+ serializeChunkError,
1670
2254
  synthesizeSpeech,
1671
2255
  synthesizeSsml,
1672
2256
  synthesizeSsmlChunks,