@ssml-builder-js/azure-tts-client 2.12.0 → 2.14.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
@@ -40,9 +40,18 @@ __export(index_exports, {
40
40
  AzureTtsClient: () => AzureTtsClient,
41
41
  AzureTtsError: () => AzureTtsError,
42
42
  AzureTtsSdkError: () => AzureTtsSdkError,
43
+ ChunkValidationError: () => ChunkValidationError,
44
+ UnsupportedMergeFormatError: () => UnsupportedMergeFormatError,
45
+ canMergeAudioFormat: () => canMergeAudioFormat,
43
46
  fetchAzureVoiceCatalog: () => fetchAzureVoiceCatalog,
47
+ mergeAudioBuffers: () => mergeAudioBuffers,
48
+ mergeSynthesisResults: () => mergeSynthesisResults,
49
+ resolveMergeAudioFormat: () => resolveMergeAudioFormat,
44
50
  synthesizeSpeech: () => synthesizeSpeech,
45
- synthesizeSsml: () => synthesizeSsml
51
+ synthesizeSsml: () => synthesizeSsml,
52
+ synthesizeSsmlChunks: () => synthesizeSsmlChunks,
53
+ synthesizeSsmlChunksSafe: () => synthesizeSsmlChunksSafe,
54
+ synthesizeSsmlSafe: () => synthesizeSsmlSafe
46
55
  });
47
56
  module.exports = __toCommonJS(index_exports);
48
57
 
@@ -65,6 +74,13 @@ var AzureTtsSdkError = class extends AzureTtsError {
65
74
  this.errorDetails = errorDetails;
66
75
  }
67
76
  };
