ssml-builder-js 2.8.0 → 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 +95 -17
- package/dist/{chunk-TKD3PU5Y.mjs → chunk-7LCSH4SZ.mjs} +610 -124
- package/dist/chunk-7LCSH4SZ.mjs.map +1 -0
- package/dist/{chunk-4RQETJUI.mjs → chunk-JNJVTEL6.mjs} +939 -10
- package/dist/chunk-JNJVTEL6.mjs.map +1 -0
- package/dist/core.d.mts +97 -3
- package/dist/core.d.ts +97 -3
- package/dist/core.js +616 -124
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +15 -3
- package/dist/elements.d.mts +10 -1
- package/dist/elements.d.ts +10 -1
- package/dist/elements.js +1245 -15
- package/dist/elements.js.map +1 -1
- package/dist/elements.mjs +316 -9
- 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 +616 -124
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +15 -3
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +47 -6
- package/dist/react.d.ts +47 -6
- package/dist/react.js +1219 -25
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +280 -10
- package/dist/react.mjs.map +1 -1
- package/package.json +4 -3
- package/dist/chunk-4RQETJUI.mjs.map +0 -1
- package/dist/chunk-TKD3PU5Y.mjs.map +0 -1
|
@@ -36,7 +36,14 @@ var SSML_TAGS = {
|
|
|
36
36
|
MSTTS_SILENCE: "mstts:silence",
|
|
37
37
|
SILENCE: "silence",
|
|
38
38
|
MSTTS_VISEME: "mstts:viseme",
|
|
39
|
-
VISEME: "viseme"
|
|
39
|
+
VISEME: "viseme",
|
|
40
|
+
MSTTS_AUDIO_DURATION: "mstts:audioduration",
|
|
41
|
+
MSTTS_DIALOG: "mstts:dialog",
|
|
42
|
+
MSTTS_TURN: "mstts:turn",
|
|
43
|
+
MSTTS_BACKGROUND_AUDIO: "mstts:backgroundaudio",
|
|
44
|
+
MSTTS_TTS_EMBEDDING: "mstts:ttsembedding",
|
|
45
|
+
MSTTS_EMBEDDING: "mstts:embedding",
|
|
46
|
+
MSTTS_VOICE_CONVERSION: "mstts:voiceconversion"
|
|
40
47
|
};
|
|
41
48
|
var SSML_ATTRS = {
|
|
42
49
|
VERSION: "version",
|
|
@@ -45,6 +52,7 @@ var SSML_ATTRS = {
|
|
|
45
52
|
LANG: "lang",
|
|
46
53
|
MSTTS_XMLNS: "xmlns:mstts",
|
|
47
54
|
NAME: "name",
|
|
55
|
+
VOICE: "voice",
|
|
48
56
|
EFFECT: "effect",
|
|
49
57
|
RATE: "rate",
|
|
50
58
|
PITCH: "pitch",
|
|
@@ -76,7 +84,9 @@ var SSML_ATTRS = {
|
|
|
76
84
|
MARK: "mark",
|
|
77
85
|
URI: "uri",
|
|
78
86
|
TYPE: "type",
|
|
79
|
-
VALUE: "value"
|
|
87
|
+
VALUE: "value",
|
|
88
|
+
FADE_IN: "fadein",
|
|
89
|
+
FADE_OUT: "fadeout"
|
|
80
90
|
};
|
|
81
91
|
|
|
82
92
|
// packages/ssml-core/src/builder.ts
|
|
@@ -165,6 +175,23 @@ function getAttributes(element) {
|
|
|
165
175
|
case SSML_TAGS.VISEME:
|
|
166
176
|
addAttribute(attributes, SSML_ATTRS.TYPE, element.typeValue ?? element.visemeType);
|
|
167
177
|
break;
|
|
178
|
+
case SSML_TAGS.MSTTS_AUDIO_DURATION:
|
|
179
|
+
addAttribute(attributes, SSML_ATTRS.VALUE, element.value);
|
|
180
|
+
break;
|
|
181
|
+
case SSML_TAGS.MSTTS_TURN:
|
|
182
|
+
addAttribute(attributes, SSML_ATTRS.VOICE, element.voice);
|
|
183
|
+
break;
|
|
184
|
+
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO:
|
|
185
|
+
addAttribute(attributes, SSML_ATTRS.SRC, element.src);
|
|
186
|
+
addAttribute(attributes, SSML_ATTRS.VOLUME, element.volume);
|
|
187
|
+
addAttribute(attributes, SSML_ATTRS.FADE_IN, element.fadeIn ?? element.fadein);
|
|
188
|
+
addAttribute(attributes, SSML_ATTRS.FADE_OUT, element.fadeOut ?? element.fadeout);
|
|
189
|
+
break;
|
|
190
|
+
case SSML_TAGS.MSTTS_DIALOG:
|
|
191
|
+
case SSML_TAGS.MSTTS_TTS_EMBEDDING:
|
|
192
|
+
case SSML_TAGS.MSTTS_EMBEDDING:
|
|
193
|
+
case SSML_TAGS.MSTTS_VOICE_CONVERSION:
|
|
194
|
+
break;
|
|
168
195
|
case SSML_TAGS.PARAGRAPH:
|
|
169
196
|
case SSML_TAGS.SENTENCE:
|
|
170
197
|
case SSML_TAGS.WORD:
|
|
@@ -189,6 +216,8 @@ function getTagName(element) {
|
|
|
189
216
|
case SSML_TAGS.VISEME:
|
|
190
217
|
case SSML_TAGS.MSTTS_VISEME:
|
|
191
218
|
return SSML_TAGS.MSTTS_VISEME;
|
|
219
|
+
case SSML_TAGS.MSTTS_AUDIO_DURATION:
|
|
220
|
+
return SSML_TAGS.MSTTS_AUDIO_DURATION;
|
|
192
221
|
case "element":
|
|
193
222
|
case "custom":
|
|
194
223
|
return element.name;
|
|
@@ -723,6 +752,46 @@ function convertElement(node) {
|
|
|
723
752
|
if (typeValue !== void 0) element.typeValue = typeValue;
|
|
724
753
|
return finishElement(element, node, attributes);
|
|
725
754
|
}
|
|
755
|
+
case SSML_TAGS.MSTTS_AUDIO_DURATION: {
|
|
756
|
+
const element = { type: SSML_TAGS.MSTTS_AUDIO_DURATION };
|
|
757
|
+
const value = readAttribute(attributes, SSML_ATTRS.VALUE);
|
|
758
|
+
if (value !== void 0) element.value = value;
|
|
759
|
+
return finishElement(element, node, attributes);
|
|
760
|
+
}
|
|
761
|
+
case SSML_TAGS.MSTTS_DIALOG: {
|
|
762
|
+
const element = { type: SSML_TAGS.MSTTS_DIALOG };
|
|
763
|
+
return finishElement(element, node, attributes);
|
|
764
|
+
}
|
|
765
|
+
case SSML_TAGS.MSTTS_TURN: {
|
|
766
|
+
const element = { type: SSML_TAGS.MSTTS_TURN };
|
|
767
|
+
const voice = readAttribute(attributes, SSML_ATTRS.VOICE);
|
|
768
|
+
if (voice !== void 0) element.voice = voice;
|
|
769
|
+
return finishElement(element, node, attributes);
|
|
770
|
+
}
|
|
771
|
+
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO: {
|
|
772
|
+
const element = { type: SSML_TAGS.MSTTS_BACKGROUND_AUDIO };
|
|
773
|
+
const src = readAttribute(attributes, SSML_ATTRS.SRC);
|
|
774
|
+
const volume = readAttribute(attributes, SSML_ATTRS.VOLUME);
|
|
775
|
+
const fadeIn = readAttribute(attributes, SSML_ATTRS.FADE_IN);
|
|
776
|
+
const fadeOut = readAttribute(attributes, SSML_ATTRS.FADE_OUT);
|
|
777
|
+
if (src !== void 0) element.src = src;
|
|
778
|
+
if (volume !== void 0) element.volume = volume;
|
|
779
|
+
if (fadeIn !== void 0) element.fadeIn = fadeIn;
|
|
780
|
+
if (fadeOut !== void 0) element.fadeOut = fadeOut;
|
|
781
|
+
return finishElement(element, node, attributes);
|
|
782
|
+
}
|
|
783
|
+
case SSML_TAGS.MSTTS_TTS_EMBEDDING: {
|
|
784
|
+
const element = { type: SSML_TAGS.MSTTS_TTS_EMBEDDING };
|
|
785
|
+
return finishElement(element, node, attributes);
|
|
786
|
+
}
|
|
787
|
+
case SSML_TAGS.MSTTS_EMBEDDING: {
|
|
788
|
+
const element = { type: SSML_TAGS.MSTTS_EMBEDDING };
|
|
789
|
+
return finishElement(element, node, attributes);
|
|
790
|
+
}
|
|
791
|
+
case SSML_TAGS.MSTTS_VOICE_CONVERSION: {
|
|
792
|
+
const element = { type: SSML_TAGS.MSTTS_VOICE_CONVERSION };
|
|
793
|
+
return finishElement(element, node, attributes);
|
|
794
|
+
}
|
|
726
795
|
default: {
|
|
727
796
|
const element = {
|
|
728
797
|
name: node.name,
|
|
@@ -997,93 +1066,403 @@ async function mapSsmlTextNodes(ssml, transform, options = {}) {
|
|
|
997
1066
|
return result + ssml.slice(cursor);
|
|
998
1067
|
}
|
|
999
1068
|
|
|
1069
|
+
// packages/ssml-core/src/migration.ts
|
|
1070
|
+
var DEFAULT_TRANSLATION_SKIP_TAGS = ["phoneme", "say-as", "sayAs", "sub"];
|
|
1071
|
+
function elementName(element) {
|
|
1072
|
+
switch (element.type) {
|
|
1073
|
+
case "custom":
|
|
1074
|
+
case "element":
|
|
1075
|
+
return element.name;
|
|
1076
|
+
case "expressAs":
|
|
1077
|
+
return "mstts:express-as";
|
|
1078
|
+
case "sayAs":
|
|
1079
|
+
return "say-as";
|
|
1080
|
+
case "silence":
|
|
1081
|
+
return "mstts:silence";
|
|
1082
|
+
case "viseme":
|
|
1083
|
+
return "mstts:viseme";
|
|
1084
|
+
default:
|
|
1085
|
+
return element.type;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
function addAttribute2(attributes, name, value) {
|
|
1089
|
+
if (value !== void 0) attributes[name] = value;
|
|
1090
|
+
}
|
|
1091
|
+
function elementAttributes(element) {
|
|
1092
|
+
const attributes = { ...element.attributes ?? {} };
|
|
1093
|
+
switch (element.type) {
|
|
1094
|
+
case "voice":
|
|
1095
|
+
addAttribute2(attributes, "name", element.name);
|
|
1096
|
+
addAttribute2(attributes, "effect", element.effect);
|
|
1097
|
+
break;
|
|
1098
|
+
case "prosody":
|
|
1099
|
+
addAttribute2(attributes, "rate", element.rate);
|
|
1100
|
+
addAttribute2(attributes, "pitch", element.pitch);
|
|
1101
|
+
addAttribute2(attributes, "volume", element.volume);
|
|
1102
|
+
addAttribute2(attributes, "contour", element.contour);
|
|
1103
|
+
addAttribute2(attributes, "range", element.range);
|
|
1104
|
+
break;
|
|
1105
|
+
case "break":
|
|
1106
|
+
addAttribute2(attributes, "time", element.time);
|
|
1107
|
+
addAttribute2(attributes, "strength", element.strength);
|
|
1108
|
+
break;
|
|
1109
|
+
case "express-as":
|
|
1110
|
+
case "expressAs":
|
|
1111
|
+
case "mstts:express-as":
|
|
1112
|
+
addAttribute2(attributes, "style", element.style);
|
|
1113
|
+
addAttribute2(attributes, "styledegree", element.styleDegree);
|
|
1114
|
+
addAttribute2(attributes, "role", element.role);
|
|
1115
|
+
break;
|
|
1116
|
+
case "say-as":
|
|
1117
|
+
case "sayAs":
|
|
1118
|
+
addAttribute2(attributes, "interpret-as", element.interpretAs);
|
|
1119
|
+
addAttribute2(attributes, "format", element.format);
|
|
1120
|
+
addAttribute2(attributes, "detail", element.detail);
|
|
1121
|
+
break;
|
|
1122
|
+
case "phoneme":
|
|
1123
|
+
addAttribute2(attributes, "alphabet", element.alphabet);
|
|
1124
|
+
addAttribute2(attributes, "ph", element.ph);
|
|
1125
|
+
break;
|
|
1126
|
+
case "emphasis":
|
|
1127
|
+
addAttribute2(attributes, "level", element.level);
|
|
1128
|
+
break;
|
|
1129
|
+
case "audio":
|
|
1130
|
+
addAttribute2(attributes, "src", element.src);
|
|
1131
|
+
addAttribute2(attributes, "desc", element.desc);
|
|
1132
|
+
addAttribute2(attributes, "clipBegin", element.clipBegin);
|
|
1133
|
+
addAttribute2(attributes, "clipEnd", element.clipEnd);
|
|
1134
|
+
addAttribute2(attributes, "speed", element.speed);
|
|
1135
|
+
addAttribute2(attributes, "repeatCount", element.repeatCount);
|
|
1136
|
+
addAttribute2(attributes, "repeatDuration", element.repeatDuration);
|
|
1137
|
+
addAttribute2(attributes, "soundLevel", element.soundLevel);
|
|
1138
|
+
break;
|
|
1139
|
+
case "sub":
|
|
1140
|
+
addAttribute2(attributes, "alias", element.alias);
|
|
1141
|
+
break;
|
|
1142
|
+
case "lang":
|
|
1143
|
+
addAttribute2(attributes, "xml:lang", element.lang);
|
|
1144
|
+
break;
|
|
1145
|
+
case "mark":
|
|
1146
|
+
addAttribute2(attributes, "name", element.name);
|
|
1147
|
+
break;
|
|
1148
|
+
case "bookmark":
|
|
1149
|
+
addAttribute2(attributes, "mark", element.mark);
|
|
1150
|
+
break;
|
|
1151
|
+
case "lexicon":
|
|
1152
|
+
addAttribute2(attributes, "uri", element.uri);
|
|
1153
|
+
break;
|
|
1154
|
+
case "mstts:silence":
|
|
1155
|
+
case "silence":
|
|
1156
|
+
addAttribute2(attributes, "type", element.typeValue ?? element.silenceType);
|
|
1157
|
+
addAttribute2(attributes, "value", element.value);
|
|
1158
|
+
break;
|
|
1159
|
+
case "mstts:viseme":
|
|
1160
|
+
case "viseme":
|
|
1161
|
+
addAttribute2(attributes, "type", element.typeValue ?? element.visemeType);
|
|
1162
|
+
break;
|
|
1163
|
+
case "mstts:audioduration":
|
|
1164
|
+
addAttribute2(attributes, "value", element.value);
|
|
1165
|
+
break;
|
|
1166
|
+
case "mstts:turn":
|
|
1167
|
+
addAttribute2(attributes, "voice", element.voice);
|
|
1168
|
+
break;
|
|
1169
|
+
case "mstts:backgroundaudio":
|
|
1170
|
+
addAttribute2(attributes, "src", element.src);
|
|
1171
|
+
addAttribute2(attributes, "volume", element.volume);
|
|
1172
|
+
addAttribute2(attributes, "fadein", element.fadeIn ?? element.fadein);
|
|
1173
|
+
addAttribute2(attributes, "fadeout", element.fadeOut ?? element.fadeout);
|
|
1174
|
+
break;
|
|
1175
|
+
}
|
|
1176
|
+
return Object.fromEntries(Object.entries(attributes).map(([name, value]) => [name, String(value)]));
|
|
1177
|
+
}
|
|
1178
|
+
function childrenOf(node) {
|
|
1179
|
+
return node.children ?? [];
|
|
1180
|
+
}
|
|
1181
|
+
function extractSsmlTranslatableText(ssml, options = {}) {
|
|
1182
|
+
const document = parseSsml(ssml);
|
|
1183
|
+
const skipTags = new Set((options.skipTags ?? DEFAULT_TRANSLATION_SKIP_TAGS).map((tag) => tag.toLowerCase()));
|
|
1184
|
+
const result = [];
|
|
1185
|
+
const visit = (nodes, ancestors, path) => {
|
|
1186
|
+
nodes.forEach((node, index) => {
|
|
1187
|
+
if (typeof node === "string") {
|
|
1188
|
+
if (options.includeWhitespace || node.trim().length > 0) {
|
|
1189
|
+
const context = {
|
|
1190
|
+
ancestorTags: [...ancestors],
|
|
1191
|
+
parentAttributes: {},
|
|
1192
|
+
parentTag: ancestors[ancestors.length - 1] ?? "",
|
|
1193
|
+
path: [...path, String(index)]
|
|
1194
|
+
};
|
|
1195
|
+
if (options.filter?.(context) ?? true) result.push(node);
|
|
1196
|
+
}
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
if (node.type === "text") {
|
|
1200
|
+
if (options.includeWhitespace || node.value.trim().length > 0) {
|
|
1201
|
+
const context = {
|
|
1202
|
+
ancestorTags: [...ancestors],
|
|
1203
|
+
parentAttributes: {},
|
|
1204
|
+
parentTag: ancestors[ancestors.length - 1] ?? "",
|
|
1205
|
+
path: [...path, String(index)]
|
|
1206
|
+
};
|
|
1207
|
+
if (options.filter?.(context) ?? true) result.push(node.value);
|
|
1208
|
+
}
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
const tag = elementName(node);
|
|
1212
|
+
if (skipTags.has(tag.toLowerCase())) return;
|
|
1213
|
+
visit(childrenOf(node), [...ancestors, tag], [...path, String(index)]);
|
|
1214
|
+
});
|
|
1215
|
+
};
|
|
1216
|
+
visit(childrenOf(document), ["speak"], []);
|
|
1217
|
+
return result;
|
|
1218
|
+
}
|
|
1219
|
+
function splitSentences(text) {
|
|
1220
|
+
const sentences = [];
|
|
1221
|
+
let start = 0;
|
|
1222
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
1223
|
+
const character = text[index];
|
|
1224
|
+
const isTerminator = "\u3002\uFF01\uFF1F!?".includes(character) || character === "." && /\s|$/.test(text[index + 1] ?? "");
|
|
1225
|
+
if (isTerminator) {
|
|
1226
|
+
const value = text.slice(start, index + 1).trim();
|
|
1227
|
+
if (value) sentences.push(value);
|
|
1228
|
+
start = index + 1;
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
const tail = text.slice(start).trim();
|
|
1232
|
+
if (tail) sentences.push(tail);
|
|
1233
|
+
return sentences;
|
|
1234
|
+
}
|
|
1235
|
+
function fromPlainTextToSsml(text, options = {}) {
|
|
1236
|
+
if (typeof text !== "string") throw new TypeError("Plain text must be a string");
|
|
1237
|
+
const paragraphs = text.replace(/\r\n?/g, "\n").split(/\n\s*\n/).map((paragraph) => paragraph.replace(/\s*\n\s*/g, " ").trim()).filter(Boolean);
|
|
1238
|
+
const useSentences = options.splitSentences ?? options.includeSentences ?? true;
|
|
1239
|
+
const paragraphNodes = paragraphs.map((paragraph) => ({
|
|
1240
|
+
type: "p",
|
|
1241
|
+
children: useSentences ? splitSentences(paragraph).map((sentence) => ({ type: "s", children: [sentence] })) : [paragraph]
|
|
1242
|
+
}));
|
|
1243
|
+
const voiceName = options.voice ?? options.voiceName;
|
|
1244
|
+
const children = voiceName ? [{ type: "voice", name: voiceName, children: paragraphNodes }] : paragraphNodes;
|
|
1245
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
1246
|
+
${serializeDocument2({
|
|
1247
|
+
version: options.version ?? "1.0",
|
|
1248
|
+
lang: options.lang ?? options.language ?? "en-US",
|
|
1249
|
+
children
|
|
1250
|
+
})}`;
|
|
1251
|
+
}
|
|
1252
|
+
function serializeDocument2(document) {
|
|
1253
|
+
const attributes = [`version="${document.version}"`, `xml:lang="${document.lang}"`];
|
|
1254
|
+
const serialize = (node) => {
|
|
1255
|
+
if (typeof node === "string") return node.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1256
|
+
if (node.type === "text") return serialize(node.value);
|
|
1257
|
+
const tag = elementName(node);
|
|
1258
|
+
const nodeAttributes = elementAttributes(node);
|
|
1259
|
+
const serializedAttributes = Object.entries(nodeAttributes).map(([name, value]) => ` ${name}="${serialize(value).replace(/"/g, """)}"`).join("");
|
|
1260
|
+
const children = childrenOf(node).map(serialize).join("");
|
|
1261
|
+
return children ? `<${tag}${serializedAttributes}>${children}</${tag}>` : `<${tag}${serializedAttributes}/>`;
|
|
1262
|
+
};
|
|
1263
|
+
return `<speak ${attributes.join(" ")} xmlns="http://www.w3.org/2001/10/synthesis">${(document.children ?? []).map(serialize).join("")}</speak>`;
|
|
1264
|
+
}
|
|
1265
|
+
function flatten(document) {
|
|
1266
|
+
const result = [];
|
|
1267
|
+
const visit = (nodes, path) => {
|
|
1268
|
+
nodes.forEach((node, index) => {
|
|
1269
|
+
if (typeof node === "string" || node.type === "text") return;
|
|
1270
|
+
const currentPath = `${path}/${index}`;
|
|
1271
|
+
result.push({ name: elementName(node), attributes: elementAttributes(node), path: currentPath });
|
|
1272
|
+
visit(childrenOf(node), currentPath);
|
|
1273
|
+
});
|
|
1274
|
+
};
|
|
1275
|
+
result.push({ name: "speak", attributes: { version: document.version, "xml:lang": document.lang }, path: "0" });
|
|
1276
|
+
visit(childrenOf(document), "0");
|
|
1277
|
+
return result;
|
|
1278
|
+
}
|
|
1279
|
+
function validateSsmlStructureIntegrity(originalSsml, translatedSsml) {
|
|
1280
|
+
const mismatches = [];
|
|
1281
|
+
let original;
|
|
1282
|
+
let translated;
|
|
1283
|
+
try {
|
|
1284
|
+
original = parseSsml(originalSsml);
|
|
1285
|
+
} catch (error) {
|
|
1286
|
+
mismatches.push({ kind: "parse", message: `Original SSML cannot be parsed: ${String(error)}`, path: "0" });
|
|
1287
|
+
return {
|
|
1288
|
+
isValid: false,
|
|
1289
|
+
valid: false,
|
|
1290
|
+
errors: mismatches.map((item) => item.message),
|
|
1291
|
+
mismatches,
|
|
1292
|
+
mismatchedTags: []
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
try {
|
|
1296
|
+
translated = parseSsml(translatedSsml);
|
|
1297
|
+
} catch (error) {
|
|
1298
|
+
mismatches.push({ kind: "parse", message: `Translated SSML cannot be parsed: ${String(error)}`, path: "0" });
|
|
1299
|
+
return {
|
|
1300
|
+
isValid: false,
|
|
1301
|
+
valid: false,
|
|
1302
|
+
errors: mismatches.map((item) => item.message),
|
|
1303
|
+
mismatches,
|
|
1304
|
+
mismatchedTags: []
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
const originalElements = flatten(original);
|
|
1308
|
+
const translatedElements = flatten(translated);
|
|
1309
|
+
const count = Math.max(originalElements.length, translatedElements.length);
|
|
1310
|
+
for (let index = 0; index < count; index += 1) {
|
|
1311
|
+
const originalElement = originalElements[index];
|
|
1312
|
+
const translatedElement = translatedElements[index];
|
|
1313
|
+
if (!originalElement || !translatedElement || originalElement.name !== translatedElement.name) {
|
|
1314
|
+
mismatches.push({
|
|
1315
|
+
kind: "element",
|
|
1316
|
+
message: `SSML element structure differs at index ${index}`,
|
|
1317
|
+
original: originalElement?.name,
|
|
1318
|
+
path: originalElement?.path ?? translatedElement?.path ?? String(index),
|
|
1319
|
+
translated: translatedElement?.name
|
|
1320
|
+
});
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
const attributeNames = /* @__PURE__ */ new Set([
|
|
1324
|
+
...Object.keys(originalElement.attributes),
|
|
1325
|
+
...Object.keys(translatedElement.attributes)
|
|
1326
|
+
]);
|
|
1327
|
+
for (const attribute of attributeNames) {
|
|
1328
|
+
if (originalElement.attributes[attribute] !== translatedElement.attributes[attribute]) {
|
|
1329
|
+
mismatches.push({
|
|
1330
|
+
kind: "attribute",
|
|
1331
|
+
message: `Attribute ${attribute} differs on <${originalElement.name}>`,
|
|
1332
|
+
original: originalElement.attributes[attribute],
|
|
1333
|
+
path: originalElement.path,
|
|
1334
|
+
translated: translatedElement.attributes[attribute]
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
const mismatchedTags = [
|
|
1340
|
+
...new Set(mismatches.flatMap((mismatch) => [mismatch.original, mismatch.translated].filter(Boolean)))
|
|
1341
|
+
];
|
|
1342
|
+
const errors = mismatches.map((mismatch) => mismatch.message);
|
|
1343
|
+
return { isValid: mismatches.length === 0, valid: mismatches.length === 0, errors, mismatches, mismatchedTags };
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
// packages/ssml-core/src/generated/azureVoiceDefinitions.ts
|
|
1347
|
+
var AZURE_VOICE_DEFINITIONS = [
|
|
1348
|
+
{ name: "de-DE-ConradNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
|
|
1349
|
+
{ name: "de-DE-KatjaNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
|
|
1350
|
+
{ name: "en-US-AndrewNeural", locale: "en-US", styles: ["empathetic", "relieved"] },
|
|
1351
|
+
{
|
|
1352
|
+
name: "en-US-GuyNeural",
|
|
1353
|
+
locale: "en-US",
|
|
1354
|
+
styles: [
|
|
1355
|
+
"angry",
|
|
1356
|
+
"cheerful",
|
|
1357
|
+
"excited",
|
|
1358
|
+
"friendly",
|
|
1359
|
+
"hopeful",
|
|
1360
|
+
"newscast",
|
|
1361
|
+
"sad",
|
|
1362
|
+
"shouting",
|
|
1363
|
+
"terrified",
|
|
1364
|
+
"unfriendly",
|
|
1365
|
+
"whispering"
|
|
1366
|
+
]
|
|
1367
|
+
},
|
|
1368
|
+
{
|
|
1369
|
+
name: "en-US-JennyMultilingualNeural",
|
|
1370
|
+
locale: "en-US",
|
|
1371
|
+
styles: [
|
|
1372
|
+
"cheerful",
|
|
1373
|
+
"empathetic",
|
|
1374
|
+
"excited",
|
|
1375
|
+
"friendly",
|
|
1376
|
+
"hopeful",
|
|
1377
|
+
"sad",
|
|
1378
|
+
"shouting",
|
|
1379
|
+
"terrified",
|
|
1380
|
+
"unfriendly",
|
|
1381
|
+
"whispering"
|
|
1382
|
+
]
|
|
1383
|
+
},
|
|
1384
|
+
{
|
|
1385
|
+
name: "en-US-JennyNeural",
|
|
1386
|
+
locale: "en-US",
|
|
1387
|
+
styles: [
|
|
1388
|
+
"assistant",
|
|
1389
|
+
"chat",
|
|
1390
|
+
"customerservice",
|
|
1391
|
+
"newscast",
|
|
1392
|
+
"cheerful",
|
|
1393
|
+
"empathetic",
|
|
1394
|
+
"excited",
|
|
1395
|
+
"friendly",
|
|
1396
|
+
"hopeful",
|
|
1397
|
+
"sad",
|
|
1398
|
+
"shouting",
|
|
1399
|
+
"terrified",
|
|
1400
|
+
"unfriendly",
|
|
1401
|
+
"whispering"
|
|
1402
|
+
]
|
|
1403
|
+
},
|
|
1404
|
+
{ name: "es-ES-ElviraNeural", locale: "es-ES" },
|
|
1405
|
+
{ name: "fil-PH-AngeloNeural", locale: "fil-PH" },
|
|
1406
|
+
{ name: "fr-FR-DeniseNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
|
|
1407
|
+
{ name: "fr-FR-HenriNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
|
|
1408
|
+
{ name: "id-ID-GadisNeural", locale: "id-ID" },
|
|
1409
|
+
{ name: "it-IT-ElsaNeural", locale: "it-IT", styles: ["cheerful", "sad"] },
|
|
1410
|
+
{ name: "ja-JP-KeitaNeural", locale: "ja-JP", styles: ["chat"] },
|
|
1411
|
+
{ name: "ja-JP-MayuNeural", locale: "ja-JP", styles: ["calm", "cheerful", "sad"] },
|
|
1412
|
+
{ name: "ja-JP-NanamiNeural", locale: "ja-JP", styles: ["chat", "customerservice", "cheerful", "whispering", "sad"] },
|
|
1413
|
+
{ name: "ko-KR-SunHiNeural", locale: "ko-KR", styles: ["cheerful", "sad"] },
|
|
1414
|
+
{ name: "ms-MY-YasminNeural", locale: "ms-MY" },
|
|
1415
|
+
{ name: "pt-BR-FranciscaNeural", locale: "pt-BR", styles: ["calm"] },
|
|
1416
|
+
{
|
|
1417
|
+
name: "ru-RU-SvetlanaNeural",
|
|
1418
|
+
locale: "ru-RU",
|
|
1419
|
+
styles: ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"]
|
|
1420
|
+
},
|
|
1421
|
+
{ name: "th-TH-PremwadeeNeural", locale: "th-TH" },
|
|
1422
|
+
{ name: "vi-VN-HoaiMyNeural", locale: "vi-VN" },
|
|
1423
|
+
{
|
|
1424
|
+
name: "zh-CN-XiaoxiaoNeural",
|
|
1425
|
+
locale: "zh-CN",
|
|
1426
|
+
styles: [
|
|
1427
|
+
"assistant",
|
|
1428
|
+
"chat",
|
|
1429
|
+
"customerservice",
|
|
1430
|
+
"newscast",
|
|
1431
|
+
"cheerful",
|
|
1432
|
+
"empathetic",
|
|
1433
|
+
"excited",
|
|
1434
|
+
"friendly",
|
|
1435
|
+
"hopeful",
|
|
1436
|
+
"sad",
|
|
1437
|
+
"terrified",
|
|
1438
|
+
"whispering",
|
|
1439
|
+
"poetry-reading",
|
|
1440
|
+
"sports_commentary",
|
|
1441
|
+
"sports_commentary_excited",
|
|
1442
|
+
"story"
|
|
1443
|
+
]
|
|
1444
|
+
},
|
|
1445
|
+
{
|
|
1446
|
+
name: "zh-CN-YunxiNeural",
|
|
1447
|
+
locale: "zh-CN",
|
|
1448
|
+
styles: [
|
|
1449
|
+
"narration-relaxed",
|
|
1450
|
+
"embarrassed",
|
|
1451
|
+
"fearful",
|
|
1452
|
+
"sad",
|
|
1453
|
+
"disgruntled",
|
|
1454
|
+
"serious",
|
|
1455
|
+
"angry",
|
|
1456
|
+
"depressed",
|
|
1457
|
+
"chat",
|
|
1458
|
+
"cheerful",
|
|
1459
|
+
"assistant"
|
|
1460
|
+
]
|
|
1461
|
+
},
|
|
1462
|
+
{ name: "zh-TW-HsiaoChenNeural", locale: "zh-TW" }
|
|
1463
|
+
];
|
|
1464
|
+
|
|
1000
1465
|
// packages/ssml-core/src/azureValidation.ts
|
|
1001
|
-
var EXPRESS_AS_STYLES = {
|
|
1002
|
-
"en-us-jennyneural": [
|
|
1003
|
-
"assistant",
|
|
1004
|
-
"chat",
|
|
1005
|
-
"customerservice",
|
|
1006
|
-
"newscast",
|
|
1007
|
-
"cheerful",
|
|
1008
|
-
"empathetic",
|
|
1009
|
-
"excited",
|
|
1010
|
-
"friendly",
|
|
1011
|
-
"hopeful",
|
|
1012
|
-
"sad",
|
|
1013
|
-
"shouting",
|
|
1014
|
-
"terrified",
|
|
1015
|
-
"unfriendly",
|
|
1016
|
-
"whispering"
|
|
1017
|
-
],
|
|
1018
|
-
"en-us-guyneural": [
|
|
1019
|
-
"angry",
|
|
1020
|
-
"cheerful",
|
|
1021
|
-
"excited",
|
|
1022
|
-
"friendly",
|
|
1023
|
-
"hopeful",
|
|
1024
|
-
"newscast",
|
|
1025
|
-
"sad",
|
|
1026
|
-
"shouting",
|
|
1027
|
-
"terrified",
|
|
1028
|
-
"unfriendly",
|
|
1029
|
-
"whispering"
|
|
1030
|
-
],
|
|
1031
|
-
"en-us-jennymultilingualneural": [
|
|
1032
|
-
"cheerful",
|
|
1033
|
-
"empathetic",
|
|
1034
|
-
"excited",
|
|
1035
|
-
"friendly",
|
|
1036
|
-
"hopeful",
|
|
1037
|
-
"sad",
|
|
1038
|
-
"shouting",
|
|
1039
|
-
"terrified",
|
|
1040
|
-
"unfriendly",
|
|
1041
|
-
"whispering"
|
|
1042
|
-
],
|
|
1043
|
-
"en-us-andrewneural": ["empathetic", "relieved"],
|
|
1044
|
-
"ja-jp-mayuneural": ["calm", "cheerful", "sad"],
|
|
1045
|
-
"ja-jp-nanamineural": ["chat", "customerservice", "cheerful", "whispering", "sad"],
|
|
1046
|
-
"ja-jp-keitaneural": ["chat"],
|
|
1047
|
-
"ko-kr-sunhineural": ["cheerful", "sad"],
|
|
1048
|
-
"zh-cn-yunxineural": [
|
|
1049
|
-
"narration-relaxed",
|
|
1050
|
-
"embarrassed",
|
|
1051
|
-
"fearful",
|
|
1052
|
-
"sad",
|
|
1053
|
-
"disgruntled",
|
|
1054
|
-
"serious",
|
|
1055
|
-
"angry",
|
|
1056
|
-
"depressed",
|
|
1057
|
-
"chat",
|
|
1058
|
-
"cheerful",
|
|
1059
|
-
"assistant"
|
|
1060
|
-
],
|
|
1061
|
-
"zh-cn-xiaoxiaoneural": [
|
|
1062
|
-
"assistant",
|
|
1063
|
-
"chat",
|
|
1064
|
-
"customerservice",
|
|
1065
|
-
"newscast",
|
|
1066
|
-
"cheerful",
|
|
1067
|
-
"empathetic",
|
|
1068
|
-
"excited",
|
|
1069
|
-
"friendly",
|
|
1070
|
-
"hopeful",
|
|
1071
|
-
"sad",
|
|
1072
|
-
"terrified",
|
|
1073
|
-
"whispering",
|
|
1074
|
-
"poetry-reading",
|
|
1075
|
-
"sports_commentary",
|
|
1076
|
-
"sports_commentary_excited",
|
|
1077
|
-
"story"
|
|
1078
|
-
],
|
|
1079
|
-
"fr-fr-deniseneural": ["cheerful", "sad"],
|
|
1080
|
-
"fr-fr-henrineural": ["cheerful", "sad"],
|
|
1081
|
-
"pt-br-franciscaneural": ["calm"],
|
|
1082
|
-
"it-it-elsaneural": ["cheerful", "sad"],
|
|
1083
|
-
"de-de-katjaneural": ["cheerful", "sad"],
|
|
1084
|
-
"de-de-conradneural": ["cheerful", "sad"],
|
|
1085
|
-
"ru-ru-svetlananeural": ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"]
|
|
1086
|
-
};
|
|
1087
1466
|
var ALLOWED_BREAK_STRENGTHS = /* @__PURE__ */ new Set(["none", "x-weak", "weak", "medium", "strong", "x-strong"]);
|
|
1088
1467
|
var ALLOWED_SAY_AS = /* @__PURE__ */ new Set([
|
|
1089
1468
|
"characters",
|
|
@@ -1178,12 +1557,23 @@ function tokenizeElements(source) {
|
|
|
1178
1557
|
attributes.set(match[1].toLowerCase(), decodeAttribute(match[3]));
|
|
1179
1558
|
}
|
|
1180
1559
|
const selfClosing = /\/\s*>$/.test(raw);
|
|
1560
|
+
const parent = openElements[openElements.length - 1];
|
|
1181
1561
|
const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
|
|
1182
|
-
|
|
1562
|
+
const tokenName = nameMatch[1];
|
|
1563
|
+
const tokenVoiceName = tokenName.toLowerCase() === "voice" ? attributes.get("name") : tokenName.toLowerCase() === "mstts:turn" ? attributes.get("voice") ?? parentVoiceName : parentVoiceName;
|
|
1564
|
+
tokens.push({
|
|
1565
|
+
attributes,
|
|
1566
|
+
end,
|
|
1567
|
+
name: tokenName,
|
|
1568
|
+
parentName: parent?.name,
|
|
1569
|
+
parentVoiceName,
|
|
1570
|
+
selfClosing,
|
|
1571
|
+
start
|
|
1572
|
+
});
|
|
1183
1573
|
if (!selfClosing) {
|
|
1184
1574
|
openElements.push({
|
|
1185
|
-
name:
|
|
1186
|
-
voiceName:
|
|
1575
|
+
name: tokenName,
|
|
1576
|
+
voiceName: tokenVoiceName
|
|
1187
1577
|
});
|
|
1188
1578
|
}
|
|
1189
1579
|
index = end + 1;
|
|
@@ -1212,18 +1602,17 @@ function isSupportedProsodyRate(value) {
|
|
|
1212
1602
|
const numericValue = Number(multiplier[1]);
|
|
1213
1603
|
return numericValue >= 0.5 && numericValue <= 2;
|
|
1214
1604
|
}
|
|
1605
|
+
function isValidAzureAudioDuration(value) {
|
|
1606
|
+
const trimmed = value.trim();
|
|
1607
|
+
const numeric = /^(\d+(?:\.\d+)?)(ms|s)$/.exec(trimmed);
|
|
1608
|
+
if (numeric) return Number(numeric[1]) > 0;
|
|
1609
|
+
const clock = /^(\d{2,}):([0-5]\d):([0-5]\d)(?:\.(\d{1,3}))?$/.exec(trimmed);
|
|
1610
|
+
if (!clock) return false;
|
|
1611
|
+
return Number(clock[1]) > 0 || Number(clock[2]) > 0 || Number(clock[3]) > 0 || Number(clock[4] ?? 0) > 0;
|
|
1612
|
+
}
|
|
1215
1613
|
function attr(token, name) {
|
|
1216
1614
|
return token.attributes.get(name.toLowerCase());
|
|
1217
1615
|
}
|
|
1218
|
-
var ADDITIONAL_VOICE_DEFINITIONS = [
|
|
1219
|
-
{ name: "zh-TW-HsiaoChenNeural", locale: "zh-TW" },
|
|
1220
|
-
{ name: "es-ES-ElviraNeural", locale: "es-ES" },
|
|
1221
|
-
{ name: "th-TH-PremwadeeNeural", locale: "th-TH" },
|
|
1222
|
-
{ name: "fil-PH-AngeloNeural", locale: "fil-PH" },
|
|
1223
|
-
{ name: "vi-VN-HoaiMyNeural", locale: "vi-VN" },
|
|
1224
|
-
{ name: "id-ID-GadisNeural", locale: "id-ID" },
|
|
1225
|
-
{ name: "ms-MY-YasminNeural", locale: "ms-MY" }
|
|
1226
|
-
];
|
|
1227
1616
|
var DEFAULT_LANGUAGE_ALIASES = {
|
|
1228
1617
|
"zh-CN": ["zh-Hans"],
|
|
1229
1618
|
"zh-TW": ["zh-Hant"]
|
|
@@ -1286,10 +1675,7 @@ function definitionFromStyleMap(voiceName, styles) {
|
|
|
1286
1675
|
}
|
|
1287
1676
|
function normalizeVoiceCatalog(options) {
|
|
1288
1677
|
const definitions = /* @__PURE__ */ new Map();
|
|
1289
|
-
for (const
|
|
1290
|
-
definitions.set(name.toLowerCase(), definitionFromStyleMap(name, styles));
|
|
1291
|
-
}
|
|
1292
|
-
for (const definition of ADDITIONAL_VOICE_DEFINITIONS) definitions.set(definition.name.toLowerCase(), definition);
|
|
1678
|
+
for (const definition of AZURE_VOICE_DEFINITIONS) definitions.set(definition.name.toLowerCase(), definition);
|
|
1293
1679
|
for (const definition of options.voiceCatalog ?? []) definitions.set(definition.name.toLowerCase(), definition);
|
|
1294
1680
|
for (const definition of options.voiceDefinitions ?? []) definitions.set(definition.name.toLowerCase(), definition);
|
|
1295
1681
|
for (const definition of options.customVoiceDefinitions ?? [])
|
|
@@ -1325,6 +1711,54 @@ function definitionMatchesLanguage(definition, voiceName, language, normalizeLan
|
|
|
1325
1711
|
if (!normalizedLanguage || !normalizedCandidates.some(Boolean)) return void 0;
|
|
1326
1712
|
return normalizedLanguage === languagePart(normalizedLanguage) ? normalizedCandidates.some((candidate) => languagePart(candidate) === normalizedLanguage) : false;
|
|
1327
1713
|
}
|
|
1714
|
+
function canonicalTagName(name) {
|
|
1715
|
+
const normalized = name.toLowerCase();
|
|
1716
|
+
if (normalized === "express-as" || normalized === "expressas") return "mstts:express-as";
|
|
1717
|
+
if (normalized === "sayas") return "say-as";
|
|
1718
|
+
return normalized;
|
|
1719
|
+
}
|
|
1720
|
+
function validateVoiceFeatureMatrix(token, source, diagnostics, voiceName, definition) {
|
|
1721
|
+
if (!voiceName || !definition || token.name.toLowerCase() === "voice" || token.name.toLowerCase() === "mstts:turn")
|
|
1722
|
+
return;
|
|
1723
|
+
const tagName = canonicalTagName(token.name);
|
|
1724
|
+
const unsupportedTags = new Set((definition.unsupportedTags ?? []).map(canonicalTagName));
|
|
1725
|
+
const supportedTags = definition.supportedTags?.map(canonicalTagName);
|
|
1726
|
+
if (unsupportedTags.has(tagName) || supportedTags !== void 0 && !supportedTags.includes(tagName)) {
|
|
1727
|
+
addDiagnostic(
|
|
1728
|
+
diagnostics,
|
|
1729
|
+
source,
|
|
1730
|
+
token.start,
|
|
1731
|
+
`Tag <${token.name}> is not supported by voice "${voiceName}" according to the configured feature matrix.`,
|
|
1732
|
+
"error",
|
|
1733
|
+
"azure-unsupported-tag-for-voice"
|
|
1734
|
+
);
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
function validateAudioSource(token, source, diagnostics, options, elementName2) {
|
|
1738
|
+
const src = attr(token, "src");
|
|
1739
|
+
if (!src) {
|
|
1740
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2}> requires a "src" attribute.`);
|
|
1741
|
+
return;
|
|
1742
|
+
}
|
|
1743
|
+
let parsed;
|
|
1744
|
+
try {
|
|
1745
|
+
parsed = new URL(src);
|
|
1746
|
+
} catch {
|
|
1747
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must be an absolute HTTP(S) URL.`);
|
|
1748
|
+
return;
|
|
1749
|
+
}
|
|
1750
|
+
if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
|
|
1751
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must use HTTPS.`);
|
|
1752
|
+
if (options.allowedAudioOrigins && !options.allowedAudioOrigins.includes(parsed.origin))
|
|
1753
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> origin "${parsed.origin}" is not allowed.`);
|
|
1754
|
+
else if (!options.allowExternalAudio)
|
|
1755
|
+
addDiagnostic(
|
|
1756
|
+
diagnostics,
|
|
1757
|
+
source,
|
|
1758
|
+
token.start,
|
|
1759
|
+
`<${elementName2} src> external origin "${parsed.origin}" is blocked by default; set allowExternalAudio to true or provide allowedAudioOrigins.`
|
|
1760
|
+
);
|
|
1761
|
+
}
|
|
1328
1762
|
function validateElement(token, source, diagnostics, voiceName, options, voiceCatalog) {
|
|
1329
1763
|
const name = token.name.toLowerCase();
|
|
1330
1764
|
if (name === "voice" && !attr(token, "name")?.trim())
|
|
@@ -1426,35 +1860,51 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceCa
|
|
|
1426
1860
|
if (!value || !/^\d+(?:\.\d+)?(?:ms|s)$/.test(value.trim()))
|
|
1427
1861
|
addDiagnostic(diagnostics, source, token.start, '<mstts:silence> requires a time-valued "value" attribute.');
|
|
1428
1862
|
}
|
|
1863
|
+
if (name === "mstts:audioduration") {
|
|
1864
|
+
const value = attr(token, "value");
|
|
1865
|
+
if (!value || !isValidAzureAudioDuration(value))
|
|
1866
|
+
addDiagnostic(
|
|
1867
|
+
diagnostics,
|
|
1868
|
+
source,
|
|
1869
|
+
token.start,
|
|
1870
|
+
'<mstts:audioduration> requires a positive duration such as "10s", "5000ms", or "00:00:10".'
|
|
1871
|
+
);
|
|
1872
|
+
if (!token.selfClosing)
|
|
1873
|
+
addDiagnostic(diagnostics, source, token.start, "<mstts:audioduration> must be self-closing.");
|
|
1874
|
+
}
|
|
1429
1875
|
if (name === "mstts:viseme") {
|
|
1430
1876
|
const type = attr(token, "type");
|
|
1431
1877
|
if (!type || !ALLOWED_VISEME_TYPES.has(type))
|
|
1432
1878
|
addDiagnostic(diagnostics, source, token.start, '<mstts:viseme> requires a supported "type" attribute.');
|
|
1433
1879
|
}
|
|
1434
1880
|
if (name === "audio") {
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1881
|
+
validateAudioSource(token, source, diagnostics, options, "audio");
|
|
1882
|
+
}
|
|
1883
|
+
if (name === "mstts:turn") {
|
|
1884
|
+
if (!attr(token, "voice")?.trim())
|
|
1885
|
+
addDiagnostic(diagnostics, source, token.start, '<mstts:turn> requires a non-empty "voice" attribute.');
|
|
1886
|
+
if (token.parentName?.toLowerCase() !== "mstts:dialog")
|
|
1887
|
+
addDiagnostic(diagnostics, source, token.start, "<mstts:turn> is only allowed directly inside <mstts:dialog>.");
|
|
1888
|
+
}
|
|
1889
|
+
if (name === "mstts:backgroundaudio") {
|
|
1890
|
+
validateAudioSource(token, source, diagnostics, options, "mstts:backgroundaudio");
|
|
1891
|
+
const volume = attr(token, "volume");
|
|
1892
|
+
if (volume && !/^(silent|x-soft|soft|medium|loud|x-loud|[+-]?\d+(?:\.\d+)?(?:dB|%))$/i.test(volume.trim()))
|
|
1893
|
+
addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:backgroundaudio volume> value "${volume}".`);
|
|
1894
|
+
for (const [attribute, value] of [
|
|
1895
|
+
["fadein", attr(token, "fadein")],
|
|
1896
|
+
["fadeout", attr(token, "fadeout")]
|
|
1897
|
+
]) {
|
|
1898
|
+
if (value && !isValidAzureAudioDuration(value))
|
|
1450
1899
|
addDiagnostic(
|
|
1451
1900
|
diagnostics,
|
|
1452
1901
|
source,
|
|
1453
1902
|
token.start,
|
|
1454
|
-
`<
|
|
1455
|
-
"error"
|
|
1903
|
+
`<mstts:backgroundaudio ${attribute}> must be a positive duration such as "500ms" or "10s".`
|
|
1456
1904
|
);
|
|
1457
1905
|
}
|
|
1906
|
+
if (!token.selfClosing)
|
|
1907
|
+
addDiagnostic(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
|
|
1458
1908
|
}
|
|
1459
1909
|
}
|
|
1460
1910
|
function validateAzureSsml(ssml, options = {}) {
|
|
@@ -1520,12 +1970,42 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
1520
1970
|
);
|
|
1521
1971
|
}
|
|
1522
1972
|
for (const token of tokens) {
|
|
1523
|
-
const
|
|
1973
|
+
const tokenName = token.name.toLowerCase();
|
|
1974
|
+
const tokenVoiceName = tokenName === "voice" ? attr(token, "name")?.trim() : tokenName === "mstts:turn" ? attr(token, "voice")?.trim() || token.parentVoiceName : options.validateNestedVoices === false ? voiceName : token.parentVoiceName;
|
|
1524
1975
|
validateElement(token, ssml, diagnostics, tokenVoiceName, options, voiceCatalog);
|
|
1976
|
+
const definition = tokenVoiceName ? voiceCatalog.get(tokenVoiceName.toLowerCase()) : void 0;
|
|
1977
|
+
validateVoiceFeatureMatrix(token, ssml, diagnostics, tokenVoiceName, definition);
|
|
1978
|
+
if (tokenName === "voice" && options.model && definition?.models && !definition.models.some((model) => model.toLowerCase() === options.model?.toLowerCase())) {
|
|
1979
|
+
addDiagnostic(
|
|
1980
|
+
diagnostics,
|
|
1981
|
+
ssml,
|
|
1982
|
+
token.start,
|
|
1983
|
+
`Voice "${tokenVoiceName}" does not support model "${options.model}" according to the configured feature matrix.`,
|
|
1984
|
+
"error",
|
|
1985
|
+
"azure-unsupported-model-for-voice"
|
|
1986
|
+
);
|
|
1987
|
+
}
|
|
1525
1988
|
}
|
|
1526
1989
|
return diagnostics;
|
|
1527
1990
|
}
|
|
1528
1991
|
|
|
1992
|
+
// packages/ssml-core/src/generated/azureVoiceCatalog.ts
|
|
1993
|
+
var AZURE_VOICE_CATALOG_METADATA = {
|
|
1994
|
+
apiVersion: "2025-10-01",
|
|
1995
|
+
generatedAt: "2026-08-28T00:00:00.000Z",
|
|
1996
|
+
regions: [],
|
|
1997
|
+
voiceCount: AZURE_VOICE_DEFINITIONS.length
|
|
1998
|
+
};
|
|
1999
|
+
|
|
2000
|
+
// packages/ssml-core/src/voiceCatalog.ts
|
|
2001
|
+
function getAzureVoiceCatalogMetadata() {
|
|
2002
|
+
return {
|
|
2003
|
+
...AZURE_VOICE_CATALOG_METADATA,
|
|
2004
|
+
regions: [...AZURE_VOICE_CATALOG_METADATA.regions]
|
|
2005
|
+
};
|
|
2006
|
+
}
|
|
2007
|
+
var getBuiltInVoiceCatalogMetadata = getAzureVoiceCatalogMetadata;
|
|
2008
|
+
|
|
1529
2009
|
export {
|
|
1530
2010
|
buildSsml,
|
|
1531
2011
|
parseSsml,
|
|
@@ -1533,8 +2013,14 @@ export {
|
|
|
1533
2013
|
validateSsml,
|
|
1534
2014
|
extractSsmlText,
|
|
1535
2015
|
mapSsmlTextNodes,
|
|
2016
|
+
extractSsmlTranslatableText,
|
|
2017
|
+
fromPlainTextToSsml,
|
|
2018
|
+
validateSsmlStructureIntegrity,
|
|
2019
|
+
isValidAzureAudioDuration,
|
|
1536
2020
|
normalizeAzureLanguage,
|
|
1537
2021
|
areAzureLanguagesEquivalent,
|
|
1538
|
-
validateAzureSsml
|
|
2022
|
+
validateAzureSsml,
|
|
2023
|
+
getAzureVoiceCatalogMetadata,
|
|
2024
|
+
getBuiltInVoiceCatalogMetadata
|
|
1539
2025
|
};
|
|
1540
|
-
//# sourceMappingURL=chunk-
|
|
2026
|
+
//# sourceMappingURL=chunk-7LCSH4SZ.mjs.map
|