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
package/dist/core.js
CHANGED
|
@@ -31,11 +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,
|
|
38
|
+
isValidAzureAudioDuration: () => isValidAzureAudioDuration,
|
|
34
39
|
mapSsmlTextNodes: () => mapSsmlTextNodes,
|
|
35
40
|
normalizeAzureLanguage: () => normalizeAzureLanguage,
|
|
36
41
|
parseSsml: () => parseSsml,
|
|
37
42
|
validateAzureSsml: () => validateAzureSsml,
|
|
38
|
-
validateSsml: () => validateSsml
|
|
43
|
+
validateSsml: () => validateSsml,
|
|
44
|
+
validateSsmlStructureIntegrity: () => validateSsmlStructureIntegrity
|
|
39
45
|
});
|
|
40
46
|
module.exports = __toCommonJS(core_exports);
|
|
41
47
|
|
|
@@ -71,7 +77,14 @@ var SSML_TAGS = {
|
|
|
71
77
|
MSTTS_SILENCE: "mstts:silence",
|
|
72
78
|
SILENCE: "silence",
|
|
73
79
|
MSTTS_VISEME: "mstts:viseme",
|
|
74
|
-
VISEME: "viseme"
|
|
80
|
+
VISEME: "viseme",
|
|
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"
|
|
75
88
|
};
|
|
76
89
|
var SSML_ATTRS = {
|
|
77
90
|
VERSION: "version",
|
|
@@ -80,6 +93,7 @@ var SSML_ATTRS = {
|
|
|
80
93
|
LANG: "lang",
|
|
81
94
|
MSTTS_XMLNS: "xmlns:mstts",
|
|
82
95
|
NAME: "name",
|
|
96
|
+
VOICE: "voice",
|
|
83
97
|
EFFECT: "effect",
|
|
84
98
|
RATE: "rate",
|
|
85
99
|
PITCH: "pitch",
|
|
@@ -111,7 +125,9 @@ var SSML_ATTRS = {
|
|
|
111
125
|
MARK: "mark",
|
|
112
126
|
URI: "uri",
|
|
113
127
|
TYPE: "type",
|
|
114
|
-
VALUE: "value"
|
|
128
|
+
VALUE: "value",
|
|
129
|
+
FADE_IN: "fadein",
|
|
130
|
+
FADE_OUT: "fadeout"
|
|
115
131
|
};
|
|
116
132
|
|
|
117
133
|
// packages/ssml-core/src/builder.ts
|
|
@@ -200,6 +216,23 @@ function getAttributes(element) {
|
|
|
200
216
|
case SSML_TAGS.VISEME:
|
|
201
217
|
addAttribute(attributes, SSML_ATTRS.TYPE, element.typeValue ?? element.visemeType);
|
|
202
218
|
break;
|
|
219
|
+
case SSML_TAGS.MSTTS_AUDIO_DURATION:
|
|
220
|
+
addAttribute(attributes, SSML_ATTRS.VALUE, element.value);
|
|
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;
|
|
203
236
|
case SSML_TAGS.PARAGRAPH:
|
|
204
237
|
case SSML_TAGS.SENTENCE:
|
|
205
238
|
case SSML_TAGS.WORD:
|
|
@@ -224,6 +257,8 @@ function getTagName(element) {
|
|
|
224
257
|
case SSML_TAGS.VISEME:
|
|
225
258
|
case SSML_TAGS.MSTTS_VISEME:
|
|
226
259
|
return SSML_TAGS.MSTTS_VISEME;
|
|
260
|
+
case SSML_TAGS.MSTTS_AUDIO_DURATION:
|
|
261
|
+
return SSML_TAGS.MSTTS_AUDIO_DURATION;
|
|
227
262
|
case "element":
|
|
228
263
|
case "custom":
|
|
229
264
|
return element.name;
|
|
@@ -758,6 +793,46 @@ function convertElement(node) {
|
|
|
758
793
|
if (typeValue !== void 0) element.typeValue = typeValue;
|
|
759
794
|
return finishElement(element, node, attributes);
|
|
760
795
|
}
|
|
796
|
+
case SSML_TAGS.MSTTS_AUDIO_DURATION: {
|
|
797
|
+
const element = { type: SSML_TAGS.MSTTS_AUDIO_DURATION };
|
|
798
|
+
const value = readAttribute(attributes, SSML_ATTRS.VALUE);
|
|
799
|
+
if (value !== void 0) element.value = value;
|
|
800
|
+
return finishElement(element, node, attributes);
|
|
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
|
+
}
|
|
761
836
|
default: {
|
|
762
837
|
const element = {
|
|
763
838
|
name: node.name,
|
|
@@ -1032,93 +1107,403 @@ async function mapSsmlTextNodes(ssml, transform, options = {}) {
|
|
|
1032
1107
|
return result + ssml.slice(cursor);
|
|
1033
1108
|
}
|
|
1034
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
|
+
|
|
1387
|
+
// packages/ssml-core/src/generated/azureVoiceDefinitions.ts
|
|
1388
|
+
var AZURE_VOICE_DEFINITIONS = [
|
|
1389
|
+
{ name: "de-DE-ConradNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
|
|
1390
|
+
{ name: "de-DE-KatjaNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
|
|
1391
|
+
{ name: "en-US-AndrewNeural", locale: "en-US", styles: ["empathetic", "relieved"] },
|
|
1392
|
+
{
|
|
1393
|
+
name: "en-US-GuyNeural",
|
|
1394
|
+
locale: "en-US",
|
|
1395
|
+
styles: [
|
|
1396
|
+
"angry",
|
|
1397
|
+
"cheerful",
|
|
1398
|
+
"excited",
|
|
1399
|
+
"friendly",
|
|
1400
|
+
"hopeful",
|
|
1401
|
+
"newscast",
|
|
1402
|
+
"sad",
|
|
1403
|
+
"shouting",
|
|
1404
|
+
"terrified",
|
|
1405
|
+
"unfriendly",
|
|
1406
|
+
"whispering"
|
|
1407
|
+
]
|
|
1408
|
+
},
|
|
1409
|
+
{
|
|
1410
|
+
name: "en-US-JennyMultilingualNeural",
|
|
1411
|
+
locale: "en-US",
|
|
1412
|
+
styles: [
|
|
1413
|
+
"cheerful",
|
|
1414
|
+
"empathetic",
|
|
1415
|
+
"excited",
|
|
1416
|
+
"friendly",
|
|
1417
|
+
"hopeful",
|
|
1418
|
+
"sad",
|
|
1419
|
+
"shouting",
|
|
1420
|
+
"terrified",
|
|
1421
|
+
"unfriendly",
|
|
1422
|
+
"whispering"
|
|
1423
|
+
]
|
|
1424
|
+
},
|
|
1425
|
+
{
|
|
1426
|
+
name: "en-US-JennyNeural",
|
|
1427
|
+
locale: "en-US",
|
|
1428
|
+
styles: [
|
|
1429
|
+
"assistant",
|
|
1430
|
+
"chat",
|
|
1431
|
+
"customerservice",
|
|
1432
|
+
"newscast",
|
|
1433
|
+
"cheerful",
|
|
1434
|
+
"empathetic",
|
|
1435
|
+
"excited",
|
|
1436
|
+
"friendly",
|
|
1437
|
+
"hopeful",
|
|
1438
|
+
"sad",
|
|
1439
|
+
"shouting",
|
|
1440
|
+
"terrified",
|
|
1441
|
+
"unfriendly",
|
|
1442
|
+
"whispering"
|
|
1443
|
+
]
|
|
1444
|
+
},
|
|
1445
|
+
{ name: "es-ES-ElviraNeural", locale: "es-ES" },
|
|
1446
|
+
{ name: "fil-PH-AngeloNeural", locale: "fil-PH" },
|
|
1447
|
+
{ name: "fr-FR-DeniseNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
|
|
1448
|
+
{ name: "fr-FR-HenriNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
|
|
1449
|
+
{ name: "id-ID-GadisNeural", locale: "id-ID" },
|
|
1450
|
+
{ name: "it-IT-ElsaNeural", locale: "it-IT", styles: ["cheerful", "sad"] },
|
|
1451
|
+
{ name: "ja-JP-KeitaNeural", locale: "ja-JP", styles: ["chat"] },
|
|
1452
|
+
{ name: "ja-JP-MayuNeural", locale: "ja-JP", styles: ["calm", "cheerful", "sad"] },
|
|
1453
|
+
{ name: "ja-JP-NanamiNeural", locale: "ja-JP", styles: ["chat", "customerservice", "cheerful", "whispering", "sad"] },
|
|
1454
|
+
{ name: "ko-KR-SunHiNeural", locale: "ko-KR", styles: ["cheerful", "sad"] },
|
|
1455
|
+
{ name: "ms-MY-YasminNeural", locale: "ms-MY" },
|
|
1456
|
+
{ name: "pt-BR-FranciscaNeural", locale: "pt-BR", styles: ["calm"] },
|
|
1457
|
+
{
|
|
1458
|
+
name: "ru-RU-SvetlanaNeural",
|
|
1459
|
+
locale: "ru-RU",
|
|
1460
|
+
styles: ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"]
|
|
1461
|
+
},
|
|
1462
|
+
{ name: "th-TH-PremwadeeNeural", locale: "th-TH" },
|
|
1463
|
+
{ name: "vi-VN-HoaiMyNeural", locale: "vi-VN" },
|
|
1464
|
+
{
|
|
1465
|
+
name: "zh-CN-XiaoxiaoNeural",
|
|
1466
|
+
locale: "zh-CN",
|
|
1467
|
+
styles: [
|
|
1468
|
+
"assistant",
|
|
1469
|
+
"chat",
|
|
1470
|
+
"customerservice",
|
|
1471
|
+
"newscast",
|
|
1472
|
+
"cheerful",
|
|
1473
|
+
"empathetic",
|
|
1474
|
+
"excited",
|
|
1475
|
+
"friendly",
|
|
1476
|
+
"hopeful",
|
|
1477
|
+
"sad",
|
|
1478
|
+
"terrified",
|
|
1479
|
+
"whispering",
|
|
1480
|
+
"poetry-reading",
|
|
1481
|
+
"sports_commentary",
|
|
1482
|
+
"sports_commentary_excited",
|
|
1483
|
+
"story"
|
|
1484
|
+
]
|
|
1485
|
+
},
|
|
1486
|
+
{
|
|
1487
|
+
name: "zh-CN-YunxiNeural",
|
|
1488
|
+
locale: "zh-CN",
|
|
1489
|
+
styles: [
|
|
1490
|
+
"narration-relaxed",
|
|
1491
|
+
"embarrassed",
|
|
1492
|
+
"fearful",
|
|
1493
|
+
"sad",
|
|
1494
|
+
"disgruntled",
|
|
1495
|
+
"serious",
|
|
1496
|
+
"angry",
|
|
1497
|
+
"depressed",
|
|
1498
|
+
"chat",
|
|
1499
|
+
"cheerful",
|
|
1500
|
+
"assistant"
|
|
1501
|
+
]
|
|
1502
|
+
},
|
|
1503
|
+
{ name: "zh-TW-HsiaoChenNeural", locale: "zh-TW" }
|
|
1504
|
+
];
|
|
1505
|
+
|
|
1035
1506
|
// packages/ssml-core/src/azureValidation.ts
|
|
1036
|
-
var EXPRESS_AS_STYLES = {
|
|
1037
|
-
"en-us-jennyneural": [
|
|
1038
|
-
"assistant",
|
|
1039
|
-
"chat",
|
|
1040
|
-
"customerservice",
|
|
1041
|
-
"newscast",
|
|
1042
|
-
"cheerful",
|
|
1043
|
-
"empathetic",
|
|
1044
|
-
"excited",
|
|
1045
|
-
"friendly",
|
|
1046
|
-
"hopeful",
|
|
1047
|
-
"sad",
|
|
1048
|
-
"shouting",
|
|
1049
|
-
"terrified",
|
|
1050
|
-
"unfriendly",
|
|
1051
|
-
"whispering"
|
|
1052
|
-
],
|
|
1053
|
-
"en-us-guyneural": [
|
|
1054
|
-
"angry",
|
|
1055
|
-
"cheerful",
|
|
1056
|
-
"excited",
|
|
1057
|
-
"friendly",
|
|
1058
|
-
"hopeful",
|
|
1059
|
-
"newscast",
|
|
1060
|
-
"sad",
|
|
1061
|
-
"shouting",
|
|
1062
|
-
"terrified",
|
|
1063
|
-
"unfriendly",
|
|
1064
|
-
"whispering"
|
|
1065
|
-
],
|
|
1066
|
-
"en-us-jennymultilingualneural": [
|
|
1067
|
-
"cheerful",
|
|
1068
|
-
"empathetic",
|
|
1069
|
-
"excited",
|
|
1070
|
-
"friendly",
|
|
1071
|
-
"hopeful",
|
|
1072
|
-
"sad",
|
|
1073
|
-
"shouting",
|
|
1074
|
-
"terrified",
|
|
1075
|
-
"unfriendly",
|
|
1076
|
-
"whispering"
|
|
1077
|
-
],
|
|
1078
|
-
"en-us-andrewneural": ["empathetic", "relieved"],
|
|
1079
|
-
"ja-jp-mayuneural": ["calm", "cheerful", "sad"],
|
|
1080
|
-
"ja-jp-nanamineural": ["chat", "customerservice", "cheerful", "whispering", "sad"],
|
|
1081
|
-
"ja-jp-keitaneural": ["chat"],
|
|
1082
|
-
"ko-kr-sunhineural": ["cheerful", "sad"],
|
|
1083
|
-
"zh-cn-yunxineural": [
|
|
1084
|
-
"narration-relaxed",
|
|
1085
|
-
"embarrassed",
|
|
1086
|
-
"fearful",
|
|
1087
|
-
"sad",
|
|
1088
|
-
"disgruntled",
|
|
1089
|
-
"serious",
|
|
1090
|
-
"angry",
|
|
1091
|
-
"depressed",
|
|
1092
|
-
"chat",
|
|
1093
|
-
"cheerful",
|
|
1094
|
-
"assistant"
|
|
1095
|
-
],
|
|
1096
|
-
"zh-cn-xiaoxiaoneural": [
|
|
1097
|
-
"assistant",
|
|
1098
|
-
"chat",
|
|
1099
|
-
"customerservice",
|
|
1100
|
-
"newscast",
|
|
1101
|
-
"cheerful",
|
|
1102
|
-
"empathetic",
|
|
1103
|
-
"excited",
|
|
1104
|
-
"friendly",
|
|
1105
|
-
"hopeful",
|
|
1106
|
-
"sad",
|
|
1107
|
-
"terrified",
|
|
1108
|
-
"whispering",
|
|
1109
|
-
"poetry-reading",
|
|
1110
|
-
"sports_commentary",
|
|
1111
|
-
"sports_commentary_excited",
|
|
1112
|
-
"story"
|
|
1113
|
-
],
|
|
1114
|
-
"fr-fr-deniseneural": ["cheerful", "sad"],
|
|
1115
|
-
"fr-fr-henrineural": ["cheerful", "sad"],
|
|
1116
|
-
"pt-br-franciscaneural": ["calm"],
|
|
1117
|
-
"it-it-elsaneural": ["cheerful", "sad"],
|
|
1118
|
-
"de-de-katjaneural": ["cheerful", "sad"],
|
|
1119
|
-
"de-de-conradneural": ["cheerful", "sad"],
|
|
1120
|
-
"ru-ru-svetlananeural": ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"]
|
|
1121
|
-
};
|
|
1122
1507
|
var ALLOWED_BREAK_STRENGTHS = /* @__PURE__ */ new Set(["none", "x-weak", "weak", "medium", "strong", "x-strong"]);
|
|
1123
1508
|
var ALLOWED_SAY_AS = /* @__PURE__ */ new Set([
|
|
1124
1509
|
"characters",
|
|
@@ -1213,12 +1598,23 @@ function tokenizeElements(source) {
|
|
|
1213
1598
|
attributes.set(match[1].toLowerCase(), decodeAttribute(match[3]));
|
|
1214
1599
|
}
|
|
1215
1600
|
const selfClosing = /\/\s*>$/.test(raw);
|
|
1601
|
+
const parent = openElements[openElements.length - 1];
|
|
1216
1602
|
const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
|
|
1217
|
-
|
|
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
|
+
});
|
|
1218
1614
|
if (!selfClosing) {
|
|
1219
1615
|
openElements.push({
|
|
1220
|
-
name:
|
|
1221
|
-
voiceName:
|
|
1616
|
+
name: tokenName,
|
|
1617
|
+
voiceName: tokenVoiceName
|
|
1222
1618
|
});
|
|
1223
1619
|
}
|
|
1224
1620
|
index = end + 1;
|
|
@@ -1247,18 +1643,17 @@ function isSupportedProsodyRate(value) {
|
|
|
1247
1643
|
const numericValue = Number(multiplier[1]);
|
|
1248
1644
|
return numericValue >= 0.5 && numericValue <= 2;
|
|
1249
1645
|
}
|
|
1646
|
+
function isValidAzureAudioDuration(value) {
|
|
1647
|
+
const trimmed = value.trim();
|
|
1648
|
+
const numeric = /^(\d+(?:\.\d+)?)(ms|s)$/.exec(trimmed);
|
|
1649
|
+
if (numeric) return Number(numeric[1]) > 0;
|
|
1650
|
+
const clock = /^(\d{2,}):([0-5]\d):([0-5]\d)(?:\.(\d{1,3}))?$/.exec(trimmed);
|
|
1651
|
+
if (!clock) return false;
|
|
1652
|
+
return Number(clock[1]) > 0 || Number(clock[2]) > 0 || Number(clock[3]) > 0 || Number(clock[4] ?? 0) > 0;
|
|
1653
|
+
}
|
|
1250
1654
|
function attr(token, name) {
|
|
1251
1655
|
return token.attributes.get(name.toLowerCase());
|
|
1252
1656
|
}
|
|
1253
|
-
var ADDITIONAL_VOICE_DEFINITIONS = [
|
|
1254
|
-
{ name: "zh-TW-HsiaoChenNeural", locale: "zh-TW" },
|
|
1255
|
-
{ name: "es-ES-ElviraNeural", locale: "es-ES" },
|
|
1256
|
-
{ name: "th-TH-PremwadeeNeural", locale: "th-TH" },
|
|
1257
|
-
{ name: "fil-PH-AngeloNeural", locale: "fil-PH" },
|
|
1258
|
-
{ name: "vi-VN-HoaiMyNeural", locale: "vi-VN" },
|
|
1259
|
-
{ name: "id-ID-GadisNeural", locale: "id-ID" },
|
|
1260
|
-
{ name: "ms-MY-YasminNeural", locale: "ms-MY" }
|
|
1261
|
-
];
|
|
1262
1657
|
var DEFAULT_LANGUAGE_ALIASES = {
|
|
1263
1658
|
"zh-CN": ["zh-Hans"],
|
|
1264
1659
|
"zh-TW": ["zh-Hant"]
|
|
@@ -1321,10 +1716,7 @@ function definitionFromStyleMap(voiceName, styles) {
|
|
|
1321
1716
|
}
|
|
1322
1717
|
function normalizeVoiceCatalog(options) {
|
|
1323
1718
|
const definitions = /* @__PURE__ */ new Map();
|
|
1324
|
-
for (const
|
|
1325
|
-
definitions.set(name.toLowerCase(), definitionFromStyleMap(name, styles));
|
|
1326
|
-
}
|
|
1327
|
-
for (const definition of ADDITIONAL_VOICE_DEFINITIONS) definitions.set(definition.name.toLowerCase(), definition);
|
|
1719
|
+
for (const definition of AZURE_VOICE_DEFINITIONS) definitions.set(definition.name.toLowerCase(), definition);
|
|
1328
1720
|
for (const definition of options.voiceCatalog ?? []) definitions.set(definition.name.toLowerCase(), definition);
|
|
1329
1721
|
for (const definition of options.voiceDefinitions ?? []) definitions.set(definition.name.toLowerCase(), definition);
|
|
1330
1722
|
for (const definition of options.customVoiceDefinitions ?? [])
|
|
@@ -1360,6 +1752,54 @@ function definitionMatchesLanguage(definition, voiceName, language, normalizeLan
|
|
|
1360
1752
|
if (!normalizedLanguage || !normalizedCandidates.some(Boolean)) return void 0;
|
|
1361
1753
|
return normalizedLanguage === languagePart(normalizedLanguage) ? normalizedCandidates.some((candidate) => languagePart(candidate) === normalizedLanguage) : false;
|
|
1362
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
|
+
}
|
|
1363
1803
|
function validateElement(token, source, diagnostics, voiceName, options, voiceCatalog) {
|
|
1364
1804
|
const name = token.name.toLowerCase();
|
|
1365
1805
|
if (name === "voice" && !attr(token, "name")?.trim())
|
|
@@ -1461,35 +1901,51 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceCa
|
|
|
1461
1901
|
if (!value || !/^\d+(?:\.\d+)?(?:ms|s)$/.test(value.trim()))
|
|
1462
1902
|
addDiagnostic(diagnostics, source, token.start, '<mstts:silence> requires a time-valued "value" attribute.');
|
|
1463
1903
|
}
|
|
1904
|
+
if (name === "mstts:audioduration") {
|
|
1905
|
+
const value = attr(token, "value");
|
|
1906
|
+
if (!value || !isValidAzureAudioDuration(value))
|
|
1907
|
+
addDiagnostic(
|
|
1908
|
+
diagnostics,
|
|
1909
|
+
source,
|
|
1910
|
+
token.start,
|
|
1911
|
+
'<mstts:audioduration> requires a positive duration such as "10s", "5000ms", or "00:00:10".'
|
|
1912
|
+
);
|
|
1913
|
+
if (!token.selfClosing)
|
|
1914
|
+
addDiagnostic(diagnostics, source, token.start, "<mstts:audioduration> must be self-closing.");
|
|
1915
|
+
}
|
|
1464
1916
|
if (name === "mstts:viseme") {
|
|
1465
1917
|
const type = attr(token, "type");
|
|
1466
1918
|
if (!type || !ALLOWED_VISEME_TYPES.has(type))
|
|
1467
1919
|
addDiagnostic(diagnostics, source, token.start, '<mstts:viseme> requires a supported "type" attribute.');
|
|
1468
1920
|
}
|
|
1469
1921
|
if (name === "audio") {
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
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))
|
|
1485
1940
|
addDiagnostic(
|
|
1486
1941
|
diagnostics,
|
|
1487
1942
|
source,
|
|
1488
1943
|
token.start,
|
|
1489
|
-
`<
|
|
1490
|
-
"error"
|
|
1944
|
+
`<mstts:backgroundaudio ${attribute}> must be a positive duration such as "500ms" or "10s".`
|
|
1491
1945
|
);
|
|
1492
1946
|
}
|
|
1947
|
+
if (!token.selfClosing)
|
|
1948
|
+
addDiagnostic(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
|
|
1493
1949
|
}
|
|
1494
1950
|
}
|
|
1495
1951
|
function validateAzureSsml(ssml, options = {}) {
|
|
@@ -1555,21 +2011,57 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
1555
2011
|
);
|
|
1556
2012
|
}
|
|
1557
2013
|
for (const token of tokens) {
|
|
1558
|
-
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;
|
|
1559
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
|
+
}
|
|
1560
2029
|
}
|
|
1561
2030
|
return diagnostics;
|
|
1562
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;
|
|
1563
2049
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1564
2050
|
0 && (module.exports = {
|
|
1565
2051
|
areAzureLanguagesEquivalent,
|
|
1566
2052
|
buildPartialSsml,
|
|
1567
2053
|
buildSsml,
|
|
1568
2054
|
extractSsmlText,
|
|
2055
|
+
extractSsmlTranslatableText,
|
|
2056
|
+
fromPlainTextToSsml,
|
|
2057
|
+
getAzureVoiceCatalogMetadata,
|
|
2058
|
+
getBuiltInVoiceCatalogMetadata,
|
|
2059
|
+
isValidAzureAudioDuration,
|
|
1569
2060
|
mapSsmlTextNodes,
|
|
1570
2061
|
normalizeAzureLanguage,
|
|
1571
2062
|
parseSsml,
|
|
1572
2063
|
validateAzureSsml,
|
|
1573
|
-
validateSsml
|
|
2064
|
+
validateSsml,
|
|
2065
|
+
validateSsmlStructureIntegrity
|
|
1574
2066
|
});
|
|
1575
2067
|
//# sourceMappingURL=core.js.map
|