77
+ var UnsupportedMergeFormatError = class extends Error {
78
+ constructor(format) {
79
+ super(`Audio format "${format}" cannot be safely concatenated; container re-multiplexing is required.`);
80
+ this.name = "UnsupportedMergeFormatError";
81
+ this.format = format;
82
+ }
83
+ };
68
84
  function createSpeechSdkError(error) {
69
85
  const message = error instanceof Error ? error.message : String(error);
70
86
  return new AzureTtsSdkError(message);
@@ -142,6 +158,149 @@ function createSpeechConfig(config) {
142
158
  }
143
159
 
144
160
  // src/synthesis.ts
161
+ function ascii(bytes, offset, value) {
162
+ return [...value].every((character, index) => bytes[offset + index] === character.charCodeAt(0));
163
+ }
164
+ function readUint32(bytes, offset) {
165
+ return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(offset, true);
166
+ }
167
+ function parseWav(buffer) {
168
+ const bytes = new Uint8Array(buffer);
169
+ if (bytes.byteLength < 12 || !ascii(bytes, 0, "RIFF") || !ascii(bytes, 8, "WAVE")) {
170
+ throw new Error("Invalid WAV/RIFF audio buffer.");
171
+ }
172
+ const chunks = [];
173
+ const dataParts = [];
174
+ let format;
175
+ let offset = 12;
176
+ while (offset < bytes.byteLength) {
177
+ if (offset + 8 > bytes.byteLength) throw new Error("Invalid WAV chunk header.");
178
+ const id = String.fromCharCode(...bytes.slice(offset, offset + 4));
179
+ const size = readUint32(bytes, offset + 4);
180
+ const dataStart = offset + 8;
181
+ const dataEnd = dataStart + size;
182
+ if (dataEnd > bytes.byteLength) throw new Error(`WAV chunk "${id}" exceeds the audio buffer.`);
183
+ const data2 = bytes.slice(dataStart, dataEnd);
184
+ chunks.push({ id, data: data2 });
185
+ if (id === "fmt ") format ?? (format = data2);
186
+ if (id === "data") dataParts.push(data2);
187
+ offset = dataEnd + (size & 1);
188
+ if (offset > bytes.byteLength) throw new Error("Invalid WAV chunk padding.");
189
+ }
190
+ if (!format || dataParts.length === 0) throw new Error("WAV audio must contain fmt and data chunks.");
191
+ const dataLength = dataParts.reduce((total, part) => total + part.byteLength, 0);
192
+ const data = new Uint8Array(dataLength);
193
+ let dataOffset = 0;
194
+ for (const part of dataParts) {
195
+ data.set(part, dataOffset);
196
+ dataOffset += part.byteLength;
197
+ }
198
+ return { chunks, data, format };
199
+ }
200
+ function writeUint32(target, offset, value) {
201
+ new DataView(target.buffer).setUint32(offset, value, true);
202
+ }
203
+ function writeChunk(target, offset, id, data) {
204
+ for (let index = 0; index < 4; index += 1) target[offset + index] = id.charCodeAt(index) ?? 0;
205
+ writeUint32(target, offset + 4, data.byteLength);
206
+ target.set(data, offset + 8);
207
+ const end = offset + 8 + data.byteLength;
208
+ if (data.byteLength & 1) target[end] = 0;
209
+ return end + (data.byteLength & 1);
210
+ }
211
+ function mergeWavBuffers(buffers) {
212
+ if (buffers.length === 0) return new ArrayBuffer(0);
213
+ const parsed = buffers.map(parseWav);
214
+ const first = parsed[0];
215
+ if (!first) throw new Error("At least one WAV buffer is required.");
216
+ if (parsed.some(
217
+ (item) => item.format.length !== first.format.length || item.format.some((value, i) => value !== first.format[i])
218
+ ))
219
+ throw new Error("WAV buffers have incompatible fmt chunks.");
220
+ const dataLength = parsed.reduce((total, item) => total + item.data.byteLength, 0);
221
+ const nonDataLength = first.chunks.reduce(
222
+ (total, chunk) => chunk.id === "data" ? total : total + 8 + chunk.data.byteLength + (chunk.data.byteLength & 1),
223
+ 0
224
+ );
225
+ const outputLength = 12 + nonDataLength + 8 + dataLength + (dataLength & 1);
226
+ if (outputLength - 8 > 4294967295) throw new RangeError("Merged WAV exceeds the RIFF format size limit.");
227
+ const output = new Uint8Array(outputLength);
228
+ output.set(Uint8Array.from([82, 73, 70, 70]), 0);
229
+ writeUint32(output, 4, outputLength - 8);
230
+ output.set(Uint8Array.from([87, 65, 86, 69]), 8);
231
+ let outputOffset = 12;
232
+ let dataWritten = false;
233
+ for (const chunk of first.chunks) {
234
+ if (chunk.id === "data") {
235
+ if (dataWritten) continue;
236
+ const data = new Uint8Array(dataLength);
237
+ let dataOffset = 0;
238
+ for (const item of parsed) {
239
+ data.set(item.data, dataOffset);
240
+ dataOffset += item.data.byteLength;
241
+ }
242
+ outputOffset = writeChunk(output, outputOffset, "data", data);
243
+ dataWritten = true;
244
+ } else {
245
+ outputOffset = writeChunk(output, outputOffset, chunk.id, chunk.data);
246
+ }
247
+ }
248
+ if (!dataWritten) throw new Error("WAV audio must contain a data chunk.");
249
+ return output.buffer;
250
+ }
251
+ function skipId3v2(bytes) {
252
+ if (!ascii(bytes, 0, "ID3") || bytes.byteLength < 10) return 0;
253
+ const size = [bytes[6], bytes[7], bytes[8], bytes[9]].reduce((total, value) => total << 7 | value & 127, 0);
254
+ const hasFooter = (bytes[5] & 16) !== 0;
255
+ return Math.min(bytes.byteLength, 10 + size + (hasFooter ? 10 : 0));
256
+ }
257
+ function stripMp3Tags(buffer) {
258
+ const bytes = new Uint8Array(buffer);
259
+ const start = skipId3v2(bytes);
260
+ const end = bytes.byteLength >= 128 && ascii(bytes, bytes.byteLength - 128, "TAG") ? bytes.byteLength - 128 : bytes.byteLength;
261
+ return bytes.slice(Math.min(start, end), end);
262
+ }
263
+ function isMp3Format(format) {
264
+ return /(?:mp3|mpeg)/i.test(format);
265
+ }
266
+ function isWavFormat(format) {
267
+ return /(?:wav|wave|riff)/i.test(format);
268
+ }
269
+ function isRawFormat(format) {
270
+ return /^raw(?:-|$)/i.test(format);
271
+ }
272
+ function resolveMergeAudioFormat(format) {
273
+ if (isWavFormat(format)) return "wav";
274
+ if (isMp3Format(format)) return "mp3";
275
+ if (isRawFormat(format)) return "raw";
276
+ return void 0;
277
+ }
278
+ function canMergeAudioFormat(format) {
279
+ return resolveMergeAudioFormat(format) !== void 0;
280
+ }
281
+ function mergeAudioBuffers(buffers, format) {
282
+ if (isWavFormat(format)) return mergeWavBuffers(buffers);
283
+ if (isMp3Format(format)) {
284
+ const parts = buffers.map(stripMp3Tags);
285
+ const output = new Uint8Array(parts.reduce((total, part) => total + part.byteLength, 0));
286
+ let offset = 0;
287
+ for (const part of parts) {
288
+ output.set(part, offset);
289
+ offset += part.byteLength;
290
+ }
291
+ return output.buffer;
292
+ }
293
+ if (isRawFormat(format)) {
294
+ const output = new Uint8Array(buffers.reduce((total, buffer) => total + buffer.byteLength, 0));
295
+ let offset = 0;
296
+ for (const buffer of buffers) {
297
+ output.set(new Uint8Array(buffer), offset);
298
+ offset += buffer.byteLength;
299
+ }
300
+ return output.buffer;
301
+ }
302
+ throw new UnsupportedMergeFormatError(format);
303
+ }
145
304
  function closeSpeechResources(speechConfig, synthesizer) {
146
305
  try {
147
306
  synthesizer.close();
@@ -214,12 +373,26 @@ async function synthesizeSsml(ssml, config) {
214
373
  ...(bookmarks ?? []).map((bookmark) => bookmark.audioOffsetMs)
215
374
  );
216
375
  const durationMs = result.audioDuration ? ticksToMilliseconds(result.audioDuration) : eventDurationMs;
376
+ const requestId = result.resultId;
377
+ const addSourceMetadata = (event) => ({
378
+ ...event,
379
+ ...config.sourceTextRange ? { textRange: { ...config.sourceTextRange } } : {},
380
+ ...config.sourceTextRange ? { originalTextRange: { ...config.sourceTextRange } } : {},
381
+ ...config.chunkIndex !== void 0 ? { chunkIndex: config.chunkIndex } : {},
382
+ ...config.sourceNodePath ? { sourceNodePath: [...config.sourceNodePath] } : {},
383
+ ...requestId ? { requestId } : {}
384
+ });
385
+ const sourceBoundaries = boundaries.map((boundary) => addSourceMetadata(boundary));
386
+ const sourceVisemes = visemes.map((viseme) => addSourceMetadata(viseme));
387
+ const sourceBookmarks = bookmarks.map((bookmark) => addSourceMetadata(bookmark));
217
388
  resolve({
218
389
  audioData: result.audioData,
219
390
  durationMs,
220
- ...boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {},
221
- ...visemes.length > 0 ? { visemes } : {},
222
- ...bookmarks.length > 0 ? { bookmarks } : {}
391
+ ...config.sourceTextRange ? { textRange: { ...config.sourceTextRange } } : {},
392
+ ...requestId ? { requestId } : {},
393
+ ...sourceBoundaries.length > 0 ? { boundaries: sourceBoundaries, wordBoundary: sourceBoundaries, wordBoundaries: sourceBoundaries } : {},
394
+ ...sourceVisemes.length > 0 ? { visemes: sourceVisemes } : {},
395
+ ...sourceBookmarks.length > 0 ? { bookmarks: sourceBookmarks } : {}
223
396
  });
224
397
  };
225
398
  try {
@@ -239,10 +412,273 @@ async function synthesizeSsml(ssml, config) {
239
412
  }
240
413
  });
241
414
  }
