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