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