415
+ async function synthesizeSsmlChunks(chunks, config) {
416
+ const results = [];
417
+ const totalChunks = chunks.length;
418
+ const report = (event) => config.onProgress?.(event);
419
+ for (const [index, chunk] of chunks.entries()) {
420
+ const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
421
+ report({
422
+ currentChunk: index,
423
+ totalChunks,
424
+ percent: totalChunks === 0 ? 100 : Math.round(index / totalChunks * 100),
425
+ chunkIndex: index,
426
+ originalTextRange: input.originalTextRange,
427
+ status: "pending",
428
+ durationMs: 0
429
+ });
430
+ }
431
+ for (const [index, chunk] of chunks.entries()) {
432
+ const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
433
+ report({
434
+ currentChunk: index,
435
+ totalChunks,
436
+ percent: totalChunks === 0 ? 100 : Math.round(index / totalChunks * 100),
437
+ chunkIndex: index,
438
+ originalTextRange: input.originalTextRange,
439
+ status: "synthesizing",
440
+ durationMs: 0
441
+ });
442
+ const startedAt = Date.now();
443
+ try {
444
+ const result = await synthesizeSsml(input.ssml, {
445
+ ...config,
446
+ ...input.originalTextRange ? { sourceTextRange: input.originalTextRange } : {},
447
+ ...input.sourceNodePath ? { sourceNodePath: input.sourceNodePath } : {},
448
+ chunkIndex: index,
449
+ onProgress: void 0
450
+ });
451
+ results.push(result);
452
+ report({
453
+ currentChunk: index + 1,
454
+ totalChunks,
455
+ percent: totalChunks === 0 ? 100 : Math.round((index + 1) / totalChunks * 100),
456
+ chunkIndex: index,
457
+ originalTextRange: input.originalTextRange,
458
+ status: "success",
459
+ durationMs: Date.now() - startedAt
460
+ });
461
+ } catch (error) {
462
+ report({
463
+ currentChunk: index,
464
+ totalChunks,
465
+ percent: totalChunks === 0 ? 100 : Math.round(index / totalChunks * 100),
466
+ chunkIndex: index,
467
+ originalTextRange: input.originalTextRange,
468
+ status: "failed",
469
+ durationMs: Date.now() - startedAt,
470
+ error
471
+ });
472
+ throw error;
473
+ }
474
+ }
475
+ return mergeSynthesisResults(results, config.outputFormat ?? "audio-16khz-128kbitrate-mono-mp3");
476
+ }
477
+ function mergeSynthesisResults(results, format) {
478
+ const audioData = format ? new Uint8Array(
479
+ mergeAudioBuffers(
480
+ results.map((result) => result.audioData),
481
+ format
482
+ )
483
+ ) : new Uint8Array(results.reduce((total, result) => total + result.audioData.byteLength, 0));
484
+ if (!format) {
485
+ let offset = 0;
486
+ for (const result of results) {
487
+ audioData.set(new Uint8Array(result.audioData), offset);
488
+ offset += result.audioData.byteLength;
489
+ }
490
+ }
491
+ const boundaries = [];
492
+ const visemes = [];
493
+ const bookmarks = [];
494
+ let durationOffset = 0;
495
+ for (const result of results) {
496
+ const chunkBoundaries = result.boundaries && result.boundaries.length > 0 ? result.boundaries : result.wordBoundary ?? result.wordBoundaries ?? [];
497
+ for (const boundary of chunkBoundaries) {
498
+ const textRange = boundary.textRange ?? result.textRange;
499
+ const originalTextRange = boundary.originalTextRange ?? textRange;
500
+ const requestId = boundary.requestId ?? result.requestId;
501
+ boundaries.push({
502
+ ...boundary,
503
+ audioOffsetMs: boundary.audioOffsetMs + durationOffset,
504
+ chunkAudioOffsetMs: boundary.chunkAudioOffsetMs ?? boundary.audioOffsetMs,
505
+ ...boundary.chunkIndex === void 0 ? { chunkIndex: results.indexOf(result) } : {},
506
+ ...boundary.sourceNodePath ? { sourceNodePath: [...boundary.sourceNodePath] } : {},
507
+ ...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
508
+ ...textRange ? { textRange: { ...textRange } } : {},
509
+ ...requestId ? { requestId } : {}
510
+ });
511
+ }
512
+ for (const viseme of result.visemes ?? []) {
513
+ const textRange = viseme.textRange ?? result.textRange;
514
+ const originalTextRange = viseme.originalTextRange ?? textRange;
515
+ const requestId = viseme.requestId ?? result.requestId;
516
+ visemes.push({
517
+ ...viseme,
518
+ audioOffsetMs: viseme.audioOffsetMs + durationOffset,
519
+ chunkAudioOffsetMs: viseme.chunkAudioOffsetMs ?? viseme.audioOffsetMs,
520
+ ...viseme.chunkIndex === void 0 ? { chunkIndex: results.indexOf(result) } : {},
521
+ ...viseme.sourceNodePath ? { sourceNodePath: [...viseme.sourceNodePath] } : {},
522
+ ...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
523
+ ...textRange ? { textRange: { ...textRange } } : {},
524
+ ...requestId ? { requestId } : {}
525
+ });
526
+ }
527
+ for (const bookmark of result.bookmarks ?? []) {
528
+ const textRange = bookmark.textRange ?? result.textRange;
529
+ const originalTextRange = bookmark.originalTextRange ?? textRange;
530
+ const requestId = bookmark.requestId ?? result.requestId;
531
+ bookmarks.push({
532
+ ...bookmark,
533
+ audioOffsetMs: bookmark.audioOffsetMs + durationOffset,
534
+ chunkAudioOffsetMs: bookmark.chunkAudioOffsetMs ?? bookmark.audioOffsetMs,
535
+ ...bookmark.chunkIndex === void 0 ? { chunkIndex: results.indexOf(result) } : {},
536
+ ...bookmark.sourceNodePath ? { sourceNodePath: [...bookmark.sourceNodePath] } : {},
537
+ ...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
538
+ ...textRange ? { textRange: { ...textRange } } : {},
539
+ ...requestId ? { requestId } : {}
540
+ });
541
+ }
542
+ durationOffset += Math.max(0, result.durationMs);
543
+ }
544
+ return {
545
+ audioData: audioData.buffer,
546
+ durationMs: durationOffset,
547
+ ...boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {},
548
+ ...visemes.length > 0 ? { visemes } : {},
549
+ ...bookmarks.length > 0 ? { bookmarks } : {},
550
+ ...results.length === 1 && results[0]?.requestId ? { requestId: results[0].requestId } : {},
551
+ ...results.length === 1 && results[0]?.textRange ? { textRange: { ...results[0].textRange } } : {}
552
+ };
553
+ }
242
554
  async function synthesizeSpeech(ssml, config) {
243
555
  return (await synthesizeSsml(ssml, config)).audioData;
244
556
  }
