ssml-builder-js 2.13.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
@@ -40,24 +40,37 @@ __export(src_exports, {
40
40
  AzureTtsClient: () => AzureTtsClient,
41
41
  AzureTtsError: () => AzureTtsError,
42
42
  AzureTtsSdkError: () => AzureTtsSdkError,
43
+ ChunkValidationError: () => ChunkValidationError,
44
+ DEFAULT_OUTPUT_FORMAT: () => DEFAULT_OUTPUT_FORMAT,
45
+ MergeError: () => MergeError,
46
+ SynthesisCancelledError: () => SynthesisCancelledError,
47
+ SynthesisTimeoutError: () => SynthesisTimeoutError,
48
+ UnsupportedMergeFormatError: () => UnsupportedMergeFormatError,
43
49
  areAzureLanguagesEquivalent: () => areAzureLanguagesEquivalent,
44
50
  buildPartialSsml: () => buildPartialSsml,
45
51
  buildSsml: () => buildSsml,
52
+ canMergeAudioFormat: () => canMergeAudioFormat,
53
+ createAzureUrlValidatorRunner: () => createAzureUrlValidatorRunner,
46
54
  extractSsmlText: () => extractSsmlText,
47
55
  extractSsmlTranslatableText: () => extractSsmlTranslatableText,
48
56
  fetchAzureVoiceCatalog: () => fetchAzureVoiceCatalog,
49
57
  fromPlainTextToSsml: () => fromPlainTextToSsml,
50
58
  getAzureVoiceCatalogMetadata: () => getAzureVoiceCatalogMetadata,
51
59
  getBuiltInVoiceCatalogMetadata: () => getBuiltInVoiceCatalogMetadata,
60
+ getSsmlSourceMap: () => getSsmlSourceMap,
52
61
  isValidAzureAudioDuration: () => isValidAzureAudioDuration,
53
62
  mapSsmlTextNodes: () => mapSsmlTextNodes,
63
+ mergeAudioBuffers: () => mergeAudioBuffers,
54
64
  mergeSynthesisResults: () => mergeSynthesisResults,
55
65
  normalizeAzureLanguage: () => normalizeAzureLanguage,
56
66
  parseSsml: () => parseSsml,
67
+ resolveMergeAudioFormat: () => resolveMergeAudioFormat,
68
+ resolveMimeType: () => resolveMimeType,
57
69
  splitSsmlDocument: () => splitSsmlDocument,
58
70
  synthesizeSpeech: () => synthesizeSpeech,
59
71
  synthesizeSsml: () => synthesizeSsml,
60
72
  synthesizeSsmlChunks: () => synthesizeSsmlChunks,
73
+ synthesizeSsmlChunksSafe: () => synthesizeSsmlChunksSafe,
61
74
  synthesizeSsmlSafe: () => synthesizeSsmlSafe,
62
75
  validateAzureSsml: () => validateAzureSsml,
63
76
  validateSsml: () => validateSsml,
@@ -992,6 +1005,236 @@ function buildPartialSsml(textOrOptions, context) {
992
1005
  return serializePartialSsml(textOrOptions.text, textOrOptions);
993
1006
  }
994
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
+
995
1238
  // packages/ssml-core/src/split.ts
996
1239
  var DEFAULT_MAX_LENGTH = 1e4;
997
1240
  function cloneElement(element, children) {
@@ -1095,7 +1338,35 @@ function collectInheritedContext(nodes) {
1095
1338
  nodes.forEach(visit);
1096
1339
  return context;
1097
1340
  }
1098
- function createChunk(document, nodes, chunkIndex, textStart, backgroundAudio, replicateBackgroundAudio) {
1341
+ function elementName(node) {
1342
+ return node.type === "custom" || node.type === "element" ? node.name : node.type;
1343
+ }
1344
+ function findSourceNodePath(nodes, targetOffset) {
1345
+ let textOffset = 0;
1346
+ let firstPath;
1347
+ let foundPath;
1348
+ const visit = (node, path) => {
1349
+ if (typeof node === "string" || node.type === "text") {
1350
+ const text = typeof node === "string" ? node : node.value;
1351
+ if (text && firstPath === void 0) firstPath = [...path];
1352
+ if (text && foundPath === void 0 && targetOffset < textOffset + text.length) foundPath = [...path];
1353
+ textOffset += text.length;
1354
+ return;
1355
+ }
1356
+ node.children?.forEach((child, index) => {
1357
+ const childPath = typeof child === "string" || child.type === "text" ? path : [...path, `${elementName(child)}[${index}]`];
1358
+ visit(child, childPath);
1359
+ });
1360
+ };
1361
+ nodes.forEach((node, index) => {
1362
+ if (!foundPath) {
1363
+ if (typeof node === "string" || node.type === "text") visit(node, ["speak"]);
1364
+ else visit(node, ["speak", `${elementName(node)}[${index}]`]);
1365
+ }
1366
+ });
1367
+ return foundPath ?? firstPath;
1368
+ }
1369
+ function createChunk(document, nodes, chunkIndex, textStart, backgroundAudio, replicateBackgroundAudio, sourceMap, includeEndMarkers) {
1099
1370
  const chunkNodes = backgroundAudio && (replicateBackgroundAudio || chunkIndex === 0) ? [backgroundAudio, ...nodes] : nodes;
1100
1371
  const text = nodes.map(textFromNode).join("");
1101
1372
  const marks = [];
@@ -1110,7 +1381,24 @@ function createChunk(document, nodes, chunkIndex, textStart, backgroundAudio, re
1110
1381
  containedMarks: marks,
1111
1382
  hasBackgroundAudio: chunkNodes.some(
1112
1383
  (node) => typeof node !== "string" && node.type !== "text" && node.type === "mstts:backgroundaudio"
1113
- )
1384
+ ),
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
+ }))
1114
1402
  };
1115
1403
  }
1116
1404
  function splitSsmlDocument(ssml, maxLength = DEFAULT_MAX_LENGTH, options = {}) {
@@ -1120,11 +1408,12 @@ function splitSsmlDocument(ssml, maxLength = DEFAULT_MAX_LENGTH, options = {}) {
1120
1408
  throw new RangeError("maxLength must be a positive integer");
1121
1409
  }
1122
1410
  const document = parseSsml(ssml);
1411
+ const sourceMap = getSsmlSourceMap(ssml);
1123
1412
  const backgroundAudio = (document.children ?? []).find(
1124
1413
  (node) => typeof node !== "string" && node.type !== "text" && node.type === "mstts:backgroundaudio"
1125
1414
  );
1126
1415
  if (ssml.length <= resolvedMaxLength) {
1127
- return [createChunk(document, document.children ?? [], 0, 0, backgroundAudio, true)];
1416
+ return [createChunk(document, document.children ?? [], 0, 0, backgroundAudio, true, sourceMap, true)];
1128
1417
  }
1129
1418
  const contentChildren = (document.children ?? []).filter((node) => node !== backgroundAudio);
1130
1419
  const plainDocumentLength = documentWithChildren(document, []).length;
@@ -1148,7 +1437,16 @@ function splitSsmlDocument(ssml, maxLength = DEFAULT_MAX_LENGTH, options = {}) {
1148
1437
  }
1149
1438
  if (group.length > 0) chunks.push(group);
1150
1439
  if (chunks.length === 0) {
1151
- 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
+ );
1152
1450
  if (result.ssml.length > resolvedMaxLength) {
1153
1451
  throw new RangeError("maxLength is too small to contain the SSML document wrapper");
1154
1452
  }
@@ -1162,7 +1460,9 @@ function splitSsmlDocument(ssml, maxLength = DEFAULT_MAX_LENGTH, options = {}) {
1162
1460
  chunkIndex,
1163
1461
  textStart,
1164
1462
  backgroundAudio,
1165
- resolvedOptions.replicateBackgroundAudio ?? false
1463
+ resolvedOptions.replicateBackgroundAudio ?? false,
1464
+ sourceMap,
1465
+ chunkIndex === chunks.length - 1
1166
1466
  );
1167
1467
  textStart = result.originalTextRange.end;
1168
1468
  return result;
@@ -1185,172 +1485,23 @@ function validateSsml(xmlString) {
1185
1485
  }
1186
1486
  }
