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/index.js
CHANGED
|
@@ -44,12 +44,18 @@ __export(src_exports, {
|
|
|
44
44
|
buildPartialSsml: () => buildPartialSsml,
|
|
45
45
|
buildSsml: () => buildSsml,
|
|
46
46
|
extractSsmlText: () => extractSsmlText,
|
|
47
|
+
extractSsmlTranslatableText: () => extractSsmlTranslatableText,
|
|
48
|
+
fromPlainTextToSsml: () => fromPlainTextToSsml,
|
|
49
|
+
getAzureVoiceCatalogMetadata: () => getAzureVoiceCatalogMetadata,
|
|
50
|
+
getBuiltInVoiceCatalogMetadata: () => getBuiltInVoiceCatalogMetadata,
|
|
51
|
+
isValidAzureAudioDuration: () => isValidAzureAudioDuration,
|
|
47
52
|
mapSsmlTextNodes: () => mapSsmlTextNodes,
|
|
48
53
|
normalizeAzureLanguage: () => normalizeAzureLanguage,
|
|
49
54
|
parseSsml: () => parseSsml,
|
|
50
55
|
synthesizeSpeech: () => synthesizeSpeech,
|
|
51
56
|
validateAzureSsml: () => validateAzureSsml,
|
|
52
|
-
validateSsml: () => validateSsml
|
|
57
|
+
validateSsml: () => validateSsml,
|
|
58
|
+
validateSsmlStructureIntegrity: () => validateSsmlStructureIntegrity
|
|
53
59
|
});
|
|
54
60
|
module.exports = __toCommonJS(src_exports);
|
|
55
61
|
|
|
@@ -85,7 +91,14 @@ var SSML_TAGS = {
|
|
|
85
91
|
MSTTS_SILENCE: "mstts:silence",
|
|
86
92
|
SILENCE: "silence",
|
|
87
93
|
MSTTS_VISEME: "mstts:viseme",
|
|
88
|
-
VISEME: "viseme"
|
|
94
|
+
VISEME: "viseme",
|
|
95
|
+
MSTTS_AUDIO_DURATION: "mstts:audioduration",
|
|
96
|
+
MSTTS_DIALOG: "mstts:dialog",
|
|
97
|
+
MSTTS_TURN: "mstts:turn",
|
|
98
|
+
MSTTS_BACKGROUND_AUDIO: "mstts:backgroundaudio",
|
|
99
|
+
MSTTS_TTS_EMBEDDING: "mstts:ttsembedding",
|
|
100
|
+
MSTTS_EMBEDDING: "mstts:embedding",
|
|
101
|
+
MSTTS_VOICE_CONVERSION: "mstts:voiceconversion"
|
|
89
102
|
};
|
|
90
103
|
var SSML_ATTRS = {
|
|
91
104
|
VERSION: "version",
|
|
@@ -94,6 +107,7 @@ var SSML_ATTRS = {
|
|
|
94
107
|
LANG: "lang",
|
|
95
108
|
MSTTS_XMLNS: "xmlns:mstts",
|
|
96
109
|
NAME: "name",
|
|
110
|
+
VOICE: "voice",
|
|
97
111
|
EFFECT: "effect",
|
|
98
112
|
RATE: "rate",
|
|
99
113
|
PITCH: "pitch",
|
|
@@ -125,7 +139,9 @@ var SSML_ATTRS = {
|
|
|
125
139
|
MARK: "mark",
|
|
126
140
|
URI: "uri",
|
|
127
141
|
TYPE: "type",
|
|
128
|
-
VALUE: "value"
|
|
142
|
+
VALUE: "value",
|
|
143
|
+
FADE_IN: "fadein",
|
|
144
|
+
FADE_OUT: "fadeout"
|
|
129
145
|
};
|
|
130
146
|
|
|
131
147
|
// packages/ssml-core/src/builder.ts
|
|
@@ -214,6 +230,23 @@ function getAttributes(element) {
|
|
|
214
230
|
case SSML_TAGS.VISEME:
|
|
215
231
|
addAttribute(attributes, SSML_ATTRS.TYPE, element.typeValue ?? element.visemeType);
|
|
216
232
|
break;
|
|
233
|
+
case SSML_TAGS.MSTTS_AUDIO_DURATION:
|
|
234
|
+
addAttribute(attributes, SSML_ATTRS.VALUE, element.value);
|
|
235
|
+
break;
|
|
236
|
+
case SSML_TAGS.MSTTS_TURN:
|
|
237
|
+
addAttribute(attributes, SSML_ATTRS.VOICE, element.voice);
|
|
238
|
+
break;
|
|
239
|
+
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO:
|
|
240
|
+
addAttribute(attributes, SSML_ATTRS.SRC, element.src);
|
|
241
|
+
addAttribute(attributes, SSML_ATTRS.VOLUME, element.volume);
|
|
242
|
+
addAttribute(attributes, SSML_ATTRS.FADE_IN, element.fadeIn ?? element.fadein);
|
|
243
|
+
addAttribute(attributes, SSML_ATTRS.FADE_OUT, element.fadeOut ?? element.fadeout);
|
|
244
|
+
break;
|
|
245
|
+
case SSML_TAGS.MSTTS_DIALOG:
|
|
246
|
+
case SSML_TAGS.MSTTS_TTS_EMBEDDING:
|
|
247
|
+
case SSML_TAGS.MSTTS_EMBEDDING:
|
|
248
|
+
case SSML_TAGS.MSTTS_VOICE_CONVERSION:
|
|
249
|
+
break;
|
|
217
250
|
case SSML_TAGS.PARAGRAPH:
|
|
218
251
|
case SSML_TAGS.SENTENCE:
|
|
219
252
|
case SSML_TAGS.WORD:
|
|
@@ -238,6 +271,8 @@ function getTagName(element) {
|
|
|
238
271
|
case SSML_TAGS.VISEME:
|
|
239
272
|
case SSML_TAGS.MSTTS_VISEME:
|
|
240
273
|
return SSML_TAGS.MSTTS_VISEME;
|
|
274
|
+
case SSML_TAGS.MSTTS_AUDIO_DURATION:
|
|
275
|
+
return SSML_TAGS.MSTTS_AUDIO_DURATION;
|
|
241
276
|
case "element":
|
|
242
277
|
case "custom":
|
|
243
278
|
return element.name;
|
|
@@ -772,6 +807,46 @@ function convertElement(node) {
|
|
|
772
807
|
if (typeValue !== void 0) element.typeValue = typeValue;
|
|
773
808
|
return finishElement(element, node, attributes);
|
|
774
809
|
}
|
|
810
|
+
case SSML_TAGS.MSTTS_AUDIO_DURATION: {
|
|
811
|
+
const element = { type: SSML_TAGS.MSTTS_AUDIO_DURATION };
|
|
812
|
+
const value = readAttribute(attributes, SSML_ATTRS.VALUE);
|
|
813
|
+
if (value !== void 0) element.value = value;
|
|
814
|
+
return finishElement(element, node, attributes);
|
|
815
|
+
}
|
|
816
|
+
case SSML_TAGS.MSTTS_DIALOG: {
|
|
817
|
+
const element = { type: SSML_TAGS.MSTTS_DIALOG };
|
|
818
|
+
return finishElement(element, node, attributes);
|
|
819
|
+
}
|
|
820
|
+
case SSML_TAGS.MSTTS_TURN: {
|
|
821
|
+
const element = { type: SSML_TAGS.MSTTS_TURN };
|
|
822
|
+
const voice = readAttribute(attributes, SSML_ATTRS.VOICE);
|
|
823
|
+
if (voice !== void 0) element.voice = voice;
|
|
824
|
+
return finishElement(element, node, attributes);
|
|
825
|
+
}
|
|
826
|
+
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO: {
|
|
827
|
+
const element = { type: SSML_TAGS.MSTTS_BACKGROUND_AUDIO };
|
|
828
|
+
const src = readAttribute(attributes, SSML_ATTRS.SRC);
|
|
829
|
+
const volume = readAttribute(attributes, SSML_ATTRS.VOLUME);
|
|
830
|
+
const fadeIn = readAttribute(attributes, SSML_ATTRS.FADE_IN);
|
|
831
|
+
const fadeOut = readAttribute(attributes, SSML_ATTRS.FADE_OUT);
|
|
832
|
+
if (src !== void 0) element.src = src;
|
|
833
|
+
if (volume !== void 0) element.volume = volume;
|
|
834
|
+
if (fadeIn !== void 0) element.fadeIn = fadeIn;
|
|
835
|
+
if (fadeOut !== void 0) element.fadeOut = fadeOut;
|
|
836
|
+
return finishElement(element, node, attributes);
|
|
837
|
+
}
|
|
838
|
+
case SSML_TAGS.MSTTS_TTS_EMBEDDING: {
|
|
839
|
+
const element = { type: SSML_TAGS.MSTTS_TTS_EMBEDDING };
|
|
840
|
+
return finishElement(element, node, attributes);
|
|
841
|
+
}
|
|
842
|
+
case SSML_TAGS.MSTTS_EMBEDDING: {
|
|
843
|
+
const element = { type: SSML_TAGS.MSTTS_EMBEDDING };
|
|
844
|
+
return finishElement(element, node, attributes);
|
|
845
|
+
}
|
|
846
|
+
case SSML_TAGS.MSTTS_VOICE_CONVERSION: {
|
|
847
|
+
const element = { type: SSML_TAGS.MSTTS_VOICE_CONVERSION };
|
|
848
|
+
return finishElement(element, node, attributes);
|
|
849
|
+
}
|
|
775
850
|
default: {
|
|
776
851
|
const element = {
|
|
777
852
|
name: node.name,
|
|
@@ -1046,93 +1121,403 @@ async function mapSsmlTextNodes(ssml, transform, options = {}) {
|
|
|
1046
1121
|
return result + ssml.slice(cursor);
|
|
1047
1122
|
}
|
|
1048
1123
|
|
|
1124
|
+
// packages/ssml-core/src/migration.ts
|
|
1125
|
+
var DEFAULT_TRANSLATION_SKIP_TAGS = ["phoneme", "say-as", "sayAs", "sub"];
|
|
1126
|
+
function elementName(element) {
|
|
1127
|
+
switch (element.type) {
|
|
1128
|
+
case "custom":
|
|
1129
|
+
case "element":
|
|
1130
|
+
return element.name;
|
|
1131
|
+
case "expressAs":
|
|
1132
|
+
return "mstts:express-as";
|
|
1133
|
+
case "sayAs":
|
|
1134
|
+
return "say-as";
|
|
1135
|
+
case "silence":
|
|
1136
|
+
return "mstts:silence";
|
|
1137
|
+
case "viseme":
|
|
1138
|
+
return "mstts:viseme";
|
|
1139
|
+
default:
|
|
1140
|
+
return element.type;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
function addAttribute2(attributes, name, value) {
|
|
1144
|
+
if (value !== void 0) attributes[name] = value;
|
|
1145
|
+
}
|
|
1146
|
+
function elementAttributes(element) {
|
|
1147
|
+
const attributes = { ...element.attributes ?? {} };
|
|
1148
|
+
switch (element.type) {
|
|
1149
|
+
case "voice":
|
|
1150
|
+
addAttribute2(attributes, "name", element.name);
|
|
1151
|
+
addAttribute2(attributes, "effect", element.effect);
|
|
1152
|
+
break;
|
|
1153
|
+
case "prosody":
|
|
1154
|
+
addAttribute2(attributes, "rate", element.rate);
|
|
1155
|
+
addAttribute2(attributes, "pitch", element.pitch);
|
|
1156
|
+
addAttribute2(attributes, "volume", element.volume);
|
|
1157
|
+
addAttribute2(attributes, "contour", element.contour);
|
|
1158
|
+
addAttribute2(attributes, "range", element.range);
|
|
1159
|
+
break;
|
|
1160
|
+
case "break":
|
|
1161
|
+
addAttribute2(attributes, "time", element.time);
|
|
1162
|
+
addAttribute2(attributes, "strength", element.strength);
|
|
1163
|
+
break;
|
|
1164
|
+
case "express-as":
|
|
1165
|
+
case "expressAs":
|
|
1166
|
+
case "mstts:express-as":
|
|
1167
|
+
addAttribute2(attributes, "style", element.style);
|
|
1168
|
+
addAttribute2(attributes, "styledegree", element.styleDegree);
|
|
1169
|
+
addAttribute2(attributes, "role", element.role);
|
|
1170
|
+
break;
|
|
1171
|
+
case "say-as":
|
|
1172
|
+
case "sayAs":
|
|
1173
|
+
addAttribute2(attributes, "interpret-as", element.interpretAs);
|
|
1174
|
+
addAttribute2(attributes, "format", element.format);
|
|
1175
|
+
addAttribute2(attributes, "detail", element.detail);
|
|
1176
|
+
break;
|
|
1177
|
+
case "phoneme":
|
|
1178
|
+
addAttribute2(attributes, "alphabet", element.alphabet);
|
|
1179
|
+
addAttribute2(attributes, "ph", element.ph);
|
|
1180
|
+
break;
|
|
1181
|
+
case "emphasis":
|
|
1182
|
+
addAttribute2(attributes, "level", element.level);
|
|
1183
|
+
break;
|
|
1184
|
+
case "audio":
|
|
1185
|
+
addAttribute2(attributes, "src", element.src);
|
|
1186
|
+
addAttribute2(attributes, "desc", element.desc);
|
|
1187
|
+
addAttribute2(attributes, "clipBegin", element.clipBegin);
|
|
1188
|
+
addAttribute2(attributes, "clipEnd", element.clipEnd);
|
|
1189
|
+
addAttribute2(attributes, "speed", element.speed);
|
|
1190
|
+
addAttribute2(attributes, "repeatCount", element.repeatCount);
|
|
1191
|
+
addAttribute2(attributes, "repeatDuration", element.repeatDuration);
|
|
1192
|
+
addAttribute2(attributes, "soundLevel", element.soundLevel);
|
|
1193
|
+
break;
|
|
1194
|
+
case "sub":
|
|
1195
|
+
addAttribute2(attributes, "alias", element.alias);
|
|
1196
|
+
break;
|
|
1197
|
+
case "lang":
|
|
1198
|
+
addAttribute2(attributes, "xml:lang", element.lang);
|
|
1199
|
+
break;
|
|
1200
|
+
case "mark":
|
|
1201
|
+
addAttribute2(attributes, "name", element.name);
|
|
1202
|
+
break;
|
|
1203
|
+
case "bookmark":
|
|
1204
|
+
addAttribute2(attributes, "mark", element.mark);
|
|
1205
|
+
break;
|
|
1206
|
+
case "lexicon":
|
|
1207
|
+
addAttribute2(attributes, "uri", element.uri);
|
|
1208
|
+
break;
|
|
1209
|
+
case "mstts:silence":
|
|
1210
|
+
case "silence":
|
|
1211
|
+
addAttribute2(attributes, "type", element.typeValue ?? element.silenceType);
|
|
1212
|
+
addAttribute2(attributes, "value", element.value);
|
|
1213
|
+
break;
|
|
1214
|
+
case "mstts:viseme":
|
|
1215
|
+
case "viseme":
|
|
1216
|
+
addAttribute2(attributes, "type", element.typeValue ?? element.visemeType);
|
|
1217
|
+
break;
|
|
1218
|
+
case "mstts:audioduration":
|
|
1219
|
+
addAttribute2(attributes, "value", element.value);
|
|
1220
|
+
break;
|
|
1221
|
+
case "mstts:turn":
|
|
1222
|
+
addAttribute2(attributes, "voice", element.voice);
|
|
1223
|
+
break;
|
|
1224
|
+
case "mstts:backgroundaudio":
|
|
1225
|
+
addAttribute2(attributes, "src", element.src);
|
|
1226
|
+
addAttribute2(attributes, "volume", element.volume);
|
|
1227
|
+
addAttribute2(attributes, "fadein", element.fadeIn ?? element.fadein);
|
|
1228
|
+
addAttribute2(attributes, "fadeout", element.fadeOut ?? element.fadeout);
|
|
1229
|
+
break;
|
|
1230
|
+
}
|
|
1231
|
+
return Object.fromEntries(Object.entries(attributes).map(([name, value]) => [name, String(value)]));
|
|
1232
|
+
}
|
|
1233
|
+
function childrenOf(node) {
|
|
1234
|
+
return node.children ?? [];
|
|
1235
|
+
}
|
|
1236
|
+
function extractSsmlTranslatableText(ssml, options = {}) {
|
|
1237
|
+
const document = parseSsml(ssml);
|
|
1238
|
+
const skipTags = new Set((options.skipTags ?? DEFAULT_TRANSLATION_SKIP_TAGS).map((tag) => tag.toLowerCase()));
|
|
1239
|
+
const result = [];
|
|
1240
|
+
const visit = (nodes, ancestors, path) => {
|
|
1241
|
+
nodes.forEach((node, index) => {
|
|
1242
|
+
if (typeof node === "string") {
|
|
1243
|
+
if (options.includeWhitespace || node.trim().length > 0) {
|
|
1244
|
+
const context = {
|
|
1245
|
+
ancestorTags: [...ancestors],
|
|
1246
|
+
parentAttributes: {},
|
|
1247
|
+
parentTag: ancestors[ancestors.length - 1] ?? "",
|
|
1248
|
+
path: [...path, String(index)]
|
|
1249
|
+
};
|
|
1250
|
+
if (options.filter?.(context) ?? true) result.push(node);
|
|
1251
|
+
}
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
if (node.type === "text") {
|
|
1255
|
+
if (options.includeWhitespace || node.value.trim().length > 0) {
|
|
1256
|
+
const context = {
|
|
1257
|
+
ancestorTags: [...ancestors],
|
|
1258
|
+
parentAttributes: {},
|
|
1259
|
+
parentTag: ancestors[ancestors.length - 1] ?? "",
|
|
1260
|
+
path: [...path, String(index)]
|
|
1261
|
+
};
|
|
1262
|
+
if (options.filter?.(context) ?? true) result.push(node.value);
|
|
1263
|
+
}
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
const tag = elementName(node);
|
|
1267
|
+
if (skipTags.has(tag.toLowerCase())) return;
|
|
1268
|
+
visit(childrenOf(node), [...ancestors, tag], [...path, String(index)]);
|
|
1269
|
+
});
|
|
1270
|
+
};
|
|
1271
|
+
visit(childrenOf(document), ["speak"], []);
|
|
1272
|
+
return result;
|
|
1273
|
+
}
|
|
1274
|
+
function splitSentences(text) {
|
|
1275
|
+
const sentences = [];
|
|
1276
|
+
let start = 0;
|
|
1277
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
1278
|
+
const character = text[index];
|
|
1279
|
+
const isTerminator = "\u3002\uFF01\uFF1F!?".includes(character) || character === "." && /\s|$/.test(text[index + 1] ?? "");
|
|
1280
|
+
if (isTerminator) {
|
|
1281
|
+
const value = text.slice(start, index + 1).trim();
|
|
1282
|
+
if (value) sentences.push(value);
|
|
1283
|
+
start = index + 1;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
const tail = text.slice(start).trim();
|
|
1287
|
+
if (tail) sentences.push(tail);
|
|
1288
|
+
return sentences;
|
|
1289
|
+
}
|
|
1290
|
+
function fromPlainTextToSsml(text, options = {}) {
|
|
1291
|
+
if (typeof text !== "string") throw new TypeError("Plain text must be a string");
|
|
1292
|
+
const paragraphs = text.replace(/\r\n?/g, "\n").split(/\n\s*\n/).map((paragraph) => paragraph.replace(/\s*\n\s*/g, " ").trim()).filter(Boolean);
|
|
1293
|
+
const useSentences = options.splitSentences ?? options.includeSentences ?? true;
|
|
1294
|
+
const paragraphNodes = paragraphs.map((paragraph) => ({
|
|
1295
|
+
type: "p",
|
|
1296
|
+
children: useSentences ? splitSentences(paragraph).map((sentence) => ({ type: "s", children: [sentence] })) : [paragraph]
|
|
1297
|
+
}));
|
|
1298
|
+
const voiceName = options.voice ?? options.voiceName;
|
|
1299
|
+
const children = voiceName ? [{ type: "voice", name: voiceName, children: paragraphNodes }] : paragraphNodes;
|
|
1300
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
1301
|
+
${serializeDocument2({
|
|
1302
|
+
version: options.version ?? "1.0",
|
|
1303
|
+
lang: options.lang ?? options.language ?? "en-US",
|
|
1304
|
+
children
|
|
1305
|
+
})}`;
|
|
1306
|
+
}
|
|
1307
|
+
function serializeDocument2(document) {
|
|
1308
|
+
const attributes = [`version="${document.version}"`, `xml:lang="${document.lang}"`];
|
|
1309
|
+
const serialize = (node) => {
|
|
1310
|
+
if (typeof node === "string") return node.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1311
|
+
if (node.type === "text") return serialize(node.value);
|
|
1312
|
+
const tag = elementName(node);
|
|
1313
|
+
const nodeAttributes = elementAttributes(node);
|
|
1314
|
+
const serializedAttributes = Object.entries(nodeAttributes).map(([name, value]) => ` ${name}="${serialize(value).replace(/"/g, """)}"`).join("");
|
|
1315
|
+
const children = childrenOf(node).map(serialize).join("");
|
|
1316
|
+
return children ? `<${tag}${serializedAttributes}>${children}</${tag}>` : `<${tag}${serializedAttributes}/>`;
|
|
1317
|
+
};
|
|
1318
|
+
return `<speak ${attributes.join(" ")} xmlns="http://www.w3.org/2001/10/synthesis">${(document.children ?? []).map(serialize).join("")}</speak>`;
|
|
1319
|
+
}
|
|
1320
|
+
function flatten(document) {
|
|
1321
|
+
const result = [];
|
|
1322
|
+
const visit = (nodes, path) => {
|
|
1323
|
+
nodes.forEach((node, index) => {
|
|
1324
|
+
if (typeof node === "string" || node.type === "text") return;
|
|
1325
|
+
const currentPath = `${path}/${index}`;
|
|
1326
|
+
result.push({ name: elementName(node), attributes: elementAttributes(node), path: currentPath });
|
|
1327
|
+
visit(childrenOf(node), currentPath);
|
|
1328
|
+
});
|
|
1329
|
+
};
|
|
1330
|
+
result.push({ name: "speak", attributes: { version: document.version, "xml:lang": document.lang }, path: "0" });
|
|
1331
|
+
visit(childrenOf(document), "0");
|
|
1332
|
+
return result;
|
|
1333
|
+
}
|
|
1334
|
+
function validateSsmlStructureIntegrity(originalSsml, translatedSsml) {
|
|
1335
|
+
const mismatches = [];
|
|
1336
|
+
let original;
|
|
1337
|
+
let translated;
|
|
1338
|
+
try {
|
|
1339
|
+
original = parseSsml(originalSsml);
|
|
1340
|
+
} catch (error) {
|
|
1341
|
+
mismatches.push({ kind: "parse", message: `Original SSML cannot be parsed: ${String(error)}`, path: "0" });
|
|
1342
|
+
return {
|
|
1343
|
+
isValid: false,
|
|
1344
|
+
valid: false,
|
|
1345
|
+
errors: mismatches.map((item) => item.message),
|
|
1346
|
+
mismatches,
|
|
1347
|
+
mismatchedTags: []
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
try {
|
|
1351
|
+
translated = parseSsml(translatedSsml);
|
|
1352
|
+
} catch (error) {
|
|
1353
|
+
mismatches.push({ kind: "parse", message: `Translated SSML cannot be parsed: ${String(error)}`, path: "0" });
|
|
1354
|
+
return {
|
|
1355
|
+
isValid: false,
|
|
1356
|
+
valid: false,
|
|
1357
|
+
errors: mismatches.map((item) => item.message),
|
|
1358
|
+
mismatches,
|
|
1359
|
+
mismatchedTags: []
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
const originalElements = flatten(original);
|
|
1363
|
+
const translatedElements = flatten(translated);
|
|
1364
|
+
const count = Math.max(originalElements.length, translatedElements.length);
|
|
1365
|
+
for (let index = 0; index < count; index += 1) {
|
|
1366
|
+
const originalElement = originalElements[index];
|
|
1367
|
+
const translatedElement = translatedElements[index];
|
|
1368
|
+
if (!originalElement || !translatedElement || originalElement.name !== translatedElement.name) {
|
|
1369
|
+
mismatches.push({
|
|
1370
|
+
kind: "element",
|
|
1371
|
+
message: `SSML element structure differs at index ${index}`,
|
|
1372
|
+
original: originalElement?.name,
|
|
1373
|
+
path: originalElement?.path ?? translatedElement?.path ?? String(index),
|
|
1374
|
+
translated: translatedElement?.name
|
|
1375
|
+
});
|
|
1376
|
+
continue;
|
|
1377
|
+
}
|
|
1378
|
+
const attributeNames = /* @__PURE__ */ new Set([
|
|
1379
|
+
...Object.keys(originalElement.attributes),
|
|
1380
|
+
...Object.keys(translatedElement.attributes)
|
|
1381
|
+
]);
|
|
1382
|
+
for (const attribute of attributeNames) {
|
|
1383
|
+
if (originalElement.attributes[attribute] !== translatedElement.attributes[attribute]) {
|
|
1384
|
+
mismatches.push({
|
|
1385
|
+
kind: "attribute",
|
|
1386
|
+
message: `Attribute ${attribute} differs on <${originalElement.name}>`,
|
|
1387
|
+
original: originalElement.attributes[attribute],
|
|
1388
|
+
path: originalElement.path,
|
|
1389
|
+
translated: translatedElement.attributes[attribute]
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
const mismatchedTags = [
|
|
1395
|
+
...new Set(mismatches.flatMap((mismatch) => [mismatch.original, mismatch.translated].filter(Boolean)))
|
|
1396
|
+
];
|
|
1397
|
+
const errors = mismatches.map((mismatch) => mismatch.message);
|
|
1398
|
+
return { isValid: mismatches.length === 0, valid: mismatches.length === 0, errors, mismatches, mismatchedTags };
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
// packages/ssml-core/src/generated/azureVoiceDefinitions.ts
|
|
1402
|
+
var AZURE_VOICE_DEFINITIONS = [
|
|
1403
|
+
{ name: "de-DE-ConradNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
|
|
1404
|
+
{ name: "de-DE-KatjaNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
|
|
1405
|
+
{ name: "en-US-AndrewNeural", locale: "en-US", styles: ["empathetic", "relieved"] },
|
|
1406
|
+
{
|
|
1407
|
+
name: "en-US-GuyNeural",
|
|
1408
|
+
locale: "en-US",
|
|
1409
|
+
styles: [
|
|
1410
|
+
"angry",
|
|
1411
|
+
"cheerful",
|
|
1412
|
+
"excited",
|
|
1413
|
+
"friendly",
|
|
1414
|
+
"hopeful",
|
|
1415
|
+
"newscast",
|
|
1416
|
+
"sad",
|
|
1417
|
+
"shouting",
|
|
1418
|
+
"terrified",
|
|
1419
|
+
"unfriendly",
|
|
1420
|
+
"whispering"
|
|
1421
|
+
]
|
|
1422
|
+
},
|
|
1423
|
+
{
|
|
1424
|
+
name: "en-US-JennyMultilingualNeural",
|
|
1425
|
+
locale: "en-US",
|
|
1426
|
+
styles: [
|
|
1427
|
+
"cheerful",
|
|
1428
|
+
"empathetic",
|
|
1429
|
+
"excited",
|
|
1430
|
+
"friendly",
|
|
1431
|
+
"hopeful",
|
|
1432
|
+
"sad",
|
|
1433
|
+
"shouting",
|
|
1434
|
+
"terrified",
|
|
1435
|
+
"unfriendly",
|
|
1436
|
+
"whispering"
|
|
1437
|
+
]
|
|
1438
|
+
},
|
|
1439
|
+
{
|
|
1440
|
+
name: "en-US-JennyNeural",
|
|
1441
|
+
locale: "en-US",
|
|
1442
|
+
styles: [
|
|
1443
|
+
"assistant",
|
|
1444
|
+
"chat",
|
|
1445
|
+
"customerservice",
|
|
1446
|
+
"newscast",
|
|
1447
|
+
"cheerful",
|
|
1448
|
+
"empathetic",
|
|
1449
|
+
"excited",
|
|
1450
|
+
"friendly",
|
|
1451
|
+
"hopeful",
|
|
1452
|
+
"sad",
|
|
1453
|
+
"shouting",
|
|
1454
|
+
"terrified",
|
|
1455
|
+
"unfriendly",
|
|
1456
|
+
"whispering"
|
|
1457
|
+
]
|
|
1458
|
+
},
|
|
1459
|
+
{ name: "es-ES-ElviraNeural", locale: "es-ES" },
|
|
1460
|
+
{ name: "fil-PH-AngeloNeural", locale: "fil-PH" },
|
|
1461
|
+
{ name: "fr-FR-DeniseNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
|
|
1462
|
+
{ name: "fr-FR-HenriNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
|
|
1463
|
+
{ name: "id-ID-GadisNeural", locale: "id-ID" },
|
|
1464
|
+
{ name: "it-IT-ElsaNeural", locale: "it-IT", styles: ["cheerful", "sad"] },
|
|
1465
|
+
{ name: "ja-JP-KeitaNeural", locale: "ja-JP", styles: ["chat"] },
|
|
1466
|
+
{ name: "ja-JP-MayuNeural", locale: "ja-JP", styles: ["calm", "cheerful", "sad"] },
|
|
1467
|
+
{ name: "ja-JP-NanamiNeural", locale: "ja-JP", styles: ["chat", "customerservice", "cheerful", "whispering", "sad"] },
|
|
1468
|
+
{ name: "ko-KR-SunHiNeural", locale: "ko-KR", styles: ["cheerful", "sad"] },
|
|
1469
|
+
{ name: "ms-MY-YasminNeural", locale: "ms-MY" },
|
|
1470
|
+
{ name: "pt-BR-FranciscaNeural", locale: "pt-BR", styles: ["calm"] },
|
|
1471
|
+
{
|
|
1472
|
+
name: "ru-RU-SvetlanaNeural",
|
|
1473
|
+
locale: "ru-RU",
|
|
1474
|
+
styles: ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"]
|
|
1475
|
+
},
|
|
1476
|
+
{ name: "th-TH-PremwadeeNeural", locale: "th-TH" },
|
|
1477
|
+
{ name: "vi-VN-HoaiMyNeural", locale: "vi-VN" },
|
|
1478
|
+
{
|
|
1479
|
+
name: "zh-CN-XiaoxiaoNeural",
|
|
1480
|
+
locale: "zh-CN",
|
|
1481
|
+
styles: [
|
|
1482
|
+
"assistant",
|
|
1483
|
+
"chat",
|
|
1484
|
+
"customerservice",
|
|
1485
|
+
"newscast",
|
|
1486
|
+
"cheerful",
|
|
1487
|
+
"empathetic",
|
|
1488
|
+
"excited",
|
|
1489
|
+
"friendly",
|
|
1490
|
+
"hopeful",
|
|
1491
|
+
"sad",
|
|
1492
|
+
"terrified",
|
|
1493
|
+
"whispering",
|
|
1494
|
+
"poetry-reading",
|
|
1495
|
+
"sports_commentary",
|
|
1496
|
+
"sports_commentary_excited",
|
|
1497
|
+
"story"
|
|
1498
|
+
]
|
|
1499
|
+
},
|
|
1500
|
+
{
|
|
1501
|
+
name: "zh-CN-YunxiNeural",
|
|
1502
|
+
locale: "zh-CN",
|
|
1503
|
+
styles: [
|
|
1504
|
+
"narration-relaxed",
|
|
1505
|
+
"embarrassed",
|
|
1506
|
+
"fearful",
|
|
1507
|
+
"sad",
|
|
1508
|
+
"disgruntled",
|
|
1509
|
+
"serious",
|
|
1510
|
+
"angry",
|
|
1511
|
+
"depressed",
|
|
1512
|
+
"chat",
|
|
1513
|
+
"cheerful",
|
|
1514
|
+
"assistant"
|
|
1515
|
+
]
|
|
1516
|
+
},
|
|
1517
|
+
{ name: "zh-TW-HsiaoChenNeural", locale: "zh-TW" }
|
|
1518
|
+
];
|
|
1519
|
+
|
|
1049
1520
|
// packages/ssml-core/src/azureValidation.ts
|
|
1050
|
-
var EXPRESS_AS_STYLES = {
|
|
1051
|
-
"en-us-jennyneural": [
|
|
1052
|
-
"assistant",
|
|
1053
|
-
"chat",
|
|
1054
|
-
"customerservice",
|
|
1055
|
-
"newscast",
|
|
1056
|
-
"cheerful",
|
|
1057
|
-
"empathetic",
|
|
1058
|
-
"excited",
|
|
1059
|
-
"friendly",
|
|
1060
|
-
"hopeful",
|
|
1061
|
-
"sad",
|
|
1062
|
-
"shouting",
|
|
1063
|
-
"terrified",
|
|
1064
|
-
"unfriendly",
|
|
1065
|
-
"whispering"
|
|
1066
|
-
],
|
|
1067
|
-
"en-us-guyneural": [
|
|
1068
|
-
"angry",
|
|
1069
|
-
"cheerful",
|
|
1070
|
-
"excited",
|
|
1071
|
-
"friendly",
|
|
1072
|
-
"hopeful",
|
|
1073
|
-
"newscast",
|
|
1074
|
-
"sad",
|
|
1075
|
-
"shouting",
|
|
1076
|
-
"terrified",
|
|
1077
|
-
"unfriendly",
|
|
1078
|
-
"whispering"
|
|
1079
|
-
],
|
|
1080
|
-
"en-us-jennymultilingualneural": [
|
|
1081
|
-
"cheerful",
|
|
1082
|
-
"empathetic",
|
|
1083
|
-
"excited",
|
|
1084
|
-
"friendly",
|
|
1085
|
-
"hopeful",
|
|
1086
|
-
"sad",
|
|
1087
|
-
"shouting",
|
|
1088
|
-
"terrified",
|
|
1089
|
-
"unfriendly",
|
|
1090
|
-
"whispering"
|
|
1091
|
-
],
|
|
1092
|
-
"en-us-andrewneural": ["empathetic", "relieved"],
|
|
1093
|
-
"ja-jp-mayuneural": ["calm", "cheerful", "sad"],
|
|
1094
|
-
"ja-jp-nanamineural": ["chat", "customerservice", "cheerful", "whispering", "sad"],
|
|
1095
|
-
"ja-jp-keitaneural": ["chat"],
|
|
1096
|
-
"ko-kr-sunhineural": ["cheerful", "sad"],
|
|
1097
|
-
"zh-cn-yunxineural": [
|
|
1098
|
-
"narration-relaxed",
|
|
1099
|
-
"embarrassed",
|
|
1100
|
-
"fearful",
|
|
1101
|
-
"sad",
|
|
1102
|
-
"disgruntled",
|
|
1103
|
-
"serious",
|
|
1104
|
-
"angry",
|
|
1105
|
-
"depressed",
|
|
1106
|
-
"chat",
|
|
1107
|
-
"cheerful",
|
|
1108
|
-
"assistant"
|
|
1109
|
-
],
|
|
1110
|
-
"zh-cn-xiaoxiaoneural": [
|
|
1111
|
-
"assistant",
|
|
1112
|
-
"chat",
|
|
1113
|
-
"customerservice",
|
|
1114
|
-
"newscast",
|
|
1115
|
-
"cheerful",
|
|
1116
|
-
"empathetic",
|
|
1117
|
-
"excited",
|
|
1118
|
-
"friendly",
|
|
1119
|
-
"hopeful",
|
|
1120
|
-
"sad",
|
|
1121
|
-
"terrified",
|
|
1122
|
-
"whispering",
|
|
1123
|
-
"poetry-reading",
|
|
1124
|
-
"sports_commentary",
|
|
1125
|
-
"sports_commentary_excited",
|
|
1126
|
-
"story"
|
|
1127
|
-
],
|
|
1128
|
-
"fr-fr-deniseneural": ["cheerful", "sad"],
|
|
1129
|
-
"fr-fr-henrineural": ["cheerful", "sad"],
|
|
1130
|
-
"pt-br-franciscaneural": ["calm"],
|
|
1131
|
-
"it-it-elsaneural": ["cheerful", "sad"],
|
|
1132
|
-
"de-de-katjaneural": ["cheerful", "sad"],
|
|
1133
|
-
"de-de-conradneural": ["cheerful", "sad"],
|
|
1134
|
-
"ru-ru-svetlananeural": ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"]
|
|
1135
|
-
};
|
|
1136
1521
|
var ALLOWED_BREAK_STRENGTHS = /* @__PURE__ */ new Set(["none", "x-weak", "weak", "medium", "strong", "x-strong"]);
|
|
1137
1522
|
var ALLOWED_SAY_AS = /* @__PURE__ */ new Set([
|
|
1138
1523
|
"characters",
|
|
@@ -1227,12 +1612,23 @@ function tokenizeElements(source) {
|
|
|
1227
1612
|
attributes.set(match[1].toLowerCase(), decodeAttribute(match[3]));
|
|
1228
1613
|
}
|
|
1229
1614
|
const selfClosing = /\/\s*>$/.test(raw);
|
|
1615
|
+
const parent = openElements[openElements.length - 1];
|
|
1230
1616
|
const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
|
|
1231
|
-
|
|
1617
|
+
const tokenName = nameMatch[1];
|
|
1618
|
+
const tokenVoiceName = tokenName.toLowerCase() === "voice" ? attributes.get("name") : tokenName.toLowerCase() === "mstts:turn" ? attributes.get("voice") ?? parentVoiceName : parentVoiceName;
|
|
1619
|
+
tokens.push({
|
|
1620
|
+
attributes,
|
|
1621
|
+
end,
|
|
1622
|
+
name: tokenName,
|
|
1623
|
+
parentName: parent?.name,
|
|
1624
|
+
parentVoiceName,
|
|
1625
|
+
selfClosing,
|
|
1626
|
+
start
|
|
1627
|
+
});
|
|
1232
1628
|
if (!selfClosing) {
|
|
1233
1629
|
openElements.push({
|
|
1234
|
-
name:
|
|
1235
|
-
voiceName:
|
|
1630
|
+
name: tokenName,
|
|
1631
|
+
voiceName: tokenVoiceName
|
|
1236
1632
|
});
|
|
1237
1633
|
}
|
|
1238
1634
|
index = end + 1;
|
|
@@ -1261,18 +1657,17 @@ function isSupportedProsodyRate(value) {
|
|
|
1261
1657
|
const numericValue = Number(multiplier[1]);
|
|
1262
1658
|
return numericValue >= 0.5 && numericValue <= 2;
|
|
1263
1659
|
}
|
|
1660
|
+
function isValidAzureAudioDuration(value) {
|
|
1661
|
+
const trimmed = value.trim();
|
|
1662
|
+
const numeric = /^(\d+(?:\.\d+)?)(ms|s)$/.exec(trimmed);
|
|
1663
|
+
if (numeric) return Number(numeric[1]) > 0;
|
|
1664
|
+
const clock = /^(\d{2,}):([0-5]\d):([0-5]\d)(?:\.(\d{1,3}))?$/.exec(trimmed);
|
|
1665
|
+
if (!clock) return false;
|
|
1666
|
+
return Number(clock[1]) > 0 || Number(clock[2]) > 0 || Number(clock[3]) > 0 || Number(clock[4] ?? 0) > 0;
|
|
1667
|
+
}
|
|
1264
1668
|
function attr(token, name) {
|
|
1265
1669
|
return token.attributes.get(name.toLowerCase());
|
|
1266
1670
|
}
|
|
1267
|
-
var ADDITIONAL_VOICE_DEFINITIONS = [
|
|
1268
|
-
{ name: "zh-TW-HsiaoChenNeural", locale: "zh-TW" },
|
|
1269
|
-
{ name: "es-ES-ElviraNeural", locale: "es-ES" },
|
|
1270
|
-
{ name: "th-TH-PremwadeeNeural", locale: "th-TH" },
|
|
1271
|
-
{ name: "fil-PH-AngeloNeural", locale: "fil-PH" },
|
|
1272
|
-
{ name: "vi-VN-HoaiMyNeural", locale: "vi-VN" },
|
|
1273
|
-
{ name: "id-ID-GadisNeural", locale: "id-ID" },
|
|
1274
|
-
{ name: "ms-MY-YasminNeural", locale: "ms-MY" }
|
|
1275
|
-
];
|
|
1276
1671
|
var DEFAULT_LANGUAGE_ALIASES = {
|
|
1277
1672
|
"zh-CN": ["zh-Hans"],
|
|
1278
1673
|
"zh-TW": ["zh-Hant"]
|
|
@@ -1335,10 +1730,7 @@ function definitionFromStyleMap(voiceName, styles) {
|
|
|
1335
1730
|
}
|
|
1336
1731
|
function normalizeVoiceCatalog(options) {
|
|
1337
1732
|
const definitions = /* @__PURE__ */ new Map();
|
|
1338
|
-
for (const
|
|
1339
|
-
definitions.set(name.toLowerCase(), definitionFromStyleMap(name, styles));
|
|
1340
|
-
}
|
|
1341
|
-
for (const definition of ADDITIONAL_VOICE_DEFINITIONS) definitions.set(definition.name.toLowerCase(), definition);
|
|
1733
|
+
for (const definition of AZURE_VOICE_DEFINITIONS) definitions.set(definition.name.toLowerCase(), definition);
|
|
1342
1734
|
for (const definition of options.voiceCatalog ?? []) definitions.set(definition.name.toLowerCase(), definition);
|
|
1343
1735
|
for (const definition of options.voiceDefinitions ?? []) definitions.set(definition.name.toLowerCase(), definition);
|
|
1344
1736
|
for (const definition of options.customVoiceDefinitions ?? [])
|
|
@@ -1374,6 +1766,54 @@ function definitionMatchesLanguage(definition, voiceName, language, normalizeLan
|
|
|
1374
1766
|
if (!normalizedLanguage || !normalizedCandidates.some(Boolean)) return void 0;
|
|
1375
1767
|
return normalizedLanguage === languagePart(normalizedLanguage) ? normalizedCandidates.some((candidate) => languagePart(candidate) === normalizedLanguage) : false;
|
|
1376
1768
|
}
|
|
1769
|
+
function canonicalTagName(name) {
|
|
1770
|
+
const normalized = name.toLowerCase();
|
|
1771
|
+
if (normalized === "express-as" || normalized === "expressas") return "mstts:express-as";
|
|
1772
|
+
if (normalized === "sayas") return "say-as";
|
|
1773
|
+
return normalized;
|
|
1774
|
+
}
|
|
1775
|
+
function validateVoiceFeatureMatrix(token, source, diagnostics, voiceName, definition) {
|
|
1776
|
+
if (!voiceName || !definition || token.name.toLowerCase() === "voice" || token.name.toLowerCase() === "mstts:turn")
|
|
1777
|
+
return;
|
|
1778
|
+
const tagName = canonicalTagName(token.name);
|
|
1779
|
+
const unsupportedTags = new Set((definition.unsupportedTags ?? []).map(canonicalTagName));
|
|
1780
|
+
const supportedTags = definition.supportedTags?.map(canonicalTagName);
|
|
1781
|
+
if (unsupportedTags.has(tagName) || supportedTags !== void 0 && !supportedTags.includes(tagName)) {
|
|
1782
|
+
addDiagnostic(
|
|
1783
|
+
diagnostics,
|
|
1784
|
+
source,
|
|
1785
|
+
token.start,
|
|
1786
|
+
`Tag <${token.name}> is not supported by voice "${voiceName}" according to the configured feature matrix.`,
|
|
1787
|
+
"error",
|
|
1788
|
+
"azure-unsupported-tag-for-voice"
|
|
1789
|
+
);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
function validateAudioSource(token, source, diagnostics, options, elementName2) {
|
|
1793
|
+
const src = attr(token, "src");
|
|
1794
|
+
if (!src) {
|
|
1795
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2}> requires a "src" attribute.`);
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1798
|
+
let parsed;
|
|
1799
|
+
try {
|
|
1800
|
+
parsed = new URL(src);
|
|
1801
|
+
} catch {
|
|
1802
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must be an absolute HTTP(S) URL.`);
|
|
1803
|
+
return;
|
|
1804
|
+
}
|
|
1805
|
+
if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
|
|
1806
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must use HTTPS.`);
|
|
1807
|
+
if (options.allowedAudioOrigins && !options.allowedAudioOrigins.includes(parsed.origin))
|
|
1808
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> origin "${parsed.origin}" is not allowed.`);
|
|
1809
|
+
else if (!options.allowExternalAudio)
|
|
1810
|
+
addDiagnostic(
|
|
1811
|
+
diagnostics,
|
|
1812
|
+
source,
|
|
1813
|
+
token.start,
|
|
1814
|
+
`<${elementName2} src> external origin "${parsed.origin}" is blocked by default; set allowExternalAudio to true or provide allowedAudioOrigins.`
|
|
1815
|
+
);
|
|
1816
|
+
}
|
|
1377
1817
|
function validateElement(token, source, diagnostics, voiceName, options, voiceCatalog) {
|
|
1378
1818
|
const name = token.name.toLowerCase();
|
|
1379
1819
|
if (name === "voice" && !attr(token, "name")?.trim())
|
|
@@ -1475,35 +1915,51 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceCa
|
|
|
1475
1915
|
if (!value || !/^\d+(?:\.\d+)?(?:ms|s)$/.test(value.trim()))
|
|
1476
1916
|
addDiagnostic(diagnostics, source, token.start, '<mstts:silence> requires a time-valued "value" attribute.');
|
|
1477
1917
|
}
|
|
1918
|
+
if (name === "mstts:audioduration") {
|
|
1919
|
+
const value = attr(token, "value");
|
|
1920
|
+
if (!value || !isValidAzureAudioDuration(value))
|
|
1921
|
+
addDiagnostic(
|
|
1922
|
+
diagnostics,
|
|
1923
|
+
source,
|
|
1924
|
+
token.start,
|
|
1925
|
+
'<mstts:audioduration> requires a positive duration such as "10s", "5000ms", or "00:00:10".'
|
|
1926
|
+
);
|
|
1927
|
+
if (!token.selfClosing)
|
|
1928
|
+
addDiagnostic(diagnostics, source, token.start, "<mstts:audioduration> must be self-closing.");
|
|
1929
|
+
}
|
|
1478
1930
|
if (name === "mstts:viseme") {
|
|
1479
1931
|
const type = attr(token, "type");
|
|
1480
1932
|
if (!type || !ALLOWED_VISEME_TYPES.has(type))
|
|
1481
1933
|
addDiagnostic(diagnostics, source, token.start, '<mstts:viseme> requires a supported "type" attribute.');
|
|
1482
1934
|
}
|
|
1483
1935
|
if (name === "audio") {
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1936
|
+
validateAudioSource(token, source, diagnostics, options, "audio");
|
|
1937
|
+
}
|
|
1938
|
+
if (name === "mstts:turn") {
|
|
1939
|
+
if (!attr(token, "voice")?.trim())
|
|
1940
|
+
addDiagnostic(diagnostics, source, token.start, '<mstts:turn> requires a non-empty "voice" attribute.');
|
|
1941
|
+
if (token.parentName?.toLowerCase() !== "mstts:dialog")
|
|
1942
|
+
addDiagnostic(diagnostics, source, token.start, "<mstts:turn> is only allowed directly inside <mstts:dialog>.");
|
|
1943
|
+
}
|
|
1944
|
+
if (name === "mstts:backgroundaudio") {
|
|
1945
|
+
validateAudioSource(token, source, diagnostics, options, "mstts:backgroundaudio");
|
|
1946
|
+
const volume = attr(token, "volume");
|
|
1947
|
+
if (volume && !/^(silent|x-soft|soft|medium|loud|x-loud|[+-]?\d+(?:\.\d+)?(?:dB|%))$/i.test(volume.trim()))
|
|
1948
|
+
addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:backgroundaudio volume> value "${volume}".`);
|
|
1949
|
+
for (const [attribute, value] of [
|
|
1950
|
+
["fadein", attr(token, "fadein")],
|
|
1951
|
+
["fadeout", attr(token, "fadeout")]
|
|
1952
|
+
]) {
|
|
1953
|
+
if (value && !isValidAzureAudioDuration(value))
|
|
1499
1954
|
addDiagnostic(
|
|
1500
1955
|
diagnostics,
|
|
1501
1956
|
source,
|
|
1502
1957
|
token.start,
|
|
1503
|
-
`<
|
|
1504
|
-
"error"
|
|
1958
|
+
`<mstts:backgroundaudio ${attribute}> must be a positive duration such as "500ms" or "10s".`
|
|
1505
1959
|
);
|
|
1506
1960
|
}
|
|
1961
|
+
if (!token.selfClosing)
|
|
1962
|
+
addDiagnostic(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
|
|
1507
1963
|
}
|
|
1508
1964
|
}
|
|
1509
1965
|
function validateAzureSsml(ssml, options = {}) {
|
|
@@ -1569,12 +2025,42 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
1569
2025
|
);
|
|
1570
2026
|
}
|
|
1571
2027
|
for (const token of tokens) {
|
|
1572
|
-
const
|
|
2028
|
+
const tokenName = token.name.toLowerCase();
|
|
2029
|
+
const tokenVoiceName = tokenName === "voice" ? attr(token, "name")?.trim() : tokenName === "mstts:turn" ? attr(token, "voice")?.trim() || token.parentVoiceName : options.validateNestedVoices === false ? voiceName : token.parentVoiceName;
|
|
1573
2030
|
validateElement(token, ssml, diagnostics, tokenVoiceName, options, voiceCatalog);
|
|
2031
|
+
const definition = tokenVoiceName ? voiceCatalog.get(tokenVoiceName.toLowerCase()) : void 0;
|
|
2032
|
+
validateVoiceFeatureMatrix(token, ssml, diagnostics, tokenVoiceName, definition);
|
|
2033
|
+
if (tokenName === "voice" && options.model && definition?.models && !definition.models.some((model) => model.toLowerCase() === options.model?.toLowerCase())) {
|
|
2034
|
+
addDiagnostic(
|
|
2035
|
+
diagnostics,
|
|
2036
|
+
ssml,
|
|
2037
|
+
token.start,
|
|
2038
|
+
`Voice "${tokenVoiceName}" does not support model "${options.model}" according to the configured feature matrix.`,
|
|
2039
|
+
"error",
|
|
2040
|
+
"azure-unsupported-model-for-voice"
|
|
2041
|
+
);
|
|
2042
|
+
}
|
|
1574
2043
|
}
|
|
1575
2044
|
return diagnostics;
|
|
1576
2045
|
}
|
|
1577
2046
|
|
|
2047
|
+
// packages/ssml-core/src/generated/azureVoiceCatalog.ts
|
|
2048
|
+
var AZURE_VOICE_CATALOG_METADATA = {
|
|
2049
|
+
apiVersion: "2025-10-01",
|
|
2050
|
+
generatedAt: "2026-08-28T00:00:00.000Z",
|
|
2051
|
+
regions: [],
|
|
2052
|
+
voiceCount: AZURE_VOICE_DEFINITIONS.length
|
|
2053
|
+
};
|
|
2054
|
+
|
|
2055
|
+
// packages/ssml-core/src/voiceCatalog.ts
|
|
2056
|
+
function getAzureVoiceCatalogMetadata() {
|
|
2057
|
+
return {
|
|
2058
|
+
...AZURE_VOICE_CATALOG_METADATA,
|
|
2059
|
+
regions: [...AZURE_VOICE_CATALOG_METADATA.regions]
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
var getBuiltInVoiceCatalogMetadata = getAzureVoiceCatalogMetadata;
|
|
2063
|
+
|
|
1578
2064
|
// packages/azure-tts-client/src/errors.ts
|
|
1579
2065
|
var AzureTtsError = class extends Error {
|
|
1580
2066
|
constructor(status, statusText, responseBody, requestId) {
|
|
@@ -1765,11 +2251,17 @@ _options = new WeakMap();
|
|
|
1765
2251
|
buildPartialSsml,
|
|
1766
2252
|
buildSsml,
|
|
1767
2253
|
extractSsmlText,
|
|
2254
|
+
extractSsmlTranslatableText,
|
|
2255
|
+
fromPlainTextToSsml,
|
|
2256
|
+
getAzureVoiceCatalogMetadata,
|
|
2257
|
+
getBuiltInVoiceCatalogMetadata,
|
|
2258
|
+
isValidAzureAudioDuration,
|
|
1768
2259
|
mapSsmlTextNodes,
|
|
1769
2260
|
normalizeAzureLanguage,
|
|
1770
2261
|
parseSsml,
|
|
1771
2262
|
synthesizeSpeech,
|
|
1772
2263
|
validateAzureSsml,
|
|
1773
|
-
validateSsml
|
|
2264
|
+
validateSsml,
|
|
2265
|
+
validateSsmlStructureIntegrity
|
|
1774
2266
|
});
|
|
1775
2267
|
//# sourceMappingURL=index.js.map
|