245
557
 
558
+ // src/safe.ts
559
+ var import_ssml_core = require("@ssml-builder-js/ssml-core");
560
+ var ChunkValidationError = class extends Error {
561
+ constructor(chunkIndex, diagnostics) {
562
+ super(`SSML validation failed for chunk ${chunkIndex}; the Azure Speech API was not called.`);
563
+ this.kind = "chunk-validation";
564
+ this.name = "ChunkValidationError";
565
+ this.chunkIndex = chunkIndex;
566
+ this.diagnostics = diagnostics;
567
+ }
568
+ };
569
+ async function synthesizeSsmlSafe(client, ssml, options = {}) {
570
+ const validationOptions = options.validation ?? options;
571
+ const diagnostics = await Promise.resolve((0, import_ssml_core.validateAzureSsml)(ssml, validationOptions));
572
+ const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
573
+ if (errors.length > 0) {
574
+ return {
575
+ ok: false,
576
+ success: false,
577
+ status: "validation-error",
578
+ error: {
579
+ kind: "validation",
580
+ message: "SSML validation failed; the Azure Speech API was not called.",
581
+ diagnostics: errors
582
+ }
583
+ };
584
+ }
585
+ try {
586
+ return { ok: true, success: true, status: "success", value: await client.synthesizeSsml(ssml) };
587
+ } catch (error) {
588
+ const azureError = error instanceof AzureTtsError ? error : createSpeechSdkError(error);
589
+ return { ok: false, success: false, status: "azure-api-error", error: azureError };
590
+ }
591
+ }
592
+ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
593
+ const validationOptions = options.validation ?? options;
594
+ const pending = (index, status, error) => {
595
+ options.onProgress?.({
596
+ currentChunk: status === "success" ? index + 1 : index,
597
+ totalChunks: chunks.length,
598
+ percent: chunks.length === 0 ? 100 : Math.round((status === "success" ? index + 1 : index) / chunks.length * 100),
599
+ chunkIndex: index,
600
+ originalTextRange: typeof chunks[index] === "string" ? void 0 : chunks[index]?.originalTextRange,
601
+ status,
602
+ durationMs: 0,
603
+ ...error ? { error } : {}
604
+ });
605
+ };
606
+ chunks.forEach((_chunk, index) => {
607
+ pending(index, "pending");
608
+ });
609
+ const validations = await Promise.all(
610
+ chunks.map(async (chunk) => {
611
+ const ssml = typeof chunk === "string" ? chunk : chunk.ssml;
612
+ const diagnostics = await Promise.resolve((0, import_ssml_core.validateAzureSsml)(ssml, validationOptions));
613
+ return diagnostics.filter((diagnostic) => diagnostic.severity === "error");
614
+ })
615
+ );
616
+ const firstInvalidIndex = validations.findIndex((diagnostics) => diagnostics.length > 0);
617
+ if (firstInvalidIndex >= 0) {
618
+ const error = new ChunkValidationError(firstInvalidIndex, validations[firstInvalidIndex] ?? []);
619
+ pending(firstInvalidIndex, "failed", error);
620
+ return { ok: false, success: false, status: "validation-error", error };
621
+ }
622
+ try {
623
+ if (client.synthesizeChunks) {
624
+ const value = await client.synthesizeChunks(chunks, { onProgress: options.onProgress });
625
+ return { ok: true, success: true, status: "success", value };
626
+ }
627
+ const results = [];
628
+ for (const [index, chunk] of chunks.entries()) {
629
+ const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
630
+ const sourceNodePath = input.sourceNodePath;
631
+ pending(index, "synthesizing");
632
+ const startedAt = Date.now();
633
+ try {
634
+ const result = await client.synthesizeSsml(input.ssml);
635
+ results.push({
636
+ ...result,
637
+ ...input.originalTextRange ? { textRange: { ...input.originalTextRange } } : {},
638
+ ...sourceNodePath ? {
639
+ boundaries: result.boundaries?.map((event) => ({
640
+ ...event,
641
+ sourceNodePath: [...sourceNodePath]
642
+ })),
643
+ visemes: result.visemes?.map((event) => ({ ...event, sourceNodePath: [...sourceNodePath] })),
644
+ bookmarks: result.bookmarks?.map((event) => ({ ...event, sourceNodePath: [...sourceNodePath] }))
645
+ } : {}
646
+ });
647
+ options.onProgress?.({
648
+ currentChunk: index + 1,
649
+ totalChunks: chunks.length,
650
+ percent: chunks.length === 0 ? 100 : Math.round((index + 1) / chunks.length * 100),
651
+ chunkIndex: index,
652
+ originalTextRange: input.originalTextRange,
653
+ status: "success",
654
+ durationMs: Date.now() - startedAt
655
+ });
656
+ } catch (error) {
657
+ options.onProgress?.({
658
+ currentChunk: index,
659
+ totalChunks: chunks.length,
660
+ percent: chunks.length === 0 ? 100 : Math.round(index / chunks.length * 100),
661
+ chunkIndex: index,
662
+ originalTextRange: input.originalTextRange,
663
+ status: "failed",
664
+ durationMs: Date.now() - startedAt,
665
+ error
666
+ });
667
+ throw error;
668
+ }
669
+ }
670
+ return {
671
+ ok: true,
672
+ success: true,
673
+ status: "success",
674
+ value: mergeSynthesisResults(results, options.outputFormat)
675
+ };
676
+ } catch (error) {
677
+ const azureError = error instanceof AzureTtsError ? error : createSpeechSdkError(error);
678
+ return { ok: false, success: false, status: "azure-api-error", error: azureError };
679
+ }
680
+ }
681
+
246
682
  // src/client.ts
