ssml-builder-js 2.14.0 → 2.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -37,10 +37,15 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
37
37
  // src/index.ts
38
38
  var src_exports = {};
39
39
  __export(src_exports, {
40
+ AudioFormatMismatchError: () => AudioFormatMismatchError,
40
41
  AzureTtsClient: () => AzureTtsClient,
41
42
  AzureTtsError: () => AzureTtsError,
42
43
  AzureTtsSdkError: () => AzureTtsSdkError,
43
44
  ChunkValidationError: () => ChunkValidationError,
45
+ DEFAULT_OUTPUT_FORMAT: () => DEFAULT_OUTPUT_FORMAT,
46
+ MergeError: () => MergeError,
47
+ SynthesisCancelledError: () => SynthesisCancelledError,
48
+ SynthesisTimeoutError: () => SynthesisTimeoutError,
44
49
  UnsupportedMergeFormatError: () => UnsupportedMergeFormatError,
45
50
  areAzureLanguagesEquivalent: () => areAzureLanguagesEquivalent,
46
51
  buildPartialSsml: () => buildPartialSsml,
@@ -53,6 +58,8 @@ __export(src_exports, {
53
58
  fromPlainTextToSsml: () => fromPlainTextToSsml,
54
59
  getAzureVoiceCatalogMetadata: () => getAzureVoiceCatalogMetadata,
55
60
  getBuiltInVoiceCatalogMetadata: () => getBuiltInVoiceCatalogMetadata,
61
+ getSsmlSourceMap: () => getSsmlSourceMap,
62
+ inspectAudioSpecification: () => inspectAudioSpecification,
56
63
  isValidAzureAudioDuration: () => isValidAzureAudioDuration,
57
64
  mapSsmlTextNodes: () => mapSsmlTextNodes,
58
65
  mergeAudioBuffers: () => mergeAudioBuffers,
@@ -60,6 +67,7 @@ __export(src_exports, {
60
67
  normalizeAzureLanguage: () => normalizeAzureLanguage,
61
68
  parseSsml: () => parseSsml,
62
69
  resolveMergeAudioFormat: () => resolveMergeAudioFormat,
70
+ resolveMimeType: () => resolveMimeType,
63
71
  splitSsmlDocument: () => splitSsmlDocument,
64
72
  synthesizeSpeech: () => synthesizeSpeech,
65
73
  synthesizeSsml: () => synthesizeSsml,
@@ -67,6 +75,7 @@ __export(src_exports, {
67
75
  synthesizeSsmlChunksSafe: () => synthesizeSsmlChunksSafe,
68
76
  synthesizeSsmlSafe: () => synthesizeSsmlSafe,
69
77
  validateAzureSsml: () => validateAzureSsml,
78
+ validateAzureSsmlChunks: () => validateAzureSsmlChunks,
70
79
  validateSsml: () => validateSsml,
71
80
  validateSsmlStructureIntegrity: () => validateSsmlStructureIntegrity
72
81
  });
@@ -999,6 +1008,236 @@ function buildPartialSsml(textOrOptions, context) {
999
1008
  return serializePartialSsml(textOrOptions.text, textOrOptions);
1000
1009
  }
1001
1010
 
1011
+ // packages/ssml-core/src/textNodes.ts
1012
+ function decodeXmlText(value) {
1013
+ return value.replace(/&(?:amp|apos|gt|lt|quot);|&#(?:x[\da-f]+|\d+);/gi, (entity) => {
1014
+ if (entity === "&") return "&";
1015
+ if (entity === "'") return "'";
1016
+ if (entity === ">") return ">";
1017
+ if (entity === "&lt;") return "<";
1018
+ if (entity === "&quot;") return '"';
1019
+ const hexadecimal = entity.toLowerCase().startsWith("&#x");
1020
+ const digits = entity.slice(hexadecimal ? 3 : 2, -1);
1021
+ return String.fromCodePoint(Number.parseInt(digits, hexadecimal ? 16 : 10));
1022
+ });
1023
+ }
1024
+ function encodeXmlText(value) {
1025
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1026
+ }
1027
+ function decodeXmlAttribute(value) {
1028
+ return decodeXmlText(value);
1029
+ }
1030
+ function findTagEnd(source, start) {
1031
+ let quote = "";
1032
+ for (let index = start; index < source.length; index += 1) {
1033
+ const character = source[index];
1034
+ if (quote) {
1035
+ if (character === quote) quote = "";
1036
+ } else if (character === '"' || character === "'") {
1037
+ quote = character;
1038
+ } else if (character === ">") {
1039
+ return index;
1040
+ }
1041
+ }
1042
+ return source.length - 1;
1043
+ }
1044
+ function readTagName(tag) {
1045
+ const match = /^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/.exec(tag);
1046
+ return match?.[1];
1047
+ }
1048
+ function readTagAttributes(tag, name) {
1049
+ const attributes = {};
1050
+ const nameStart = tag.indexOf(name);
1051
+ const attributeSource = tag.slice(nameStart + name.length, tag.length - 1).replace(/\/\s*$/, "");
1052
+ const attributePattern = /([A-Za-z_][A-Za-z0-9_.:-]*)\s*=\s*(["'])([\s\S]*?)\2/g;
1053
+ for (const match of attributeSource.matchAll(attributePattern)) {
1054
+ attributes[match[1].toLowerCase()] = decodeXmlAttribute(match[3]);
1055
+ }
1056
+ return attributes;
1057
+ }
1058
+ function collectTextNodes(source) {
1059
+ const nodes = [];
1060
+ const elements = [];
1061
+ let index = 0;
1062
+ const addText = (start, end, rawText, sourceStart = start, sourceEnd = end) => {
1063
+ if (!rawText) return;
1064
+ const path = elements.map((element) => element.name);
1065
+ const parent = elements[elements.length - 1];
1066
+ nodes.push({
1067
+ context: {
1068
+ ancestorTags: path.slice(0, -1),
1069
+ parentAttributes: { ...parent?.attributes ?? {} },
1070
+ parentTag: parent?.name ?? "",
1071
+ path
1072
+ },
1073
+ decodedText: decodeXmlText(rawText),
1074
+ end,
1075
+ sourceEnd,
1076
+ sourceStart,
1077
+ start
1078
+ });
1079
+ };
1080
+ while (index < source.length) {
1081
+ if (source[index] !== "<") {
1082
+ const nextTag = source.indexOf("<", index);
1083
+ const end2 = nextTag === -1 ? source.length : nextTag;
1084
+ addText(index, end2, source.slice(index, end2));
1085
+ index = end2;
1086
+ continue;
1087
+ }
1088
+ if (source.startsWith("<!--", index)) {
1089
+ const end2 = source.indexOf("-->", index + 4);
1090
+ index = end2 === -1 ? source.length : end2 + 3;
1091
+ continue;
1092
+ }
1093
+ if (source.startsWith("<![CDATA[", index)) {
1094
+ const contentStart = index + 9;
1095
+ const end2 = source.indexOf("]]>", contentStart);
1096
+ const contentEnd = end2 === -1 ? source.length : end2;
1097
+ addText(
1098
+ contentStart,
1099
+ contentEnd,
1100
+ source.slice(contentStart, contentEnd),
1101
+ index,
1102
+ end2 === -1 ? source.length : end2 + 3
1103
+ );
1104
+ index = end2 === -1 ? source.length : end2 + 3;
1105
+ continue;
1106
+ }
1107
+ if (source.startsWith("<?", index)) {
1108
+ const end2 = source.indexOf("?>", index + 2);
1109
+ index = end2 === -1 ? source.length : end2 + 2;
1110
+ continue;
1111
+ }
1112
+ if (source.startsWith("</", index)) {
1113
+ const end2 = findTagEnd(source, index + 2);
1114
+ elements.pop();
1115
+ index = end2 + 1;
1116
+ continue;
1117
+ }
1118
+ const end = findTagEnd(source, index + 1);
1119
+ const tag = source.slice(index, end + 1);
1120
+ const name = readTagName(tag);
1121
+ if (name && !/\/\s*>$/.test(tag)) elements.push({ attributes: readTagAttributes(tag, name), name });
1122
+ index = end + 1;
1123
+ }
1124
+ return nodes;
1125
+ }
1126
+ function collectSourceMap(source) {
1127
+ const segments = [];
1128
+ const markers = [];
1129
+ const elements = [];
1130
+ let textOffset = 0;
1131
+ let index = 0;
1132
+ const textParts = [];
1133
+ const addText = (value) => {
1134
+ if (!value) return;
1135
+ const parent = elements[elements.length - 1];
1136
+ if (parent) parent.nextChildIndex += 1;
1137
+ const sourceNodePath = parent?.path ?? ["speak"];
1138
+ const start = textOffset;
1139
+ textOffset += value.length;
1140
+ textParts.push(value);
1141
+ segments.push({ text: value, range: { start, end: textOffset }, sourceNodePath: [...sourceNodePath] });
1142
+ };
1143
+ while (index < source.length) {
1144
+ if (source[index] !== "<") {
1145
+ const end2 = source.indexOf("<", index);
1146
+ const textEnd = end2 === -1 ? source.length : end2;
1147
+ addText(decodeXmlText(source.slice(index, textEnd)));
1148
+ index = textEnd;
1149
+ continue;
1150
+ }
1151
+ if (source.startsWith("<!--", index)) {
1152
+ const end2 = source.indexOf("-->", index + 4);
1153
+ index = end2 === -1 ? source.length : end2 + 3;
1154
+ continue;
1155
+ }
1156
+ if (source.startsWith("<![CDATA[", index)) {
1157
+ const contentStart = index + 9;
1158
+ const end2 = source.indexOf("]]>", contentStart);
1159
+ const contentEnd = end2 === -1 ? source.length : end2;
1160
+ addText(source.slice(contentStart, contentEnd));
1161
+ index = end2 === -1 ? source.length : end2 + 3;
1162
+ continue;
1163
+ }
1164
+ if (source.startsWith("<?", index)) {
1165
+ const end2 = source.indexOf("?>", index + 2);
1166
+ index = end2 === -1 ? source.length : end2 + 2;
1167
+ continue;
1168
+ }
1169
+ const end = findTagEnd(source, index + 1);
1170
+ const rawTag = source.slice(index, end + 1);
1171
+ if (rawTag.startsWith("</")) {
1172
+ elements.pop();
1173
+ index = end + 1;
1174
+ continue;
1175
+ }
1176
+ const name = readTagName(rawTag);
1177
+ if (!name) {
1178
+ index = end + 1;
1179
+ continue;
1180
+ }
1181
+ const parent = elements[elements.length - 1];
1182
+ const childIndex = parent?.nextChildIndex ?? 0;
1183
+ if (parent) parent.nextChildIndex += 1;
1184
+ const path = parent ? [...parent.path, `${name}[${childIndex}]`] : [name];
1185
+ const attributes = readTagAttributes(rawTag, name);
1186
+ const normalizedName = name.toLowerCase();
1187
+ if (normalizedName === "mark" || normalizedName === "bookmark") {
1188
+ const markerName = attributes[normalizedName === "mark" ? "name" : "mark"];
1189
+ if (markerName) {
1190
+ markers.push({
1191
+ kind: normalizedName,
1192
+ name: markerName,
1193
+ originalTextRange: { start: textOffset, end: textOffset },
1194
+ sourceNodePath: [...path]
1195
+ });
1196
+ }
1197
+ }
1198
+ if (!/\/\s*>$/.test(rawTag)) elements.push({ name, path, nextChildIndex: 0 });
1199
+ index = end + 1;
1200
+ }
1201
+ return { text: textParts.join(""), segments, markers };
1202
+ }
1203
+ function getSsmlSourceMap(ssml) {
1204
+ parseSsml(ssml);
1205
+ return collectSourceMap(ssml);
1206
+ }
1207
+ function extractSsmlText(ssml) {
1208
+ parseSsml(ssml);
1209
+ return collectTextNodes(ssml).map((node) => node.decodedText);
1210
+ }
1211
+ async function mapSsmlTextNodes(ssml, transform, options = {}) {
1212
+ parseSsml(ssml);
1213
+ const nodes = collectTextNodes(ssml);
1214
+ const skipTags = new Set((options.skipTags ?? ["phoneme", "say-as", "sub"]).map((tag) => tag.toLowerCase()));
1215
+ const replacements = await Promise.all(
1216
+ nodes.map(async (node) => {
1217
+ const context = {
1218
+ ancestorTags: [...node.context.ancestorTags],
1219
+ parentAttributes: { ...node.context.parentAttributes },
1220
+ parentTag: node.context.parentTag,
1221
+ path: [...node.context.path]
1222
+ };
1223
+ const shouldTransform = !skipTags.has(context.parentTag.toLowerCase()) && (options.filter?.(context) ?? true);
1224
+ if (!shouldTransform) return ssml.slice(node.sourceStart, node.sourceEnd);
1225
+ const transformed = await transform(node.decodedText, context);
1226
+ if (typeof transformed !== "string") {
1227
+ throw new TypeError("SSML text node transform must return a string");
1228
+ }
1229
+ return transformed === node.decodedText ? ssml.slice(node.sourceStart, node.sourceEnd) : encodeXmlText(transformed);
1230
+ })
1231
+ );
1232
+ let result = "";
1233
+ let cursor = 0;
1234
+ nodes.forEach((node, nodeIndex) => {
1235
+ result += ssml.slice(cursor, node.sourceStart) + replacements[nodeIndex];
1236
+ cursor = node.sourceEnd;
1237
+ });
1238
+ return result + ssml.slice(cursor);
1239
+ }
1240
+
1002
1241
  // packages/ssml-core/src/split.ts
1003
1242
  var DEFAULT_MAX_LENGTH = 1e4;
1004
1243
  function cloneElement(element, children) {
@@ -1130,7 +1369,7 @@ function findSourceNodePath(nodes, targetOffset) {
1130
1369
  });
1131
1370
  return foundPath ?? firstPath;
1132
1371
  }
1133
- function createChunk(document, nodes, chunkIndex, textStart, backgroundAudio, replicateBackgroundAudio) {
1372
+ function createChunk(document, nodes, chunkIndex, textStart, backgroundAudio, replicateBackgroundAudio, sourceMap, includeEndMarkers) {
1134
1373
  const chunkNodes = backgroundAudio && (replicateBackgroundAudio || chunkIndex === 0) ? [backgroundAudio, ...nodes] : nodes;
1135
1374
  const text = nodes.map(textFromNode).join("");
1136
1375
  const marks = [];
@@ -1146,7 +1385,23 @@ function createChunk(document, nodes, chunkIndex, textStart, backgroundAudio, re
1146
1385
  hasBackgroundAudio: chunkNodes.some(
1147
1386
  (node) => typeof node !== "string" && node.type !== "text" && node.type === "mstts:backgroundaudio"
1148
1387
  ),
1149
- sourceNodePath: findSourceNodePath(document.children ?? [], textStart)
1388
+ sourceNodePath: findSourceNodePath(document.children ?? [], textStart),
1389
+ sourceTextSegments: sourceMap.segments.filter(({ range }) => range.end > textStart && range.start < textStart + text.length).map((segment) => {
1390
+ const start = Math.max(segment.range.start, textStart);
1391
+ const end = Math.min(segment.range.end, textStart + text.length);
1392
+ return {
1393
+ text: segment.text.slice(start - segment.range.start, end - segment.range.start),
1394
+ range: { start, end },
1395
+ sourceNodePath: [...segment.sourceNodePath]
1396
+ };
1397
+ }),
1398
+ sourceMarkers: sourceMap.markers.filter(
1399
+ ({ originalTextRange }) => originalTextRange.start >= textStart && (originalTextRange.start < textStart + text.length || includeEndMarkers && originalTextRange.start === textStart + text.length)
1400
+ ).map((marker) => ({
1401
+ ...marker,
1402
+ originalTextRange: { ...marker.originalTextRange },
1403
+ sourceNodePath: [...marker.sourceNodePath]
1404
+ }))
1150
1405
  };
1151
1406
  }
1152
1407
  function splitSsmlDocument(ssml, maxLength = DEFAULT_MAX_LENGTH, options = {}) {
@@ -1156,11 +1411,12 @@ function splitSsmlDocument(ssml, maxLength = DEFAULT_MAX_LENGTH, options = {}) {
1156
1411
  throw new RangeError("maxLength must be a positive integer");
1157
1412
  }
1158
1413
  const document = parseSsml(ssml);
1414
+ const sourceMap = getSsmlSourceMap(ssml);
1159
1415
  const backgroundAudio = (document.children ?? []).find(
1160
1416
  (node) => typeof node !== "string" && node.type !== "text" && node.type === "mstts:backgroundaudio"
1161
1417
  );
1162
1418
  if (ssml.length <= resolvedMaxLength) {
1163
- return [createChunk(document, document.children ?? [], 0, 0, backgroundAudio, true)];
1419
+ return [createChunk(document, document.children ?? [], 0, 0, backgroundAudio, true, sourceMap, true)];
1164
1420
  }
1165
1421
  const contentChildren = (document.children ?? []).filter((node) => node !== backgroundAudio);
1166
1422
  const plainDocumentLength = documentWithChildren(document, []).length;
@@ -1184,7 +1440,16 @@ function splitSsmlDocument(ssml, maxLength = DEFAULT_MAX_LENGTH, options = {}) {
1184
1440
  }
1185
1441
  if (group.length > 0) chunks.push(group);
1186
1442
  if (chunks.length === 0) {
1187
- const result = createChunk(document, [], 0, 0, backgroundAudio, resolvedOptions.replicateBackgroundAudio ?? false);
1443
+ const result = createChunk(
1444
+ document,
1445
+ [],
1446
+ 0,
1447
+ 0,
1448
+ backgroundAudio,
1449
+ resolvedOptions.replicateBackgroundAudio ?? false,
1450
+ sourceMap,
1451
+ true
1452
+ );
1188
1453
  if (result.ssml.length > resolvedMaxLength) {
1189
1454
  throw new RangeError("maxLength is too small to contain the SSML document wrapper");
1190
1455
  }
@@ -1198,7 +1463,9 @@ function splitSsmlDocument(ssml, maxLength = DEFAULT_MAX_LENGTH, options = {}) {
1198
1463
  chunkIndex,
1199
1464
  textStart,
1200
1465
  backgroundAudio,
1201
- resolvedOptions.replicateBackgroundAudio ?? false
1466
+ resolvedOptions.replicateBackgroundAudio ?? false,
1467
+ sourceMap,
1468
+ chunkIndex === chunks.length - 1
1202
1469
  );
1203
1470
  textStart = result.originalTextRange.end;
1204
1471
  return result;
@@ -1221,176 +1488,27 @@ function validateSsml(xmlString) {
1221
1488
  }
1222
1489
  }
1223
1490
 
1224
- // packages/ssml-core/src/textNodes.ts
1225
- function decodeXmlText(value) {
1226
- return value.replace(/&(?:amp|apos|gt|lt|quot);|&#(?:x[\da-f]+|\d+);/gi, (entity) => {
1227
- if (entity === "&amp;") return "&";
1228
- if (entity === "&apos;") return "'";
1229
- if (entity === "&gt;") return ">";
1230
- if (entity === "&lt;") return "<";
1231
- if (entity === "&quot;") return '"';
1232
- const hexadecimal = entity.toLowerCase().startsWith("&#x");
1233
- const digits = entity.slice(hexadecimal ? 3 : 2, -1);
1234
- return String.fromCodePoint(Number.parseInt(digits, hexadecimal ? 16 : 10));
1235
- });
1236
- }
1237
- function encodeXmlText(value) {
1238
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1239
- }
1240
- function decodeXmlAttribute(value) {
1241
- return decodeXmlText(value);
1242
- }
1243
- function findTagEnd(source, start) {
1244
- let quote = "";
1245
- for (let index = start; index < source.length; index += 1) {
1246
- const character = source[index];
1247
- if (quote) {
1248
- if (character === quote) quote = "";
1249
- } else if (character === '"' || character === "'") {
1250
- quote = character;
1251
- } else if (character === ">") {
1252
- return index;
1253
- }
1491
+ // packages/ssml-core/src/migration.ts
1492
+ var DEFAULT_TRANSLATION_SKIP_TAGS = ["phoneme", "say-as", "sayAs", "sub"];
1493
+ function elementName2(element) {
1494
+ switch (element.type) {
1495
+ case "custom":
1496
+ case "element":
1497
+ return element.name;
1498
+ case "expressAs":
1499
+ return "mstts:express-as";
1500
+ case "sayAs":
1501
+ return "say-as";
1502
+ case "silence":
1503
+ return "mstts:silence";
1504
+ case "viseme":
1505
+ return "mstts:viseme";
1506
+ default:
1507
+ return element.type;
1254
1508
  }
1255
- return source.length - 1;
1256
1509
  }
1257
- function readTagName(tag) {
1258
- const match = /^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/.exec(tag);
1259
- return match?.[1];
1260
- }
1261
- function readTagAttributes(tag, name) {
1262
- const attributes = {};
1263
- const nameStart = tag.indexOf(name);
1264
- const attributeSource = tag.slice(nameStart + name.length, tag.length - 1).replace(/\/\s*$/, "");
1265
- const attributePattern = /([A-Za-z_][A-Za-z0-9_.:-]*)\s*=\s*(["'])([\s\S]*?)\2/g;
1266
- for (const match of attributeSource.matchAll(attributePattern)) {
1267
- attributes[match[1].toLowerCase()] = decodeXmlAttribute(match[3]);
1268
- }
1269
- return attributes;
1270
- }
1271
- function collectTextNodes(source) {
1272
- const nodes = [];
1273
- const elements = [];
1274
- let index = 0;
1275
- const addText = (start, end, rawText, sourceStart = start, sourceEnd = end) => {
1276
- if (!rawText) return;
1277
- const path = elements.map((element) => element.name);
1278
- const parent = elements[elements.length - 1];
1279
- nodes.push({
1280
- context: {
1281
- ancestorTags: path.slice(0, -1),
1282
- parentAttributes: { ...parent?.attributes ?? {} },
1283
- parentTag: parent?.name ?? "",
1284
- path
1285
- },
1286
- decodedText: decodeXmlText(rawText),
1287
- end,
1288
- sourceEnd,
1289
- sourceStart,
1290
- start
1291
- });
1292
- };
1293
- while (index < source.length) {
1294
- if (source[index] !== "<") {
1295
- const nextTag = source.indexOf("<", index);
1296
- const end2 = nextTag === -1 ? source.length : nextTag;
1297
- addText(index, end2, source.slice(index, end2));
1298
- index = end2;
1299
- continue;
1300
- }
1301
- if (source.startsWith("<!--", index)) {
1302
- const end2 = source.indexOf("-->", index + 4);
1303
- index = end2 === -1 ? source.length : end2 + 3;
1304
- continue;
1305
- }
1306
- if (source.startsWith("<![CDATA[", index)) {
1307
- const contentStart = index + 9;
1308
- const end2 = source.indexOf("]]>", contentStart);
1309
- const contentEnd = end2 === -1 ? source.length : end2;
1310
- addText(
1311
- contentStart,
1312
- contentEnd,
1313
- source.slice(contentStart, contentEnd),
1314
- index,
1315
- end2 === -1 ? source.length : end2 + 3
1316
- );
1317
- index = end2 === -1 ? source.length : end2 + 3;
1318
- continue;
1319
- }
1320
- if (source.startsWith("<?", index)) {
1321
- const end2 = source.indexOf("?>", index + 2);
1322
- index = end2 === -1 ? source.length : end2 + 2;
1323
- continue;
1324
- }
1325
- if (source.startsWith("</", index)) {
1326
- const end2 = findTagEnd(source, index + 2);
1327
- elements.pop();
1328
- index = end2 + 1;
1329
- continue;
1330
- }
1331
- const end = findTagEnd(source, index + 1);
1332
- const tag = source.slice(index, end + 1);
1333
- const name = readTagName(tag);
1334
- if (name && !/\/\s*>$/.test(tag)) elements.push({ attributes: readTagAttributes(tag, name), name });
1335
- index = end + 1;
1336
- }
1337
- return nodes;
1338
- }
1339
- function extractSsmlText(ssml) {
1340
- parseSsml(ssml);
1341
- return collectTextNodes(ssml).map((node) => node.decodedText);
1342
- }
1343
- async function mapSsmlTextNodes(ssml, transform, options = {}) {
1344
- parseSsml(ssml);
1345
- const nodes = collectTextNodes(ssml);
1346
- const skipTags = new Set((options.skipTags ?? ["phoneme", "say-as", "sub"]).map((tag) => tag.toLowerCase()));
1347
- const replacements = await Promise.all(
1348
- nodes.map(async (node) => {
1349
- const context = {
1350
- ancestorTags: [...node.context.ancestorTags],
1351
- parentAttributes: { ...node.context.parentAttributes },
1352
- parentTag: node.context.parentTag,
1353
- path: [...node.context.path]
1354
- };
1355
- const shouldTransform = !skipTags.has(context.parentTag.toLowerCase()) && (options.filter?.(context) ?? true);
1356
- if (!shouldTransform) return ssml.slice(node.sourceStart, node.sourceEnd);
1357
- const transformed = await transform(node.decodedText, context);
1358
- if (typeof transformed !== "string") {
1359
- throw new TypeError("SSML text node transform must return a string");
1360
- }
1361
- return transformed === node.decodedText ? ssml.slice(node.sourceStart, node.sourceEnd) : encodeXmlText(transformed);
1362
- })
1363
- );
1364
- let result = "";
1365
- let cursor = 0;
1366
- nodes.forEach((node, nodeIndex) => {
1367
- result += ssml.slice(cursor, node.sourceStart) + replacements[nodeIndex];
1368
- cursor = node.sourceEnd;
1369
- });
1370
- return result + ssml.slice(cursor);
1371
- }
1372
-
1373
- // packages/ssml-core/src/migration.ts
1374
- var DEFAULT_TRANSLATION_SKIP_TAGS = ["phoneme", "say-as", "sayAs", "sub"];
1375
- function elementName2(element) {
1376
- switch (element.type) {
1377
- case "custom":
1378
- case "element":
1379
- return element.name;
1380
- case "expressAs":
1381
- return "mstts:express-as";
1382
- case "sayAs":
1383
- return "say-as";
1384
- case "silence":
1385
- return "mstts:silence";
1386
- case "viseme":
1387
- return "mstts:viseme";
1388
- default:
1389
- return element.type;
1390
- }
1391
- }
1392
- function addAttribute2(attributes, name, value) {
1393
- if (value !== void 0) attributes[name] = value;
1510
+ function addAttribute2(attributes, name, value) {
1511
+ if (value !== void 0) attributes[name] = value;
1394
1512
  }
1395
1513
  function elementAttributes(element) {
1396
1514
  const attributes = { ...element.attributes ?? {} };
@@ -1790,34 +1908,51 @@ function createAzureUrlValidatorRunner(validator, options = {}) {
1790
1908
  const inFlight = /* @__PURE__ */ new Map();
1791
1909
  const waiters = [];
1792
1910
  let active = 0;
1793
- const acquire = async () => {
1911
+ const configuredSignal = options.signal ?? new AbortController().signal;
1912
+ const acquire = async (signal) => {
1913
+ if (signal.aborted) throw new Error("URL validation was aborted.");
1794
1914
  if (active < concurrency) {
1795
1915
  active += 1;
1796
1916
  return;
1797
1917
  }
1798
- await new Promise((resolve) => waiters.push(resolve));
1918
+ await new Promise((resolve, reject) => {
1919
+ let waiter;
1920
+ const abortHandler = () => {
1921
+ const index = waiters.indexOf(waiter);
1922
+ if (index >= 0) waiters.splice(index, 1);
1923
+ signal.removeEventListener("abort", abortHandler);
1924
+ reject(new Error("URL validation was aborted."));
1925
+ };
1926
+ signal.addEventListener("abort", abortHandler, { once: true });
1927
+ waiter = () => {
1928
+ signal.removeEventListener("abort", abortHandler);
1929
+ resolve();
1930
+ };
1931
+ waiters.push(waiter);
1932
+ });
1799
1933
  active += 1;
1800
1934
  };
1801
1935
  const release = () => {
1802
1936
  active -= 1;
1803
1937
  waiters.shift()?.();
1804
1938
  };
1805
- const check = async (url, context) => {
1806
- if (options.signal?.aborted) throw new Error("URL validation was aborted.");
1807
- const cached = cache.get(url);
1939
+ const check = async (url, context, signal = configuredSignal) => {
1940
+ if (signal.aborted) throw new Error("URL validation was aborted.");
1941
+ const key = `${context.tag}:${context.attribute}:${url}`;
1942
+ const cached = cache.get(key);
1808
1943
  if (cached !== void 0) return cached;
1809
- const existing = inFlight.get(url);
1944
+ const existing = inFlight.get(key);
1810
1945
  if (existing) return existing;
1811
1946
  const promise = (async () => {
1812
- await acquire();
1947
+ await acquire(signal);
1813
1948
  try {
1814
- if (options.signal?.aborted) throw new Error("URL validation was aborted.");
1815
- const validation = Promise.resolve(validator(url, context));
1949
+ if (signal.aborted) throw new Error("URL validation was aborted.");
1950
+ const validation = Promise.resolve(validator(url, context, signal));
1816
1951
  let timer;
1817
1952
  let abortHandler;
1818
1953
  const cancellation = new Promise((_resolve, reject) => {
1819
1954
  abortHandler = () => reject(new Error("URL validation was aborted."));
1820
- options.signal?.addEventListener("abort", abortHandler, { once: true });
1955
+ signal.addEventListener("abort", abortHandler, { once: true });
1821
1956
  if (options.timeoutMs !== void 0 && options.timeoutMs > 0) {
1822
1957
  timer = setTimeout(
1823
1958
  () => reject(new Error(`URL validation timed out after ${options.timeoutMs} ms.`)),
@@ -1827,24 +1962,24 @@ function createAzureUrlValidatorRunner(validator, options = {}) {
1827
1962
  });
1828
1963
  try {
1829
1964
  const result = await (timer || abortHandler ? Promise.race([validation, cancellation]) : validation);
1830
- cache.set(url, result);
1965
+ cache.set(key, result);
1831
1966
  return result;
1832
1967
  } finally {
1833
1968
  if (timer) clearTimeout(timer);
1834
- if (abortHandler) options.signal?.removeEventListener("abort", abortHandler);
1969
+ if (abortHandler) signal.removeEventListener("abort", abortHandler);
1835
1970
  }
1836
1971
  } finally {
1837
1972
  release();
1838
1973
  }
1839
1974
  })();
1840
- inFlight.set(url, promise);
1975
+ inFlight.set(key, promise);
1841
1976
  try {
1842
1977
  return await promise;
1843
1978
  } finally {
1844
- inFlight.delete(url);
1979
+ inFlight.delete(key);
1845
1980
  }
1846
1981
  };
1847
- return (url, context) => check(url, context);
1982
+ return (url, context, signal) => check(url, context, signal ?? configuredSignal);
1848
1983
  }
1849
1984
  var ALLOWED_BREAK_STRENGTHS = /* @__PURE__ */ new Set(["none", "x-weak", "weak", "medium", "strong", "x-strong"]);
1850
1985
  var ALLOWED_SAY_AS = /* @__PURE__ */ new Set([
@@ -1957,6 +2092,7 @@ function tokenizeElements(source) {
1957
2092
  if (parent) parent.childElementCount += 1;
1958
2093
  const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
1959
2094
  const tokenName = nameMatch[1];
2095
+ const path = parent ? [...parent.path, `${tokenName}[${childElementIndex ?? 0}]`] : [tokenName];
1960
2096
  const tokenVoiceName = tokenName.toLowerCase() === "voice" ? attributes.get("name") : tokenName.toLowerCase() === "mstts:turn" ? attributes.get("voice") ?? parentVoiceName : parentVoiceName;
1961
2097
  tokens.push({
1962
2098
  attributes,
@@ -1967,12 +2103,14 @@ function tokenizeElements(source) {
1967
2103
  parentName: parent?.name,
1968
2104
  parentVoiceName,
1969
2105
  selfClosing,
1970
- start
2106
+ start,
2107
+ path
1971
2108
  });
1972
2109
  if (!selfClosing) {
1973
2110
  openElements.push({
1974
2111
  childElementCount: 0,
1975
2112
  name: tokenName,
2113
+ path,
1976
2114
  voiceName: tokenVoiceName
1977
2115
  });
1978
2116
  }
@@ -1985,13 +2123,14 @@ function location(source, offset) {
1985
2123
  const line = before.split("\n").length;
1986
2124
  return { line, column: before.length - (before.lastIndexOf("\n") + 1) + 1 };
1987
2125
  }
1988
- function addDiagnostic(diagnostics, source, offset, message, severity = "error", code) {
2126
+ function addDiagnostic(diagnostics, source, offset, message, severity = "error", code, metadata = {}) {
1989
2127
  diagnostics.push({
1990
2128
  ...location(source, offset),
1991
2129
  message,
1992
2130
  severity,
1993
2131
  source: "ssml-static-validator",
1994
- ...code ? { code } : {}
2132
+ ...code ? { code } : {},
2133
+ ...metadata
1995
2134
  });
1996
2135
  }
1997
2136
  function isSupportedProsodyRate(value) {
@@ -2464,9 +2603,11 @@ function validateAzureSsmlStatic(ssml, options = {}) {
2464
2603
  for (const token of tokens) {
2465
2604
  const tokenName = token.name.toLowerCase();
2466
2605
  const tokenVoiceName = tokenName === "voice" ? attr(token, "name")?.trim() : tokenName === "mstts:turn" ? attr(token, "voice")?.trim() || token.parentVoiceName : options.validateNestedVoices === false ? voiceName : token.parentVoiceName;
2606
+ const tokenDiagnosticStart = diagnostics.length;
2467
2607
  validateElement(token, ssml, diagnostics, tokenVoiceName, options, voiceCatalog);
2468
2608
  const definition = tokenVoiceName ? voiceCatalog.get(tokenVoiceName.toLowerCase()) : void 0;
2469
2609
  validateVoiceFeatureMatrix(token, ssml, diagnostics, tokenVoiceName, definition);
2610
+ annotateTokenDiagnostics(diagnostics, tokenDiagnosticStart, token, options, tokenVoiceName);
2470
2611
  if (tokenName === "voice" && options.model && definition?.models && !definition.models.some((model) => model.toLowerCase() === options.model?.toLowerCase())) {
2471
2612
  addDiagnostic(
2472
2613
  diagnostics,
@@ -2488,18 +2629,37 @@ function urlAttributes(token) {
2488
2629
  return value === void 0 ? [] : [{ attribute, value }];
2489
2630
  });
2490
2631
  }
2632
+ function annotateTokenDiagnostics(diagnostics, startIndex, token, options, voiceName) {
2633
+ const attributes = [...token.attributes.keys()];
2634
+ for (const diagnostic of diagnostics.slice(startIndex)) {
2635
+ const attributeName = attributes.find(
2636
+ (attribute) => new RegExp(`(?:<[^> ]+\\s+|")${attribute}(?:"|>|\\s)`, "i").test(diagnostic.message)
2637
+ );
2638
+ const nodePath = options.sourceNodePath ? [...options.sourceNodePath] : [...token.path];
2639
+ Object.assign(diagnostic, {
2640
+ range: { start: token.start, end: token.end + 1 },
2641
+ tagName: token.name,
2642
+ ...attributeName ? { attributeName } : {},
2643
+ ...voiceName ? { voiceName } : {},
2644
+ ...options.chunkIndex !== void 0 ? { chunkIndex: options.chunkIndex } : {},
2645
+ nodePath,
2646
+ targetNodePath: [...token.path]
2647
+ });
2648
+ }
2649
+ }
2491
2650
  function validateAzureSsml(ssml, options = {}) {
2492
2651
  const diagnostics = validateAzureSsmlStatic(ssml, options);
2493
2652
  const validator = options.urlValidator ?? options.customUrlValidator;
2494
2653
  if (!validator || typeof ssml !== "string") return diagnostics;
2495
2654
  const runnerOptions = options.urlValidation ?? {};
2496
- const boundedValidator = createAzureUrlValidatorRunner(validator, {
2655
+ const boundedValidator = options.urlValidatorRunner ?? createAzureUrlValidatorRunner(validator, {
2497
2656
  ...runnerOptions,
2498
2657
  ...options.urlValidatorConcurrency !== void 0 ? { concurrency: options.urlValidatorConcurrency } : {},
2499
2658
  ...options.urlValidatorTimeoutMs !== void 0 ? { timeoutMs: options.urlValidatorTimeoutMs } : {},
2500
2659
  ...options.urlValidatorSignal ? { signal: options.urlValidatorSignal } : {},
2501
2660
  ...options.urlValidatorCache ? { cache: options.urlValidatorCache } : {}
2502
2661
  });
2662
+ const validationSignal = options.urlValidatorSignal ?? options.urlValidation?.signal ?? new AbortController().signal;
2503
2663
  let tokens;
2504
2664
  try {
2505
2665
  tokens = tokenizeElements(ssml);
@@ -2509,30 +2669,54 @@ function validateAzureSsml(ssml, options = {}) {
2509
2669
  const checks = tokens.flatMap(
2510
2670
  (token) => urlAttributes(token).map(async ({ attribute, value }) => {
2511
2671
  try {
2512
- const result = await boundedValidator(value, { tag: token.name, attribute });
2672
+ const result = await boundedValidator(
2673
+ value,
2674
+ { tag: token.name, attribute, ...options.sourceNodePath ? { sourceNodePath: options.sourceNodePath } : {} },
2675
+ validationSignal
2676
+ );
2513
2677
  const valid = typeof result === "boolean" ? result : result.valid;
2514
2678
  if (!valid) {
2515
2679
  const reason = typeof result === "boolean" ? void 0 : result.reason;
2680
+ const diagnosticStart = diagnostics.length;
2516
2681
  addDiagnostic(
2517
2682
  diagnostics,
2518
2683
  ssml,
2519
2684
  token.start,
2520
2685
  `<${token.name} ${attribute}> was rejected by the custom URL validator${reason ? `: ${reason}` : "."}`
2521
2686
  );
2687
+ annotateTokenDiagnostics(diagnostics, diagnosticStart, token, options, token.parentVoiceName);
2522
2688
  }
2523
2689
  } catch (error) {
2524
2690
  const reason = error instanceof Error ? error.message : String(error);
2691
+ const diagnosticStart = diagnostics.length;
2525
2692
  addDiagnostic(
2526
2693
  diagnostics,
2527
2694
  ssml,
2528
2695
  token.start,
2529
2696
  `<${token.name} ${attribute}> could not be validated by the custom URL validator: ${reason}`
2530
2697
  );
2698
+ annotateTokenDiagnostics(diagnostics, diagnosticStart, token, options, token.parentVoiceName);
2531
2699
  }
2532
2700
  })
2533
2701
  );
2534
2702
  return Promise.all(checks).then(() => diagnostics);
2535
2703
  }
2704
+ async function validateAzureSsmlChunks(chunks, options = {}) {
2705
+ const validator = options.urlValidator ?? options.customUrlValidator;
2706
+ const sharedOptions = validator ? {
2707
+ ...options,
2708
+ urlValidatorRunner: options.urlValidatorRunner ?? createAzureUrlValidatorRunner(validator, {
2709
+ ...options.urlValidation ?? {},
2710
+ ...options.urlValidatorConcurrency !== void 0 ? { concurrency: options.urlValidatorConcurrency } : {},
2711
+ ...options.urlValidatorTimeoutMs !== void 0 ? { timeoutMs: options.urlValidatorTimeoutMs } : {},
2712
+ ...options.urlValidatorSignal ? { signal: options.urlValidatorSignal } : {},
2713
+ ...options.urlValidatorCache ? { cache: options.urlValidatorCache } : {}
2714
+ })
2715
+ } : options;
2716
+ return Promise.all(
2717
+ chunks.map((chunk, chunkIndex) => Promise.resolve(validateAzureSsml(chunk, { ...sharedOptions, chunkIndex })))
2718
+ );
2719
+ }
2536
2720
 
2537
2721
  // packages/ssml-core/src/generated/azureVoiceCatalog.ts
2538
2722
  var AZURE_VOICE_CATALOG_METADATA = {
@@ -2555,6 +2739,7 @@ var getBuiltInVoiceCatalogMetadata = getAzureVoiceCatalogMetadata;
2555
2739
  var AzureTtsError = class extends Error {
2556
2740
  constructor(status, statusText, responseBody, requestId) {
2557
2741
  super(`Azure TTS request failed: ${status} ${statusText}`);
2742
+ this.kind = "azure-api-error";
2558
2743
  this.name = "AzureTtsError";
2559
2744
  this.status = status;
2560
2745
  this.statusText = statusText;
@@ -2570,13 +2755,52 @@ var AzureTtsSdkError = class extends AzureTtsError {
2570
2755
  this.errorDetails = errorDetails;
2571
2756
  }
2572
2757
  };
2758
+ var SynthesisCancelledError = class extends Error {
2759
+ constructor(message = "Speech synthesis was cancelled.") {
2760
+ super(message);
2761
+ this.kind = "cancelled";
2762
+ this.name = "SynthesisCancelledError";
2763
+ }
2764
+ };
2765
+ var SynthesisTimeoutError = class extends Error {
2766
+ constructor(message) {
2767
+ super(message);
2768
+ this.kind = "timeout";
2769
+ this.name = "SynthesisTimeoutError";
2770
+ }
2771
+ };
2772
+ var MergeError = class extends Error {
2773
+ constructor(message, cause) {
2774
+ super(message);
2775
+ this.kind = "merge-error";
2776
+ this.name = "MergeError";
2777
+ this.cause = cause;
2778
+ }
2779
+ };
2780
+ var AudioFormatMismatchError = class extends Error {
2781
+ constructor(message, inputSpecs = []) {
2782
+ super(message);
2783
+ this.kind = "audio-format-mismatch";
2784
+ this.name = "AudioFormatMismatchError";
2785
+ this.inputSpecs = inputSpecs;
2786
+ }
2787
+ };
2573
2788
  var UnsupportedMergeFormatError = class extends Error {
2574
2789
  constructor(format) {
2575
2790
  super(`Audio format "${format}" cannot be safely concatenated; container re-multiplexing is required.`);
2791
+ this.kind = "unsupported-format-error";
2576
2792
  this.name = "UnsupportedMergeFormatError";
2577
2793
  this.format = format;
2578
2794
  }
2579
2795
  };
2796
+ function toSynthesisError(error) {
2797
+ if (error instanceof AzureTtsError || error instanceof MergeError || error instanceof AudioFormatMismatchError || error instanceof UnsupportedMergeFormatError || error instanceof SynthesisCancelledError || error instanceof SynthesisTimeoutError)
2798
+ return error;
2799
+ const message = error instanceof Error ? error.message : String(error);
2800
+ if (/cancel|abort/i.test(message)) return new SynthesisCancelledError(message);
2801
+ if (/tim(?:e|ed) ?out/i.test(message)) return new SynthesisTimeoutError(message);
2802
+ return createSpeechSdkError(error);
2803
+ }
2580
2804
  function createSpeechSdkError(error) {
2581
2805
  const message = error instanceof Error ? error.message : String(error);
2582
2806
  return new AzureTtsSdkError(message);
@@ -2585,657 +2809,191 @@ function createSpeechSdkError(error) {
2585
2809
  // packages/azure-tts-client/src/synthesis.ts
2586
2810
  var SpeechSDK2 = __toESM(require("microsoft-cognitiveservices-speech-sdk"));
2587
2811
 
2588
- // packages/azure-tts-client/src/speechConfig.ts
2589
- var import_microsoft_cognitiveservices_speech_sdk = require("microsoft-cognitiveservices-speech-sdk");
2590
-
2591
- // packages/azure-tts-client/src/outputFormats.ts
2592
- var SpeechSDK = __toESM(require("microsoft-cognitiveservices-speech-sdk"));
2593
- var DEFAULT_OUTPUT_FORMAT = "audio-16khz-128kbitrate-mono-mp3";
2594
- var OUTPUT_FORMATS = {
2595
- "raw-8khz-8bit-mono-mulaw": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz8BitMonoMULaw,
2596
- "riff-16khz-16kbps-mono-siren": SpeechSDK.SpeechSynthesisOutputFormat.Riff16Khz16KbpsMonoSiren,
2597
- "audio-16khz-16kbps-mono-siren": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz16KbpsMonoSiren,
2598
- "audio-16khz-32kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3,
2599
- "audio-16khz-128kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz128KBitRateMonoMp3,
2600
- "audio-16khz-64kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz64KBitRateMonoMp3,
2601
- "audio-24khz-48kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz48KBitRateMonoMp3,
2602
- "audio-24khz-96kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz96KBitRateMonoMp3,
2603
- "audio-24khz-160kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz160KBitRateMonoMp3,
2604
- "raw-16khz-16bit-mono-truesilk": SpeechSDK.SpeechSynthesisOutputFormat.Raw16Khz16BitMonoTrueSilk,
2605
- "riff-16khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Riff16Khz16BitMonoPcm,
2606
- "riff-8khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz16BitMonoPcm,
2607
- "riff-24khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm,
2608
- "riff-8khz-8bit-mono-mulaw": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz8BitMonoMULaw,
2609
- "raw-16khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Raw16Khz16BitMonoPcm,
2610
- "raw-24khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Raw24Khz16BitMonoPcm,
2611
- "raw-8khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz16BitMonoPcm,
2612
- "ogg-16khz-16bit-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Ogg16Khz16BitMonoOpus,
2613
- "ogg-24khz-16bit-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Ogg24Khz16BitMonoOpus,
2614
- "raw-48khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Raw48Khz16BitMonoPcm,
2615
- "riff-48khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Riff48Khz16BitMonoPcm,
2616
- "audio-48khz-96kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio48Khz96KBitRateMonoMp3,
2617
- "audio-48khz-192kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio48Khz192KBitRateMonoMp3,
2618
- "ogg-48khz-16bit-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Ogg48Khz16BitMonoOpus,
2619
- "webm-16khz-16bit-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Webm16Khz16BitMonoOpus,
2620
- "webm-24khz-16bit-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Webm24Khz16BitMonoOpus,
2621
- "webm-24khz-16bit-24kbps-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Webm24Khz16Bit24KbpsMonoOpus,
2622
- "raw-24khz-16bit-mono-truesilk": SpeechSDK.SpeechSynthesisOutputFormat.Raw24Khz16BitMonoTrueSilk,
2623
- "raw-8khz-8bit-mono-alaw": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz8BitMonoALaw,
2624
- "riff-8khz-8bit-mono-alaw": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz8BitMonoALaw,
2625
- "audio-16khz-16bit-32kbps-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz16Bit32KbpsMonoOpus,
2626
- "audio-24khz-16bit-48kbps-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz16Bit48KbpsMonoOpus,
2627
- "audio-24khz-16bit-24kbps-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz16Bit24KbpsMonoOpus,
2628
- "raw-22050hz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Raw22050Hz16BitMonoPcm,
2629
- "riff-22050hz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Riff22050Hz16BitMonoPcm,
2630
- "raw-44100hz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Raw44100Hz16BitMonoPcm,
2631
- "riff-44100hz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Riff44100Hz16BitMonoPcm,
2632
- "amr-wb-16000hz": SpeechSDK.SpeechSynthesisOutputFormat.AmrWb16000Hz,
2633
- "g722-16khz-64kbps": SpeechSDK.SpeechSynthesisOutputFormat.G72216Khz64Kbps
2812
+ // packages/ssml-core/dist/index.mjs
2813
+ var __typeError2 = (msg) => {
2814
+ throw TypeError(msg);
2634
2815
  };
2635
- function resolveOutputFormat(outputFormat) {
2636
- const resolvedFormat = OUTPUT_FORMATS[outputFormat];
2637
- if (resolvedFormat === void 0) {
2638
- throw new Error(`Unsupported Azure Speech output format: ${outputFormat}`);
2639
- }
2640
- return resolvedFormat;
2641
- }
2642
-
2643
- // packages/azure-tts-client/src/speechConfig.ts
2644
- function resolveEndpoint(config) {
2645
- const endpoint = config.endpoint?.trim() || "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
2646
- return endpoint.replace(/\{region\}/g, encodeURIComponent(config.region));
2647
- }
2648
- function createSpeechConfig(config) {
2649
- const { outputFormat = DEFAULT_OUTPUT_FORMAT, subscriptionKey } = config;
2650
- const endpoint = new URL(resolveEndpoint(config));
2651
- const speechConfig = import_microsoft_cognitiveservices_speech_sdk.SpeechConfig.fromEndpoint(endpoint, subscriptionKey);
2652
- speechConfig.speechSynthesisOutputFormat = resolveOutputFormat(outputFormat);
2653
- return speechConfig;
2654
- }
2655
-
2656
- // packages/azure-tts-client/src/synthesis.ts
2657
- function ascii(bytes, offset, value) {
2658
- return [...value].every((character, index) => bytes[offset + index] === character.charCodeAt(0));
2659
- }
2660
- function readUint32(bytes, offset) {
2661
- return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(offset, true);
2662
- }
2663
- function parseWav(buffer) {
2664
- const bytes = new Uint8Array(buffer);
2665
- if (bytes.byteLength < 12 || !ascii(bytes, 0, "RIFF") || !ascii(bytes, 8, "WAVE")) {
2666
- throw new Error("Invalid WAV/RIFF audio buffer.");
2667
- }
2668
- const chunks = [];
2669
- const dataParts = [];
2670
- let format;
2671
- let offset = 12;
2672
- while (offset < bytes.byteLength) {
2673
- if (offset + 8 > bytes.byteLength) throw new Error("Invalid WAV chunk header.");
2674
- const id = String.fromCharCode(...bytes.slice(offset, offset + 4));
2675
- const size = readUint32(bytes, offset + 4);
2676
- const dataStart = offset + 8;
2677
- const dataEnd = dataStart + size;
2678
- if (dataEnd > bytes.byteLength) throw new Error(`WAV chunk "${id}" exceeds the audio buffer.`);
2679
- const data2 = bytes.slice(dataStart, dataEnd);
2680
- chunks.push({ id, data: data2 });
2681
- if (id === "fmt ") format ?? (format = data2);
2682
- if (id === "data") dataParts.push(data2);
2683
- offset = dataEnd + (size & 1);
2684
- if (offset > bytes.byteLength) throw new Error("Invalid WAV chunk padding.");
2685
- }
2686
- if (!format || dataParts.length === 0) throw new Error("WAV audio must contain fmt and data chunks.");
2687
- const dataLength = dataParts.reduce((total, part) => total + part.byteLength, 0);
2688
- const data = new Uint8Array(dataLength);
2689
- let dataOffset = 0;
2690
- for (const part of dataParts) {
2691
- data.set(part, dataOffset);
2692
- dataOffset += part.byteLength;
2693
- }
2694
- return { chunks, data, format };
2816
+ var __accessCheck2 = (obj, member, msg) => member.has(obj) || __typeError2("Cannot " + msg);
2817
+ var __privateGet2 = (obj, member, getter) => (__accessCheck2(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
2818
+ var __privateAdd2 = (obj, member, value) => member.has(obj) ? __typeError2("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
2819
+ var __privateSet2 = (obj, member, value, setter) => (__accessCheck2(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
2820
+ var SYNTHESIS_NAMESPACE2 = "http://www.w3.org/2001/10/synthesis";
2821
+ var MSTTS_NAMESPACE2 = "http://www.w3.org/2001/mstts";
2822
+ var MAX_NESTING_DEPTH2 = 1e3;
2823
+ var SSML_TAGS2 = {
2824
+ SPEAK: "speak",
2825
+ VOICE: "voice",
2826
+ PROSODY: "prosody",
2827
+ BREAK: "break",
2828
+ EXPRESS_AS: "express-as",
2829
+ EXPRESS_AS_CAMEL: "expressAs",
2830
+ MSTTS_EXPRESS_AS: "mstts:express-as",
2831
+ SAY_AS: "say-as",
2832
+ SAY_AS_CAMEL: "sayAs",
2833
+ PHONEME: "phoneme",
2834
+ EMPHASIS: "emphasis",
2835
+ AUDIO: "audio",
2836
+ SUB: "sub",
2837
+ LANG: "lang",
2838
+ MARK: "mark",
2839
+ BOOKMARK: "bookmark",
2840
+ LEXICON: "lexicon",
2841
+ PARAGRAPH: "p",
2842
+ SENTENCE: "s",
2843
+ WORD: "w",
2844
+ MSTTS_SILENCE: "mstts:silence",
2845
+ SILENCE: "silence",
2846
+ MSTTS_VISEME: "mstts:viseme",
2847
+ VISEME: "viseme",
2848
+ MSTTS_AUDIO_DURATION: "mstts:audioduration",
2849
+ MSTTS_DIALOG: "mstts:dialog",
2850
+ MSTTS_TURN: "mstts:turn",
2851
+ MSTTS_BACKGROUND_AUDIO: "mstts:backgroundaudio",
2852
+ MSTTS_TTS_EMBEDDING: "mstts:ttsembedding",
2853
+ MSTTS_EMBEDDING: "mstts:embedding",
2854
+ MSTTS_VOICE_CONVERSION: "mstts:voiceconversion"
2855
+ };
2856
+ var SSML_ATTRS2 = {
2857
+ VERSION: "version",
2858
+ XMLNS: "xmlns",
2859
+ XML_LANG: "xml:lang",
2860
+ LANG: "lang",
2861
+ MSTTS_XMLNS: "xmlns:mstts",
2862
+ NAME: "name",
2863
+ VOICE: "voice",
2864
+ SPEAKER: "speaker",
2865
+ EFFECT: "effect",
2866
+ RATE: "rate",
2867
+ PITCH: "pitch",
2868
+ VOLUME: "volume",
2869
+ CONTOUR: "contour",
2870
+ RANGE: "range",
2871
+ TIME: "time",
2872
+ STRENGTH: "strength",
2873
+ STYLE: "style",
2874
+ STYLE_DEGREE: "styledegree",
2875
+ STYLE_DEGREE_CAMEL: "styleDegree",
2876
+ STYLE_DEGREE_HYPHEN: "style-degree",
2877
+ ROLE: "role",
2878
+ INTERPRET_AS: "interpret-as",
2879
+ FORMAT: "format",
2880
+ DETAIL: "detail",
2881
+ ALPHABET: "alphabet",
2882
+ PH: "ph",
2883
+ LEVEL: "level",
2884
+ SRC: "src",
2885
+ DESC: "desc",
2886
+ CLIP_BEGIN: "clipBegin",
2887
+ CLIP_END: "clipEnd",
2888
+ SPEED: "speed",
2889
+ REPEAT_COUNT: "repeatCount",
2890
+ REPEAT_DURATION: "repeatDuration",
2891
+ SOUND_LEVEL: "soundLevel",
2892
+ ALIAS: "alias",
2893
+ MARK: "mark",
2894
+ URI: "uri",
2895
+ ID: "id",
2896
+ MODEL: "model",
2897
+ PROFILE: "profile",
2898
+ URL: "url",
2899
+ SPEAKER_PROFILE_ID: "speakerProfileId",
2900
+ TYPE: "type",
2901
+ VALUE: "value",
2902
+ FADE_IN: "fadein",
2903
+ FADE_OUT: "fadeout"
2904
+ };
2905
+ var XML_ENTITIES2 = {
2906
+ amp: "&",
2907
+ apos: "'",
2908
+ gt: ">",
2909
+ lt: "<",
2910
+ quot: '"'
2911
+ };
2912
+ function hasOwn2(object, property) {
2913
+ return Object.getOwnPropertyDescriptor(object, property) !== void 0;
2695
2914
  }
2696
- function writeUint32(target, offset, value) {
2697
- new DataView(target.buffer).setUint32(offset, value, true);
2915
+ function setAttribute2(attributes, name, value) {
2916
+ Object.defineProperty(attributes, name, {
2917
+ configurable: true,
2918
+ enumerable: true,
2919
+ value,
2920
+ writable: true
2921
+ });
2698
2922
  }
2699
- function writeChunk(target, offset, id, data) {
2700
- for (let index = 0; index < 4; index += 1) target[offset + index] = id.charCodeAt(index) ?? 0;
2701
- writeUint32(target, offset + 4, data.byteLength);
2702
- target.set(data, offset + 8);
2703
- const end = offset + 8 + data.byteLength;
2704
- if (data.byteLength & 1) target[end] = 0;
2705
- return end + (data.byteLength & 1);
2923
+ function decodeEntity2(entity) {
2924
+ const namedValue = hasOwn2(XML_ENTITIES2, entity) ? XML_ENTITIES2[entity] : void 0;
2925
+ if (namedValue !== void 0) {
2926
+ return namedValue;
2927
+ }
2928
+ const isHexadecimal = entity.startsWith("#x") || entity.startsWith("#X");
2929
+ const isDecimal = entity.startsWith("#");
2930
+ if (!isHexadecimal && !isDecimal) {
2931
+ throw new Error(`Unknown XML entity: &${entity};`);
2932
+ }
2933
+ const digits = entity.slice(isHexadecimal ? 2 : 1);
2934
+ const codePoint = Number.parseInt(digits, isHexadecimal ? 16 : 10);
2935
+ if (!digits || !Number.isInteger(codePoint) || codePoint < 0 || codePoint > 1114111 || codePoint >= 55296 && codePoint <= 57343 || codePoint < 32 && ![9, 10, 13].includes(codePoint)) {
2936
+ throw new Error(`Invalid XML character reference: &${entity};`);
2937
+ }
2938
+ return String.fromCodePoint(codePoint);
2706
2939
  }
2707
- function mergeWavBuffers(buffers) {
2708
- if (buffers.length === 0) return new ArrayBuffer(0);
2709
- const parsed = buffers.map(parseWav);
2710
- const first = parsed[0];
2711
- if (!first) throw new Error("At least one WAV buffer is required.");
2712
- if (parsed.some(
2713
- (item) => item.format.length !== first.format.length || item.format.some((value, i) => value !== first.format[i])
2714
- ))
2715
- throw new Error("WAV buffers have incompatible fmt chunks.");
2716
- const dataLength = parsed.reduce((total, item) => total + item.data.byteLength, 0);
2717
- const nonDataLength = first.chunks.reduce(
2718
- (total, chunk) => chunk.id === "data" ? total : total + 8 + chunk.data.byteLength + (chunk.data.byteLength & 1),
2719
- 0
2720
- );
2721
- const outputLength = 12 + nonDataLength + 8 + dataLength + (dataLength & 1);
2722
- if (outputLength - 8 > 4294967295) throw new RangeError("Merged WAV exceeds the RIFF format size limit.");
2723
- const output = new Uint8Array(outputLength);
2724
- output.set(Uint8Array.from([82, 73, 70, 70]), 0);
2725
- writeUint32(output, 4, outputLength - 8);
2726
- output.set(Uint8Array.from([87, 65, 86, 69]), 8);
2727
- let outputOffset = 12;
2728
- let dataWritten = false;
2729
- for (const chunk of first.chunks) {
2730
- if (chunk.id === "data") {
2731
- if (dataWritten) continue;
2732
- const data = new Uint8Array(dataLength);
2733
- let dataOffset = 0;
2734
- for (const item of parsed) {
2735
- data.set(item.data, dataOffset);
2736
- dataOffset += item.data.byteLength;
2737
- }
2738
- outputOffset = writeChunk(output, outputOffset, "data", data);
2739
- dataWritten = true;
2740
- } else {
2741
- outputOffset = writeChunk(output, outputOffset, chunk.id, chunk.data);
2940
+ function decodeXmlEntities2(value) {
2941
+ let result = "";
2942
+ let start = 0;
2943
+ while (true) {
2944
+ const ampersand = value.indexOf("&", start);
2945
+ if (ampersand === -1) {
2946
+ return result + value.slice(start);
2742
2947
  }
2948
+ result += value.slice(start, ampersand);
2949
+ const semicolon = value.indexOf(";", ampersand + 1);
2950
+ if (semicolon === -1) {
2951
+ throw new Error("Unterminated XML entity reference");
2952
+ }
2953
+ result += decodeEntity2(value.slice(ampersand + 1, semicolon));
2954
+ start = semicolon + 1;
2743
2955
  }
2744
- if (!dataWritten) throw new Error("WAV audio must contain a data chunk.");
2745
- return output.buffer;
2746
- }
2747
- function skipId3v2(bytes) {
2748
- if (!ascii(bytes, 0, "ID3") || bytes.byteLength < 10) return 0;
2749
- const size = [bytes[6], bytes[7], bytes[8], bytes[9]].reduce((total, value) => total << 7 | value & 127, 0);
2750
- const hasFooter = (bytes[5] & 16) !== 0;
2751
- return Math.min(bytes.byteLength, 10 + size + (hasFooter ? 10 : 0));
2752
- }
2753
- function stripMp3Tags(buffer) {
2754
- const bytes = new Uint8Array(buffer);
2755
- const start = skipId3v2(bytes);
2756
- const end = bytes.byteLength >= 128 && ascii(bytes, bytes.byteLength - 128, "TAG") ? bytes.byteLength - 128 : bytes.byteLength;
2757
- return bytes.slice(Math.min(start, end), end);
2758
- }
2759
- function isMp3Format(format) {
2760
- return /(?:mp3|mpeg)/i.test(format);
2761
2956
  }
2762
- function isWavFormat(format) {
2763
- return /(?:wav|wave|riff)/i.test(format);
2764
- }
2765
- function isRawFormat(format) {
2766
- return /^raw(?:-|$)/i.test(format);
2957
+ function isXmlNameStart2(value) {
2958
+ return value !== void 0 && /[A-Za-z_]/.test(value);
2767
2959
  }
2768
- function resolveMergeAudioFormat(format) {
2769
- if (isWavFormat(format)) return "wav";
2770
- if (isMp3Format(format)) return "mp3";
2771
- if (isRawFormat(format)) return "raw";
2772
- return void 0;
2960
+ function isXmlNameCharacter2(value) {
2961
+ return value !== void 0 && /[A-Za-z0-9_.:-]/.test(value);
2773
2962
  }
2774
- function canMergeAudioFormat(format) {
2775
- return resolveMergeAudioFormat(format) !== void 0;
2963
+ function isXmlWhitespace2(value) {
2964
+ return value === " " || value === " " || value === "\r" || value === "\n";
2776
2965
  }
2777
- function mergeAudioBuffers(buffers, format) {
2778
- if (isWavFormat(format)) return mergeWavBuffers(buffers);
2779
- if (isMp3Format(format)) {
2780
- const parts = buffers.map(stripMp3Tags);
2781
- const output = new Uint8Array(parts.reduce((total, part) => total + part.byteLength, 0));
2782
- let offset = 0;
2783
- for (const part of parts) {
2784
- output.set(part, offset);
2785
- offset += part.byteLength;
2786
- }
2787
- return output.buffer;
2966
+ function removeStandardNamespaceAttributes2(attributes) {
2967
+ if (attributes[SSML_ATTRS2.XMLNS] === SYNTHESIS_NAMESPACE2) {
2968
+ delete attributes[SSML_ATTRS2.XMLNS];
2788
2969
  }
2789
- if (isRawFormat(format)) {
2790
- const output = new Uint8Array(buffers.reduce((total, buffer) => total + buffer.byteLength, 0));
2791
- let offset = 0;
2792
- for (const buffer of buffers) {
2793
- output.set(new Uint8Array(buffer), offset);
2794
- offset += buffer.byteLength;
2795
- }
2796
- return output.buffer;
2970
+ if (attributes[SSML_ATTRS2.MSTTS_XMLNS] === MSTTS_NAMESPACE2) {
2971
+ delete attributes[SSML_ATTRS2.MSTTS_XMLNS];
2797
2972
  }
2798
- throw new UnsupportedMergeFormatError(format);
2799
2973
  }
2800
- function closeSpeechResources(speechConfig, synthesizer) {
2801
- try {
2802
- synthesizer.close();
2803
- } catch {
2974
+ var _index2;
2975
+ var XmlParser2 = class {
2976
+ constructor(source) {
2977
+ __privateAdd2(this, _index2, 0);
2978
+ this.source = source;
2804
2979
  }
2805
- try {
2806
- speechConfig.close();
2807
- } catch {
2808
- }
2809
- }
2810
- var ticksToMilliseconds = (ticks) => Math.max(0, ticks) / 1e4;
2811
- async function synthesizeSsml(ssml, config) {
2812
- if (config.signal?.aborted) {
2813
- throw createSpeechSdkError("Speech synthesis was cancelled.");
2814
- }
2815
- const speechConfig = createSpeechConfig(config);
2816
- const synthesizer = new SpeechSDK2.SpeechSynthesizer(speechConfig, null);
2817
- return await new Promise((resolve, reject) => {
2818
- let resourcesClosed = false;
2819
- let settled = false;
2820
- let timeout;
2821
- let abortHandler;
2822
- const cleanup = () => {
2823
- if (timeout) clearTimeout(timeout);
2824
- if (abortHandler) config.signal?.removeEventListener("abort", abortHandler);
2825
- };
2826
- const closeResources = () => {
2827
- if (resourcesClosed) return;
2828
- resourcesClosed = true;
2829
- closeSpeechResources(speechConfig, synthesizer);
2830
- };
2831
- const rejectWithError = (error) => {
2832
- if (settled) return;
2833
- settled = true;
2834
- cleanup();
2835
- closeResources();
2836
- reject(createSpeechSdkError(error));
2837
- };
2838
- const boundaries = [];
2839
- const visemes = [];
2840
- const bookmarks = [];
2841
- synthesizer.wordBoundary = (_sender, event) => {
2842
- boundaries.push({
2843
- text: event.text,
2844
- audioOffsetMs: ticksToMilliseconds(event.audioOffset),
2845
- durationMs: ticksToMilliseconds(event.duration)
2846
- });
2847
- };
2848
- synthesizer.visemeReceived = (_sender, event) => {
2849
- visemes.push({ visemeId: event.visemeId, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });
2850
- };
2851
- synthesizer.bookmarkReached = (_sender, event) => {
2852
- bookmarks.push({ name: event.text, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });
2853
- };
2854
- const cb = (result) => {
2855
- if (settled) return;
2856
- const { reason, errorDetails } = result;
2857
- if (reason !== SpeechSDK2.ResultReason.SynthesizingAudioCompleted) {
2858
- const err = errorDetails || `Speech synthesis failed with reason ${reason}.`;
2859
- rejectWithError(err);
2860
- return;
2861
- }
2862
- settled = true;
2863
- cleanup();
2864
- closeResources();
2865
- const eventDurationMs = Math.max(
2866
- 0,
2867
- ...(boundaries ?? []).map((boundary) => boundary.audioOffsetMs + boundary.durationMs),
2868
- ...(visemes ?? []).map((viseme) => viseme.audioOffsetMs),
2869
- ...(bookmarks ?? []).map((bookmark) => bookmark.audioOffsetMs)
2870
- );
2871
- const durationMs = result.audioDuration ? ticksToMilliseconds(result.audioDuration) : eventDurationMs;
2872
- const requestId = result.resultId;
2873
- const addSourceMetadata = (event) => ({
2874
- ...event,
2875
- ...config.sourceTextRange ? { textRange: { ...config.sourceTextRange } } : {},
2876
- ...config.sourceTextRange ? { originalTextRange: { ...config.sourceTextRange } } : {},
2877
- ...config.chunkIndex !== void 0 ? { chunkIndex: config.chunkIndex } : {},
2878
- ...config.sourceNodePath ? { sourceNodePath: [...config.sourceNodePath] } : {},
2879
- ...requestId ? { requestId } : {}
2880
- });
2881
- const sourceBoundaries = boundaries.map((boundary) => addSourceMetadata(boundary));
2882
- const sourceVisemes = visemes.map((viseme) => addSourceMetadata(viseme));
2883
- const sourceBookmarks = bookmarks.map((bookmark) => addSourceMetadata(bookmark));
2884
- resolve({
2885
- audioData: result.audioData,
2886
- durationMs,
2887
- ...config.sourceTextRange ? { textRange: { ...config.sourceTextRange } } : {},
2888
- ...requestId ? { requestId } : {},
2889
- ...sourceBoundaries.length > 0 ? { boundaries: sourceBoundaries, wordBoundary: sourceBoundaries, wordBoundaries: sourceBoundaries } : {},
2890
- ...sourceVisemes.length > 0 ? { visemes: sourceVisemes } : {},
2891
- ...sourceBookmarks.length > 0 ? { bookmarks: sourceBookmarks } : {}
2892
- });
2893
- };
2894
- try {
2895
- if (config.signal) {
2896
- abortHandler = () => rejectWithError("Speech synthesis was cancelled.");
2897
- config.signal.addEventListener("abort", abortHandler, { once: true });
2898
- }
2899
- if (config.timeoutMs !== void 0 && config.timeoutMs > 0) {
2900
- timeout = setTimeout(
2901
- () => rejectWithError(`Speech synthesis timed out after ${config.timeoutMs} ms.`),
2902
- config.timeoutMs
2903
- );
2904
- }
2905
- synthesizer.speakSsmlAsync(ssml, cb, rejectWithError);
2906
- } catch (error) {
2907
- rejectWithError(error);
2908
- }
2909
- });
2910
- }
2911
- async function synthesizeSsmlChunks(chunks, config) {
2912
- const results = [];
2913
- const totalChunks = chunks.length;
2914
- const report = (event) => config.onProgress?.(event);
2915
- for (const [index, chunk] of chunks.entries()) {
2916
- const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
2917
- report({
2918
- currentChunk: index,
2919
- totalChunks,
2920
- percent: totalChunks === 0 ? 100 : Math.round(index / totalChunks * 100),
2921
- chunkIndex: index,
2922
- originalTextRange: input.originalTextRange,
2923
- status: "pending",
2924
- durationMs: 0
2925
- });
2926
- }
2927
- for (const [index, chunk] of chunks.entries()) {
2928
- const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
2929
- report({
2930
- currentChunk: index,
2931
- totalChunks,
2932
- percent: totalChunks === 0 ? 100 : Math.round(index / totalChunks * 100),
2933
- chunkIndex: index,
2934
- originalTextRange: input.originalTextRange,
2935
- status: "synthesizing",
2936
- durationMs: 0
2937
- });
2938
- const startedAt = Date.now();
2939
- try {
2940
- const result = await synthesizeSsml(input.ssml, {
2941
- ...config,
2942
- ...input.originalTextRange ? { sourceTextRange: input.originalTextRange } : {},
2943
- ...input.sourceNodePath ? { sourceNodePath: input.sourceNodePath } : {},
2944
- chunkIndex: index,
2945
- onProgress: void 0
2946
- });
2947
- results.push(result);
2948
- report({
2949
- currentChunk: index + 1,
2950
- totalChunks,
2951
- percent: totalChunks === 0 ? 100 : Math.round((index + 1) / totalChunks * 100),
2952
- chunkIndex: index,
2953
- originalTextRange: input.originalTextRange,
2954
- status: "success",
2955
- durationMs: Date.now() - startedAt
2956
- });
2957
- } catch (error) {
2958
- report({
2959
- currentChunk: index,
2960
- totalChunks,
2961
- percent: totalChunks === 0 ? 100 : Math.round(index / totalChunks * 100),
2962
- chunkIndex: index,
2963
- originalTextRange: input.originalTextRange,
2964
- status: "failed",
2965
- durationMs: Date.now() - startedAt,
2966
- error
2967
- });
2968
- throw error;
2969
- }
2970
- }
2971
- return mergeSynthesisResults(results, config.outputFormat ?? "audio-16khz-128kbitrate-mono-mp3");
2972
- }
2973
- function mergeSynthesisResults(results, format) {
2974
- const audioData = format ? new Uint8Array(
2975
- mergeAudioBuffers(
2976
- results.map((result) => result.audioData),
2977
- format
2978
- )
2979
- ) : new Uint8Array(results.reduce((total, result) => total + result.audioData.byteLength, 0));
2980
- if (!format) {
2981
- let offset = 0;
2982
- for (const result of results) {
2983
- audioData.set(new Uint8Array(result.audioData), offset);
2984
- offset += result.audioData.byteLength;
2985
- }
2986
- }
2987
- const boundaries = [];
2988
- const visemes = [];
2989
- const bookmarks = [];
2990
- let durationOffset = 0;
2991
- for (const result of results) {
2992
- const chunkBoundaries = result.boundaries && result.boundaries.length > 0 ? result.boundaries : result.wordBoundary ?? result.wordBoundaries ?? [];
2993
- for (const boundary of chunkBoundaries) {
2994
- const textRange = boundary.textRange ?? result.textRange;
2995
- const originalTextRange = boundary.originalTextRange ?? textRange;
2996
- const requestId = boundary.requestId ?? result.requestId;
2997
- boundaries.push({
2998
- ...boundary,
2999
- audioOffsetMs: boundary.audioOffsetMs + durationOffset,
3000
- chunkAudioOffsetMs: boundary.chunkAudioOffsetMs ?? boundary.audioOffsetMs,
3001
- ...boundary.chunkIndex === void 0 ? { chunkIndex: results.indexOf(result) } : {},
3002
- ...boundary.sourceNodePath ? { sourceNodePath: [...boundary.sourceNodePath] } : {},
3003
- ...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
3004
- ...textRange ? { textRange: { ...textRange } } : {},
3005
- ...requestId ? { requestId } : {}
3006
- });
3007
- }
3008
- for (const viseme of result.visemes ?? []) {
3009
- const textRange = viseme.textRange ?? result.textRange;
3010
- const originalTextRange = viseme.originalTextRange ?? textRange;
3011
- const requestId = viseme.requestId ?? result.requestId;
3012
- visemes.push({
3013
- ...viseme,
3014
- audioOffsetMs: viseme.audioOffsetMs + durationOffset,
3015
- chunkAudioOffsetMs: viseme.chunkAudioOffsetMs ?? viseme.audioOffsetMs,
3016
- ...viseme.chunkIndex === void 0 ? { chunkIndex: results.indexOf(result) } : {},
3017
- ...viseme.sourceNodePath ? { sourceNodePath: [...viseme.sourceNodePath] } : {},
3018
- ...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
3019
- ...textRange ? { textRange: { ...textRange } } : {},
3020
- ...requestId ? { requestId } : {}
3021
- });
3022
- }
3023
- for (const bookmark of result.bookmarks ?? []) {
3024
- const textRange = bookmark.textRange ?? result.textRange;
3025
- const originalTextRange = bookmark.originalTextRange ?? textRange;
3026
- const requestId = bookmark.requestId ?? result.requestId;
3027
- bookmarks.push({
3028
- ...bookmark,
3029
- audioOffsetMs: bookmark.audioOffsetMs + durationOffset,
3030
- chunkAudioOffsetMs: bookmark.chunkAudioOffsetMs ?? bookmark.audioOffsetMs,
3031
- ...bookmark.chunkIndex === void 0 ? { chunkIndex: results.indexOf(result) } : {},
3032
- ...bookmark.sourceNodePath ? { sourceNodePath: [...bookmark.sourceNodePath] } : {},
3033
- ...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
3034
- ...textRange ? { textRange: { ...textRange } } : {},
3035
- ...requestId ? { requestId } : {}
3036
- });
3037
- }
3038
- durationOffset += Math.max(0, result.durationMs);
3039
- }
3040
- return {
3041
- audioData: audioData.buffer,
3042
- durationMs: durationOffset,
3043
- ...boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {},
3044
- ...visemes.length > 0 ? { visemes } : {},
3045
- ...bookmarks.length > 0 ? { bookmarks } : {},
3046
- ...results.length === 1 && results[0]?.requestId ? { requestId: results[0].requestId } : {},
3047
- ...results.length === 1 && results[0]?.textRange ? { textRange: { ...results[0].textRange } } : {}
3048
- };
3049
- }
3050
- async function synthesizeSpeech(ssml, config) {
3051
- return (await synthesizeSsml(ssml, config)).audioData;
3052
- }
3053
-
3054
- // packages/ssml-core/dist/index.mjs
3055
- var __typeError2 = (msg) => {
3056
- throw TypeError(msg);
3057
- };
3058
- var __accessCheck2 = (obj, member, msg) => member.has(obj) || __typeError2("Cannot " + msg);
3059
- var __privateGet2 = (obj, member, getter) => (__accessCheck2(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
3060
- var __privateAdd2 = (obj, member, value) => member.has(obj) ? __typeError2("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
3061
- var __privateSet2 = (obj, member, value, setter) => (__accessCheck2(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
3062
- var SYNTHESIS_NAMESPACE2 = "http://www.w3.org/2001/10/synthesis";
3063
- var MSTTS_NAMESPACE2 = "http://www.w3.org/2001/mstts";
3064
- var MAX_NESTING_DEPTH2 = 1e3;
3065
- var SSML_TAGS2 = {
3066
- SPEAK: "speak",
3067
- VOICE: "voice",
3068
- PROSODY: "prosody",
3069
- BREAK: "break",
3070
- EXPRESS_AS: "express-as",
3071
- EXPRESS_AS_CAMEL: "expressAs",
3072
- MSTTS_EXPRESS_AS: "mstts:express-as",
3073
- SAY_AS: "say-as",
3074
- SAY_AS_CAMEL: "sayAs",
3075
- PHONEME: "phoneme",
3076
- EMPHASIS: "emphasis",
3077
- AUDIO: "audio",
3078
- SUB: "sub",
3079
- LANG: "lang",
3080
- MARK: "mark",
3081
- BOOKMARK: "bookmark",
3082
- LEXICON: "lexicon",
3083
- PARAGRAPH: "p",
3084
- SENTENCE: "s",
3085
- WORD: "w",
3086
- MSTTS_SILENCE: "mstts:silence",
3087
- SILENCE: "silence",
3088
- MSTTS_VISEME: "mstts:viseme",
3089
- VISEME: "viseme",
3090
- MSTTS_AUDIO_DURATION: "mstts:audioduration",
3091
- MSTTS_DIALOG: "mstts:dialog",
3092
- MSTTS_TURN: "mstts:turn",
3093
- MSTTS_BACKGROUND_AUDIO: "mstts:backgroundaudio",
3094
- MSTTS_TTS_EMBEDDING: "mstts:ttsembedding",
3095
- MSTTS_EMBEDDING: "mstts:embedding",
3096
- MSTTS_VOICE_CONVERSION: "mstts:voiceconversion"
3097
- };
3098
- var SSML_ATTRS2 = {
3099
- VERSION: "version",
3100
- XMLNS: "xmlns",
3101
- XML_LANG: "xml:lang",
3102
- LANG: "lang",
3103
- MSTTS_XMLNS: "xmlns:mstts",
3104
- NAME: "name",
3105
- VOICE: "voice",
3106
- SPEAKER: "speaker",
3107
- EFFECT: "effect",
3108
- RATE: "rate",
3109
- PITCH: "pitch",
3110
- VOLUME: "volume",
3111
- CONTOUR: "contour",
3112
- RANGE: "range",
3113
- TIME: "time",
3114
- STRENGTH: "strength",
3115
- STYLE: "style",
3116
- STYLE_DEGREE: "styledegree",
3117
- STYLE_DEGREE_CAMEL: "styleDegree",
3118
- STYLE_DEGREE_HYPHEN: "style-degree",
3119
- ROLE: "role",
3120
- INTERPRET_AS: "interpret-as",
3121
- FORMAT: "format",
3122
- DETAIL: "detail",
3123
- ALPHABET: "alphabet",
3124
- PH: "ph",
3125
- LEVEL: "level",
3126
- SRC: "src",
3127
- DESC: "desc",
3128
- CLIP_BEGIN: "clipBegin",
3129
- CLIP_END: "clipEnd",
3130
- SPEED: "speed",
3131
- REPEAT_COUNT: "repeatCount",
3132
- REPEAT_DURATION: "repeatDuration",
3133
- SOUND_LEVEL: "soundLevel",
3134
- ALIAS: "alias",
3135
- MARK: "mark",
3136
- URI: "uri",
3137
- ID: "id",
3138
- MODEL: "model",
3139
- PROFILE: "profile",
3140
- URL: "url",
3141
- SPEAKER_PROFILE_ID: "speakerProfileId",
3142
- TYPE: "type",
3143
- VALUE: "value",
3144
- FADE_IN: "fadein",
3145
- FADE_OUT: "fadeout"
3146
- };
3147
- var XML_ENTITIES2 = {
3148
- amp: "&",
3149
- apos: "'",
3150
- gt: ">",
3151
- lt: "<",
3152
- quot: '"'
3153
- };
3154
- function hasOwn2(object, property) {
3155
- return Object.getOwnPropertyDescriptor(object, property) !== void 0;
3156
- }
3157
- function setAttribute2(attributes, name, value) {
3158
- Object.defineProperty(attributes, name, {
3159
- configurable: true,
3160
- enumerable: true,
3161
- value,
3162
- writable: true
3163
- });
3164
- }
3165
- function decodeEntity2(entity) {
3166
- const namedValue = hasOwn2(XML_ENTITIES2, entity) ? XML_ENTITIES2[entity] : void 0;
3167
- if (namedValue !== void 0) {
3168
- return namedValue;
3169
- }
3170
- const isHexadecimal = entity.startsWith("#x") || entity.startsWith("#X");
3171
- const isDecimal = entity.startsWith("#");
3172
- if (!isHexadecimal && !isDecimal) {
3173
- throw new Error(`Unknown XML entity: &${entity};`);
3174
- }
3175
- const digits = entity.slice(isHexadecimal ? 2 : 1);
3176
- const codePoint = Number.parseInt(digits, isHexadecimal ? 16 : 10);
3177
- if (!digits || !Number.isInteger(codePoint) || codePoint < 0 || codePoint > 1114111 || codePoint >= 55296 && codePoint <= 57343 || codePoint < 32 && ![9, 10, 13].includes(codePoint)) {
3178
- throw new Error(`Invalid XML character reference: &${entity};`);
3179
- }
3180
- return String.fromCodePoint(codePoint);
3181
- }
3182
- function decodeXmlEntities2(value) {
3183
- let result = "";
3184
- let start = 0;
3185
- while (true) {
3186
- const ampersand = value.indexOf("&", start);
3187
- if (ampersand === -1) {
3188
- return result + value.slice(start);
3189
- }
3190
- result += value.slice(start, ampersand);
3191
- const semicolon = value.indexOf(";", ampersand + 1);
3192
- if (semicolon === -1) {
3193
- throw new Error("Unterminated XML entity reference");
3194
- }
3195
- result += decodeEntity2(value.slice(ampersand + 1, semicolon));
3196
- start = semicolon + 1;
3197
- }
3198
- }
3199
- function isXmlNameStart2(value) {
3200
- return value !== void 0 && /[A-Za-z_]/.test(value);
3201
- }
3202
- function isXmlNameCharacter2(value) {
3203
- return value !== void 0 && /[A-Za-z0-9_.:-]/.test(value);
3204
- }
3205
- function isXmlWhitespace2(value) {
3206
- return value === " " || value === " " || value === "\r" || value === "\n";
3207
- }
3208
- function removeStandardNamespaceAttributes2(attributes) {
3209
- if (attributes[SSML_ATTRS2.XMLNS] === SYNTHESIS_NAMESPACE2) {
3210
- delete attributes[SSML_ATTRS2.XMLNS];
3211
- }
3212
- if (attributes[SSML_ATTRS2.MSTTS_XMLNS] === MSTTS_NAMESPACE2) {
3213
- delete attributes[SSML_ATTRS2.MSTTS_XMLNS];
3214
- }
3215
- }
3216
- var _index2;
3217
- var XmlParser2 = class {
3218
- constructor(source) {
3219
- __privateAdd2(this, _index2, 0);
3220
- this.source = source;
3221
- }
3222
- parse() {
3223
- if (this.source.charCodeAt(0) === 65279) {
3224
- __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
3225
- }
3226
- this.skipMisc();
3227
- if (__privateGet2(this, _index2) >= this.source.length) {
3228
- this.fail("SSML input is empty");
3229
- }
3230
- if (this.source[__privateGet2(this, _index2)] !== "<") {
3231
- this.fail("SSML input must start with an XML element");
3232
- }
3233
- const root = this.parseElement(0);
3234
- this.skipMisc();
3235
- if (__privateGet2(this, _index2) !== this.source.length) {
3236
- this.fail("Unexpected content after the root XML element");
3237
- }
3238
- return root;
2980
+ parse() {
2981
+ if (this.source.charCodeAt(0) === 65279) {
2982
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
2983
+ }
2984
+ this.skipMisc();
2985
+ if (__privateGet2(this, _index2) >= this.source.length) {
2986
+ this.fail("SSML input is empty");
2987
+ }
2988
+ if (this.source[__privateGet2(this, _index2)] !== "<") {
2989
+ this.fail("SSML input must start with an XML element");
2990
+ }
2991
+ const root = this.parseElement(0);
2992
+ this.skipMisc();
2993
+ if (__privateGet2(this, _index2) !== this.source.length) {
2994
+ this.fail("Unexpected content after the root XML element");
2995
+ }
2996
+ return root;
3239
2997
  }
3240
2998
  parseElement(depth) {
3241
2999
  if (depth > MAX_NESTING_DEPTH2) {
@@ -3702,6 +3460,130 @@ function parseSsml2(xmlString) {
3702
3460
  }
3703
3461
  return document;
3704
3462
  }
3463
+ function decodeXmlText2(value) {
3464
+ return value.replace(/&(?:amp|apos|gt|lt|quot);|&#(?:x[\da-f]+|\d+);/gi, (entity) => {
3465
+ if (entity === "&amp;") return "&";
3466
+ if (entity === "&apos;") return "'";
3467
+ if (entity === "&gt;") return ">";
3468
+ if (entity === "&lt;") return "<";
3469
+ if (entity === "&quot;") return '"';
3470
+ const hexadecimal = entity.toLowerCase().startsWith("&#x");
3471
+ const digits = entity.slice(hexadecimal ? 3 : 2, -1);
3472
+ return String.fromCodePoint(Number.parseInt(digits, hexadecimal ? 16 : 10));
3473
+ });
3474
+ }
3475
+ function decodeXmlAttribute2(value) {
3476
+ return decodeXmlText2(value);
3477
+ }
3478
+ function findTagEnd3(source, start) {
3479
+ let quote = "";
3480
+ for (let index = start; index < source.length; index += 1) {
3481
+ const character = source[index];
3482
+ if (quote) {
3483
+ if (character === quote) quote = "";
3484
+ } else if (character === '"' || character === "'") {
3485
+ quote = character;
3486
+ } else if (character === ">") {
3487
+ return index;
3488
+ }
3489
+ }
3490
+ return source.length - 1;
3491
+ }
3492
+ function readTagName2(tag) {
3493
+ const match = /^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/.exec(tag);
3494
+ return match?.[1];
3495
+ }
3496
+ function readTagAttributes2(tag, name) {
3497
+ const attributes = {};
3498
+ const nameStart = tag.indexOf(name);
3499
+ const attributeSource = tag.slice(nameStart + name.length, tag.length - 1).replace(/\/\s*$/, "");
3500
+ const attributePattern = /([A-Za-z_][A-Za-z0-9_.:-]*)\s*=\s*(["'])([\s\S]*?)\2/g;
3501
+ for (const match of attributeSource.matchAll(attributePattern)) {
3502
+ attributes[match[1].toLowerCase()] = decodeXmlAttribute2(match[3]);
3503
+ }
3504
+ return attributes;
3505
+ }
3506
+ function collectSourceMap2(source) {
3507
+ const segments = [];
3508
+ const markers = [];
3509
+ const elements = [];
3510
+ let textOffset = 0;
3511
+ let index = 0;
3512
+ const textParts = [];
3513
+ const addText = (value) => {
3514
+ if (!value) return;
3515
+ const parent = elements[elements.length - 1];
3516
+ if (parent) parent.nextChildIndex += 1;
3517
+ const sourceNodePath = parent?.path ?? ["speak"];
3518
+ const start = textOffset;
3519
+ textOffset += value.length;
3520
+ textParts.push(value);
3521
+ segments.push({ text: value, range: { start, end: textOffset }, sourceNodePath: [...sourceNodePath] });
3522
+ };
3523
+ while (index < source.length) {
3524
+ if (source[index] !== "<") {
3525
+ const end2 = source.indexOf("<", index);
3526
+ const textEnd = end2 === -1 ? source.length : end2;
3527
+ addText(decodeXmlText2(source.slice(index, textEnd)));
3528
+ index = textEnd;
3529
+ continue;
3530
+ }
3531
+ if (source.startsWith("<!--", index)) {
3532
+ const end2 = source.indexOf("-->", index + 4);
3533
+ index = end2 === -1 ? source.length : end2 + 3;
3534
+ continue;
3535
+ }
3536
+ if (source.startsWith("<![CDATA[", index)) {
3537
+ const contentStart = index + 9;
3538
+ const end2 = source.indexOf("]]>", contentStart);
3539
+ const contentEnd = end2 === -1 ? source.length : end2;
3540
+ addText(source.slice(contentStart, contentEnd));
3541
+ index = end2 === -1 ? source.length : end2 + 3;
3542
+ continue;
3543
+ }
3544
+ if (source.startsWith("<?", index)) {
3545
+ const end2 = source.indexOf("?>", index + 2);
3546
+ index = end2 === -1 ? source.length : end2 + 2;
3547
+ continue;
3548
+ }
3549
+ const end = findTagEnd3(source, index + 1);
3550
+ const rawTag = source.slice(index, end + 1);
3551
+ if (rawTag.startsWith("</")) {
3552
+ elements.pop();
3553
+ index = end + 1;
3554
+ continue;
3555
+ }
3556
+ const name = readTagName2(rawTag);
3557
+ if (!name) {
3558
+ index = end + 1;
3559
+ continue;
3560
+ }
3561
+ const parent = elements[elements.length - 1];
3562
+ const childIndex = parent?.nextChildIndex ?? 0;
3563
+ if (parent) parent.nextChildIndex += 1;
3564
+ const path = parent ? [...parent.path, `${name}[${childIndex}]`] : [name];
3565
+ const attributes = readTagAttributes2(rawTag, name);
3566
+ const normalizedName = name.toLowerCase();
3567
+ if (normalizedName === "mark" || normalizedName === "bookmark") {
3568
+ const markerName = attributes[normalizedName === "mark" ? "name" : "mark"];
3569
+ if (markerName) {
3570
+ markers.push({
3571
+ kind: normalizedName,
3572
+ name: markerName,
3573
+ originalTextRange: { start: textOffset, end: textOffset },
3574
+ sourceNodePath: [...path]
3575
+ });
3576
+ }
3577
+ }
3578
+ if (!/\/\s*>$/.test(rawTag)) elements.push({ name, path, nextChildIndex: 0 });
3579
+ index = end + 1;
3580
+ }
3581
+ return { text: textParts.join(""), segments, markers };
3582
+ }
3583
+ function getSsmlSourceMap2(ssml) {
3584
+ parseSsml2(ssml);
3585
+ return collectSourceMap2(ssml);
3586
+ }
3705
3587
  var AZURE_VOICE_DEFINITIONS2 = [
3706
3588
  { name: "de-DE-ConradNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
3707
3589
  { name: "de-DE-KatjaNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
@@ -3829,34 +3711,51 @@ function createAzureUrlValidatorRunner2(validator, options = {}) {
3829
3711
  const inFlight = /* @__PURE__ */ new Map();
3830
3712
  const waiters = [];
3831
3713
  let active = 0;
3832
- const acquire = async () => {
3714
+ const configuredSignal = options.signal ?? new AbortController().signal;
3715
+ const acquire = async (signal) => {
3716
+ if (signal.aborted) throw new Error("URL validation was aborted.");
3833
3717
  if (active < concurrency) {
3834
3718
  active += 1;
3835
3719
  return;
3836
3720
  }
3837
- await new Promise((resolve) => waiters.push(resolve));
3721
+ await new Promise((resolve, reject) => {
3722
+ let waiter;
3723
+ const abortHandler = () => {
3724
+ const index = waiters.indexOf(waiter);
3725
+ if (index >= 0) waiters.splice(index, 1);
3726
+ signal.removeEventListener("abort", abortHandler);
3727
+ reject(new Error("URL validation was aborted."));
3728
+ };
3729
+ signal.addEventListener("abort", abortHandler, { once: true });
3730
+ waiter = () => {
3731
+ signal.removeEventListener("abort", abortHandler);
3732
+ resolve();
3733
+ };
3734
+ waiters.push(waiter);
3735
+ });
3838
3736
  active += 1;
3839
3737
  };
3840
3738
  const release = () => {
3841
3739
  active -= 1;
3842
3740
  waiters.shift()?.();
3843
3741
  };
3844
- const check = async (url, context) => {
3845
- if (options.signal?.aborted) throw new Error("URL validation was aborted.");
3846
- const cached = cache.get(url);
3742
+ const check = async (url, context, signal = configuredSignal) => {
3743
+ if (signal.aborted) throw new Error("URL validation was aborted.");
3744
+ const key = `${context.tag}:${context.attribute}:${url}`;
3745
+ const cached = cache.get(key);
3847
3746
  if (cached !== void 0) return cached;
3848
- const existing = inFlight.get(url);
3747
+ const existing = inFlight.get(key);
3849
3748
  if (existing) return existing;
3850
3749
  const promise = (async () => {
3851
- await acquire();
3750
+ await acquire(signal);
3852
3751
  try {
3853
- if (options.signal?.aborted) throw new Error("URL validation was aborted.");
3854
- const validation = Promise.resolve(validator(url, context));
3752
+ if (signal.aborted) throw new Error("URL validation was aborted.");
3753
+ const validation = Promise.resolve(validator(url, context, signal));
3855
3754
  let timer;
3856
3755
  let abortHandler;
3857
3756
  const cancellation = new Promise((_resolve, reject) => {
3858
3757
  abortHandler = () => reject(new Error("URL validation was aborted."));
3859
- options.signal?.addEventListener("abort", abortHandler, { once: true });
3758
+ signal.addEventListener("abort", abortHandler, { once: true });
3860
3759
  if (options.timeoutMs !== void 0 && options.timeoutMs > 0) {
3861
3760
  timer = setTimeout(
3862
3761
  () => reject(new Error(`URL validation timed out after ${options.timeoutMs} ms.`)),
@@ -3866,24 +3765,24 @@ function createAzureUrlValidatorRunner2(validator, options = {}) {
3866
3765
  });
3867
3766
  try {
3868
3767
  const result = await (timer || abortHandler ? Promise.race([validation, cancellation]) : validation);
3869
- cache.set(url, result);
3768
+ cache.set(key, result);
3870
3769
  return result;
3871
3770
  } finally {
3872
3771
  if (timer) clearTimeout(timer);
3873
- if (abortHandler) options.signal?.removeEventListener("abort", abortHandler);
3772
+ if (abortHandler) signal.removeEventListener("abort", abortHandler);
3874
3773
  }
3875
3774
  } finally {
3876
3775
  release();
3877
3776
  }
3878
3777
  })();
3879
- inFlight.set(url, promise);
3778
+ inFlight.set(key, promise);
3880
3779
  try {
3881
3780
  return await promise;
3882
3781
  } finally {
3883
- inFlight.delete(url);
3782
+ inFlight.delete(key);
3884
3783
  }
3885
3784
  };
3886
- return (url, context) => check(url, context);
3785
+ return (url, context, signal) => check(url, context, signal ?? configuredSignal);
3887
3786
  }
3888
3787
  var ALLOWED_BREAK_STRENGTHS2 = /* @__PURE__ */ new Set(["none", "x-weak", "weak", "medium", "strong", "x-strong"]);
3889
3788
  var ALLOWED_SAY_AS2 = /* @__PURE__ */ new Set([
@@ -3996,6 +3895,7 @@ function tokenizeElements2(source) {
3996
3895
  if (parent) parent.childElementCount += 1;
3997
3896
  const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
3998
3897
  const tokenName = nameMatch[1];
3898
+ const path = parent ? [...parent.path, `${tokenName}[${childElementIndex ?? 0}]`] : [tokenName];
3999
3899
  const tokenVoiceName = tokenName.toLowerCase() === "voice" ? attributes.get("name") : tokenName.toLowerCase() === "mstts:turn" ? attributes.get("voice") ?? parentVoiceName : parentVoiceName;
4000
3900
  tokens.push({
4001
3901
  attributes,
@@ -4006,12 +3906,14 @@ function tokenizeElements2(source) {
4006
3906
  parentName: parent?.name,
4007
3907
  parentVoiceName,
4008
3908
  selfClosing,
4009
- start
3909
+ start,
3910
+ path
4010
3911
  });
4011
3912
  if (!selfClosing) {
4012
3913
  openElements.push({
4013
3914
  childElementCount: 0,
4014
3915
  name: tokenName,
3916
+ path,
4015
3917
  voiceName: tokenVoiceName
4016
3918
  });
4017
3919
  }
@@ -4024,13 +3926,14 @@ function location2(source, offset) {
4024
3926
  const line = before.split("\n").length;
4025
3927
  return { line, column: before.length - (before.lastIndexOf("\n") + 1) + 1 };
4026
3928
  }
4027
- function addDiagnostic2(diagnostics, source, offset, message, severity = "error", code) {
3929
+ function addDiagnostic2(diagnostics, source, offset, message, severity = "error", code, metadata = {}) {
4028
3930
  diagnostics.push({
4029
3931
  ...location2(source, offset),
4030
3932
  message,
4031
3933
  severity,
4032
3934
  source: "ssml-static-validator",
4033
- ...code ? { code } : {}
3935
+ ...code ? { code } : {},
3936
+ ...metadata
4034
3937
  });
4035
3938
  }
4036
3939
  function isSupportedProsodyRate2(value) {
@@ -4331,279 +4234,1184 @@ function validateElement2(token, source, diagnostics, voiceName, options, voiceC
4331
4234
  if (!value || !isValidAzureAudioDuration2(value))
4332
4235
  addDiagnostic2(
4333
4236
  diagnostics,
4334
- source,
4237
+ source,
4238
+ token.start,
4239
+ '<mstts:audioduration> requires a positive duration such as "10s", "5000ms", or "00:00:10".'
4240
+ );
4241
+ if (!token.selfClosing)
4242
+ addDiagnostic2(diagnostics, source, token.start, "<mstts:audioduration> must be self-closing.");
4243
+ }
4244
+ if (name === "mstts:viseme") {
4245
+ const type = attr2(token, "type");
4246
+ if (!type || !ALLOWED_VISEME_TYPES2.has(type))
4247
+ addDiagnostic2(diagnostics, source, token.start, '<mstts:viseme> requires a supported "type" attribute.');
4248
+ }
4249
+ if (name === "audio") {
4250
+ validateAudioSource2(token, source, diagnostics, options, "audio");
4251
+ }
4252
+ if (name === "mstts:turn") {
4253
+ if (!attr2(token, "voice")?.trim() && !attr2(token, "speaker")?.trim())
4254
+ addDiagnostic2(
4255
+ diagnostics,
4256
+ source,
4257
+ token.start,
4258
+ '<mstts:turn> requires a non-empty "voice" or "speaker" attribute.'
4259
+ );
4260
+ if (token.parentName?.toLowerCase() !== "mstts:dialog")
4261
+ addDiagnostic2(diagnostics, source, token.start, "<mstts:turn> is only allowed directly inside <mstts:dialog>.");
4262
+ }
4263
+ if (name === "mstts:backgroundaudio") {
4264
+ validateAudioSource2(token, source, diagnostics, options, "mstts:backgroundaudio");
4265
+ const volume = attr2(token, "volume");
4266
+ if (volume !== void 0 && (!/^\d+(?:\.\d+)?$/.test(volume.trim()) || Number(volume) > 100))
4267
+ addDiagnostic2(diagnostics, source, token.start, `Unsupported <mstts:backgroundaudio volume> value "${volume}".`);
4268
+ for (const [attribute, value] of [
4269
+ ["fadein", attr2(token, "fadein")],
4270
+ ["fadeout", attr2(token, "fadeout")]
4271
+ ]) {
4272
+ if (value !== void 0 && !isValidAzureBackgroundAudioDuration2(value))
4273
+ addDiagnostic2(
4274
+ diagnostics,
4275
+ source,
4276
+ token.start,
4277
+ `<mstts:backgroundaudio ${attribute}> must be between 0 and 10000 milliseconds, for example "500ms" or "10s".`
4278
+ );
4279
+ }
4280
+ if (token.parentName?.toLowerCase() !== "speak" || token.childElementIndex !== 0)
4281
+ addDiagnostic2(
4282
+ diagnostics,
4283
+ source,
4284
+ token.start,
4285
+ "<mstts:backgroundaudio> must be the first element directly under <speak>."
4286
+ );
4287
+ if (!token.selfClosing)
4288
+ addDiagnostic2(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
4289
+ }
4290
+ }
4291
+ function validateAzureSsmlStatic2(ssml, options = {}) {
4292
+ const diagnostics = [];
4293
+ if (typeof ssml !== "string") {
4294
+ return [
4295
+ {
4296
+ line: 1,
4297
+ column: 1,
4298
+ message: "SSML input must be a string",
4299
+ severity: "error",
4300
+ source: "ssml-static-validator"
4301
+ }
4302
+ ];
4303
+ }
4304
+ const maxLength = options.maxLength ?? 1e4;
4305
+ if (ssml.length > maxLength)
4306
+ addDiagnostic2(diagnostics, ssml, maxLength, `SSML exceeds the maximum length of ${maxLength} characters.`);
4307
+ if (options.maxXmlDepth !== void 0 && (!Number.isInteger(options.maxXmlDepth) || options.maxXmlDepth <= 0)) {
4308
+ addDiagnostic2(diagnostics, ssml, 0, "maxXmlDepth must be a positive integer.");
4309
+ }
4310
+ try {
4311
+ parseSsml2(ssml);
4312
+ } catch (error) {
4313
+ const message = error instanceof Error ? error.message.replace(/ at position \d+$/, "") : String(error);
4314
+ const match = / at position (\d+)$/.exec(error instanceof Error ? error.message : "");
4315
+ addDiagnostic2(diagnostics, ssml, match ? Number(match[1]) : 0, message);
4316
+ return diagnostics;
4317
+ }
4318
+ const tokens = tokenizeElements2(ssml);
4319
+ if (options.maxXmlDepth !== void 0) {
4320
+ for (const token of tokens) {
4321
+ if (token.depth > options.maxXmlDepth) {
4322
+ addDiagnostic2(
4323
+ diagnostics,
4324
+ ssml,
4325
+ token.start,
4326
+ `XML nesting depth ${token.depth} exceeds the configured maximum of ${options.maxXmlDepth}.`
4327
+ );
4328
+ }
4329
+ }
4330
+ }
4331
+ const speak = tokens.find((token) => token.name.toLowerCase() === "speak");
4332
+ const voices = tokens.filter((token) => token.name.toLowerCase() === "voice");
4333
+ const backgroundAudioTokens = tokens.filter((token) => token.name.toLowerCase() === "mstts:backgroundaudio");
4334
+ for (const [index, token] of backgroundAudioTokens.entries()) {
4335
+ if (index > 0)
4336
+ addDiagnostic2(
4337
+ diagnostics,
4338
+ ssml,
4339
+ token.start,
4340
+ "An SSML document can contain at most one <mstts:backgroundaudio> element."
4341
+ );
4342
+ }
4343
+ if (!speak || voices.length === 0)
4344
+ addDiagnostic2(
4345
+ diagnostics,
4346
+ ssml,
4347
+ speak?.start ?? 0,
4348
+ "Azure SSML requires at least one <voice> element under <speak>."
4349
+ );
4350
+ const voiceName = voices[0] ? attr2(voices[0], "name") : void 0;
4351
+ const voiceCatalog = normalizeVoiceCatalog2(options);
4352
+ const normalizeLanguage = createLanguageNormalizer2(options);
4353
+ const policySeverity = diagnosticSeverity2(options.unknownVoicePolicy ?? "warn");
4354
+ const voicesToValidate = options.validateNestedVoices === false ? voices.slice(0, 1) : voices;
4355
+ for (const token of voicesToValidate) {
4356
+ const name = attr2(token, "name")?.trim();
4357
+ const language = attr2(token, "xml:lang")?.trim() || (speak ? attr2(speak, "xml:lang")?.trim() : void 0);
4358
+ const definition = name ? voiceCatalog.get(name.toLowerCase()) : void 0;
4359
+ if (name && definition?.status === "preview")
4360
+ addDiagnostic2(
4361
+ diagnostics,
4362
+ ssml,
4363
+ token.start,
4364
+ `Voice "${name}" is an Azure Speech preview voice and may change or require preview access.`,
4365
+ "warning",
4366
+ "azure-preview-voice"
4367
+ );
4368
+ if (name && definition?.status === "deprecated")
4369
+ addDiagnostic2(
4370
+ diagnostics,
4371
+ ssml,
4372
+ token.start,
4373
+ `Voice "${name}" is deprecated by Azure Speech; migrate to a supported voice.`,
4374
+ "info",
4375
+ "azure-deprecated-voice"
4376
+ );
4377
+ if (name && !definition && policySeverity)
4378
+ addDiagnostic2(
4379
+ diagnostics,
4380
+ ssml,
4381
+ token.start,
4382
+ `Unknown voice "${name}" is not registered in the voice catalog.`,
4383
+ policySeverity,
4384
+ "azure-unknown-voice"
4385
+ );
4386
+ if (name && language && definitionMatchesLanguage2(definition, name, language, normalizeLanguage) === false)
4387
+ addDiagnostic2(
4388
+ diagnostics,
4389
+ ssml,
4390
+ token.start,
4391
+ `Voice "${name}" does not match language "${language}"; the voice name prefix indicates a different language or region.`,
4392
+ "warning",
4393
+ "azure-locale-mismatch"
4394
+ );
4395
+ }
4396
+ for (const token of tokens) {
4397
+ const tokenName = token.name.toLowerCase();
4398
+ const tokenVoiceName = tokenName === "voice" ? attr2(token, "name")?.trim() : tokenName === "mstts:turn" ? attr2(token, "voice")?.trim() || token.parentVoiceName : options.validateNestedVoices === false ? voiceName : token.parentVoiceName;
4399
+ const tokenDiagnosticStart = diagnostics.length;
4400
+ validateElement2(token, ssml, diagnostics, tokenVoiceName, options, voiceCatalog);
4401
+ const definition = tokenVoiceName ? voiceCatalog.get(tokenVoiceName.toLowerCase()) : void 0;
4402
+ validateVoiceFeatureMatrix2(token, ssml, diagnostics, tokenVoiceName, definition);
4403
+ annotateTokenDiagnostics2(diagnostics, tokenDiagnosticStart, token, options, tokenVoiceName);
4404
+ if (tokenName === "voice" && options.model && definition?.models && !definition.models.some((model) => model.toLowerCase() === options.model?.toLowerCase())) {
4405
+ addDiagnostic2(
4406
+ diagnostics,
4407
+ ssml,
4335
4408
  token.start,
4336
- '<mstts:audioduration> requires a positive duration such as "10s", "5000ms", or "00:00:10".'
4409
+ `Voice "${tokenVoiceName}" does not support model "${options.model}" according to the configured feature matrix.`,
4410
+ "error",
4411
+ "azure-unsupported-model-for-voice"
4337
4412
  );
4338
- if (!token.selfClosing)
4339
- addDiagnostic2(diagnostics, source, token.start, "<mstts:audioduration> must be self-closing.");
4340
- }
4341
- if (name === "mstts:viseme") {
4342
- const type = attr2(token, "type");
4343
- if (!type || !ALLOWED_VISEME_TYPES2.has(type))
4344
- addDiagnostic2(diagnostics, source, token.start, '<mstts:viseme> requires a supported "type" attribute.');
4413
+ }
4345
4414
  }
4346
- if (name === "audio") {
4347
- validateAudioSource2(token, source, diagnostics, options, "audio");
4415
+ return diagnostics;
4416
+ }
4417
+ function urlAttributes2(token) {
4418
+ const tag = canonicalTagName2(token.name);
4419
+ const attributes = tag === "audio" || tag === "mstts:backgroundaudio" ? ["src"] : tag === "lexicon" ? ["uri"] : tag === "mstts:voiceconversion" ? ["url"] : [];
4420
+ return attributes.flatMap((attribute) => {
4421
+ const value = attr2(token, attribute);
4422
+ return value === void 0 ? [] : [{ attribute, value }];
4423
+ });
4424
+ }
4425
+ function annotateTokenDiagnostics2(diagnostics, startIndex, token, options, voiceName) {
4426
+ const attributes = [...token.attributes.keys()];
4427
+ for (const diagnostic of diagnostics.slice(startIndex)) {
4428
+ const attributeName = attributes.find(
4429
+ (attribute) => new RegExp(`(?:<[^> ]+\\s+|")${attribute}(?:"|>|\\s)`, "i").test(diagnostic.message)
4430
+ );
4431
+ const nodePath = options.sourceNodePath ? [...options.sourceNodePath] : [...token.path];
4432
+ Object.assign(diagnostic, {
4433
+ range: { start: token.start, end: token.end + 1 },
4434
+ tagName: token.name,
4435
+ ...attributeName ? { attributeName } : {},
4436
+ ...voiceName ? { voiceName } : {},
4437
+ ...options.chunkIndex !== void 0 ? { chunkIndex: options.chunkIndex } : {},
4438
+ nodePath,
4439
+ targetNodePath: [...token.path]
4440
+ });
4348
4441
  }
4349
- if (name === "mstts:turn") {
4350
- if (!attr2(token, "voice")?.trim() && !attr2(token, "speaker")?.trim())
4351
- addDiagnostic2(
4352
- diagnostics,
4353
- source,
4354
- token.start,
4355
- '<mstts:turn> requires a non-empty "voice" or "speaker" attribute.'
4356
- );
4357
- if (token.parentName?.toLowerCase() !== "mstts:dialog")
4358
- addDiagnostic2(diagnostics, source, token.start, "<mstts:turn> is only allowed directly inside <mstts:dialog>.");
4442
+ }
4443
+ function validateAzureSsml2(ssml, options = {}) {
4444
+ const diagnostics = validateAzureSsmlStatic2(ssml, options);
4445
+ const validator = options.urlValidator ?? options.customUrlValidator;
4446
+ if (!validator || typeof ssml !== "string") return diagnostics;
4447
+ const runnerOptions = options.urlValidation ?? {};
4448
+ const boundedValidator = options.urlValidatorRunner ?? createAzureUrlValidatorRunner2(validator, {
4449
+ ...runnerOptions,
4450
+ ...options.urlValidatorConcurrency !== void 0 ? { concurrency: options.urlValidatorConcurrency } : {},
4451
+ ...options.urlValidatorTimeoutMs !== void 0 ? { timeoutMs: options.urlValidatorTimeoutMs } : {},
4452
+ ...options.urlValidatorSignal ? { signal: options.urlValidatorSignal } : {},
4453
+ ...options.urlValidatorCache ? { cache: options.urlValidatorCache } : {}
4454
+ });
4455
+ const validationSignal = options.urlValidatorSignal ?? options.urlValidation?.signal ?? new AbortController().signal;
4456
+ let tokens;
4457
+ try {
4458
+ tokens = tokenizeElements2(ssml);
4459
+ } catch {
4460
+ return diagnostics;
4359
4461
  }
4360
- if (name === "mstts:backgroundaudio") {
4361
- validateAudioSource2(token, source, diagnostics, options, "mstts:backgroundaudio");
4362
- const volume = attr2(token, "volume");
4363
- if (volume !== void 0 && (!/^\d+(?:\.\d+)?$/.test(volume.trim()) || Number(volume) > 100))
4364
- addDiagnostic2(diagnostics, source, token.start, `Unsupported <mstts:backgroundaudio volume> value "${volume}".`);
4365
- for (const [attribute, value] of [
4366
- ["fadein", attr2(token, "fadein")],
4367
- ["fadeout", attr2(token, "fadeout")]
4368
- ]) {
4369
- if (value !== void 0 && !isValidAzureBackgroundAudioDuration2(value))
4462
+ const checks = tokens.flatMap(
4463
+ (token) => urlAttributes2(token).map(async ({ attribute, value }) => {
4464
+ try {
4465
+ const result = await boundedValidator(
4466
+ value,
4467
+ { tag: token.name, attribute, ...options.sourceNodePath ? { sourceNodePath: options.sourceNodePath } : {} },
4468
+ validationSignal
4469
+ );
4470
+ const valid = typeof result === "boolean" ? result : result.valid;
4471
+ if (!valid) {
4472
+ const reason = typeof result === "boolean" ? void 0 : result.reason;
4473
+ const diagnosticStart = diagnostics.length;
4474
+ addDiagnostic2(
4475
+ diagnostics,
4476
+ ssml,
4477
+ token.start,
4478
+ `<${token.name} ${attribute}> was rejected by the custom URL validator${reason ? `: ${reason}` : "."}`
4479
+ );
4480
+ annotateTokenDiagnostics2(diagnostics, diagnosticStart, token, options, token.parentVoiceName);
4481
+ }
4482
+ } catch (error) {
4483
+ const reason = error instanceof Error ? error.message : String(error);
4484
+ const diagnosticStart = diagnostics.length;
4370
4485
  addDiagnostic2(
4371
4486
  diagnostics,
4372
- source,
4487
+ ssml,
4373
4488
  token.start,
4374
- `<mstts:backgroundaudio ${attribute}> must be between 0 and 10000 milliseconds, for example "500ms" or "10s".`
4489
+ `<${token.name} ${attribute}> could not be validated by the custom URL validator: ${reason}`
4375
4490
  );
4491
+ annotateTokenDiagnostics2(diagnostics, diagnosticStart, token, options, token.parentVoiceName);
4492
+ }
4493
+ })
4494
+ );
4495
+ return Promise.all(checks).then(() => diagnostics);
4496
+ }
4497
+ var AZURE_VOICE_CATALOG_METADATA2 = {
4498
+ apiVersion: "2025-10-01",
4499
+ generatedAt: "2026-08-28T00:00:00.000Z",
4500
+ regions: [],
4501
+ voiceCount: AZURE_VOICE_DEFINITIONS2.length
4502
+ };
4503
+
4504
+ // packages/azure-tts-client/src/outputFormats.ts
4505
+ var SpeechSDK = __toESM(require("microsoft-cognitiveservices-speech-sdk"));
4506
+ var DEFAULT_OUTPUT_FORMAT = "audio-16khz-128kbitrate-mono-mp3";
4507
+ var OUTPUT_FORMATS = {
4508
+ "raw-8khz-8bit-mono-mulaw": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz8BitMonoMULaw,
4509
+ "riff-16khz-16kbps-mono-siren": SpeechSDK.SpeechSynthesisOutputFormat.Riff16Khz16KbpsMonoSiren,
4510
+ "audio-16khz-16kbps-mono-siren": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz16KbpsMonoSiren,
4511
+ "audio-16khz-32kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3,
4512
+ "audio-16khz-128kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz128KBitRateMonoMp3,
4513
+ "audio-16khz-64kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz64KBitRateMonoMp3,
4514
+ "audio-24khz-48kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz48KBitRateMonoMp3,
4515
+ "audio-24khz-96kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz96KBitRateMonoMp3,
4516
+ "audio-24khz-160kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz160KBitRateMonoMp3,
4517
+ "raw-16khz-16bit-mono-truesilk": SpeechSDK.SpeechSynthesisOutputFormat.Raw16Khz16BitMonoTrueSilk,
4518
+ "riff-16khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Riff16Khz16BitMonoPcm,
4519
+ "riff-8khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz16BitMonoPcm,
4520
+ "riff-24khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm,
4521
+ "riff-8khz-8bit-mono-mulaw": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz8BitMonoMULaw,
4522
+ "raw-16khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Raw16Khz16BitMonoPcm,
4523
+ "raw-24khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Raw24Khz16BitMonoPcm,
4524
+ "raw-8khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz16BitMonoPcm,
4525
+ "ogg-16khz-16bit-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Ogg16Khz16BitMonoOpus,
4526
+ "ogg-24khz-16bit-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Ogg24Khz16BitMonoOpus,
4527
+ "raw-48khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Raw48Khz16BitMonoPcm,
4528
+ "riff-48khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Riff48Khz16BitMonoPcm,
4529
+ "audio-48khz-96kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio48Khz96KBitRateMonoMp3,
4530
+ "audio-48khz-192kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio48Khz192KBitRateMonoMp3,
4531
+ "ogg-48khz-16bit-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Ogg48Khz16BitMonoOpus,
4532
+ "webm-16khz-16bit-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Webm16Khz16BitMonoOpus,
4533
+ "webm-24khz-16bit-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Webm24Khz16BitMonoOpus,
4534
+ "webm-24khz-16bit-24kbps-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Webm24Khz16Bit24KbpsMonoOpus,
4535
+ "raw-24khz-16bit-mono-truesilk": SpeechSDK.SpeechSynthesisOutputFormat.Raw24Khz16BitMonoTrueSilk,
4536
+ "raw-8khz-8bit-mono-alaw": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz8BitMonoALaw,
4537
+ "riff-8khz-8bit-mono-alaw": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz8BitMonoALaw,
4538
+ "audio-16khz-16bit-32kbps-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz16Bit32KbpsMonoOpus,
4539
+ "audio-24khz-16bit-48kbps-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz16Bit48KbpsMonoOpus,
4540
+ "audio-24khz-16bit-24kbps-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz16Bit24KbpsMonoOpus,
4541
+ "raw-22050hz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Raw22050Hz16BitMonoPcm,
4542
+ "riff-22050hz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Riff22050Hz16BitMonoPcm,
4543
+ "raw-44100hz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Raw44100Hz16BitMonoPcm,
4544
+ "riff-44100hz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Riff44100Hz16BitMonoPcm,
4545
+ "amr-wb-16000hz": SpeechSDK.SpeechSynthesisOutputFormat.AmrWb16000Hz,
4546
+ "g722-16khz-64kbps": SpeechSDK.SpeechSynthesisOutputFormat.G72216Khz64Kbps
4547
+ };
4548
+ function resolveMimeType(outputFormat) {
4549
+ if (/(?:wav|wave|riff)/i.test(outputFormat)) return "audio/wav";
4550
+ if (/(?:mp3|mpeg)/i.test(outputFormat)) return "audio/mpeg";
4551
+ if (/ogg/i.test(outputFormat)) return "audio/ogg";
4552
+ if (/webm/i.test(outputFormat)) return "audio/webm";
4553
+ if (/raw/i.test(outputFormat)) return "audio/L16";
4554
+ return "application/octet-stream";
4555
+ }
4556
+ function resolveOutputFormat(outputFormat) {
4557
+ const resolvedFormat = OUTPUT_FORMATS[outputFormat];
4558
+ if (resolvedFormat === void 0) {
4559
+ throw new Error(`Unsupported Azure Speech output format: ${outputFormat}`);
4560
+ }
4561
+ return resolvedFormat;
4562
+ }
4563
+
4564
+ // packages/azure-tts-client/src/speechConfig.ts
4565
+ var import_microsoft_cognitiveservices_speech_sdk = require("microsoft-cognitiveservices-speech-sdk");
4566
+ function resolveEndpoint(config) {
4567
+ const endpoint = config.endpoint?.trim() || "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
4568
+ return endpoint.replace(/\{region\}/g, encodeURIComponent(config.region));
4569
+ }
4570
+ function createSpeechConfig(config) {
4571
+ const { outputFormat = DEFAULT_OUTPUT_FORMAT, subscriptionKey } = config;
4572
+ const endpoint = new URL(resolveEndpoint(config));
4573
+ const speechConfig = import_microsoft_cognitiveservices_speech_sdk.SpeechConfig.fromEndpoint(endpoint, subscriptionKey);
4574
+ speechConfig.speechSynthesisOutputFormat = resolveOutputFormat(outputFormat);
4575
+ return speechConfig;
4576
+ }
4577
+
4578
+ // packages/azure-tts-client/src/synthesis.ts
4579
+ function ascii(bytes, offset, value) {
4580
+ return [...value].every((character, index) => bytes[offset + index] === character.charCodeAt(0));
4581
+ }
4582
+ function readUint32(bytes, offset) {
4583
+ return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(offset, true);
4584
+ }
4585
+ function parseWav(buffer) {
4586
+ const bytes = new Uint8Array(buffer);
4587
+ if (bytes.byteLength < 12 || !ascii(bytes, 0, "RIFF") || !ascii(bytes, 8, "WAVE")) {
4588
+ throw new Error("Invalid WAV/RIFF audio buffer.");
4589
+ }
4590
+ const chunks = [];
4591
+ const dataParts = [];
4592
+ let format;
4593
+ let offset = 12;
4594
+ while (offset < bytes.byteLength) {
4595
+ if (offset + 8 > bytes.byteLength) throw new Error("Invalid WAV chunk header.");
4596
+ const id = String.fromCharCode(...bytes.slice(offset, offset + 4));
4597
+ const size = readUint32(bytes, offset + 4);
4598
+ const dataStart = offset + 8;
4599
+ const dataEnd = dataStart + size;
4600
+ if (dataEnd > bytes.byteLength) throw new Error(`WAV chunk "${id}" exceeds the audio buffer.`);
4601
+ const data2 = bytes.slice(dataStart, dataEnd);
4602
+ chunks.push({ id, data: data2 });
4603
+ if (id === "fmt ") format ?? (format = data2);
4604
+ if (id === "data") dataParts.push(data2);
4605
+ offset = dataEnd + (size & 1);
4606
+ if (offset > bytes.byteLength) throw new Error("Invalid WAV chunk padding.");
4607
+ }
4608
+ if (!format || dataParts.length === 0) throw new Error("WAV audio must contain fmt and data chunks.");
4609
+ const dataLength = dataParts.reduce((total, part) => total + part.byteLength, 0);
4610
+ const data = new Uint8Array(dataLength);
4611
+ let dataOffset = 0;
4612
+ for (const part of dataParts) {
4613
+ data.set(part, dataOffset);
4614
+ dataOffset += part.byteLength;
4615
+ }
4616
+ return { chunks, data, format };
4617
+ }
4618
+ function formatNumber(format, pattern, fallback) {
4619
+ const match = pattern.exec(format);
4620
+ return match?.[1] ? Number(match[1]) : fallback;
4621
+ }
4622
+ function formatChannels(format, fallback) {
4623
+ if (/stereo|2ch|dual/i.test(format)) return 2;
4624
+ if (/mono|1ch/i.test(format)) return 1;
4625
+ return fallback;
4626
+ }
4627
+ function formatAudioSpecification(format) {
4628
+ const sampleRate = formatNumber(format, /(\d+)(?:khz|kHz|hz|Hz)/, 0);
4629
+ const channels = formatChannels(format, 0);
4630
+ const bitrateMatch = /(\d+)(?:kbitrate|kbps|kbits?)/i.exec(format);
4631
+ const bitrate = bitrateMatch?.[1] ? Number(bitrateMatch[1]) * 1e3 : void 0;
4632
+ 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";
4633
+ return {
4634
+ format,
4635
+ mimeType: resolveMimeType(format),
4636
+ codec,
4637
+ sampleRate,
4638
+ channels,
4639
+ ...bitrate ? { bitrate } : {},
4640
+ isCompressed: codec === "mp3" || codec === "opus" || codec === "silk"
4641
+ };
4642
+ }
4643
+ function parseMp3Specification(buffer, format) {
4644
+ const bytes = stripMp3Tags(buffer);
4645
+ const bitrates = [
4646
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
4647
+ [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0],
4648
+ [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0]
4649
+ ];
4650
+ const sampleRates = [
4651
+ [44100, 48e3, 32e3],
4652
+ [22050, 24e3, 16e3],
4653
+ [11025, 12e3, 8e3]
4654
+ ];
4655
+ for (let index = 0; index + 4 <= bytes.length; index += 1) {
4656
+ if (bytes[index] !== 255 || (bytes[index + 1] ?? 0) < 224) continue;
4657
+ const header = bytes[index + 1] ?? 0;
4658
+ const versionBits = header >> 3 & 3;
4659
+ const layer = header >> 1 & 3;
4660
+ const bitrateIndex = (bytes[index + 2] ?? 0) >> 4;
4661
+ const sampleIndex = (bytes[index + 2] ?? 0) >> 2 & 3;
4662
+ if (versionBits === 1 || layer !== 1 || bitrateIndex === 0 || bitrateIndex === 15 || sampleIndex === 3) continue;
4663
+ const versionIndex = versionBits === 3 ? 0 : versionBits === 2 ? 1 : 2;
4664
+ const bitrateTable = versionBits === 3 ? bitrates[1] : bitrates[2];
4665
+ const sampleRate = sampleRates[versionIndex]?.[sampleIndex] ?? 0;
4666
+ const bitrateKbps = bitrateTable?.[bitrateIndex] ?? 0;
4667
+ if (!sampleRate || !bitrateKbps) continue;
4668
+ return {
4669
+ format,
4670
+ mimeType: "audio/mpeg",
4671
+ codec: "mp3",
4672
+ sampleRate,
4673
+ channels: (bytes[index + 3] ?? 0) >> 6 === 3 ? 1 : 2,
4674
+ bitrate: bitrateKbps * 1e3,
4675
+ isCompressed: true
4676
+ };
4677
+ }
4678
+ return void 0;
4679
+ }
4680
+ function inspectAudioSpecification(buffer, format) {
4681
+ if (isWavFormat(format) || ascii(new Uint8Array(buffer), 0, "RIFF")) {
4682
+ const parsed = parseWav(buffer);
4683
+ if (parsed.format.byteLength < 16) throw new Error("Invalid WAV fmt chunk.");
4684
+ const view = new DataView(parsed.format.buffer, parsed.format.byteOffset, parsed.format.byteLength);
4685
+ const sampleRate = view.getUint32(4, true);
4686
+ const channels = view.getUint16(2, true);
4687
+ const bitsPerSample = parsed.format.byteLength >= 16 ? view.getUint16(14, true) : 0;
4688
+ const formatCode = view.getUint16(0, true);
4689
+ return {
4690
+ format,
4691
+ mimeType: "audio/wav",
4692
+ codec: formatCode === 1 ? "pcm" : "unknown",
4693
+ sampleRate,
4694
+ channels,
4695
+ ...sampleRate && channels && bitsPerSample ? { bitrate: sampleRate * channels * bitsPerSample } : {},
4696
+ isCompressed: formatCode !== 1
4697
+ };
4698
+ }
4699
+ if (isMp3Format(format)) return parseMp3Specification(buffer, format) ?? formatAudioSpecification(format);
4700
+ return formatAudioSpecification(format);
4701
+ }
4702
+ function validateAudioSpecifications(specs) {
4703
+ const first = specs[0];
4704
+ if (!first) return;
4705
+ const mismatch = specs.find(
4706
+ (spec) => spec.sampleRate !== first.sampleRate || spec.channels !== first.channels || first.bitrate !== void 0 && spec.bitrate !== void 0 && spec.bitrate !== first.bitrate
4707
+ );
4708
+ if (mismatch)
4709
+ throw new AudioFormatMismatchError(
4710
+ `Audio chunks have incompatible specifications: ${first.sampleRate}Hz/${first.channels}ch versus ${mismatch.sampleRate}Hz/${mismatch.channels}ch.`,
4711
+ specs
4712
+ );
4713
+ }
4714
+ function isAudioFormatMismatch(error) {
4715
+ return error instanceof AudioFormatMismatchError || error !== null && typeof error === "object" && "kind" in error && error.kind === "audio-format-mismatch";
4716
+ }
4717
+ function writeUint32(target, offset, value) {
4718
+ new DataView(target.buffer).setUint32(offset, value, true);
4719
+ }
4720
+ function writeChunk(target, offset, id, data) {
4721
+ for (let index = 0; index < 4; index += 1) target[offset + index] = id.charCodeAt(index) ?? 0;
4722
+ writeUint32(target, offset + 4, data.byteLength);
4723
+ target.set(data, offset + 8);
4724
+ const end = offset + 8 + data.byteLength;
4725
+ if (data.byteLength & 1) target[end] = 0;
4726
+ return end + (data.byteLength & 1);
4727
+ }
4728
+ function mergeWavBuffers(buffers) {
4729
+ if (buffers.length === 0) return new ArrayBuffer(0);
4730
+ const parsed = buffers.map(parseWav);
4731
+ const first = parsed[0];
4732
+ if (!first) throw new Error("At least one WAV buffer is required.");
4733
+ if (parsed.some(
4734
+ (item) => item.format.length !== first.format.length || item.format.some((value, i) => value !== first.format[i])
4735
+ ))
4736
+ throw new Error("WAV buffers have incompatible fmt chunks.");
4737
+ const dataLength = parsed.reduce((total, item) => total + item.data.byteLength, 0);
4738
+ const nonDataLength = first.chunks.reduce(
4739
+ (total, chunk) => chunk.id === "data" ? total : total + 8 + chunk.data.byteLength + (chunk.data.byteLength & 1),
4740
+ 0
4741
+ );
4742
+ const outputLength = 12 + nonDataLength + 8 + dataLength + (dataLength & 1);
4743
+ if (outputLength - 8 > 4294967295) throw new RangeError("Merged WAV exceeds the RIFF format size limit.");
4744
+ const output = new Uint8Array(outputLength);
4745
+ output.set(Uint8Array.from([82, 73, 70, 70]), 0);
4746
+ writeUint32(output, 4, outputLength - 8);
4747
+ output.set(Uint8Array.from([87, 65, 86, 69]), 8);
4748
+ let outputOffset = 12;
4749
+ let dataWritten = false;
4750
+ for (const chunk of first.chunks) {
4751
+ if (chunk.id === "data") {
4752
+ if (dataWritten) continue;
4753
+ const data = new Uint8Array(dataLength);
4754
+ let dataOffset = 0;
4755
+ for (const item of parsed) {
4756
+ data.set(item.data, dataOffset);
4757
+ dataOffset += item.data.byteLength;
4758
+ }
4759
+ outputOffset = writeChunk(output, outputOffset, "data", data);
4760
+ dataWritten = true;
4761
+ } else {
4762
+ outputOffset = writeChunk(output, outputOffset, chunk.id, chunk.data);
4376
4763
  }
4377
- if (token.parentName?.toLowerCase() !== "speak" || token.childElementIndex !== 0)
4378
- addDiagnostic2(
4379
- diagnostics,
4380
- source,
4381
- token.start,
4382
- "<mstts:backgroundaudio> must be the first element directly under <speak>."
4383
- );
4384
- if (!token.selfClosing)
4385
- addDiagnostic2(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
4386
4764
  }
4765
+ if (!dataWritten) throw new Error("WAV audio must contain a data chunk.");
4766
+ return output.buffer;
4387
4767
  }
4388
- function validateAzureSsmlStatic2(ssml, options = {}) {
4389
- const diagnostics = [];
4390
- if (typeof ssml !== "string") {
4391
- return [
4392
- {
4393
- line: 1,
4394
- column: 1,
4395
- message: "SSML input must be a string",
4396
- severity: "error",
4397
- source: "ssml-static-validator"
4768
+ function skipId3v2(bytes) {
4769
+ if (!ascii(bytes, 0, "ID3") || bytes.byteLength < 10) return 0;
4770
+ const size = [bytes[6], bytes[7], bytes[8], bytes[9]].reduce((total, value) => total << 7 | value & 127, 0);
4771
+ const hasFooter = (bytes[5] & 16) !== 0;
4772
+ return Math.min(bytes.byteLength, 10 + size + (hasFooter ? 10 : 0));
4773
+ }
4774
+ function stripMp3Tags(buffer) {
4775
+ const bytes = new Uint8Array(buffer);
4776
+ const start = skipId3v2(bytes);
4777
+ const end = bytes.byteLength >= 128 && ascii(bytes, bytes.byteLength - 128, "TAG") ? bytes.byteLength - 128 : bytes.byteLength;
4778
+ return bytes.slice(Math.min(start, end), end);
4779
+ }
4780
+ function isMp3Format(format) {
4781
+ return /(?:mp3|mpeg)/i.test(format);
4782
+ }
4783
+ function isWavFormat(format) {
4784
+ return /(?:wav|wave|riff)/i.test(format);
4785
+ }
4786
+ function isRawFormat(format) {
4787
+ return /^raw(?:-|$)/i.test(format);
4788
+ }
4789
+ function resolveMergeAudioFormat(format) {
4790
+ if (isWavFormat(format)) return "wav";
4791
+ if (isMp3Format(format)) return "mp3";
4792
+ if (isRawFormat(format)) return "raw";
4793
+ return void 0;
4794
+ }
4795
+ function canMergeAudioFormat(format) {
4796
+ return resolveMergeAudioFormat(format) !== void 0;
4797
+ }
4798
+ function mergeAudioBuffers(buffers, options) {
4799
+ const format = typeof options === "string" ? options : options?.format;
4800
+ if (!format) throw new UnsupportedMergeFormatError("");
4801
+ try {
4802
+ validateAudioSpecifications(buffers.map((buffer) => inspectAudioSpecification(buffer, format)));
4803
+ if (isWavFormat(format)) return mergeWavBuffers(buffers);
4804
+ if (isMp3Format(format)) {
4805
+ const parts = buffers.map(stripMp3Tags);
4806
+ const output = new Uint8Array(parts.reduce((total, part) => total + part.byteLength, 0));
4807
+ let offset = 0;
4808
+ for (const part of parts) {
4809
+ output.set(part, offset);
4810
+ offset += part.byteLength;
4398
4811
  }
4399
- ];
4812
+ return output.buffer;
4813
+ }
4814
+ if (isRawFormat(format)) {
4815
+ const output = new Uint8Array(buffers.reduce((total, buffer) => total + buffer.byteLength, 0));
4816
+ let offset = 0;
4817
+ for (const buffer of buffers) {
4818
+ output.set(new Uint8Array(buffer), offset);
4819
+ offset += buffer.byteLength;
4820
+ }
4821
+ return output.buffer;
4822
+ }
4823
+ throw new UnsupportedMergeFormatError(format);
4824
+ } catch (error) {
4825
+ if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
4826
+ throw error;
4827
+ throw new MergeError(`Audio buffers could not be merged for format "${format}".`, error);
4400
4828
  }
4401
- const maxLength = options.maxLength ?? 1e4;
4402
- if (ssml.length > maxLength)
4403
- addDiagnostic2(diagnostics, ssml, maxLength, `SSML exceeds the maximum length of ${maxLength} characters.`);
4404
- if (options.maxXmlDepth !== void 0 && (!Number.isInteger(options.maxXmlDepth) || options.maxXmlDepth <= 0)) {
4405
- addDiagnostic2(diagnostics, ssml, 0, "maxXmlDepth must be a positive integer.");
4829
+ }
4830
+ function closeSpeechResources(speechConfig, synthesizer) {
4831
+ try {
4832
+ synthesizer.close();
4833
+ } catch {
4406
4834
  }
4407
4835
  try {
4408
- parseSsml2(ssml);
4409
- } catch (error) {
4410
- const message = error instanceof Error ? error.message.replace(/ at position \d+$/, "") : String(error);
4411
- const match = / at position (\d+)$/.exec(error instanceof Error ? error.message : "");
4412
- addDiagnostic2(diagnostics, ssml, match ? Number(match[1]) : 0, message);
4413
- return diagnostics;
4836
+ speechConfig.close();
4837
+ } catch {
4414
4838
  }
4415
- const tokens = tokenizeElements2(ssml);
4416
- if (options.maxXmlDepth !== void 0) {
4417
- for (const token of tokens) {
4418
- if (token.depth > options.maxXmlDepth) {
4419
- addDiagnostic2(
4420
- diagnostics,
4421
- ssml,
4422
- token.start,
4423
- `XML nesting depth ${token.depth} exceeds the configured maximum of ${options.maxXmlDepth}.`
4424
- );
4839
+ }
4840
+ var ticksToMilliseconds = (ticks) => Math.max(0, ticks) / 1e4;
4841
+ async function synthesizeSsml(ssml, config) {
4842
+ if (config.signal?.aborted) {
4843
+ throw new SynthesisCancelledError();
4844
+ }
4845
+ const speechConfig = createSpeechConfig(config);
4846
+ const synthesizer = new SpeechSDK2.SpeechSynthesizer(speechConfig, null);
4847
+ return await new Promise((resolve, reject) => {
4848
+ let resourcesClosed = false;
4849
+ let settled = false;
4850
+ let timeout;
4851
+ let abortHandler;
4852
+ const cleanup = () => {
4853
+ if (timeout) clearTimeout(timeout);
4854
+ if (abortHandler) config.signal?.removeEventListener("abort", abortHandler);
4855
+ };
4856
+ const closeResources = () => {
4857
+ if (resourcesClosed) return;
4858
+ resourcesClosed = true;
4859
+ closeSpeechResources(speechConfig, synthesizer);
4860
+ };
4861
+ const rejectWithError = (error) => {
4862
+ if (settled) return;
4863
+ settled = true;
4864
+ cleanup();
4865
+ closeResources();
4866
+ reject(toSynthesisError(error));
4867
+ };
4868
+ const boundaries = [];
4869
+ const visemes = [];
4870
+ const bookmarks = [];
4871
+ let sourceEventCursor = 0;
4872
+ let generatedSourceMap;
4873
+ if (!config.sourceTextSegments && !config.sourceMarkers) {
4874
+ try {
4875
+ generatedSourceMap = getSsmlSourceMap2(ssml);
4876
+ } catch {
4877
+ generatedSourceMap = void 0;
4425
4878
  }
4426
4879
  }
4427
- }
4428
- const speak = tokens.find((token) => token.name.toLowerCase() === "speak");
4429
- const voices = tokens.filter((token) => token.name.toLowerCase() === "voice");
4430
- const backgroundAudioTokens = tokens.filter((token) => token.name.toLowerCase() === "mstts:backgroundaudio");
4431
- for (const [index, token] of backgroundAudioTokens.entries()) {
4432
- if (index > 0)
4433
- addDiagnostic2(
4434
- diagnostics,
4435
- ssml,
4436
- token.start,
4437
- "An SSML document can contain at most one <mstts:backgroundaudio> element."
4438
- );
4439
- }
4440
- if (!speak || voices.length === 0)
4441
- addDiagnostic2(
4442
- diagnostics,
4443
- ssml,
4444
- speak?.start ?? 0,
4445
- "Azure SSML requires at least one <voice> element under <speak>."
4446
- );
4447
- const voiceName = voices[0] ? attr2(voices[0], "name") : void 0;
4448
- const voiceCatalog = normalizeVoiceCatalog2(options);
4449
- const normalizeLanguage = createLanguageNormalizer2(options);
4450
- const policySeverity = diagnosticSeverity2(options.unknownVoicePolicy ?? "warn");
4451
- const voicesToValidate = options.validateNestedVoices === false ? voices.slice(0, 1) : voices;
4452
- for (const token of voicesToValidate) {
4453
- const name = attr2(token, "name")?.trim();
4454
- const language = attr2(token, "xml:lang")?.trim() || (speak ? attr2(speak, "xml:lang")?.trim() : void 0);
4455
- const definition = name ? voiceCatalog.get(name.toLowerCase()) : void 0;
4456
- if (name && definition?.status === "preview")
4457
- addDiagnostic2(
4458
- diagnostics,
4459
- ssml,
4460
- token.start,
4461
- `Voice "${name}" is an Azure Speech preview voice and may change or require preview access.`,
4462
- "warning",
4463
- "azure-preview-voice"
4464
- );
4465
- if (name && definition?.status === "deprecated")
4466
- addDiagnostic2(
4467
- diagnostics,
4468
- ssml,
4469
- token.start,
4470
- `Voice "${name}" is deprecated by Azure Speech; migrate to a supported voice.`,
4471
- "info",
4472
- "azure-deprecated-voice"
4473
- );
4474
- if (name && !definition && policySeverity)
4475
- addDiagnostic2(
4476
- diagnostics,
4477
- ssml,
4478
- token.start,
4479
- `Unknown voice "${name}" is not registered in the voice catalog.`,
4480
- policySeverity,
4481
- "azure-unknown-voice"
4482
- );
4483
- if (name && language && definitionMatchesLanguage2(definition, name, language, normalizeLanguage) === false)
4484
- addDiagnostic2(
4485
- diagnostics,
4486
- ssml,
4487
- token.start,
4488
- `Voice "${name}" does not match language "${language}"; the voice name prefix indicates a different language or region.`,
4489
- "warning",
4490
- "azure-locale-mismatch"
4491
- );
4492
- }
4493
- for (const token of tokens) {
4494
- const tokenName = token.name.toLowerCase();
4495
- const tokenVoiceName = tokenName === "voice" ? attr2(token, "name")?.trim() : tokenName === "mstts:turn" ? attr2(token, "voice")?.trim() || token.parentVoiceName : options.validateNestedVoices === false ? voiceName : token.parentVoiceName;
4496
- validateElement2(token, ssml, diagnostics, tokenVoiceName, options, voiceCatalog);
4497
- const definition = tokenVoiceName ? voiceCatalog.get(tokenVoiceName.toLowerCase()) : void 0;
4498
- validateVoiceFeatureMatrix2(token, ssml, diagnostics, tokenVoiceName, definition);
4499
- if (tokenName === "voice" && options.model && definition?.models && !definition.models.some((model) => model.toLowerCase() === options.model?.toLowerCase())) {
4500
- addDiagnostic2(
4501
- diagnostics,
4502
- ssml,
4503
- token.start,
4504
- `Voice "${tokenVoiceName}" does not support model "${options.model}" according to the configured feature matrix.`,
4505
- "error",
4506
- "azure-unsupported-model-for-voice"
4880
+ const sourceBaseOffset = config.sourceTextRange?.start ?? 0;
4881
+ const sourceSegments = config.sourceTextSegments ?? generatedSourceMap?.segments.map((segment) => ({
4882
+ ...segment,
4883
+ range: {
4884
+ start: segment.range.start + sourceBaseOffset,
4885
+ end: segment.range.end + sourceBaseOffset
4886
+ },
4887
+ sourceNodePath: [...segment.sourceNodePath]
4888
+ })) ?? [];
4889
+ const sourceMarkers = config.sourceMarkers ?? generatedSourceMap?.markers.map((marker) => ({
4890
+ ...marker,
4891
+ originalTextRange: {
4892
+ start: marker.originalTextRange.start + sourceBaseOffset,
4893
+ end: marker.originalTextRange.end + sourceBaseOffset
4894
+ },
4895
+ sourceNodePath: [...marker.sourceNodePath]
4896
+ })) ?? [];
4897
+ const sourceText = sourceSegments.map((segment) => segment.text).join("");
4898
+ const mapSourceEvent = (text, offsetHint, markerName) => {
4899
+ const marker = markerName ? sourceMarkers.find((candidate) => candidate.name === markerName) : void 0;
4900
+ if (marker) {
4901
+ return {
4902
+ originalTextRange: { ...marker.originalTextRange },
4903
+ sourceNodePath: [...marker.sourceNodePath],
4904
+ textRange: { ...marker.originalTextRange },
4905
+ mappingStatus: "exact"
4906
+ };
4907
+ }
4908
+ if (sourceSegments.length === 0 && !config.sourceTextRange && !config.sourceNodePath) {
4909
+ const unmapped = { mappingStatus: "unmapped" };
4910
+ Object.defineProperty(unmapped, "mappingStatus", { value: "unmapped", enumerable: false });
4911
+ return unmapped;
4912
+ }
4913
+ const value = text ?? "";
4914
+ let localStart = Number.isFinite(offsetHint) && (offsetHint ?? 0) >= 0 ? offsetHint : -1;
4915
+ let mappingStatus = "exact";
4916
+ if (value && localStart >= 0 && sourceText.slice(localStart, localStart + value.length) !== value) {
4917
+ localStart = -1;
4918
+ mappingStatus = "fallback";
4919
+ }
4920
+ if (localStart < 0 || localStart > sourceText.length) {
4921
+ localStart = value ? sourceText.indexOf(value, sourceEventCursor) : sourceEventCursor;
4922
+ if (localStart < 0) localStart = value ? sourceText.indexOf(value) : sourceEventCursor;
4923
+ mappingStatus = "fallback";
4924
+ }
4925
+ localStart = Math.max(0, localStart);
4926
+ const localEnd = Math.min(sourceText.length, localStart + value.length);
4927
+ sourceEventCursor = Math.max(sourceEventCursor, localEnd);
4928
+ const baseStart = config.sourceTextRange?.start ?? sourceSegments[0]?.range.start ?? 0;
4929
+ const fallbackRange = { start: baseStart + localStart, end: baseStart + localEnd };
4930
+ const segment = sourceSegments.find(({ range }) => range.start <= fallbackRange.start && range.end > fallbackRange.start) ?? sourceSegments.find(({ range }) => range.end > fallbackRange.start) ?? (value.length === 0 ? sourceSegments.find(({ range }) => range.start <= fallbackRange.start && range.end >= fallbackRange.start) : void 0);
4931
+ return {
4932
+ originalTextRange: { ...fallbackRange },
4933
+ textRange: { ...fallbackRange },
4934
+ ...segment ? { sourceNodePath: [...segment.sourceNodePath] } : config.sourceNodePath ? { sourceNodePath: [...config.sourceNodePath] } : {},
4935
+ mappingStatus: segment || config.sourceTextRange || config.sourceNodePath ? mappingStatus : "unmapped"
4936
+ };
4937
+ };
4938
+ synthesizer.wordBoundary = (_sender, event) => {
4939
+ boundaries.push({
4940
+ text: event.text,
4941
+ audioOffsetMs: ticksToMilliseconds(event.audioOffset),
4942
+ durationMs: ticksToMilliseconds(event.duration),
4943
+ ...mapSourceEvent(
4944
+ event.text,
4945
+ event.textOffset
4946
+ )
4947
+ });
4948
+ };
4949
+ synthesizer.visemeReceived = (_sender, event) => {
4950
+ const eventWithOffset = event;
4951
+ visemes.push({
4952
+ visemeId: event.visemeId,
4953
+ audioOffsetMs: ticksToMilliseconds(event.audioOffset),
4954
+ ...mapSourceEvent(void 0, eventWithOffset.textOffset)
4955
+ });
4956
+ };
4957
+ synthesizer.bookmarkReached = (_sender, event) => {
4958
+ const eventWithOffset = event;
4959
+ bookmarks.push({
4960
+ name: event.text,
4961
+ audioOffsetMs: ticksToMilliseconds(event.audioOffset),
4962
+ ...mapSourceEvent(void 0, eventWithOffset.textOffset, event.text)
4963
+ });
4964
+ };
4965
+ const cb = (result) => {
4966
+ if (settled) return;
4967
+ const { reason, errorDetails } = result;
4968
+ if (reason !== SpeechSDK2.ResultReason.SynthesizingAudioCompleted) {
4969
+ const err = errorDetails || `Speech synthesis failed with reason ${reason}.`;
4970
+ rejectWithError(err);
4971
+ return;
4972
+ }
4973
+ settled = true;
4974
+ cleanup();
4975
+ closeResources();
4976
+ const eventDurationMs = Math.max(
4977
+ 0,
4978
+ ...(boundaries ?? []).map((boundary) => boundary.audioOffsetMs + boundary.durationMs),
4979
+ ...(visemes ?? []).map((viseme) => viseme.audioOffsetMs),
4980
+ ...(bookmarks ?? []).map((bookmark) => bookmark.audioOffsetMs)
4507
4981
  );
4982
+ const durationMs = result.audioDuration ? ticksToMilliseconds(result.audioDuration) : eventDurationMs;
4983
+ const requestId = result.resultId;
4984
+ const addSourceMetadata = (event) => {
4985
+ const mapped = {
4986
+ ...event,
4987
+ ...config.sourceTextRange && !("textRange" in event) ? { textRange: { ...config.sourceTextRange } } : {},
4988
+ ...config.sourceTextRange && !("originalTextRange" in event) ? { originalTextRange: { ...config.sourceTextRange } } : {},
4989
+ ...config.chunkIndex !== void 0 ? { chunkIndex: config.chunkIndex } : {},
4990
+ ...config.sourceNodePath ? { sourceNodePath: [...config.sourceNodePath] } : {},
4991
+ ...requestId ? { requestId } : {}
4992
+ };
4993
+ if (event.mappingStatus === "unmapped")
4994
+ Object.defineProperty(mapped, "mappingStatus", { value: "unmapped", enumerable: false });
4995
+ return mapped;
4996
+ };
4997
+ const sourceBoundaries = boundaries.map((boundary) => addSourceMetadata(boundary));
4998
+ const sourceVisemes = visemes.map((viseme) => addSourceMetadata(viseme));
4999
+ const sourceBookmarks = bookmarks.map((bookmark) => addSourceMetadata(bookmark));
5000
+ resolve({
5001
+ audioData: result.audioData,
5002
+ durationMs,
5003
+ audioSpec: inspectAudioSpecification(result.audioData, config.outputFormat ?? DEFAULT_OUTPUT_FORMAT),
5004
+ mimeType: resolveMimeType(config.outputFormat ?? DEFAULT_OUTPUT_FORMAT),
5005
+ ...config.sourceTextRange ? { textRange: { ...config.sourceTextRange } } : {},
5006
+ ...requestId ? { requestId } : {},
5007
+ ...sourceBoundaries.length > 0 ? { boundaries: sourceBoundaries, wordBoundary: sourceBoundaries, wordBoundaries: sourceBoundaries } : {},
5008
+ ...sourceVisemes.length > 0 ? { visemes: sourceVisemes } : {},
5009
+ ...sourceBookmarks.length > 0 ? { bookmarks: sourceBookmarks } : {}
5010
+ });
5011
+ };
5012
+ try {
5013
+ if (config.signal) {
5014
+ abortHandler = () => rejectWithError(new SynthesisCancelledError());
5015
+ config.signal.addEventListener("abort", abortHandler, { once: true });
5016
+ }
5017
+ if (config.timeoutMs !== void 0 && config.timeoutMs > 0) {
5018
+ timeout = setTimeout(
5019
+ () => rejectWithError(new SynthesisTimeoutError(`Speech synthesis timed out after ${config.timeoutMs} ms.`)),
5020
+ config.timeoutMs
5021
+ );
5022
+ }
5023
+ synthesizer.speakSsmlAsync(ssml, cb, rejectWithError);
5024
+ } catch (error) {
5025
+ rejectWithError(error);
4508
5026
  }
4509
- }
4510
- return diagnostics;
4511
- }
4512
- function urlAttributes2(token) {
4513
- const tag = canonicalTagName2(token.name);
4514
- const attributes = tag === "audio" || tag === "mstts:backgroundaudio" ? ["src"] : tag === "lexicon" ? ["uri"] : tag === "mstts:voiceconversion" ? ["url"] : [];
4515
- return attributes.flatMap((attribute) => {
4516
- const value = attr2(token, attribute);
4517
- return value === void 0 ? [] : [{ attribute, value }];
4518
5027
  });
4519
5028
  }
4520
- function validateAzureSsml2(ssml, options = {}) {
4521
- const diagnostics = validateAzureSsmlStatic2(ssml, options);
4522
- const validator = options.urlValidator ?? options.customUrlValidator;
4523
- if (!validator || typeof ssml !== "string") return diagnostics;
4524
- const runnerOptions = options.urlValidation ?? {};
4525
- const boundedValidator = createAzureUrlValidatorRunner2(validator, {
4526
- ...runnerOptions,
4527
- ...options.urlValidatorConcurrency !== void 0 ? { concurrency: options.urlValidatorConcurrency } : {},
4528
- ...options.urlValidatorTimeoutMs !== void 0 ? { timeoutMs: options.urlValidatorTimeoutMs } : {},
4529
- ...options.urlValidatorSignal ? { signal: options.urlValidatorSignal } : {},
4530
- ...options.urlValidatorCache ? { cache: options.urlValidatorCache } : {}
5029
+ function isRetryableSynthesisError(error) {
5030
+ if (error instanceof SynthesisCancelledError || error instanceof SynthesisTimeoutError) return false;
5031
+ if (error instanceof AzureTtsError && error.status !== 0)
5032
+ return error.status === 429 || error.status >= 500 && error.status < 600;
5033
+ const message = error instanceof Error ? error.message : String(error);
5034
+ if (/\b4\d{2}\b/.test(message)) return false;
5035
+ const status = error && typeof error === "object" && "status" in error ? error.status : void 0;
5036
+ if (typeof status === "number") return status === 429 || status >= 500 && status < 600;
5037
+ return /network|connection|connect|socket|fetch failed|econn|etimedout|temporar|transient|unavailable/i.test(message);
5038
+ }
5039
+ function retryDelay(options, retryAttempt) {
5040
+ const base = Math.min(options.maxDelayMs, options.initialDelayMs * 2 ** Math.max(0, retryAttempt - 1));
5041
+ return Math.floor(Math.random() * (base + 1));
5042
+ }
5043
+ function resolveConcurrency(value, total) {
5044
+ if (value === void 0) return 1;
5045
+ if (value === Infinity) return Math.max(1, total);
5046
+ return Number.isFinite(value) ? Math.max(1, Math.floor(value)) : 1;
5047
+ }
5048
+ async function waitForRetry(delayMs, signal) {
5049
+ if (signal?.aborted) throw new SynthesisCancelledError();
5050
+ if (delayMs <= 0) return;
5051
+ await new Promise((resolve, reject) => {
5052
+ let timer;
5053
+ const abort = () => {
5054
+ clearTimeout(timer);
5055
+ signal?.removeEventListener("abort", abort);
5056
+ reject(new SynthesisCancelledError());
5057
+ };
5058
+ timer = setTimeout(() => {
5059
+ signal?.removeEventListener("abort", abort);
5060
+ resolve();
5061
+ }, delayMs);
5062
+ if (signal) {
5063
+ signal.addEventListener("abort", abort, { once: true });
5064
+ }
4531
5065
  });
4532
- let tokens;
4533
- try {
4534
- tokens = tokenizeElements2(ssml);
4535
- } catch {
4536
- return diagnostics;
5066
+ }
5067
+ async function synthesizeWithRetry(ssml, config, retryOptions, onRetry) {
5068
+ const options = retryOptions ? {
5069
+ maxRetries: Math.max(0, Math.floor(retryOptions.maxRetries)),
5070
+ initialDelayMs: Math.max(0, retryOptions.initialDelayMs),
5071
+ maxDelayMs: Math.max(0, retryOptions.maxDelayMs)
5072
+ } : void 0;
5073
+ let attempt = 0;
5074
+ while (true) {
5075
+ if (config.signal?.aborted) throw new SynthesisCancelledError();
5076
+ try {
5077
+ return await synthesizeSsml(ssml, config);
5078
+ } catch (error) {
5079
+ if (!options || attempt >= options.maxRetries || !isRetryableSynthesisError(error)) throw error;
5080
+ attempt += 1;
5081
+ const delayMs = retryDelay(options, attempt);
5082
+ onRetry(attempt, delayMs);
5083
+ await waitForRetry(delayMs, config.signal);
5084
+ }
4537
5085
  }
4538
- const checks = tokens.flatMap(
4539
- (token) => urlAttributes2(token).map(async ({ attribute, value }) => {
5086
+ }
5087
+ async function synthesizeSsmlChunks(chunks, config) {
5088
+ const results = new Array(chunks.length);
5089
+ const totalChunks = chunks.length;
5090
+ const report = (event) => config.onProgress?.(event);
5091
+ for (const [index, chunk] of chunks.entries()) {
5092
+ const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
5093
+ report({
5094
+ currentChunk: index,
5095
+ totalChunks,
5096
+ percent: totalChunks === 0 ? 100 : Math.round(index / totalChunks * 100),
5097
+ chunkIndex: index,
5098
+ originalTextRange: input.originalTextRange,
5099
+ status: "pending",
5100
+ durationMs: 0
5101
+ });
5102
+ }
5103
+ let completed = 0;
5104
+ let nextIndex = 0;
5105
+ const concurrency = resolveConcurrency(config.concurrency, chunks.length);
5106
+ const worker = async () => {
5107
+ while (true) {
5108
+ const index = nextIndex++;
5109
+ if (index >= chunks.length) return;
5110
+ const chunk = chunks[index];
5111
+ const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
5112
+ report({
5113
+ currentChunk: completed,
5114
+ totalChunks,
5115
+ percent: totalChunks === 0 ? 100 : Math.round(completed / totalChunks * 100),
5116
+ chunkIndex: index,
5117
+ originalTextRange: input.originalTextRange,
5118
+ status: "synthesizing",
5119
+ durationMs: 0
5120
+ });
5121
+ const startedAt = Date.now();
4540
5122
  try {
4541
- const result = await boundedValidator(value, { tag: token.name, attribute });
4542
- const valid = typeof result === "boolean" ? result : result.valid;
4543
- if (!valid) {
4544
- const reason = typeof result === "boolean" ? void 0 : result.reason;
4545
- addDiagnostic2(
4546
- diagnostics,
4547
- ssml,
4548
- token.start,
4549
- `<${token.name} ${attribute}> was rejected by the custom URL validator${reason ? `: ${reason}` : "."}`
4550
- );
4551
- }
4552
- } catch (error) {
4553
- const reason = error instanceof Error ? error.message : String(error);
4554
- addDiagnostic2(
4555
- diagnostics,
4556
- ssml,
4557
- token.start,
4558
- `<${token.name} ${attribute}> could not be validated by the custom URL validator: ${reason}`
5123
+ const result = await synthesizeWithRetry(
5124
+ input.ssml,
5125
+ {
5126
+ ...config,
5127
+ ...input.originalTextRange ? { sourceTextRange: input.originalTextRange } : {},
5128
+ ...input.sourceNodePath ?? config.sourceNodePath ? { sourceNodePath: [...input.sourceNodePath ?? config.sourceNodePath ?? []] } : {},
5129
+ ...input.sourceTextSegments ? { sourceTextSegments: input.sourceTextSegments } : {},
5130
+ ...input.sourceMarkers ? { sourceMarkers: input.sourceMarkers } : {},
5131
+ chunkIndex: index,
5132
+ onProgress: void 0
5133
+ },
5134
+ config.retryOptions,
5135
+ (retryAttempt, nextRetryDelayMs) => report({
5136
+ currentChunk: completed,
5137
+ totalChunks,
5138
+ percent: totalChunks === 0 ? 100 : Math.round(completed / totalChunks * 100),
5139
+ chunkIndex: index,
5140
+ originalTextRange: input.originalTextRange,
5141
+ status: "synthesizing",
5142
+ durationMs: Date.now() - startedAt,
5143
+ retryAttempt,
5144
+ nextRetryDelayMs,
5145
+ isRetrying: true
5146
+ })
4559
5147
  );
5148
+ results[index] = result;
5149
+ completed += 1;
5150
+ report({
5151
+ currentChunk: completed,
5152
+ totalChunks,
5153
+ percent: totalChunks === 0 ? 100 : Math.round(completed / totalChunks * 100),
5154
+ chunkIndex: index,
5155
+ originalTextRange: input.originalTextRange,
5156
+ status: "success",
5157
+ durationMs: Date.now() - startedAt
5158
+ });
5159
+ } catch (error) {
5160
+ report({
5161
+ currentChunk: completed,
5162
+ totalChunks,
5163
+ percent: totalChunks === 0 ? 100 : Math.round(completed / totalChunks * 100),
5164
+ chunkIndex: index,
5165
+ originalTextRange: input.originalTextRange,
5166
+ status: "failed",
5167
+ durationMs: Date.now() - startedAt,
5168
+ error
5169
+ });
5170
+ throw error;
4560
5171
  }
4561
- })
4562
- );
4563
- return Promise.all(checks).then(() => diagnostics);
5172
+ }
5173
+ };
5174
+ await Promise.all(Array.from({ length: Math.min(concurrency, chunks.length) }, () => worker()));
5175
+ const orderedResults = results.filter((result) => result !== void 0);
5176
+ return mergeSynthesisResults(orderedResults, {
5177
+ format: config.outputFormat ?? DEFAULT_OUTPUT_FORMAT,
5178
+ signal: config.signal
5179
+ });
5180
+ }
5181
+ function createMergedResult(results, audioData, format, audioSpec, outputMimeType) {
5182
+ const boundaries = [];
5183
+ const visemes = [];
5184
+ const bookmarks = [];
5185
+ let durationOffset = 0;
5186
+ for (const [resultIndex, result] of results.entries()) {
5187
+ const chunkBoundaries = result.boundaries && result.boundaries.length > 0 ? result.boundaries : result.wordBoundary ?? result.wordBoundaries ?? [];
5188
+ for (const boundary of chunkBoundaries) {
5189
+ const textRange = boundary.textRange ?? result.textRange;
5190
+ const originalTextRange = boundary.originalTextRange ?? textRange;
5191
+ const requestId = boundary.requestId ?? result.requestId;
5192
+ boundaries.push({
5193
+ ...boundary,
5194
+ audioOffsetMs: boundary.audioOffsetMs + durationOffset,
5195
+ chunkAudioOffsetMs: boundary.chunkAudioOffsetMs ?? boundary.audioOffsetMs,
5196
+ ...boundary.chunkIndex === void 0 ? { chunkIndex: resultIndex } : {},
5197
+ ...boundary.sourceNodePath ? { sourceNodePath: [...boundary.sourceNodePath] } : {},
5198
+ ...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
5199
+ ...textRange ? { textRange: { ...textRange } } : {},
5200
+ ...requestId ? { requestId } : {},
5201
+ mappingStatus: boundary.mappingStatus ?? "unmapped"
5202
+ });
5203
+ }
5204
+ for (const viseme of result.visemes ?? []) {
5205
+ const textRange = viseme.textRange ?? result.textRange;
5206
+ const originalTextRange = viseme.originalTextRange ?? textRange;
5207
+ const requestId = viseme.requestId ?? result.requestId;
5208
+ visemes.push({
5209
+ ...viseme,
5210
+ audioOffsetMs: viseme.audioOffsetMs + durationOffset,
5211
+ chunkAudioOffsetMs: viseme.chunkAudioOffsetMs ?? viseme.audioOffsetMs,
5212
+ ...viseme.chunkIndex === void 0 ? { chunkIndex: resultIndex } : {},
5213
+ ...viseme.sourceNodePath ? { sourceNodePath: [...viseme.sourceNodePath] } : {},
5214
+ ...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
5215
+ ...textRange ? { textRange: { ...textRange } } : {},
5216
+ ...requestId ? { requestId } : {},
5217
+ mappingStatus: viseme.mappingStatus ?? "unmapped"
5218
+ });
5219
+ }
5220
+ for (const bookmark of result.bookmarks ?? []) {
5221
+ const textRange = bookmark.textRange ?? result.textRange;
5222
+ const originalTextRange = bookmark.originalTextRange ?? textRange;
5223
+ const requestId = bookmark.requestId ?? result.requestId;
5224
+ bookmarks.push({
5225
+ ...bookmark,
5226
+ audioOffsetMs: bookmark.audioOffsetMs + durationOffset,
5227
+ chunkAudioOffsetMs: bookmark.chunkAudioOffsetMs ?? bookmark.audioOffsetMs,
5228
+ ...bookmark.chunkIndex === void 0 ? { chunkIndex: resultIndex } : {},
5229
+ ...bookmark.sourceNodePath ? { sourceNodePath: [...bookmark.sourceNodePath] } : {},
5230
+ ...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
5231
+ ...textRange ? { textRange: { ...textRange } } : {},
5232
+ ...requestId ? { requestId } : {},
5233
+ mappingStatus: bookmark.mappingStatus ?? "unmapped"
5234
+ });
5235
+ }
5236
+ durationOffset += Math.max(0, result.durationMs);
5237
+ }
5238
+ return {
5239
+ audioData,
5240
+ durationMs: durationOffset,
5241
+ mimeType: resolveMimeType(format),
5242
+ audioSpec: audioSpec ?? formatAudioSpecification(format),
5243
+ ...outputMimeType ? { mimeType: outputMimeType } : {},
5244
+ ...boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {},
5245
+ ...visemes.length > 0 ? { visemes } : {},
5246
+ ...bookmarks.length > 0 ? { bookmarks } : {},
5247
+ ...results.length === 1 && results[0]?.requestId ? { requestId: results[0].requestId } : {},
5248
+ ...results.length === 1 && results[0]?.textRange ? { textRange: { ...results[0].textRange } } : {}
5249
+ };
5250
+ }
5251
+ function mergeSynthesisResults(results, options) {
5252
+ const resolvedOptions = typeof options === "string" ? { format: options } : options;
5253
+ const format = resolvedOptions?.format;
5254
+ if (!format) throw new UnsupportedMergeFormatError("");
5255
+ const buffers = results.map((result) => result.audioData);
5256
+ const inputSpecs = results.map((result) => result.audioSpec ?? inspectAudioSpecification(result.audioData, format));
5257
+ validateAudioSpecifications(inputSpecs);
5258
+ const signal = resolvedOptions.signal ?? new AbortController().signal;
5259
+ if (signal.aborted) throw new SynthesisCancelledError();
5260
+ if (resolvedOptions.customMerger) {
5261
+ return Promise.resolve().then(
5262
+ () => resolvedOptions.customMerger?.(buffers, {
5263
+ format,
5264
+ outputMimeType: resolvedOptions.outputMimeType ?? resolveMimeType(format),
5265
+ inputSpecs,
5266
+ signal
5267
+ })
5268
+ ).then((merged) => {
5269
+ if (!merged) throw new MergeError("The custom audio merger returned no audio buffer.");
5270
+ if (!(merged instanceof ArrayBuffer) || buffers.some((buffer) => buffer.byteLength > 0) && merged.byteLength === 0)
5271
+ throw new MergeError("The custom audio merger returned an invalid audio buffer.");
5272
+ if (signal.aborted) throw new SynthesisCancelledError();
5273
+ return createMergedResult(
5274
+ results,
5275
+ merged,
5276
+ format,
5277
+ inspectAudioSpecification(merged, format),
5278
+ resolvedOptions.outputMimeType
5279
+ );
5280
+ }).catch((error) => {
5281
+ if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
5282
+ throw error;
5283
+ throw new MergeError(`Custom audio merger failed for format "${format}".`, error);
5284
+ });
5285
+ }
5286
+ try {
5287
+ return createMergedResult(
5288
+ results,
5289
+ mergeAudioBuffers(buffers, { format }),
5290
+ format,
5291
+ inputSpecs[0],
5292
+ resolvedOptions.outputMimeType
5293
+ );
5294
+ } catch (error) {
5295
+ if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
5296
+ throw error;
5297
+ throw new MergeError(`Audio buffers could not be merged for format "${format}".`, error);
5298
+ }
5299
+ }
5300
+ async function synthesizeSpeech(ssml, config) {
5301
+ return (await synthesizeSsml(ssml, config)).audioData;
4564
5302
  }
4565
- var AZURE_VOICE_CATALOG_METADATA2 = {
4566
- apiVersion: "2025-10-01",
4567
- generatedAt: "2026-08-28T00:00:00.000Z",
4568
- regions: [],
4569
- voiceCount: AZURE_VOICE_DEFINITIONS2.length
4570
- };
4571
5303
 
4572
5304
  // packages/azure-tts-client/src/safe.ts
4573
5305
  var ChunkValidationError = class extends Error {
4574
5306
  constructor(chunkIndex, diagnostics) {
4575
5307
  super(`SSML validation failed for chunk ${chunkIndex}; the Azure Speech API was not called.`);
4576
- this.kind = "chunk-validation";
5308
+ this.kind = "validation-error";
4577
5309
  this.name = "ChunkValidationError";
4578
5310
  this.chunkIndex = chunkIndex;
4579
5311
  this.diagnostics = diagnostics;
4580
5312
  }
4581
5313
  };
5314
+ function failure(error) {
5315
+ return { ok: false, success: false, status: error.kind, error };
5316
+ }
5317
+ function isRetryable(error) {
5318
+ if (error instanceof Error && /cancel|abort|tim(?:e|ed) ?out/i.test(error.message)) return false;
5319
+ const status = error && typeof error === "object" && "status" in error ? error.status : void 0;
5320
+ if (typeof status === "number" && status !== 0) return status === 429 || status >= 500 && status < 600;
5321
+ const message = error instanceof Error ? error.message : String(error);
5322
+ if (/\b4\d{2}\b/.test(message)) return false;
5323
+ return /network|connection|connect|socket|fetch failed|econn|etimedout|temporar|transient|unavailable/i.test(message);
5324
+ }
5325
+ function delayForRetry(options, attempt) {
5326
+ const maxDelay = Math.max(0, options.maxDelayMs);
5327
+ const base = Math.min(maxDelay, Math.max(0, options.initialDelayMs) * 2 ** Math.max(0, attempt - 1));
5328
+ return Math.floor(Math.random() * (base + 1));
5329
+ }
5330
+ function resolveConcurrency2(value, total) {
5331
+ if (value === void 0) return 1;
5332
+ if (value === Infinity) return Math.max(1, total);
5333
+ return Number.isFinite(value) ? Math.max(1, Math.floor(value)) : 1;
5334
+ }
5335
+ async function retryableSynthesis(synthesize, options, signal, onRetry) {
5336
+ const retry = options ? {
5337
+ maxRetries: Math.max(0, Math.floor(options.maxRetries)),
5338
+ initialDelayMs: options.initialDelayMs,
5339
+ maxDelayMs: options.maxDelayMs
5340
+ } : void 0;
5341
+ let attempt = 0;
5342
+ while (true) {
5343
+ if (signal?.aborted) throw new Error("Speech synthesis was cancelled.");
5344
+ try {
5345
+ return await synthesize();
5346
+ } catch (error) {
5347
+ if (!retry || attempt >= retry.maxRetries || !isRetryable(error)) throw error;
5348
+ attempt += 1;
5349
+ const delayMs = delayForRetry(retry, attempt);
5350
+ onRetry(attempt, delayMs);
5351
+ if (delayMs > 0)
5352
+ await new Promise((resolve, reject) => {
5353
+ const timer = setTimeout(() => {
5354
+ signal?.removeEventListener("abort", abort);
5355
+ resolve();
5356
+ }, delayMs);
5357
+ const abort = () => {
5358
+ clearTimeout(timer);
5359
+ signal?.removeEventListener("abort", abort);
5360
+ reject(new Error("Speech synthesis was cancelled."));
5361
+ };
5362
+ signal?.addEventListener("abort", abort, { once: true });
5363
+ });
5364
+ }
5365
+ }
5366
+ }
5367
+ function sharedValidationOptions(options, signal) {
5368
+ const validator = options.urlValidator ?? options.customUrlValidator;
5369
+ if (!validator) return signal ? withValidationSignal(options, signal) : options;
5370
+ const runner = createAzureUrlValidatorRunner2(validator, {
5371
+ ...options.urlValidation ?? {},
5372
+ ...options.urlValidatorConcurrency !== void 0 ? { concurrency: options.urlValidatorConcurrency } : {},
5373
+ ...options.urlValidatorTimeoutMs !== void 0 ? { timeoutMs: options.urlValidatorTimeoutMs } : {},
5374
+ ...signal ? { signal } : {},
5375
+ ...options.urlValidatorCache ? { cache: options.urlValidatorCache } : {}
5376
+ });
5377
+ return {
5378
+ ...withValidationSignal(options, signal),
5379
+ urlValidatorRunner: runner
5380
+ };
5381
+ }
4582
5382
  async function synthesizeSsmlSafe(client, ssml, options = {}) {
4583
- const validationOptions = options.validation ?? options;
5383
+ const validationOptions = sharedValidationOptions(options.validation ?? options, options.signal);
4584
5384
  const diagnostics = await Promise.resolve(validateAzureSsml2(ssml, validationOptions));
5385
+ if (options.signal?.aborted) {
5386
+ const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
5387
+ return failure(error);
5388
+ }
4585
5389
  const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
4586
5390
  if (errors.length > 0) {
4587
- return {
4588
- ok: false,
4589
- success: false,
4590
- status: "validation-error",
4591
- error: {
4592
- kind: "validation",
4593
- message: "SSML validation failed; the Azure Speech API was not called.",
4594
- diagnostics: errors
4595
- }
4596
- };
5391
+ return failure({
5392
+ kind: "validation-error",
5393
+ message: "SSML validation failed; the Azure Speech API was not called.",
5394
+ diagnostics: errors
5395
+ });
4597
5396
  }
4598
5397
  try {
4599
- return { ok: true, success: true, status: "success", value: await client.synthesizeSsml(ssml) };
5398
+ return {
5399
+ ok: true,
5400
+ success: true,
5401
+ status: "success",
5402
+ value: await client.synthesizeSsml(ssml, { signal: options.signal })
5403
+ };
4600
5404
  } catch (error) {
4601
- const azureError = error instanceof AzureTtsError ? error : createSpeechSdkError(error);
4602
- return { ok: false, success: false, status: "azure-api-error", error: azureError };
5405
+ const synthesisError = toSynthesisError(error);
5406
+ return failure(synthesisError);
4603
5407
  }
4604
5408
  }
4605
5409
  async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
4606
- const validationOptions = options.validation ?? options;
5410
+ const validationOptions = sharedValidationOptions(options.validation ?? options, options.signal);
5411
+ if (options.signal?.aborted) {
5412
+ const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
5413
+ return failure(error);
5414
+ }
4607
5415
  const pending = (index, status, error) => {
4608
5416
  options.onProgress?.({
4609
5417
  currentChunk: status === "success" ? index + 1 : index,
@@ -4620,77 +5428,175 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
4620
5428
  pending(index, "pending");
4621
5429
  });
4622
5430
  const validations = await Promise.all(
4623
- chunks.map(async (chunk) => {
5431
+ chunks.map(async (chunk, index) => {
4624
5432
  const ssml = typeof chunk === "string" ? chunk : chunk.ssml;
4625
- const diagnostics = await Promise.resolve(validateAzureSsml2(ssml, validationOptions));
5433
+ const sourceNodePath = typeof chunk === "string" ? options.sourceNodePath : chunk.sourceNodePath ?? options.sourceNodePath;
5434
+ const diagnostics = await Promise.resolve(
5435
+ validateAzureSsml2(ssml, {
5436
+ ...validationOptions,
5437
+ ...sourceNodePath ? { sourceNodePath } : {},
5438
+ chunkIndex: index
5439
+ })
5440
+ );
4626
5441
  return diagnostics.filter((diagnostic) => diagnostic.severity === "error");
4627
5442
  })
4628
5443
  );
4629
5444
  const firstInvalidIndex = validations.findIndex((diagnostics) => diagnostics.length > 0);
5445
+ if (options.signal?.aborted) {
5446
+ const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
5447
+ return failure(error);
5448
+ }
4630
5449
  if (firstInvalidIndex >= 0) {
4631
5450
  const error = new ChunkValidationError(firstInvalidIndex, validations[firstInvalidIndex] ?? []);
4632
5451
  pending(firstInvalidIndex, "failed", error);
4633
- return { ok: false, success: false, status: "validation-error", error };
5452
+ return failure(error);
4634
5453
  }
4635
5454
  try {
4636
5455
  if (client.synthesizeChunks) {
4637
- const value = await client.synthesizeChunks(chunks, { onProgress: options.onProgress });
5456
+ const normalizedChunks = chunks.map((chunk) => {
5457
+ if (typeof chunk === "string" || chunk.sourceNodePath || !options.sourceNodePath) return chunk;
5458
+ return { ...chunk, sourceNodePath: [...options.sourceNodePath] };
5459
+ });
5460
+ const value = await client.synthesizeChunks(normalizedChunks, {
5461
+ onProgress: options.onProgress,
5462
+ outputFormat: options.outputFormat,
5463
+ signal: options.signal,
5464
+ timeoutMs: options.timeoutMs,
5465
+ sourceNodePath: options.sourceNodePath,
5466
+ concurrency: options.concurrency,
5467
+ retryOptions: options.retryOptions
5468
+ });
4638
5469
  return { ok: true, success: true, status: "success", value };
4639
5470
  }
4640
- const results = [];
4641
- for (const [index, chunk] of chunks.entries()) {
4642
- const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
4643
- const sourceNodePath = input.sourceNodePath;
4644
- pending(index, "synthesizing");
4645
- const startedAt = Date.now();
4646
- try {
4647
- const result = await client.synthesizeSsml(input.ssml);
4648
- results.push({
4649
- ...result,
4650
- ...input.originalTextRange ? { textRange: { ...input.originalTextRange } } : {},
4651
- ...sourceNodePath ? {
4652
- boundaries: result.boundaries?.map((event) => ({
4653
- ...event,
4654
- sourceNodePath: [...sourceNodePath]
4655
- })),
4656
- visemes: result.visemes?.map((event) => ({ ...event, sourceNodePath: [...sourceNodePath] })),
4657
- bookmarks: result.bookmarks?.map((event) => ({ ...event, sourceNodePath: [...sourceNodePath] }))
4658
- } : {}
4659
- });
4660
- options.onProgress?.({
4661
- currentChunk: index + 1,
4662
- totalChunks: chunks.length,
4663
- percent: chunks.length === 0 ? 100 : Math.round((index + 1) / chunks.length * 100),
4664
- chunkIndex: index,
4665
- originalTextRange: input.originalTextRange,
4666
- status: "success",
4667
- durationMs: Date.now() - startedAt
4668
- });
4669
- } catch (error) {
4670
- options.onProgress?.({
4671
- currentChunk: index,
4672
- totalChunks: chunks.length,
4673
- percent: chunks.length === 0 ? 100 : Math.round(index / chunks.length * 100),
4674
- chunkIndex: index,
4675
- originalTextRange: input.originalTextRange,
4676
- status: "failed",
4677
- durationMs: Date.now() - startedAt,
4678
- error
4679
- });
4680
- throw error;
5471
+ const results = new Array(chunks.length);
5472
+ let completed = 0;
5473
+ let nextIndex = 0;
5474
+ const concurrency = resolveConcurrency2(options.concurrency, chunks.length);
5475
+ const worker = async () => {
5476
+ while (true) {
5477
+ const index = nextIndex++;
5478
+ if (index >= chunks.length) return;
5479
+ const chunk = chunks[index];
5480
+ const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
5481
+ const sourceNodePath = input.sourceNodePath;
5482
+ const originalTextRange = input.originalTextRange;
5483
+ pending(index, "synthesizing");
5484
+ const startedAt = Date.now();
5485
+ try {
5486
+ const result = await retryableSynthesis(
5487
+ () => client.synthesizeSsml(input.ssml, {
5488
+ outputFormat: options.outputFormat,
5489
+ signal: options.signal,
5490
+ timeoutMs: options.timeoutMs,
5491
+ sourceNodePath: input.sourceNodePath ?? options.sourceNodePath
5492
+ }),
5493
+ options.retryOptions,
5494
+ options.signal,
5495
+ (retryAttempt, nextRetryDelayMs) => options.onProgress?.({
5496
+ currentChunk: completed,
5497
+ totalChunks: chunks.length,
5498
+ percent: chunks.length === 0 ? 100 : Math.round(completed / chunks.length * 100),
5499
+ chunkIndex: index,
5500
+ originalTextRange: input.originalTextRange,
5501
+ status: "synthesizing",
5502
+ durationMs: Date.now() - startedAt,
5503
+ retryAttempt,
5504
+ nextRetryDelayMs,
5505
+ isRetrying: true
5506
+ })
5507
+ );
5508
+ results[index] = {
5509
+ ...result,
5510
+ ...input.originalTextRange ? { textRange: { ...input.originalTextRange } } : {},
5511
+ ...sourceNodePath ? {
5512
+ boundaries: result.boundaries?.map((event) => ({
5513
+ ...event,
5514
+ sourceNodePath: [...sourceNodePath],
5515
+ ...event.originalTextRange ? { originalTextRange: { ...event.originalTextRange } } : input.originalTextRange ? { originalTextRange: { ...input.originalTextRange } } : {}
5516
+ })),
5517
+ visemes: result.visemes?.map((event) => ({
5518
+ ...event,
5519
+ sourceNodePath: [...sourceNodePath],
5520
+ ...event.originalTextRange ? { originalTextRange: { ...event.originalTextRange } } : input.originalTextRange ? { originalTextRange: { ...input.originalTextRange } } : {}
5521
+ })),
5522
+ bookmarks: result.bookmarks?.map((event) => ({
5523
+ ...event,
5524
+ sourceNodePath: [...sourceNodePath],
5525
+ ...event.originalTextRange ? { originalTextRange: { ...event.originalTextRange } } : input.originalTextRange ? { originalTextRange: { ...input.originalTextRange } } : {}
5526
+ }))
5527
+ } : {},
5528
+ ...originalTextRange ? {
5529
+ boundaries: result.boundaries?.map((event) => ({
5530
+ ...event,
5531
+ originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
5532
+ })),
5533
+ wordBoundary: result.wordBoundary?.map((event) => ({
5534
+ ...event,
5535
+ originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
5536
+ })),
5537
+ wordBoundaries: result.wordBoundaries?.map((event) => ({
5538
+ ...event,
5539
+ originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
5540
+ })),
5541
+ visemes: result.visemes?.map((event) => ({
5542
+ ...event,
5543
+ originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
5544
+ })),
5545
+ bookmarks: result.bookmarks?.map((event) => ({
5546
+ ...event,
5547
+ originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
5548
+ }))
5549
+ } : {}
5550
+ };
5551
+ completed += 1;
5552
+ options.onProgress?.({
5553
+ currentChunk: completed,
5554
+ totalChunks: chunks.length,
5555
+ percent: chunks.length === 0 ? 100 : Math.round(completed / chunks.length * 100),
5556
+ chunkIndex: index,
5557
+ originalTextRange: input.originalTextRange,
5558
+ status: "success",
5559
+ durationMs: Date.now() - startedAt
5560
+ });
5561
+ } catch (error) {
5562
+ options.onProgress?.({
5563
+ currentChunk: completed,
5564
+ totalChunks: chunks.length,
5565
+ percent: chunks.length === 0 ? 100 : Math.round(index / chunks.length * 100),
5566
+ chunkIndex: index,
5567
+ originalTextRange: input.originalTextRange,
5568
+ status: "failed",
5569
+ durationMs: Date.now() - startedAt,
5570
+ error
5571
+ });
5572
+ throw error;
5573
+ }
4681
5574
  }
4682
- }
5575
+ };
5576
+ await Promise.all(Array.from({ length: Math.min(concurrency, chunks.length) }, () => worker()));
5577
+ const orderedResults = results.filter((result) => result !== void 0);
4683
5578
  return {
4684
5579
  ok: true,
4685
5580
  success: true,
4686
5581
  status: "success",
4687
- value: mergeSynthesisResults(results, options.outputFormat)
5582
+ value: mergeSynthesisResults(orderedResults, {
5583
+ format: options.outputFormat ?? "audio-16khz-128kbitrate-mono-mp3",
5584
+ signal: options.signal
5585
+ })
4688
5586
  };
4689
5587
  } catch (error) {
4690
- const azureError = error instanceof AzureTtsError ? error : createSpeechSdkError(error);
4691
- return { ok: false, success: false, status: "azure-api-error", error: azureError };
5588
+ const synthesisError = toSynthesisError(error);
5589
+ return failure(synthesisError);
4692
5590
  }
4693
5591
  }
5592
+ function withValidationSignal(options, signal) {
5593
+ if (!signal) return options;
5594
+ return {
5595
+ ...options,
5596
+ urlValidatorSignal: signal,
5597
+ urlValidation: { ...options.urlValidation ?? {}, signal }
5598
+ };
5599
+ }
4694
5600
 
4695
5601
  // packages/azure-tts-client/src/client.ts
4696
5602
  var ENDPOINT_TEMPLATE = "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
@@ -4707,11 +5613,21 @@ var AzureTtsClient = class {
4707
5613
  const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };
4708
5614
  return synthesizeSpeech(ssml, config);
4709
5615
  }
4710
- async synthesizeSsml(ssml) {
5616
+ async synthesizeSsml(ssml, options = {}) {
4711
5617
  const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
4712
5618
  const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
4713
5619
  __privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
4714
- return synthesizeSsml(ssml, { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs });
5620
+ return synthesizeSsml(ssml, {
5621
+ endpoint,
5622
+ region,
5623
+ subscriptionKey,
5624
+ outputFormat: options.outputFormat ?? outputFormat,
5625
+ signal: options.signal ?? signal,
5626
+ timeoutMs: options.timeoutMs ?? timeoutMs,
5627
+ sourceNodePath: options.sourceNodePath,
5628
+ sourceTextSegments: options.sourceTextSegments,
5629
+ sourceMarkers: options.sourceMarkers
5630
+ });
4715
5631
  }
4716
5632
  async synthesizeChunks(chunks, options = {}) {
4717
5633
  const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
@@ -4720,10 +5636,13 @@ var AzureTtsClient = class {
4720
5636
  endpoint,
4721
5637
  region,
4722
5638
  subscriptionKey,
4723
- outputFormat,
4724
- signal,
4725
- timeoutMs,
4726
- onProgress: options.onProgress ?? __privateGet(this, _options).onProgress
5639
+ outputFormat: options.outputFormat ?? outputFormat,
5640
+ signal: options.signal ?? signal,
5641
+ timeoutMs: options.timeoutMs ?? timeoutMs,
5642
+ sourceNodePath: options.sourceNodePath,
5643
+ onProgress: options.onProgress ?? __privateGet(this, _options).onProgress,
5644
+ concurrency: options.concurrency ?? __privateGet(this, _options).concurrency,
5645
+ retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions
4727
5646
  });
4728
5647
  }
4729
5648
  async synthesizeSsmlSafe(ssml, options = {}) {
@@ -4733,7 +5652,11 @@ var AzureTtsClient = class {
4733
5652
  return synthesizeSsmlChunksSafe(this, chunks, {
4734
5653
  ...options,
4735
5654
  outputFormat: options.outputFormat ?? __privateGet(this, _options).outputFormat,
4736
- onProgress: options.onProgress ?? __privateGet(this, _options).onProgress
5655
+ signal: options.signal ?? __privateGet(this, _options).signal,
5656
+ timeoutMs: options.timeoutMs ?? __privateGet(this, _options).timeoutMs,
5657
+ onProgress: options.onProgress ?? __privateGet(this, _options).onProgress,
5658
+ concurrency: options.concurrency ?? __privateGet(this, _options).concurrency,
5659
+ retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions
4737
5660
  });
4738
5661
  }
4739
5662
  async synthesizeSsmlChunksSafe(chunks, options = {}) {
@@ -4830,10 +5753,15 @@ async function fetchAzureVoiceCatalog(options) {
4830
5753
  }
4831
5754
  // Annotate the CommonJS export names for ESM import in node:
4832
5755
  0 && (module.exports = {
5756
+ AudioFormatMismatchError,
4833
5757
  AzureTtsClient,
4834
5758
  AzureTtsError,
4835
5759
  AzureTtsSdkError,
4836
5760
  ChunkValidationError,
5761
+ DEFAULT_OUTPUT_FORMAT,
5762
+ MergeError,
5763
+ SynthesisCancelledError,
5764
+ SynthesisTimeoutError,
4837
5765
  UnsupportedMergeFormatError,
4838
5766
  areAzureLanguagesEquivalent,
4839
5767
  buildPartialSsml,
@@ -4846,6 +5774,8 @@ async function fetchAzureVoiceCatalog(options) {
4846
5774
  fromPlainTextToSsml,
4847
5775
  getAzureVoiceCatalogMetadata,
4848
5776
  getBuiltInVoiceCatalogMetadata,
5777
+ getSsmlSourceMap,
5778
+ inspectAudioSpecification,
4849
5779
  isValidAzureAudioDuration,
4850
5780
  mapSsmlTextNodes,
4851
5781
  mergeAudioBuffers,
@@ -4853,6 +5783,7 @@ async function fetchAzureVoiceCatalog(options) {
4853
5783
  normalizeAzureLanguage,
4854
5784
  parseSsml,
4855
5785
  resolveMergeAudioFormat,
5786
+ resolveMimeType,
4856
5787
  splitSsmlDocument,
4857
5788
  synthesizeSpeech,
4858
5789
  synthesizeSsml,
@@ -4860,6 +5791,7 @@ async function fetchAzureVoiceCatalog(options) {
4860
5791
  synthesizeSsmlChunksSafe,
4861
5792
  synthesizeSsmlSafe,
4862
5793
  validateAzureSsml,
5794
+ validateAzureSsmlChunks,
4863
5795
  validateSsml,
4864
5796
  validateSsmlStructureIntegrity
4865
5797
  });