1187
1487
 
1188
- // packages/ssml-core/src/textNodes.ts
1189
- function decodeXmlText(value) {
1190
- return value.replace(/&(?:amp|apos|gt|lt|quot);|&#(?:x[\da-f]+|\d+);/gi, (entity) => {
1191
- if (entity === "&amp;") return "&";
1192
- if (entity === "&apos;") return "'";
1193
- if (entity === "&gt;") return ">";
1194
- if (entity === "&lt;") return "<";
1195
- if (entity === "&quot;") return '"';
1196
- const hexadecimal = entity.toLowerCase().startsWith("&#x");
1197
- const digits = entity.slice(hexadecimal ? 3 : 2, -1);
1198
- return String.fromCodePoint(Number.parseInt(digits, hexadecimal ? 16 : 10));
1199
- });
1200
- }
1201
- function encodeXmlText(value) {
1202
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1203
- }
1204
- function decodeXmlAttribute(value) {
1205
- return decodeXmlText(value);
1206
- }
1207
- function findTagEnd(source, start) {
1208
- let quote = "";
1209
- for (let index = start; index < source.length; index += 1) {
1210
- const character = source[index];
1211
- if (quote) {
1212
- if (character === quote) quote = "";
1213
- } else if (character === '"' || character === "'") {
1214
- quote = character;
1215
- } else if (character === ">") {
1216
- return index;
1217
- }
1218
- }
1219
- return source.length - 1;
1220
- }
1221
- function readTagName(tag) {
1222
- const match = /^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/.exec(tag);
1223
- return match?.[1];
1224
- }
1225
- function readTagAttributes(tag, name) {
1226
- const attributes = {};
1227
- const nameStart = tag.indexOf(name);
1228
- const attributeSource = tag.slice(nameStart + name.length, tag.length - 1).replace(/\/\s*$/, "");
1229
- const attributePattern = /([A-Za-z_][A-Za-z0-9_.:-]*)\s*=\s*(["'])([\s\S]*?)\2/g;
1230
- for (const match of attributeSource.matchAll(attributePattern)) {
1231
- attributes[match[1].toLowerCase()] = decodeXmlAttribute(match[3]);
1232
- }
1233
- return attributes;
1234
- }
1235
- function collectTextNodes(source) {
1236
- const nodes = [];
1237
- const elements = [];
1238
- let index = 0;
1239
- const addText = (start, end, rawText, sourceStart = start, sourceEnd = end) => {
1240
- if (!rawText) return;
1241
- const path = elements.map((element) => element.name);
1242
- const parent = elements[elements.length - 1];
1243
- nodes.push({
1244
- context: {
1245
- ancestorTags: path.slice(0, -1),
1246
- parentAttributes: { ...parent?.attributes ?? {} },
1247
- parentTag: parent?.name ?? "",
1248
- path
1249
- },
1250
- decodedText: decodeXmlText(rawText),
1251
- end,
1252
- sourceEnd,
1253
- sourceStart,
1254
- start
1255
- });
1256
- };
1257
- while (index < source.length) {
1258
- if (source[index] !== "<") {
1259
- const nextTag = source.indexOf("<", index);
1260
- const end2 = nextTag === -1 ? source.length : nextTag;
1261
- addText(index, end2, source.slice(index, end2));
1262
- index = end2;
1263
- continue;
1264
- }
1265
- if (source.startsWith("<!--", index)) {
1266
- const end2 = source.indexOf("-->", index + 4);
1267
- index = end2 === -1 ? source.length : end2 + 3;
1268
- continue;
1269
- }
1270
- if (source.startsWith("<![CDATA[", index)) {
1271
- const contentStart = index + 9;
1272
- const end2 = source.indexOf("]]>", contentStart);
1273
- const contentEnd = end2 === -1 ? source.length : end2;
1274
- addText(
1275
- contentStart,
1276
- contentEnd,
1277
- source.slice(contentStart, contentEnd),
1278
- index,
1279
- end2 === -1 ? source.length : end2 + 3
1280
- );
1281
- index = end2 === -1 ? source.length : end2 + 3;
1282
- continue;
1283
- }
1284
- if (source.startsWith("<?", index)) {
1285
- const end2 = source.indexOf("?>", index + 2);
1286
- index = end2 === -1 ? source.length : end2 + 2;
1287
- continue;
1288
- }
1289
- if (source.startsWith("</", index)) {
1290
- const end2 = findTagEnd(source, index + 2);
1291
- elements.pop();
1292
- index = end2 + 1;
1293
- continue;
1294
- }
1295
- const end = findTagEnd(source, index + 1);
1296
- const tag = source.slice(index, end + 1);
1297
- const name = readTagName(tag);
1298
- if (name && !/\/\s*>$/.test(tag)) elements.push({ attributes: readTagAttributes(tag, name), name });
1299
- index = end + 1;
1300
- }
1301
- return nodes;
1302
- }
1303
- function extractSsmlText(ssml) {
1304
- parseSsml(ssml);
1305
- return collectTextNodes(ssml).map((node) => node.decodedText);
1306
- }
1307
- async function mapSsmlTextNodes(ssml, transform, options = {}) {
1308
- parseSsml(ssml);
1309
- const nodes = collectTextNodes(ssml);
1310
- const skipTags = new Set((options.skipTags ?? ["phoneme", "say-as", "sub"]).map((tag) => tag.toLowerCase()));
1311
- const replacements = await Promise.all(
1312
- nodes.map(async (node) => {
1313
- const context = {
1314
- ancestorTags: [...node.context.ancestorTags],
1315
- parentAttributes: { ...node.context.parentAttributes },
1316
- parentTag: node.context.parentTag,
1317
- path: [...node.context.path]
1318
- };
1319
- const shouldTransform = !skipTags.has(context.parentTag.toLowerCase()) && (options.filter?.(context) ?? true);
1320
- if (!shouldTransform) return ssml.slice(node.sourceStart, node.sourceEnd);
1321
- const transformed = await transform(node.decodedText, context);
1322
- if (typeof transformed !== "string") {
1323
- throw new TypeError("SSML text node transform must return a string");
1324
- }
1325
- return transformed === node.decodedText ? ssml.slice(node.sourceStart, node.sourceEnd) : encodeXmlText(transformed);
1326
- })
1327
- );
1328
- let result = "";
1329
- let cursor = 0;
1330
- nodes.forEach((node, nodeIndex) => {
1331
- result += ssml.slice(cursor, node.sourceStart) + replacements[nodeIndex];
1332
- cursor = node.sourceEnd;
1333
- });
1334
- return result + ssml.slice(cursor);
1335
- }
1336
-
1337
- // packages/ssml-core/src/migration.ts
1338
- var DEFAULT_TRANSLATION_SKIP_TAGS = ["phoneme", "say-as", "sayAs", "sub"];
1339
- function elementName(element) {
1340
- switch (element.type) {
1341
- case "custom":
1342
- case "element":
1343
- return element.name;
1344
- case "expressAs":
1345
- return "mstts:express-as";
1346
- case "sayAs":
1347
- return "say-as";
1348
- case "silence":
1349
- return "mstts:silence";
1350
- case "viseme":
1351
- return "mstts:viseme";
1352
- default:
1353
- return element.type;
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;
1354
1505
  }
1355
1506
  }
1356
1507
  function addAttribute2(attributes, name, value) {
@@ -1489,7 +1640,7 @@ function extractSsmlTranslatableText(ssml, options = {}) {
1489
1640
  }
1490
1641
  return;
1491
1642
  }
1492
- const tag = elementName(node);
1643
+ const tag = elementName2(node);
1493
1644
  if (skipTags.has(tag.toLowerCase())) return;
1494
1645
  visit(childrenOf(node), [...ancestors, tag], [...path, String(index)]);
1495
1646
  });