247
683
  var ENDPOINT_TEMPLATE = "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
248
684
  var _options;
@@ -264,6 +700,32 @@ var AzureTtsClient = class {
264
700
  __privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
265
701
  return synthesizeSsml(ssml, { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs });
266
702
  }
703
+ async synthesizeChunks(chunks, options = {}) {
704
+ const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
705
+ const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
706
+ return synthesizeSsmlChunks(chunks, {
707
+ endpoint,
708
+ region,
709
+ subscriptionKey,
710
+ outputFormat,
711
+ signal,
712
+ timeoutMs,
713
+ onProgress: options.onProgress ?? __privateGet(this, _options).onProgress
714
+ });
715
+ }
716
+ async synthesizeSsmlSafe(ssml, options = {}) {
717
+ return synthesizeSsmlSafe(this, ssml, options);
718
+ }
719
+ async synthesizeChunksSafe(chunks, options = {}) {
720
+ return synthesizeSsmlChunksSafe(this, chunks, {
721
+ ...options,
722
+ outputFormat: options.outputFormat ?? __privateGet(this, _options).outputFormat,
723
+ onProgress: options.onProgress ?? __privateGet(this, _options).onProgress
724
+ });
725
+ }
726
+ async synthesizeSsmlChunksSafe(chunks, options = {}) {
727
+ return this.synthesizeChunksSafe(chunks, options);
728
+ }
267
729
  };
