ssml-builder-js 2.8.1 → 2.10.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 +88 -12
- package/dist/{chunk-MWSDFGXW.mjs → chunk-BYIZQL2W.mjs} +854 -9
- package/dist/chunk-BYIZQL2W.mjs.map +1 -0
- package/dist/{chunk-VCDMKVVT.mjs → chunk-GW6CKHXF.mjs} +539 -25
- package/dist/chunk-GW6CKHXF.mjs.map +1 -0
- package/dist/core.d.mts +98 -3
- package/dist/core.d.ts +98 -3
- package/dist/core.js +544 -25
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +13 -3
- package/dist/elements.d.mts +10 -1
- package/dist/elements.d.ts +10 -1
- package/dist/elements.js +1140 -13
- package/dist/elements.js.map +1 -1
- package/dist/elements.mjs +292 -8
- package/dist/elements.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +544 -25
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +13 -3
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +47 -3
- package/dist/react.d.ts +47 -3
- package/dist/react.js +1095 -19
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +237 -5
- package/dist/react.mjs.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-MWSDFGXW.mjs.map +0 -1
- package/dist/chunk-VCDMKVVT.mjs.map +0 -1
package/dist/core.js
CHANGED
|
@@ -31,12 +31,17 @@ __export(core_exports, {
|
|
|
31
31
|
buildPartialSsml: () => buildPartialSsml,
|
|
32
32
|
buildSsml: () => buildSsml,
|
|
33
33
|
extractSsmlText: () => extractSsmlText,
|
|
34
|
+
extractSsmlTranslatableText: () => extractSsmlTranslatableText,
|
|
35
|
+
fromPlainTextToSsml: () => fromPlainTextToSsml,
|
|
36
|
+
getAzureVoiceCatalogMetadata: () => getAzureVoiceCatalogMetadata,
|
|
37
|
+
getBuiltInVoiceCatalogMetadata: () => getBuiltInVoiceCatalogMetadata,
|
|
34
38
|
isValidAzureAudioDuration: () => isValidAzureAudioDuration,
|
|
35
39
|
mapSsmlTextNodes: () => mapSsmlTextNodes,
|
|
36
40
|
normalizeAzureLanguage: () => normalizeAzureLanguage,
|
|
37
41
|
parseSsml: () => parseSsml,
|
|
38
42
|
validateAzureSsml: () => validateAzureSsml,
|
|
39
|
-
validateSsml: () => validateSsml
|
|
43
|
+
validateSsml: () => validateSsml,
|
|
44
|
+
validateSsmlStructureIntegrity: () => validateSsmlStructureIntegrity
|
|
40
45
|
});
|
|
41
46
|
module.exports = __toCommonJS(core_exports);
|
|
42
47
|
|
|
@@ -73,7 +78,13 @@ var SSML_TAGS = {
|
|
|
73
78
|
SILENCE: "silence",
|
|
74
79
|
MSTTS_VISEME: "mstts:viseme",
|
|
75
80
|
VISEME: "viseme",
|
|
76
|
-
MSTTS_AUDIO_DURATION: "mstts:audioduration"
|
|
81
|
+
MSTTS_AUDIO_DURATION: "mstts:audioduration",
|
|
82
|
+
MSTTS_DIALOG: "mstts:dialog",
|
|
83
|
+
MSTTS_TURN: "mstts:turn",
|
|
84
|
+
MSTTS_BACKGROUND_AUDIO: "mstts:backgroundaudio",
|
|
85
|
+
MSTTS_TTS_EMBEDDING: "mstts:ttsembedding",
|
|
86
|
+
MSTTS_EMBEDDING: "mstts:embedding",
|
|
87
|
+
MSTTS_VOICE_CONVERSION: "mstts:voiceconversion"
|
|
77
88
|
};
|
|
78
89
|
var SSML_ATTRS = {
|
|
79
90
|
VERSION: "version",
|
|
@@ -82,6 +93,8 @@ var SSML_ATTRS = {
|
|
|
82
93
|
LANG: "lang",
|
|
83
94
|
MSTTS_XMLNS: "xmlns:mstts",
|
|
84
95
|
NAME: "name",
|
|
96
|
+
VOICE: "voice",
|
|
97
|
+
SPEAKER: "speaker",
|
|
85
98
|
EFFECT: "effect",
|
|
86
99
|
RATE: "rate",
|
|
87
100
|
PITCH: "pitch",
|
|
@@ -112,8 +125,15 @@ var SSML_ATTRS = {
|
|
|
112
125
|
ALIAS: "alias",
|
|
113
126
|
MARK: "mark",
|
|
114
127
|
URI: "uri",
|
|
128
|
+
ID: "id",
|
|
129
|
+
MODEL: "model",
|
|
130
|
+
PROFILE: "profile",
|
|
131
|
+
URL: "url",
|
|
132
|
+
SPEAKER_PROFILE_ID: "speakerProfileId",
|
|
115
133
|
TYPE: "type",
|
|
116
|
-
VALUE: "value"
|
|
134
|
+
VALUE: "value",
|
|
135
|
+
FADE_IN: "fadein",
|
|
136
|
+
FADE_OUT: "fadeout"
|
|
117
137
|
};
|
|
118
138
|
|
|
119
139
|
// packages/ssml-core/src/builder.ts
|
|
@@ -205,6 +225,30 @@ function getAttributes(element) {
|
|
|
205
225
|
case SSML_TAGS.MSTTS_AUDIO_DURATION:
|
|
206
226
|
addAttribute(attributes, SSML_ATTRS.VALUE, element.value);
|
|
207
227
|
break;
|
|
228
|
+
case SSML_TAGS.MSTTS_TURN:
|
|
229
|
+
addAttribute(attributes, SSML_ATTRS.VOICE, element.voice);
|
|
230
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER, element.speaker);
|
|
231
|
+
break;
|
|
232
|
+
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO:
|
|
233
|
+
addAttribute(attributes, SSML_ATTRS.SRC, element.src);
|
|
234
|
+
addAttribute(attributes, SSML_ATTRS.VOLUME, element.volume);
|
|
235
|
+
addAttribute(attributes, SSML_ATTRS.FADE_IN, element.fadeIn ?? element.fadein);
|
|
236
|
+
addAttribute(attributes, SSML_ATTRS.FADE_OUT, element.fadeOut ?? element.fadeout);
|
|
237
|
+
break;
|
|
238
|
+
case SSML_TAGS.MSTTS_DIALOG:
|
|
239
|
+
break;
|
|
240
|
+
case SSML_TAGS.MSTTS_TTS_EMBEDDING:
|
|
241
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
|
|
242
|
+
break;
|
|
243
|
+
case SSML_TAGS.MSTTS_EMBEDDING:
|
|
244
|
+
addAttribute(attributes, SSML_ATTRS.ID, element.id);
|
|
245
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
|
|
246
|
+
break;
|
|
247
|
+
case SSML_TAGS.MSTTS_VOICE_CONVERSION:
|
|
248
|
+
addAttribute(attributes, SSML_ATTRS.URL, element.url);
|
|
249
|
+
addAttribute(attributes, SSML_ATTRS.PROFILE, element.profile);
|
|
250
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
|
|
251
|
+
break;
|
|
208
252
|
case SSML_TAGS.PARAGRAPH:
|
|
209
253
|
case SSML_TAGS.SENTENCE:
|
|
210
254
|
case SSML_TAGS.WORD:
|
|
@@ -771,6 +815,54 @@ function convertElement(node) {
|
|
|
771
815
|
if (value !== void 0) element.value = value;
|
|
772
816
|
return finishElement(element, node, attributes);
|
|
773
817
|
}
|
|
818
|
+
case SSML_TAGS.MSTTS_DIALOG: {
|
|
819
|
+
const element = { type: SSML_TAGS.MSTTS_DIALOG };
|
|
820
|
+
return finishElement(element, node, attributes);
|
|
821
|
+
}
|
|
822
|
+
case SSML_TAGS.MSTTS_TURN: {
|
|
823
|
+
const element = { type: SSML_TAGS.MSTTS_TURN };
|
|
824
|
+
const voice = readAttribute(attributes, SSML_ATTRS.VOICE);
|
|
825
|
+
const speaker = readAttribute(attributes, SSML_ATTRS.SPEAKER);
|
|
826
|
+
if (voice !== void 0) element.voice = voice;
|
|
827
|
+
if (speaker !== void 0) element.speaker = speaker;
|
|
828
|
+
return finishElement(element, node, attributes);
|
|
829
|
+
}
|
|
830
|
+
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO: {
|
|
831
|
+
const element = { type: SSML_TAGS.MSTTS_BACKGROUND_AUDIO };
|
|
832
|
+
const src = readAttribute(attributes, SSML_ATTRS.SRC);
|
|
833
|
+
const volume = readAttribute(attributes, SSML_ATTRS.VOLUME);
|
|
834
|
+
const fadeIn = readAttribute(attributes, SSML_ATTRS.FADE_IN);
|
|
835
|
+
const fadeOut = readAttribute(attributes, SSML_ATTRS.FADE_OUT);
|
|
836
|
+
if (src !== void 0) element.src = src;
|
|
837
|
+
if (volume !== void 0) element.volume = volume;
|
|
838
|
+
if (fadeIn !== void 0) element.fadeIn = fadeIn;
|
|
839
|
+
if (fadeOut !== void 0) element.fadeOut = fadeOut;
|
|
840
|
+
return finishElement(element, node, attributes);
|
|
841
|
+
}
|
|
842
|
+
case SSML_TAGS.MSTTS_TTS_EMBEDDING: {
|
|
843
|
+
const element = { type: SSML_TAGS.MSTTS_TTS_EMBEDDING };
|
|
844
|
+
const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
|
|
845
|
+
if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
|
|
846
|
+
return finishElement(element, node, attributes);
|
|
847
|
+
}
|
|
848
|
+
case SSML_TAGS.MSTTS_EMBEDDING: {
|
|
849
|
+
const element = { type: SSML_TAGS.MSTTS_EMBEDDING };
|
|
850
|
+
const id = readAttribute(attributes, SSML_ATTRS.ID);
|
|
851
|
+
const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
|
|
852
|
+
if (id !== void 0) element.id = id;
|
|
853
|
+
if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
|
|
854
|
+
return finishElement(element, node, attributes);
|
|
855
|
+
}
|
|
856
|
+
case SSML_TAGS.MSTTS_VOICE_CONVERSION: {
|
|
857
|
+
const element = { type: SSML_TAGS.MSTTS_VOICE_CONVERSION };
|
|
858
|
+
const url = readAttribute(attributes, SSML_ATTRS.URL);
|
|
859
|
+
const profile = readAttribute(attributes, SSML_ATTRS.PROFILE);
|
|
860
|
+
const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
|
|
861
|
+
if (url !== void 0) element.url = url;
|
|
862
|
+
if (profile !== void 0) element.profile = profile;
|
|
863
|
+
if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
|
|
864
|
+
return finishElement(element, node, attributes);
|
|
865
|
+
}
|
|
774
866
|
default: {
|
|
775
867
|
const element = {
|
|
776
868
|
name: node.name,
|
|
@@ -1045,6 +1137,296 @@ async function mapSsmlTextNodes(ssml, transform, options = {}) {
|
|
|
1045
1137
|
return result + ssml.slice(cursor);
|
|
1046
1138
|
}
|
|
1047
1139
|
|
|
1140
|
+
// packages/ssml-core/src/migration.ts
|
|
1141
|
+
var DEFAULT_TRANSLATION_SKIP_TAGS = ["phoneme", "say-as", "sayAs", "sub"];
|
|
1142
|
+
function elementName(element) {
|
|
1143
|
+
switch (element.type) {
|
|
1144
|
+
case "custom":
|
|
1145
|
+
case "element":
|
|
1146
|
+
return element.name;
|
|
1147
|
+
case "expressAs":
|
|
1148
|
+
return "mstts:express-as";
|
|
1149
|
+
case "sayAs":
|
|
1150
|
+
return "say-as";
|
|
1151
|
+
case "silence":
|
|
1152
|
+
return "mstts:silence";
|
|
1153
|
+
case "viseme":
|
|
1154
|
+
return "mstts:viseme";
|
|
1155
|
+
default:
|
|
1156
|
+
return element.type;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
function addAttribute2(attributes, name, value) {
|
|
1160
|
+
if (value !== void 0) attributes[name] = value;
|
|
1161
|
+
}
|
|
1162
|
+
function elementAttributes(element) {
|
|
1163
|
+
const attributes = { ...element.attributes ?? {} };
|
|
1164
|
+
switch (element.type) {
|
|
1165
|
+
case "voice":
|
|
1166
|
+
addAttribute2(attributes, "name", element.name);
|
|
1167
|
+
addAttribute2(attributes, "effect", element.effect);
|
|
1168
|
+
break;
|
|
1169
|
+
case "prosody":
|
|
1170
|
+
addAttribute2(attributes, "rate", element.rate);
|
|
1171
|
+
addAttribute2(attributes, "pitch", element.pitch);
|
|
1172
|
+
addAttribute2(attributes, "volume", element.volume);
|
|
1173
|
+
addAttribute2(attributes, "contour", element.contour);
|
|
1174
|
+
addAttribute2(attributes, "range", element.range);
|
|
1175
|
+
break;
|
|
1176
|
+
case "break":
|
|
1177
|
+
addAttribute2(attributes, "time", element.time);
|
|
1178
|
+
addAttribute2(attributes, "strength", element.strength);
|
|
1179
|
+
break;
|
|
1180
|
+
case "express-as":
|
|
1181
|
+
case "expressAs":
|
|
1182
|
+
case "mstts:express-as":
|
|
1183
|
+
addAttribute2(attributes, "style", element.style);
|
|
1184
|
+
addAttribute2(attributes, "styledegree", element.styleDegree);
|
|
1185
|
+
addAttribute2(attributes, "role", element.role);
|
|
1186
|
+
break;
|
|
1187
|
+
case "say-as":
|
|
1188
|
+
case "sayAs":
|
|
1189
|
+
addAttribute2(attributes, "interpret-as", element.interpretAs);
|
|
1190
|
+
addAttribute2(attributes, "format", element.format);
|
|
1191
|
+
addAttribute2(attributes, "detail", element.detail);
|
|
1192
|
+
break;
|
|
1193
|
+
case "phoneme":
|
|
1194
|
+
addAttribute2(attributes, "alphabet", element.alphabet);
|
|
1195
|
+
addAttribute2(attributes, "ph", element.ph);
|
|
1196
|
+
break;
|
|
1197
|
+
case "emphasis":
|
|
1198
|
+
addAttribute2(attributes, "level", element.level);
|
|
1199
|
+
break;
|
|
1200
|
+
case "audio":
|
|
1201
|
+
addAttribute2(attributes, "src", element.src);
|
|
1202
|
+
addAttribute2(attributes, "desc", element.desc);
|
|
1203
|
+
addAttribute2(attributes, "clipBegin", element.clipBegin);
|
|
1204
|
+
addAttribute2(attributes, "clipEnd", element.clipEnd);
|
|
1205
|
+
addAttribute2(attributes, "speed", element.speed);
|
|
1206
|
+
addAttribute2(attributes, "repeatCount", element.repeatCount);
|
|
1207
|
+
addAttribute2(attributes, "repeatDuration", element.repeatDuration);
|
|
1208
|
+
addAttribute2(attributes, "soundLevel", element.soundLevel);
|
|
1209
|
+
break;
|
|
1210
|
+
case "sub":
|
|
1211
|
+
addAttribute2(attributes, "alias", element.alias);
|
|
1212
|
+
break;
|
|
1213
|
+
case "lang":
|
|
1214
|
+
addAttribute2(attributes, "xml:lang", element.lang);
|
|
1215
|
+
break;
|
|
1216
|
+
case "mark":
|
|
1217
|
+
addAttribute2(attributes, "name", element.name);
|
|
1218
|
+
break;
|
|
1219
|
+
case "bookmark":
|
|
1220
|
+
addAttribute2(attributes, "mark", element.mark);
|
|
1221
|
+
break;
|
|
1222
|
+
case "lexicon":
|
|
1223
|
+
addAttribute2(attributes, "uri", element.uri);
|
|
1224
|
+
break;
|
|
1225
|
+
case "mstts:silence":
|
|
1226
|
+
case "silence":
|
|
1227
|
+
addAttribute2(attributes, "type", element.typeValue ?? element.silenceType);
|
|
1228
|
+
addAttribute2(attributes, "value", element.value);
|
|
1229
|
+
break;
|
|
1230
|
+
case "mstts:viseme":
|
|
1231
|
+
case "viseme":
|
|
1232
|
+
addAttribute2(attributes, "type", element.typeValue ?? element.visemeType);
|
|
1233
|
+
break;
|
|
1234
|
+
case "mstts:audioduration":
|
|
1235
|
+
addAttribute2(attributes, "value", element.value);
|
|
1236
|
+
break;
|
|
1237
|
+
case "mstts:turn":
|
|
1238
|
+
addAttribute2(attributes, "voice", element.voice);
|
|
1239
|
+
addAttribute2(attributes, "speaker", element.speaker);
|
|
1240
|
+
break;
|
|
1241
|
+
case "mstts:backgroundaudio":
|
|
1242
|
+
addAttribute2(attributes, "src", element.src);
|
|
1243
|
+
addAttribute2(attributes, "volume", element.volume);
|
|
1244
|
+
addAttribute2(attributes, "fadein", element.fadeIn ?? element.fadein);
|
|
1245
|
+
addAttribute2(attributes, "fadeout", element.fadeOut ?? element.fadeout);
|
|
1246
|
+
break;
|
|
1247
|
+
case "mstts:ttsembedding":
|
|
1248
|
+
addAttribute2(attributes, "speakerProfileId", element.speakerProfileId);
|
|
1249
|
+
break;
|
|
1250
|
+
case "mstts:embedding":
|
|
1251
|
+
addAttribute2(attributes, "id", element.id);
|
|
1252
|
+
addAttribute2(attributes, "speakerProfileId", element.speakerProfileId);
|
|
1253
|
+
break;
|
|
1254
|
+
case "mstts:voiceconversion":
|
|
1255
|
+
addAttribute2(attributes, "url", element.url);
|
|
1256
|
+
addAttribute2(attributes, "profile", element.profile);
|
|
1257
|
+
addAttribute2(attributes, "speakerProfileId", element.speakerProfileId);
|
|
1258
|
+
break;
|
|
1259
|
+
}
|
|
1260
|
+
return Object.fromEntries(Object.entries(attributes).map(([name, value]) => [name, String(value)]));
|
|
1261
|
+
}
|
|
1262
|
+
function childrenOf(node) {
|
|
1263
|
+
return node.children ?? [];
|
|
1264
|
+
}
|
|
1265
|
+
function extractSsmlTranslatableText(ssml, options = {}) {
|
|
1266
|
+
const document = parseSsml(ssml);
|
|
1267
|
+
const skipTags = new Set((options.skipTags ?? DEFAULT_TRANSLATION_SKIP_TAGS).map((tag) => tag.toLowerCase()));
|
|
1268
|
+
const result = [];
|
|
1269
|
+
const visit = (nodes, ancestors, path) => {
|
|
1270
|
+
nodes.forEach((node, index) => {
|
|
1271
|
+
if (typeof node === "string") {
|
|
1272
|
+
if (options.includeWhitespace || node.trim().length > 0) {
|
|
1273
|
+
const context = {
|
|
1274
|
+
ancestorTags: [...ancestors],
|
|
1275
|
+
parentAttributes: {},
|
|
1276
|
+
parentTag: ancestors[ancestors.length - 1] ?? "",
|
|
1277
|
+
path: [...path, String(index)]
|
|
1278
|
+
};
|
|
1279
|
+
if (options.filter?.(context) ?? true) result.push(node);
|
|
1280
|
+
}
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
if (node.type === "text") {
|
|
1284
|
+
if (options.includeWhitespace || node.value.trim().length > 0) {
|
|
1285
|
+
const context = {
|
|
1286
|
+
ancestorTags: [...ancestors],
|
|
1287
|
+
parentAttributes: {},
|
|
1288
|
+
parentTag: ancestors[ancestors.length - 1] ?? "",
|
|
1289
|
+
path: [...path, String(index)]
|
|
1290
|
+
};
|
|
1291
|
+
if (options.filter?.(context) ?? true) result.push(node.value);
|
|
1292
|
+
}
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
const tag = elementName(node);
|
|
1296
|
+
if (skipTags.has(tag.toLowerCase())) return;
|
|
1297
|
+
visit(childrenOf(node), [...ancestors, tag], [...path, String(index)]);
|
|
1298
|
+
});
|
|
1299
|
+
};
|
|
1300
|
+
visit(childrenOf(document), ["speak"], []);
|
|
1301
|
+
return result;
|
|
1302
|
+
}
|
|
1303
|
+
function splitSentences(text) {
|
|
1304
|
+
const sentences = [];
|
|
1305
|
+
let start = 0;
|
|
1306
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
1307
|
+
const character = text[index];
|
|
1308
|
+
const isTerminator = "\u3002\uFF01\uFF1F!?".includes(character) || character === "." && /\s|$/.test(text[index + 1] ?? "");
|
|
1309
|
+
if (isTerminator) {
|
|
1310
|
+
const value = text.slice(start, index + 1).trim();
|
|
1311
|
+
if (value) sentences.push(value);
|
|
1312
|
+
start = index + 1;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
const tail = text.slice(start).trim();
|
|
1316
|
+
if (tail) sentences.push(tail);
|
|
1317
|
+
return sentences;
|
|
1318
|
+
}
|
|
1319
|
+
function fromPlainTextToSsml(text, options = {}) {
|
|
1320
|
+
if (typeof text !== "string") throw new TypeError("Plain text must be a string");
|
|
1321
|
+
const paragraphs = text.replace(/\r\n?/g, "\n").split(/\n\s*\n/).map((paragraph) => paragraph.replace(/\s*\n\s*/g, " ").trim()).filter(Boolean);
|
|
1322
|
+
const useSentences = options.splitSentences ?? options.includeSentences ?? true;
|
|
1323
|
+
const paragraphNodes = paragraphs.map((paragraph) => ({
|
|
1324
|
+
type: "p",
|
|
1325
|
+
children: useSentences ? splitSentences(paragraph).map((sentence) => ({ type: "s", children: [sentence] })) : [paragraph]
|
|
1326
|
+
}));
|
|
1327
|
+
const voiceName = options.voice ?? options.voiceName;
|
|
1328
|
+
const children = voiceName ? [{ type: "voice", name: voiceName, children: paragraphNodes }] : paragraphNodes;
|
|
1329
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
1330
|
+
${serializeDocument2({
|
|
1331
|
+
version: options.version ?? "1.0",
|
|
1332
|
+
lang: options.lang ?? options.language ?? "en-US",
|
|
1333
|
+
children
|
|
1334
|
+
})}`;
|
|
1335
|
+
}
|
|
1336
|
+
function serializeDocument2(document) {
|
|
1337
|
+
const attributes = [`version="${document.version}"`, `xml:lang="${document.lang}"`];
|
|
1338
|
+
const serialize = (node) => {
|
|
1339
|
+
if (typeof node === "string") return node.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1340
|
+
if (node.type === "text") return serialize(node.value);
|
|
1341
|
+
const tag = elementName(node);
|
|
1342
|
+
const nodeAttributes = elementAttributes(node);
|
|
1343
|
+
const serializedAttributes = Object.entries(nodeAttributes).map(([name, value]) => ` ${name}="${serialize(value).replace(/"/g, """)}"`).join("");
|
|
1344
|
+
const children = childrenOf(node).map(serialize).join("");
|
|
1345
|
+
return children ? `<${tag}${serializedAttributes}>${children}</${tag}>` : `<${tag}${serializedAttributes}/>`;
|
|
1346
|
+
};
|
|
1347
|
+
return `<speak ${attributes.join(" ")} xmlns="http://www.w3.org/2001/10/synthesis">${(document.children ?? []).map(serialize).join("")}</speak>`;
|
|
1348
|
+
}
|
|
1349
|
+
function flatten(document) {
|
|
1350
|
+
const result = [];
|
|
1351
|
+
const visit = (nodes, path) => {
|
|
1352
|
+
nodes.forEach((node, index) => {
|
|
1353
|
+
if (typeof node === "string" || node.type === "text") return;
|
|
1354
|
+
const currentPath = `${path}/${index}`;
|
|
1355
|
+
result.push({ name: elementName(node), attributes: elementAttributes(node), path: currentPath });
|
|
1356
|
+
visit(childrenOf(node), currentPath);
|
|
1357
|
+
});
|
|
1358
|
+
};
|
|
1359
|
+
result.push({ name: "speak", attributes: { version: document.version, "xml:lang": document.lang }, path: "0" });
|
|
1360
|
+
visit(childrenOf(document), "0");
|
|
1361
|
+
return result;
|
|
1362
|
+
}
|
|
1363
|
+
function validateSsmlStructureIntegrity(originalSsml, translatedSsml) {
|
|
1364
|
+
const mismatches = [];
|
|
1365
|
+
let original;
|
|
1366
|
+
let translated;
|
|
1367
|
+
try {
|
|
1368
|
+
original = parseSsml(originalSsml);
|
|
1369
|
+
} catch (error) {
|
|
1370
|
+
mismatches.push({ kind: "parse", message: `Original SSML cannot be parsed: ${String(error)}`, path: "0" });
|
|
1371
|
+
return {
|
|
1372
|
+
isValid: false,
|
|
1373
|
+
valid: false,
|
|
1374
|
+
errors: mismatches.map((item) => item.message),
|
|
1375
|
+
mismatches,
|
|
1376
|
+
mismatchedTags: []
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
try {
|
|
1380
|
+
translated = parseSsml(translatedSsml);
|
|
1381
|
+
} catch (error) {
|
|
1382
|
+
mismatches.push({ kind: "parse", message: `Translated SSML cannot be parsed: ${String(error)}`, path: "0" });
|
|
1383
|
+
return {
|
|
1384
|
+
isValid: false,
|
|
1385
|
+
valid: false,
|
|
1386
|
+
errors: mismatches.map((item) => item.message),
|
|
1387
|
+
mismatches,
|
|
1388
|
+
mismatchedTags: []
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
const originalElements = flatten(original);
|
|
1392
|
+
const translatedElements = flatten(translated);
|
|
1393
|
+
const count = Math.max(originalElements.length, translatedElements.length);
|
|
1394
|
+
for (let index = 0; index < count; index += 1) {
|
|
1395
|
+
const originalElement = originalElements[index];
|
|
1396
|
+
const translatedElement = translatedElements[index];
|
|
1397
|
+
if (!originalElement || !translatedElement || originalElement.name !== translatedElement.name) {
|
|
1398
|
+
mismatches.push({
|
|
1399
|
+
kind: "element",
|
|
1400
|
+
message: `SSML element structure differs at index ${index}`,
|
|
1401
|
+
original: originalElement?.name,
|
|
1402
|
+
path: originalElement?.path ?? translatedElement?.path ?? String(index),
|
|
1403
|
+
translated: translatedElement?.name
|
|
1404
|
+
});
|
|
1405
|
+
continue;
|
|
1406
|
+
}
|
|
1407
|
+
const attributeNames = /* @__PURE__ */ new Set([
|
|
1408
|
+
...Object.keys(originalElement.attributes),
|
|
1409
|
+
...Object.keys(translatedElement.attributes)
|
|
1410
|
+
]);
|
|
1411
|
+
for (const attribute of attributeNames) {
|
|
1412
|
+
if (originalElement.attributes[attribute] !== translatedElement.attributes[attribute]) {
|
|
1413
|
+
mismatches.push({
|
|
1414
|
+
kind: "attribute",
|
|
1415
|
+
message: `Attribute ${attribute} differs on <${originalElement.name}>`,
|
|
1416
|
+
original: originalElement.attributes[attribute],
|
|
1417
|
+
path: originalElement.path,
|
|
1418
|
+
translated: translatedElement.attributes[attribute]
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
const mismatchedTags = [
|
|
1424
|
+
...new Set(mismatches.flatMap((mismatch) => [mismatch.original, mismatch.translated].filter(Boolean)))
|
|
1425
|
+
];
|
|
1426
|
+
const errors = mismatches.map((mismatch) => mismatch.message);
|
|
1427
|
+
return { isValid: mismatches.length === 0, valid: mismatches.length === 0, errors, mismatches, mismatchedTags };
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1048
1430
|
// packages/ssml-core/src/generated/azureVoiceDefinitions.ts
|
|
1049
1431
|
var AZURE_VOICE_DEFINITIONS = [
|
|
1050
1432
|
{ name: "de-DE-ConradNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
|
|
@@ -1259,12 +1641,27 @@ function tokenizeElements(source) {
|
|
|
1259
1641
|
attributes.set(match[1].toLowerCase(), decodeAttribute(match[3]));
|
|
1260
1642
|
}
|
|
1261
1643
|
const selfClosing = /\/\s*>$/.test(raw);
|
|
1644
|
+
const parent = openElements[openElements.length - 1];
|
|
1645
|
+
const childElementIndex = parent?.childElementCount;
|
|
1646
|
+
if (parent) parent.childElementCount += 1;
|
|
1262
1647
|
const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
|
|
1263
|
-
|
|
1648
|
+
const tokenName = nameMatch[1];
|
|
1649
|
+
const tokenVoiceName = tokenName.toLowerCase() === "voice" ? attributes.get("name") : tokenName.toLowerCase() === "mstts:turn" ? attributes.get("voice") ?? parentVoiceName : parentVoiceName;
|
|
1650
|
+
tokens.push({
|
|
1651
|
+
attributes,
|
|
1652
|
+
childElementIndex,
|
|
1653
|
+
end,
|
|
1654
|
+
name: tokenName,
|
|
1655
|
+
parentName: parent?.name,
|
|
1656
|
+
parentVoiceName,
|
|
1657
|
+
selfClosing,
|
|
1658
|
+
start
|
|
1659
|
+
});
|
|
1264
1660
|
if (!selfClosing) {
|
|
1265
1661
|
openElements.push({
|
|
1266
|
-
|
|
1267
|
-
|
|
1662
|
+
childElementCount: 0,
|
|
1663
|
+
name: tokenName,
|
|
1664
|
+
voiceName: tokenVoiceName
|
|
1268
1665
|
});
|
|
1269
1666
|
}
|
|
1270
1667
|
index = end + 1;
|
|
@@ -1301,6 +1698,12 @@ function isValidAzureAudioDuration(value) {
|
|
|
1301
1698
|
if (!clock) return false;
|
|
1302
1699
|
return Number(clock[1]) > 0 || Number(clock[2]) > 0 || Number(clock[3]) > 0 || Number(clock[4] ?? 0) > 0;
|
|
1303
1700
|
}
|
|
1701
|
+
function isValidAzureBackgroundAudioDuration(value) {
|
|
1702
|
+
const match = /^(\d+(?:\.\d+)?)(ms|s)?$/i.exec(value.trim());
|
|
1703
|
+
if (!match) return false;
|
|
1704
|
+
const milliseconds = Number(match[1]) * (match[2]?.toLowerCase() === "s" ? 1e3 : 1);
|
|
1705
|
+
return Number.isFinite(milliseconds) && milliseconds >= 0 && milliseconds <= 1e4;
|
|
1706
|
+
}
|
|
1304
1707
|
function attr(token, name) {
|
|
1305
1708
|
return token.attributes.get(name.toLowerCase());
|
|
1306
1709
|
}
|
|
@@ -1402,6 +1805,61 @@ function definitionMatchesLanguage(definition, voiceName, language, normalizeLan
|
|
|
1402
1805
|
if (!normalizedLanguage || !normalizedCandidates.some(Boolean)) return void 0;
|
|
1403
1806
|
return normalizedLanguage === languagePart(normalizedLanguage) ? normalizedCandidates.some((candidate) => languagePart(candidate) === normalizedLanguage) : false;
|
|
1404
1807
|
}
|
|
1808
|
+
function canonicalTagName(name) {
|
|
1809
|
+
const normalized = name.toLowerCase();
|
|
1810
|
+
if (normalized === "express-as" || normalized === "expressas") return "mstts:express-as";
|
|
1811
|
+
if (normalized === "sayas") return "say-as";
|
|
1812
|
+
return normalized;
|
|
1813
|
+
}
|
|
1814
|
+
function validateVoiceFeatureMatrix(token, source, diagnostics, voiceName, definition) {
|
|
1815
|
+
if (!voiceName || !definition || token.name.toLowerCase() === "voice" || token.name.toLowerCase() === "mstts:turn")
|
|
1816
|
+
return;
|
|
1817
|
+
const tagName = canonicalTagName(token.name);
|
|
1818
|
+
const unsupportedTags = new Set((definition.unsupportedTags ?? []).map(canonicalTagName));
|
|
1819
|
+
const supportedTags = definition.supportedTags?.map(canonicalTagName);
|
|
1820
|
+
if (unsupportedTags.has(tagName) || supportedTags !== void 0 && !supportedTags.includes(tagName)) {
|
|
1821
|
+
addDiagnostic(
|
|
1822
|
+
diagnostics,
|
|
1823
|
+
source,
|
|
1824
|
+
token.start,
|
|
1825
|
+
`Tag <${token.name}> is not supported by voice "${voiceName}" according to the configured feature matrix.`,
|
|
1826
|
+
"error",
|
|
1827
|
+
"azure-unsupported-tag-for-voice"
|
|
1828
|
+
);
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
function validateAudioSource(token, source, diagnostics, options, elementName2) {
|
|
1832
|
+
const src = attr(token, "src");
|
|
1833
|
+
if (!src) {
|
|
1834
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2}> requires a "src" attribute.`);
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
let parsed;
|
|
1838
|
+
try {
|
|
1839
|
+
parsed = new URL(src);
|
|
1840
|
+
} catch {
|
|
1841
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must be an absolute HTTP(S) URL.`);
|
|
1842
|
+
return;
|
|
1843
|
+
}
|
|
1844
|
+
if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
|
|
1845
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must use HTTPS.`);
|
|
1846
|
+
const isAllowedOrigin = options.allowedAudioOrigins?.some((allowedOrigin) => {
|
|
1847
|
+
try {
|
|
1848
|
+
return new URL(allowedOrigin).origin === parsed.origin;
|
|
1849
|
+
} catch {
|
|
1850
|
+
return allowedOrigin === parsed.origin;
|
|
1851
|
+
}
|
|
1852
|
+
}) ?? false;
|
|
1853
|
+
if (options.allowedAudioOrigins && !isAllowedOrigin)
|
|
1854
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> origin "${parsed.origin}" is not allowed.`);
|
|
1855
|
+
else if (!isAllowedOrigin && !options.allowExternalAudio)
|
|
1856
|
+
addDiagnostic(
|
|
1857
|
+
diagnostics,
|
|
1858
|
+
source,
|
|
1859
|
+
token.start,
|
|
1860
|
+
`<${elementName2} src> external origin "${parsed.origin}" is blocked by default; set allowExternalAudio to true or provide allowedAudioOrigins.`
|
|
1861
|
+
);
|
|
1862
|
+
}
|
|
1405
1863
|
function validateElement(token, source, diagnostics, voiceName, options, voiceCatalog) {
|
|
1406
1864
|
const name = token.name.toLowerCase();
|
|
1407
1865
|
if (name === "voice" && !attr(token, "name")?.trim())
|
|
@@ -1521,29 +1979,45 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceCa
|
|
|
1521
1979
|
addDiagnostic(diagnostics, source, token.start, '<mstts:viseme> requires a supported "type" attribute.');
|
|
1522
1980
|
}
|
|
1523
1981
|
if (name === "audio") {
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1982
|
+
validateAudioSource(token, source, diagnostics, options, "audio");
|
|
1983
|
+
}
|
|
1984
|
+
if (name === "mstts:turn") {
|
|
1985
|
+
if (!attr(token, "voice")?.trim() && !attr(token, "speaker")?.trim())
|
|
1986
|
+
addDiagnostic(
|
|
1987
|
+
diagnostics,
|
|
1988
|
+
source,
|
|
1989
|
+
token.start,
|
|
1990
|
+
'<mstts:turn> requires a non-empty "voice" or "speaker" attribute.'
|
|
1991
|
+
);
|
|
1992
|
+
if (token.parentName?.toLowerCase() !== "mstts:dialog")
|
|
1993
|
+
addDiagnostic(diagnostics, source, token.start, "<mstts:turn> is only allowed directly inside <mstts:dialog>.");
|
|
1994
|
+
}
|
|
1995
|
+
if (name === "mstts:backgroundaudio") {
|
|
1996
|
+
validateAudioSource(token, source, diagnostics, options, "mstts:backgroundaudio");
|
|
1997
|
+
const volume = attr(token, "volume");
|
|
1998
|
+
if (volume !== void 0 && (!/^\d+(?:\.\d+)?$/.test(volume.trim()) || Number(volume) > 100))
|
|
1999
|
+
addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:backgroundaudio volume> value "${volume}".`);
|
|
2000
|
+
for (const [attribute, value] of [
|
|
2001
|
+
["fadein", attr(token, "fadein")],
|
|
2002
|
+
["fadeout", attr(token, "fadeout")]
|
|
2003
|
+
]) {
|
|
2004
|
+
if (value !== void 0 && !isValidAzureBackgroundAudioDuration(value))
|
|
1539
2005
|
addDiagnostic(
|
|
1540
2006
|
diagnostics,
|
|
1541
2007
|
source,
|
|
1542
2008
|
token.start,
|
|
1543
|
-
`<
|
|
1544
|
-
"error"
|
|
2009
|
+
`<mstts:backgroundaudio ${attribute}> must be between 0 and 10000 milliseconds, for example "500ms" or "10s".`
|
|
1545
2010
|
);
|
|
1546
2011
|
}
|
|
2012
|
+
if (token.parentName?.toLowerCase() !== "speak" || token.childElementIndex !== 0)
|
|
2013
|
+
addDiagnostic(
|
|
2014
|
+
diagnostics,
|
|
2015
|
+
source,
|
|
2016
|
+
token.start,
|
|
2017
|
+
"<mstts:backgroundaudio> must be the first element directly under <speak>."
|
|
2018
|
+
);
|
|
2019
|
+
if (!token.selfClosing)
|
|
2020
|
+
addDiagnostic(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
|
|
1547
2021
|
}
|
|
1548
2022
|
}
|
|
1549
2023
|
function validateAzureSsml(ssml, options = {}) {
|
|
@@ -1573,6 +2047,16 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
1573
2047
|
const tokens = tokenizeElements(ssml);
|
|
1574
2048
|
const speak = tokens.find((token) => token.name.toLowerCase() === "speak");
|
|
1575
2049
|
const voices = tokens.filter((token) => token.name.toLowerCase() === "voice");
|
|
2050
|
+
const backgroundAudioTokens = tokens.filter((token) => token.name.toLowerCase() === "mstts:backgroundaudio");
|
|
2051
|
+
for (const [index, token] of backgroundAudioTokens.entries()) {
|
|
2052
|
+
if (index > 0)
|
|
2053
|
+
addDiagnostic(
|
|
2054
|
+
diagnostics,
|
|
2055
|
+
ssml,
|
|
2056
|
+
token.start,
|
|
2057
|
+
"An SSML document can contain at most one <mstts:backgroundaudio> element."
|
|
2058
|
+
);
|
|
2059
|
+
}
|
|
1576
2060
|
if (!speak || voices.length === 0)
|
|
1577
2061
|
addDiagnostic(
|
|
1578
2062
|
diagnostics,
|
|
@@ -1609,22 +2093,57 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
1609
2093
|
);
|
|
1610
2094
|
}
|
|
1611
2095
|
for (const token of tokens) {
|
|
1612
|
-
const
|
|
2096
|
+
const tokenName = token.name.toLowerCase();
|
|
2097
|
+
const tokenVoiceName = tokenName === "voice" ? attr(token, "name")?.trim() : tokenName === "mstts:turn" ? attr(token, "voice")?.trim() || token.parentVoiceName : options.validateNestedVoices === false ? voiceName : token.parentVoiceName;
|
|
1613
2098
|
validateElement(token, ssml, diagnostics, tokenVoiceName, options, voiceCatalog);
|
|
2099
|
+
const definition = tokenVoiceName ? voiceCatalog.get(tokenVoiceName.toLowerCase()) : void 0;
|
|
2100
|
+
validateVoiceFeatureMatrix(token, ssml, diagnostics, tokenVoiceName, definition);
|
|
2101
|
+
if (tokenName === "voice" && options.model && definition?.models && !definition.models.some((model) => model.toLowerCase() === options.model?.toLowerCase())) {
|
|
2102
|
+
addDiagnostic(
|
|
2103
|
+
diagnostics,
|
|
2104
|
+
ssml,
|
|
2105
|
+
token.start,
|
|
2106
|
+
`Voice "${tokenVoiceName}" does not support model "${options.model}" according to the configured feature matrix.`,
|
|
2107
|
+
"error",
|
|
2108
|
+
"azure-unsupported-model-for-voice"
|
|
2109
|
+
);
|
|
2110
|
+
}
|
|
1614
2111
|
}
|
|
1615
2112
|
return diagnostics;
|
|
1616
2113
|
}
|
|
2114
|
+
|
|
2115
|
+
// packages/ssml-core/src/generated/azureVoiceCatalog.ts
|
|
2116
|
+
var AZURE_VOICE_CATALOG_METADATA = {
|
|
2117
|
+
apiVersion: "2025-10-01",
|
|
2118
|
+
generatedAt: "2026-08-28T00:00:00.000Z",
|
|
2119
|
+
regions: [],
|
|
2120
|
+
voiceCount: AZURE_VOICE_DEFINITIONS.length
|
|
2121
|
+
};
|
|
2122
|
+
|
|
2123
|
+
// packages/ssml-core/src/voiceCatalog.ts
|
|
2124
|
+
function getAzureVoiceCatalogMetadata() {
|
|
2125
|
+
return {
|
|
2126
|
+
...AZURE_VOICE_CATALOG_METADATA,
|
|
2127
|
+
regions: [...AZURE_VOICE_CATALOG_METADATA.regions]
|
|
2128
|
+
};
|
|
2129
|
+
}
|
|
2130
|
+
var getBuiltInVoiceCatalogMetadata = getAzureVoiceCatalogMetadata;
|
|
1617
2131
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1618
2132
|
0 && (module.exports = {
|
|
1619
2133
|
areAzureLanguagesEquivalent,
|
|
1620
2134
|
buildPartialSsml,
|
|
1621
2135
|
buildSsml,
|
|
1622
2136
|
extractSsmlText,
|
|
2137
|
+
extractSsmlTranslatableText,
|
|
2138
|
+
fromPlainTextToSsml,
|
|
2139
|
+
getAzureVoiceCatalogMetadata,
|
|
2140
|
+
getBuiltInVoiceCatalogMetadata,
|
|
1623
2141
|
isValidAzureAudioDuration,
|
|
1624
2142
|
mapSsmlTextNodes,
|
|
1625
2143
|
normalizeAzureLanguage,
|
|
1626
2144
|
parseSsml,
|
|
1627
2145
|
validateAzureSsml,
|
|
1628
|
-
validateSsml
|
|
2146
|
+
validateSsml,
|
|
2147
|
+
validateSsmlStructureIntegrity
|
|
1629
2148
|
});
|
|
1630
2149
|
//# sourceMappingURL=core.js.map
|