@@ -1535,7 +1686,7 @@ function serializeDocument2(document) {
1535
1686
  const serialize = (node) => {
1536
1687
  if (typeof node === "string") return node.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1537
1688
  if (node.type === "text") return serialize(node.value);
1538
- const tag = elementName(node);
1689
+ const tag = elementName2(node);
1539
1690
  const nodeAttributes = elementAttributes(node);
1540
1691
  const serializedAttributes = Object.entries(nodeAttributes).map(([name, value]) => ` ${name}="${serialize(value).replace(/"/g, "&quot;")}"`).join("");
1541
1692
  const children = childrenOf(node).map(serialize).join("");
@@ -1549,7 +1700,7 @@ function flatten(document) {
1549
1700
  nodes.forEach((node, index) => {
1550
1701
  if (typeof node === "string" || node.type === "text") return;
1551
1702
  const currentPath = `${path}/${index}`;
1552
- result.push({ name: elementName(node), attributes: elementAttributes(node), path: currentPath });
1703
+ result.push({ name: elementName2(node), attributes: elementAttributes(node), path: currentPath });
1553
1704
  visit(childrenOf(node), currentPath);
1554
1705
  });
1555
1706
  };
@@ -1747,6 +1898,86 @@ var AZURE_VOICE_DEFINITIONS = [
1747
1898
  ];
1748
1899
 
1749
1900
  // packages/ssml-core/src/azureValidation.ts
1901
+ function createAzureUrlValidatorRunner(validator, options = {}) {
1902
+ if (typeof validator !== "function") throw new TypeError("A URL validator function is required.");
1903
+ const concurrency = options.concurrency === void 0 ? Infinity : Number.isFinite(options.concurrency) ? Math.max(1, Math.floor(options.concurrency)) : Infinity;
1904
+ const cache = options.cache ?? /* @__PURE__ */ new Map();
1905
+ const inFlight = /* @__PURE__ */ new Map();
1906
+ const waiters = [];
1907
+ let active = 0;
1908
+ const configuredSignal = options.signal ?? new AbortController().signal;
1909
+ const acquire = async (signal) => {
1910
+ if (signal.aborted) throw new Error("URL validation was aborted.");
1911
+ if (active < concurrency) {
1912
+ active += 1;
1913
+ return;
1914
+ }
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
+ });
1930
+ active += 1;
1931
+ };
1932
+ const release = () => {
1933
+ active -= 1;
1934
+ waiters.shift()?.();
1935
+ };
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);
1940
+ if (cached !== void 0) return cached;
1941
+ const existing = inFlight.get(key);
1942
+ if (existing) return existing;
1943
+ const promise = (async () => {
1944
+ await acquire(signal);
1945
+ try {
1946
+ if (signal.aborted) throw new Error("URL validation was aborted.");
1947
+ const validation = Promise.resolve(validator(url, context, signal));
1948
+ let timer;
1949
+ let abortHandler;
1950
+ const cancellation = new Promise((_resolve, reject) => {
1951
+ abortHandler = () => reject(new Error("URL validation was aborted."));
1952
+ signal.addEventListener("abort", abortHandler, { once: true });
1953
+ if (options.timeoutMs !== void 0 && options.timeoutMs > 0) {
1954
+ timer = setTimeout(
1955
+ () => reject(new Error(`URL validation timed out after ${options.timeoutMs} ms.`)),
1956
+ options.timeoutMs
1957
+ );
1958
+ }
1959
+ });
1960
+ try {
1961
+ const result = await (timer || abortHandler ? Promise.race([validation, cancellation]) : validation);
1962
+ cache.set(key, result);
1963
+ return result;
1964
+ } finally {
1965
+ if (timer) clearTimeout(timer);
1966
+ if (abortHandler) signal.removeEventListener("abort", abortHandler);
1967
+ }
1968
+ } finally {
1969
+ release();
1970
+ }
1971
+ })();
1972
+ inFlight.set(key, promise);
1973
+ try {
1974
+ return await promise;
1975
+ } finally {
1976
+ inFlight.delete(key);
1977
+ }
1978
+ };
1979
+ return (url, context, signal) => check(url, context, signal ?? configuredSignal);
1980
+ }
1750
1981
  var ALLOWED_BREAK_STRENGTHS = /* @__PURE__ */ new Set(["none", "x-weak", "weak", "medium", "strong", "x-strong"]);
1751
1982
  var ALLOWED_SAY_AS = /* @__PURE__ */ new Set([
1752
1983
  "characters",
@@ -2041,23 +2272,23 @@ function validateVoiceFeatureMatrix(token, source, diagnostics, voiceName, defin
2041
2272
  );
2042
2273
  }
2043
2274
  }
2044
- function validateAudioSource(token, source, diagnostics, options, elementName2) {
2275
+ function validateAudioSource(token, source, diagnostics, options, elementName3) {
2045
2276
  const src = attr(token, "src");
2046
2277
  if (!src) {
2047
- addDiagnostic(diagnostics, source, token.start, `<${elementName2}> requires a "src" attribute.`);
2278
+ addDiagnostic(diagnostics, source, token.start, `<${elementName3}> requires a "src" attribute.`);
2048
2279
  return;
2049
2280
  }
2050
2281
  let parsed;
2051
2282
  try {
2052
2283
  parsed = new URL(src);
2053
2284
  } catch {
2054
- addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must be an absolute HTTP(S) URL.`);
2285
+ addDiagnostic(diagnostics, source, token.start, `<${elementName3} src> must be an absolute HTTP(S) URL.`);
2055
2286
  return;
2056
2287
  }
2057
2288
  if (parsed.username || parsed.password)
2058
- addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must not contain URL credentials.`);
2289
+ addDiagnostic(diagnostics, source, token.start, `<${elementName3} src> must not contain URL credentials.`);
2059
2290
  if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
2060
- addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must use HTTPS.`);
2291
+ addDiagnostic(diagnostics, source, token.start, `<${elementName3} src> must use HTTPS.`);
2061
2292
  const isAllowedOrigin = options.allowedAudioOrigins?.some((allowedOrigin) => {
2062
2293
  try {
2063
2294
  const configured = new URL(allowedOrigin);
@@ -2069,13 +2300,13 @@ function validateAudioSource(token, source, diagnostics, options, elementName2)
2069
2300
  }
2070
2301
  }) ?? false;
2071
2302
  if (options.allowedAudioOrigins && !isAllowedOrigin)
2072
- addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> origin "${parsed.origin}" is not allowed.`);
2303
+ addDiagnostic(diagnostics, source, token.start, `<${elementName3} src> origin "${parsed.origin}" is not allowed.`);
2073
2304
  else if (!isAllowedOrigin && !options.allowExternalAudio)
2074
2305
  addDiagnostic(
2075
2306
  diagnostics,
2076
2307
  source,
2077
2308
  token.start,
2078
- `<${elementName2} src> external origin "${parsed.origin}" is blocked by default; set allowExternalAudio to true or provide allowedAudioOrigins.`
2309
+ `<${elementName3} src> external origin "${parsed.origin}" is blocked by default; set allowExternalAudio to true or provide allowedAudioOrigins.`
2079
2310
  );
2080
2311
  }