268
730
  _options = new WeakMap();
269
731
 
@@ -319,6 +781,9 @@ async function fetchAzureVoiceCatalog(options) {
319
781
  const secondaryLocales = stringList(record.SecondaryLocaleList);
320
782
  const styles = stringList(record.StyleList);
321
783
  const status = normalizeStatus(record.Status);
784
+ const supportedTags = stringList(record.SupportedTags);
785
+ const unsupportedTags = stringList(record.UnsupportedTags);
786
+ const models = stringList(record.Models);
322
787
  const merged = {
323
788
  name: existing?.name ?? name,
324
789
  locale: existing?.locale ?? locale,
@@ -328,6 +793,12 @@ async function fetchAzureVoiceCatalog(options) {
328
793
  if (mergedSecondaryLocales.length > 0) merged.secondaryLocales = mergedSecondaryLocales;
329
794
  const mergedStyles = [.../* @__PURE__ */ new Set([...existing?.styles ?? [], ...styles])];
330
795
  if (mergedStyles.length > 0) merged.styles = mergedStyles;
796
+ const mergedSupportedTags = [.../* @__PURE__ */ new Set([...existing?.supportedTags ?? [], ...supportedTags])];
797
+ if (mergedSupportedTags.length > 0) merged.supportedTags = mergedSupportedTags;
798
+ const mergedUnsupportedTags = [.../* @__PURE__ */ new Set([...existing?.unsupportedTags ?? [], ...unsupportedTags])];
799
+ if (mergedUnsupportedTags.length > 0) merged.unsupportedTags = mergedUnsupportedTags;
800
+ const mergedModels = [.../* @__PURE__ */ new Set([...existing?.models ?? [], ...models])];
801
+ if (mergedModels.length > 0) merged.models = mergedModels;
331
802
  if (status) merged.status = status;
332
803
  else if (existing?.status) merged.status = existing.status;
333
804
  voices.set(key, merged);
@@ -349,8 +820,17 @@ async function fetchAzureVoiceCatalog(options) {
349
820
  AzureTtsClient,
350
821
  AzureTtsError,
351
822
  AzureTtsSdkError,
823
+ ChunkValidationError,
824
+ UnsupportedMergeFormatError,
825
+ canMergeAudioFormat,
352
826
  fetchAzureVoiceCatalog,
827
+ mergeAudioBuffers,
828
+ mergeSynthesisResults,
829
+ resolveMergeAudioFormat,
353
830
  synthesizeSpeech,
354
- synthesizeSsml
831
+ synthesizeSsml,
832
+ synthesizeSsmlChunks,
833
+ synthesizeSsmlChunksSafe,
834
+ synthesizeSsmlSafe
355
835
  });
356
836
  //# sourceMappingURL=index.js.map