ssml-builder-js 2.8.1 → 2.9.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 +80 -8
- package/dist/{chunk-VCDMKVVT.mjs → chunk-7LCSH4SZ.mjs} +457 -25
- package/dist/chunk-7LCSH4SZ.mjs.map +1 -0
- package/dist/{chunk-MWSDFGXW.mjs → chunk-JNJVTEL6.mjs} +785 -9
- package/dist/chunk-JNJVTEL6.mjs.map +1 -0
- package/dist/core.d.mts +91 -3
- package/dist/core.d.ts +91 -3
- package/dist/core.js +462 -25
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +13 -3
- package/dist/elements.d.mts +10 -1
- package/dist/elements.d.ts +10 -1
- package/dist/elements.js +1071 -13
- package/dist/elements.js.map +1 -1
- package/dist/elements.mjs +292 -8
- package/dist/elements.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +462 -25
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +13 -3
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +40 -3
- package/dist/react.d.ts +40 -3
- package/dist/react.js +1026 -19
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +237 -5
- package/dist/react.mjs.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-MWSDFGXW.mjs.map +0 -1
- package/dist/chunk-VCDMKVVT.mjs.map +0 -1
package/dist/core.js
CHANGED
|
@@ -31,12 +31,17 @@ __export(core_exports, {
|
|
|
31
31
|
buildPartialSsml: () => buildPartialSsml,
|
|
32
32
|
buildSsml: () => buildSsml,
|
|
33
33
|
extractSsmlText: () => extractSsmlText,
|
|
34
|
+
extractSsmlTranslatableText: () => extractSsmlTranslatableText,
|
|
35
|
+
fromPlainTextToSsml: () => fromPlainTextToSsml,
|
|
36
|
+
getAzureVoiceCatalogMetadata: () => getAzureVoiceCatalogMetadata,
|
|
37
|
+
getBuiltInVoiceCatalogMetadata: () => getBuiltInVoiceCatalogMetadata,
|
|
34
38
|
isValidAzureAudioDuration: () => isValidAzureAudioDuration,
|
|
35
39
|
mapSsmlTextNodes: () => mapSsmlTextNodes,
|
|
36
40
|
normalizeAzureLanguage: () => normalizeAzureLanguage,
|
|
37
41
|
parseSsml: () => parseSsml,
|
|
38
42
|
validateAzureSsml: () => validateAzureSsml,
|
|
39
|
-
validateSsml: () => validateSsml
|
|
43
|
+
validateSsml: () => validateSsml,
|
|
44
|
+
validateSsmlStructureIntegrity: () => validateSsmlStructureIntegrity
|
|
40
45
|
});
|
|
41
46
|
module.exports = __toCommonJS(core_exports);
|
|
42
47
|
|
|
@@ -73,7 +78,13 @@ var SSML_TAGS = {
|
|
|
73
78
|
SILENCE: "silence",
|
|
74
79
|
MSTTS_VISEME: "mstts:viseme",
|
|
75
80
|
VISEME: "viseme",
|
|
76
|
-
MSTTS_AUDIO_DURATION: "mstts:audioduration"
|
|
81
|
+
MSTTS_AUDIO_DURATION: "mstts:audioduration",
|
|
82
|
+
MSTTS_DIALOG: "mstts:dialog",
|
|
83
|
+
MSTTS_TURN: "mstts:turn",
|
|
84
|
+
MSTTS_BACKGROUND_AUDIO: "mstts:backgroundaudio",
|
|
85
|
+
MSTTS_TTS_EMBEDDING: "mstts:ttsembedding",
|
|
86
|
+
MSTTS_EMBEDDING: "mstts:embedding",
|
|
87
|
+
MSTTS_VOICE_CONVERSION: "mstts:voiceconversion"
|
|
77
88
|
};
|
|
78
89
|
var SSML_ATTRS = {
|
|
79
90
|
VERSION: "version",
|
|
@@ -82,6 +93,7 @@ var SSML_ATTRS = {
|
|
|
82
93
|
LANG: "lang",
|
|
83
94
|
MSTTS_XMLNS: "xmlns:mstts",
|
|
84
95
|
NAME: "name",
|
|
96
|
+
VOICE: "voice",
|
|
85
97
|
EFFECT: "effect",
|
|
86
98
|
RATE: "rate",
|
|
87
99
|
PITCH: "pitch",
|
|
@@ -113,7 +125,9 @@ var SSML_ATTRS = {
|
|
|
113
125
|
MARK: "mark",
|
|
114
126
|
URI: "uri",
|
|
115
127
|
TYPE: "type",
|
|
116
|
-
VALUE: "value"
|
|
128
|
+
VALUE: "value",
|
|
129
|
+
FADE_IN: "fadein",
|
|
130
|
+
FADE_OUT: "fadeout"
|
|
117
131
|
};
|
|
118
132
|
|
|
119
133
|
// packages/ssml-core/src/builder.ts
|
|
@@ -205,6 +219,20 @@ function getAttributes(element) {
|
|
|
205
219
|
case SSML_TAGS.MSTTS_AUDIO_DURATION:
|
|
206
220
|
addAttribute(attributes, SSML_ATTRS.VALUE, element.value);
|
|
207
221
|
break;
|
|
222
|
+
case SSML_TAGS.MSTTS_TURN:
|
|
223
|
+
addAttribute(attributes, SSML_ATTRS.VOICE, element.voice);
|
|
224
|
+
break;
|
|
225
|
+
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO:
|
|
226
|
+
addAttribute(attributes, SSML_ATTRS.SRC, element.src);
|
|
227
|
+
addAttribute(attributes, SSML_ATTRS.VOLUME, element.volume);
|
|
228
|
+
addAttribute(attributes, SSML_ATTRS.FADE_IN, element.fadeIn ?? element.fadein);
|
|
229
|
+
addAttribute(attributes, SSML_ATTRS.FADE_OUT, element.fadeOut ?? element.fadeout);
|
|
230
|
+
break;
|
|
231
|
+
case SSML_TAGS.MSTTS_DIALOG:
|
|
232
|
+
case SSML_TAGS.MSTTS_TTS_EMBEDDING:
|
|
233
|
+
case SSML_TAGS.MSTTS_EMBEDDING:
|
|
234
|
+
case SSML_TAGS.MSTTS_VOICE_CONVERSION:
|
|
235
|
+
break;
|
|
208
236
|
case SSML_TAGS.PARAGRAPH:
|
|
209
237
|
case SSML_TAGS.SENTENCE:
|
|
210
238
|
case SSML_TAGS.WORD:
|
|
@@ -771,6 +799,40 @@ function convertElement(node) {
|
|
|
771
799
|
if (value !== void 0) element.value = value;
|
|
772
800
|
return finishElement(element, node, attributes);
|
|
773
801
|
}
|
|
802
|
+
case SSML_TAGS.MSTTS_DIALOG: {
|
|
803
|
+
const element = { type: SSML_TAGS.MSTTS_DIALOG };
|
|
804
|
+
return finishElement(element, node, attributes);
|
|
805
|
+
}
|
|
806
|
+
case SSML_TAGS.MSTTS_TURN: {
|
|
807
|
+
const element = { type: SSML_TAGS.MSTTS_TURN };
|
|
808
|
+
const voice = readAttribute(attributes, SSML_ATTRS.VOICE);
|
|
809
|
+
if (voice !== void 0) element.voice = voice;
|
|
810
|
+
return finishElement(element, node, attributes);
|
|
811
|
+
}
|
|
812
|
+
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO: {
|
|
813
|
+
const element = { type: SSML_TAGS.MSTTS_BACKGROUND_AUDIO };
|
|
814
|
+
const src = readAttribute(attributes, SSML_ATTRS.SRC);
|
|
815
|
+
const volume = readAttribute(attributes, SSML_ATTRS.VOLUME);
|
|
816
|
+
const fadeIn = readAttribute(attributes, SSML_ATTRS.FADE_IN);
|
|
817
|
+
const fadeOut = readAttribute(attributes, SSML_ATTRS.FADE_OUT);
|
|
818
|
+
if (src !== void 0) element.src = src;
|
|
819
|
+
if (volume !== void 0) element.volume = volume;
|
|
820
|
+
if (fadeIn !== void 0) element.fadeIn = fadeIn;
|
|
821
|
+
if (fadeOut !== void 0) element.fadeOut = fadeOut;
|
|
822
|
+
return finishElement(element, node, attributes);
|
|
823
|
+
}
|
|
824
|
+
case SSML_TAGS.MSTTS_TTS_EMBEDDING: {
|
|
825
|
+
const element = { type: SSML_TAGS.MSTTS_TTS_EMBEDDING };
|
|
826
|
+
return finishElement(element, node, attributes);
|
|
827
|
+
}
|
|
828
|
+
case SSML_TAGS.MSTTS_EMBEDDING: {
|
|
829
|
+
const element = { type: SSML_TAGS.MSTTS_EMBEDDING };
|
|
830
|
+
return finishElement(element, node, attributes);
|
|
831
|
+
}
|
|
832
|
+
case SSML_TAGS.MSTTS_VOICE_CONVERSION: {
|
|
833
|
+
const element = { type: SSML_TAGS.MSTTS_VOICE_CONVERSION };
|
|
834
|
+
return finishElement(element, node, attributes);
|
|
835
|
+
}
|
|
774
836
|
default: {
|
|
775
837
|
const element = {
|
|
776
838
|
name: node.name,
|
|
@@ -1045,6 +1107,283 @@ async function mapSsmlTextNodes(ssml, transform, options = {}) {
|
|
|
1045
1107
|
return result + ssml.slice(cursor);
|
|
1046
1108
|
}
|
|
1047
1109
|
|
|
1110
|
+
// packages/ssml-core/src/migration.ts
|
|
1111
|
+
var DEFAULT_TRANSLATION_SKIP_TAGS = ["phoneme", "say-as", "sayAs", "sub"];
|
|
1112
|
+
function elementName(element) {
|
|
1113
|
+
switch (element.type) {
|
|
1114
|
+
case "custom":
|
|
1115
|
+
case "element":
|
|
1116
|
+
return element.name;
|
|
1117
|
+
case "expressAs":
|
|
1118
|
+
return "mstts:express-as";
|
|
1119
|
+
case "sayAs":
|
|
1120
|
+
return "say-as";
|
|
1121
|
+
case "silence":
|
|
1122
|
+
return "mstts:silence";
|
|
1123
|
+
case "viseme":
|
|
1124
|
+
return "mstts:viseme";
|
|
1125
|
+
default:
|
|
1126
|
+
return element.type;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
function addAttribute2(attributes, name, value) {
|
|
1130
|
+
if (value !== void 0) attributes[name] = value;
|
|
1131
|
+
}
|
|
1132
|
+
function elementAttributes(element) {
|
|
1133
|
+
const attributes = { ...element.attributes ?? {} };
|
|
1134
|
+
switch (element.type) {
|
|
1135
|
+
case "voice":
|
|
1136
|
+
addAttribute2(attributes, "name", element.name);
|
|
1137
|
+
addAttribute2(attributes, "effect", element.effect);
|
|
1138
|
+
break;
|
|
1139
|
+
case "prosody":
|
|
1140
|
+
addAttribute2(attributes, "rate", element.rate);
|
|
1141
|
+
addAttribute2(attributes, "pitch", element.pitch);
|
|
1142
|
+
addAttribute2(attributes, "volume", element.volume);
|
|
1143
|
+
addAttribute2(attributes, "contour", element.contour);
|
|
1144
|
+
addAttribute2(attributes, "range", element.range);
|
|
1145
|
+
break;
|
|
1146
|
+
case "break":
|
|
1147
|
+
addAttribute2(attributes, "time", element.time);
|
|
1148
|
+
addAttribute2(attributes, "strength", element.strength);
|
|
1149
|
+
break;
|
|
1150
|
+
case "express-as":
|
|
1151
|
+
case "expressAs":
|
|
1152
|
+
case "mstts:express-as":
|
|
1153
|
+
addAttribute2(attributes, "style", element.style);
|
|
1154
|
+
addAttribute2(attributes, "styledegree", element.styleDegree);
|
|
1155
|
+
addAttribute2(attributes, "role", element.role);
|
|
1156
|
+
break;
|
|
1157
|
+
case "say-as":
|
|
1158
|
+
case "sayAs":
|
|
1159
|
+
addAttribute2(attributes, "interpret-as", element.interpretAs);
|
|
1160
|
+
addAttribute2(attributes, "format", element.format);
|
|
1161
|
+
addAttribute2(attributes, "detail", element.detail);
|
|
1162
|
+
break;
|
|
1163
|
+
case "phoneme":
|
|
1164
|
+
addAttribute2(attributes, "alphabet", element.alphabet);
|
|
1165
|
+
addAttribute2(attributes, "ph", element.ph);
|
|
1166
|
+
break;
|
|
1167
|
+
case "emphasis":
|
|
1168
|
+
addAttribute2(attributes, "level", element.level);
|
|
1169
|
+
break;
|
|
1170
|
+
case "audio":
|
|
1171
|
+
addAttribute2(attributes, "src", element.src);
|
|
1172
|
+
addAttribute2(attributes, "desc", element.desc);
|
|
1173
|
+
addAttribute2(attributes, "clipBegin", element.clipBegin);
|
|
1174
|
+
addAttribute2(attributes, "clipEnd", element.clipEnd);
|
|
1175
|
+
addAttribute2(attributes, "speed", element.speed);
|
|
1176
|
+
addAttribute2(attributes, "repeatCount", element.repeatCount);
|
|
1177
|
+
addAttribute2(attributes, "repeatDuration", element.repeatDuration);
|
|
1178
|
+
addAttribute2(attributes, "soundLevel", element.soundLevel);
|
|
1179
|
+
break;
|
|
1180
|
+
case "sub":
|
|
1181
|
+
addAttribute2(attributes, "alias", element.alias);
|
|
1182
|
+
break;
|
|
1183
|
+
case "lang":
|
|
1184
|
+
addAttribute2(attributes, "xml:lang", element.lang);
|
|
1185
|
+
break;
|
|
1186
|
+
case "mark":
|
|
1187
|
+
addAttribute2(attributes, "name", element.name);
|
|
1188
|
+
break;
|
|
1189
|
+
case "bookmark":
|
|
1190
|
+
addAttribute2(attributes, "mark", element.mark);
|
|
1191
|
+
break;
|
|
1192
|
+
case "lexicon":
|
|
1193
|
+
addAttribute2(attributes, "uri", element.uri);
|
|
1194
|
+
break;
|
|
1195
|
+
case "mstts:silence":
|
|
1196
|
+
case "silence":
|
|
1197
|
+
addAttribute2(attributes, "type", element.typeValue ?? element.silenceType);
|
|
1198
|
+
addAttribute2(attributes, "value", element.value);
|
|
1199
|
+
break;
|
|
1200
|
+
case "mstts:viseme":
|
|
1201
|
+
case "viseme":
|
|
1202
|
+
addAttribute2(attributes, "type", element.typeValue ?? element.visemeType);
|
|
1203
|
+
break;
|
|
1204
|
+
case "mstts:audioduration":
|
|
1205
|
+
addAttribute2(attributes, "value", element.value);
|
|
1206
|
+
break;
|
|
1207
|
+
case "mstts:turn":
|
|
1208
|
+
addAttribute2(attributes, "voice", element.voice);
|
|
1209
|
+
break;
|
|
1210
|
+
case "mstts:backgroundaudio":
|
|
1211
|
+
addAttribute2(attributes, "src", element.src);
|
|
1212
|
+
addAttribute2(attributes, "volume", element.volume);
|
|
1213
|
+
addAttribute2(attributes, "fadein", element.fadeIn ?? element.fadein);
|
|
1214
|
+
addAttribute2(attributes, "fadeout", element.fadeOut ?? element.fadeout);
|
|
1215
|
+
break;
|
|
1216
|
+
}
|
|
1217
|
+
return Object.fromEntries(Object.entries(attributes).map(([name, value]) => [name, String(value)]));
|
|
1218
|
+
}
|
|
1219
|
+
function childrenOf(node) {
|
|
1220
|
+
return node.children ?? [];
|
|
1221
|
+
}
|
|
1222
|
+
function extractSsmlTranslatableText(ssml, options = {}) {
|
|
1223
|
+
const document = parseSsml(ssml);
|
|
1224
|
+
const skipTags = new Set((options.skipTags ?? DEFAULT_TRANSLATION_SKIP_TAGS).map((tag) => tag.toLowerCase()));
|
|
1225
|
+
const result = [];
|
|
1226
|
+
const visit = (nodes, ancestors, path) => {
|
|
1227
|
+
nodes.forEach((node, index) => {
|
|
1228
|
+
if (typeof node === "string") {
|
|
1229
|
+
if (options.includeWhitespace || node.trim().length > 0) {
|
|
1230
|
+
const context = {
|
|
1231
|
+
ancestorTags: [...ancestors],
|
|
1232
|
+
parentAttributes: {},
|
|
1233
|
+
parentTag: ancestors[ancestors.length - 1] ?? "",
|
|
1234
|
+
path: [...path, String(index)]
|
|
1235
|
+
};
|
|
1236
|
+
if (options.filter?.(context) ?? true) result.push(node);
|
|
1237
|
+
}
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
if (node.type === "text") {
|
|
1241
|
+
if (options.includeWhitespace || node.value.trim().length > 0) {
|
|
1242
|
+
const context = {
|
|
1243
|
+
ancestorTags: [...ancestors],
|
|
1244
|
+
parentAttributes: {},
|
|
1245
|
+
parentTag: ancestors[ancestors.length - 1] ?? "",
|
|
1246
|
+
path: [...path, String(index)]
|
|
1247
|
+
};
|
|
1248
|
+
if (options.filter?.(context) ?? true) result.push(node.value);
|
|
1249
|
+
}
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
const tag = elementName(node);
|
|
1253
|
+
if (skipTags.has(tag.toLowerCase())) return;
|
|
1254
|
+
visit(childrenOf(node), [...ancestors, tag], [...path, String(index)]);
|
|
1255
|
+
});
|
|
1256
|
+
};
|
|
1257
|
+
visit(childrenOf(document), ["speak"], []);
|
|
1258
|
+
return result;
|
|
1259
|
+
}
|
|
1260
|
+
function splitSentences(text) {
|
|
1261
|
+
const sentences = [];
|
|
1262
|
+
let start = 0;
|
|
1263
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
1264
|
+
const character = text[index];
|
|
1265
|
+
const isTerminator = "\u3002\uFF01\uFF1F!?".includes(character) || character === "." && /\s|$/.test(text[index + 1] ?? "");
|
|
1266
|
+
if (isTerminator) {
|
|
1267
|
+
const value = text.slice(start, index + 1).trim();
|
|
1268
|
+
if (value) sentences.push(value);
|
|
1269
|
+
start = index + 1;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
const tail = text.slice(start).trim();
|
|
1273
|
+
if (tail) sentences.push(tail);
|
|
1274
|
+
return sentences;
|
|
1275
|
+
}
|
|
1276
|
+
function fromPlainTextToSsml(text, options = {}) {
|
|
1277
|
+
if (typeof text !== "string") throw new TypeError("Plain text must be a string");
|
|
1278
|
+
const paragraphs = text.replace(/\r\n?/g, "\n").split(/\n\s*\n/).map((paragraph) => paragraph.replace(/\s*\n\s*/g, " ").trim()).filter(Boolean);
|
|
1279
|
+
const useSentences = options.splitSentences ?? options.includeSentences ?? true;
|
|
1280
|
+
const paragraphNodes = paragraphs.map((paragraph) => ({
|
|
1281
|
+
type: "p",
|
|
1282
|
+
children: useSentences ? splitSentences(paragraph).map((sentence) => ({ type: "s", children: [sentence] })) : [paragraph]
|
|
1283
|
+
}));
|
|
1284
|
+
const voiceName = options.voice ?? options.voiceName;
|
|
1285
|
+
const children = voiceName ? [{ type: "voice", name: voiceName, children: paragraphNodes }] : paragraphNodes;
|
|
1286
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
1287
|
+
${serializeDocument2({
|
|
1288
|
+
version: options.version ?? "1.0",
|
|
1289
|
+
lang: options.lang ?? options.language ?? "en-US",
|
|
1290
|
+
children
|
|
1291
|
+
})}`;
|
|
1292
|
+
}
|
|
1293
|
+
function serializeDocument2(document) {
|
|
1294
|
+
const attributes = [`version="${document.version}"`, `xml:lang="${document.lang}"`];
|
|
1295
|
+
const serialize = (node) => {
|
|
1296
|
+
if (typeof node === "string") return node.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1297
|
+
if (node.type === "text") return serialize(node.value);
|
|
1298
|
+
const tag = elementName(node);
|
|
1299
|
+
const nodeAttributes = elementAttributes(node);
|
|
1300
|
+
const serializedAttributes = Object.entries(nodeAttributes).map(([name, value]) => ` ${name}="${serialize(value).replace(/"/g, """)}"`).join("");
|
|
1301
|
+
const children = childrenOf(node).map(serialize).join("");
|
|
1302
|
+
return children ? `<${tag}${serializedAttributes}>${children}</${tag}>` : `<${tag}${serializedAttributes}/>`;
|
|
1303
|
+
};
|
|
1304
|
+
return `<speak ${attributes.join(" ")} xmlns="http://www.w3.org/2001/10/synthesis">${(document.children ?? []).map(serialize).join("")}</speak>`;
|
|
1305
|
+
}
|
|
1306
|
+
function flatten(document) {
|
|
1307
|
+
const result = [];
|
|
1308
|
+
const visit = (nodes, path) => {
|
|
1309
|
+
nodes.forEach((node, index) => {
|
|
1310
|
+
if (typeof node === "string" || node.type === "text") return;
|
|
1311
|
+
const currentPath = `${path}/${index}`;
|
|
1312
|
+
result.push({ name: elementName(node), attributes: elementAttributes(node), path: currentPath });
|
|
1313
|
+
visit(childrenOf(node), currentPath);
|
|
1314
|
+
});
|
|
1315
|
+
};
|
|
1316
|
+
result.push({ name: "speak", attributes: { version: document.version, "xml:lang": document.lang }, path: "0" });
|
|
1317
|
+
visit(childrenOf(document), "0");
|
|
1318
|
+
return result;
|
|
1319
|
+
}
|
|
1320
|
+
function validateSsmlStructureIntegrity(originalSsml, translatedSsml) {
|
|
1321
|
+
const mismatches = [];
|
|
1322
|
+
let original;
|
|
1323
|
+
let translated;
|
|
1324
|
+
try {
|
|
1325
|
+
original = parseSsml(originalSsml);
|
|
1326
|
+
} catch (error) {
|
|
1327
|
+
mismatches.push({ kind: "parse", message: `Original SSML cannot be parsed: ${String(error)}`, path: "0" });
|
|
1328
|
+
return {
|
|
1329
|
+
isValid: false,
|
|
1330
|
+
valid: false,
|
|
1331
|
+
errors: mismatches.map((item) => item.message),
|
|
1332
|
+
mismatches,
|
|
1333
|
+
mismatchedTags: []
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
try {
|
|
1337
|
+
translated = parseSsml(translatedSsml);
|
|
1338
|
+
} catch (error) {
|
|
1339
|
+
mismatches.push({ kind: "parse", message: `Translated SSML cannot be parsed: ${String(error)}`, path: "0" });
|
|
1340
|
+
return {
|
|
1341
|
+
isValid: false,
|
|
1342
|
+
valid: false,
|
|
1343
|
+
errors: mismatches.map((item) => item.message),
|
|
1344
|
+
mismatches,
|
|
1345
|
+
mismatchedTags: []
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
const originalElements = flatten(original);
|
|
1349
|
+
const translatedElements = flatten(translated);
|
|
1350
|
+
const count = Math.max(originalElements.length, translatedElements.length);
|
|
1351
|
+
for (let index = 0; index < count; index += 1) {
|
|
1352
|
+
const originalElement = originalElements[index];
|
|
1353
|
+
const translatedElement = translatedElements[index];
|
|
1354
|
+
if (!originalElement || !translatedElement || originalElement.name !== translatedElement.name) {
|
|
1355
|
+
mismatches.push({
|
|
1356
|
+
kind: "element",
|
|
1357
|
+
message: `SSML element structure differs at index ${index}`,
|
|
1358
|
+
original: originalElement?.name,
|
|
1359
|
+
path: originalElement?.path ?? translatedElement?.path ?? String(index),
|
|
1360
|
+
translated: translatedElement?.name
|
|
1361
|
+
});
|
|
1362
|
+
continue;
|
|
1363
|
+
}
|
|
1364
|
+
const attributeNames = /* @__PURE__ */ new Set([
|
|
1365
|
+
...Object.keys(originalElement.attributes),
|
|
1366
|
+
...Object.keys(translatedElement.attributes)
|
|
1367
|
+
]);
|
|
1368
|
+
for (const attribute of attributeNames) {
|
|
1369
|
+
if (originalElement.attributes[attribute] !== translatedElement.attributes[attribute]) {
|
|
1370
|
+
mismatches.push({
|
|
1371
|
+
kind: "attribute",
|
|
1372
|
+
message: `Attribute ${attribute} differs on <${originalElement.name}>`,
|
|
1373
|
+
original: originalElement.attributes[attribute],
|
|
1374
|
+
path: originalElement.path,
|
|
1375
|
+
translated: translatedElement.attributes[attribute]
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
const mismatchedTags = [
|
|
1381
|
+
...new Set(mismatches.flatMap((mismatch) => [mismatch.original, mismatch.translated].filter(Boolean)))
|
|
1382
|
+
];
|
|
1383
|
+
const errors = mismatches.map((mismatch) => mismatch.message);
|
|
1384
|
+
return { isValid: mismatches.length === 0, valid: mismatches.length === 0, errors, mismatches, mismatchedTags };
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1048
1387
|
// packages/ssml-core/src/generated/azureVoiceDefinitions.ts
|
|
1049
1388
|
var AZURE_VOICE_DEFINITIONS = [
|
|
1050
1389
|
{ name: "de-DE-ConradNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
|
|
@@ -1259,12 +1598,23 @@ function tokenizeElements(source) {
|
|
|
1259
1598
|
attributes.set(match[1].toLowerCase(), decodeAttribute(match[3]));
|
|
1260
1599
|
}
|
|
1261
1600
|
const selfClosing = /\/\s*>$/.test(raw);
|
|
1601
|
+
const parent = openElements[openElements.length - 1];
|
|
1262
1602
|
const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
|
|
1263
|
-
|
|
1603
|
+
const tokenName = nameMatch[1];
|
|
1604
|
+
const tokenVoiceName = tokenName.toLowerCase() === "voice" ? attributes.get("name") : tokenName.toLowerCase() === "mstts:turn" ? attributes.get("voice") ?? parentVoiceName : parentVoiceName;
|
|
1605
|
+
tokens.push({
|
|
1606
|
+
attributes,
|
|
1607
|
+
end,
|
|
1608
|
+
name: tokenName,
|
|
1609
|
+
parentName: parent?.name,
|
|
1610
|
+
parentVoiceName,
|
|
1611
|
+
selfClosing,
|
|
1612
|
+
start
|
|
1613
|
+
});
|
|
1264
1614
|
if (!selfClosing) {
|
|
1265
1615
|
openElements.push({
|
|
1266
|
-
name:
|
|
1267
|
-
voiceName:
|
|
1616
|
+
name: tokenName,
|
|
1617
|
+
voiceName: tokenVoiceName
|
|
1268
1618
|
});
|
|
1269
1619
|
}
|
|
1270
1620
|
index = end + 1;
|
|
@@ -1402,6 +1752,54 @@ function definitionMatchesLanguage(definition, voiceName, language, normalizeLan
|
|
|
1402
1752
|
if (!normalizedLanguage || !normalizedCandidates.some(Boolean)) return void 0;
|
|
1403
1753
|
return normalizedLanguage === languagePart(normalizedLanguage) ? normalizedCandidates.some((candidate) => languagePart(candidate) === normalizedLanguage) : false;
|
|
1404
1754
|
}
|
|
1755
|
+
function canonicalTagName(name) {
|
|
1756
|
+
const normalized = name.toLowerCase();
|
|
1757
|
+
if (normalized === "express-as" || normalized === "expressas") return "mstts:express-as";
|
|
1758
|
+
if (normalized === "sayas") return "say-as";
|
|
1759
|
+
return normalized;
|
|
1760
|
+
}
|
|
1761
|
+
function validateVoiceFeatureMatrix(token, source, diagnostics, voiceName, definition) {
|
|
1762
|
+
if (!voiceName || !definition || token.name.toLowerCase() === "voice" || token.name.toLowerCase() === "mstts:turn")
|
|
1763
|
+
return;
|
|
1764
|
+
const tagName = canonicalTagName(token.name);
|
|
1765
|
+
const unsupportedTags = new Set((definition.unsupportedTags ?? []).map(canonicalTagName));
|
|
1766
|
+
const supportedTags = definition.supportedTags?.map(canonicalTagName);
|
|
1767
|
+
if (unsupportedTags.has(tagName) || supportedTags !== void 0 && !supportedTags.includes(tagName)) {
|
|
1768
|
+
addDiagnostic(
|
|
1769
|
+
diagnostics,
|
|
1770
|
+
source,
|
|
1771
|
+
token.start,
|
|
1772
|
+
`Tag <${token.name}> is not supported by voice "${voiceName}" according to the configured feature matrix.`,
|
|
1773
|
+
"error",
|
|
1774
|
+
"azure-unsupported-tag-for-voice"
|
|
1775
|
+
);
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
function validateAudioSource(token, source, diagnostics, options, elementName2) {
|
|
1779
|
+
const src = attr(token, "src");
|
|
1780
|
+
if (!src) {
|
|
1781
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2}> requires a "src" attribute.`);
|
|
1782
|
+
return;
|
|
1783
|
+
}
|
|
1784
|
+
let parsed;
|
|
1785
|
+
try {
|
|
1786
|
+
parsed = new URL(src);
|
|
1787
|
+
} catch {
|
|
1788
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must be an absolute HTTP(S) URL.`);
|
|
1789
|
+
return;
|
|
1790
|
+
}
|
|
1791
|
+
if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
|
|
1792
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must use HTTPS.`);
|
|
1793
|
+
if (options.allowedAudioOrigins && !options.allowedAudioOrigins.includes(parsed.origin))
|
|
1794
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> origin "${parsed.origin}" is not allowed.`);
|
|
1795
|
+
else if (!options.allowExternalAudio)
|
|
1796
|
+
addDiagnostic(
|
|
1797
|
+
diagnostics,
|
|
1798
|
+
source,
|
|
1799
|
+
token.start,
|
|
1800
|
+
`<${elementName2} src> external origin "${parsed.origin}" is blocked by default; set allowExternalAudio to true or provide allowedAudioOrigins.`
|
|
1801
|
+
);
|
|
1802
|
+
}
|
|
1405
1803
|
function validateElement(token, source, diagnostics, voiceName, options, voiceCatalog) {
|
|
1406
1804
|
const name = token.name.toLowerCase();
|
|
1407
1805
|
if (name === "voice" && !attr(token, "name")?.trim())
|
|
@@ -1521,29 +1919,33 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceCa
|
|
|
1521
1919
|
addDiagnostic(diagnostics, source, token.start, '<mstts:viseme> requires a supported "type" attribute.');
|
|
1522
1920
|
}
|
|
1523
1921
|
if (name === "audio") {
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1922
|
+
validateAudioSource(token, source, diagnostics, options, "audio");
|
|
1923
|
+
}
|
|
1924
|
+
if (name === "mstts:turn") {
|
|
1925
|
+
if (!attr(token, "voice")?.trim())
|
|
1926
|
+
addDiagnostic(diagnostics, source, token.start, '<mstts:turn> requires a non-empty "voice" attribute.');
|
|
1927
|
+
if (token.parentName?.toLowerCase() !== "mstts:dialog")
|
|
1928
|
+
addDiagnostic(diagnostics, source, token.start, "<mstts:turn> is only allowed directly inside <mstts:dialog>.");
|
|
1929
|
+
}
|
|
1930
|
+
if (name === "mstts:backgroundaudio") {
|
|
1931
|
+
validateAudioSource(token, source, diagnostics, options, "mstts:backgroundaudio");
|
|
1932
|
+
const volume = attr(token, "volume");
|
|
1933
|
+
if (volume && !/^(silent|x-soft|soft|medium|loud|x-loud|[+-]?\d+(?:\.\d+)?(?:dB|%))$/i.test(volume.trim()))
|
|
1934
|
+
addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:backgroundaudio volume> value "${volume}".`);
|
|
1935
|
+
for (const [attribute, value] of [
|
|
1936
|
+
["fadein", attr(token, "fadein")],
|
|
1937
|
+
["fadeout", attr(token, "fadeout")]
|
|
1938
|
+
]) {
|
|
1939
|
+
if (value && !isValidAzureAudioDuration(value))
|
|
1539
1940
|
addDiagnostic(
|
|
1540
1941
|
diagnostics,
|
|
1541
1942
|
source,
|
|
1542
1943
|
token.start,
|
|
1543
|
-
`<
|
|
1544
|
-
"error"
|
|
1944
|
+
`<mstts:backgroundaudio ${attribute}> must be a positive duration such as "500ms" or "10s".`
|
|
1545
1945
|
);
|
|
1546
1946
|
}
|
|
1947
|
+
if (!token.selfClosing)
|
|
1948
|
+
addDiagnostic(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
|
|
1547
1949
|
}
|
|
1548
1950
|
}
|
|
1549
1951
|
function validateAzureSsml(ssml, options = {}) {
|
|
@@ -1609,22 +2011,57 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
1609
2011
|
);
|
|
1610
2012
|
}
|
|
1611
2013
|
for (const token of tokens) {
|
|
1612
|
-
const
|
|
2014
|
+
const tokenName = token.name.toLowerCase();
|
|
2015
|
+
const tokenVoiceName = tokenName === "voice" ? attr(token, "name")?.trim() : tokenName === "mstts:turn" ? attr(token, "voice")?.trim() || token.parentVoiceName : options.validateNestedVoices === false ? voiceName : token.parentVoiceName;
|
|
1613
2016
|
validateElement(token, ssml, diagnostics, tokenVoiceName, options, voiceCatalog);
|
|
2017
|
+
const definition = tokenVoiceName ? voiceCatalog.get(tokenVoiceName.toLowerCase()) : void 0;
|
|
2018
|
+
validateVoiceFeatureMatrix(token, ssml, diagnostics, tokenVoiceName, definition);
|
|
2019
|
+
if (tokenName === "voice" && options.model && definition?.models && !definition.models.some((model) => model.toLowerCase() === options.model?.toLowerCase())) {
|
|
2020
|
+
addDiagnostic(
|
|
2021
|
+
diagnostics,
|
|
2022
|
+
ssml,
|
|
2023
|
+
token.start,
|
|
2024
|
+
`Voice "${tokenVoiceName}" does not support model "${options.model}" according to the configured feature matrix.`,
|
|
2025
|
+
"error",
|
|
2026
|
+
"azure-unsupported-model-for-voice"
|
|
2027
|
+
);
|
|
2028
|
+
}
|
|
1614
2029
|
}
|
|
1615
2030
|
return diagnostics;
|
|
1616
2031
|
}
|
|
2032
|
+
|
|
2033
|
+
// packages/ssml-core/src/generated/azureVoiceCatalog.ts
|
|
2034
|
+
var AZURE_VOICE_CATALOG_METADATA = {
|
|
2035
|
+
apiVersion: "2025-10-01",
|
|
2036
|
+
generatedAt: "2026-08-28T00:00:00.000Z",
|
|
2037
|
+
regions: [],
|
|
2038
|
+
voiceCount: AZURE_VOICE_DEFINITIONS.length
|
|
2039
|
+
};
|
|
2040
|
+
|
|
2041
|
+
// packages/ssml-core/src/voiceCatalog.ts
|
|
2042
|
+
function getAzureVoiceCatalogMetadata() {
|
|
2043
|
+
return {
|
|
2044
|
+
...AZURE_VOICE_CATALOG_METADATA,
|
|
2045
|
+
regions: [...AZURE_VOICE_CATALOG_METADATA.regions]
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
var getBuiltInVoiceCatalogMetadata = getAzureVoiceCatalogMetadata;
|
|
1617
2049
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1618
2050
|
0 && (module.exports = {
|
|
1619
2051
|
areAzureLanguagesEquivalent,
|
|
1620
2052
|
buildPartialSsml,
|
|
1621
2053
|
buildSsml,
|
|
1622
2054
|
extractSsmlText,
|
|
2055
|
+
extractSsmlTranslatableText,
|
|
2056
|
+
fromPlainTextToSsml,
|
|
2057
|
+
getAzureVoiceCatalogMetadata,
|
|
2058
|
+
getBuiltInVoiceCatalogMetadata,
|
|
1623
2059
|
isValidAzureAudioDuration,
|
|
1624
2060
|
mapSsmlTextNodes,
|
|
1625
2061
|
normalizeAzureLanguage,
|
|
1626
2062
|
parseSsml,
|
|
1627
2063
|
validateAzureSsml,
|
|
1628
|
-
validateSsml
|
|
2064
|
+
validateSsml,
|
|
2065
|
+
validateSsmlStructureIntegrity
|
|
1629
2066
|
});
|
|
1630
2067
|
//# sourceMappingURL=core.js.map
|