2081
2312
  function validateElement(token, source, diagnostics, voiceName, options, voiceCatalog) {
@@ -2393,6 +2624,15 @@ function validateAzureSsml(ssml, options = {}) {
2393
2624
  const diagnostics = validateAzureSsmlStatic(ssml, options);
2394
2625
  const validator = options.urlValidator ?? options.customUrlValidator;
2395
2626
  if (!validator || typeof ssml !== "string") return diagnostics;
2627
+ const runnerOptions = options.urlValidation ?? {};
2628
+ const boundedValidator = createAzureUrlValidatorRunner(validator, {
2629
+ ...runnerOptions,
2630
+ ...options.urlValidatorConcurrency !== void 0 ? { concurrency: options.urlValidatorConcurrency } : {},
2631
+ ...options.urlValidatorTimeoutMs !== void 0 ? { timeoutMs: options.urlValidatorTimeoutMs } : {},
2632
+ ...options.urlValidatorSignal ? { signal: options.urlValidatorSignal } : {},
2633
+ ...options.urlValidatorCache ? { cache: options.urlValidatorCache } : {}
2634
+ });
2635
+ const validationSignal = options.urlValidatorSignal ?? options.urlValidation?.signal ?? new AbortController().signal;
2396
2636
  let tokens;
2397
2637
  try {
2398
2638
  tokens = tokenizeElements(ssml);
@@ -2402,7 +2642,11 @@ function validateAzureSsml(ssml, options = {}) {
2402
2642
  const checks = tokens.flatMap(
2403
2643
  (token) => urlAttributes(token).map(async ({ attribute, value }) => {
2404
2644
  try {
2405
- const result = await validator(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
+ );
2406
2650
  const valid = typeof result === "boolean" ? result : result.valid;
2407
2651
  if (!valid) {
2408
2652
  const reason = typeof result === "boolean" ? void 0 : result.reason;
@@ -2448,6 +2692,7 @@ var getBuiltInVoiceCatalogMetadata = getAzureVoiceCatalogMetadata;
2448
2692
  var AzureTtsError = class extends Error {
2449
2693
  constructor(status, statusText, responseBody, requestId) {
2450
2694
  super(`Azure TTS request failed: ${status} ${statusText}`);
2695
+ this.kind = "azure-api-error";
2451
2696
  this.name = "AzureTtsError";
2452
2697
  this.status = status;
2453
2698
  this.statusText = statusText;
@@ -2463,6 +2708,44 @@ var AzureTtsSdkError = class extends AzureTtsError {
2463
2708
  this.errorDetails = errorDetails;
2464
2709
  }
2465
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
+ };
2733
+ var UnsupportedMergeFormatError = class extends Error {
2734
+ constructor(format) {
2735
+ super(`Audio format "${format}" cannot be safely concatenated; container re-multiplexing is required.`);
2736
+ this.kind = "unsupported-format-error";
2737
+ this.name = "UnsupportedMergeFormatError";
2738
+ this.format = format;
2739
+ }
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
+ }
2466
2749
  function createSpeechSdkError(error) {
2467
2750
  const message = error instanceof Error ? error.message : String(error);
2468
2751
  return new AzureTtsSdkError(message);
@@ -2471,263 +2754,9 @@ function createSpeechSdkError(error) {
2471
2754
  // packages/azure-tts-client/src/synthesis.ts
2472
2755
  var SpeechSDK2 = __toESM(require("microsoft-cognitiveservices-speech-sdk"));
2473
2756
 
2474
- // packages/azure-tts-client/src/speechConfig.ts
2475
- var import_microsoft_cognitiveservices_speech_sdk = require("microsoft-cognitiveservices-speech-sdk");
2476
-
2477
- // packages/azure-tts-client/src/outputFormats.ts
2478
- var SpeechSDK = __toESM(require("microsoft-cognitiveservices-speech-sdk"));
2479
- var DEFAULT_OUTPUT_FORMAT = "audio-16khz-128kbitrate-mono-mp3";
2480
- var OUTPUT_FORMATS = {
2481
- "raw-8khz-8bit-mono-mulaw": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz8BitMonoMULaw,
2482
- "riff-16khz-16kbps-mono-siren": SpeechSDK.SpeechSynthesisOutputFormat.Riff16Khz16KbpsMonoSiren,
2483
- "audio-16khz-16kbps-mono-siren": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz16KbpsMonoSiren,
2484
- "audio-16khz-32kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3,
2485
- "audio-16khz-128kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz128KBitRateMonoMp3,
2486
- "audio-16khz-64kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz64KBitRateMonoMp3,
2487
- "audio-24khz-48kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz48KBitRateMonoMp3,
2488
- "audio-24khz-96kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz96KBitRateMonoMp3,
2489
- "audio-24khz-160kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz160KBitRateMonoMp3,
2490
- "raw-16khz-16bit-mono-truesilk": SpeechSDK.SpeechSynthesisOutputFormat.Raw16Khz16BitMonoTrueSilk,
2491
- "riff-16khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Riff16Khz16BitMonoPcm,
2492
- "riff-8khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz16BitMonoPcm,
2493
- "riff-24khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm,
2494
- "riff-8khz-8bit-mono-mulaw": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz8BitMonoMULaw,
2495
- "raw-16khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Raw16Khz16BitMonoPcm,
2496
- "raw-24khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Raw24Khz16BitMonoPcm,
2497
- "raw-8khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz16BitMonoPcm,
2498
- "ogg-16khz-16bit-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Ogg16Khz16BitMonoOpus,
2499
- "ogg-24khz-16bit-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Ogg24Khz16BitMonoOpus,
2500
- "raw-48khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Raw48Khz16BitMonoPcm,
2501
- "riff-48khz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Riff48Khz16BitMonoPcm,
2502
- "audio-48khz-96kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio48Khz96KBitRateMonoMp3,
2503
- "audio-48khz-192kbitrate-mono-mp3": SpeechSDK.SpeechSynthesisOutputFormat.Audio48Khz192KBitRateMonoMp3,
2504
- "ogg-48khz-16bit-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Ogg48Khz16BitMonoOpus,
2505
- "webm-16khz-16bit-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Webm16Khz16BitMonoOpus,
2506
- "webm-24khz-16bit-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Webm24Khz16BitMonoOpus,
2507
- "webm-24khz-16bit-24kbps-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Webm24Khz16Bit24KbpsMonoOpus,
2508
- "raw-24khz-16bit-mono-truesilk": SpeechSDK.SpeechSynthesisOutputFormat.Raw24Khz16BitMonoTrueSilk,
2509
- "raw-8khz-8bit-mono-alaw": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz8BitMonoALaw,
2510
- "riff-8khz-8bit-mono-alaw": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz8BitMonoALaw,
2511
- "audio-16khz-16bit-32kbps-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz16Bit32KbpsMonoOpus,
2512
- "audio-24khz-16bit-48kbps-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz16Bit48KbpsMonoOpus,
2513
- "audio-24khz-16bit-24kbps-mono-opus": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz16Bit24KbpsMonoOpus,
2514
- "raw-22050hz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Raw22050Hz16BitMonoPcm,
2515
- "riff-22050hz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Riff22050Hz16BitMonoPcm,
2516
- "raw-44100hz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Raw44100Hz16BitMonoPcm,
2517
- "riff-44100hz-16bit-mono-pcm": SpeechSDK.SpeechSynthesisOutputFormat.Riff44100Hz16BitMonoPcm,
2518
- "amr-wb-16000hz": SpeechSDK.SpeechSynthesisOutputFormat.AmrWb16000Hz,
2519
- "g722-16khz-64kbps": SpeechSDK.SpeechSynthesisOutputFormat.G72216Khz64Kbps
2520
- };
2521
- function resolveOutputFormat(outputFormat) {
2522
- const resolvedFormat = OUTPUT_FORMATS[outputFormat];
2523
- if (resolvedFormat === void 0) {
2524
- throw new Error(`Unsupported Azure Speech output format: ${outputFormat}`);
2525
- }
2526
- return resolvedFormat;
2527
- }
2528
-
2529
- // packages/azure-tts-client/src/speechConfig.ts
2530
- function resolveEndpoint(config) {
2531
- const endpoint = config.endpoint?.trim() || "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
2532
- return endpoint.replace(/\{region\}/g, encodeURIComponent(config.region));
2533
- }
2534
- function createSpeechConfig(config) {
2535
- const { outputFormat = DEFAULT_OUTPUT_FORMAT, subscriptionKey } = config;
2536
- const endpoint = new URL(resolveEndpoint(config));
2537
- const speechConfig = import_microsoft_cognitiveservices_speech_sdk.SpeechConfig.fromEndpoint(endpoint, subscriptionKey);
2538
- speechConfig.speechSynthesisOutputFormat = resolveOutputFormat(outputFormat);
2539
- return speechConfig;
2540
- }
2541
-
2542
- // packages/azure-tts-client/src/synthesis.ts
2543
- function closeSpeechResources(speechConfig, synthesizer) {
2544
- try {
2545
- synthesizer.close();
2546
- } catch {
2547
- }
2548
- try {
2549
- speechConfig.close();
2550
- } catch {
2551
- }
2552
- }
2553
- var ticksToMilliseconds = (ticks) => Math.max(0, ticks) / 1e4;
2554
- async function synthesizeSsml(ssml, config) {
2555
- if (config.signal?.aborted) {
2556
- throw createSpeechSdkError("Speech synthesis was cancelled.");
2557
- }
2558
- const speechConfig = createSpeechConfig(config);
2559
- const synthesizer = new SpeechSDK2.SpeechSynthesizer(speechConfig, null);
2560
- return await new Promise((resolve, reject) => {
2561
- let resourcesClosed = false;
2562
- let settled = false;
2563
- let timeout;
2564
- let abortHandler;
2565
- const cleanup = () => {
2566
- if (timeout) clearTimeout(timeout);
2567
- if (abortHandler) config.signal?.removeEventListener("abort", abortHandler);
2568
- };
2569
- const closeResources = () => {
2570
- if (resourcesClosed) return;
2571
- resourcesClosed = true;
2572
- closeSpeechResources(speechConfig, synthesizer);
2573
- };
2574
- const rejectWithError = (error) => {
2575
- if (settled) return;
2576
- settled = true;
2577
- cleanup();
2578
- closeResources();
2579
- reject(createSpeechSdkError(error));
2580
- };
2581
- const boundaries = [];
2582
- const visemes = [];
2583
- const bookmarks = [];
2584
- synthesizer.wordBoundary = (_sender, event) => {
2585
- boundaries.push({
2586
- text: event.text,
2587
- audioOffsetMs: ticksToMilliseconds(event.audioOffset),
2588
- durationMs: ticksToMilliseconds(event.duration)
2589
- });
2590
- };
2591
- synthesizer.visemeReceived = (_sender, event) => {
2592
- visemes.push({ visemeId: event.visemeId, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });
2593
- };
2594
- synthesizer.bookmarkReached = (_sender, event) => {
2595
- bookmarks.push({ name: event.text, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });
2596
- };
2597
- const cb = (result) => {
2598
- if (settled) return;
2599
- const { reason, errorDetails } = result;
2600
- if (reason !== SpeechSDK2.ResultReason.SynthesizingAudioCompleted) {
2601
- const err = errorDetails || `Speech synthesis failed with reason ${reason}.`;
2602
- rejectWithError(err);
2603
- return;
2604
- }
2605
- settled = true;
2606
- cleanup();
2607
- closeResources();
2608
- const eventDurationMs = Math.max(
2609
- 0,
2610
- ...(boundaries ?? []).map((boundary) => boundary.audioOffsetMs + boundary.durationMs),
2611
- ...(visemes ?? []).map((viseme) => viseme.audioOffsetMs),
2612
- ...(bookmarks ?? []).map((bookmark) => bookmark.audioOffsetMs)
2613
- );
2614
- const durationMs = result.audioDuration ? ticksToMilliseconds(result.audioDuration) : eventDurationMs;
2615
- const requestId = result.resultId;
2616
- const addSourceMetadata = (event) => ({
2617
- ...event,
2618
- ...config.sourceTextRange ? { textRange: { ...config.sourceTextRange } } : {},
2619
- ...requestId ? { requestId } : {}
2620
- });
2621
- const sourceBoundaries = boundaries.map((boundary) => addSourceMetadata(boundary));
2622
- const sourceVisemes = visemes.map((viseme) => addSourceMetadata(viseme));
2623
- const sourceBookmarks = bookmarks.map((bookmark) => addSourceMetadata(bookmark));
2624
- resolve({
2625
- audioData: result.audioData,
2626
- durationMs,
2627
- ...config.sourceTextRange ? { textRange: { ...config.sourceTextRange } } : {},
2628
- ...requestId ? { requestId } : {},
2629
- ...sourceBoundaries.length > 0 ? { boundaries: sourceBoundaries, wordBoundary: sourceBoundaries, wordBoundaries: sourceBoundaries } : {},
2630
- ...sourceVisemes.length > 0 ? { visemes: sourceVisemes } : {},
2631
- ...sourceBookmarks.length > 0 ? { bookmarks: sourceBookmarks } : {}
2632
- });
2633
- };
2634
- try {
2635
- if (config.signal) {
2636
- abortHandler = () => rejectWithError("Speech synthesis was cancelled.");
2637
- config.signal.addEventListener("abort", abortHandler, { once: true });
2638
- }
2639
- if (config.timeoutMs !== void 0 && config.timeoutMs > 0) {
2640
- timeout = setTimeout(
2641
- () => rejectWithError(`Speech synthesis timed out after ${config.timeoutMs} ms.`),
2642
- config.timeoutMs
2643
- );
2644
- }
2645
- synthesizer.speakSsmlAsync(ssml, cb, rejectWithError);
2646
- } catch (error) {
2647
- rejectWithError(error);
2648
- }
2649
- });
2650
- }
2651
- async function synthesizeSsmlChunks(chunks, config) {
2652
- const results = [];
2653
- const totalChunks = chunks.length;
2654
- for (const [index, chunk] of chunks.entries()) {
2655
- const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
2656
- const result = await synthesizeSsml(input.ssml, {
2657
- ...config,
2658
- ...input.originalTextRange ? { sourceTextRange: input.originalTextRange } : {},
2659
- onProgress: void 0
2660
- });
2661
- results.push(result);
2662
- config.onProgress?.({
2663
- currentChunk: index + 1,
2664
- totalChunks,
2665
- percent: totalChunks === 0 ? 100 : Math.round((index + 1) / totalChunks * 100)
2666
- });
2667
- }
2668
- return mergeSynthesisResults(results);
2669
- }
2670
- function mergeSynthesisResults(results) {
2671
- const audioLength = results.reduce((total, result) => total + result.audioData.byteLength, 0);
2672
- const audioData = new Uint8Array(audioLength);
2673
- const boundaries = [];
2674
- const visemes = [];
2675
- const bookmarks = [];
2676
- let byteOffset = 0;
2677
- let durationOffset = 0;
2678
- for (const result of results) {
2679
- audioData.set(new Uint8Array(result.audioData), byteOffset);
2680
- byteOffset += result.audioData.byteLength;
2681
- const chunkBoundaries = result.boundaries && result.boundaries.length > 0 ? result.boundaries : result.wordBoundary ?? result.wordBoundaries ?? [];
2682
- for (const boundary of chunkBoundaries) {
2683
- const textRange = boundary.textRange ?? result.textRange;
2684
- const requestId = boundary.requestId ?? result.requestId;
2685
- boundaries.push({
2686
- ...boundary,
2687
- audioOffsetMs: boundary.audioOffsetMs + durationOffset,
2688
- ...textRange ? { textRange: { ...textRange } } : {},
2689
- ...requestId ? { requestId } : {}
2690
- });
2691
- }
2692
- for (const viseme of result.visemes ?? []) {
2693
- const textRange = viseme.textRange ?? result.textRange;
2694
- const requestId = viseme.requestId ?? result.requestId;
2695
- visemes.push({
2696
- ...viseme,
2697
- audioOffsetMs: viseme.audioOffsetMs + durationOffset,
2698
- ...textRange ? { textRange: { ...textRange } } : {},
2699
- ...requestId ? { requestId } : {}
2700
- });
2701
- }
2702
- for (const bookmark of result.bookmarks ?? []) {
2703
- const textRange = bookmark.textRange ?? result.textRange;
2704
- const requestId = bookmark.requestId ?? result.requestId;
2705
- bookmarks.push({
2706
- ...bookmark,
2707
- audioOffsetMs: bookmark.audioOffsetMs + durationOffset,
2708
- ...textRange ? { textRange: { ...textRange } } : {},
2709
- ...requestId ? { requestId } : {}
2710
- });
2711
- }
2712
- durationOffset += Math.max(0, result.durationMs);
2713
- }
2714
- return {
2715
- audioData: audioData.buffer,
2716
- durationMs: durationOffset,
2717
- ...boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {},
2718
- ...visemes.length > 0 ? { visemes } : {},
2719
- ...bookmarks.length > 0 ? { bookmarks } : {},
2720
- ...results.length === 1 && results[0]?.requestId ? { requestId: results[0].requestId } : {},
2721
- ...results.length === 1 && results[0]?.textRange ? { textRange: { ...results[0].textRange } } : {}
2722
- };
2723
- }
2724
- async function synthesizeSpeech(ssml, config) {
2725
- return (await synthesizeSsml(ssml, config)).audioData;
2726
- }
2727
-
2728
- // packages/ssml-core/dist/index.mjs
2729
- var __typeError2 = (msg) => {
2730
- throw TypeError(msg);
2757
+ // packages/ssml-core/dist/index.mjs
2758
+ var __typeError2 = (msg) => {
2759
+ throw TypeError(msg);
2731
2760
  };
2732
2761
  var __accessCheck2 = (obj, member, msg) => member.has(obj) || __typeError2("Cannot " + msg);
2733
2762
  var __privateGet2 = (obj, member, getter) => (__accessCheck2(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
@@ -3376,6 +3405,130 @@ function parseSsml2(xmlString) {
3376
3405
  }
3377
3406
  return document;
3378
3407
  }
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
+ }
3379
3532
  var AZURE_VOICE_DEFINITIONS2 = [
3380
3533
  { name: "de-DE-ConradNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
3381
3534
  { name: "de-DE-KatjaNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
@@ -3496,6 +3649,86 @@ var AZURE_VOICE_DEFINITIONS2 = [
3496
3649
  },
3497
3650
  { name: "zh-TW-HsiaoChenNeural", locale: "zh-TW" }
3498
3651
  ];
3652
+ function createAzureUrlValidatorRunner2(validator, options = {}) {
3653
+ if (typeof validator !== "function") throw new TypeError("A URL validator function is required.");
3654
+ const concurrency = options.concurrency === void 0 ? Infinity : Number.isFinite(options.concurrency) ? Math.max(1, Math.floor(options.concurrency)) : Infinity;
3655
+ const cache = options.cache ?? /* @__PURE__ */ new Map();
3656
+ const inFlight = /* @__PURE__ */ new Map();
3657
+ const waiters = [];
3658
+ let active = 0;
3659
+ const configuredSignal = options.signal ?? new AbortController().signal;
3660
+ const acquire = async (signal) => {
3661
+ if (signal.aborted) throw new Error("URL validation was aborted.");
3662
+ if (active < concurrency) {
3663
+ active += 1;
3664
+ return;
3665
+ }
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
+ });
3681
+ active += 1;
3682
+ };
3683
+ const release = () => {
3684
+ active -= 1;
3685
+ waiters.shift()?.();
3686
+ };
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);
3691
+ if (cached !== void 0) return cached;
3692
+ const existing = inFlight.get(key);
3693
+ if (existing) return existing;
3694
+ const promise = (async () => {
3695
+ await acquire(signal);
3696
+ try {
3697
+ if (signal.aborted) throw new Error("URL validation was aborted.");
3698
+ const validation = Promise.resolve(validator(url, context, signal));
3699
+ let timer;
3700
+ let abortHandler;
3701
+ const cancellation = new Promise((_resolve, reject) => {
3702
+ abortHandler = () => reject(new Error("URL validation was aborted."));
3703
+ signal.addEventListener("abort", abortHandler, { once: true });
3704
+ if (options.timeoutMs !== void 0 && options.timeoutMs > 0) {
3705
+ timer = setTimeout(
3706
+ () => reject(new Error(`URL validation timed out after ${options.timeoutMs} ms.`)),
3707
+ options.timeoutMs
3708
+ );
3709
+ }
3710
+ });
3711
+ try {
3712
+ const result = await (timer || abortHandler ? Promise.race([validation, cancellation]) : validation);
3713
+ cache.set(key, result);
3714
+ return result;
3715
+ } finally {
3716
+ if (timer) clearTimeout(timer);
3717
+ if (abortHandler) signal.removeEventListener("abort", abortHandler);
3718
+ }
3719
+ } finally {
3720
+ release();
3721
+ }
3722
+ })();
3723
+ inFlight.set(key, promise);
3724
+ try {
3725
+ return await promise;
3726
+ } finally {
3727
+ inFlight.delete(key);
3728
+ }
3729
+ };
3730
+ return (url, context, signal) => check(url, context, signal ?? configuredSignal);
3731
+ }
3499
3732
  var ALLOWED_BREAK_STRENGTHS2 = /* @__PURE__ */ new Set(["none", "x-weak", "weak", "medium", "strong", "x-strong"]);
3500
3733
  var ALLOWED_SAY_AS2 = /* @__PURE__ */ new Set([
3501
3734
  "characters",
@@ -3780,23 +4013,23 @@ function validateVoiceFeatureMatrix2(token, source, diagnostics, voiceName, defi
3780
4013
  );
3781
4014
  }
3782
4015
  }
3783
- function validateAudioSource2(token, source, diagnostics, options, elementName2) {
4016
+ function validateAudioSource2(token, source, diagnostics, options, elementName3) {
3784
4017
  const src = attr2(token, "src");
3785
4018
  if (!src) {
3786
- addDiagnostic2(diagnostics, source, token.start, `<${elementName2}> requires a "src" attribute.`);
4019
+ addDiagnostic2(diagnostics, source, token.start, `<${elementName3}> requires a "src" attribute.`);
3787
4020
  return;
3788
4021
  }
3789
4022
  let parsed;
3790
4023
  try {
3791
4024
  parsed = new URL(src);
3792
4025
  } catch {
3793
- addDiagnostic2(diagnostics, source, token.start, `<${elementName2} src> must be an absolute HTTP(S) URL.`);
4026
+ addDiagnostic2(diagnostics, source, token.start, `<${elementName3} src> must be an absolute HTTP(S) URL.`);
3794
4027
  return;
3795
4028
  }
3796
4029
  if (parsed.username || parsed.password)
3797
- addDiagnostic2(diagnostics, source, token.start, `<${elementName2} src> must not contain URL credentials.`);
4030
+ addDiagnostic2(diagnostics, source, token.start, `<${elementName3} src> must not contain URL credentials.`);
3798
4031
  if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
3799
- addDiagnostic2(diagnostics, source, token.start, `<${elementName2} src> must use HTTPS.`);
4032
+ addDiagnostic2(diagnostics, source, token.start, `<${elementName3} src> must use HTTPS.`);
3800
4033
  const isAllowedOrigin = options.allowedAudioOrigins?.some((allowedOrigin) => {
3801
4034
  try {
3802
4035
  const configured = new URL(allowedOrigin);
@@ -3808,13 +4041,13 @@ function validateAudioSource2(token, source, diagnostics, options, elementName2)
3808
4041
  }
3809
4042
  }) ?? false;
3810
4043
  if (options.allowedAudioOrigins && !isAllowedOrigin)
3811
- addDiagnostic2(diagnostics, source, token.start, `<${elementName2} src> origin "${parsed.origin}" is not allowed.`);
4044
+ addDiagnostic2(diagnostics, source, token.start, `<${elementName3} src> origin "${parsed.origin}" is not allowed.`);
3812
4045
  else if (!isAllowedOrigin && !options.allowExternalAudio)
3813
4046
  addDiagnostic2(
3814
4047
  diagnostics,
3815
4048
  source,
3816
4049
  token.start,
3817
- `<${elementName2} src> external origin "${parsed.origin}" is blocked by default; set allowExternalAudio to true or provide allowedAudioOrigins.`
4050
+ `<${elementName3} src> external origin "${parsed.origin}" is blocked by default; set allowExternalAudio to true or provide allowedAudioOrigins.`
3818
4051
  );
3819
4052
  }
3820
4053
  function validateElement2(token, source, diagnostics, voiceName, options, voiceCatalog) {
@@ -4132,6 +4365,15 @@ function validateAzureSsml2(ssml, options = {}) {
4132
4365
  const diagnostics = validateAzureSsmlStatic2(ssml, options);
4133
4366
  const validator = options.urlValidator ?? options.customUrlValidator;
4134
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;
4135
4377
  let tokens;
4136
4378
  try {
4137
4379
  tokens = tokenizeElements2(ssml);
@@ -4141,7 +4383,11 @@ function validateAzureSsml2(ssml, options = {}) {
4141
4383
  const checks = tokens.flatMap(
4142
4384
  (token) => urlAttributes2(token).map(async ({ attribute, value }) => {
4143
4385
  try {
4144
- const result = await validator(value, { tag: token.name, attribute });
4386
+ const result = await boundedValidator(
4387
+ value,
4388
+ { tag: token.name, attribute, ...options.sourceNodePath ? { sourceNodePath: options.sourceNodePath } : {} },
4389
+ validationSignal
4390
+ );
4145
4391
  const valid = typeof result === "boolean" ? result : result.valid;
4146
4392
  if (!valid) {
4147
4393
  const reason = typeof result === "boolean" ? void 0 : result.reason;
@@ -4172,51 +4418,793 @@ var AZURE_VOICE_CATALOG_METADATA2 = {
4172
4418
  voiceCount: AZURE_VOICE_DEFINITIONS2.length
4173
4419
  };
4174
4420
 
4175
- // packages/azure-tts-client/src/safe.ts
4176
- async function synthesizeSsmlSafe(client, ssml, options = {}) {
4177
- const validationOptions = options.validation ?? options;
4178
- const diagnostics = await Promise.resolve(validateAzureSsml2(ssml, validationOptions));
4179
- const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
4180
- if (errors.length > 0) {
4181
- return {
4182
- ok: false,
4183
- success: false,
4184
- status: "validation-error",
4185
- error: {
4186
- kind: "validation",
4187
- message: "SSML validation failed; the Azure Speech API was not called.",
4188
- diagnostics: errors
4189
- }
4190
- };
4191
- }
4192
- try {
4193
- return { ok: true, success: true, status: "success", value: await client.synthesizeSsml(ssml) };
4194
- } catch (error) {
4195
- const azureError = error instanceof AzureTtsError ? error : createSpeechSdkError(error);
4196
- return { ok: false, success: false, status: "azure-api-error", error: azureError };
4197
- }
4198
- }
4199
-
4200
- // packages/azure-tts-client/src/client.ts
4201
- var ENDPOINT_TEMPLATE = "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
4202
- var _options;
4203
- var AzureTtsClient = class {
4204
- constructor(options) {
4205
- __privateAdd(this, _options);
4206
- __privateSet(this, _options, options);
4207
- }
4208
- async synthesize(ssml) {
4209
- const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
4210
- const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
4211
- __privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
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);
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
+ };
4958
+ }
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
+ });
4972
+ }
4973
+ try {
4974
+ return createMergedResult(results, mergeAudioBuffers(buffers, { format }), format);
4975
+ } catch (error) {
4976
+ if (error instanceof UnsupportedMergeFormatError || error instanceof MergeError) throw error;
4977
+ throw new MergeError(`Audio buffers could not be merged for format "${format}".`, error);
4978
+ }
4979
+ }
4980
+ async function synthesizeSpeech(ssml, config) {
4981
+ return (await synthesizeSsml(ssml, config)).audioData;
4982
+ }
4983
+
4984
+ // packages/azure-tts-client/src/safe.ts
4985
+ var ChunkValidationError = class extends Error {
4986
+ constructor(chunkIndex, diagnostics) {
4987
+ super(`SSML validation failed for chunk ${chunkIndex}; the Azure Speech API was not called.`);
4988
+ this.kind = "validation-error";
4989
+ this.name = "ChunkValidationError";
4990
+ this.chunkIndex = chunkIndex;
4991
+ this.diagnostics = diagnostics;
4992
+ }
4993
+ };
4994
+ function failure(error) {
4995
+ return { ok: false, success: false, status: error.kind, error };
4996
+ }
4997
+ async function synthesizeSsmlSafe(client, ssml, options = {}) {
4998
+ const validationOptions = withValidationSignal(options.validation ?? options, options.signal);
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
+ }
5004
+ const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
5005
+ if (errors.length > 0) {
5006
+ return failure({
5007
+ kind: "validation-error",
5008
+ message: "SSML validation failed; the Azure Speech API was not called.",
5009
+ diagnostics: errors
5010
+ });
5011
+ }
5012
+ try {
5013
+ return {
5014
+ ok: true,
5015
+ success: true,
5016
+ status: "success",
5017
+ value: await client.synthesizeSsml(ssml, { signal: options.signal })
5018
+ };
5019
+ } catch (error) {
5020
+ const synthesisError = toSynthesisError(error);
5021
+ return failure(synthesisError);
5022
+ }
5023
+ }
5024
+ async function synthesizeSsmlChunksSafe(client, chunks, 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
+ }
5030
+ const pending = (index, status, error) => {
5031
+ options.onProgress?.({
5032
+ currentChunk: status === "success" ? index + 1 : index,
5033
+ totalChunks: chunks.length,
5034
+ percent: chunks.length === 0 ? 100 : Math.round((status === "success" ? index + 1 : index) / chunks.length * 100),
5035
+ chunkIndex: index,
5036
+ originalTextRange: typeof chunks[index] === "string" ? void 0 : chunks[index]?.originalTextRange,
5037
+ status,
5038
+ durationMs: 0,
5039
+ ...error ? { error } : {}
5040
+ });
5041
+ };
5042
+ chunks.forEach((_chunk, index) => {
5043
+ pending(index, "pending");
5044
+ });
5045
+ const validations = await Promise.all(
5046
+ chunks.map(async (chunk) => {
5047
+ const ssml = typeof chunk === "string" ? chunk : chunk.ssml;
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
+ );
5052
+ return diagnostics.filter((diagnostic) => diagnostic.severity === "error");
5053
+ })
5054
+ );
5055
+ const firstInvalidIndex = validations.findIndex((diagnostics) => diagnostics.length > 0);
5056
+ if (firstInvalidIndex >= 0) {
5057
+ const error = new ChunkValidationError(firstInvalidIndex, validations[firstInvalidIndex] ?? []);
5058
+ pending(firstInvalidIndex, "failed", error);
5059
+ return failure(error);
5060
+ }
5061
+ try {
5062
+ if (client.synthesizeChunks) {
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
+ });
5074
+ return { ok: true, success: true, status: "success", value };
5075
+ }
5076
+ const results = [];
5077
+ for (const [index, chunk] of chunks.entries()) {
5078
+ const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
5079
+ const sourceNodePath = input.sourceNodePath;
5080
+ const originalTextRange = input.originalTextRange;
5081
+ pending(index, "synthesizing");
5082
+ const startedAt = Date.now();
5083
+ try {
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
+ });
5090
+ results.push({
5091
+ ...result,
5092
+ ...input.originalTextRange ? { textRange: { ...input.originalTextRange } } : {},
5093
+ ...sourceNodePath ? {
5094
+ boundaries: result.boundaries?.map((event) => ({
5095
+ ...event,
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 }
5126
+ })),
5127
+ bookmarks: result.bookmarks?.map((event) => ({
5128
+ ...event,
5129
+ originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
5130
+ }))
5131
+ } : {}
5132
+ });
5133
+ options.onProgress?.({
5134
+ currentChunk: index + 1,
5135
+ totalChunks: chunks.length,
5136
+ percent: chunks.length === 0 ? 100 : Math.round((index + 1) / chunks.length * 100),
5137
+ chunkIndex: index,
5138
+ originalTextRange: input.originalTextRange,
5139
+ status: "success",
5140
+ durationMs: Date.now() - startedAt
5141
+ });
5142
+ } catch (error) {
5143
+ options.onProgress?.({
5144
+ currentChunk: index,
5145
+ totalChunks: chunks.length,
5146
+ percent: chunks.length === 0 ? 100 : Math.round(index / chunks.length * 100),
5147
+ chunkIndex: index,
5148
+ originalTextRange: input.originalTextRange,
5149
+ status: "failed",
5150
+ durationMs: Date.now() - startedAt,
5151
+ error
5152
+ });
5153
+ throw error;
5154
+ }
5155
+ }
5156
+ return {
5157
+ ok: true,
5158
+ success: true,
5159
+ status: "success",
5160
+ value: mergeSynthesisResults(results, {
5161
+ format: options.outputFormat ?? "audio-16khz-128kbitrate-mono-mp3"
5162
+ })
5163
+ };
5164
+ } catch (error) {
5165
+ const synthesisError = toSynthesisError(error);
5166
+ return failure(synthesisError);
5167
+ }
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
+ }
5177
+
5178
+ // packages/azure-tts-client/src/client.ts
5179
+ var ENDPOINT_TEMPLATE = "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
5180
+ var _options;
5181
+ var AzureTtsClient = class {
5182
+ constructor(options) {
5183
+ __privateAdd(this, _options);
5184
+ __privateSet(this, _options, options);
5185
+ }
5186
+ async synthesize(ssml) {
5187
+ const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
5188
+ const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
5189
+ __privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
4212
5190
  const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };
4213
5191
  return synthesizeSpeech(ssml, config);
4214
5192
  }
4215
- async synthesizeSsml(ssml) {
5193
+ async synthesizeSsml(ssml, options = {}) {
4216
5194
  const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
4217
5195
  const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
4218
5196
  __privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
4219
- 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
+ });
4220
5208
  }
4221
5209
  async synthesizeChunks(chunks, options = {}) {
4222
5210
  const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
@@ -4225,15 +5213,28 @@ var AzureTtsClient = class {
4225
5213
  endpoint,
4226
5214
  region,
4227
5215
  subscriptionKey,
4228
- outputFormat,
4229
- signal,
4230
- timeoutMs,
5216
+ outputFormat: options.outputFormat ?? outputFormat,
5217
+ signal: options.signal ?? signal,
5218
+ timeoutMs: options.timeoutMs ?? timeoutMs,
5219
+ sourceNodePath: options.sourceNodePath,
4231
5220
  onProgress: options.onProgress ?? __privateGet(this, _options).onProgress
4232
5221
  });
4233
5222
  }
4234
5223
  async synthesizeSsmlSafe(ssml, options = {}) {
4235
5224
  return synthesizeSsmlSafe(this, ssml, options);
4236
5225
  }
5226
+ async synthesizeChunksSafe(chunks, options = {}) {
5227
+ return synthesizeSsmlChunksSafe(this, chunks, {
5228
+ ...options,
5229
+ outputFormat: options.outputFormat ?? __privateGet(this, _options).outputFormat,
5230
+ signal: options.signal ?? __privateGet(this, _options).signal,
5231
+ timeoutMs: options.timeoutMs ?? __privateGet(this, _options).timeoutMs,
5232
+ onProgress: options.onProgress ?? __privateGet(this, _options).onProgress
5233
+ });
5234
+ }
5235
+ async synthesizeSsmlChunksSafe(chunks, options = {}) {
5236
+ return this.synthesizeChunksSafe(chunks, options);
5237
+ }
4237
5238
  };
4238
5239
  _options = new WeakMap();
4239
5240
 
@@ -4289,6 +5290,9 @@ async function fetchAzureVoiceCatalog(options) {
4289
5290
  const secondaryLocales = stringList(record.SecondaryLocaleList);
4290
5291
  const styles = stringList(record.StyleList);
4291
5292
  const status = normalizeStatus(record.Status);
5293
+ const supportedTags = stringList(record.SupportedTags);
5294
+ const unsupportedTags = stringList(record.UnsupportedTags);
5295
+ const models = stringList(record.Models);
4292
5296
  const merged = {
4293
5297
  name: existing?.name ?? name,
4294
5298
  locale: existing?.locale ?? locale,
@@ -4298,6 +5302,12 @@ async function fetchAzureVoiceCatalog(options) {
4298
5302
  if (mergedSecondaryLocales.length > 0) merged.secondaryLocales = mergedSecondaryLocales;
4299
5303
  const mergedStyles = [.../* @__PURE__ */ new Set([...existing?.styles ?? [], ...styles])];
4300
5304
  if (mergedStyles.length > 0) merged.styles = mergedStyles;
5305
+ const mergedSupportedTags = [.../* @__PURE__ */ new Set([...existing?.supportedTags ?? [], ...supportedTags])];
5306
+ if (mergedSupportedTags.length > 0) merged.supportedTags = mergedSupportedTags;
5307
+ const mergedUnsupportedTags = [.../* @__PURE__ */ new Set([...existing?.unsupportedTags ?? [], ...unsupportedTags])];
5308
+ if (mergedUnsupportedTags.length > 0) merged.unsupportedTags = mergedUnsupportedTags;
5309
+ const mergedModels = [.../* @__PURE__ */ new Set([...existing?.models ?? [], ...models])];
5310
+ if (mergedModels.length > 0) merged.models = mergedModels;
4301
5311
  if (status) merged.status = status;
4302
5312
  else if (existing?.status) merged.status = existing.status;
4303
5313
  voices.set(key, merged);
@@ -4319,24 +5329,37 @@ async function fetchAzureVoiceCatalog(options) {
4319
5329
  AzureTtsClient,
4320
5330
  AzureTtsError,
4321
5331
  AzureTtsSdkError,
5332
+ ChunkValidationError,
5333
+ DEFAULT_OUTPUT_FORMAT,
5334
+ MergeError,
5335
+ SynthesisCancelledError,
5336
+ SynthesisTimeoutError,
5337
+ UnsupportedMergeFormatError,
4322
5338
  areAzureLanguagesEquivalent,
4323
5339
  buildPartialSsml,
4324
5340
  buildSsml,
5341
+ canMergeAudioFormat,
5342
+ createAzureUrlValidatorRunner,
4325
5343
  extractSsmlText,
4326
5344
  extractSsmlTranslatableText,
4327
5345
  fetchAzureVoiceCatalog,
4328
5346
  fromPlainTextToSsml,
4329
5347
  getAzureVoiceCatalogMetadata,
4330
5348
  getBuiltInVoiceCatalogMetadata,
5349
+ getSsmlSourceMap,
4331
5350
  isValidAzureAudioDuration,
4332
5351
  mapSsmlTextNodes,
5352
+ mergeAudioBuffers,
4333
5353
  mergeSynthesisResults,
4334
5354
  normalizeAzureLanguage,
4335
5355
  parseSsml,
5356
+ resolveMergeAudioFormat,
5357
+ resolveMimeType,
4336
5358
  splitSsmlDocument,
4337
5359
  synthesizeSpeech,
4338
5360
  synthesizeSsml,
4339
5361
  synthesizeSsmlChunks,
5362
+ synthesizeSsmlChunksSafe,
4340
5363
  synthesizeSsmlSafe,
4341
5364
  validateAzureSsml,
4342
5365
  validateSsml,