ssml-builder-js 2.14.0 → 2.15.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
@@ -41,6 +41,10 @@ __export(src_exports, {
41
41
  AzureTtsError: () => AzureTtsError,
42
42
  AzureTtsSdkError: () => AzureTtsSdkError,
43
43
  ChunkValidationError: () => ChunkValidationError,
44
+ DEFAULT_OUTPUT_FORMAT: () => DEFAULT_OUTPUT_FORMAT,
45
+ MergeError: () => MergeError,
46
+ SynthesisCancelledError: () => SynthesisCancelledError,
47
+ SynthesisTimeoutError: () => SynthesisTimeoutError,
44
48
  UnsupportedMergeFormatError: () => UnsupportedMergeFormatError,
45
49
  areAzureLanguagesEquivalent: () => areAzureLanguagesEquivalent,
46
50
  buildPartialSsml: () => buildPartialSsml,
@@ -53,6 +57,7 @@ __export(src_exports, {
53
57
  fromPlainTextToSsml: () => fromPlainTextToSsml,
54
58
  getAzureVoiceCatalogMetadata: () => getAzureVoiceCatalogMetadata,
55
59
  getBuiltInVoiceCatalogMetadata: () => getBuiltInVoiceCatalogMetadata,
60
+ getSsmlSourceMap: () => getSsmlSourceMap,
56
61
  isValidAzureAudioDuration: () => isValidAzureAudioDuration,
57
62
  mapSsmlTextNodes: () => mapSsmlTextNodes,
58
63
  mergeAudioBuffers: () => mergeAudioBuffers,
@@ -60,6 +65,7 @@ __export(src_exports, {
60
65
  normalizeAzureLanguage: () => normalizeAzureLanguage,
61
66
  parseSsml: () => parseSsml,
62
67
  resolveMergeAudioFormat: () => resolveMergeAudioFormat,
68
+ resolveMimeType: () => resolveMimeType,
63
69
  splitSsmlDocument: () => splitSsmlDocument,
64
70
  synthesizeSpeech: () => synthesizeSpeech,
65
71
  synthesizeSsml: () => synthesizeSsml,
@@ -999,6 +1005,236 @@ function buildPartialSsml(textOrOptions, context) {
999
1005
  return serializePartialSsml(textOrOptions.text, textOrOptions);
1000
1006
  }
1001
1007
 
1008
+ // packages/ssml-core/src/textNodes.ts
1009
+ function decodeXmlText(value) {
1010
+ return value.replace(/&(?:amp|apos|gt|lt|quot);|&#(?:x[\da-f]+|\d+);/gi, (entity) => {
1011
+ if (entity === "&") return "&";
1012
+ if (entity === "'") return "'";
1013
+ if (entity === ">") return ">";
1014
+ if (entity === "&lt;") return "<";
1015
+ if (entity === "&quot;") return '"';
1016
+ const hexadecimal = entity.toLowerCase().startsWith("&#x");
1017
+ const digits = entity.slice(hexadecimal ? 3 : 2, -1);
1018
+ return String.fromCodePoint(Number.parseInt(digits, hexadecimal ? 16 : 10));
1019
+ });
1020
+ }
1021
+ function encodeXmlText(value) {
1022
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1023
+ }
1024
+ function decodeXmlAttribute(value) {
1025
+ return decodeXmlText(value);
1026
+ }
1027
+ function findTagEnd(source, start) {
1028
+ let quote = "";
1029
+ for (let index = start; index < source.length; index += 1) {
1030
+ const character = source[index];
1031
+ if (quote) {
1032
+ if (character === quote) quote = "";
1033
+ } else if (character === '"' || character === "'") {
1034
+ quote = character;
1035
+ } else if (character === ">") {
1036
+ return index;
1037
+ }
1038
+ }
1039
+ return source.length - 1;
1040
+ }
1041
+ function readTagName(tag) {
1042
+ const match = /^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/.exec(tag);
1043
+ return match?.[1];
1044
+ }
1045
+ function readTagAttributes(tag, name) {
1046
+ const attributes = {};
1047
+ const nameStart = tag.indexOf(name);
1048
+ const attributeSource = tag.slice(nameStart + name.length, tag.length - 1).replace(/\/\s*$/, "");
1049
+ const attributePattern = /([A-Za-z_][A-Za-z0-9_.:-]*)\s*=\s*(["'])([\s\S]*?)\2/g;
1050
+ for (const match of attributeSource.matchAll(attributePattern)) {
1051
+ attributes[match[1].toLowerCase()] = decodeXmlAttribute(match[3]);
1052
+ }
1053
+ return attributes;
1054
+ }
1055
+ function collectTextNodes(source) {
1056
+ const nodes = [];
1057
+ const elements = [];
1058
+ let index = 0;
1059
+ const addText = (start, end, rawText, sourceStart = start, sourceEnd = end) => {
1060
+ if (!rawText) return;
1061
+ const path = elements.map((element) => element.name);
1062
+ const parent = elements[elements.length - 1];
1063
+ nodes.push({
1064
+ context: {
1065
+ ancestorTags: path.slice(0, -1),
1066
+ parentAttributes: { ...parent?.attributes ?? {} },
1067
+ parentTag: parent?.name ?? "",
1068
+ path
1069
+ },
1070
+ decodedText: decodeXmlText(rawText),
1071
+ end,
1072
+ sourceEnd,
1073
+ sourceStart,
1074
+ start
1075
+ });
1076
+ };
1077
+ while (index < source.length) {
1078
+ if (source[index] !== "<") {
1079
+ const nextTag = source.indexOf("<", index);
1080
+ const end2 = nextTag === -1 ? source.length : nextTag;
1081
+ addText(index, end2, source.slice(index, end2));
1082
+ index = end2;
1083
+ continue;
1084
+ }
1085
+ if (source.startsWith("<!--", index)) {
1086
+ const end2 = source.indexOf("-->", index + 4);
1087
+ index = end2 === -1 ? source.length : end2 + 3;
1088
+ continue;
1089
+ }
1090
+ if (source.startsWith("<![CDATA[", index)) {
1091
+ const contentStart = index + 9;
1092
+ const end2 = source.indexOf("]]>", contentStart);
1093
+ const contentEnd = end2 === -1 ? source.length : end2;
1094
+ addText(
1095
+ contentStart,
1096
+ contentEnd,
1097
+ source.slice(contentStart, contentEnd),
1098
+ index,
1099
+ end2 === -1 ? source.length : end2 + 3
1100
+ );
1101
+ index = end2 === -1 ? source.length : end2 + 3;
1102
+ continue;
1103
+ }
1104
+ if (source.startsWith("<?", index)) {
1105
+ const end2 = source.indexOf("?>", index + 2);
1106
+ index = end2 === -1 ? source.length : end2 + 2;
1107
+ continue;
1108
+ }
1109
+ if (source.startsWith("</", index)) {
1110
+ const end2 = findTagEnd(source, index + 2);
1111
+ elements.pop();
1112
+ index = end2 + 1;
1113
+ continue;
1114
+ }
1115
+ const end = findTagEnd(source, index + 1);
1116
+ const tag = source.slice(index, end + 1);
1117
+ const name = readTagName(tag);
1118
+ if (name && !/\/\s*>$/.test(tag)) elements.push({ attributes: readTagAttributes(tag, name), name });
1119
+ index = end + 1;
1120
+ }
1121
+ return nodes;
1122
+ }
1123
+ function collectSourceMap(source) {
1124
+ const segments = [];
1125
+ const markers = [];
1126
+ const elements = [];
1127
+ let textOffset = 0;
1128
+ let index = 0;
1129
+ const textParts = [];
1130
+ const addText = (value) => {
1131
+ if (!value) return;
1132
+ const parent = elements[elements.length - 1];
1133
+ if (parent) parent.nextChildIndex += 1;
1134
+ const sourceNodePath = parent?.path ?? ["speak"];
1135
+ const start = textOffset;
1136
+ textOffset += value.length;
1137
+ textParts.push(value);
1138
+ segments.push({ text: value, range: { start, end: textOffset }, sourceNodePath: [...sourceNodePath] });
1139
+ };
1140
+ while (index < source.length) {
1141
+ if (source[index] !== "<") {
1142
+ const end2 = source.indexOf("<", index);
1143
+ const textEnd = end2 === -1 ? source.length : end2;
1144
+ addText(decodeXmlText(source.slice(index, textEnd)));
1145
+ index = textEnd;
1146
+ continue;
1147
+ }
1148
+ if (source.startsWith("<!--", index)) {
1149
+ const end2 = source.indexOf("-->", index + 4);
1150
+ index = end2 === -1 ? source.length : end2 + 3;
1151
+ continue;
1152
+ }
1153
+ if (source.startsWith("<![CDATA[", index)) {
1154
+ const contentStart = index + 9;
1155
+ const end2 = source.indexOf("]]>", contentStart);
1156
+ const contentEnd = end2 === -1 ? source.length : end2;
1157
+ addText(source.slice(contentStart, contentEnd));
1158
+ index = end2 === -1 ? source.length : end2 + 3;
1159
+ continue;
1160
+ }
1161
+ if (source.startsWith("<?", index)) {
1162
+ const end2 = source.indexOf("?>", index + 2);
1163
+ index = end2 === -1 ? source.length : end2 + 2;
1164
+ continue;
1165
+ }
1166
+ const end = findTagEnd(source, index + 1);
1167
+ const rawTag = source.slice(index, end + 1);
1168
+ if (rawTag.startsWith("</")) {
1169
+ elements.pop();
1170
+ index = end + 1;
1171
+ continue;
1172
+ }
1173
+ const name = readTagName(rawTag);
1174
+ if (!name) {
1175
+ index = end + 1;
1176
+ continue;
1177
+ }
1178
+ const parent = elements[elements.length - 1];
1179
+ const childIndex = parent?.nextChildIndex ?? 0;
1180
+ if (parent) parent.nextChildIndex += 1;
1181
+ const path = parent ? [...parent.path, `${name}[${childIndex}]`] : [name];
1182
+ const attributes = readTagAttributes(rawTag, name);
1183
+ const normalizedName = name.toLowerCase();
1184
+ if (normalizedName === "mark" || normalizedName === "bookmark") {
1185
+ const markerName = attributes[normalizedName === "mark" ? "name" : "mark"];
1186
+ if (markerName) {
1187
+ markers.push({
1188
+ kind: normalizedName,
1189
+ name: markerName,
1190
+ originalTextRange: { start: textOffset, end: textOffset },
1191
+ sourceNodePath: [...path]
1192
+ });
1193
+ }
1194
+ }
1195
+ if (!/\/\s*>$/.test(rawTag)) elements.push({ name, path, nextChildIndex: 0 });
1196
+ index = end + 1;
1197
+ }
1198
+ return { text: textParts.join(""), segments, markers };
1199
+ }
1200
+ function getSsmlSourceMap(ssml) {
1201
+ parseSsml(ssml);
1202
+ return collectSourceMap(ssml);
1203
+ }
1204
+ function extractSsmlText(ssml) {
1205
+ parseSsml(ssml);
1206
+ return collectTextNodes(ssml).map((node) => node.decodedText);
1207
+ }
1208
+ async function mapSsmlTextNodes(ssml, transform, options = {}) {
1209
+ parseSsml(ssml);
1210
+ const nodes = collectTextNodes(ssml);
1211
+ const skipTags = new Set((options.skipTags ?? ["phoneme", "say-as", "sub"]).map((tag) => tag.toLowerCase()));
1212
+ const replacements = await Promise.all(
1213
+ nodes.map(async (node) => {
1214
+ const context = {
1215
+ ancestorTags: [...node.context.ancestorTags],
1216
+ parentAttributes: { ...node.context.parentAttributes },
1217
+ parentTag: node.context.parentTag,
1218
+ path: [...node.context.path]
1219
+ };
1220
+ const shouldTransform = !skipTags.has(context.parentTag.toLowerCase()) && (options.filter?.(context) ?? true);
1221
+ if (!shouldTransform) return ssml.slice(node.sourceStart, node.sourceEnd);
1222
+ const transformed = await transform(node.decodedText, context);
1223
+ if (typeof transformed !== "string") {
1224
+ throw new TypeError("SSML text node transform must return a string");
1225
+ }
1226
+ return transformed === node.decodedText ? ssml.slice(node.sourceStart, node.sourceEnd) : encodeXmlText(transformed);
1227
+ })
1228
+ );
1229
+ let result = "";
1230
+ let cursor = 0;
1231
+ nodes.forEach((node, nodeIndex) => {
1232
+ result += ssml.slice(cursor, node.sourceStart) + replacements[nodeIndex];
1233
+ cursor = node.sourceEnd;
1234
+ });
1235
+ return result + ssml.slice(cursor);
1236
+ }
1237
+
1002
1238
  // packages/ssml-core/src/split.ts
1003
1239
  var DEFAULT_MAX_LENGTH = 1e4;
1004
1240
  function cloneElement(element, children) {
@@ -1130,7 +1366,7 @@ function findSourceNodePath(nodes, targetOffset) {
1130
1366
  });
1131
1367
  return foundPath ?? firstPath;
1132
1368
  }
1133
- function createChunk(document, nodes, chunkIndex, textStart, backgroundAudio, replicateBackgroundAudio) {
1369
+ function createChunk(document, nodes, chunkIndex, textStart, backgroundAudio, replicateBackgroundAudio, sourceMap, includeEndMarkers) {
1134
1370
  const chunkNodes = backgroundAudio && (replicateBackgroundAudio || chunkIndex === 0) ? [backgroundAudio, ...nodes] : nodes;
1135
1371
  const text = nodes.map(textFromNode).join("");
1136
1372
  const marks = [];
@@ -1146,7 +1382,23 @@ function createChunk(document, nodes, chunkIndex, textStart, backgroundAudio, re
1146
1382
  hasBackgroundAudio: chunkNodes.some(
1147
1383
  (node) => typeof node !== "string" && node.type !== "text" && node.type === "mstts:backgroundaudio"
1148
1384
  ),
1149
- sourceNodePath: findSourceNodePath(document.children ?? [], textStart)
1385
+ sourceNodePath: findSourceNodePath(document.children ?? [], textStart),
1386
+ sourceTextSegments: sourceMap.segments.filter(({ range }) => range.end > textStart && range.start < textStart + text.length).map((segment) => {
1387
+ const start = Math.max(segment.range.start, textStart);
1388
+ const end = Math.min(segment.range.end, textStart + text.length);
1389
+ return {
1390
+ text: segment.text.slice(start - segment.range.start, end - segment.range.start),
1391
+ range: { start, end },
1392
+ sourceNodePath: [...segment.sourceNodePath]
1393
+ };
1394
+ }),
1395
+ sourceMarkers: sourceMap.markers.filter(
1396
+ ({ originalTextRange }) => originalTextRange.start >= textStart && (originalTextRange.start < textStart + text.length || includeEndMarkers && originalTextRange.start === textStart + text.length)
1397
+ ).map((marker) => ({
1398
+ ...marker,
1399
+ originalTextRange: { ...marker.originalTextRange },
1400
+ sourceNodePath: [...marker.sourceNodePath]
1401
+ }))
1150
1402
  };
1151
1403
  }
1152
1404
  function splitSsmlDocument(ssml, maxLength = DEFAULT_MAX_LENGTH, options = {}) {
@@ -1156,11 +1408,12 @@ function splitSsmlDocument(ssml, maxLength = DEFAULT_MAX_LENGTH, options = {}) {
1156
1408
  throw new RangeError("maxLength must be a positive integer");
1157
1409
  }
1158
1410
  const document = parseSsml(ssml);
1411
+ const sourceMap = getSsmlSourceMap(ssml);
1159
1412
  const backgroundAudio = (document.children ?? []).find(
1160
1413
  (node) => typeof node !== "string" && node.type !== "text" && node.type === "mstts:backgroundaudio"
1161
1414
  );
1162
1415
  if (ssml.length <= resolvedMaxLength) {
1163
- return [createChunk(document, document.children ?? [], 0, 0, backgroundAudio, true)];
1416
+ return [createChunk(document, document.children ?? [], 0, 0, backgroundAudio, true, sourceMap, true)];
1164
1417
  }
1165
1418
  const contentChildren = (document.children ?? []).filter((node) => node !== backgroundAudio);
1166
1419
  const plainDocumentLength = documentWithChildren(document, []).length;
@@ -1184,7 +1437,16 @@ function splitSsmlDocument(ssml, maxLength = DEFAULT_MAX_LENGTH, options = {}) {
1184
1437
  }
1185
1438
  if (group.length > 0) chunks.push(group);
1186
1439
  if (chunks.length === 0) {
1187
- const result = createChunk(document, [], 0, 0, backgroundAudio, resolvedOptions.replicateBackgroundAudio ?? false);
1440
+ const result = createChunk(
1441
+ document,
1442
+ [],
1443
+ 0,
1444
+ 0,
1445
+ backgroundAudio,
1446
+ resolvedOptions.replicateBackgroundAudio ?? false,
1447
+ sourceMap,
1448
+ true
1449
+ );
1188
1450
  if (result.ssml.length > resolvedMaxLength) {
1189
1451
  throw new RangeError("maxLength is too small to contain the SSML document wrapper");
1190
1452
  }
@@ -1198,7 +1460,9 @@ function splitSsmlDocument(ssml, maxLength = DEFAULT_MAX_LENGTH, options = {}) {
1198
1460
  chunkIndex,
1199
1461
  textStart,
1200
1462
  backgroundAudio,
1201
- resolvedOptions.replicateBackgroundAudio ?? false
1463
+ resolvedOptions.replicateBackgroundAudio ?? false,
1464
+ sourceMap,
1465
+ chunkIndex === chunks.length - 1
1202
1466
  );
1203
1467
  textStart = result.originalTextRange.end;
1204
1468
  return result;
@@ -1221,176 +1485,27 @@ function validateSsml(xmlString) {
1221
1485
  }
1222
1486
  }
1223
1487
 
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
- }
1488
+ // packages/ssml-core/src/migration.ts
1489
+ var DEFAULT_TRANSLATION_SKIP_TAGS = ["phoneme", "say-as", "sayAs", "sub"];
1490
+ function elementName2(element) {
1491
+ switch (element.type) {
1492
+ case "custom":
1493
+ case "element":
1494
+ return element.name;
1495
+ case "expressAs":
1496
+ return "mstts:express-as";
1497
+ case "sayAs":
1498
+ return "say-as";
1499
+ case "silence":
1500
+ return "mstts:silence";
1501
+ case "viseme":
1502
+ return "mstts:viseme";
1503
+ default:
1504
+ return element.type;
1254
1505
  }
1255
- return source.length - 1;
1256
1506
  }
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;
1507
+ function addAttribute2(attributes, name, value) {
1508
+ if (value !== void 0) attributes[name] = value;
1394
1509
  }
1395
1510
  function elementAttributes(element) {
1396
1511
  const attributes = { ...element.attributes ?? {} };
@@ -1790,34 +1905,51 @@ function createAzureUrlValidatorRunner(validator, options = {}) {
1790
1905
  const inFlight = /* @__PURE__ */ new Map();
1791
1906
  const waiters = [];
1792
1907
  let active = 0;
1793
- const acquire = async () => {
1908
+ const configuredSignal = options.signal ?? new AbortController().signal;
1909
+ const acquire = async (signal) => {
1910
+ if (signal.aborted) throw new Error("URL validation was aborted.");
1794
1911
  if (active < concurrency) {
1795
1912
  active += 1;
1796
1913
  return;
1797
1914
  }
1798
- await new Promise((resolve) => waiters.push(resolve));
1915
+ await new Promise((resolve, reject) => {
1916
+ let waiter;
1917
+ const abortHandler = () => {
1918
+ const index = waiters.indexOf(waiter);
1919
+ if (index >= 0) waiters.splice(index, 1);
1920
+ signal.removeEventListener("abort", abortHandler);
1921
+ reject(new Error("URL validation was aborted."));
1922
+ };
1923
+ signal.addEventListener("abort", abortHandler, { once: true });
1924
+ waiter = () => {
1925
+ signal.removeEventListener("abort", abortHandler);
1926
+ resolve();
1927
+ };
1928
+ waiters.push(waiter);
1929
+ });
1799
1930
  active += 1;
1800
1931
  };
1801
1932
  const release = () => {
1802
1933
  active -= 1;
1803
1934
  waiters.shift()?.();
1804
1935
  };
1805
- const check = async (url, context) => {
1806
- if (options.signal?.aborted) throw new Error("URL validation was aborted.");
1807
- const cached = cache.get(url);
1936
+ const check = async (url, context, signal = configuredSignal) => {
1937
+ if (signal.aborted) throw new Error("URL validation was aborted.");
1938
+ const key = `${context.tag}:${context.attribute}:${url}`;
1939
+ const cached = cache.get(key);
1808
1940
  if (cached !== void 0) return cached;
1809
- const existing = inFlight.get(url);
1941
+ const existing = inFlight.get(key);
1810
1942
  if (existing) return existing;
1811
1943
  const promise = (async () => {
1812
- await acquire();
1944
+ await acquire(signal);
1813
1945
  try {
1814
- if (options.signal?.aborted) throw new Error("URL validation was aborted.");
1815
- const validation = Promise.resolve(validator(url, context));
1946
+ if (signal.aborted) throw new Error("URL validation was aborted.");
1947
+ const validation = Promise.resolve(validator(url, context, signal));
1816
1948
  let timer;
1817
1949
  let abortHandler;
1818
1950
  const cancellation = new Promise((_resolve, reject) => {
1819
1951
  abortHandler = () => reject(new Error("URL validation was aborted."));
1820
- options.signal?.addEventListener("abort", abortHandler, { once: true });
1952
+ signal.addEventListener("abort", abortHandler, { once: true });
1821
1953
  if (options.timeoutMs !== void 0 && options.timeoutMs > 0) {
1822
1954
  timer = setTimeout(
1823
1955
  () => reject(new Error(`URL validation timed out after ${options.timeoutMs} ms.`)),
@@ -1827,24 +1959,24 @@ function createAzureUrlValidatorRunner(validator, options = {}) {
1827
1959
  });
1828
1960
  try {
1829
1961
  const result = await (timer || abortHandler ? Promise.race([validation, cancellation]) : validation);
1830
- cache.set(url, result);
1962
+ cache.set(key, result);
1831
1963
  return result;
1832
1964
  } finally {
1833
1965
  if (timer) clearTimeout(timer);
1834
- if (abortHandler) options.signal?.removeEventListener("abort", abortHandler);
1966
+ if (abortHandler) signal.removeEventListener("abort", abortHandler);
1835
1967
  }
1836
1968
  } finally {
1837
1969
  release();
1838
1970
  }
1839
1971
  })();
1840
- inFlight.set(url, promise);
1972
+ inFlight.set(key, promise);
1841
1973
  try {
1842
1974
  return await promise;
1843
1975
  } finally {
1844
- inFlight.delete(url);
1976
+ inFlight.delete(key);
1845
1977
  }
1846
1978
  };
1847
- return (url, context) => check(url, context);
1979
+ return (url, context, signal) => check(url, context, signal ?? configuredSignal);
1848
1980
  }
1849
1981
  var ALLOWED_BREAK_STRENGTHS = /* @__PURE__ */ new Set(["none", "x-weak", "weak", "medium", "strong", "x-strong"]);
1850
1982
  var ALLOWED_SAY_AS = /* @__PURE__ */ new Set([
@@ -2500,6 +2632,7 @@ function validateAzureSsml(ssml, options = {}) {
2500
2632
  ...options.urlValidatorSignal ? { signal: options.urlValidatorSignal } : {},
2501
2633
  ...options.urlValidatorCache ? { cache: options.urlValidatorCache } : {}
2502
2634
  });
2635
+ const validationSignal = options.urlValidatorSignal ?? options.urlValidation?.signal ?? new AbortController().signal;
2503
2636
  let tokens;
2504
2637
  try {
2505
2638
  tokens = tokenizeElements(ssml);
@@ -2509,7 +2642,11 @@ function validateAzureSsml(ssml, options = {}) {
2509
2642
  const checks = tokens.flatMap(
2510
2643
  (token) => urlAttributes(token).map(async ({ attribute, value }) => {
2511
2644
  try {
2512
- const result = await boundedValidator(value, { tag: token.name, attribute });
2645
+ const result = await boundedValidator(
2646
+ value,
2647
+ { tag: token.name, attribute, ...options.sourceNodePath ? { sourceNodePath: options.sourceNodePath } : {} },
2648
+ validationSignal
2649
+ );
2513
2650
  const valid = typeof result === "boolean" ? result : result.valid;
2514
2651
  if (!valid) {
2515
2652
  const reason = typeof result === "boolean" ? void 0 : result.reason;
@@ -2555,6 +2692,7 @@ var getBuiltInVoiceCatalogMetadata = getAzureVoiceCatalogMetadata;
2555
2692
  var AzureTtsError = class extends Error {
2556
2693
  constructor(status, statusText, responseBody, requestId) {
2557
2694
  super(`Azure TTS request failed: ${status} ${statusText}`);
2695
+ this.kind = "azure-api-error";
2558
2696
  this.name = "AzureTtsError";
2559
2697
  this.status = status;
2560
2698
  this.statusText = statusText;
@@ -2570,13 +2708,44 @@ var AzureTtsSdkError = class extends AzureTtsError {
2570
2708
  this.errorDetails = errorDetails;
2571
2709
  }
2572
2710
  };
2711
+ var SynthesisCancelledError = class extends Error {
2712
+ constructor(message = "Speech synthesis was cancelled.") {
2713
+ super(message);
2714
+ this.kind = "cancelled";
2715
+ this.name = "SynthesisCancelledError";
2716
+ }
2717
+ };
2718
+ var SynthesisTimeoutError = class extends Error {
2719
+ constructor(message) {
2720
+ super(message);
2721
+ this.kind = "timeout";
2722
+ this.name = "SynthesisTimeoutError";
2723
+ }
2724
+ };
2725
+ var MergeError = class extends Error {
2726
+ constructor(message, cause) {
2727
+ super(message);
2728
+ this.kind = "merge-error";
2729
+ this.name = "MergeError";
2730
+ this.cause = cause;
2731
+ }
2732
+ };
2573
2733
  var UnsupportedMergeFormatError = class extends Error {
2574
2734
  constructor(format) {
2575
2735
  super(`Audio format "${format}" cannot be safely concatenated; container re-multiplexing is required.`);
2736
+ this.kind = "unsupported-format-error";
2576
2737
  this.name = "UnsupportedMergeFormatError";
2577
2738
  this.format = format;
2578
2739
  }
2579
2740
  };
2741
+ function toSynthesisError(error) {
2742
+ if (error instanceof AzureTtsError || error instanceof MergeError || error instanceof UnsupportedMergeFormatError || error instanceof SynthesisCancelledError || error instanceof SynthesisTimeoutError)
2743
+ return error;
2744
+ const message = error instanceof Error ? error.message : String(error);
2745
+ if (/cancel|abort/i.test(message)) return new SynthesisCancelledError(message);
2746
+ if (/tim(?:e|ed) ?out/i.test(message)) return new SynthesisTimeoutError(message);
2747
+ return createSpeechSdkError(error);
2748
+ }
2580
2749
  function createSpeechSdkError(error) {
2581
2750
  const message = error instanceof Error ? error.message : String(error);
2582
2751
  return new AzureTtsSdkError(message);
@@ -2585,829 +2754,363 @@ function createSpeechSdkError(error) {
2585
2754
  // packages/azure-tts-client/src/synthesis.ts
2586
2755
  var SpeechSDK2 = __toESM(require("microsoft-cognitiveservices-speech-sdk"));
2587
2756
 
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
2757
+ // packages/ssml-core/dist/index.mjs
2758
+ var __typeError2 = (msg) => {
2759
+ throw TypeError(msg);
2634
2760
  };
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 };
2695
- }
2696
- function writeUint32(target, offset, value) {
2697
- new DataView(target.buffer).setUint32(offset, value, true);
2698
- }
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);
2706
- }
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);
2742
- }
2743
- }
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));
2761
+ var __accessCheck2 = (obj, member, msg) => member.has(obj) || __typeError2("Cannot " + msg);
2762
+ var __privateGet2 = (obj, member, getter) => (__accessCheck2(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
2763
+ 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);
2764
+ var __privateSet2 = (obj, member, value, setter) => (__accessCheck2(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
2765
+ var SYNTHESIS_NAMESPACE2 = "http://www.w3.org/2001/10/synthesis";
2766
+ var MSTTS_NAMESPACE2 = "http://www.w3.org/2001/mstts";
2767
+ var MAX_NESTING_DEPTH2 = 1e3;
2768
+ var SSML_TAGS2 = {
2769
+ SPEAK: "speak",
2770
+ VOICE: "voice",
2771
+ PROSODY: "prosody",
2772
+ BREAK: "break",
2773
+ EXPRESS_AS: "express-as",
2774
+ EXPRESS_AS_CAMEL: "expressAs",
2775
+ MSTTS_EXPRESS_AS: "mstts:express-as",
2776
+ SAY_AS: "say-as",
2777
+ SAY_AS_CAMEL: "sayAs",
2778
+ PHONEME: "phoneme",
2779
+ EMPHASIS: "emphasis",
2780
+ AUDIO: "audio",
2781
+ SUB: "sub",
2782
+ LANG: "lang",
2783
+ MARK: "mark",
2784
+ BOOKMARK: "bookmark",
2785
+ LEXICON: "lexicon",
2786
+ PARAGRAPH: "p",
2787
+ SENTENCE: "s",
2788
+ WORD: "w",
2789
+ MSTTS_SILENCE: "mstts:silence",
2790
+ SILENCE: "silence",
2791
+ MSTTS_VISEME: "mstts:viseme",
2792
+ VISEME: "viseme",
2793
+ MSTTS_AUDIO_DURATION: "mstts:audioduration",
2794
+ MSTTS_DIALOG: "mstts:dialog",
2795
+ MSTTS_TURN: "mstts:turn",
2796
+ MSTTS_BACKGROUND_AUDIO: "mstts:backgroundaudio",
2797
+ MSTTS_TTS_EMBEDDING: "mstts:ttsembedding",
2798
+ MSTTS_EMBEDDING: "mstts:embedding",
2799
+ MSTTS_VOICE_CONVERSION: "mstts:voiceconversion"
2800
+ };
2801
+ var SSML_ATTRS2 = {
2802
+ VERSION: "version",
2803
+ XMLNS: "xmlns",
2804
+ XML_LANG: "xml:lang",
2805
+ LANG: "lang",
2806
+ MSTTS_XMLNS: "xmlns:mstts",
2807
+ NAME: "name",
2808
+ VOICE: "voice",
2809
+ SPEAKER: "speaker",
2810
+ EFFECT: "effect",
2811
+ RATE: "rate",
2812
+ PITCH: "pitch",
2813
+ VOLUME: "volume",
2814
+ CONTOUR: "contour",
2815
+ RANGE: "range",
2816
+ TIME: "time",
2817
+ STRENGTH: "strength",
2818
+ STYLE: "style",
2819
+ STYLE_DEGREE: "styledegree",
2820
+ STYLE_DEGREE_CAMEL: "styleDegree",
2821
+ STYLE_DEGREE_HYPHEN: "style-degree",
2822
+ ROLE: "role",
2823
+ INTERPRET_AS: "interpret-as",
2824
+ FORMAT: "format",
2825
+ DETAIL: "detail",
2826
+ ALPHABET: "alphabet",
2827
+ PH: "ph",
2828
+ LEVEL: "level",
2829
+ SRC: "src",
2830
+ DESC: "desc",
2831
+ CLIP_BEGIN: "clipBegin",
2832
+ CLIP_END: "clipEnd",
2833
+ SPEED: "speed",
2834
+ REPEAT_COUNT: "repeatCount",
2835
+ REPEAT_DURATION: "repeatDuration",
2836
+ SOUND_LEVEL: "soundLevel",
2837
+ ALIAS: "alias",
2838
+ MARK: "mark",
2839
+ URI: "uri",
2840
+ ID: "id",
2841
+ MODEL: "model",
2842
+ PROFILE: "profile",
2843
+ URL: "url",
2844
+ SPEAKER_PROFILE_ID: "speakerProfileId",
2845
+ TYPE: "type",
2846
+ VALUE: "value",
2847
+ FADE_IN: "fadein",
2848
+ FADE_OUT: "fadeout"
2849
+ };
2850
+ var XML_ENTITIES2 = {
2851
+ amp: "&",
2852
+ apos: "'",
2853
+ gt: ">",
2854
+ lt: "<",
2855
+ quot: '"'
2856
+ };
2857
+ function hasOwn2(object, property) {
2858
+ return Object.getOwnPropertyDescriptor(object, property) !== void 0;
2752
2859
  }
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);
2860
+ function setAttribute2(attributes, name, value) {
2861
+ Object.defineProperty(attributes, name, {
2862
+ configurable: true,
2863
+ enumerable: true,
2864
+ value,
2865
+ writable: true
2866
+ });
2758
2867
  }
2759
- function isMp3Format(format) {
2760
- return /(?:mp3|mpeg)/i.test(format);
2868
+ function decodeEntity2(entity) {
2869
+ const namedValue = hasOwn2(XML_ENTITIES2, entity) ? XML_ENTITIES2[entity] : void 0;
2870
+ if (namedValue !== void 0) {
2871
+ return namedValue;
2872
+ }
2873
+ const isHexadecimal = entity.startsWith("#x") || entity.startsWith("#X");
2874
+ const isDecimal = entity.startsWith("#");
2875
+ if (!isHexadecimal && !isDecimal) {
2876
+ throw new Error(`Unknown XML entity: &${entity};`);
2877
+ }
2878
+ const digits = entity.slice(isHexadecimal ? 2 : 1);
2879
+ const codePoint = Number.parseInt(digits, isHexadecimal ? 16 : 10);
2880
+ if (!digits || !Number.isInteger(codePoint) || codePoint < 0 || codePoint > 1114111 || codePoint >= 55296 && codePoint <= 57343 || codePoint < 32 && ![9, 10, 13].includes(codePoint)) {
2881
+ throw new Error(`Invalid XML character reference: &${entity};`);
2882
+ }
2883
+ return String.fromCodePoint(codePoint);
2761
2884
  }
2762
- function isWavFormat(format) {
2763
- return /(?:wav|wave|riff)/i.test(format);
2885
+ function decodeXmlEntities2(value) {
2886
+ let result = "";
2887
+ let start = 0;
2888
+ while (true) {
2889
+ const ampersand = value.indexOf("&", start);
2890
+ if (ampersand === -1) {
2891
+ return result + value.slice(start);
2892
+ }
2893
+ result += value.slice(start, ampersand);
2894
+ const semicolon = value.indexOf(";", ampersand + 1);
2895
+ if (semicolon === -1) {
2896
+ throw new Error("Unterminated XML entity reference");
2897
+ }
2898
+ result += decodeEntity2(value.slice(ampersand + 1, semicolon));
2899
+ start = semicolon + 1;
2900
+ }
2764
2901
  }
2765
- function isRawFormat(format) {
2766
- return /^raw(?:-|$)/i.test(format);
2902
+ function isXmlNameStart2(value) {
2903
+ return value !== void 0 && /[A-Za-z_]/.test(value);
2767
2904
  }
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;
2905
+ function isXmlNameCharacter2(value) {
2906
+ return value !== void 0 && /[A-Za-z0-9_.:-]/.test(value);
2773
2907
  }
2774
- function canMergeAudioFormat(format) {
2775
- return resolveMergeAudioFormat(format) !== void 0;
2908
+ function isXmlWhitespace2(value) {
2909
+ return value === " " || value === " " || value === "\r" || value === "\n";
2776
2910
  }
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;
2911
+ function removeStandardNamespaceAttributes2(attributes) {
2912
+ if (attributes[SSML_ATTRS2.XMLNS] === SYNTHESIS_NAMESPACE2) {
2913
+ delete attributes[SSML_ATTRS2.XMLNS];
2788
2914
  }
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;
2915
+ if (attributes[SSML_ATTRS2.MSTTS_XMLNS] === MSTTS_NAMESPACE2) {
2916
+ delete attributes[SSML_ATTRS2.MSTTS_XMLNS];
2797
2917
  }
2798
- throw new UnsupportedMergeFormatError(format);
2799
2918
  }
2800
- function closeSpeechResources(speechConfig, synthesizer) {
2801
- try {
2802
- synthesizer.close();
2803
- } catch {
2804
- }
2805
- try {
2806
- speechConfig.close();
2807
- } catch {
2919
+ var _index2;
2920
+ var XmlParser2 = class {
2921
+ constructor(source) {
2922
+ __privateAdd2(this, _index2, 0);
2923
+ this.source = source;
2808
2924
  }
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.");
2925
+ parse() {
2926
+ if (this.source.charCodeAt(0) === 65279) {
2927
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
2928
+ }
2929
+ this.skipMisc();
2930
+ if (__privateGet2(this, _index2) >= this.source.length) {
2931
+ this.fail("SSML input is empty");
2932
+ }
2933
+ if (this.source[__privateGet2(this, _index2)] !== "<") {
2934
+ this.fail("SSML input must start with an XML element");
2935
+ }
2936
+ const root = this.parseElement(0);
2937
+ this.skipMisc();
2938
+ if (__privateGet2(this, _index2) !== this.source.length) {
2939
+ this.fail("Unexpected content after the root XML element");
2940
+ }
2941
+ return root;
2814
2942
  }
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;
2943
+ parseElement(depth) {
2944
+ if (depth > MAX_NESTING_DEPTH2) {
2945
+ this.fail("XML nesting depth exceeds the supported limit");
2946
+ }
2947
+ this.expect("<");
2948
+ if (this.source[__privateGet2(this, _index2)] === "/") {
2949
+ this.fail("Unexpected closing XML element");
2950
+ }
2951
+ const name = this.parseName();
2952
+ const { attributes, selfClosing } = this.parseStartTag();
2953
+ if (selfClosing) {
2954
+ return { name, attributes, children: [] };
2955
+ }
2956
+ const children = [];
2957
+ while (__privateGet2(this, _index2) < this.source.length) {
2958
+ if (this.source.startsWith("</", __privateGet2(this, _index2))) {
2959
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + 2);
2960
+ const closingName = this.parseName();
2961
+ this.skipWhitespace();
2962
+ this.expect(">");
2963
+ if (closingName !== name) {
2964
+ this.fail(`Mismatched closing element: expected </${name}> but found </${closingName}>`);
2965
+ }
2966
+ return { name, attributes, children };
2861
2967
  }
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 });
2968
+ if (this.source.startsWith("<!--", __privateGet2(this, _index2))) {
2969
+ this.skipComment();
2970
+ continue;
2898
2971
  }
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
- );
2972
+ if (this.source.startsWith("<![CDATA[", __privateGet2(this, _index2))) {
2973
+ this.appendText(children, this.parseCdata());
2974
+ continue;
2975
+ }
2976
+ if (this.source.startsWith("<?", __privateGet2(this, _index2))) {
2977
+ this.skipProcessingInstruction();
2978
+ continue;
2979
+ }
2980
+ if (this.source.startsWith("<!", __privateGet2(this, _index2))) {
2981
+ this.fail("Unsupported XML declaration inside an element");
2982
+ }
2983
+ if (this.source[__privateGet2(this, _index2)] === "<") {
2984
+ children.push(this.parseElement(depth + 1));
2985
+ } else {
2986
+ this.appendText(children, this.parseText());
2904
2987
  }
2905
- synthesizer.speakSsmlAsync(ssml, cb, rejectWithError);
2906
- } catch (error) {
2907
- rejectWithError(error);
2908
2988
  }
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
- });
2989
+ this.fail(`Unclosed XML element: <${name}>`);
2926
2990
  }
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;
2991
+ parseStartTag() {
2992
+ const attributes = {};
2993
+ while (__privateGet2(this, _index2) < this.source.length) {
2994
+ this.skipWhitespace();
2995
+ if (this.source.startsWith("/>", __privateGet2(this, _index2))) {
2996
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + 2);
2997
+ return { attributes, selfClosing: true };
2998
+ }
2999
+ if (this.source[__privateGet2(this, _index2)] === ">") {
3000
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
3001
+ return { attributes, selfClosing: false };
3002
+ }
3003
+ const name = this.parseName();
3004
+ this.skipWhitespace();
3005
+ this.expect("=");
3006
+ this.skipWhitespace();
3007
+ const quote = this.source[__privateGet2(this, _index2)];
3008
+ if (quote !== '"' && quote !== "'") {
3009
+ this.fail(`XML attribute ${name} must use a quoted value`);
3010
+ }
3011
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
3012
+ const valueStart = __privateGet2(this, _index2);
3013
+ while (__privateGet2(this, _index2) < this.source.length && this.source[__privateGet2(this, _index2)] !== quote) {
3014
+ if (this.source[__privateGet2(this, _index2)] === "<") {
3015
+ this.fail(`Invalid "<" in XML attribute ${name}`);
3016
+ }
3017
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
3018
+ }
3019
+ if (__privateGet2(this, _index2) >= this.source.length) {
3020
+ this.fail(`Unclosed XML attribute ${name}`);
3021
+ }
3022
+ const value = decodeXmlEntities2(this.source.slice(valueStart, __privateGet2(this, _index2)));
3023
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
3024
+ if (hasOwn2(attributes, name)) {
3025
+ this.fail(`Duplicate XML attribute: ${name}`);
3026
+ }
3027
+ setAttribute2(attributes, name, value);
2969
3028
  }
3029
+ this.fail("Unclosed XML start tag");
2970
3030
  }
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;
3031
+ parseText() {
3032
+ const start = __privateGet2(this, _index2);
3033
+ while (__privateGet2(this, _index2) < this.source.length && this.source[__privateGet2(this, _index2)] !== "<") {
3034
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
3035
+ }
3036
+ const value = this.source.slice(start, __privateGet2(this, _index2));
3037
+ if (value.includes("]]>")) {
3038
+ this.fail("CDATA termination is not valid in ordinary XML text");
2985
3039
  }
3040
+ return decodeXmlEntities2(value);
2986
3041
  }
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
- });
3042
+ parseCdata() {
3043
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + "<![CDATA[".length);
3044
+ const end = this.source.indexOf("]]>", __privateGet2(this, _index2));
3045
+ if (end === -1) {
3046
+ this.fail("Unclosed XML CDATA section");
3007
3047
  }
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
- });
3048
+ const value = this.source.slice(__privateGet2(this, _index2), end);
3049
+ __privateSet2(this, _index2, end + 3);
3050
+ return value;
3051
+ }
3052
+ skipComment() {
3053
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + "<!--".length);
3054
+ const end = this.source.indexOf("-->", __privateGet2(this, _index2));
3055
+ if (end === -1) {
3056
+ this.fail("Unclosed XML comment");
3022
3057
  }
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
- });
3058
+ if (this.source.slice(__privateGet2(this, _index2), end).includes("--")) {
3059
+ this.fail("XML comments cannot contain consecutive hyphens");
3037
3060
  }
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;
3061
+ __privateSet2(this, _index2, end + 3);
3169
3062
  }
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};`);
3063
+ skipProcessingInstruction() {
3064
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + "<?".length);
3065
+ this.parseName();
3066
+ const end = this.source.indexOf("?>", __privateGet2(this, _index2));
3067
+ if (end === -1) {
3068
+ this.fail("Unclosed XML processing instruction");
3069
+ }
3070
+ __privateSet2(this, _index2, end + 2);
3174
3071
  }
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};`);
3072
+ skipMisc() {
3073
+ while (__privateGet2(this, _index2) < this.source.length) {
3074
+ this.skipWhitespace();
3075
+ if (this.source.startsWith("<!--", __privateGet2(this, _index2))) {
3076
+ this.skipComment();
3077
+ continue;
3078
+ }
3079
+ if (this.source.startsWith("<?", __privateGet2(this, _index2))) {
3080
+ this.skipProcessingInstruction();
3081
+ continue;
3082
+ }
3083
+ if (this.source.startsWith("<!DOCTYPE", __privateGet2(this, _index2))) {
3084
+ this.fail("DOCTYPE declarations are not supported");
3085
+ }
3086
+ break;
3087
+ }
3179
3088
  }
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);
3089
+ parseName() {
3090
+ const first = this.source[__privateGet2(this, _index2)];
3091
+ if (!isXmlNameStart2(first)) {
3092
+ this.fail("Invalid XML name");
3189
3093
  }
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");
3094
+ const start = __privateGet2(this, _index2);
3095
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
3096
+ while (isXmlNameCharacter2(this.source[__privateGet2(this, _index2)])) {
3097
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
3194
3098
  }
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];
3099
+ return this.source.slice(start, __privateGet2(this, _index2));
3214
3100
  }
3215
- }
3216
- var _index2;
3217
- var XmlParser2 = class {
3218
- constructor(source) {
3219
- __privateAdd2(this, _index2, 0);
3220
- this.source = source;
3101
+ appendText(children, value) {
3102
+ if (!value) {
3103
+ return;
3104
+ }
3105
+ const previous = children[children.length - 1];
3106
+ if (typeof previous === "string") {
3107
+ children[children.length - 1] = previous + value;
3108
+ } else {
3109
+ children.push(value);
3110
+ }
3221
3111
  }
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;
3239
- }
3240
- parseElement(depth) {
3241
- if (depth > MAX_NESTING_DEPTH2) {
3242
- this.fail("XML nesting depth exceeds the supported limit");
3243
- }
3244
- this.expect("<");
3245
- if (this.source[__privateGet2(this, _index2)] === "/") {
3246
- this.fail("Unexpected closing XML element");
3247
- }
3248
- const name = this.parseName();
3249
- const { attributes, selfClosing } = this.parseStartTag();
3250
- if (selfClosing) {
3251
- return { name, attributes, children: [] };
3252
- }
3253
- const children = [];
3254
- while (__privateGet2(this, _index2) < this.source.length) {
3255
- if (this.source.startsWith("</", __privateGet2(this, _index2))) {
3256
- __privateSet2(this, _index2, __privateGet2(this, _index2) + 2);
3257
- const closingName = this.parseName();
3258
- this.skipWhitespace();
3259
- this.expect(">");
3260
- if (closingName !== name) {
3261
- this.fail(`Mismatched closing element: expected </${name}> but found </${closingName}>`);
3262
- }
3263
- return { name, attributes, children };
3264
- }
3265
- if (this.source.startsWith("<!--", __privateGet2(this, _index2))) {
3266
- this.skipComment();
3267
- continue;
3268
- }
3269
- if (this.source.startsWith("<![CDATA[", __privateGet2(this, _index2))) {
3270
- this.appendText(children, this.parseCdata());
3271
- continue;
3272
- }
3273
- if (this.source.startsWith("<?", __privateGet2(this, _index2))) {
3274
- this.skipProcessingInstruction();
3275
- continue;
3276
- }
3277
- if (this.source.startsWith("<!", __privateGet2(this, _index2))) {
3278
- this.fail("Unsupported XML declaration inside an element");
3279
- }
3280
- if (this.source[__privateGet2(this, _index2)] === "<") {
3281
- children.push(this.parseElement(depth + 1));
3282
- } else {
3283
- this.appendText(children, this.parseText());
3284
- }
3285
- }
3286
- this.fail(`Unclosed XML element: <${name}>`);
3287
- }
3288
- parseStartTag() {
3289
- const attributes = {};
3290
- while (__privateGet2(this, _index2) < this.source.length) {
3291
- this.skipWhitespace();
3292
- if (this.source.startsWith("/>", __privateGet2(this, _index2))) {
3293
- __privateSet2(this, _index2, __privateGet2(this, _index2) + 2);
3294
- return { attributes, selfClosing: true };
3295
- }
3296
- if (this.source[__privateGet2(this, _index2)] === ">") {
3297
- __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
3298
- return { attributes, selfClosing: false };
3299
- }
3300
- const name = this.parseName();
3301
- this.skipWhitespace();
3302
- this.expect("=");
3303
- this.skipWhitespace();
3304
- const quote = this.source[__privateGet2(this, _index2)];
3305
- if (quote !== '"' && quote !== "'") {
3306
- this.fail(`XML attribute ${name} must use a quoted value`);
3307
- }
3308
- __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
3309
- const valueStart = __privateGet2(this, _index2);
3310
- while (__privateGet2(this, _index2) < this.source.length && this.source[__privateGet2(this, _index2)] !== quote) {
3311
- if (this.source[__privateGet2(this, _index2)] === "<") {
3312
- this.fail(`Invalid "<" in XML attribute ${name}`);
3313
- }
3314
- __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
3315
- }
3316
- if (__privateGet2(this, _index2) >= this.source.length) {
3317
- this.fail(`Unclosed XML attribute ${name}`);
3318
- }
3319
- const value = decodeXmlEntities2(this.source.slice(valueStart, __privateGet2(this, _index2)));
3320
- __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
3321
- if (hasOwn2(attributes, name)) {
3322
- this.fail(`Duplicate XML attribute: ${name}`);
3323
- }
3324
- setAttribute2(attributes, name, value);
3325
- }
3326
- this.fail("Unclosed XML start tag");
3327
- }
3328
- parseText() {
3329
- const start = __privateGet2(this, _index2);
3330
- while (__privateGet2(this, _index2) < this.source.length && this.source[__privateGet2(this, _index2)] !== "<") {
3331
- __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
3332
- }
3333
- const value = this.source.slice(start, __privateGet2(this, _index2));
3334
- if (value.includes("]]>")) {
3335
- this.fail("CDATA termination is not valid in ordinary XML text");
3336
- }
3337
- return decodeXmlEntities2(value);
3338
- }
3339
- parseCdata() {
3340
- __privateSet2(this, _index2, __privateGet2(this, _index2) + "<![CDATA[".length);
3341
- const end = this.source.indexOf("]]>", __privateGet2(this, _index2));
3342
- if (end === -1) {
3343
- this.fail("Unclosed XML CDATA section");
3344
- }
3345
- const value = this.source.slice(__privateGet2(this, _index2), end);
3346
- __privateSet2(this, _index2, end + 3);
3347
- return value;
3348
- }
3349
- skipComment() {
3350
- __privateSet2(this, _index2, __privateGet2(this, _index2) + "<!--".length);
3351
- const end = this.source.indexOf("-->", __privateGet2(this, _index2));
3352
- if (end === -1) {
3353
- this.fail("Unclosed XML comment");
3354
- }
3355
- if (this.source.slice(__privateGet2(this, _index2), end).includes("--")) {
3356
- this.fail("XML comments cannot contain consecutive hyphens");
3357
- }
3358
- __privateSet2(this, _index2, end + 3);
3359
- }
3360
- skipProcessingInstruction() {
3361
- __privateSet2(this, _index2, __privateGet2(this, _index2) + "<?".length);
3362
- this.parseName();
3363
- const end = this.source.indexOf("?>", __privateGet2(this, _index2));
3364
- if (end === -1) {
3365
- this.fail("Unclosed XML processing instruction");
3366
- }
3367
- __privateSet2(this, _index2, end + 2);
3368
- }
3369
- skipMisc() {
3370
- while (__privateGet2(this, _index2) < this.source.length) {
3371
- this.skipWhitespace();
3372
- if (this.source.startsWith("<!--", __privateGet2(this, _index2))) {
3373
- this.skipComment();
3374
- continue;
3375
- }
3376
- if (this.source.startsWith("<?", __privateGet2(this, _index2))) {
3377
- this.skipProcessingInstruction();
3378
- continue;
3379
- }
3380
- if (this.source.startsWith("<!DOCTYPE", __privateGet2(this, _index2))) {
3381
- this.fail("DOCTYPE declarations are not supported");
3382
- }
3383
- break;
3384
- }
3385
- }
3386
- parseName() {
3387
- const first = this.source[__privateGet2(this, _index2)];
3388
- if (!isXmlNameStart2(first)) {
3389
- this.fail("Invalid XML name");
3390
- }
3391
- const start = __privateGet2(this, _index2);
3392
- __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
3393
- while (isXmlNameCharacter2(this.source[__privateGet2(this, _index2)])) {
3394
- __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
3395
- }
3396
- return this.source.slice(start, __privateGet2(this, _index2));
3397
- }
3398
- appendText(children, value) {
3399
- if (!value) {
3400
- return;
3401
- }
3402
- const previous = children[children.length - 1];
3403
- if (typeof previous === "string") {
3404
- children[children.length - 1] = previous + value;
3405
- } else {
3406
- children.push(value);
3407
- }
3408
- }
3409
- skipWhitespace() {
3410
- while (isXmlWhitespace2(this.source[__privateGet2(this, _index2)])) {
3112
+ skipWhitespace() {
3113
+ while (isXmlWhitespace2(this.source[__privateGet2(this, _index2)])) {
3411
3114
  __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
3412
3115
  }
3413
3116
  }
@@ -3702,75 +3405,199 @@ function parseSsml2(xmlString) {
3702
3405
  }
3703
3406
  return document;
3704
3407
  }
3705
- var AZURE_VOICE_DEFINITIONS2 = [
3706
- { name: "de-DE-ConradNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
3707
- { name: "de-DE-KatjaNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
3708
- { name: "en-US-AndrewNeural", locale: "en-US", styles: ["empathetic", "relieved"] },
3709
- {
3710
- name: "en-US-GuyNeural",
3711
- locale: "en-US",
3712
- styles: [
3713
- "angry",
3714
- "cheerful",
3715
- "excited",
3716
- "friendly",
3717
- "hopeful",
3718
- "newscast",
3719
- "sad",
3720
- "shouting",
3721
- "terrified",
3722
- "unfriendly",
3723
- "whispering"
3724
- ]
3725
- },
3726
- {
3727
- name: "en-US-JennyMultilingualNeural",
3728
- locale: "en-US",
3729
- styles: [
3730
- "cheerful",
3731
- "empathetic",
3732
- "excited",
3733
- "friendly",
3734
- "hopeful",
3735
- "sad",
3736
- "shouting",
3737
- "terrified",
3738
- "unfriendly",
3739
- "whispering"
3740
- ]
3741
- },
3742
- {
3743
- name: "en-US-JennyNeural",
3744
- locale: "en-US",
3745
- styles: [
3746
- "assistant",
3747
- "chat",
3748
- "customerservice",
3749
- "newscast",
3750
- "cheerful",
3751
- "empathetic",
3752
- "excited",
3753
- "friendly",
3754
- "hopeful",
3755
- "sad",
3756
- "shouting",
3757
- "terrified",
3758
- "unfriendly",
3759
- "whispering"
3760
- ]
3761
- },
3762
- { name: "es-ES-ElviraNeural", locale: "es-ES" },
3763
- { name: "fil-PH-AngeloNeural", locale: "fil-PH" },
3764
- { name: "fil-PH-Angelo:DragonHDLatestNeural", locale: "fil-PH" },
3765
- { name: "fil-PH-BlessicaNeural", locale: "fil-PH" },
3766
- { name: "fil-PH-Blessica:DragonHDLatestNeural", locale: "fil-PH" },
3767
- { name: "fr-FR-DeniseNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
3768
- { name: "fr-FR-HenriNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
3769
- { name: "id-ID-GadisNeural", locale: "id-ID" },
3770
- { name: "it-IT-ElsaNeural", locale: "it-IT", styles: ["cheerful", "sad"] },
3771
- { name: "ja-JP-KeitaNeural", locale: "ja-JP", styles: ["chat"] },
3772
- { name: "ja-JP-MayuNeural", locale: "ja-JP", styles: ["calm", "cheerful", "sad"] },
3773
- { name: "ja-JP-NanamiNeural", locale: "ja-JP", styles: ["chat", "customerservice", "cheerful", "whispering", "sad"] },
3408
+ function decodeXmlText2(value) {
3409
+ return value.replace(/&(?:amp|apos|gt|lt|quot);|&#(?:x[\da-f]+|\d+);/gi, (entity) => {
3410
+ if (entity === "&amp;") return "&";
3411
+ if (entity === "&apos;") return "'";
3412
+ if (entity === "&gt;") return ">";
3413
+ if (entity === "&lt;") return "<";
3414
+ if (entity === "&quot;") return '"';
3415
+ const hexadecimal = entity.toLowerCase().startsWith("&#x");
3416
+ const digits = entity.slice(hexadecimal ? 3 : 2, -1);
3417
+ return String.fromCodePoint(Number.parseInt(digits, hexadecimal ? 16 : 10));
3418
+ });
3419
+ }
3420
+ function decodeXmlAttribute2(value) {
3421
+ return decodeXmlText2(value);
3422
+ }
3423
+ function findTagEnd3(source, start) {
3424
+ let quote = "";
3425
+ for (let index = start; index < source.length; index += 1) {
3426
+ const character = source[index];
3427
+ if (quote) {
3428
+ if (character === quote) quote = "";
3429
+ } else if (character === '"' || character === "'") {
3430
+ quote = character;
3431
+ } else if (character === ">") {
3432
+ return index;
3433
+ }
3434
+ }
3435
+ return source.length - 1;
3436
+ }
3437
+ function readTagName2(tag) {
3438
+ const match = /^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/.exec(tag);
3439
+ return match?.[1];
3440
+ }
3441
+ function readTagAttributes2(tag, name) {
3442
+ const attributes = {};
3443
+ const nameStart = tag.indexOf(name);
3444
+ const attributeSource = tag.slice(nameStart + name.length, tag.length - 1).replace(/\/\s*$/, "");
3445
+ const attributePattern = /([A-Za-z_][A-Za-z0-9_.:-]*)\s*=\s*(["'])([\s\S]*?)\2/g;
3446
+ for (const match of attributeSource.matchAll(attributePattern)) {
3447
+ attributes[match[1].toLowerCase()] = decodeXmlAttribute2(match[3]);
3448
+ }
3449
+ return attributes;
3450
+ }
3451
+ function collectSourceMap2(source) {
3452
+ const segments = [];
3453
+ const markers = [];
3454
+ const elements = [];
3455
+ let textOffset = 0;
3456
+ let index = 0;
3457
+ const textParts = [];
3458
+ const addText = (value) => {
3459
+ if (!value) return;
3460
+ const parent = elements[elements.length - 1];
3461
+ if (parent) parent.nextChildIndex += 1;
3462
+ const sourceNodePath = parent?.path ?? ["speak"];
3463
+ const start = textOffset;
3464
+ textOffset += value.length;
3465
+ textParts.push(value);
3466
+ segments.push({ text: value, range: { start, end: textOffset }, sourceNodePath: [...sourceNodePath] });
3467
+ };
3468
+ while (index < source.length) {
3469
+ if (source[index] !== "<") {
3470
+ const end2 = source.indexOf("<", index);
3471
+ const textEnd = end2 === -1 ? source.length : end2;
3472
+ addText(decodeXmlText2(source.slice(index, textEnd)));
3473
+ index = textEnd;
3474
+ continue;
3475
+ }
3476
+ if (source.startsWith("<!--", index)) {
3477
+ const end2 = source.indexOf("-->", index + 4);
3478
+ index = end2 === -1 ? source.length : end2 + 3;
3479
+ continue;
3480
+ }
3481
+ if (source.startsWith("<![CDATA[", index)) {
3482
+ const contentStart = index + 9;
3483
+ const end2 = source.indexOf("]]>", contentStart);
3484
+ const contentEnd = end2 === -1 ? source.length : end2;
3485
+ addText(source.slice(contentStart, contentEnd));
3486
+ index = end2 === -1 ? source.length : end2 + 3;
3487
+ continue;
3488
+ }
3489
+ if (source.startsWith("<?", index)) {
3490
+ const end2 = source.indexOf("?>", index + 2);
3491
+ index = end2 === -1 ? source.length : end2 + 2;
3492
+ continue;
3493
+ }
3494
+ const end = findTagEnd3(source, index + 1);
3495
+ const rawTag = source.slice(index, end + 1);
3496
+ if (rawTag.startsWith("</")) {
3497
+ elements.pop();
3498
+ index = end + 1;
3499
+ continue;
3500
+ }
3501
+ const name = readTagName2(rawTag);
3502
+ if (!name) {
3503
+ index = end + 1;
3504
+ continue;
3505
+ }
3506
+ const parent = elements[elements.length - 1];
3507
+ const childIndex = parent?.nextChildIndex ?? 0;
3508
+ if (parent) parent.nextChildIndex += 1;
3509
+ const path = parent ? [...parent.path, `${name}[${childIndex}]`] : [name];
3510
+ const attributes = readTagAttributes2(rawTag, name);
3511
+ const normalizedName = name.toLowerCase();
3512
+ if (normalizedName === "mark" || normalizedName === "bookmark") {
3513
+ const markerName = attributes[normalizedName === "mark" ? "name" : "mark"];
3514
+ if (markerName) {
3515
+ markers.push({
3516
+ kind: normalizedName,
3517
+ name: markerName,
3518
+ originalTextRange: { start: textOffset, end: textOffset },
3519
+ sourceNodePath: [...path]
3520
+ });
3521
+ }
3522
+ }
3523
+ if (!/\/\s*>$/.test(rawTag)) elements.push({ name, path, nextChildIndex: 0 });
3524
+ index = end + 1;
3525
+ }
3526
+ return { text: textParts.join(""), segments, markers };
3527
+ }
3528
+ function getSsmlSourceMap2(ssml) {
3529
+ parseSsml2(ssml);
3530
+ return collectSourceMap2(ssml);
3531
+ }
3532
+ var AZURE_VOICE_DEFINITIONS2 = [
3533
+ { name: "de-DE-ConradNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
3534
+ { name: "de-DE-KatjaNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
3535
+ { name: "en-US-AndrewNeural", locale: "en-US", styles: ["empathetic", "relieved"] },
3536
+ {
3537
+ name: "en-US-GuyNeural",
3538
+ locale: "en-US",
3539
+ styles: [
3540
+ "angry",
3541
+ "cheerful",
3542
+ "excited",
3543
+ "friendly",
3544
+ "hopeful",
3545
+ "newscast",
3546
+ "sad",
3547
+ "shouting",
3548
+ "terrified",
3549
+ "unfriendly",
3550
+ "whispering"
3551
+ ]
3552
+ },
3553
+ {
3554
+ name: "en-US-JennyMultilingualNeural",
3555
+ locale: "en-US",
3556
+ styles: [
3557
+ "cheerful",
3558
+ "empathetic",
3559
+ "excited",
3560
+ "friendly",
3561
+ "hopeful",
3562
+ "sad",
3563
+ "shouting",
3564
+ "terrified",
3565
+ "unfriendly",
3566
+ "whispering"
3567
+ ]
3568
+ },
3569
+ {
3570
+ name: "en-US-JennyNeural",
3571
+ locale: "en-US",
3572
+ styles: [
3573
+ "assistant",
3574
+ "chat",
3575
+ "customerservice",
3576
+ "newscast",
3577
+ "cheerful",
3578
+ "empathetic",
3579
+ "excited",
3580
+ "friendly",
3581
+ "hopeful",
3582
+ "sad",
3583
+ "shouting",
3584
+ "terrified",
3585
+ "unfriendly",
3586
+ "whispering"
3587
+ ]
3588
+ },
3589
+ { name: "es-ES-ElviraNeural", locale: "es-ES" },
3590
+ { name: "fil-PH-AngeloNeural", locale: "fil-PH" },
3591
+ { name: "fil-PH-Angelo:DragonHDLatestNeural", locale: "fil-PH" },
3592
+ { name: "fil-PH-BlessicaNeural", locale: "fil-PH" },
3593
+ { name: "fil-PH-Blessica:DragonHDLatestNeural", locale: "fil-PH" },
3594
+ { name: "fr-FR-DeniseNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
3595
+ { name: "fr-FR-HenriNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
3596
+ { name: "id-ID-GadisNeural", locale: "id-ID" },
3597
+ { name: "it-IT-ElsaNeural", locale: "it-IT", styles: ["cheerful", "sad"] },
3598
+ { name: "ja-JP-KeitaNeural", locale: "ja-JP", styles: ["chat"] },
3599
+ { name: "ja-JP-MayuNeural", locale: "ja-JP", styles: ["calm", "cheerful", "sad"] },
3600
+ { name: "ja-JP-NanamiNeural", locale: "ja-JP", styles: ["chat", "customerservice", "cheerful", "whispering", "sad"] },
3774
3601
  { name: "ko-KR-SunHiNeural", locale: "ko-KR", styles: ["cheerful", "sad"] },
3775
3602
  { name: "ms-MY-YasminNeural", locale: "ms-MY" },
3776
3603
  { name: "pt-BR-FranciscaNeural", locale: "pt-BR", styles: ["calm"] },
@@ -3829,34 +3656,51 @@ function createAzureUrlValidatorRunner2(validator, options = {}) {
3829
3656
  const inFlight = /* @__PURE__ */ new Map();
3830
3657
  const waiters = [];
3831
3658
  let active = 0;
3832
- const acquire = async () => {
3659
+ const configuredSignal = options.signal ?? new AbortController().signal;
3660
+ const acquire = async (signal) => {
3661
+ if (signal.aborted) throw new Error("URL validation was aborted.");
3833
3662
  if (active < concurrency) {
3834
3663
  active += 1;
3835
3664
  return;
3836
3665
  }
3837
- await new Promise((resolve) => waiters.push(resolve));
3666
+ await new Promise((resolve, reject) => {
3667
+ let waiter;
3668
+ const abortHandler = () => {
3669
+ const index = waiters.indexOf(waiter);
3670
+ if (index >= 0) waiters.splice(index, 1);
3671
+ signal.removeEventListener("abort", abortHandler);
3672
+ reject(new Error("URL validation was aborted."));
3673
+ };
3674
+ signal.addEventListener("abort", abortHandler, { once: true });
3675
+ waiter = () => {
3676
+ signal.removeEventListener("abort", abortHandler);
3677
+ resolve();
3678
+ };
3679
+ waiters.push(waiter);
3680
+ });
3838
3681
  active += 1;
3839
3682
  };
3840
3683
  const release = () => {
3841
3684
  active -= 1;
3842
3685
  waiters.shift()?.();
3843
3686
  };
3844
- const check = async (url, context) => {
3845
- if (options.signal?.aborted) throw new Error("URL validation was aborted.");
3846
- const cached = cache.get(url);
3687
+ const check = async (url, context, signal = configuredSignal) => {
3688
+ if (signal.aborted) throw new Error("URL validation was aborted.");
3689
+ const key = `${context.tag}:${context.attribute}:${url}`;
3690
+ const cached = cache.get(key);
3847
3691
  if (cached !== void 0) return cached;
3848
- const existing = inFlight.get(url);
3692
+ const existing = inFlight.get(key);
3849
3693
  if (existing) return existing;
3850
3694
  const promise = (async () => {
3851
- await acquire();
3695
+ await acquire(signal);
3852
3696
  try {
3853
- if (options.signal?.aborted) throw new Error("URL validation was aborted.");
3854
- const validation = Promise.resolve(validator(url, context));
3697
+ if (signal.aborted) throw new Error("URL validation was aborted.");
3698
+ const validation = Promise.resolve(validator(url, context, signal));
3855
3699
  let timer;
3856
3700
  let abortHandler;
3857
3701
  const cancellation = new Promise((_resolve, reject) => {
3858
3702
  abortHandler = () => reject(new Error("URL validation was aborted."));
3859
- options.signal?.addEventListener("abort", abortHandler, { once: true });
3703
+ signal.addEventListener("abort", abortHandler, { once: true });
3860
3704
  if (options.timeoutMs !== void 0 && options.timeoutMs > 0) {
3861
3705
  timer = setTimeout(
3862
3706
  () => reject(new Error(`URL validation timed out after ${options.timeoutMs} ms.`)),
@@ -3866,24 +3710,24 @@ function createAzureUrlValidatorRunner2(validator, options = {}) {
3866
3710
  });
3867
3711
  try {
3868
3712
  const result = await (timer || abortHandler ? Promise.race([validation, cancellation]) : validation);
3869
- cache.set(url, result);
3713
+ cache.set(key, result);
3870
3714
  return result;
3871
3715
  } finally {
3872
3716
  if (timer) clearTimeout(timer);
3873
- if (abortHandler) options.signal?.removeEventListener("abort", abortHandler);
3717
+ if (abortHandler) signal.removeEventListener("abort", abortHandler);
3874
3718
  }
3875
3719
  } finally {
3876
3720
  release();
3877
3721
  }
3878
3722
  })();
3879
- inFlight.set(url, promise);
3723
+ inFlight.set(key, promise);
3880
3724
  try {
3881
3725
  return await promise;
3882
3726
  } finally {
3883
- inFlight.delete(url);
3727
+ inFlight.delete(key);
3884
3728
  }
3885
3729
  };
3886
- return (url, context) => check(url, context);
3730
+ return (url, context, signal) => check(url, context, signal ?? configuredSignal);
3887
3731
  }
3888
3732
  var ALLOWED_BREAK_STRENGTHS2 = /* @__PURE__ */ new Set(["none", "x-weak", "weak", "medium", "strong", "x-strong"]);
3889
3733
  var ALLOWED_SAY_AS2 = /* @__PURE__ */ new Set([
@@ -4374,236 +4218,815 @@ function validateElement2(token, source, diagnostics, voiceName, options, voiceC
4374
4218
  `<mstts:backgroundaudio ${attribute}> must be between 0 and 10000 milliseconds, for example "500ms" or "10s".`
4375
4219
  );
4376
4220
  }
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.");
4221
+ if (token.parentName?.toLowerCase() !== "speak" || token.childElementIndex !== 0)
4222
+ addDiagnostic2(
4223
+ diagnostics,
4224
+ source,
4225
+ token.start,
4226
+ "<mstts:backgroundaudio> must be the first element directly under <speak>."
4227
+ );
4228
+ if (!token.selfClosing)
4229
+ addDiagnostic2(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
4230
+ }
4231
+ }
4232
+ function validateAzureSsmlStatic2(ssml, options = {}) {
4233
+ const diagnostics = [];
4234
+ if (typeof ssml !== "string") {
4235
+ return [
4236
+ {
4237
+ line: 1,
4238
+ column: 1,
4239
+ message: "SSML input must be a string",
4240
+ severity: "error",
4241
+ source: "ssml-static-validator"
4242
+ }
4243
+ ];
4244
+ }
4245
+ const maxLength = options.maxLength ?? 1e4;
4246
+ if (ssml.length > maxLength)
4247
+ addDiagnostic2(diagnostics, ssml, maxLength, `SSML exceeds the maximum length of ${maxLength} characters.`);
4248
+ if (options.maxXmlDepth !== void 0 && (!Number.isInteger(options.maxXmlDepth) || options.maxXmlDepth <= 0)) {
4249
+ addDiagnostic2(diagnostics, ssml, 0, "maxXmlDepth must be a positive integer.");
4250
+ }
4251
+ try {
4252
+ parseSsml2(ssml);
4253
+ } catch (error) {
4254
+ const message = error instanceof Error ? error.message.replace(/ at position \d+$/, "") : String(error);
4255
+ const match = / at position (\d+)$/.exec(error instanceof Error ? error.message : "");
4256
+ addDiagnostic2(diagnostics, ssml, match ? Number(match[1]) : 0, message);
4257
+ return diagnostics;
4258
+ }
4259
+ const tokens = tokenizeElements2(ssml);
4260
+ if (options.maxXmlDepth !== void 0) {
4261
+ for (const token of tokens) {
4262
+ if (token.depth > options.maxXmlDepth) {
4263
+ addDiagnostic2(
4264
+ diagnostics,
4265
+ ssml,
4266
+ token.start,
4267
+ `XML nesting depth ${token.depth} exceeds the configured maximum of ${options.maxXmlDepth}.`
4268
+ );
4269
+ }
4270
+ }
4271
+ }
4272
+ const speak = tokens.find((token) => token.name.toLowerCase() === "speak");
4273
+ const voices = tokens.filter((token) => token.name.toLowerCase() === "voice");
4274
+ const backgroundAudioTokens = tokens.filter((token) => token.name.toLowerCase() === "mstts:backgroundaudio");
4275
+ for (const [index, token] of backgroundAudioTokens.entries()) {
4276
+ if (index > 0)
4277
+ addDiagnostic2(
4278
+ diagnostics,
4279
+ ssml,
4280
+ token.start,
4281
+ "An SSML document can contain at most one <mstts:backgroundaudio> element."
4282
+ );
4283
+ }
4284
+ if (!speak || voices.length === 0)
4285
+ addDiagnostic2(
4286
+ diagnostics,
4287
+ ssml,
4288
+ speak?.start ?? 0,
4289
+ "Azure SSML requires at least one <voice> element under <speak>."
4290
+ );
4291
+ const voiceName = voices[0] ? attr2(voices[0], "name") : void 0;
4292
+ const voiceCatalog = normalizeVoiceCatalog2(options);
4293
+ const normalizeLanguage = createLanguageNormalizer2(options);
4294
+ const policySeverity = diagnosticSeverity2(options.unknownVoicePolicy ?? "warn");
4295
+ const voicesToValidate = options.validateNestedVoices === false ? voices.slice(0, 1) : voices;
4296
+ for (const token of voicesToValidate) {
4297
+ const name = attr2(token, "name")?.trim();
4298
+ const language = attr2(token, "xml:lang")?.trim() || (speak ? attr2(speak, "xml:lang")?.trim() : void 0);
4299
+ const definition = name ? voiceCatalog.get(name.toLowerCase()) : void 0;
4300
+ if (name && definition?.status === "preview")
4301
+ addDiagnostic2(
4302
+ diagnostics,
4303
+ ssml,
4304
+ token.start,
4305
+ `Voice "${name}" is an Azure Speech preview voice and may change or require preview access.`,
4306
+ "warning",
4307
+ "azure-preview-voice"
4308
+ );
4309
+ if (name && definition?.status === "deprecated")
4310
+ addDiagnostic2(
4311
+ diagnostics,
4312
+ ssml,
4313
+ token.start,
4314
+ `Voice "${name}" is deprecated by Azure Speech; migrate to a supported voice.`,
4315
+ "info",
4316
+ "azure-deprecated-voice"
4317
+ );
4318
+ if (name && !definition && policySeverity)
4319
+ addDiagnostic2(
4320
+ diagnostics,
4321
+ ssml,
4322
+ token.start,
4323
+ `Unknown voice "${name}" is not registered in the voice catalog.`,
4324
+ policySeverity,
4325
+ "azure-unknown-voice"
4326
+ );
4327
+ if (name && language && definitionMatchesLanguage2(definition, name, language, normalizeLanguage) === false)
4328
+ addDiagnostic2(
4329
+ diagnostics,
4330
+ ssml,
4331
+ token.start,
4332
+ `Voice "${name}" does not match language "${language}"; the voice name prefix indicates a different language or region.`,
4333
+ "warning",
4334
+ "azure-locale-mismatch"
4335
+ );
4336
+ }
4337
+ for (const token of tokens) {
4338
+ const tokenName = token.name.toLowerCase();
4339
+ const tokenVoiceName = tokenName === "voice" ? attr2(token, "name")?.trim() : tokenName === "mstts:turn" ? attr2(token, "voice")?.trim() || token.parentVoiceName : options.validateNestedVoices === false ? voiceName : token.parentVoiceName;
4340
+ validateElement2(token, ssml, diagnostics, tokenVoiceName, options, voiceCatalog);
4341
+ const definition = tokenVoiceName ? voiceCatalog.get(tokenVoiceName.toLowerCase()) : void 0;
4342
+ validateVoiceFeatureMatrix2(token, ssml, diagnostics, tokenVoiceName, definition);
4343
+ if (tokenName === "voice" && options.model && definition?.models && !definition.models.some((model) => model.toLowerCase() === options.model?.toLowerCase())) {
4344
+ addDiagnostic2(
4345
+ diagnostics,
4346
+ ssml,
4347
+ token.start,
4348
+ `Voice "${tokenVoiceName}" does not support model "${options.model}" according to the configured feature matrix.`,
4349
+ "error",
4350
+ "azure-unsupported-model-for-voice"
4351
+ );
4352
+ }
4353
+ }
4354
+ return diagnostics;
4355
+ }
4356
+ function urlAttributes2(token) {
4357
+ const tag = canonicalTagName2(token.name);
4358
+ const attributes = tag === "audio" || tag === "mstts:backgroundaudio" ? ["src"] : tag === "lexicon" ? ["uri"] : tag === "mstts:voiceconversion" ? ["url"] : [];
4359
+ return attributes.flatMap((attribute) => {
4360
+ const value = attr2(token, attribute);
4361
+ return value === void 0 ? [] : [{ attribute, value }];
4362
+ });
4363
+ }
4364
+ function validateAzureSsml2(ssml, options = {}) {
4365
+ const diagnostics = validateAzureSsmlStatic2(ssml, options);
4366
+ const validator = options.urlValidator ?? options.customUrlValidator;
4367
+ if (!validator || typeof ssml !== "string") return diagnostics;
4368
+ const runnerOptions = options.urlValidation ?? {};
4369
+ const boundedValidator = createAzureUrlValidatorRunner2(validator, {
4370
+ ...runnerOptions,
4371
+ ...options.urlValidatorConcurrency !== void 0 ? { concurrency: options.urlValidatorConcurrency } : {},
4372
+ ...options.urlValidatorTimeoutMs !== void 0 ? { timeoutMs: options.urlValidatorTimeoutMs } : {},
4373
+ ...options.urlValidatorSignal ? { signal: options.urlValidatorSignal } : {},
4374
+ ...options.urlValidatorCache ? { cache: options.urlValidatorCache } : {}
4375
+ });
4376
+ const validationSignal = options.urlValidatorSignal ?? options.urlValidation?.signal ?? new AbortController().signal;
4377
+ let tokens;
4378
+ try {
4379
+ tokens = tokenizeElements2(ssml);
4380
+ } catch {
4381
+ return diagnostics;
4382
+ }
4383
+ const checks = tokens.flatMap(
4384
+ (token) => urlAttributes2(token).map(async ({ attribute, value }) => {
4385
+ try {
4386
+ const result = await boundedValidator(
4387
+ value,
4388
+ { tag: token.name, attribute, ...options.sourceNodePath ? { sourceNodePath: options.sourceNodePath } : {} },
4389
+ validationSignal
4390
+ );
4391
+ const valid = typeof result === "boolean" ? result : result.valid;
4392
+ if (!valid) {
4393
+ const reason = typeof result === "boolean" ? void 0 : result.reason;
4394
+ addDiagnostic2(
4395
+ diagnostics,
4396
+ ssml,
4397
+ token.start,
4398
+ `<${token.name} ${attribute}> was rejected by the custom URL validator${reason ? `: ${reason}` : "."}`
4399
+ );
4400
+ }
4401
+ } catch (error) {
4402
+ const reason = error instanceof Error ? error.message : String(error);
4403
+ addDiagnostic2(
4404
+ diagnostics,
4405
+ ssml,
4406
+ token.start,
4407
+ `<${token.name} ${attribute}> could not be validated by the custom URL validator: ${reason}`
4408
+ );
4409
+ }
4410
+ })
4411
+ );
4412
+ return Promise.all(checks).then(() => diagnostics);
4413
+ }
4414
+ var AZURE_VOICE_CATALOG_METADATA2 = {
4415
+ apiVersion: "2025-10-01",
4416
+ generatedAt: "2026-08-28T00:00:00.000Z",
4417
+ regions: [],
4418
+ voiceCount: AZURE_VOICE_DEFINITIONS2.length
4419
+ };
4420
+
4421
+ // packages/azure-tts-client/src/outputFormats.ts
4422
+ var SpeechSDK = __toESM(require("microsoft-cognitiveservices-speech-sdk"));
4423
+ var DEFAULT_OUTPUT_FORMAT = "audio-16khz-128kbitrate-mono-mp3";
4424
+ var OUTPUT_FORMATS = {
4425
+ "raw-8khz-8bit-mono-mulaw": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz8BitMonoMULaw,
4426
+ "riff-16khz-16kbps-mono-siren": SpeechSDK.SpeechSynthesisOutputFormat.Riff16Khz16KbpsMonoSiren,
4427
+ "audio-16khz-16kbps-mono-siren": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz16KbpsMonoSiren,
4428
+ "audio-16khz-32kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3,
4429
+ "audio-16khz-128kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz128KBitRateMonoMp3,
4430
+ "audio-16khz-64kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz64KBitRateMonoMp3,
4431
+ "audio-24khz-48kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz48KBitRateMonoMp3,
4432
+ "audio-24khz-96kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz96KBitRateMonoMp3,
4433
+ "audio-24khz-160kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz160KBitRateMonoMp3,
4434
+ "raw-16khz-16bit-mono-truesilk": SpeechSDK.SpeechSynthesisOutputFormat.Raw16Khz16BitMonoTrueSilk,
4435
+ "riff-16khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Riff16Khz16BitMonoPcm,
4436
+ "riff-8khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz16BitMonoPcm,
4437
+ "riff-24khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm,
4438
+ "riff-8khz-8bit-mono-mulaw": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz8BitMonoMULaw,
4439
+ "raw-16khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Raw16Khz16BitMonoPcm,
4440
+ "raw-24khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Raw24Khz16BitMonoPcm,
4441
+ "raw-8khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz16BitMonoPcm,
4442
+ "ogg-16khz-16bit-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Ogg16Khz16BitMonoOpus,
4443
+ "ogg-24khz-16bit-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Ogg24Khz16BitMonoOpus,
4444
+ "raw-48khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Raw48Khz16BitMonoPcm,
4445
+ "riff-48khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Riff48Khz16BitMonoPcm,
4446
+ "audio-48khz-96kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio48Khz96KBitRateMonoMp3,
4447
+ "audio-48khz-192kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio48Khz192KBitRateMonoMp3,
4448
+ "ogg-48khz-16bit-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Ogg48Khz16BitMonoOpus,
4449
+ "webm-16khz-16bit-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Webm16Khz16BitMonoOpus,
4450
+ "webm-24khz-16bit-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Webm24Khz16BitMonoOpus,
4451
+ "webm-24khz-16bit-24kbps-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Webm24Khz16Bit24KbpsMonoOpus,
4452
+ "raw-24khz-16bit-mono-truesilk": SpeechSDK.SpeechSynthesisOutputFormat.Raw24Khz16BitMonoTrueSilk,
4453
+ "raw-8khz-8bit-mono-alaw": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz8BitMonoALaw,
4454
+ "riff-8khz-8bit-mono-alaw": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz8BitMonoALaw,
4455
+ "audio-16khz-16bit-32kbps-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz16Bit32KbpsMonoOpus,
4456
+ "audio-24khz-16bit-48kbps-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz16Bit48KbpsMonoOpus,
4457
+ "audio-24khz-16bit-24kbps-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz16Bit24KbpsMonoOpus,
4458
+ "raw-22050hz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Raw22050Hz16BitMonoPcm,
4459
+ "riff-22050hz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Riff22050Hz16BitMonoPcm,
4460
+ "raw-44100hz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Raw44100Hz16BitMonoPcm,
4461
+ "riff-44100hz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Riff44100Hz16BitMonoPcm,
4462
+ "amr-wb-16000hz": SpeechSDK.SpeechSynthesisOutputFormat.AmrWb16000Hz,
4463
+ "g722-16khz-64kbps": SpeechSDK.SpeechSynthesisOutputFormat.G72216Khz64Kbps
4464
+ };
4465
+ function resolveMimeType(outputFormat) {
4466
+ if (/(?:wav|wave|riff)/i.test(outputFormat)) return "audio/wav";
4467
+ if (/(?:mp3|mpeg)/i.test(outputFormat)) return "audio/mpeg";
4468
+ if (/ogg/i.test(outputFormat)) return "audio/ogg";
4469
+ if (/webm/i.test(outputFormat)) return "audio/webm";
4470
+ if (/raw/i.test(outputFormat)) return "audio/L16";
4471
+ return "application/octet-stream";
4472
+ }
4473
+ function resolveOutputFormat(outputFormat) {
4474
+ const resolvedFormat = OUTPUT_FORMATS[outputFormat];
4475
+ if (resolvedFormat === void 0) {
4476
+ throw new Error(`Unsupported Azure Speech output format: ${outputFormat}`);
4477
+ }
4478
+ return resolvedFormat;
4479
+ }
4480
+
4481
+ // packages/azure-tts-client/src/speechConfig.ts
4482
+ var import_microsoft_cognitiveservices_speech_sdk = require("microsoft-cognitiveservices-speech-sdk");
4483
+ function resolveEndpoint(config) {
4484
+ const endpoint = config.endpoint?.trim() || "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
4485
+ return endpoint.replace(/\{region\}/g, encodeURIComponent(config.region));
4486
+ }
4487
+ function createSpeechConfig(config) {
4488
+ const { outputFormat = DEFAULT_OUTPUT_FORMAT, subscriptionKey } = config;
4489
+ const endpoint = new URL(resolveEndpoint(config));
4490
+ const speechConfig = import_microsoft_cognitiveservices_speech_sdk.SpeechConfig.fromEndpoint(endpoint, subscriptionKey);
4491
+ speechConfig.speechSynthesisOutputFormat = resolveOutputFormat(outputFormat);
4492
+ return speechConfig;
4493
+ }
4494
+
4495
+ // packages/azure-tts-client/src/synthesis.ts
4496
+ function ascii(bytes, offset, value) {
4497
+ return [...value].every((character, index) => bytes[offset + index] === character.charCodeAt(0));
4498
+ }
4499
+ function readUint32(bytes, offset) {
4500
+ return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(offset, true);
4501
+ }
4502
+ function parseWav(buffer) {
4503
+ const bytes = new Uint8Array(buffer);
4504
+ if (bytes.byteLength < 12 || !ascii(bytes, 0, "RIFF") || !ascii(bytes, 8, "WAVE")) {
4505
+ throw new Error("Invalid WAV/RIFF audio buffer.");
4506
+ }
4507
+ const chunks = [];
4508
+ const dataParts = [];
4509
+ let format;
4510
+ let offset = 12;
4511
+ while (offset < bytes.byteLength) {
4512
+ if (offset + 8 > bytes.byteLength) throw new Error("Invalid WAV chunk header.");
4513
+ const id = String.fromCharCode(...bytes.slice(offset, offset + 4));
4514
+ const size = readUint32(bytes, offset + 4);
4515
+ const dataStart = offset + 8;
4516
+ const dataEnd = dataStart + size;
4517
+ if (dataEnd > bytes.byteLength) throw new Error(`WAV chunk "${id}" exceeds the audio buffer.`);
4518
+ const data2 = bytes.slice(dataStart, dataEnd);
4519
+ chunks.push({ id, data: data2 });
4520
+ if (id === "fmt ") format ?? (format = data2);
4521
+ if (id === "data") dataParts.push(data2);
4522
+ offset = dataEnd + (size & 1);
4523
+ if (offset > bytes.byteLength) throw new Error("Invalid WAV chunk padding.");
4524
+ }
4525
+ if (!format || dataParts.length === 0) throw new Error("WAV audio must contain fmt and data chunks.");
4526
+ const dataLength = dataParts.reduce((total, part) => total + part.byteLength, 0);
4527
+ const data = new Uint8Array(dataLength);
4528
+ let dataOffset = 0;
4529
+ for (const part of dataParts) {
4530
+ data.set(part, dataOffset);
4531
+ dataOffset += part.byteLength;
4532
+ }
4533
+ return { chunks, data, format };
4534
+ }
4535
+ function writeUint32(target, offset, value) {
4536
+ new DataView(target.buffer).setUint32(offset, value, true);
4537
+ }
4538
+ function writeChunk(target, offset, id, data) {
4539
+ for (let index = 0; index < 4; index += 1) target[offset + index] = id.charCodeAt(index) ?? 0;
4540
+ writeUint32(target, offset + 4, data.byteLength);
4541
+ target.set(data, offset + 8);
4542
+ const end = offset + 8 + data.byteLength;
4543
+ if (data.byteLength & 1) target[end] = 0;
4544
+ return end + (data.byteLength & 1);
4545
+ }
4546
+ function mergeWavBuffers(buffers) {
4547
+ if (buffers.length === 0) return new ArrayBuffer(0);
4548
+ const parsed = buffers.map(parseWav);
4549
+ const first = parsed[0];
4550
+ if (!first) throw new Error("At least one WAV buffer is required.");
4551
+ if (parsed.some(
4552
+ (item) => item.format.length !== first.format.length || item.format.some((value, i) => value !== first.format[i])
4553
+ ))
4554
+ throw new Error("WAV buffers have incompatible fmt chunks.");
4555
+ const dataLength = parsed.reduce((total, item) => total + item.data.byteLength, 0);
4556
+ const nonDataLength = first.chunks.reduce(
4557
+ (total, chunk) => chunk.id === "data" ? total : total + 8 + chunk.data.byteLength + (chunk.data.byteLength & 1),
4558
+ 0
4559
+ );
4560
+ const outputLength = 12 + nonDataLength + 8 + dataLength + (dataLength & 1);
4561
+ if (outputLength - 8 > 4294967295) throw new RangeError("Merged WAV exceeds the RIFF format size limit.");
4562
+ const output = new Uint8Array(outputLength);
4563
+ output.set(Uint8Array.from([82, 73, 70, 70]), 0);
4564
+ writeUint32(output, 4, outputLength - 8);
4565
+ output.set(Uint8Array.from([87, 65, 86, 69]), 8);
4566
+ let outputOffset = 12;
4567
+ let dataWritten = false;
4568
+ for (const chunk of first.chunks) {
4569
+ if (chunk.id === "data") {
4570
+ if (dataWritten) continue;
4571
+ const data = new Uint8Array(dataLength);
4572
+ let dataOffset = 0;
4573
+ for (const item of parsed) {
4574
+ data.set(item.data, dataOffset);
4575
+ dataOffset += item.data.byteLength;
4576
+ }
4577
+ outputOffset = writeChunk(output, outputOffset, "data", data);
4578
+ dataWritten = true;
4579
+ } else {
4580
+ outputOffset = writeChunk(output, outputOffset, chunk.id, chunk.data);
4581
+ }
4582
+ }
4583
+ if (!dataWritten) throw new Error("WAV audio must contain a data chunk.");
4584
+ return output.buffer;
4585
+ }
4586
+ function skipId3v2(bytes) {
4587
+ if (!ascii(bytes, 0, "ID3") || bytes.byteLength < 10) return 0;
4588
+ const size = [bytes[6], bytes[7], bytes[8], bytes[9]].reduce((total, value) => total << 7 | value & 127, 0);
4589
+ const hasFooter = (bytes[5] & 16) !== 0;
4590
+ return Math.min(bytes.byteLength, 10 + size + (hasFooter ? 10 : 0));
4591
+ }
4592
+ function stripMp3Tags(buffer) {
4593
+ const bytes = new Uint8Array(buffer);
4594
+ const start = skipId3v2(bytes);
4595
+ const end = bytes.byteLength >= 128 && ascii(bytes, bytes.byteLength - 128, "TAG") ? bytes.byteLength - 128 : bytes.byteLength;
4596
+ return bytes.slice(Math.min(start, end), end);
4597
+ }
4598
+ function isMp3Format(format) {
4599
+ return /(?:mp3|mpeg)/i.test(format);
4600
+ }
4601
+ function isWavFormat(format) {
4602
+ return /(?:wav|wave|riff)/i.test(format);
4603
+ }
4604
+ function isRawFormat(format) {
4605
+ return /^raw(?:-|$)/i.test(format);
4606
+ }
4607
+ function resolveMergeAudioFormat(format) {
4608
+ if (isWavFormat(format)) return "wav";
4609
+ if (isMp3Format(format)) return "mp3";
4610
+ if (isRawFormat(format)) return "raw";
4611
+ return void 0;
4612
+ }
4613
+ function canMergeAudioFormat(format) {
4614
+ return resolveMergeAudioFormat(format) !== void 0;
4615
+ }
4616
+ function mergeAudioBuffers(buffers, options) {
4617
+ const format = typeof options === "string" ? options : options?.format;
4618
+ if (!format) throw new UnsupportedMergeFormatError("");
4619
+ try {
4620
+ if (isWavFormat(format)) return mergeWavBuffers(buffers);
4621
+ if (isMp3Format(format)) {
4622
+ const parts = buffers.map(stripMp3Tags);
4623
+ const output = new Uint8Array(parts.reduce((total, part) => total + part.byteLength, 0));
4624
+ let offset = 0;
4625
+ for (const part of parts) {
4626
+ output.set(part, offset);
4627
+ offset += part.byteLength;
4628
+ }
4629
+ return output.buffer;
4630
+ }
4631
+ if (isRawFormat(format)) {
4632
+ const output = new Uint8Array(buffers.reduce((total, buffer) => total + buffer.byteLength, 0));
4633
+ let offset = 0;
4634
+ for (const buffer of buffers) {
4635
+ output.set(new Uint8Array(buffer), offset);
4636
+ offset += buffer.byteLength;
4637
+ }
4638
+ return output.buffer;
4639
+ }
4640
+ throw new UnsupportedMergeFormatError(format);
4641
+ } catch (error) {
4642
+ if (error instanceof UnsupportedMergeFormatError || error instanceof MergeError) throw error;
4643
+ throw new MergeError(`Audio buffers could not be merged for format "${format}".`, error);
4644
+ }
4645
+ }
4646
+ function closeSpeechResources(speechConfig, synthesizer) {
4647
+ try {
4648
+ synthesizer.close();
4649
+ } catch {
4650
+ }
4651
+ try {
4652
+ speechConfig.close();
4653
+ } catch {
4654
+ }
4655
+ }
4656
+ var ticksToMilliseconds = (ticks) => Math.max(0, ticks) / 1e4;
4657
+ async function synthesizeSsml(ssml, config) {
4658
+ if (config.signal?.aborted) {
4659
+ throw new SynthesisCancelledError();
4660
+ }
4661
+ const speechConfig = createSpeechConfig(config);
4662
+ const synthesizer = new SpeechSDK2.SpeechSynthesizer(speechConfig, null);
4663
+ return await new Promise((resolve, reject) => {
4664
+ let resourcesClosed = false;
4665
+ let settled = false;
4666
+ let timeout;
4667
+ let abortHandler;
4668
+ const cleanup = () => {
4669
+ if (timeout) clearTimeout(timeout);
4670
+ if (abortHandler) config.signal?.removeEventListener("abort", abortHandler);
4671
+ };
4672
+ const closeResources = () => {
4673
+ if (resourcesClosed) return;
4674
+ resourcesClosed = true;
4675
+ closeSpeechResources(speechConfig, synthesizer);
4676
+ };
4677
+ const rejectWithError = (error) => {
4678
+ if (settled) return;
4679
+ settled = true;
4680
+ cleanup();
4681
+ closeResources();
4682
+ reject(toSynthesisError(error));
4683
+ };
4684
+ const boundaries = [];
4685
+ const visemes = [];
4686
+ const bookmarks = [];
4687
+ let sourceEventCursor = 0;
4688
+ let generatedSourceMap;
4689
+ if (!config.sourceTextSegments && !config.sourceMarkers) {
4690
+ try {
4691
+ generatedSourceMap = getSsmlSourceMap2(ssml);
4692
+ } catch {
4693
+ generatedSourceMap = void 0;
4694
+ }
4695
+ }
4696
+ const sourceBaseOffset = config.sourceTextRange?.start ?? 0;
4697
+ const sourceSegments = config.sourceTextSegments ?? generatedSourceMap?.segments.map((segment) => ({
4698
+ ...segment,
4699
+ range: {
4700
+ start: segment.range.start + sourceBaseOffset,
4701
+ end: segment.range.end + sourceBaseOffset
4702
+ },
4703
+ sourceNodePath: [...segment.sourceNodePath]
4704
+ })) ?? [];
4705
+ const sourceMarkers = config.sourceMarkers ?? generatedSourceMap?.markers.map((marker) => ({
4706
+ ...marker,
4707
+ originalTextRange: {
4708
+ start: marker.originalTextRange.start + sourceBaseOffset,
4709
+ end: marker.originalTextRange.end + sourceBaseOffset
4710
+ },
4711
+ sourceNodePath: [...marker.sourceNodePath]
4712
+ })) ?? [];
4713
+ const sourceText = sourceSegments.map((segment) => segment.text).join("");
4714
+ const mapSourceEvent = (text, offsetHint, markerName) => {
4715
+ const marker = markerName ? sourceMarkers.find((candidate) => candidate.name === markerName) : void 0;
4716
+ if (marker) {
4717
+ return {
4718
+ originalTextRange: { ...marker.originalTextRange },
4719
+ sourceNodePath: [...marker.sourceNodePath],
4720
+ textRange: { ...marker.originalTextRange }
4721
+ };
4722
+ }
4723
+ if (sourceSegments.length === 0 && !config.sourceTextRange && !config.sourceNodePath) return {};
4724
+ const value = text ?? "";
4725
+ let localStart = Number.isFinite(offsetHint) && (offsetHint ?? 0) >= 0 ? offsetHint : -1;
4726
+ if (value && localStart >= 0 && sourceText.slice(localStart, localStart + value.length) !== value)
4727
+ localStart = -1;
4728
+ if (localStart < 0 || localStart > sourceText.length) {
4729
+ localStart = value ? sourceText.indexOf(value, sourceEventCursor) : sourceEventCursor;
4730
+ if (localStart < 0) localStart = value ? sourceText.indexOf(value) : sourceEventCursor;
4731
+ }
4732
+ localStart = Math.max(0, localStart);
4733
+ const localEnd = Math.min(sourceText.length, localStart + value.length);
4734
+ sourceEventCursor = Math.max(sourceEventCursor, localEnd);
4735
+ const baseStart = config.sourceTextRange?.start ?? sourceSegments[0]?.range.start ?? 0;
4736
+ const fallbackRange = { start: baseStart + localStart, end: baseStart + localEnd };
4737
+ 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);
4738
+ return {
4739
+ originalTextRange: { ...fallbackRange },
4740
+ textRange: { ...fallbackRange },
4741
+ ...segment ? { sourceNodePath: [...segment.sourceNodePath] } : config.sourceNodePath ? { sourceNodePath: [...config.sourceNodePath] } : {}
4742
+ };
4743
+ };
4744
+ synthesizer.wordBoundary = (_sender, event) => {
4745
+ boundaries.push({
4746
+ text: event.text,
4747
+ audioOffsetMs: ticksToMilliseconds(event.audioOffset),
4748
+ durationMs: ticksToMilliseconds(event.duration),
4749
+ ...mapSourceEvent(
4750
+ event.text,
4751
+ event.textOffset
4752
+ )
4753
+ });
4754
+ };
4755
+ synthesizer.visemeReceived = (_sender, event) => {
4756
+ const eventWithOffset = event;
4757
+ visemes.push({
4758
+ visemeId: event.visemeId,
4759
+ audioOffsetMs: ticksToMilliseconds(event.audioOffset),
4760
+ ...mapSourceEvent(void 0, eventWithOffset.textOffset)
4761
+ });
4762
+ };
4763
+ synthesizer.bookmarkReached = (_sender, event) => {
4764
+ const eventWithOffset = event;
4765
+ bookmarks.push({
4766
+ name: event.text,
4767
+ audioOffsetMs: ticksToMilliseconds(event.audioOffset),
4768
+ ...mapSourceEvent(void 0, eventWithOffset.textOffset, event.text)
4769
+ });
4770
+ };
4771
+ const cb = (result) => {
4772
+ if (settled) return;
4773
+ const { reason, errorDetails } = result;
4774
+ if (reason !== SpeechSDK2.ResultReason.SynthesizingAudioCompleted) {
4775
+ const err = errorDetails || `Speech synthesis failed with reason ${reason}.`;
4776
+ rejectWithError(err);
4777
+ return;
4778
+ }
4779
+ settled = true;
4780
+ cleanup();
4781
+ closeResources();
4782
+ const eventDurationMs = Math.max(
4783
+ 0,
4784
+ ...(boundaries ?? []).map((boundary) => boundary.audioOffsetMs + boundary.durationMs),
4785
+ ...(visemes ?? []).map((viseme) => viseme.audioOffsetMs),
4786
+ ...(bookmarks ?? []).map((bookmark) => bookmark.audioOffsetMs)
4787
+ );
4788
+ const durationMs = result.audioDuration ? ticksToMilliseconds(result.audioDuration) : eventDurationMs;
4789
+ const requestId = result.resultId;
4790
+ const addSourceMetadata = (event) => ({
4791
+ ...event,
4792
+ ...config.sourceTextRange && !("textRange" in event) ? { textRange: { ...config.sourceTextRange } } : {},
4793
+ ...config.sourceTextRange && !("originalTextRange" in event) ? { originalTextRange: { ...config.sourceTextRange } } : {},
4794
+ ...config.chunkIndex !== void 0 ? { chunkIndex: config.chunkIndex } : {},
4795
+ ...config.sourceNodePath ? { sourceNodePath: [...config.sourceNodePath] } : {},
4796
+ ...requestId ? { requestId } : {}
4797
+ });
4798
+ const sourceBoundaries = boundaries.map((boundary) => addSourceMetadata(boundary));
4799
+ const sourceVisemes = visemes.map((viseme) => addSourceMetadata(viseme));
4800
+ const sourceBookmarks = bookmarks.map((bookmark) => addSourceMetadata(bookmark));
4801
+ resolve({
4802
+ audioData: result.audioData,
4803
+ durationMs,
4804
+ ...config.sourceTextRange ? { textRange: { ...config.sourceTextRange } } : {},
4805
+ ...requestId ? { requestId } : {},
4806
+ ...sourceBoundaries.length > 0 ? { boundaries: sourceBoundaries, wordBoundary: sourceBoundaries, wordBoundaries: sourceBoundaries } : {},
4807
+ ...sourceVisemes.length > 0 ? { visemes: sourceVisemes } : {},
4808
+ ...sourceBookmarks.length > 0 ? { bookmarks: sourceBookmarks } : {}
4809
+ });
4810
+ };
4811
+ try {
4812
+ if (config.signal) {
4813
+ abortHandler = () => rejectWithError(new SynthesisCancelledError());
4814
+ config.signal.addEventListener("abort", abortHandler, { once: true });
4815
+ }
4816
+ if (config.timeoutMs !== void 0 && config.timeoutMs > 0) {
4817
+ timeout = setTimeout(
4818
+ () => rejectWithError(new SynthesisTimeoutError(`Speech synthesis timed out after ${config.timeoutMs} ms.`)),
4819
+ config.timeoutMs
4820
+ );
4821
+ }
4822
+ synthesizer.speakSsmlAsync(ssml, cb, rejectWithError);
4823
+ } catch (error) {
4824
+ rejectWithError(error);
4825
+ }
4826
+ });
4827
+ }
4828
+ async function synthesizeSsmlChunks(chunks, config) {
4829
+ const results = [];
4830
+ const totalChunks = chunks.length;
4831
+ const report = (event) => config.onProgress?.(event);
4832
+ for (const [index, chunk] of chunks.entries()) {
4833
+ const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
4834
+ report({
4835
+ currentChunk: index,
4836
+ totalChunks,
4837
+ percent: totalChunks === 0 ? 100 : Math.round(index / totalChunks * 100),
4838
+ chunkIndex: index,
4839
+ originalTextRange: input.originalTextRange,
4840
+ status: "pending",
4841
+ durationMs: 0
4842
+ });
4843
+ }
4844
+ for (const [index, chunk] of chunks.entries()) {
4845
+ const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
4846
+ report({
4847
+ currentChunk: index,
4848
+ totalChunks,
4849
+ percent: totalChunks === 0 ? 100 : Math.round(index / totalChunks * 100),
4850
+ chunkIndex: index,
4851
+ originalTextRange: input.originalTextRange,
4852
+ status: "synthesizing",
4853
+ durationMs: 0
4854
+ });
4855
+ const startedAt = Date.now();
4856
+ try {
4857
+ const result = await synthesizeSsml(input.ssml, {
4858
+ ...config,
4859
+ ...input.originalTextRange ? { sourceTextRange: input.originalTextRange } : {},
4860
+ ...input.sourceNodePath ?? config.sourceNodePath ? { sourceNodePath: [...input.sourceNodePath ?? config.sourceNodePath ?? []] } : {},
4861
+ ...input.sourceTextSegments ? { sourceTextSegments: input.sourceTextSegments } : {},
4862
+ ...input.sourceMarkers ? { sourceMarkers: input.sourceMarkers } : {},
4863
+ chunkIndex: index,
4864
+ onProgress: void 0
4865
+ });
4866
+ results.push(result);
4867
+ report({
4868
+ currentChunk: index + 1,
4869
+ totalChunks,
4870
+ percent: totalChunks === 0 ? 100 : Math.round((index + 1) / totalChunks * 100),
4871
+ chunkIndex: index,
4872
+ originalTextRange: input.originalTextRange,
4873
+ status: "success",
4874
+ durationMs: Date.now() - startedAt
4875
+ });
4876
+ } catch (error) {
4877
+ report({
4878
+ currentChunk: index,
4879
+ totalChunks,
4880
+ percent: totalChunks === 0 ? 100 : Math.round(index / totalChunks * 100),
4881
+ chunkIndex: index,
4882
+ originalTextRange: input.originalTextRange,
4883
+ status: "failed",
4884
+ durationMs: Date.now() - startedAt,
4885
+ error
4886
+ });
4887
+ throw error;
4888
+ }
4889
+ }
4890
+ return mergeSynthesisResults(results, {
4891
+ format: config.outputFormat ?? "audio-16khz-128kbitrate-mono-mp3"
4892
+ });
4893
+ }
4894
+ function createMergedResult(results, audioData, format) {
4895
+ const boundaries = [];
4896
+ const visemes = [];
4897
+ const bookmarks = [];
4898
+ let durationOffset = 0;
4899
+ for (const [resultIndex, result] of results.entries()) {
4900
+ const chunkBoundaries = result.boundaries && result.boundaries.length > 0 ? result.boundaries : result.wordBoundary ?? result.wordBoundaries ?? [];
4901
+ for (const boundary of chunkBoundaries) {
4902
+ const textRange = boundary.textRange ?? result.textRange;
4903
+ const originalTextRange = boundary.originalTextRange ?? textRange;
4904
+ const requestId = boundary.requestId ?? result.requestId;
4905
+ boundaries.push({
4906
+ ...boundary,
4907
+ audioOffsetMs: boundary.audioOffsetMs + durationOffset,
4908
+ chunkAudioOffsetMs: boundary.chunkAudioOffsetMs ?? boundary.audioOffsetMs,
4909
+ ...boundary.chunkIndex === void 0 ? { chunkIndex: resultIndex } : {},
4910
+ ...boundary.sourceNodePath ? { sourceNodePath: [...boundary.sourceNodePath] } : {},
4911
+ ...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
4912
+ ...textRange ? { textRange: { ...textRange } } : {},
4913
+ ...requestId ? { requestId } : {}
4914
+ });
4915
+ }
4916
+ for (const viseme of result.visemes ?? []) {
4917
+ const textRange = viseme.textRange ?? result.textRange;
4918
+ const originalTextRange = viseme.originalTextRange ?? textRange;
4919
+ const requestId = viseme.requestId ?? result.requestId;
4920
+ visemes.push({
4921
+ ...viseme,
4922
+ audioOffsetMs: viseme.audioOffsetMs + durationOffset,
4923
+ chunkAudioOffsetMs: viseme.chunkAudioOffsetMs ?? viseme.audioOffsetMs,
4924
+ ...viseme.chunkIndex === void 0 ? { chunkIndex: resultIndex } : {},
4925
+ ...viseme.sourceNodePath ? { sourceNodePath: [...viseme.sourceNodePath] } : {},
4926
+ ...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
4927
+ ...textRange ? { textRange: { ...textRange } } : {},
4928
+ ...requestId ? { requestId } : {}
4929
+ });
4930
+ }
4931
+ for (const bookmark of result.bookmarks ?? []) {
4932
+ const textRange = bookmark.textRange ?? result.textRange;
4933
+ const originalTextRange = bookmark.originalTextRange ?? textRange;
4934
+ const requestId = bookmark.requestId ?? result.requestId;
4935
+ bookmarks.push({
4936
+ ...bookmark,
4937
+ audioOffsetMs: bookmark.audioOffsetMs + durationOffset,
4938
+ chunkAudioOffsetMs: bookmark.chunkAudioOffsetMs ?? bookmark.audioOffsetMs,
4939
+ ...bookmark.chunkIndex === void 0 ? { chunkIndex: resultIndex } : {},
4940
+ ...bookmark.sourceNodePath ? { sourceNodePath: [...bookmark.sourceNodePath] } : {},
4941
+ ...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
4942
+ ...textRange ? { textRange: { ...textRange } } : {},
4943
+ ...requestId ? { requestId } : {}
4944
+ });
4945
+ }
4946
+ durationOffset += Math.max(0, result.durationMs);
4386
4947
  }
4948
+ return {
4949
+ audioData,
4950
+ durationMs: durationOffset,
4951
+ mimeType: resolveMimeType(format),
4952
+ ...boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {},
4953
+ ...visemes.length > 0 ? { visemes } : {},
4954
+ ...bookmarks.length > 0 ? { bookmarks } : {},
4955
+ ...results.length === 1 && results[0]?.requestId ? { requestId: results[0].requestId } : {},
4956
+ ...results.length === 1 && results[0]?.textRange ? { textRange: { ...results[0].textRange } } : {}
4957
+ };
4387
4958
  }
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"
4398
- }
4399
- ];
4400
- }
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.");
4959
+ function mergeSynthesisResults(results, options) {
4960
+ const resolvedOptions = typeof options === "string" ? { format: options } : options;
4961
+ const format = resolvedOptions?.format;
4962
+ if (!format) throw new UnsupportedMergeFormatError("");
4963
+ const buffers = results.map((result) => result.audioData);
4964
+ if (resolvedOptions.customMerger) {
4965
+ return Promise.resolve().then(() => resolvedOptions.customMerger?.(buffers, format)).then((merged) => {
4966
+ if (!merged) throw new MergeError("The custom audio merger returned no audio buffer.");
4967
+ return createMergedResult(results, merged, format);
4968
+ }).catch((error) => {
4969
+ if (error instanceof UnsupportedMergeFormatError || error instanceof MergeError) throw error;
4970
+ throw new MergeError(`Custom audio merger failed for format "${format}".`, error);
4971
+ });
4406
4972
  }
4407
4973
  try {
4408
- parseSsml2(ssml);
4974
+ return createMergedResult(results, mergeAudioBuffers(buffers, { format }), format);
4409
4975
  } 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;
4414
- }
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
- );
4425
- }
4426
- }
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"
4507
- );
4508
- }
4976
+ if (error instanceof UnsupportedMergeFormatError || error instanceof MergeError) throw error;
4977
+ throw new MergeError(`Audio buffers could not be merged for format "${format}".`, error);
4509
4978
  }
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
- });
4519
4979
  }
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 } : {}
4531
- });
4532
- let tokens;
4533
- try {
4534
- tokens = tokenizeElements2(ssml);
4535
- } catch {
4536
- return diagnostics;
4537
- }
4538
- const checks = tokens.flatMap(
4539
- (token) => urlAttributes2(token).map(async ({ attribute, value }) => {
4540
- 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}`
4559
- );
4560
- }
4561
- })
4562
- );
4563
- return Promise.all(checks).then(() => diagnostics);
4980
+ async function synthesizeSpeech(ssml, config) {
4981
+ return (await synthesizeSsml(ssml, config)).audioData;
4564
4982
  }
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
4983
 
4572
4984
  // packages/azure-tts-client/src/safe.ts
4573
4985
  var ChunkValidationError = class extends Error {
4574
4986
  constructor(chunkIndex, diagnostics) {
4575
4987
  super(`SSML validation failed for chunk ${chunkIndex}; the Azure Speech API was not called.`);
4576
- this.kind = "chunk-validation";
4988
+ this.kind = "validation-error";
4577
4989
  this.name = "ChunkValidationError";
4578
4990
  this.chunkIndex = chunkIndex;
4579
4991
  this.diagnostics = diagnostics;
4580
4992
  }
4581
4993
  };
4994
+ function failure(error) {
4995
+ return { ok: false, success: false, status: error.kind, error };
4996
+ }
4582
4997
  async function synthesizeSsmlSafe(client, ssml, options = {}) {
4583
- const validationOptions = options.validation ?? options;
4998
+ const validationOptions = withValidationSignal(options.validation ?? options, options.signal);
4584
4999
  const diagnostics = await Promise.resolve(validateAzureSsml2(ssml, validationOptions));
5000
+ if (options.signal?.aborted) {
5001
+ const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
5002
+ return failure(error);
5003
+ }
4585
5004
  const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
4586
5005
  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
- };
5006
+ return failure({
5007
+ kind: "validation-error",
5008
+ message: "SSML validation failed; the Azure Speech API was not called.",
5009
+ diagnostics: errors
5010
+ });
4597
5011
  }
4598
5012
  try {
4599
- return { ok: true, success: true, status: "success", value: await client.synthesizeSsml(ssml) };
5013
+ return {
5014
+ ok: true,
5015
+ success: true,
5016
+ status: "success",
5017
+ value: await client.synthesizeSsml(ssml, { signal: options.signal })
5018
+ };
4600
5019
  } catch (error) {
4601
- const azureError = error instanceof AzureTtsError ? error : createSpeechSdkError(error);
4602
- return { ok: false, success: false, status: "azure-api-error", error: azureError };
5020
+ const synthesisError = toSynthesisError(error);
5021
+ return failure(synthesisError);
4603
5022
  }
4604
5023
  }
4605
5024
  async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
4606
- const validationOptions = options.validation ?? options;
5025
+ const validationOptions = withValidationSignal(options.validation ?? options, options.signal);
5026
+ if (options.signal?.aborted) {
5027
+ const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
5028
+ return failure(error);
5029
+ }
4607
5030
  const pending = (index, status, error) => {
4608
5031
  options.onProgress?.({
4609
5032
  currentChunk: status === "success" ? index + 1 : index,
@@ -4622,7 +5045,10 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
4622
5045
  const validations = await Promise.all(
4623
5046
  chunks.map(async (chunk) => {
4624
5047
  const ssml = typeof chunk === "string" ? chunk : chunk.ssml;
4625
- const diagnostics = await Promise.resolve(validateAzureSsml2(ssml, validationOptions));
5048
+ const sourceNodePath = typeof chunk === "string" ? options.sourceNodePath : chunk.sourceNodePath ?? options.sourceNodePath;
5049
+ const diagnostics = await Promise.resolve(
5050
+ validateAzureSsml2(ssml, { ...validationOptions, ...sourceNodePath ? { sourceNodePath } : {} })
5051
+ );
4626
5052
  return diagnostics.filter((diagnostic) => diagnostic.severity === "error");
4627
5053
  })
4628
5054
  );
@@ -4630,31 +5056,78 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
4630
5056
  if (firstInvalidIndex >= 0) {
4631
5057
  const error = new ChunkValidationError(firstInvalidIndex, validations[firstInvalidIndex] ?? []);
4632
5058
  pending(firstInvalidIndex, "failed", error);
4633
- return { ok: false, success: false, status: "validation-error", error };
5059
+ return failure(error);
4634
5060
  }
4635
5061
  try {
4636
5062
  if (client.synthesizeChunks) {
4637
- const value = await client.synthesizeChunks(chunks, { onProgress: options.onProgress });
5063
+ const normalizedChunks = chunks.map((chunk) => {
5064
+ if (typeof chunk === "string" || chunk.sourceNodePath || !options.sourceNodePath) return chunk;
5065
+ return { ...chunk, sourceNodePath: [...options.sourceNodePath] };
5066
+ });
5067
+ const value = await client.synthesizeChunks(normalizedChunks, {
5068
+ onProgress: options.onProgress,
5069
+ outputFormat: options.outputFormat,
5070
+ signal: options.signal,
5071
+ timeoutMs: options.timeoutMs,
5072
+ sourceNodePath: options.sourceNodePath
5073
+ });
4638
5074
  return { ok: true, success: true, status: "success", value };
4639
5075
  }
4640
5076
  const results = [];
4641
5077
  for (const [index, chunk] of chunks.entries()) {
4642
5078
  const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
4643
5079
  const sourceNodePath = input.sourceNodePath;
5080
+ const originalTextRange = input.originalTextRange;
4644
5081
  pending(index, "synthesizing");
4645
5082
  const startedAt = Date.now();
4646
5083
  try {
4647
- const result = await client.synthesizeSsml(input.ssml);
5084
+ const result = await client.synthesizeSsml(input.ssml, {
5085
+ outputFormat: options.outputFormat,
5086
+ signal: options.signal,
5087
+ timeoutMs: options.timeoutMs,
5088
+ sourceNodePath: input.sourceNodePath ?? options.sourceNodePath
5089
+ });
4648
5090
  results.push({
4649
5091
  ...result,
4650
5092
  ...input.originalTextRange ? { textRange: { ...input.originalTextRange } } : {},
4651
5093
  ...sourceNodePath ? {
4652
5094
  boundaries: result.boundaries?.map((event) => ({
4653
5095
  ...event,
4654
- sourceNodePath: [...sourceNodePath]
5096
+ sourceNodePath: [...sourceNodePath],
5097
+ ...event.originalTextRange ? { originalTextRange: { ...event.originalTextRange } } : input.originalTextRange ? { originalTextRange: { ...input.originalTextRange } } : {}
5098
+ })),
5099
+ visemes: result.visemes?.map((event) => ({
5100
+ ...event,
5101
+ sourceNodePath: [...sourceNodePath],
5102
+ ...event.originalTextRange ? { originalTextRange: { ...event.originalTextRange } } : input.originalTextRange ? { originalTextRange: { ...input.originalTextRange } } : {}
5103
+ })),
5104
+ bookmarks: result.bookmarks?.map((event) => ({
5105
+ ...event,
5106
+ sourceNodePath: [...sourceNodePath],
5107
+ ...event.originalTextRange ? { originalTextRange: { ...event.originalTextRange } } : input.originalTextRange ? { originalTextRange: { ...input.originalTextRange } } : {}
5108
+ }))
5109
+ } : {},
5110
+ ...originalTextRange ? {
5111
+ boundaries: result.boundaries?.map((event) => ({
5112
+ ...event,
5113
+ originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
5114
+ })),
5115
+ wordBoundary: result.wordBoundary?.map((event) => ({
5116
+ ...event,
5117
+ originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
5118
+ })),
5119
+ wordBoundaries: result.wordBoundaries?.map((event) => ({
5120
+ ...event,
5121
+ originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
5122
+ })),
5123
+ visemes: result.visemes?.map((event) => ({
5124
+ ...event,
5125
+ originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
4655
5126
  })),
4656
- visemes: result.visemes?.map((event) => ({ ...event, sourceNodePath: [...sourceNodePath] })),
4657
- bookmarks: result.bookmarks?.map((event) => ({ ...event, sourceNodePath: [...sourceNodePath] }))
5127
+ bookmarks: result.bookmarks?.map((event) => ({
5128
+ ...event,
5129
+ originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
5130
+ }))
4658
5131
  } : {}
4659
5132
  });
4660
5133
  options.onProgress?.({
@@ -4684,13 +5157,23 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
4684
5157
  ok: true,
4685
5158
  success: true,
4686
5159
  status: "success",
4687
- value: mergeSynthesisResults(results, options.outputFormat)
5160
+ value: mergeSynthesisResults(results, {
5161
+ format: options.outputFormat ?? "audio-16khz-128kbitrate-mono-mp3"
5162
+ })
4688
5163
  };
4689
5164
  } catch (error) {
4690
- const azureError = error instanceof AzureTtsError ? error : createSpeechSdkError(error);
4691
- return { ok: false, success: false, status: "azure-api-error", error: azureError };
5165
+ const synthesisError = toSynthesisError(error);
5166
+ return failure(synthesisError);
4692
5167
  }
4693
5168
  }
5169
+ function withValidationSignal(options, signal) {
5170
+ if (!signal) return options;
5171
+ return {
5172
+ ...options,
5173
+ urlValidatorSignal: signal,
5174
+ urlValidation: { ...options.urlValidation ?? {}, signal }
5175
+ };
5176
+ }
4694
5177
 
4695
5178
  // packages/azure-tts-client/src/client.ts
4696
5179
  var ENDPOINT_TEMPLATE = "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
@@ -4707,11 +5190,21 @@ var AzureTtsClient = class {
4707
5190
  const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };
4708
5191
  return synthesizeSpeech(ssml, config);
4709
5192
  }
4710
- async synthesizeSsml(ssml) {
5193
+ async synthesizeSsml(ssml, options = {}) {
4711
5194
  const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
4712
5195
  const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
4713
5196
  __privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
4714
- return synthesizeSsml(ssml, { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs });
5197
+ return synthesizeSsml(ssml, {
5198
+ endpoint,
5199
+ region,
5200
+ subscriptionKey,
5201
+ outputFormat: options.outputFormat ?? outputFormat,
5202
+ signal: options.signal ?? signal,
5203
+ timeoutMs: options.timeoutMs ?? timeoutMs,
5204
+ sourceNodePath: options.sourceNodePath,
5205
+ sourceTextSegments: options.sourceTextSegments,
5206
+ sourceMarkers: options.sourceMarkers
5207
+ });
4715
5208
  }
4716
5209
  async synthesizeChunks(chunks, options = {}) {
4717
5210
  const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
@@ -4720,9 +5213,10 @@ var AzureTtsClient = class {
4720
5213
  endpoint,
4721
5214
  region,
4722
5215
  subscriptionKey,
4723
- outputFormat,
4724
- signal,
4725
- timeoutMs,
5216
+ outputFormat: options.outputFormat ?? outputFormat,
5217
+ signal: options.signal ?? signal,
5218
+ timeoutMs: options.timeoutMs ?? timeoutMs,
5219
+ sourceNodePath: options.sourceNodePath,
4726
5220
  onProgress: options.onProgress ?? __privateGet(this, _options).onProgress
4727
5221
  });
4728
5222
  }
@@ -4733,6 +5227,8 @@ var AzureTtsClient = class {
4733
5227
  return synthesizeSsmlChunksSafe(this, chunks, {
4734
5228
  ...options,
4735
5229
  outputFormat: options.outputFormat ?? __privateGet(this, _options).outputFormat,
5230
+ signal: options.signal ?? __privateGet(this, _options).signal,
5231
+ timeoutMs: options.timeoutMs ?? __privateGet(this, _options).timeoutMs,
4736
5232
  onProgress: options.onProgress ?? __privateGet(this, _options).onProgress
4737
5233
  });
4738
5234
  }
@@ -4834,6 +5330,10 @@ async function fetchAzureVoiceCatalog(options) {
4834
5330
  AzureTtsError,
4835
5331
  AzureTtsSdkError,
4836
5332
  ChunkValidationError,
5333
+ DEFAULT_OUTPUT_FORMAT,
5334
+ MergeError,
5335
+ SynthesisCancelledError,
5336
+ SynthesisTimeoutError,
4837
5337
  UnsupportedMergeFormatError,
4838
5338
  areAzureLanguagesEquivalent,
4839
5339
  buildPartialSsml,
@@ -4846,6 +5346,7 @@ async function fetchAzureVoiceCatalog(options) {
4846
5346
  fromPlainTextToSsml,
4847
5347
  getAzureVoiceCatalogMetadata,
4848
5348
  getBuiltInVoiceCatalogMetadata,
5349
+ getSsmlSourceMap,
4849
5350
  isValidAzureAudioDuration,
4850
5351
  mapSsmlTextNodes,
4851
5352
  mergeAudioBuffers,
@@ -4853,6 +5354,7 @@ async function fetchAzureVoiceCatalog(options) {
4853
5354
  normalizeAzureLanguage,
4854
5355
  parseSsml,
4855
5356
  resolveMergeAudioFormat,
5357
+ resolveMimeType,
4856
5358
  splitSsmlDocument,
4857
5359
  synthesizeSpeech,
4858
5360
  synthesizeSsml,