ssml-builder-js 2.12.0 → 2.14.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 +34 -1
- package/dist/{chunk-4BVNAUVR.mjs → chunk-AQ55MOPU.mjs} +246 -21
- package/dist/chunk-AQ55MOPU.mjs.map +1 -0
- package/dist/chunk-QFIBPCO4.mjs +1816 -0
- package/dist/chunk-QFIBPCO4.mjs.map +1 -0
- package/dist/{chunk-FHDFRM2F.mjs → chunk-UZSCXPC5.mjs} +248 -1722
- package/dist/chunk-UZSCXPC5.mjs.map +1 -0
- package/dist/core.d.mts +62 -3
- package/dist/core.d.ts +62 -3
- package/dist/core.js +246 -20
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +3 -1
- package/dist/elements.js +196 -8
- package/dist/elements.js.map +1 -1
- package/dist/elements.mjs +6 -4
- package/dist/elements.mjs.map +1 -1
- package/dist/index.d-CUXRwSw0.d.mts +232 -0
- package/dist/index.d-CUXRwSw0.d.ts +232 -0
- package/dist/index.d.mts +151 -2
- package/dist/index.d.ts +151 -2
- package/dist/index.js +2264 -41
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +479 -4
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +65 -163
- package/dist/react.d.ts +65 -163
- package/dist/react.js +498 -22
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +162 -18
- package/dist/react.mjs.map +1 -1
- package/package.json +3 -2
- package/dist/chunk-4BVNAUVR.mjs.map +0 -1
- package/dist/chunk-FHDFRM2F.mjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -40,9 +40,13 @@ __export(src_exports, {
|
|
|
40
40
|
AzureTtsClient: () => AzureTtsClient,
|
|
41
41
|
AzureTtsError: () => AzureTtsError,
|
|
42
42
|
AzureTtsSdkError: () => AzureTtsSdkError,
|
|
43
|
+
ChunkValidationError: () => ChunkValidationError,
|
|
44
|
+
UnsupportedMergeFormatError: () => UnsupportedMergeFormatError,
|
|
43
45
|
areAzureLanguagesEquivalent: () => areAzureLanguagesEquivalent,
|
|
44
46
|
buildPartialSsml: () => buildPartialSsml,
|
|
45
47
|
buildSsml: () => buildSsml,
|
|
48
|
+
canMergeAudioFormat: () => canMergeAudioFormat,
|
|
49
|
+
createAzureUrlValidatorRunner: () => createAzureUrlValidatorRunner,
|
|
46
50
|
extractSsmlText: () => extractSsmlText,
|
|
47
51
|
extractSsmlTranslatableText: () => extractSsmlTranslatableText,
|
|
48
52
|
fetchAzureVoiceCatalog: () => fetchAzureVoiceCatalog,
|
|
@@ -51,11 +55,17 @@ __export(src_exports, {
|
|
|
51
55
|
getBuiltInVoiceCatalogMetadata: () => getBuiltInVoiceCatalogMetadata,
|
|
52
56
|
isValidAzureAudioDuration: () => isValidAzureAudioDuration,
|
|
53
57
|
mapSsmlTextNodes: () => mapSsmlTextNodes,
|
|
58
|
+
mergeAudioBuffers: () => mergeAudioBuffers,
|
|
59
|
+
mergeSynthesisResults: () => mergeSynthesisResults,
|
|
54
60
|
normalizeAzureLanguage: () => normalizeAzureLanguage,
|
|
55
61
|
parseSsml: () => parseSsml,
|
|
62
|
+
resolveMergeAudioFormat: () => resolveMergeAudioFormat,
|
|
56
63
|
splitSsmlDocument: () => splitSsmlDocument,
|
|
57
64
|
synthesizeSpeech: () => synthesizeSpeech,
|
|
58
65
|
synthesizeSsml: () => synthesizeSsml,
|
|
66
|
+
synthesizeSsmlChunks: () => synthesizeSsmlChunks,
|
|
67
|
+
synthesizeSsmlChunksSafe: () => synthesizeSsmlChunksSafe,
|
|
68
|
+
synthesizeSsmlSafe: () => synthesizeSsmlSafe,
|
|
59
69
|
validateAzureSsml: () => validateAzureSsml,
|
|
60
70
|
validateSsml: () => validateSsml,
|
|
61
71
|
validateSsmlStructureIntegrity: () => validateSsmlStructureIntegrity
|
|
@@ -1061,30 +1071,138 @@ function splitNode(document, node, maxLength, context = []) {
|
|
|
1061
1071
|
flush();
|
|
1062
1072
|
return parts;
|
|
1063
1073
|
}
|
|
1064
|
-
function
|
|
1065
|
-
if (
|
|
1074
|
+
function textFromNode(node) {
|
|
1075
|
+
if (typeof node === "string") return node;
|
|
1076
|
+
if (node.type === "text") return node.value;
|
|
1077
|
+
return (node.children ?? []).map(textFromNode).join("");
|
|
1078
|
+
}
|
|
1079
|
+
function collectMarks(node, marks) {
|
|
1080
|
+
if (typeof node === "string" || node.type === "text") return;
|
|
1081
|
+
if (node.type === "mark" && node.name) marks.push(node.name);
|
|
1082
|
+
if (node.type === "bookmark" && node.mark) marks.push(node.mark);
|
|
1083
|
+
for (const child of node.children ?? []) collectMarks(child, marks);
|
|
1084
|
+
}
|
|
1085
|
+
function collectInheritedContext(nodes) {
|
|
1086
|
+
const context = {};
|
|
1087
|
+
const visit = (node) => {
|
|
1088
|
+
if (typeof node === "string" || node.type === "text") return;
|
|
1089
|
+
if (context.voice === void 0 && node.type === "voice" && node.name) context.voice = node.name;
|
|
1090
|
+
if (context.lang === void 0 && node.type === "lang" && node.lang) context.lang = node.lang;
|
|
1091
|
+
if (context.prosody === void 0 && node.type === "prosody") {
|
|
1092
|
+
const prosody = {};
|
|
1093
|
+
for (const [key, value] of Object.entries(node.attributes ?? {})) prosody[key] = String(value);
|
|
1094
|
+
for (const key of ["rate", "pitch", "volume", "contour", "range"]) {
|
|
1095
|
+
const value = node[key];
|
|
1096
|
+
if (value !== void 0) prosody[key] = String(value);
|
|
1097
|
+
}
|
|
1098
|
+
if (Object.keys(prosody).length > 0) context.prosody = prosody;
|
|
1099
|
+
}
|
|
1100
|
+
for (const child of node.children ?? []) visit(child);
|
|
1101
|
+
};
|
|
1102
|
+
nodes.forEach(visit);
|
|
1103
|
+
return context;
|
|
1104
|
+
}
|
|
1105
|
+
function elementName(node) {
|
|
1106
|
+
return node.type === "custom" || node.type === "element" ? node.name : node.type;
|
|
1107
|
+
}
|
|
1108
|
+
function findSourceNodePath(nodes, targetOffset) {
|
|
1109
|
+
let textOffset = 0;
|
|
1110
|
+
let firstPath;
|
|
1111
|
+
let foundPath;
|
|
1112
|
+
const visit = (node, path) => {
|
|
1113
|
+
if (typeof node === "string" || node.type === "text") {
|
|
1114
|
+
const text = typeof node === "string" ? node : node.value;
|
|
1115
|
+
if (text && firstPath === void 0) firstPath = [...path];
|
|
1116
|
+
if (text && foundPath === void 0 && targetOffset < textOffset + text.length) foundPath = [...path];
|
|
1117
|
+
textOffset += text.length;
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
node.children?.forEach((child, index) => {
|
|
1121
|
+
const childPath = typeof child === "string" || child.type === "text" ? path : [...path, `${elementName(child)}[${index}]`];
|
|
1122
|
+
visit(child, childPath);
|
|
1123
|
+
});
|
|
1124
|
+
};
|
|
1125
|
+
nodes.forEach((node, index) => {
|
|
1126
|
+
if (!foundPath) {
|
|
1127
|
+
if (typeof node === "string" || node.type === "text") visit(node, ["speak"]);
|
|
1128
|
+
else visit(node, ["speak", `${elementName(node)}[${index}]`]);
|
|
1129
|
+
}
|
|
1130
|
+
});
|
|
1131
|
+
return foundPath ?? firstPath;
|
|
1132
|
+
}
|
|
1133
|
+
function createChunk(document, nodes, chunkIndex, textStart, backgroundAudio, replicateBackgroundAudio) {
|
|
1134
|
+
const chunkNodes = backgroundAudio && (replicateBackgroundAudio || chunkIndex === 0) ? [backgroundAudio, ...nodes] : nodes;
|
|
1135
|
+
const text = nodes.map(textFromNode).join("");
|
|
1136
|
+
const marks = [];
|
|
1137
|
+
for (const node of nodes) collectMarks(node, marks);
|
|
1138
|
+
const inheritedContext = collectInheritedContext(nodes);
|
|
1139
|
+
if (inheritedContext.lang === void 0 && document.lang) inheritedContext.lang = document.lang;
|
|
1140
|
+
return {
|
|
1141
|
+
chunkIndex,
|
|
1142
|
+
ssml: documentWithChildren(document, chunkNodes),
|
|
1143
|
+
originalTextRange: { start: textStart, end: textStart + text.length },
|
|
1144
|
+
inheritedContext,
|
|
1145
|
+
containedMarks: marks,
|
|
1146
|
+
hasBackgroundAudio: chunkNodes.some(
|
|
1147
|
+
(node) => typeof node !== "string" && node.type !== "text" && node.type === "mstts:backgroundaudio"
|
|
1148
|
+
),
|
|
1149
|
+
sourceNodePath: findSourceNodePath(document.children ?? [], textStart)
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
function splitSsmlDocument(ssml, maxLength = DEFAULT_MAX_LENGTH, options = {}) {
|
|
1153
|
+
const resolvedMaxLength = typeof maxLength === "number" ? maxLength : maxLength.maxLength ?? DEFAULT_MAX_LENGTH;
|
|
1154
|
+
const resolvedOptions = typeof maxLength === "number" ? options : maxLength;
|
|
1155
|
+
if (!Number.isInteger(resolvedMaxLength) || resolvedMaxLength <= 0) {
|
|
1066
1156
|
throw new RangeError("maxLength must be a positive integer");
|
|
1067
1157
|
}
|
|
1068
1158
|
const document = parseSsml(ssml);
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1159
|
+
const backgroundAudio = (document.children ?? []).find(
|
|
1160
|
+
(node) => typeof node !== "string" && node.type !== "text" && node.type === "mstts:backgroundaudio"
|
|
1161
|
+
);
|
|
1162
|
+
if (ssml.length <= resolvedMaxLength) {
|
|
1163
|
+
return [createChunk(document, document.children ?? [], 0, 0, backgroundAudio, true)];
|
|
1164
|
+
}
|
|
1165
|
+
const contentChildren = (document.children ?? []).filter((node) => node !== backgroundAudio);
|
|
1166
|
+
const plainDocumentLength = documentWithChildren(document, []).length;
|
|
1167
|
+
const backgroundDocumentLength = backgroundAudio ? documentWithChildren(document, [backgroundAudio]).length : plainDocumentLength;
|
|
1168
|
+
const backgroundOverhead = Math.max(0, backgroundDocumentLength - plainDocumentLength);
|
|
1169
|
+
const contentMaxLength = Math.max(1, resolvedMaxLength - backgroundOverhead);
|
|
1170
|
+
const splitChildren = contentChildren.flatMap((child) => splitNode(document, child, contentMaxLength));
|
|
1072
1171
|
const chunks = [];
|
|
1073
1172
|
let group = [];
|
|
1074
1173
|
for (const child of splitChildren) {
|
|
1075
1174
|
const candidate = [...group, child];
|
|
1076
|
-
if (documentWithChildren(document, candidate).length <=
|
|
1175
|
+
if (documentWithChildren(document, candidate).length <= contentMaxLength) {
|
|
1077
1176
|
group = candidate;
|
|
1078
1177
|
continue;
|
|
1079
1178
|
}
|
|
1080
1179
|
if (group.length > 0) chunks.push(group);
|
|
1081
1180
|
group = [child];
|
|
1082
|
-
if (documentWithChildren(document, group).length >
|
|
1181
|
+
if (documentWithChildren(document, group).length > contentMaxLength) {
|
|
1083
1182
|
throw new RangeError("maxLength is too small to contain the SSML document wrapper");
|
|
1084
1183
|
}
|
|
1085
1184
|
}
|
|
1086
1185
|
if (group.length > 0) chunks.push(group);
|
|
1087
|
-
|
|
1186
|
+
if (chunks.length === 0) {
|
|
1187
|
+
const result = createChunk(document, [], 0, 0, backgroundAudio, resolvedOptions.replicateBackgroundAudio ?? false);
|
|
1188
|
+
if (result.ssml.length > resolvedMaxLength) {
|
|
1189
|
+
throw new RangeError("maxLength is too small to contain the SSML document wrapper");
|
|
1190
|
+
}
|
|
1191
|
+
return [result];
|
|
1192
|
+
}
|
|
1193
|
+
let textStart = 0;
|
|
1194
|
+
return chunks.map((chunk, chunkIndex) => {
|
|
1195
|
+
const result = createChunk(
|
|
1196
|
+
document,
|
|
1197
|
+
chunk,
|
|
1198
|
+
chunkIndex,
|
|
1199
|
+
textStart,
|
|
1200
|
+
backgroundAudio,
|
|
1201
|
+
resolvedOptions.replicateBackgroundAudio ?? false
|
|
1202
|
+
);
|
|
1203
|
+
textStart = result.originalTextRange.end;
|
|
1204
|
+
return result;
|
|
1205
|
+
});
|
|
1088
1206
|
}
|
|
1089
1207
|
|
|
1090
1208
|
// packages/ssml-core/src/validation.ts
|
|
@@ -1254,7 +1372,7 @@ async function mapSsmlTextNodes(ssml, transform, options = {}) {
|
|
|
1254
1372
|
|
|
1255
1373
|
// packages/ssml-core/src/migration.ts
|
|
1256
1374
|
var DEFAULT_TRANSLATION_SKIP_TAGS = ["phoneme", "say-as", "sayAs", "sub"];
|
|
1257
|
-
function
|
|
1375
|
+
function elementName2(element) {
|
|
1258
1376
|
switch (element.type) {
|
|
1259
1377
|
case "custom":
|
|
1260
1378
|
case "element":
|
|
@@ -1407,7 +1525,7 @@ function extractSsmlTranslatableText(ssml, options = {}) {
|
|
|
1407
1525
|
}
|
|
1408
1526
|
return;
|
|
1409
1527
|
}
|
|
1410
|
-
const tag =
|
|
1528
|
+
const tag = elementName2(node);
|
|
1411
1529
|
if (skipTags.has(tag.toLowerCase())) return;
|
|
1412
1530
|
visit(childrenOf(node), [...ancestors, tag], [...path, String(index)]);
|
|
1413
1531
|
});
|
|
@@ -1453,7 +1571,7 @@ function serializeDocument2(document) {
|
|
|
1453
1571
|
const serialize = (node) => {
|
|
1454
1572
|
if (typeof node === "string") return node.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1455
1573
|
if (node.type === "text") return serialize(node.value);
|
|
1456
|
-
const tag =
|
|
1574
|
+
const tag = elementName2(node);
|
|
1457
1575
|
const nodeAttributes = elementAttributes(node);
|
|
1458
1576
|
const serializedAttributes = Object.entries(nodeAttributes).map(([name, value]) => ` ${name}="${serialize(value).replace(/"/g, """)}"`).join("");
|
|
1459
1577
|
const children = childrenOf(node).map(serialize).join("");
|
|
@@ -1467,7 +1585,7 @@ function flatten(document) {
|
|
|
1467
1585
|
nodes.forEach((node, index) => {
|
|
1468
1586
|
if (typeof node === "string" || node.type === "text") return;
|
|
1469
1587
|
const currentPath = `${path}/${index}`;
|
|
1470
|
-
result.push({ name:
|
|
1588
|
+
result.push({ name: elementName2(node), attributes: elementAttributes(node), path: currentPath });
|
|
1471
1589
|
visit(childrenOf(node), currentPath);
|
|
1472
1590
|
});
|
|
1473
1591
|
};
|
|
@@ -1665,6 +1783,69 @@ var AZURE_VOICE_DEFINITIONS = [
|
|
|
1665
1783
|
];
|
|
1666
1784
|
|
|
1667
1785
|
// packages/ssml-core/src/azureValidation.ts
|
|
1786
|
+
function createAzureUrlValidatorRunner(validator, options = {}) {
|
|
1787
|
+
if (typeof validator !== "function") throw new TypeError("A URL validator function is required.");
|
|
1788
|
+
const concurrency = options.concurrency === void 0 ? Infinity : Number.isFinite(options.concurrency) ? Math.max(1, Math.floor(options.concurrency)) : Infinity;
|
|
1789
|
+
const cache = options.cache ?? /* @__PURE__ */ new Map();
|
|
1790
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
1791
|
+
const waiters = [];
|
|
1792
|
+
let active = 0;
|
|
1793
|
+
const acquire = async () => {
|
|
1794
|
+
if (active < concurrency) {
|
|
1795
|
+
active += 1;
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1798
|
+
await new Promise((resolve) => waiters.push(resolve));
|
|
1799
|
+
active += 1;
|
|
1800
|
+
};
|
|
1801
|
+
const release = () => {
|
|
1802
|
+
active -= 1;
|
|
1803
|
+
waiters.shift()?.();
|
|
1804
|
+
};
|
|
1805
|
+
const check = async (url, context) => {
|
|
1806
|
+
if (options.signal?.aborted) throw new Error("URL validation was aborted.");
|
|
1807
|
+
const cached = cache.get(url);
|
|
1808
|
+
if (cached !== void 0) return cached;
|
|
1809
|
+
const existing = inFlight.get(url);
|
|
1810
|
+
if (existing) return existing;
|
|
1811
|
+
const promise = (async () => {
|
|
1812
|
+
await acquire();
|
|
1813
|
+
try {
|
|
1814
|
+
if (options.signal?.aborted) throw new Error("URL validation was aborted.");
|
|
1815
|
+
const validation = Promise.resolve(validator(url, context));
|
|
1816
|
+
let timer;
|
|
1817
|
+
let abortHandler;
|
|
1818
|
+
const cancellation = new Promise((_resolve, reject) => {
|
|
1819
|
+
abortHandler = () => reject(new Error("URL validation was aborted."));
|
|
1820
|
+
options.signal?.addEventListener("abort", abortHandler, { once: true });
|
|
1821
|
+
if (options.timeoutMs !== void 0 && options.timeoutMs > 0) {
|
|
1822
|
+
timer = setTimeout(
|
|
1823
|
+
() => reject(new Error(`URL validation timed out after ${options.timeoutMs} ms.`)),
|
|
1824
|
+
options.timeoutMs
|
|
1825
|
+
);
|
|
1826
|
+
}
|
|
1827
|
+
});
|
|
1828
|
+
try {
|
|
1829
|
+
const result = await (timer || abortHandler ? Promise.race([validation, cancellation]) : validation);
|
|
1830
|
+
cache.set(url, result);
|
|
1831
|
+
return result;
|
|
1832
|
+
} finally {
|
|
1833
|
+
if (timer) clearTimeout(timer);
|
|
1834
|
+
if (abortHandler) options.signal?.removeEventListener("abort", abortHandler);
|
|
1835
|
+
}
|
|
1836
|
+
} finally {
|
|
1837
|
+
release();
|
|
1838
|
+
}
|
|
1839
|
+
})();
|
|
1840
|
+
inFlight.set(url, promise);
|
|
1841
|
+
try {
|
|
1842
|
+
return await promise;
|
|
1843
|
+
} finally {
|
|
1844
|
+
inFlight.delete(url);
|
|
1845
|
+
}
|
|
1846
|
+
};
|
|
1847
|
+
return (url, context) => check(url, context);
|
|
1848
|
+
}
|
|
1668
1849
|
var ALLOWED_BREAK_STRENGTHS = /* @__PURE__ */ new Set(["none", "x-weak", "weak", "medium", "strong", "x-strong"]);
|
|
1669
1850
|
var ALLOWED_SAY_AS = /* @__PURE__ */ new Set([
|
|
1670
1851
|
"characters",
|
|
@@ -1959,23 +2140,23 @@ function validateVoiceFeatureMatrix(token, source, diagnostics, voiceName, defin
|
|
|
1959
2140
|
);
|
|
1960
2141
|
}
|
|
1961
2142
|
}
|
|
1962
|
-
function validateAudioSource(token, source, diagnostics, options,
|
|
2143
|
+
function validateAudioSource(token, source, diagnostics, options, elementName3) {
|
|
1963
2144
|
const src = attr(token, "src");
|
|
1964
2145
|
if (!src) {
|
|
1965
|
-
addDiagnostic(diagnostics, source, token.start, `<${
|
|
2146
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName3}> requires a "src" attribute.`);
|
|
1966
2147
|
return;
|
|
1967
2148
|
}
|
|
1968
2149
|
let parsed;
|
|
1969
2150
|
try {
|
|
1970
2151
|
parsed = new URL(src);
|
|
1971
2152
|
} catch {
|
|
1972
|
-
addDiagnostic(diagnostics, source, token.start, `<${
|
|
2153
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName3} src> must be an absolute HTTP(S) URL.`);
|
|
1973
2154
|
return;
|
|
1974
2155
|
}
|
|
1975
2156
|
if (parsed.username || parsed.password)
|
|
1976
|
-
addDiagnostic(diagnostics, source, token.start, `<${
|
|
2157
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName3} src> must not contain URL credentials.`);
|
|
1977
2158
|
if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
|
|
1978
|
-
addDiagnostic(diagnostics, source, token.start, `<${
|
|
2159
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName3} src> must use HTTPS.`);
|
|
1979
2160
|
const isAllowedOrigin = options.allowedAudioOrigins?.some((allowedOrigin) => {
|
|
1980
2161
|
try {
|
|
1981
2162
|
const configured = new URL(allowedOrigin);
|
|
@@ -1987,13 +2168,13 @@ function validateAudioSource(token, source, diagnostics, options, elementName2)
|
|
|
1987
2168
|
}
|
|
1988
2169
|
}) ?? false;
|
|
1989
2170
|
if (options.allowedAudioOrigins && !isAllowedOrigin)
|
|
1990
|
-
addDiagnostic(diagnostics, source, token.start, `<${
|
|
2171
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName3} src> origin "${parsed.origin}" is not allowed.`);
|
|
1991
2172
|
else if (!isAllowedOrigin && !options.allowExternalAudio)
|
|
1992
2173
|
addDiagnostic(
|
|
1993
2174
|
diagnostics,
|
|
1994
2175
|
source,
|
|
1995
2176
|
token.start,
|
|
1996
|
-
`<${
|
|
2177
|
+
`<${elementName3} src> external origin "${parsed.origin}" is blocked by default; set allowExternalAudio to true or provide allowedAudioOrigins.`
|
|
1997
2178
|
);
|
|
1998
2179
|
}
|
|
1999
2180
|
function validateElement(token, source, diagnostics, voiceName, options, voiceCatalog) {
|
|
@@ -2175,7 +2356,7 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceCa
|
|
|
2175
2356
|
addDiagnostic(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
|
|
2176
2357
|
}
|
|
2177
2358
|
}
|
|
2178
|
-
function
|
|
2359
|
+
function validateAzureSsmlStatic(ssml, options = {}) {
|
|
2179
2360
|
const diagnostics = [];
|
|
2180
2361
|
if (typeof ssml !== "string") {
|
|
2181
2362
|
return [
|
|
@@ -2299,6 +2480,59 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
2299
2480
|
}
|
|
2300
2481
|
return diagnostics;
|
|
2301
2482
|
}
|
|
2483
|
+
function urlAttributes(token) {
|
|
2484
|
+
const tag = canonicalTagName(token.name);
|
|
2485
|
+
const attributes = tag === "audio" || tag === "mstts:backgroundaudio" ? ["src"] : tag === "lexicon" ? ["uri"] : tag === "mstts:voiceconversion" ? ["url"] : [];
|
|
2486
|
+
return attributes.flatMap((attribute) => {
|
|
2487
|
+
const value = attr(token, attribute);
|
|
2488
|
+
return value === void 0 ? [] : [{ attribute, value }];
|
|
2489
|
+
});
|
|
2490
|
+
}
|
|
2491
|
+
function validateAzureSsml(ssml, options = {}) {
|
|
2492
|
+
const diagnostics = validateAzureSsmlStatic(ssml, options);
|
|
2493
|
+
const validator = options.urlValidator ?? options.customUrlValidator;
|
|
2494
|
+
if (!validator || typeof ssml !== "string") return diagnostics;
|
|
2495
|
+
const runnerOptions = options.urlValidation ?? {};
|
|
2496
|
+
const boundedValidator = createAzureUrlValidatorRunner(validator, {
|
|
2497
|
+
...runnerOptions,
|
|
2498
|
+
...options.urlValidatorConcurrency !== void 0 ? { concurrency: options.urlValidatorConcurrency } : {},
|
|
2499
|
+
...options.urlValidatorTimeoutMs !== void 0 ? { timeoutMs: options.urlValidatorTimeoutMs } : {},
|
|
2500
|
+
...options.urlValidatorSignal ? { signal: options.urlValidatorSignal } : {},
|
|
2501
|
+
...options.urlValidatorCache ? { cache: options.urlValidatorCache } : {}
|
|
2502
|
+
});
|
|
2503
|
+
let tokens;
|
|
2504
|
+
try {
|
|
2505
|
+
tokens = tokenizeElements(ssml);
|
|
2506
|
+
} catch {
|
|
2507
|
+
return diagnostics;
|
|
2508
|
+
}
|
|
2509
|
+
const checks = tokens.flatMap(
|
|
2510
|
+
(token) => urlAttributes(token).map(async ({ attribute, value }) => {
|
|
2511
|
+
try {
|
|
2512
|
+
const result = await boundedValidator(value, { tag: token.name, attribute });
|
|
2513
|
+
const valid = typeof result === "boolean" ? result : result.valid;
|
|
2514
|
+
if (!valid) {
|
|
2515
|
+
const reason = typeof result === "boolean" ? void 0 : result.reason;
|
|
2516
|
+
addDiagnostic(
|
|
2517
|
+
diagnostics,
|
|
2518
|
+
ssml,
|
|
2519
|
+
token.start,
|
|
2520
|
+
`<${token.name} ${attribute}> was rejected by the custom URL validator${reason ? `: ${reason}` : "."}`
|
|
2521
|
+
);
|
|
2522
|
+
}
|
|
2523
|
+
} catch (error) {
|
|
2524
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
2525
|
+
addDiagnostic(
|
|
2526
|
+
diagnostics,
|
|
2527
|
+
ssml,
|
|
2528
|
+
token.start,
|
|
2529
|
+
`<${token.name} ${attribute}> could not be validated by the custom URL validator: ${reason}`
|
|
2530
|
+
);
|
|
2531
|
+
}
|
|
2532
|
+
})
|
|
2533
|
+
);
|
|
2534
|
+
return Promise.all(checks).then(() => diagnostics);
|
|
2535
|
+
}
|
|
2302
2536
|
|
|
2303
2537
|
// packages/ssml-core/src/generated/azureVoiceCatalog.ts
|
|
2304
2538
|
var AZURE_VOICE_CATALOG_METADATA = {
|
|
@@ -2336,6 +2570,13 @@ var AzureTtsSdkError = class extends AzureTtsError {
|
|
|
2336
2570
|
this.errorDetails = errorDetails;
|
|
2337
2571
|
}
|
|
2338
2572
|
};
|
|
2573
|
+
var UnsupportedMergeFormatError = class extends Error {
|
|
2574
|
+
constructor(format) {
|
|
2575
|
+
super(`Audio format "${format}" cannot be safely concatenated; container re-multiplexing is required.`);
|
|
2576
|
+
this.name = "UnsupportedMergeFormatError";
|
|
2577
|
+
this.format = format;
|
|
2578
|
+
}
|
|
2579
|
+
};
|
|
2339
2580
|
function createSpeechSdkError(error) {
|
|
2340
2581
|
const message = error instanceof Error ? error.message : String(error);
|
|
2341
2582
|
return new AzureTtsSdkError(message);
|
|
@@ -2413,6 +2654,149 @@ function createSpeechConfig(config) {
|
|
|
2413
2654
|
}
|
|
2414
2655
|
|
|
2415
2656
|
// packages/azure-tts-client/src/synthesis.ts
|
|
2657
|
+
function ascii(bytes, offset, value) {
|
|
2658
|
+
return [...value].every((character, index) => bytes[offset + index] === character.charCodeAt(0));
|
|
2659
|
+
}
|
|
2660
|
+
function readUint32(bytes, offset) {
|
|
2661
|
+
return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(offset, true);
|
|
2662
|
+
}
|
|
2663
|
+
function parseWav(buffer) {
|
|
2664
|
+
const bytes = new Uint8Array(buffer);
|
|
2665
|
+
if (bytes.byteLength < 12 || !ascii(bytes, 0, "RIFF") || !ascii(bytes, 8, "WAVE")) {
|
|
2666
|
+
throw new Error("Invalid WAV/RIFF audio buffer.");
|
|
2667
|
+
}
|
|
2668
|
+
const chunks = [];
|
|
2669
|
+
const dataParts = [];
|
|
2670
|
+
let format;
|
|
2671
|
+
let offset = 12;
|
|
2672
|
+
while (offset < bytes.byteLength) {
|
|
2673
|
+
if (offset + 8 > bytes.byteLength) throw new Error("Invalid WAV chunk header.");
|
|
2674
|
+
const id = String.fromCharCode(...bytes.slice(offset, offset + 4));
|
|
2675
|
+
const size = readUint32(bytes, offset + 4);
|
|
2676
|
+
const dataStart = offset + 8;
|
|
2677
|
+
const dataEnd = dataStart + size;
|
|
2678
|
+
if (dataEnd > bytes.byteLength) throw new Error(`WAV chunk "${id}" exceeds the audio buffer.`);
|
|
2679
|
+
const data2 = bytes.slice(dataStart, dataEnd);
|
|
2680
|
+
chunks.push({ id, data: data2 });
|
|
2681
|
+
if (id === "fmt ") format ?? (format = data2);
|
|
2682
|
+
if (id === "data") dataParts.push(data2);
|
|
2683
|
+
offset = dataEnd + (size & 1);
|
|
2684
|
+
if (offset > bytes.byteLength) throw new Error("Invalid WAV chunk padding.");
|
|
2685
|
+
}
|
|
2686
|
+
if (!format || dataParts.length === 0) throw new Error("WAV audio must contain fmt and data chunks.");
|
|
2687
|
+
const dataLength = dataParts.reduce((total, part) => total + part.byteLength, 0);
|
|
2688
|
+
const data = new Uint8Array(dataLength);
|
|
2689
|
+
let dataOffset = 0;
|
|
2690
|
+
for (const part of dataParts) {
|
|
2691
|
+
data.set(part, dataOffset);
|
|
2692
|
+
dataOffset += part.byteLength;
|
|
2693
|
+
}
|
|
2694
|
+
return { chunks, data, format };
|
|
2695
|
+
}
|
|
2696
|
+
function writeUint32(target, offset, value) {
|
|
2697
|
+
new DataView(target.buffer).setUint32(offset, value, true);
|
|
2698
|
+
}
|
|
2699
|
+
function writeChunk(target, offset, id, data) {
|
|
2700
|
+
for (let index = 0; index < 4; index += 1) target[offset + index] = id.charCodeAt(index) ?? 0;
|
|
2701
|
+
writeUint32(target, offset + 4, data.byteLength);
|
|
2702
|
+
target.set(data, offset + 8);
|
|
2703
|
+
const end = offset + 8 + data.byteLength;
|
|
2704
|
+
if (data.byteLength & 1) target[end] = 0;
|
|
2705
|
+
return end + (data.byteLength & 1);
|
|
2706
|
+
}
|
|
2707
|
+
function mergeWavBuffers(buffers) {
|
|
2708
|
+
if (buffers.length === 0) return new ArrayBuffer(0);
|
|
2709
|
+
const parsed = buffers.map(parseWav);
|
|
2710
|
+
const first = parsed[0];
|
|
2711
|
+
if (!first) throw new Error("At least one WAV buffer is required.");
|
|
2712
|
+
if (parsed.some(
|
|
2713
|
+
(item) => item.format.length !== first.format.length || item.format.some((value, i) => value !== first.format[i])
|
|
2714
|
+
))
|
|
2715
|
+
throw new Error("WAV buffers have incompatible fmt chunks.");
|
|
2716
|
+
const dataLength = parsed.reduce((total, item) => total + item.data.byteLength, 0);
|
|
2717
|
+
const nonDataLength = first.chunks.reduce(
|
|
2718
|
+
(total, chunk) => chunk.id === "data" ? total : total + 8 + chunk.data.byteLength + (chunk.data.byteLength & 1),
|
|
2719
|
+
0
|
|
2720
|
+
);
|
|
2721
|
+
const outputLength = 12 + nonDataLength + 8 + dataLength + (dataLength & 1);
|
|
2722
|
+
if (outputLength - 8 > 4294967295) throw new RangeError("Merged WAV exceeds the RIFF format size limit.");
|
|
2723
|
+
const output = new Uint8Array(outputLength);
|
|
2724
|
+
output.set(Uint8Array.from([82, 73, 70, 70]), 0);
|
|
2725
|
+
writeUint32(output, 4, outputLength - 8);
|
|
2726
|
+
output.set(Uint8Array.from([87, 65, 86, 69]), 8);
|
|
2727
|
+
let outputOffset = 12;
|
|
2728
|
+
let dataWritten = false;
|
|
2729
|
+
for (const chunk of first.chunks) {
|
|
2730
|
+
if (chunk.id === "data") {
|
|
2731
|
+
if (dataWritten) continue;
|
|
2732
|
+
const data = new Uint8Array(dataLength);
|
|
2733
|
+
let dataOffset = 0;
|
|
2734
|
+
for (const item of parsed) {
|
|
2735
|
+
data.set(item.data, dataOffset);
|
|
2736
|
+
dataOffset += item.data.byteLength;
|
|
2737
|
+
}
|
|
2738
|
+
outputOffset = writeChunk(output, outputOffset, "data", data);
|
|
2739
|
+
dataWritten = true;
|
|
2740
|
+
} else {
|
|
2741
|
+
outputOffset = writeChunk(output, outputOffset, chunk.id, chunk.data);
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
if (!dataWritten) throw new Error("WAV audio must contain a data chunk.");
|
|
2745
|
+
return output.buffer;
|
|
2746
|
+
}
|
|
2747
|
+
function skipId3v2(bytes) {
|
|
2748
|
+
if (!ascii(bytes, 0, "ID3") || bytes.byteLength < 10) return 0;
|
|
2749
|
+
const size = [bytes[6], bytes[7], bytes[8], bytes[9]].reduce((total, value) => total << 7 | value & 127, 0);
|
|
2750
|
+
const hasFooter = (bytes[5] & 16) !== 0;
|
|
2751
|
+
return Math.min(bytes.byteLength, 10 + size + (hasFooter ? 10 : 0));
|
|
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
|
+
}
|
|
2762
|
+
function isWavFormat(format) {
|
|
2763
|
+
return /(?:wav|wave|riff)/i.test(format);
|
|
2764
|
+
}
|
|
2765
|
+
function isRawFormat(format) {
|
|
2766
|
+
return /^raw(?:-|$)/i.test(format);
|
|
2767
|
+
}
|
|
2768
|
+
function resolveMergeAudioFormat(format) {
|
|
2769
|
+
if (isWavFormat(format)) return "wav";
|
|
2770
|
+
if (isMp3Format(format)) return "mp3";
|
|
2771
|
+
if (isRawFormat(format)) return "raw";
|
|
2772
|
+
return void 0;
|
|
2773
|
+
}
|
|
2774
|
+
function canMergeAudioFormat(format) {
|
|
2775
|
+
return resolveMergeAudioFormat(format) !== void 0;
|
|
2776
|
+
}
|
|
2777
|
+
function mergeAudioBuffers(buffers, format) {
|
|
2778
|
+
if (isWavFormat(format)) return mergeWavBuffers(buffers);
|
|
2779
|
+
if (isMp3Format(format)) {
|
|
2780
|
+
const parts = buffers.map(stripMp3Tags);
|
|
2781
|
+
const output = new Uint8Array(parts.reduce((total, part) => total + part.byteLength, 0));
|
|
2782
|
+
let offset = 0;
|
|
2783
|
+
for (const part of parts) {
|
|
2784
|
+
output.set(part, offset);
|
|
2785
|
+
offset += part.byteLength;
|
|
2786
|
+
}
|
|
2787
|
+
return output.buffer;
|
|
2788
|
+
}
|
|
2789
|
+
if (isRawFormat(format)) {
|
|
2790
|
+
const output = new Uint8Array(buffers.reduce((total, buffer) => total + buffer.byteLength, 0));
|
|
2791
|
+
let offset = 0;
|
|
2792
|
+
for (const buffer of buffers) {
|
|
2793
|
+
output.set(new Uint8Array(buffer), offset);
|
|
2794
|
+
offset += buffer.byteLength;
|
|
2795
|
+
}
|
|
2796
|
+
return output.buffer;
|
|
2797
|
+
}
|
|
2798
|
+
throw new UnsupportedMergeFormatError(format);
|
|
2799
|
+
}
|
|
2416
2800
|
function closeSpeechResources(speechConfig, synthesizer) {
|
|
2417
2801
|
try {
|
|
2418
2802
|
synthesizer.close();
|
|
@@ -2485,12 +2869,26 @@ async function synthesizeSsml(ssml, config) {
|
|
|
2485
2869
|
...(bookmarks ?? []).map((bookmark) => bookmark.audioOffsetMs)
|
|
2486
2870
|
);
|
|
2487
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));
|
|
2488
2884
|
resolve({
|
|
2489
2885
|
audioData: result.audioData,
|
|
2490
2886
|
durationMs,
|
|
2491
|
-
...
|
|
2492
|
-
...
|
|
2493
|
-
...
|
|
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 } : {}
|
|
2494
2892
|
});
|
|
2495
2893
|
};
|
|
2496
2894
|
try {
|
|
@@ -2510,30 +2908,1836 @@ async function synthesizeSsml(ssml, config) {
|
|
|
2510
2908
|
}
|
|
2511
2909
|
});
|
|
2512
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
|
+
}
|
|
2513
3050
|
async function synthesizeSpeech(ssml, config) {
|
|
2514
3051
|
return (await synthesizeSsml(ssml, config)).audioData;
|
|
2515
3052
|
}
|
|
2516
3053
|
|
|
2517
|
-
// packages/
|
|
2518
|
-
var
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
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;
|
|
2524
3169
|
}
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };
|
|
2530
|
-
return synthesizeSpeech(ssml, config);
|
|
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};`);
|
|
2531
3174
|
}
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
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;
|
|
3239
|
+
}
|
|
3240
|
+
parseElement(depth) {
|
|
3241
|
+
if (depth > MAX_NESTING_DEPTH2) {
|
|
3242
|
+
this.fail("XML nesting depth exceeds the supported limit");
|
|
3243
|
+
}
|
|
3244
|
+
this.expect("<");
|
|
3245
|
+
if (this.source[__privateGet2(this, _index2)] === "/") {
|
|
3246
|
+
this.fail("Unexpected closing XML element");
|
|
3247
|
+
}
|
|
3248
|
+
const name = this.parseName();
|
|
3249
|
+
const { attributes, selfClosing } = this.parseStartTag();
|
|
3250
|
+
if (selfClosing) {
|
|
3251
|
+
return { name, attributes, children: [] };
|
|
3252
|
+
}
|
|
3253
|
+
const children = [];
|
|
3254
|
+
while (__privateGet2(this, _index2) < this.source.length) {
|
|
3255
|
+
if (this.source.startsWith("</", __privateGet2(this, _index2))) {
|
|
3256
|
+
__privateSet2(this, _index2, __privateGet2(this, _index2) + 2);
|
|
3257
|
+
const closingName = this.parseName();
|
|
3258
|
+
this.skipWhitespace();
|
|
3259
|
+
this.expect(">");
|
|
3260
|
+
if (closingName !== name) {
|
|
3261
|
+
this.fail(`Mismatched closing element: expected </${name}> but found </${closingName}>`);
|
|
3262
|
+
}
|
|
3263
|
+
return { name, attributes, children };
|
|
3264
|
+
}
|
|
3265
|
+
if (this.source.startsWith("<!--", __privateGet2(this, _index2))) {
|
|
3266
|
+
this.skipComment();
|
|
3267
|
+
continue;
|
|
3268
|
+
}
|
|
3269
|
+
if (this.source.startsWith("<![CDATA[", __privateGet2(this, _index2))) {
|
|
3270
|
+
this.appendText(children, this.parseCdata());
|
|
3271
|
+
continue;
|
|
3272
|
+
}
|
|
3273
|
+
if (this.source.startsWith("<?", __privateGet2(this, _index2))) {
|
|
3274
|
+
this.skipProcessingInstruction();
|
|
3275
|
+
continue;
|
|
3276
|
+
}
|
|
3277
|
+
if (this.source.startsWith("<!", __privateGet2(this, _index2))) {
|
|
3278
|
+
this.fail("Unsupported XML declaration inside an element");
|
|
3279
|
+
}
|
|
3280
|
+
if (this.source[__privateGet2(this, _index2)] === "<") {
|
|
3281
|
+
children.push(this.parseElement(depth + 1));
|
|
3282
|
+
} else {
|
|
3283
|
+
this.appendText(children, this.parseText());
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
this.fail(`Unclosed XML element: <${name}>`);
|
|
3287
|
+
}
|
|
3288
|
+
parseStartTag() {
|
|
3289
|
+
const attributes = {};
|
|
3290
|
+
while (__privateGet2(this, _index2) < this.source.length) {
|
|
3291
|
+
this.skipWhitespace();
|
|
3292
|
+
if (this.source.startsWith("/>", __privateGet2(this, _index2))) {
|
|
3293
|
+
__privateSet2(this, _index2, __privateGet2(this, _index2) + 2);
|
|
3294
|
+
return { attributes, selfClosing: true };
|
|
3295
|
+
}
|
|
3296
|
+
if (this.source[__privateGet2(this, _index2)] === ">") {
|
|
3297
|
+
__privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
|
|
3298
|
+
return { attributes, selfClosing: false };
|
|
3299
|
+
}
|
|
3300
|
+
const name = this.parseName();
|
|
3301
|
+
this.skipWhitespace();
|
|
3302
|
+
this.expect("=");
|
|
3303
|
+
this.skipWhitespace();
|
|
3304
|
+
const quote = this.source[__privateGet2(this, _index2)];
|
|
3305
|
+
if (quote !== '"' && quote !== "'") {
|
|
3306
|
+
this.fail(`XML attribute ${name} must use a quoted value`);
|
|
3307
|
+
}
|
|
3308
|
+
__privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
|
|
3309
|
+
const valueStart = __privateGet2(this, _index2);
|
|
3310
|
+
while (__privateGet2(this, _index2) < this.source.length && this.source[__privateGet2(this, _index2)] !== quote) {
|
|
3311
|
+
if (this.source[__privateGet2(this, _index2)] === "<") {
|
|
3312
|
+
this.fail(`Invalid "<" in XML attribute ${name}`);
|
|
3313
|
+
}
|
|
3314
|
+
__privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
|
|
3315
|
+
}
|
|
3316
|
+
if (__privateGet2(this, _index2) >= this.source.length) {
|
|
3317
|
+
this.fail(`Unclosed XML attribute ${name}`);
|
|
3318
|
+
}
|
|
3319
|
+
const value = decodeXmlEntities2(this.source.slice(valueStart, __privateGet2(this, _index2)));
|
|
3320
|
+
__privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
|
|
3321
|
+
if (hasOwn2(attributes, name)) {
|
|
3322
|
+
this.fail(`Duplicate XML attribute: ${name}`);
|
|
3323
|
+
}
|
|
3324
|
+
setAttribute2(attributes, name, value);
|
|
3325
|
+
}
|
|
3326
|
+
this.fail("Unclosed XML start tag");
|
|
3327
|
+
}
|
|
3328
|
+
parseText() {
|
|
3329
|
+
const start = __privateGet2(this, _index2);
|
|
3330
|
+
while (__privateGet2(this, _index2) < this.source.length && this.source[__privateGet2(this, _index2)] !== "<") {
|
|
3331
|
+
__privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
|
|
3332
|
+
}
|
|
3333
|
+
const value = this.source.slice(start, __privateGet2(this, _index2));
|
|
3334
|
+
if (value.includes("]]>")) {
|
|
3335
|
+
this.fail("CDATA termination is not valid in ordinary XML text");
|
|
3336
|
+
}
|
|
3337
|
+
return decodeXmlEntities2(value);
|
|
3338
|
+
}
|
|
3339
|
+
parseCdata() {
|
|
3340
|
+
__privateSet2(this, _index2, __privateGet2(this, _index2) + "<![CDATA[".length);
|
|
3341
|
+
const end = this.source.indexOf("]]>", __privateGet2(this, _index2));
|
|
3342
|
+
if (end === -1) {
|
|
3343
|
+
this.fail("Unclosed XML CDATA section");
|
|
3344
|
+
}
|
|
3345
|
+
const value = this.source.slice(__privateGet2(this, _index2), end);
|
|
3346
|
+
__privateSet2(this, _index2, end + 3);
|
|
3347
|
+
return value;
|
|
3348
|
+
}
|
|
3349
|
+
skipComment() {
|
|
3350
|
+
__privateSet2(this, _index2, __privateGet2(this, _index2) + "<!--".length);
|
|
3351
|
+
const end = this.source.indexOf("-->", __privateGet2(this, _index2));
|
|
3352
|
+
if (end === -1) {
|
|
3353
|
+
this.fail("Unclosed XML comment");
|
|
3354
|
+
}
|
|
3355
|
+
if (this.source.slice(__privateGet2(this, _index2), end).includes("--")) {
|
|
3356
|
+
this.fail("XML comments cannot contain consecutive hyphens");
|
|
3357
|
+
}
|
|
3358
|
+
__privateSet2(this, _index2, end + 3);
|
|
3359
|
+
}
|
|
3360
|
+
skipProcessingInstruction() {
|
|
3361
|
+
__privateSet2(this, _index2, __privateGet2(this, _index2) + "<?".length);
|
|
3362
|
+
this.parseName();
|
|
3363
|
+
const end = this.source.indexOf("?>", __privateGet2(this, _index2));
|
|
3364
|
+
if (end === -1) {
|
|
3365
|
+
this.fail("Unclosed XML processing instruction");
|
|
3366
|
+
}
|
|
3367
|
+
__privateSet2(this, _index2, end + 2);
|
|
3368
|
+
}
|
|
3369
|
+
skipMisc() {
|
|
3370
|
+
while (__privateGet2(this, _index2) < this.source.length) {
|
|
3371
|
+
this.skipWhitespace();
|
|
3372
|
+
if (this.source.startsWith("<!--", __privateGet2(this, _index2))) {
|
|
3373
|
+
this.skipComment();
|
|
3374
|
+
continue;
|
|
3375
|
+
}
|
|
3376
|
+
if (this.source.startsWith("<?", __privateGet2(this, _index2))) {
|
|
3377
|
+
this.skipProcessingInstruction();
|
|
3378
|
+
continue;
|
|
3379
|
+
}
|
|
3380
|
+
if (this.source.startsWith("<!DOCTYPE", __privateGet2(this, _index2))) {
|
|
3381
|
+
this.fail("DOCTYPE declarations are not supported");
|
|
3382
|
+
}
|
|
3383
|
+
break;
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
parseName() {
|
|
3387
|
+
const first = this.source[__privateGet2(this, _index2)];
|
|
3388
|
+
if (!isXmlNameStart2(first)) {
|
|
3389
|
+
this.fail("Invalid XML name");
|
|
3390
|
+
}
|
|
3391
|
+
const start = __privateGet2(this, _index2);
|
|
3392
|
+
__privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
|
|
3393
|
+
while (isXmlNameCharacter2(this.source[__privateGet2(this, _index2)])) {
|
|
3394
|
+
__privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
|
|
3395
|
+
}
|
|
3396
|
+
return this.source.slice(start, __privateGet2(this, _index2));
|
|
3397
|
+
}
|
|
3398
|
+
appendText(children, value) {
|
|
3399
|
+
if (!value) {
|
|
3400
|
+
return;
|
|
3401
|
+
}
|
|
3402
|
+
const previous = children[children.length - 1];
|
|
3403
|
+
if (typeof previous === "string") {
|
|
3404
|
+
children[children.length - 1] = previous + value;
|
|
3405
|
+
} else {
|
|
3406
|
+
children.push(value);
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
skipWhitespace() {
|
|
3410
|
+
while (isXmlWhitespace2(this.source[__privateGet2(this, _index2)])) {
|
|
3411
|
+
__privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
expect(value) {
|
|
3415
|
+
if (!this.source.startsWith(value, __privateGet2(this, _index2))) {
|
|
3416
|
+
this.fail(`Expected "${value}"`);
|
|
3417
|
+
}
|
|
3418
|
+
__privateSet2(this, _index2, __privateGet2(this, _index2) + value.length);
|
|
3419
|
+
}
|
|
3420
|
+
fail(message) {
|
|
3421
|
+
throw new Error(`${message} at position ${__privateGet2(this, _index2)}`);
|
|
3422
|
+
}
|
|
3423
|
+
};
|
|
3424
|
+
_index2 = /* @__PURE__ */ new WeakMap();
|
|
3425
|
+
function readAttribute2(attributes, ...names) {
|
|
3426
|
+
let found = false;
|
|
3427
|
+
let value;
|
|
3428
|
+
for (const name of names) {
|
|
3429
|
+
if (hasOwn2(attributes, name)) {
|
|
3430
|
+
if (!found) {
|
|
3431
|
+
value = String(attributes[name]);
|
|
3432
|
+
found = true;
|
|
3433
|
+
}
|
|
3434
|
+
delete attributes[name];
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
return value;
|
|
3438
|
+
}
|
|
3439
|
+
function getElementAttributes2(node) {
|
|
3440
|
+
const attributes = { ...node.attributes };
|
|
3441
|
+
removeStandardNamespaceAttributes2(attributes);
|
|
3442
|
+
return attributes;
|
|
3443
|
+
}
|
|
3444
|
+
function finishElement2(element, node, attributes) {
|
|
3445
|
+
if (node.children.length > 0) {
|
|
3446
|
+
element.children = node.children.map(convertNode2);
|
|
3447
|
+
}
|
|
3448
|
+
if (Object.keys(attributes).length > 0) {
|
|
3449
|
+
element.attributes = attributes;
|
|
3450
|
+
}
|
|
3451
|
+
return element;
|
|
3452
|
+
}
|
|
3453
|
+
function convertElement2(node) {
|
|
3454
|
+
const attributes = getElementAttributes2(node);
|
|
3455
|
+
switch (node.name) {
|
|
3456
|
+
case SSML_TAGS2.VOICE: {
|
|
3457
|
+
const element = { type: SSML_TAGS2.VOICE };
|
|
3458
|
+
const name = readAttribute2(attributes, SSML_ATTRS2.NAME);
|
|
3459
|
+
const effect = readAttribute2(attributes, SSML_ATTRS2.EFFECT);
|
|
3460
|
+
if (name !== void 0) element.name = name;
|
|
3461
|
+
if (effect !== void 0) element.effect = effect;
|
|
3462
|
+
return finishElement2(element, node, attributes);
|
|
3463
|
+
}
|
|
3464
|
+
case SSML_TAGS2.PROSODY: {
|
|
3465
|
+
const element = { type: SSML_TAGS2.PROSODY };
|
|
3466
|
+
const rate = readAttribute2(attributes, SSML_ATTRS2.RATE);
|
|
3467
|
+
const pitch = readAttribute2(attributes, SSML_ATTRS2.PITCH);
|
|
3468
|
+
const volume = readAttribute2(attributes, SSML_ATTRS2.VOLUME);
|
|
3469
|
+
const contour = readAttribute2(attributes, SSML_ATTRS2.CONTOUR);
|
|
3470
|
+
const range = readAttribute2(attributes, SSML_ATTRS2.RANGE);
|
|
3471
|
+
if (rate !== void 0) element.rate = rate;
|
|
3472
|
+
if (pitch !== void 0) element.pitch = pitch;
|
|
3473
|
+
if (volume !== void 0) element.volume = volume;
|
|
3474
|
+
if (contour !== void 0) element.contour = contour;
|
|
3475
|
+
if (range !== void 0) element.range = range;
|
|
3476
|
+
return finishElement2(element, node, attributes);
|
|
3477
|
+
}
|
|
3478
|
+
case SSML_TAGS2.BREAK: {
|
|
3479
|
+
const element = { type: SSML_TAGS2.BREAK };
|
|
3480
|
+
const time = readAttribute2(attributes, SSML_ATTRS2.TIME);
|
|
3481
|
+
const strength = readAttribute2(attributes, SSML_ATTRS2.STRENGTH);
|
|
3482
|
+
if (time !== void 0) element.time = time;
|
|
3483
|
+
if (strength !== void 0) element.strength = strength;
|
|
3484
|
+
return finishElement2(element, node, attributes);
|
|
3485
|
+
}
|
|
3486
|
+
case SSML_TAGS2.EXPRESS_AS:
|
|
3487
|
+
case SSML_TAGS2.EXPRESS_AS_CAMEL:
|
|
3488
|
+
case SSML_TAGS2.MSTTS_EXPRESS_AS: {
|
|
3489
|
+
const element = { type: node.name };
|
|
3490
|
+
const style = readAttribute2(attributes, SSML_ATTRS2.STYLE);
|
|
3491
|
+
const styleDegree = readAttribute2(
|
|
3492
|
+
attributes,
|
|
3493
|
+
SSML_ATTRS2.STYLE_DEGREE,
|
|
3494
|
+
SSML_ATTRS2.STYLE_DEGREE_CAMEL,
|
|
3495
|
+
SSML_ATTRS2.STYLE_DEGREE_HYPHEN
|
|
3496
|
+
);
|
|
3497
|
+
const role = readAttribute2(attributes, SSML_ATTRS2.ROLE);
|
|
3498
|
+
if (style !== void 0) element.style = style;
|
|
3499
|
+
if (styleDegree !== void 0) element.styleDegree = styleDegree;
|
|
3500
|
+
if (role !== void 0) element.role = role;
|
|
3501
|
+
return finishElement2(element, node, attributes);
|
|
3502
|
+
}
|
|
3503
|
+
case SSML_TAGS2.SAY_AS:
|
|
3504
|
+
case SSML_TAGS2.SAY_AS_CAMEL: {
|
|
3505
|
+
const element = { type: node.name };
|
|
3506
|
+
const interpretAs = readAttribute2(attributes, SSML_ATTRS2.INTERPRET_AS);
|
|
3507
|
+
const format = readAttribute2(attributes, SSML_ATTRS2.FORMAT);
|
|
3508
|
+
const detail = readAttribute2(attributes, SSML_ATTRS2.DETAIL);
|
|
3509
|
+
if (interpretAs !== void 0) element.interpretAs = interpretAs;
|
|
3510
|
+
if (format !== void 0) element.format = format;
|
|
3511
|
+
if (detail !== void 0) element.detail = detail;
|
|
3512
|
+
return finishElement2(element, node, attributes);
|
|
3513
|
+
}
|
|
3514
|
+
case SSML_TAGS2.PHONEME: {
|
|
3515
|
+
const element = { type: SSML_TAGS2.PHONEME };
|
|
3516
|
+
const alphabet = readAttribute2(attributes, SSML_ATTRS2.ALPHABET);
|
|
3517
|
+
const ph = readAttribute2(attributes, SSML_ATTRS2.PH);
|
|
3518
|
+
if (alphabet !== void 0) element.alphabet = alphabet;
|
|
3519
|
+
if (ph !== void 0) element.ph = ph;
|
|
3520
|
+
return finishElement2(element, node, attributes);
|
|
3521
|
+
}
|
|
3522
|
+
case SSML_TAGS2.EMPHASIS: {
|
|
3523
|
+
const element = { type: SSML_TAGS2.EMPHASIS };
|
|
3524
|
+
const level = readAttribute2(attributes, SSML_ATTRS2.LEVEL);
|
|
3525
|
+
if (level !== void 0) element.level = level;
|
|
3526
|
+
return finishElement2(element, node, attributes);
|
|
3527
|
+
}
|
|
3528
|
+
case SSML_TAGS2.AUDIO: {
|
|
3529
|
+
const element = { type: SSML_TAGS2.AUDIO };
|
|
3530
|
+
const src = readAttribute2(attributes, SSML_ATTRS2.SRC);
|
|
3531
|
+
const desc = readAttribute2(attributes, SSML_ATTRS2.DESC);
|
|
3532
|
+
const clipBegin = readAttribute2(attributes, SSML_ATTRS2.CLIP_BEGIN);
|
|
3533
|
+
const clipEnd = readAttribute2(attributes, SSML_ATTRS2.CLIP_END);
|
|
3534
|
+
const speed = readAttribute2(attributes, SSML_ATTRS2.SPEED);
|
|
3535
|
+
const repeatCount = readAttribute2(attributes, SSML_ATTRS2.REPEAT_COUNT);
|
|
3536
|
+
const repeatDuration = readAttribute2(attributes, SSML_ATTRS2.REPEAT_DURATION);
|
|
3537
|
+
const soundLevel = readAttribute2(attributes, SSML_ATTRS2.SOUND_LEVEL);
|
|
3538
|
+
if (src !== void 0) element.src = src;
|
|
3539
|
+
if (desc !== void 0) element.desc = desc;
|
|
3540
|
+
if (clipBegin !== void 0) element.clipBegin = clipBegin;
|
|
3541
|
+
if (clipEnd !== void 0) element.clipEnd = clipEnd;
|
|
3542
|
+
if (speed !== void 0) element.speed = speed;
|
|
3543
|
+
if (repeatCount !== void 0) element.repeatCount = repeatCount;
|
|
3544
|
+
if (repeatDuration !== void 0) element.repeatDuration = repeatDuration;
|
|
3545
|
+
if (soundLevel !== void 0) element.soundLevel = soundLevel;
|
|
3546
|
+
return finishElement2(element, node, attributes);
|
|
3547
|
+
}
|
|
3548
|
+
case SSML_TAGS2.SUB: {
|
|
3549
|
+
const element = { type: SSML_TAGS2.SUB };
|
|
3550
|
+
const alias = readAttribute2(attributes, SSML_ATTRS2.ALIAS);
|
|
3551
|
+
if (alias !== void 0) element.alias = alias;
|
|
3552
|
+
return finishElement2(element, node, attributes);
|
|
3553
|
+
}
|
|
3554
|
+
case SSML_TAGS2.LANG: {
|
|
3555
|
+
const element = { type: SSML_TAGS2.LANG };
|
|
3556
|
+
const lang = readAttribute2(attributes, SSML_ATTRS2.XML_LANG, SSML_ATTRS2.LANG);
|
|
3557
|
+
if (lang !== void 0) element.lang = lang;
|
|
3558
|
+
return finishElement2(element, node, attributes);
|
|
3559
|
+
}
|
|
3560
|
+
case SSML_TAGS2.MARK: {
|
|
3561
|
+
const element = { type: SSML_TAGS2.MARK };
|
|
3562
|
+
const name = readAttribute2(attributes, SSML_ATTRS2.NAME);
|
|
3563
|
+
if (name !== void 0) element.name = name;
|
|
3564
|
+
return finishElement2(element, node, attributes);
|
|
3565
|
+
}
|
|
3566
|
+
case SSML_TAGS2.BOOKMARK: {
|
|
3567
|
+
const element = { type: SSML_TAGS2.BOOKMARK };
|
|
3568
|
+
const mark = readAttribute2(attributes, SSML_ATTRS2.MARK);
|
|
3569
|
+
if (mark !== void 0) element.mark = mark;
|
|
3570
|
+
return finishElement2(element, node, attributes);
|
|
3571
|
+
}
|
|
3572
|
+
case SSML_TAGS2.LEXICON: {
|
|
3573
|
+
const element = { type: SSML_TAGS2.LEXICON };
|
|
3574
|
+
const uri = readAttribute2(attributes, SSML_ATTRS2.URI);
|
|
3575
|
+
if (uri !== void 0) element.uri = uri;
|
|
3576
|
+
return finishElement2(element, node, attributes);
|
|
3577
|
+
}
|
|
3578
|
+
case SSML_TAGS2.PARAGRAPH: {
|
|
3579
|
+
const element = { type: SSML_TAGS2.PARAGRAPH };
|
|
3580
|
+
return finishElement2(element, node, attributes);
|
|
3581
|
+
}
|
|
3582
|
+
case SSML_TAGS2.SENTENCE: {
|
|
3583
|
+
const element = { type: SSML_TAGS2.SENTENCE };
|
|
3584
|
+
return finishElement2(element, node, attributes);
|
|
3585
|
+
}
|
|
3586
|
+
case SSML_TAGS2.WORD: {
|
|
3587
|
+
const element = { type: SSML_TAGS2.WORD };
|
|
3588
|
+
return finishElement2(element, node, attributes);
|
|
3589
|
+
}
|
|
3590
|
+
case SSML_TAGS2.MSTTS_SILENCE:
|
|
3591
|
+
case SSML_TAGS2.SILENCE: {
|
|
3592
|
+
const element = {
|
|
3593
|
+
type: node.name === SSML_TAGS2.MSTTS_SILENCE ? SSML_TAGS2.MSTTS_SILENCE : SSML_TAGS2.SILENCE
|
|
3594
|
+
};
|
|
3595
|
+
const typeValue = readAttribute2(attributes, SSML_ATTRS2.TYPE);
|
|
3596
|
+
const value = readAttribute2(attributes, SSML_ATTRS2.VALUE);
|
|
3597
|
+
if (typeValue !== void 0) element.typeValue = typeValue;
|
|
3598
|
+
if (value !== void 0) element.value = value;
|
|
3599
|
+
return finishElement2(element, node, attributes);
|
|
3600
|
+
}
|
|
3601
|
+
case SSML_TAGS2.MSTTS_VISEME:
|
|
3602
|
+
case SSML_TAGS2.VISEME: {
|
|
3603
|
+
const element = {
|
|
3604
|
+
type: node.name === SSML_TAGS2.MSTTS_VISEME ? SSML_TAGS2.MSTTS_VISEME : SSML_TAGS2.VISEME
|
|
3605
|
+
};
|
|
3606
|
+
const typeValue = readAttribute2(attributes, SSML_ATTRS2.TYPE);
|
|
3607
|
+
if (typeValue !== void 0) element.typeValue = typeValue;
|
|
3608
|
+
return finishElement2(element, node, attributes);
|
|
3609
|
+
}
|
|
3610
|
+
case SSML_TAGS2.MSTTS_AUDIO_DURATION: {
|
|
3611
|
+
const element = { type: SSML_TAGS2.MSTTS_AUDIO_DURATION };
|
|
3612
|
+
const value = readAttribute2(attributes, SSML_ATTRS2.VALUE);
|
|
3613
|
+
if (value !== void 0) element.value = value;
|
|
3614
|
+
return finishElement2(element, node, attributes);
|
|
3615
|
+
}
|
|
3616
|
+
case SSML_TAGS2.MSTTS_DIALOG: {
|
|
3617
|
+
const element = { type: SSML_TAGS2.MSTTS_DIALOG };
|
|
3618
|
+
return finishElement2(element, node, attributes);
|
|
3619
|
+
}
|
|
3620
|
+
case SSML_TAGS2.MSTTS_TURN: {
|
|
3621
|
+
const element = { type: SSML_TAGS2.MSTTS_TURN };
|
|
3622
|
+
const voice = readAttribute2(attributes, SSML_ATTRS2.VOICE);
|
|
3623
|
+
const speaker = readAttribute2(attributes, SSML_ATTRS2.SPEAKER);
|
|
3624
|
+
if (voice !== void 0) element.voice = voice;
|
|
3625
|
+
if (speaker !== void 0) element.speaker = speaker;
|
|
3626
|
+
return finishElement2(element, node, attributes);
|
|
3627
|
+
}
|
|
3628
|
+
case SSML_TAGS2.MSTTS_BACKGROUND_AUDIO: {
|
|
3629
|
+
const element = { type: SSML_TAGS2.MSTTS_BACKGROUND_AUDIO };
|
|
3630
|
+
const src = readAttribute2(attributes, SSML_ATTRS2.SRC);
|
|
3631
|
+
const volume = readAttribute2(attributes, SSML_ATTRS2.VOLUME);
|
|
3632
|
+
const fadeIn = readAttribute2(attributes, SSML_ATTRS2.FADE_IN);
|
|
3633
|
+
const fadeOut = readAttribute2(attributes, SSML_ATTRS2.FADE_OUT);
|
|
3634
|
+
if (src !== void 0) element.src = src;
|
|
3635
|
+
if (volume !== void 0) element.volume = volume;
|
|
3636
|
+
if (fadeIn !== void 0) element.fadeIn = fadeIn;
|
|
3637
|
+
if (fadeOut !== void 0) element.fadeOut = fadeOut;
|
|
3638
|
+
return finishElement2(element, node, attributes);
|
|
3639
|
+
}
|
|
3640
|
+
case SSML_TAGS2.MSTTS_TTS_EMBEDDING: {
|
|
3641
|
+
const element = { type: SSML_TAGS2.MSTTS_TTS_EMBEDDING };
|
|
3642
|
+
const speakerProfileId = readAttribute2(attributes, SSML_ATTRS2.SPEAKER_PROFILE_ID);
|
|
3643
|
+
if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
|
|
3644
|
+
return finishElement2(element, node, attributes);
|
|
3645
|
+
}
|
|
3646
|
+
case SSML_TAGS2.MSTTS_EMBEDDING: {
|
|
3647
|
+
const element = { type: SSML_TAGS2.MSTTS_EMBEDDING };
|
|
3648
|
+
const id = readAttribute2(attributes, SSML_ATTRS2.ID);
|
|
3649
|
+
const speakerProfileId = readAttribute2(attributes, SSML_ATTRS2.SPEAKER_PROFILE_ID);
|
|
3650
|
+
if (id !== void 0) element.id = id;
|
|
3651
|
+
if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
|
|
3652
|
+
return finishElement2(element, node, attributes);
|
|
3653
|
+
}
|
|
3654
|
+
case SSML_TAGS2.MSTTS_VOICE_CONVERSION: {
|
|
3655
|
+
const element = { type: SSML_TAGS2.MSTTS_VOICE_CONVERSION };
|
|
3656
|
+
const url = readAttribute2(attributes, SSML_ATTRS2.URL);
|
|
3657
|
+
const profile = readAttribute2(attributes, SSML_ATTRS2.PROFILE);
|
|
3658
|
+
const speakerProfileId = readAttribute2(attributes, SSML_ATTRS2.SPEAKER_PROFILE_ID);
|
|
3659
|
+
if (url !== void 0) element.url = url;
|
|
3660
|
+
if (profile !== void 0) element.profile = profile;
|
|
3661
|
+
if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
|
|
3662
|
+
return finishElement2(element, node, attributes);
|
|
3663
|
+
}
|
|
3664
|
+
default: {
|
|
3665
|
+
const element = {
|
|
3666
|
+
name: node.name,
|
|
3667
|
+
type: "custom"
|
|
3668
|
+
};
|
|
3669
|
+
return finishElement2(element, node, attributes);
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3673
|
+
function convertNode2(node) {
|
|
3674
|
+
return typeof node === "string" ? node : convertElement2(node);
|
|
3675
|
+
}
|
|
3676
|
+
function parseSsml2(xmlString) {
|
|
3677
|
+
if (typeof xmlString !== "string") {
|
|
3678
|
+
throw new TypeError("SSML input must be a string");
|
|
3679
|
+
}
|
|
3680
|
+
const root = new XmlParser2(xmlString).parse();
|
|
3681
|
+
if (root.name !== SSML_TAGS2.SPEAK) {
|
|
3682
|
+
throw new Error(`SSML root element must be <${SSML_TAGS2.SPEAK}>, found <${root.name}>`);
|
|
3683
|
+
}
|
|
3684
|
+
const attributes = { ...root.attributes };
|
|
3685
|
+
const version = readAttribute2(attributes, SSML_ATTRS2.VERSION);
|
|
3686
|
+
const lang = readAttribute2(attributes, SSML_ATTRS2.XML_LANG, SSML_ATTRS2.LANG);
|
|
3687
|
+
if (version === void 0) {
|
|
3688
|
+
throw new Error(`SSML <${SSML_TAGS2.SPEAK}> element is missing the "${SSML_ATTRS2.VERSION}" attribute`);
|
|
3689
|
+
}
|
|
3690
|
+
if (lang === void 0) {
|
|
3691
|
+
throw new Error(`SSML <${SSML_TAGS2.SPEAK}> element is missing the "${SSML_ATTRS2.XML_LANG}" attribute`);
|
|
3692
|
+
}
|
|
3693
|
+
removeStandardNamespaceAttributes2(attributes);
|
|
3694
|
+
const document = {
|
|
3695
|
+
children: root.children.map(convertNode2),
|
|
3696
|
+
lang,
|
|
3697
|
+
type: SSML_TAGS2.SPEAK,
|
|
3698
|
+
version
|
|
3699
|
+
};
|
|
3700
|
+
if (Object.keys(attributes).length > 0) {
|
|
3701
|
+
document.attributes = attributes;
|
|
3702
|
+
}
|
|
3703
|
+
return document;
|
|
3704
|
+
}
|
|
3705
|
+
var AZURE_VOICE_DEFINITIONS2 = [
|
|
3706
|
+
{ name: "de-DE-ConradNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
|
|
3707
|
+
{ name: "de-DE-KatjaNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
|
|
3708
|
+
{ name: "en-US-AndrewNeural", locale: "en-US", styles: ["empathetic", "relieved"] },
|
|
3709
|
+
{
|
|
3710
|
+
name: "en-US-GuyNeural",
|
|
3711
|
+
locale: "en-US",
|
|
3712
|
+
styles: [
|
|
3713
|
+
"angry",
|
|
3714
|
+
"cheerful",
|
|
3715
|
+
"excited",
|
|
3716
|
+
"friendly",
|
|
3717
|
+
"hopeful",
|
|
3718
|
+
"newscast",
|
|
3719
|
+
"sad",
|
|
3720
|
+
"shouting",
|
|
3721
|
+
"terrified",
|
|
3722
|
+
"unfriendly",
|
|
3723
|
+
"whispering"
|
|
3724
|
+
]
|
|
3725
|
+
},
|
|
3726
|
+
{
|
|
3727
|
+
name: "en-US-JennyMultilingualNeural",
|
|
3728
|
+
locale: "en-US",
|
|
3729
|
+
styles: [
|
|
3730
|
+
"cheerful",
|
|
3731
|
+
"empathetic",
|
|
3732
|
+
"excited",
|
|
3733
|
+
"friendly",
|
|
3734
|
+
"hopeful",
|
|
3735
|
+
"sad",
|
|
3736
|
+
"shouting",
|
|
3737
|
+
"terrified",
|
|
3738
|
+
"unfriendly",
|
|
3739
|
+
"whispering"
|
|
3740
|
+
]
|
|
3741
|
+
},
|
|
3742
|
+
{
|
|
3743
|
+
name: "en-US-JennyNeural",
|
|
3744
|
+
locale: "en-US",
|
|
3745
|
+
styles: [
|
|
3746
|
+
"assistant",
|
|
3747
|
+
"chat",
|
|
3748
|
+
"customerservice",
|
|
3749
|
+
"newscast",
|
|
3750
|
+
"cheerful",
|
|
3751
|
+
"empathetic",
|
|
3752
|
+
"excited",
|
|
3753
|
+
"friendly",
|
|
3754
|
+
"hopeful",
|
|
3755
|
+
"sad",
|
|
3756
|
+
"shouting",
|
|
3757
|
+
"terrified",
|
|
3758
|
+
"unfriendly",
|
|
3759
|
+
"whispering"
|
|
3760
|
+
]
|
|
3761
|
+
},
|
|
3762
|
+
{ name: "es-ES-ElviraNeural", locale: "es-ES" },
|
|
3763
|
+
{ name: "fil-PH-AngeloNeural", locale: "fil-PH" },
|
|
3764
|
+
{ name: "fil-PH-Angelo:DragonHDLatestNeural", locale: "fil-PH" },
|
|
3765
|
+
{ name: "fil-PH-BlessicaNeural", locale: "fil-PH" },
|
|
3766
|
+
{ name: "fil-PH-Blessica:DragonHDLatestNeural", locale: "fil-PH" },
|
|
3767
|
+
{ name: "fr-FR-DeniseNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
|
|
3768
|
+
{ name: "fr-FR-HenriNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
|
|
3769
|
+
{ name: "id-ID-GadisNeural", locale: "id-ID" },
|
|
3770
|
+
{ name: "it-IT-ElsaNeural", locale: "it-IT", styles: ["cheerful", "sad"] },
|
|
3771
|
+
{ name: "ja-JP-KeitaNeural", locale: "ja-JP", styles: ["chat"] },
|
|
3772
|
+
{ name: "ja-JP-MayuNeural", locale: "ja-JP", styles: ["calm", "cheerful", "sad"] },
|
|
3773
|
+
{ name: "ja-JP-NanamiNeural", locale: "ja-JP", styles: ["chat", "customerservice", "cheerful", "whispering", "sad"] },
|
|
3774
|
+
{ name: "ko-KR-SunHiNeural", locale: "ko-KR", styles: ["cheerful", "sad"] },
|
|
3775
|
+
{ name: "ms-MY-YasminNeural", locale: "ms-MY" },
|
|
3776
|
+
{ name: "pt-BR-FranciscaNeural", locale: "pt-BR", styles: ["calm"] },
|
|
3777
|
+
{
|
|
3778
|
+
name: "ru-RU-SvetlanaNeural",
|
|
3779
|
+
locale: "ru-RU",
|
|
3780
|
+
styles: ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"]
|
|
3781
|
+
},
|
|
3782
|
+
{ name: "th-TH-PremwadeeNeural", locale: "th-TH" },
|
|
3783
|
+
{ name: "vi-VN-HoaiMyNeural", locale: "vi-VN" },
|
|
3784
|
+
{
|
|
3785
|
+
name: "zh-CN-XiaoxiaoNeural",
|
|
3786
|
+
locale: "zh-CN",
|
|
3787
|
+
styles: [
|
|
3788
|
+
"assistant",
|
|
3789
|
+
"chat",
|
|
3790
|
+
"customerservice",
|
|
3791
|
+
"newscast",
|
|
3792
|
+
"cheerful",
|
|
3793
|
+
"empathetic",
|
|
3794
|
+
"excited",
|
|
3795
|
+
"friendly",
|
|
3796
|
+
"hopeful",
|
|
3797
|
+
"sad",
|
|
3798
|
+
"terrified",
|
|
3799
|
+
"whispering",
|
|
3800
|
+
"poetry-reading",
|
|
3801
|
+
"sports_commentary",
|
|
3802
|
+
"sports_commentary_excited",
|
|
3803
|
+
"story"
|
|
3804
|
+
]
|
|
3805
|
+
},
|
|
3806
|
+
{
|
|
3807
|
+
name: "zh-CN-YunxiNeural",
|
|
3808
|
+
locale: "zh-CN",
|
|
3809
|
+
styles: [
|
|
3810
|
+
"narration-relaxed",
|
|
3811
|
+
"embarrassed",
|
|
3812
|
+
"fearful",
|
|
3813
|
+
"sad",
|
|
3814
|
+
"disgruntled",
|
|
3815
|
+
"serious",
|
|
3816
|
+
"angry",
|
|
3817
|
+
"depressed",
|
|
3818
|
+
"chat",
|
|
3819
|
+
"cheerful",
|
|
3820
|
+
"assistant"
|
|
3821
|
+
]
|
|
3822
|
+
},
|
|
3823
|
+
{ name: "zh-TW-HsiaoChenNeural", locale: "zh-TW" }
|
|
3824
|
+
];
|
|
3825
|
+
function createAzureUrlValidatorRunner2(validator, options = {}) {
|
|
3826
|
+
if (typeof validator !== "function") throw new TypeError("A URL validator function is required.");
|
|
3827
|
+
const concurrency = options.concurrency === void 0 ? Infinity : Number.isFinite(options.concurrency) ? Math.max(1, Math.floor(options.concurrency)) : Infinity;
|
|
3828
|
+
const cache = options.cache ?? /* @__PURE__ */ new Map();
|
|
3829
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
3830
|
+
const waiters = [];
|
|
3831
|
+
let active = 0;
|
|
3832
|
+
const acquire = async () => {
|
|
3833
|
+
if (active < concurrency) {
|
|
3834
|
+
active += 1;
|
|
3835
|
+
return;
|
|
3836
|
+
}
|
|
3837
|
+
await new Promise((resolve) => waiters.push(resolve));
|
|
3838
|
+
active += 1;
|
|
3839
|
+
};
|
|
3840
|
+
const release = () => {
|
|
3841
|
+
active -= 1;
|
|
3842
|
+
waiters.shift()?.();
|
|
3843
|
+
};
|
|
3844
|
+
const check = async (url, context) => {
|
|
3845
|
+
if (options.signal?.aborted) throw new Error("URL validation was aborted.");
|
|
3846
|
+
const cached = cache.get(url);
|
|
3847
|
+
if (cached !== void 0) return cached;
|
|
3848
|
+
const existing = inFlight.get(url);
|
|
3849
|
+
if (existing) return existing;
|
|
3850
|
+
const promise = (async () => {
|
|
3851
|
+
await acquire();
|
|
3852
|
+
try {
|
|
3853
|
+
if (options.signal?.aborted) throw new Error("URL validation was aborted.");
|
|
3854
|
+
const validation = Promise.resolve(validator(url, context));
|
|
3855
|
+
let timer;
|
|
3856
|
+
let abortHandler;
|
|
3857
|
+
const cancellation = new Promise((_resolve, reject) => {
|
|
3858
|
+
abortHandler = () => reject(new Error("URL validation was aborted."));
|
|
3859
|
+
options.signal?.addEventListener("abort", abortHandler, { once: true });
|
|
3860
|
+
if (options.timeoutMs !== void 0 && options.timeoutMs > 0) {
|
|
3861
|
+
timer = setTimeout(
|
|
3862
|
+
() => reject(new Error(`URL validation timed out after ${options.timeoutMs} ms.`)),
|
|
3863
|
+
options.timeoutMs
|
|
3864
|
+
);
|
|
3865
|
+
}
|
|
3866
|
+
});
|
|
3867
|
+
try {
|
|
3868
|
+
const result = await (timer || abortHandler ? Promise.race([validation, cancellation]) : validation);
|
|
3869
|
+
cache.set(url, result);
|
|
3870
|
+
return result;
|
|
3871
|
+
} finally {
|
|
3872
|
+
if (timer) clearTimeout(timer);
|
|
3873
|
+
if (abortHandler) options.signal?.removeEventListener("abort", abortHandler);
|
|
3874
|
+
}
|
|
3875
|
+
} finally {
|
|
3876
|
+
release();
|
|
3877
|
+
}
|
|
3878
|
+
})();
|
|
3879
|
+
inFlight.set(url, promise);
|
|
3880
|
+
try {
|
|
3881
|
+
return await promise;
|
|
3882
|
+
} finally {
|
|
3883
|
+
inFlight.delete(url);
|
|
3884
|
+
}
|
|
3885
|
+
};
|
|
3886
|
+
return (url, context) => check(url, context);
|
|
3887
|
+
}
|
|
3888
|
+
var ALLOWED_BREAK_STRENGTHS2 = /* @__PURE__ */ new Set(["none", "x-weak", "weak", "medium", "strong", "x-strong"]);
|
|
3889
|
+
var ALLOWED_SAY_AS2 = /* @__PURE__ */ new Set([
|
|
3890
|
+
"characters",
|
|
3891
|
+
"spell-out",
|
|
3892
|
+
"cardinal",
|
|
3893
|
+
"ordinal",
|
|
3894
|
+
"number",
|
|
3895
|
+
"date",
|
|
3896
|
+
"time",
|
|
3897
|
+
"telephone",
|
|
3898
|
+
"fraction",
|
|
3899
|
+
"address",
|
|
3900
|
+
"name",
|
|
3901
|
+
"currency",
|
|
3902
|
+
"number_digit"
|
|
3903
|
+
]);
|
|
3904
|
+
var ALLOWED_ROLES2 = /* @__PURE__ */ new Set([
|
|
3905
|
+
"Girl",
|
|
3906
|
+
"Boy",
|
|
3907
|
+
"YoungAdultFemale",
|
|
3908
|
+
"YoungAdultMale",
|
|
3909
|
+
"OlderAdultFemale",
|
|
3910
|
+
"OlderAdultMale",
|
|
3911
|
+
"SeniorFemale",
|
|
3912
|
+
"SeniorMale"
|
|
3913
|
+
]);
|
|
3914
|
+
var ALLOWED_EMPHASIS_LEVELS2 = /* @__PURE__ */ new Set(["strong", "moderate", "reduced", "none"]);
|
|
3915
|
+
var ALLOWED_SILENCE_TYPES2 = /* @__PURE__ */ new Set([
|
|
3916
|
+
"Leading",
|
|
3917
|
+
"Tailing",
|
|
3918
|
+
"Sentenceboundary",
|
|
3919
|
+
"Comma",
|
|
3920
|
+
"Semicolon",
|
|
3921
|
+
"Enumerationcomma"
|
|
3922
|
+
]);
|
|
3923
|
+
var ALLOWED_VISEME_TYPES2 = /* @__PURE__ */ new Set(["redlips_front", "FacialExpression"]);
|
|
3924
|
+
var DEFAULT_PREVIEW_TAGS2 = /* @__PURE__ */ new Set(["mstts:voiceconversion"]);
|
|
3925
|
+
function featureStatusForTag2(name, options) {
|
|
3926
|
+
const tagName = canonicalTagName2(name);
|
|
3927
|
+
const configured = Object.entries(options.tagStatuses ?? {}).find(
|
|
3928
|
+
([candidate]) => canonicalTagName2(candidate) === tagName
|
|
3929
|
+
)?.[1];
|
|
3930
|
+
if (configured) return configured;
|
|
3931
|
+
if ((options.previewTags ?? [...DEFAULT_PREVIEW_TAGS2]).some((candidate) => canonicalTagName2(candidate) === tagName))
|
|
3932
|
+
return "preview";
|
|
3933
|
+
if ((options.deprecatedTags ?? []).some((candidate) => canonicalTagName2(candidate) === tagName)) return "deprecated";
|
|
3934
|
+
return void 0;
|
|
3935
|
+
}
|
|
3936
|
+
function decodeAttribute2(value) {
|
|
3937
|
+
return value.replace(
|
|
3938
|
+
/&(?:amp|apos|gt|lt|quot);/gi,
|
|
3939
|
+
(entity) => ({ "&": "&", "'": "'", ">": ">", "<": "<", """: '"' })[entity.toLowerCase()] ?? entity
|
|
3940
|
+
);
|
|
3941
|
+
}
|
|
3942
|
+
function findTagEnd22(source, start) {
|
|
3943
|
+
let quote = "";
|
|
3944
|
+
for (let index = start; index < source.length; index += 1) {
|
|
3945
|
+
const character = source[index];
|
|
3946
|
+
if (quote) {
|
|
3947
|
+
if (character === quote) quote = "";
|
|
3948
|
+
} else if (character === '"' || character === "'") quote = character;
|
|
3949
|
+
else if (character === ">") return index;
|
|
3950
|
+
}
|
|
3951
|
+
return source.length - 1;
|
|
3952
|
+
}
|
|
3953
|
+
function tokenizeElements2(source) {
|
|
3954
|
+
const tokens = [];
|
|
3955
|
+
const openElements = [];
|
|
3956
|
+
let index = 0;
|
|
3957
|
+
while (index < source.length) {
|
|
3958
|
+
const start = source.indexOf("<", index);
|
|
3959
|
+
if (start === -1) break;
|
|
3960
|
+
if (source.startsWith("<!--", start)) {
|
|
3961
|
+
const end2 = source.indexOf("-->", start + 4);
|
|
3962
|
+
index = end2 === -1 ? source.length : end2 + 3;
|
|
3963
|
+
continue;
|
|
3964
|
+
}
|
|
3965
|
+
if (source.startsWith("<![CDATA[", start)) {
|
|
3966
|
+
const end2 = source.indexOf("]]>", start + 9);
|
|
3967
|
+
index = end2 === -1 ? source.length : end2 + 3;
|
|
3968
|
+
continue;
|
|
3969
|
+
}
|
|
3970
|
+
if (source.startsWith("<?", start)) {
|
|
3971
|
+
const end2 = source.indexOf("?>", start + 2);
|
|
3972
|
+
index = end2 === -1 ? source.length : end2 + 2;
|
|
3973
|
+
continue;
|
|
3974
|
+
}
|
|
3975
|
+
const end = findTagEnd22(source, start + 1);
|
|
3976
|
+
const raw = source.slice(start, end + 1);
|
|
3977
|
+
if (raw.startsWith("</")) {
|
|
3978
|
+
openElements.pop();
|
|
3979
|
+
index = end + 1;
|
|
3980
|
+
continue;
|
|
3981
|
+
}
|
|
3982
|
+
const nameMatch = /^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/.exec(raw);
|
|
3983
|
+
if (!nameMatch?.[1]) {
|
|
3984
|
+
index = end + 1;
|
|
3985
|
+
continue;
|
|
3986
|
+
}
|
|
3987
|
+
const attributes = /* @__PURE__ */ new Map();
|
|
3988
|
+
const attributeSource = raw.slice(nameMatch[0].length, raw.length - 1).replace(/\/\s*$/, "");
|
|
3989
|
+
const attributePattern = /([A-Za-z_][A-Za-z0-9_.:-]*)\s*=\s*(["'])([\s\S]*?)\2/g;
|
|
3990
|
+
for (const match of attributeSource.matchAll(attributePattern)) {
|
|
3991
|
+
attributes.set(match[1].toLowerCase(), decodeAttribute2(match[3]));
|
|
3992
|
+
}
|
|
3993
|
+
const selfClosing = /\/\s*>$/.test(raw);
|
|
3994
|
+
const parent = openElements[openElements.length - 1];
|
|
3995
|
+
const childElementIndex = parent?.childElementCount;
|
|
3996
|
+
if (parent) parent.childElementCount += 1;
|
|
3997
|
+
const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
|
|
3998
|
+
const tokenName = nameMatch[1];
|
|
3999
|
+
const tokenVoiceName = tokenName.toLowerCase() === "voice" ? attributes.get("name") : tokenName.toLowerCase() === "mstts:turn" ? attributes.get("voice") ?? parentVoiceName : parentVoiceName;
|
|
4000
|
+
tokens.push({
|
|
4001
|
+
attributes,
|
|
4002
|
+
childElementIndex,
|
|
4003
|
+
end,
|
|
4004
|
+
depth: openElements.length + 1,
|
|
4005
|
+
name: tokenName,
|
|
4006
|
+
parentName: parent?.name,
|
|
4007
|
+
parentVoiceName,
|
|
4008
|
+
selfClosing,
|
|
4009
|
+
start
|
|
4010
|
+
});
|
|
4011
|
+
if (!selfClosing) {
|
|
4012
|
+
openElements.push({
|
|
4013
|
+
childElementCount: 0,
|
|
4014
|
+
name: tokenName,
|
|
4015
|
+
voiceName: tokenVoiceName
|
|
4016
|
+
});
|
|
4017
|
+
}
|
|
4018
|
+
index = end + 1;
|
|
4019
|
+
}
|
|
4020
|
+
return tokens;
|
|
4021
|
+
}
|
|
4022
|
+
function location2(source, offset) {
|
|
4023
|
+
const before = source.slice(0, Math.max(0, offset));
|
|
4024
|
+
const line = before.split("\n").length;
|
|
4025
|
+
return { line, column: before.length - (before.lastIndexOf("\n") + 1) + 1 };
|
|
4026
|
+
}
|
|
4027
|
+
function addDiagnostic2(diagnostics, source, offset, message, severity = "error", code) {
|
|
4028
|
+
diagnostics.push({
|
|
4029
|
+
...location2(source, offset),
|
|
4030
|
+
message,
|
|
4031
|
+
severity,
|
|
4032
|
+
source: "ssml-static-validator",
|
|
4033
|
+
...code ? { code } : {}
|
|
4034
|
+
});
|
|
4035
|
+
}
|
|
4036
|
+
function isSupportedProsodyRate2(value) {
|
|
4037
|
+
const trimmed = value.trim();
|
|
4038
|
+
if (/^(x-slow|slow|medium|fast|x-fast|[+-]?\d+(?:\.\d+)?%)$/.test(trimmed)) return true;
|
|
4039
|
+
const multiplier = /^(\d+(?:\.\d+)?)(x)?$/i.exec(trimmed);
|
|
4040
|
+
if (!multiplier) return false;
|
|
4041
|
+
const numericValue = Number(multiplier[1]);
|
|
4042
|
+
return numericValue >= 0.5 && numericValue <= 2;
|
|
4043
|
+
}
|
|
4044
|
+
function isValidAzureAudioDuration2(value) {
|
|
4045
|
+
const trimmed = value.trim();
|
|
4046
|
+
const numeric = /^(\d+(?:\.\d+)?)(ms|s)$/.exec(trimmed);
|
|
4047
|
+
if (numeric) return Number(numeric[1]) > 0;
|
|
4048
|
+
const clock = /^(\d{2,}):([0-5]\d):([0-5]\d)(?:\.(\d{1,3}))?$/.exec(trimmed);
|
|
4049
|
+
if (!clock) return false;
|
|
4050
|
+
return Number(clock[1]) > 0 || Number(clock[2]) > 0 || Number(clock[3]) > 0 || Number(clock[4] ?? 0) > 0;
|
|
4051
|
+
}
|
|
4052
|
+
function isValidAzureBackgroundAudioDuration2(value) {
|
|
4053
|
+
const match = /^(\d+)$/.exec(value.trim());
|
|
4054
|
+
if (!match) return false;
|
|
4055
|
+
const milliseconds = Number(match[1]);
|
|
4056
|
+
return Number.isFinite(milliseconds) && milliseconds >= 0 && milliseconds <= 1e4;
|
|
4057
|
+
}
|
|
4058
|
+
function attr2(token, name) {
|
|
4059
|
+
return token.attributes.get(name.toLowerCase());
|
|
4060
|
+
}
|
|
4061
|
+
var DEFAULT_LANGUAGE_ALIASES2 = {
|
|
4062
|
+
"zh-CN": ["zh-Hans"],
|
|
4063
|
+
"zh-TW": ["zh-Hant"]
|
|
4064
|
+
};
|
|
4065
|
+
function canonicalLanguageTag2(language) {
|
|
4066
|
+
const trimmed = language.trim();
|
|
4067
|
+
if (!trimmed) return "";
|
|
4068
|
+
try {
|
|
4069
|
+
return new Intl.Locale(trimmed).toString().toLowerCase();
|
|
4070
|
+
} catch {
|
|
4071
|
+
return trimmed.toLowerCase();
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
function createLanguageNormalizer2(options) {
|
|
4075
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
4076
|
+
const addAliasGroup = (canonical, values) => {
|
|
4077
|
+
const normalizedCanonical = canonicalLanguageTag2(canonical);
|
|
4078
|
+
if (!normalizedCanonical) return;
|
|
4079
|
+
aliases.set(normalizedCanonical, normalizedCanonical);
|
|
4080
|
+
for (const value of values) {
|
|
4081
|
+
const normalizedValue = canonicalLanguageTag2(value);
|
|
4082
|
+
if (normalizedValue) aliases.set(normalizedValue, normalizedCanonical);
|
|
4083
|
+
}
|
|
4084
|
+
};
|
|
4085
|
+
for (const [canonical, values] of Object.entries(DEFAULT_LANGUAGE_ALIASES2)) addAliasGroup(canonical, values);
|
|
4086
|
+
for (const [canonical, valueOrValues] of Object.entries(options.languageAliases ?? {}))
|
|
4087
|
+
addAliasGroup(canonical, typeof valueOrValues === "string" ? [valueOrValues] : valueOrValues);
|
|
4088
|
+
return (language) => {
|
|
4089
|
+
const customValue = options.normalizeLanguage ? options.normalizeLanguage(language) : language;
|
|
4090
|
+
const normalized = canonicalLanguageTag2(customValue);
|
|
4091
|
+
return aliases.get(normalized) ?? normalized;
|
|
4092
|
+
};
|
|
4093
|
+
}
|
|
4094
|
+
function voiceLocalePrefix2(voiceName) {
|
|
4095
|
+
const match = /^(?<language>[A-Za-z]{2,3})-(?<region>[A-Za-z]{2}|\d{3})(?:-|$)/.exec(voiceName.trim());
|
|
4096
|
+
if (!match?.groups) return void 0;
|
|
4097
|
+
const tag = `${match.groups.language}-${match.groups.region}`;
|
|
4098
|
+
return {
|
|
4099
|
+
language: match.groups.language.toLowerCase(),
|
|
4100
|
+
region: match.groups.region.toLowerCase(),
|
|
4101
|
+
tag
|
|
4102
|
+
};
|
|
4103
|
+
}
|
|
4104
|
+
function definitionFromStyleMap2(voiceName, styles) {
|
|
4105
|
+
return {
|
|
4106
|
+
name: voiceName,
|
|
4107
|
+
locale: voiceLocalePrefix2(voiceName)?.tag ?? "",
|
|
4108
|
+
styles
|
|
4109
|
+
};
|
|
4110
|
+
}
|
|
4111
|
+
function normalizeVoiceCatalog2(options) {
|
|
4112
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
4113
|
+
for (const definition of AZURE_VOICE_DEFINITIONS2) definitions.set(definition.name.toLowerCase(), definition);
|
|
4114
|
+
for (const definition of options.voiceCatalog ?? []) definitions.set(definition.name.toLowerCase(), definition);
|
|
4115
|
+
for (const definition of options.voiceDefinitions ?? []) definitions.set(definition.name.toLowerCase(), definition);
|
|
4116
|
+
for (const definition of options.customVoiceDefinitions ?? [])
|
|
4117
|
+
definitions.set(definition.name.toLowerCase(), definition);
|
|
4118
|
+
for (const [voiceName, styles] of Object.entries(options.customVoiceStyleMap ?? {})) {
|
|
4119
|
+
const key = voiceName.toLowerCase();
|
|
4120
|
+
const current = definitions.get(key);
|
|
4121
|
+
definitions.set(key, {
|
|
4122
|
+
...current ?? definitionFromStyleMap2(voiceName, styles),
|
|
4123
|
+
name: current?.name ?? voiceName,
|
|
4124
|
+
styles: styles.map((style) => style.toLowerCase())
|
|
4125
|
+
});
|
|
4126
|
+
}
|
|
4127
|
+
return definitions;
|
|
4128
|
+
}
|
|
4129
|
+
function diagnosticSeverity2(policy) {
|
|
4130
|
+
if (policy === "ignore") return void 0;
|
|
4131
|
+
return policy === "error" ? "error" : "warning";
|
|
4132
|
+
}
|
|
4133
|
+
function languagePart2(language) {
|
|
4134
|
+
try {
|
|
4135
|
+
return new Intl.Locale(language).language.toLowerCase();
|
|
4136
|
+
} catch {
|
|
4137
|
+
return language.split("-")[0]?.toLowerCase() ?? "";
|
|
4138
|
+
}
|
|
4139
|
+
}
|
|
4140
|
+
function definitionMatchesLanguage2(definition, voiceName, language, normalizeLanguage) {
|
|
4141
|
+
const candidateLanguages = definition ? [definition.locale, ...definition.secondaryLocales ?? []].filter(Boolean) : [voiceLocalePrefix2(voiceName)?.tag ?? ""];
|
|
4142
|
+
if (candidateLanguages.length === 0 || !language.trim()) return void 0;
|
|
4143
|
+
const normalizedLanguage = normalizeLanguage(language);
|
|
4144
|
+
const normalizedCandidates = candidateLanguages.map(normalizeLanguage);
|
|
4145
|
+
if (normalizedCandidates.includes(normalizedLanguage)) return true;
|
|
4146
|
+
if (!normalizedLanguage || !normalizedCandidates.some(Boolean)) return void 0;
|
|
4147
|
+
return normalizedLanguage === languagePart2(normalizedLanguage) ? normalizedCandidates.some((candidate) => languagePart2(candidate) === normalizedLanguage) : false;
|
|
4148
|
+
}
|
|
4149
|
+
function canonicalTagName2(name) {
|
|
4150
|
+
const normalized = name.toLowerCase();
|
|
4151
|
+
if (normalized === "express-as" || normalized === "expressas") return "mstts:express-as";
|
|
4152
|
+
if (normalized === "sayas") return "say-as";
|
|
4153
|
+
return normalized;
|
|
4154
|
+
}
|
|
4155
|
+
function validateVoiceFeatureMatrix2(token, source, diagnostics, voiceName, definition) {
|
|
4156
|
+
if (!voiceName || !definition || token.name.toLowerCase() === "voice" || token.name.toLowerCase() === "mstts:turn")
|
|
4157
|
+
return;
|
|
4158
|
+
const tagName = canonicalTagName2(token.name);
|
|
4159
|
+
const unsupportedTags = new Set((definition.unsupportedTags ?? []).map(canonicalTagName2));
|
|
4160
|
+
const supportedTags = definition.supportedTags?.map(canonicalTagName2);
|
|
4161
|
+
if (unsupportedTags.has(tagName) || supportedTags !== void 0 && !supportedTags.includes(tagName)) {
|
|
4162
|
+
addDiagnostic2(
|
|
4163
|
+
diagnostics,
|
|
4164
|
+
source,
|
|
4165
|
+
token.start,
|
|
4166
|
+
`Tag <${token.name}> is not supported by voice "${voiceName}" according to the configured feature matrix.`,
|
|
4167
|
+
"error",
|
|
4168
|
+
"azure-unsupported-tag-for-voice"
|
|
4169
|
+
);
|
|
4170
|
+
}
|
|
4171
|
+
}
|
|
4172
|
+
function validateAudioSource2(token, source, diagnostics, options, elementName3) {
|
|
4173
|
+
const src = attr2(token, "src");
|
|
4174
|
+
if (!src) {
|
|
4175
|
+
addDiagnostic2(diagnostics, source, token.start, `<${elementName3}> requires a "src" attribute.`);
|
|
4176
|
+
return;
|
|
4177
|
+
}
|
|
4178
|
+
let parsed;
|
|
4179
|
+
try {
|
|
4180
|
+
parsed = new URL(src);
|
|
4181
|
+
} catch {
|
|
4182
|
+
addDiagnostic2(diagnostics, source, token.start, `<${elementName3} src> must be an absolute HTTP(S) URL.`);
|
|
4183
|
+
return;
|
|
4184
|
+
}
|
|
4185
|
+
if (parsed.username || parsed.password)
|
|
4186
|
+
addDiagnostic2(diagnostics, source, token.start, `<${elementName3} src> must not contain URL credentials.`);
|
|
4187
|
+
if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
|
|
4188
|
+
addDiagnostic2(diagnostics, source, token.start, `<${elementName3} src> must use HTTPS.`);
|
|
4189
|
+
const isAllowedOrigin = options.allowedAudioOrigins?.some((allowedOrigin) => {
|
|
4190
|
+
try {
|
|
4191
|
+
const configured = new URL(allowedOrigin);
|
|
4192
|
+
if (configured.protocol !== "https:" && configured.protocol !== "http:" || configured.username || configured.password || configured.pathname !== "/" || configured.search || configured.hash)
|
|
4193
|
+
return false;
|
|
4194
|
+
return configured.origin === parsed.origin;
|
|
4195
|
+
} catch {
|
|
4196
|
+
return false;
|
|
4197
|
+
}
|
|
4198
|
+
}) ?? false;
|
|
4199
|
+
if (options.allowedAudioOrigins && !isAllowedOrigin)
|
|
4200
|
+
addDiagnostic2(diagnostics, source, token.start, `<${elementName3} src> origin "${parsed.origin}" is not allowed.`);
|
|
4201
|
+
else if (!isAllowedOrigin && !options.allowExternalAudio)
|
|
4202
|
+
addDiagnostic2(
|
|
4203
|
+
diagnostics,
|
|
4204
|
+
source,
|
|
4205
|
+
token.start,
|
|
4206
|
+
`<${elementName3} src> external origin "${parsed.origin}" is blocked by default; set allowExternalAudio to true or provide allowedAudioOrigins.`
|
|
4207
|
+
);
|
|
4208
|
+
}
|
|
4209
|
+
function validateElement2(token, source, diagnostics, voiceName, options, voiceCatalog) {
|
|
4210
|
+
const name = token.name.toLowerCase();
|
|
4211
|
+
const tagStatus = featureStatusForTag2(token.name, options);
|
|
4212
|
+
if (tagStatus === "preview")
|
|
4213
|
+
addDiagnostic2(
|
|
4214
|
+
diagnostics,
|
|
4215
|
+
source,
|
|
4216
|
+
token.start,
|
|
4217
|
+
`<${token.name}> is an Azure Speech preview feature and may change or require preview access.`,
|
|
4218
|
+
"warning",
|
|
4219
|
+
"azure-preview-tag"
|
|
4220
|
+
);
|
|
4221
|
+
if (tagStatus === "deprecated")
|
|
4222
|
+
addDiagnostic2(
|
|
4223
|
+
diagnostics,
|
|
4224
|
+
source,
|
|
4225
|
+
token.start,
|
|
4226
|
+
`<${token.name}> is deprecated by Azure Speech; migrate to a supported alternative.`,
|
|
4227
|
+
"info",
|
|
4228
|
+
"azure-deprecated-tag"
|
|
4229
|
+
);
|
|
4230
|
+
if (name === "voice" && !attr2(token, "name")?.trim())
|
|
4231
|
+
addDiagnostic2(diagnostics, source, token.start, '<voice> requires a non-empty "name" attribute.');
|
|
4232
|
+
if (name === "break") {
|
|
4233
|
+
const time = attr2(token, "time");
|
|
4234
|
+
const strength = attr2(token, "strength");
|
|
4235
|
+
if (!time && !strength)
|
|
4236
|
+
addDiagnostic2(diagnostics, source, token.start, '<break> requires either "time" or "strength".');
|
|
4237
|
+
if (time && strength)
|
|
4238
|
+
addDiagnostic2(diagnostics, source, token.start, '<break> must not specify both "time" and "strength".');
|
|
4239
|
+
if (time && !/^\d+(?:\.\d+)?(?:ms|s)$/.test(time.trim()))
|
|
4240
|
+
addDiagnostic2(diagnostics, source, token.start, '<break time> must use a numeric value followed by "ms" or "s".');
|
|
4241
|
+
if (strength && !ALLOWED_BREAK_STRENGTHS2.has(strength))
|
|
4242
|
+
addDiagnostic2(diagnostics, source, token.start, `Unsupported <break strength> value "${strength}".`);
|
|
4243
|
+
}
|
|
4244
|
+
if (name === "prosody") {
|
|
4245
|
+
const rate = attr2(token, "rate");
|
|
4246
|
+
const pitch = attr2(token, "pitch");
|
|
4247
|
+
const volume = attr2(token, "volume");
|
|
4248
|
+
if (rate && !isSupportedProsodyRate2(rate))
|
|
4249
|
+
addDiagnostic2(diagnostics, source, token.start, `Unsupported <prosody rate> value "${rate}".`);
|
|
4250
|
+
if (pitch && !/^(x-low|low|medium|high|x-high|[+-]?\d+(?:\.\d+)?(?:st|Hz|%)?)$/.test(pitch.trim()))
|
|
4251
|
+
addDiagnostic2(diagnostics, source, token.start, `Unsupported <prosody pitch> value "${pitch}".`);
|
|
4252
|
+
if (volume && !/^(silent|x-soft|soft|medium|loud|x-loud|[+-]?\d+(?:\.\d+)?(?:dB|%)?)$/.test(volume.trim()))
|
|
4253
|
+
addDiagnostic2(diagnostics, source, token.start, `Unsupported <prosody volume> value "${volume}".`);
|
|
4254
|
+
}
|
|
4255
|
+
if (name === "mstts:express-as" || name === "express-as" || name === "expressas") {
|
|
4256
|
+
const style = attr2(token, "style");
|
|
4257
|
+
if (!style?.trim())
|
|
4258
|
+
addDiagnostic2(diagnostics, source, token.start, '<mstts:express-as> requires a non-empty "style" attribute.');
|
|
4259
|
+
const degree = attr2(token, "styledegree") ?? attr2(token, "style-degree");
|
|
4260
|
+
if (degree && (!/^\d+(?:\.\d+)?$/.test(degree) || Number(degree) < 0.01 || Number(degree) > 2))
|
|
4261
|
+
addDiagnostic2(
|
|
4262
|
+
diagnostics,
|
|
4263
|
+
source,
|
|
4264
|
+
token.start,
|
|
4265
|
+
"<mstts:express-as styledegree> must be a number between 0.01 and 2."
|
|
4266
|
+
);
|
|
4267
|
+
const role = attr2(token, "role");
|
|
4268
|
+
if (role && !ALLOWED_ROLES2.has(role))
|
|
4269
|
+
addDiagnostic2(diagnostics, source, token.start, `Unsupported <mstts:express-as role> value "${role}".`);
|
|
4270
|
+
const definition = voiceName ? voiceCatalog.get(voiceName.toLowerCase()) : void 0;
|
|
4271
|
+
const supportedStyles = definition?.styles;
|
|
4272
|
+
const severity = diagnosticSeverity2(options.unsupportedStylePolicy ?? options.unknownVoicePolicy ?? "warn");
|
|
4273
|
+
if (style && definition && !supportedStyles?.some((candidate) => candidate.toLowerCase() === style.toLowerCase()) && severity)
|
|
4274
|
+
addDiagnostic2(
|
|
4275
|
+
diagnostics,
|
|
4276
|
+
source,
|
|
4277
|
+
token.start,
|
|
4278
|
+
`Unknown style "${style}" is not supported by voice "${voiceName}" according to the configured voice style map.`,
|
|
4279
|
+
severity,
|
|
4280
|
+
"azure-unsupported-style"
|
|
4281
|
+
);
|
|
4282
|
+
if (style && voiceName && !definition && severity)
|
|
4283
|
+
addDiagnostic2(
|
|
4284
|
+
diagnostics,
|
|
4285
|
+
source,
|
|
4286
|
+
token.start,
|
|
4287
|
+
`Unknown style "${style}" cannot be verified because voice "${voiceName}" is not registered in the voice style map.`,
|
|
4288
|
+
severity
|
|
4289
|
+
);
|
|
4290
|
+
}
|
|
4291
|
+
if (name === "say-as" || name === "sayas") {
|
|
4292
|
+
const interpretAs = attr2(token, "interpret-as");
|
|
4293
|
+
if (!interpretAs || !ALLOWED_SAY_AS2.has(interpretAs))
|
|
4294
|
+
addDiagnostic2(diagnostics, source, token.start, `<say-as> requires a supported "interpret-as" value.`);
|
|
4295
|
+
}
|
|
4296
|
+
if (name === "phoneme" && (!attr2(token, "alphabet") || !attr2(token, "ph")))
|
|
4297
|
+
addDiagnostic2(diagnostics, source, token.start, '<phoneme> requires both "alphabet" and "ph" attributes.');
|
|
4298
|
+
if (name === "emphasis" && attr2(token, "level") && !ALLOWED_EMPHASIS_LEVELS2.has(attr2(token, "level") ?? ""))
|
|
4299
|
+
addDiagnostic2(diagnostics, source, token.start, `Unsupported <emphasis level> value "${attr2(token, "level")}".`);
|
|
4300
|
+
if (name === "sub" && !attr2(token, "alias")?.trim())
|
|
4301
|
+
addDiagnostic2(diagnostics, source, token.start, '<sub> requires a non-empty "alias" attribute.');
|
|
4302
|
+
if (name === "lang" && !attr2(token, "xml:lang")?.trim() && !attr2(token, "lang")?.trim())
|
|
4303
|
+
addDiagnostic2(diagnostics, source, token.start, '<lang> requires an "xml:lang" attribute.');
|
|
4304
|
+
if (name === "mark" && !attr2(token, "name")?.trim())
|
|
4305
|
+
addDiagnostic2(diagnostics, source, token.start, '<mark> requires a non-empty "name" attribute.');
|
|
4306
|
+
if (name === "bookmark" && !attr2(token, "mark")?.trim())
|
|
4307
|
+
addDiagnostic2(diagnostics, source, token.start, '<bookmark> requires a non-empty "mark" attribute.');
|
|
4308
|
+
if (name === "lexicon") {
|
|
4309
|
+
const uri = attr2(token, "uri");
|
|
4310
|
+
if (!uri) addDiagnostic2(diagnostics, source, token.start, '<lexicon> requires a "uri" attribute.');
|
|
4311
|
+
else {
|
|
4312
|
+
try {
|
|
4313
|
+
const parsed = new URL(uri);
|
|
4314
|
+
if (parsed.protocol !== "https:")
|
|
4315
|
+
addDiagnostic2(diagnostics, source, token.start, "<lexicon uri> must use HTTPS.");
|
|
4316
|
+
} catch {
|
|
4317
|
+
addDiagnostic2(diagnostics, source, token.start, "<lexicon uri> must be an absolute HTTPS URL.");
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
}
|
|
4321
|
+
if (name === "mstts:silence") {
|
|
4322
|
+
const type = attr2(token, "type");
|
|
4323
|
+
const value = attr2(token, "value");
|
|
4324
|
+
if (!type || !ALLOWED_SILENCE_TYPES2.has(type))
|
|
4325
|
+
addDiagnostic2(diagnostics, source, token.start, '<mstts:silence> requires a supported "type" attribute.');
|
|
4326
|
+
if (!value || !/^\d+(?:\.\d+)?(?:ms|s)$/.test(value.trim()))
|
|
4327
|
+
addDiagnostic2(diagnostics, source, token.start, '<mstts:silence> requires a time-valued "value" attribute.');
|
|
4328
|
+
}
|
|
4329
|
+
if (name === "mstts:audioduration") {
|
|
4330
|
+
const value = attr2(token, "value");
|
|
4331
|
+
if (!value || !isValidAzureAudioDuration2(value))
|
|
4332
|
+
addDiagnostic2(
|
|
4333
|
+
diagnostics,
|
|
4334
|
+
source,
|
|
4335
|
+
token.start,
|
|
4336
|
+
'<mstts:audioduration> requires a positive duration such as "10s", "5000ms", or "00:00:10".'
|
|
4337
|
+
);
|
|
4338
|
+
if (!token.selfClosing)
|
|
4339
|
+
addDiagnostic2(diagnostics, source, token.start, "<mstts:audioduration> must be self-closing.");
|
|
4340
|
+
}
|
|
4341
|
+
if (name === "mstts:viseme") {
|
|
4342
|
+
const type = attr2(token, "type");
|
|
4343
|
+
if (!type || !ALLOWED_VISEME_TYPES2.has(type))
|
|
4344
|
+
addDiagnostic2(diagnostics, source, token.start, '<mstts:viseme> requires a supported "type" attribute.');
|
|
4345
|
+
}
|
|
4346
|
+
if (name === "audio") {
|
|
4347
|
+
validateAudioSource2(token, source, diagnostics, options, "audio");
|
|
4348
|
+
}
|
|
4349
|
+
if (name === "mstts:turn") {
|
|
4350
|
+
if (!attr2(token, "voice")?.trim() && !attr2(token, "speaker")?.trim())
|
|
4351
|
+
addDiagnostic2(
|
|
4352
|
+
diagnostics,
|
|
4353
|
+
source,
|
|
4354
|
+
token.start,
|
|
4355
|
+
'<mstts:turn> requires a non-empty "voice" or "speaker" attribute.'
|
|
4356
|
+
);
|
|
4357
|
+
if (token.parentName?.toLowerCase() !== "mstts:dialog")
|
|
4358
|
+
addDiagnostic2(diagnostics, source, token.start, "<mstts:turn> is only allowed directly inside <mstts:dialog>.");
|
|
4359
|
+
}
|
|
4360
|
+
if (name === "mstts:backgroundaudio") {
|
|
4361
|
+
validateAudioSource2(token, source, diagnostics, options, "mstts:backgroundaudio");
|
|
4362
|
+
const volume = attr2(token, "volume");
|
|
4363
|
+
if (volume !== void 0 && (!/^\d+(?:\.\d+)?$/.test(volume.trim()) || Number(volume) > 100))
|
|
4364
|
+
addDiagnostic2(diagnostics, source, token.start, `Unsupported <mstts:backgroundaudio volume> value "${volume}".`);
|
|
4365
|
+
for (const [attribute, value] of [
|
|
4366
|
+
["fadein", attr2(token, "fadein")],
|
|
4367
|
+
["fadeout", attr2(token, "fadeout")]
|
|
4368
|
+
]) {
|
|
4369
|
+
if (value !== void 0 && !isValidAzureBackgroundAudioDuration2(value))
|
|
4370
|
+
addDiagnostic2(
|
|
4371
|
+
diagnostics,
|
|
4372
|
+
source,
|
|
4373
|
+
token.start,
|
|
4374
|
+
`<mstts:backgroundaudio ${attribute}> must be between 0 and 10000 milliseconds, for example "500ms" or "10s".`
|
|
4375
|
+
);
|
|
4376
|
+
}
|
|
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
|
+
}
|
|
4387
|
+
}
|
|
4388
|
+
function validateAzureSsmlStatic2(ssml, options = {}) {
|
|
4389
|
+
const diagnostics = [];
|
|
4390
|
+
if (typeof ssml !== "string") {
|
|
4391
|
+
return [
|
|
4392
|
+
{
|
|
4393
|
+
line: 1,
|
|
4394
|
+
column: 1,
|
|
4395
|
+
message: "SSML input must be a string",
|
|
4396
|
+
severity: "error",
|
|
4397
|
+
source: "ssml-static-validator"
|
|
4398
|
+
}
|
|
4399
|
+
];
|
|
4400
|
+
}
|
|
4401
|
+
const maxLength = options.maxLength ?? 1e4;
|
|
4402
|
+
if (ssml.length > maxLength)
|
|
4403
|
+
addDiagnostic2(diagnostics, ssml, maxLength, `SSML exceeds the maximum length of ${maxLength} characters.`);
|
|
4404
|
+
if (options.maxXmlDepth !== void 0 && (!Number.isInteger(options.maxXmlDepth) || options.maxXmlDepth <= 0)) {
|
|
4405
|
+
addDiagnostic2(diagnostics, ssml, 0, "maxXmlDepth must be a positive integer.");
|
|
4406
|
+
}
|
|
4407
|
+
try {
|
|
4408
|
+
parseSsml2(ssml);
|
|
4409
|
+
} catch (error) {
|
|
4410
|
+
const message = error instanceof Error ? error.message.replace(/ at position \d+$/, "") : String(error);
|
|
4411
|
+
const match = / at position (\d+)$/.exec(error instanceof Error ? error.message : "");
|
|
4412
|
+
addDiagnostic2(diagnostics, ssml, match ? Number(match[1]) : 0, message);
|
|
4413
|
+
return diagnostics;
|
|
4414
|
+
}
|
|
4415
|
+
const tokens = tokenizeElements2(ssml);
|
|
4416
|
+
if (options.maxXmlDepth !== void 0) {
|
|
4417
|
+
for (const token of tokens) {
|
|
4418
|
+
if (token.depth > options.maxXmlDepth) {
|
|
4419
|
+
addDiagnostic2(
|
|
4420
|
+
diagnostics,
|
|
4421
|
+
ssml,
|
|
4422
|
+
token.start,
|
|
4423
|
+
`XML nesting depth ${token.depth} exceeds the configured maximum of ${options.maxXmlDepth}.`
|
|
4424
|
+
);
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4427
|
+
}
|
|
4428
|
+
const speak = tokens.find((token) => token.name.toLowerCase() === "speak");
|
|
4429
|
+
const voices = tokens.filter((token) => token.name.toLowerCase() === "voice");
|
|
4430
|
+
const backgroundAudioTokens = tokens.filter((token) => token.name.toLowerCase() === "mstts:backgroundaudio");
|
|
4431
|
+
for (const [index, token] of backgroundAudioTokens.entries()) {
|
|
4432
|
+
if (index > 0)
|
|
4433
|
+
addDiagnostic2(
|
|
4434
|
+
diagnostics,
|
|
4435
|
+
ssml,
|
|
4436
|
+
token.start,
|
|
4437
|
+
"An SSML document can contain at most one <mstts:backgroundaudio> element."
|
|
4438
|
+
);
|
|
4439
|
+
}
|
|
4440
|
+
if (!speak || voices.length === 0)
|
|
4441
|
+
addDiagnostic2(
|
|
4442
|
+
diagnostics,
|
|
4443
|
+
ssml,
|
|
4444
|
+
speak?.start ?? 0,
|
|
4445
|
+
"Azure SSML requires at least one <voice> element under <speak>."
|
|
4446
|
+
);
|
|
4447
|
+
const voiceName = voices[0] ? attr2(voices[0], "name") : void 0;
|
|
4448
|
+
const voiceCatalog = normalizeVoiceCatalog2(options);
|
|
4449
|
+
const normalizeLanguage = createLanguageNormalizer2(options);
|
|
4450
|
+
const policySeverity = diagnosticSeverity2(options.unknownVoicePolicy ?? "warn");
|
|
4451
|
+
const voicesToValidate = options.validateNestedVoices === false ? voices.slice(0, 1) : voices;
|
|
4452
|
+
for (const token of voicesToValidate) {
|
|
4453
|
+
const name = attr2(token, "name")?.trim();
|
|
4454
|
+
const language = attr2(token, "xml:lang")?.trim() || (speak ? attr2(speak, "xml:lang")?.trim() : void 0);
|
|
4455
|
+
const definition = name ? voiceCatalog.get(name.toLowerCase()) : void 0;
|
|
4456
|
+
if (name && definition?.status === "preview")
|
|
4457
|
+
addDiagnostic2(
|
|
4458
|
+
diagnostics,
|
|
4459
|
+
ssml,
|
|
4460
|
+
token.start,
|
|
4461
|
+
`Voice "${name}" is an Azure Speech preview voice and may change or require preview access.`,
|
|
4462
|
+
"warning",
|
|
4463
|
+
"azure-preview-voice"
|
|
4464
|
+
);
|
|
4465
|
+
if (name && definition?.status === "deprecated")
|
|
4466
|
+
addDiagnostic2(
|
|
4467
|
+
diagnostics,
|
|
4468
|
+
ssml,
|
|
4469
|
+
token.start,
|
|
4470
|
+
`Voice "${name}" is deprecated by Azure Speech; migrate to a supported voice.`,
|
|
4471
|
+
"info",
|
|
4472
|
+
"azure-deprecated-voice"
|
|
4473
|
+
);
|
|
4474
|
+
if (name && !definition && policySeverity)
|
|
4475
|
+
addDiagnostic2(
|
|
4476
|
+
diagnostics,
|
|
4477
|
+
ssml,
|
|
4478
|
+
token.start,
|
|
4479
|
+
`Unknown voice "${name}" is not registered in the voice catalog.`,
|
|
4480
|
+
policySeverity,
|
|
4481
|
+
"azure-unknown-voice"
|
|
4482
|
+
);
|
|
4483
|
+
if (name && language && definitionMatchesLanguage2(definition, name, language, normalizeLanguage) === false)
|
|
4484
|
+
addDiagnostic2(
|
|
4485
|
+
diagnostics,
|
|
4486
|
+
ssml,
|
|
4487
|
+
token.start,
|
|
4488
|
+
`Voice "${name}" does not match language "${language}"; the voice name prefix indicates a different language or region.`,
|
|
4489
|
+
"warning",
|
|
4490
|
+
"azure-locale-mismatch"
|
|
4491
|
+
);
|
|
4492
|
+
}
|
|
4493
|
+
for (const token of tokens) {
|
|
4494
|
+
const tokenName = token.name.toLowerCase();
|
|
4495
|
+
const tokenVoiceName = tokenName === "voice" ? attr2(token, "name")?.trim() : tokenName === "mstts:turn" ? attr2(token, "voice")?.trim() || token.parentVoiceName : options.validateNestedVoices === false ? voiceName : token.parentVoiceName;
|
|
4496
|
+
validateElement2(token, ssml, diagnostics, tokenVoiceName, options, voiceCatalog);
|
|
4497
|
+
const definition = tokenVoiceName ? voiceCatalog.get(tokenVoiceName.toLowerCase()) : void 0;
|
|
4498
|
+
validateVoiceFeatureMatrix2(token, ssml, diagnostics, tokenVoiceName, definition);
|
|
4499
|
+
if (tokenName === "voice" && options.model && definition?.models && !definition.models.some((model) => model.toLowerCase() === options.model?.toLowerCase())) {
|
|
4500
|
+
addDiagnostic2(
|
|
4501
|
+
diagnostics,
|
|
4502
|
+
ssml,
|
|
4503
|
+
token.start,
|
|
4504
|
+
`Voice "${tokenVoiceName}" does not support model "${options.model}" according to the configured feature matrix.`,
|
|
4505
|
+
"error",
|
|
4506
|
+
"azure-unsupported-model-for-voice"
|
|
4507
|
+
);
|
|
4508
|
+
}
|
|
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
|
+
});
|
|
4519
|
+
}
|
|
4520
|
+
function validateAzureSsml2(ssml, options = {}) {
|
|
4521
|
+
const diagnostics = validateAzureSsmlStatic2(ssml, options);
|
|
4522
|
+
const validator = options.urlValidator ?? options.customUrlValidator;
|
|
4523
|
+
if (!validator || typeof ssml !== "string") return diagnostics;
|
|
4524
|
+
const runnerOptions = options.urlValidation ?? {};
|
|
4525
|
+
const boundedValidator = createAzureUrlValidatorRunner2(validator, {
|
|
4526
|
+
...runnerOptions,
|
|
4527
|
+
...options.urlValidatorConcurrency !== void 0 ? { concurrency: options.urlValidatorConcurrency } : {},
|
|
4528
|
+
...options.urlValidatorTimeoutMs !== void 0 ? { timeoutMs: options.urlValidatorTimeoutMs } : {},
|
|
4529
|
+
...options.urlValidatorSignal ? { signal: options.urlValidatorSignal } : {},
|
|
4530
|
+
...options.urlValidatorCache ? { cache: options.urlValidatorCache } : {}
|
|
4531
|
+
});
|
|
4532
|
+
let tokens;
|
|
4533
|
+
try {
|
|
4534
|
+
tokens = tokenizeElements2(ssml);
|
|
4535
|
+
} catch {
|
|
4536
|
+
return diagnostics;
|
|
4537
|
+
}
|
|
4538
|
+
const checks = tokens.flatMap(
|
|
4539
|
+
(token) => urlAttributes2(token).map(async ({ attribute, value }) => {
|
|
4540
|
+
try {
|
|
4541
|
+
const result = await boundedValidator(value, { tag: token.name, attribute });
|
|
4542
|
+
const valid = typeof result === "boolean" ? result : result.valid;
|
|
4543
|
+
if (!valid) {
|
|
4544
|
+
const reason = typeof result === "boolean" ? void 0 : result.reason;
|
|
4545
|
+
addDiagnostic2(
|
|
4546
|
+
diagnostics,
|
|
4547
|
+
ssml,
|
|
4548
|
+
token.start,
|
|
4549
|
+
`<${token.name} ${attribute}> was rejected by the custom URL validator${reason ? `: ${reason}` : "."}`
|
|
4550
|
+
);
|
|
4551
|
+
}
|
|
4552
|
+
} catch (error) {
|
|
4553
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
4554
|
+
addDiagnostic2(
|
|
4555
|
+
diagnostics,
|
|
4556
|
+
ssml,
|
|
4557
|
+
token.start,
|
|
4558
|
+
`<${token.name} ${attribute}> could not be validated by the custom URL validator: ${reason}`
|
|
4559
|
+
);
|
|
4560
|
+
}
|
|
4561
|
+
})
|
|
4562
|
+
);
|
|
4563
|
+
return Promise.all(checks).then(() => diagnostics);
|
|
4564
|
+
}
|
|
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
|
+
|
|
4572
|
+
// packages/azure-tts-client/src/safe.ts
|
|
4573
|
+
var ChunkValidationError = class extends Error {
|
|
4574
|
+
constructor(chunkIndex, diagnostics) {
|
|
4575
|
+
super(`SSML validation failed for chunk ${chunkIndex}; the Azure Speech API was not called.`);
|
|
4576
|
+
this.kind = "chunk-validation";
|
|
4577
|
+
this.name = "ChunkValidationError";
|
|
4578
|
+
this.chunkIndex = chunkIndex;
|
|
4579
|
+
this.diagnostics = diagnostics;
|
|
4580
|
+
}
|
|
4581
|
+
};
|
|
4582
|
+
async function synthesizeSsmlSafe(client, ssml, options = {}) {
|
|
4583
|
+
const validationOptions = options.validation ?? options;
|
|
4584
|
+
const diagnostics = await Promise.resolve(validateAzureSsml2(ssml, validationOptions));
|
|
4585
|
+
const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
4586
|
+
if (errors.length > 0) {
|
|
4587
|
+
return {
|
|
4588
|
+
ok: false,
|
|
4589
|
+
success: false,
|
|
4590
|
+
status: "validation-error",
|
|
4591
|
+
error: {
|
|
4592
|
+
kind: "validation",
|
|
4593
|
+
message: "SSML validation failed; the Azure Speech API was not called.",
|
|
4594
|
+
diagnostics: errors
|
|
4595
|
+
}
|
|
4596
|
+
};
|
|
4597
|
+
}
|
|
4598
|
+
try {
|
|
4599
|
+
return { ok: true, success: true, status: "success", value: await client.synthesizeSsml(ssml) };
|
|
4600
|
+
} catch (error) {
|
|
4601
|
+
const azureError = error instanceof AzureTtsError ? error : createSpeechSdkError(error);
|
|
4602
|
+
return { ok: false, success: false, status: "azure-api-error", error: azureError };
|
|
4603
|
+
}
|
|
4604
|
+
}
|
|
4605
|
+
async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
4606
|
+
const validationOptions = options.validation ?? options;
|
|
4607
|
+
const pending = (index, status, error) => {
|
|
4608
|
+
options.onProgress?.({
|
|
4609
|
+
currentChunk: status === "success" ? index + 1 : index,
|
|
4610
|
+
totalChunks: chunks.length,
|
|
4611
|
+
percent: chunks.length === 0 ? 100 : Math.round((status === "success" ? index + 1 : index) / chunks.length * 100),
|
|
4612
|
+
chunkIndex: index,
|
|
4613
|
+
originalTextRange: typeof chunks[index] === "string" ? void 0 : chunks[index]?.originalTextRange,
|
|
4614
|
+
status,
|
|
4615
|
+
durationMs: 0,
|
|
4616
|
+
...error ? { error } : {}
|
|
4617
|
+
});
|
|
4618
|
+
};
|
|
4619
|
+
chunks.forEach((_chunk, index) => {
|
|
4620
|
+
pending(index, "pending");
|
|
4621
|
+
});
|
|
4622
|
+
const validations = await Promise.all(
|
|
4623
|
+
chunks.map(async (chunk) => {
|
|
4624
|
+
const ssml = typeof chunk === "string" ? chunk : chunk.ssml;
|
|
4625
|
+
const diagnostics = await Promise.resolve(validateAzureSsml2(ssml, validationOptions));
|
|
4626
|
+
return diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
4627
|
+
})
|
|
4628
|
+
);
|
|
4629
|
+
const firstInvalidIndex = validations.findIndex((diagnostics) => diagnostics.length > 0);
|
|
4630
|
+
if (firstInvalidIndex >= 0) {
|
|
4631
|
+
const error = new ChunkValidationError(firstInvalidIndex, validations[firstInvalidIndex] ?? []);
|
|
4632
|
+
pending(firstInvalidIndex, "failed", error);
|
|
4633
|
+
return { ok: false, success: false, status: "validation-error", error };
|
|
4634
|
+
}
|
|
4635
|
+
try {
|
|
4636
|
+
if (client.synthesizeChunks) {
|
|
4637
|
+
const value = await client.synthesizeChunks(chunks, { onProgress: options.onProgress });
|
|
4638
|
+
return { ok: true, success: true, status: "success", value };
|
|
4639
|
+
}
|
|
4640
|
+
const results = [];
|
|
4641
|
+
for (const [index, chunk] of chunks.entries()) {
|
|
4642
|
+
const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
|
|
4643
|
+
const sourceNodePath = input.sourceNodePath;
|
|
4644
|
+
pending(index, "synthesizing");
|
|
4645
|
+
const startedAt = Date.now();
|
|
4646
|
+
try {
|
|
4647
|
+
const result = await client.synthesizeSsml(input.ssml);
|
|
4648
|
+
results.push({
|
|
4649
|
+
...result,
|
|
4650
|
+
...input.originalTextRange ? { textRange: { ...input.originalTextRange } } : {},
|
|
4651
|
+
...sourceNodePath ? {
|
|
4652
|
+
boundaries: result.boundaries?.map((event) => ({
|
|
4653
|
+
...event,
|
|
4654
|
+
sourceNodePath: [...sourceNodePath]
|
|
4655
|
+
})),
|
|
4656
|
+
visemes: result.visemes?.map((event) => ({ ...event, sourceNodePath: [...sourceNodePath] })),
|
|
4657
|
+
bookmarks: result.bookmarks?.map((event) => ({ ...event, sourceNodePath: [...sourceNodePath] }))
|
|
4658
|
+
} : {}
|
|
4659
|
+
});
|
|
4660
|
+
options.onProgress?.({
|
|
4661
|
+
currentChunk: index + 1,
|
|
4662
|
+
totalChunks: chunks.length,
|
|
4663
|
+
percent: chunks.length === 0 ? 100 : Math.round((index + 1) / chunks.length * 100),
|
|
4664
|
+
chunkIndex: index,
|
|
4665
|
+
originalTextRange: input.originalTextRange,
|
|
4666
|
+
status: "success",
|
|
4667
|
+
durationMs: Date.now() - startedAt
|
|
4668
|
+
});
|
|
4669
|
+
} catch (error) {
|
|
4670
|
+
options.onProgress?.({
|
|
4671
|
+
currentChunk: index,
|
|
4672
|
+
totalChunks: chunks.length,
|
|
4673
|
+
percent: chunks.length === 0 ? 100 : Math.round(index / chunks.length * 100),
|
|
4674
|
+
chunkIndex: index,
|
|
4675
|
+
originalTextRange: input.originalTextRange,
|
|
4676
|
+
status: "failed",
|
|
4677
|
+
durationMs: Date.now() - startedAt,
|
|
4678
|
+
error
|
|
4679
|
+
});
|
|
4680
|
+
throw error;
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
4683
|
+
return {
|
|
4684
|
+
ok: true,
|
|
4685
|
+
success: true,
|
|
4686
|
+
status: "success",
|
|
4687
|
+
value: mergeSynthesisResults(results, options.outputFormat)
|
|
4688
|
+
};
|
|
4689
|
+
} catch (error) {
|
|
4690
|
+
const azureError = error instanceof AzureTtsError ? error : createSpeechSdkError(error);
|
|
4691
|
+
return { ok: false, success: false, status: "azure-api-error", error: azureError };
|
|
4692
|
+
}
|
|
4693
|
+
}
|
|
4694
|
+
|
|
4695
|
+
// packages/azure-tts-client/src/client.ts
|
|
4696
|
+
var ENDPOINT_TEMPLATE = "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
|
|
4697
|
+
var _options;
|
|
4698
|
+
var AzureTtsClient = class {
|
|
4699
|
+
constructor(options) {
|
|
4700
|
+
__privateAdd(this, _options);
|
|
4701
|
+
__privateSet(this, _options, options);
|
|
4702
|
+
}
|
|
4703
|
+
async synthesize(ssml) {
|
|
4704
|
+
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
4705
|
+
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
4706
|
+
__privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
|
|
4707
|
+
const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };
|
|
4708
|
+
return synthesizeSpeech(ssml, config);
|
|
4709
|
+
}
|
|
4710
|
+
async synthesizeSsml(ssml) {
|
|
4711
|
+
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
4712
|
+
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
4713
|
+
__privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
|
|
4714
|
+
return synthesizeSsml(ssml, { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs });
|
|
4715
|
+
}
|
|
4716
|
+
async synthesizeChunks(chunks, options = {}) {
|
|
4717
|
+
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
4718
|
+
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
4719
|
+
return synthesizeSsmlChunks(chunks, {
|
|
4720
|
+
endpoint,
|
|
4721
|
+
region,
|
|
4722
|
+
subscriptionKey,
|
|
4723
|
+
outputFormat,
|
|
4724
|
+
signal,
|
|
4725
|
+
timeoutMs,
|
|
4726
|
+
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress
|
|
4727
|
+
});
|
|
4728
|
+
}
|
|
4729
|
+
async synthesizeSsmlSafe(ssml, options = {}) {
|
|
4730
|
+
return synthesizeSsmlSafe(this, ssml, options);
|
|
4731
|
+
}
|
|
4732
|
+
async synthesizeChunksSafe(chunks, options = {}) {
|
|
4733
|
+
return synthesizeSsmlChunksSafe(this, chunks, {
|
|
4734
|
+
...options,
|
|
4735
|
+
outputFormat: options.outputFormat ?? __privateGet(this, _options).outputFormat,
|
|
4736
|
+
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress
|
|
4737
|
+
});
|
|
4738
|
+
}
|
|
4739
|
+
async synthesizeSsmlChunksSafe(chunks, options = {}) {
|
|
4740
|
+
return this.synthesizeChunksSafe(chunks, options);
|
|
2537
4741
|
}
|
|
2538
4742
|
};
|
|
2539
4743
|
_options = new WeakMap();
|
|
@@ -2590,6 +4794,9 @@ async function fetchAzureVoiceCatalog(options) {
|
|
|
2590
4794
|
const secondaryLocales = stringList(record.SecondaryLocaleList);
|
|
2591
4795
|
const styles = stringList(record.StyleList);
|
|
2592
4796
|
const status = normalizeStatus(record.Status);
|
|
4797
|
+
const supportedTags = stringList(record.SupportedTags);
|
|
4798
|
+
const unsupportedTags = stringList(record.UnsupportedTags);
|
|
4799
|
+
const models = stringList(record.Models);
|
|
2593
4800
|
const merged = {
|
|
2594
4801
|
name: existing?.name ?? name,
|
|
2595
4802
|
locale: existing?.locale ?? locale,
|
|
@@ -2599,6 +4806,12 @@ async function fetchAzureVoiceCatalog(options) {
|
|
|
2599
4806
|
if (mergedSecondaryLocales.length > 0) merged.secondaryLocales = mergedSecondaryLocales;
|
|
2600
4807
|
const mergedStyles = [.../* @__PURE__ */ new Set([...existing?.styles ?? [], ...styles])];
|
|
2601
4808
|
if (mergedStyles.length > 0) merged.styles = mergedStyles;
|
|
4809
|
+
const mergedSupportedTags = [.../* @__PURE__ */ new Set([...existing?.supportedTags ?? [], ...supportedTags])];
|
|
4810
|
+
if (mergedSupportedTags.length > 0) merged.supportedTags = mergedSupportedTags;
|
|
4811
|
+
const mergedUnsupportedTags = [.../* @__PURE__ */ new Set([...existing?.unsupportedTags ?? [], ...unsupportedTags])];
|
|
4812
|
+
if (mergedUnsupportedTags.length > 0) merged.unsupportedTags = mergedUnsupportedTags;
|
|
4813
|
+
const mergedModels = [.../* @__PURE__ */ new Set([...existing?.models ?? [], ...models])];
|
|
4814
|
+
if (mergedModels.length > 0) merged.models = mergedModels;
|
|
2602
4815
|
if (status) merged.status = status;
|
|
2603
4816
|
else if (existing?.status) merged.status = existing.status;
|
|
2604
4817
|
voices.set(key, merged);
|
|
@@ -2620,9 +4833,13 @@ async function fetchAzureVoiceCatalog(options) {
|
|
|
2620
4833
|
AzureTtsClient,
|
|
2621
4834
|
AzureTtsError,
|
|
2622
4835
|
AzureTtsSdkError,
|
|
4836
|
+
ChunkValidationError,
|
|
4837
|
+
UnsupportedMergeFormatError,
|
|
2623
4838
|
areAzureLanguagesEquivalent,
|
|
2624
4839
|
buildPartialSsml,
|
|
2625
4840
|
buildSsml,
|
|
4841
|
+
canMergeAudioFormat,
|
|
4842
|
+
createAzureUrlValidatorRunner,
|
|
2626
4843
|
extractSsmlText,
|
|
2627
4844
|
extractSsmlTranslatableText,
|
|
2628
4845
|
fetchAzureVoiceCatalog,
|
|
@@ -2631,11 +4848,17 @@ async function fetchAzureVoiceCatalog(options) {
|
|
|
2631
4848
|
getBuiltInVoiceCatalogMetadata,
|
|
2632
4849
|
isValidAzureAudioDuration,
|
|
2633
4850
|
mapSsmlTextNodes,
|
|
4851
|
+
mergeAudioBuffers,
|
|
4852
|
+
mergeSynthesisResults,
|
|
2634
4853
|
normalizeAzureLanguage,
|
|
2635
4854
|
parseSsml,
|
|
4855
|
+
resolveMergeAudioFormat,
|
|
2636
4856
|
splitSsmlDocument,
|
|
2637
4857
|
synthesizeSpeech,
|
|
2638
4858
|
synthesizeSsml,
|
|
4859
|
+
synthesizeSsmlChunks,
|
|
4860
|
+
synthesizeSsmlChunksSafe,
|
|
4861
|
+
synthesizeSsmlSafe,
|
|
2639
4862
|
validateAzureSsml,
|
|
2640
4863
|
validateSsml,
|
|
2641
4864
|
validateSsmlStructureIntegrity
|