ssml-builder-js 2.9.0 → 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 +10 -6
- package/dist/{chunk-JNJVTEL6.mjs → chunk-BYIZQL2W.mjs} +77 -8
- package/dist/chunk-BYIZQL2W.mjs.map +1 -0
- package/dist/{chunk-7LCSH4SZ.mjs → chunk-GW6CKHXF.mjs} +90 -8
- package/dist/chunk-GW6CKHXF.mjs.map +1 -0
- package/dist/core.d.mts +7 -0
- package/dist/core.d.ts +7 -0
- package/dist/core.js +89 -7
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +1 -1
- package/dist/elements.js +76 -7
- package/dist/elements.js.map +1 -1
- package/dist/elements.mjs +1 -1
- package/dist/index.js +89 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/react.d.mts +7 -0
- package/dist/react.d.ts +7 -0
- package/dist/react.js +76 -7
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +1 -1
- package/package.json +1 -1
- package/dist/chunk-7LCSH4SZ.mjs.map +0 -1
- package/dist/chunk-JNJVTEL6.mjs.map +0 -1
package/dist/core.mjs
CHANGED
package/dist/elements.js
CHANGED
|
@@ -91,6 +91,7 @@ var SSML_ATTRS = {
|
|
|
91
91
|
MSTTS_XMLNS: "xmlns:mstts",
|
|
92
92
|
NAME: "name",
|
|
93
93
|
VOICE: "voice",
|
|
94
|
+
SPEAKER: "speaker",
|
|
94
95
|
EFFECT: "effect",
|
|
95
96
|
RATE: "rate",
|
|
96
97
|
PITCH: "pitch",
|
|
@@ -121,6 +122,11 @@ var SSML_ATTRS = {
|
|
|
121
122
|
ALIAS: "alias",
|
|
122
123
|
MARK: "mark",
|
|
123
124
|
URI: "uri",
|
|
125
|
+
ID: "id",
|
|
126
|
+
MODEL: "model",
|
|
127
|
+
PROFILE: "profile",
|
|
128
|
+
URL: "url",
|
|
129
|
+
SPEAKER_PROFILE_ID: "speakerProfileId",
|
|
124
130
|
TYPE: "type",
|
|
125
131
|
VALUE: "value",
|
|
126
132
|
FADE_IN: "fadein",
|
|
@@ -216,6 +222,7 @@ function getAttributes(element) {
|
|
|
216
222
|
break;
|
|
217
223
|
case SSML_TAGS.MSTTS_TURN:
|
|
218
224
|
addAttribute(attributes, SSML_ATTRS.VOICE, element.voice);
|
|
225
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER, element.speaker);
|
|
219
226
|
break;
|
|
220
227
|
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO:
|
|
221
228
|
addAttribute(attributes, SSML_ATTRS.SRC, element.src);
|
|
@@ -224,9 +231,18 @@ function getAttributes(element) {
|
|
|
224
231
|
addAttribute(attributes, SSML_ATTRS.FADE_OUT, element.fadeOut ?? element.fadeout);
|
|
225
232
|
break;
|
|
226
233
|
case SSML_TAGS.MSTTS_DIALOG:
|
|
234
|
+
break;
|
|
227
235
|
case SSML_TAGS.MSTTS_TTS_EMBEDDING:
|
|
236
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
|
|
237
|
+
break;
|
|
228
238
|
case SSML_TAGS.MSTTS_EMBEDDING:
|
|
239
|
+
addAttribute(attributes, SSML_ATTRS.ID, element.id);
|
|
240
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
|
|
241
|
+
break;
|
|
229
242
|
case SSML_TAGS.MSTTS_VOICE_CONVERSION:
|
|
243
|
+
addAttribute(attributes, SSML_ATTRS.URL, element.url);
|
|
244
|
+
addAttribute(attributes, SSML_ATTRS.PROFILE, element.profile);
|
|
245
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
|
|
230
246
|
break;
|
|
231
247
|
case SSML_TAGS.PARAGRAPH:
|
|
232
248
|
case SSML_TAGS.SENTENCE:
|
|
@@ -799,7 +815,9 @@ function convertElement(node) {
|
|
|
799
815
|
case SSML_TAGS.MSTTS_TURN: {
|
|
800
816
|
const element = { type: SSML_TAGS.MSTTS_TURN };
|
|
801
817
|
const voice = readAttribute(attributes, SSML_ATTRS.VOICE);
|
|
818
|
+
const speaker = readAttribute(attributes, SSML_ATTRS.SPEAKER);
|
|
802
819
|
if (voice !== void 0) element.voice = voice;
|
|
820
|
+
if (speaker !== void 0) element.speaker = speaker;
|
|
803
821
|
return finishElement(element, node, attributes);
|
|
804
822
|
}
|
|
805
823
|
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO: {
|
|
@@ -816,14 +834,26 @@ function convertElement(node) {
|
|
|
816
834
|
}
|
|
817
835
|
case SSML_TAGS.MSTTS_TTS_EMBEDDING: {
|
|
818
836
|
const element = { type: SSML_TAGS.MSTTS_TTS_EMBEDDING };
|
|
837
|
+
const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
|
|
838
|
+
if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
|
|
819
839
|
return finishElement(element, node, attributes);
|
|
820
840
|
}
|
|
821
841
|
case SSML_TAGS.MSTTS_EMBEDDING: {
|
|
822
842
|
const element = { type: SSML_TAGS.MSTTS_EMBEDDING };
|
|
843
|
+
const id = readAttribute(attributes, SSML_ATTRS.ID);
|
|
844
|
+
const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
|
|
845
|
+
if (id !== void 0) element.id = id;
|
|
846
|
+
if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
|
|
823
847
|
return finishElement(element, node, attributes);
|
|
824
848
|
}
|
|
825
849
|
case SSML_TAGS.MSTTS_VOICE_CONVERSION: {
|
|
826
850
|
const element = { type: SSML_TAGS.MSTTS_VOICE_CONVERSION };
|
|
851
|
+
const url = readAttribute(attributes, SSML_ATTRS.URL);
|
|
852
|
+
const profile = readAttribute(attributes, SSML_ATTRS.PROFILE);
|
|
853
|
+
const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
|
|
854
|
+
if (url !== void 0) element.url = url;
|
|
855
|
+
if (profile !== void 0) element.profile = profile;
|
|
856
|
+
if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
|
|
827
857
|
return finishElement(element, node, attributes);
|
|
828
858
|
}
|
|
829
859
|
default: {
|
|
@@ -1079,11 +1109,14 @@ function tokenizeElements(source) {
|
|
|
1079
1109
|
}
|
|
1080
1110
|
const selfClosing = /\/\s*>$/.test(raw);
|
|
1081
1111
|
const parent = openElements[openElements.length - 1];
|
|
1112
|
+
const childElementIndex = parent?.childElementCount;
|
|
1113
|
+
if (parent) parent.childElementCount += 1;
|
|
1082
1114
|
const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
|
|
1083
1115
|
const tokenName = nameMatch[1];
|
|
1084
1116
|
const tokenVoiceName = tokenName.toLowerCase() === "voice" ? attributes.get("name") : tokenName.toLowerCase() === "mstts:turn" ? attributes.get("voice") ?? parentVoiceName : parentVoiceName;
|
|
1085
1117
|
tokens.push({
|
|
1086
1118
|
attributes,
|
|
1119
|
+
childElementIndex,
|
|
1087
1120
|
end,
|
|
1088
1121
|
name: tokenName,
|
|
1089
1122
|
parentName: parent?.name,
|
|
@@ -1093,6 +1126,7 @@ function tokenizeElements(source) {
|
|
|
1093
1126
|
});
|
|
1094
1127
|
if (!selfClosing) {
|
|
1095
1128
|
openElements.push({
|
|
1129
|
+
childElementCount: 0,
|
|
1096
1130
|
name: tokenName,
|
|
1097
1131
|
voiceName: tokenVoiceName
|
|
1098
1132
|
});
|
|
@@ -1131,6 +1165,12 @@ function isValidAzureAudioDuration(value) {
|
|
|
1131
1165
|
if (!clock) return false;
|
|
1132
1166
|
return Number(clock[1]) > 0 || Number(clock[2]) > 0 || Number(clock[3]) > 0 || Number(clock[4] ?? 0) > 0;
|
|
1133
1167
|
}
|
|
1168
|
+
function isValidAzureBackgroundAudioDuration(value) {
|
|
1169
|
+
const match = /^(\d+(?:\.\d+)?)(ms|s)?$/i.exec(value.trim());
|
|
1170
|
+
if (!match) return false;
|
|
1171
|
+
const milliseconds = Number(match[1]) * (match[2]?.toLowerCase() === "s" ? 1e3 : 1);
|
|
1172
|
+
return Number.isFinite(milliseconds) && milliseconds >= 0 && milliseconds <= 1e4;
|
|
1173
|
+
}
|
|
1134
1174
|
function attr(token, name) {
|
|
1135
1175
|
return token.attributes.get(name.toLowerCase());
|
|
1136
1176
|
}
|
|
@@ -1260,9 +1300,16 @@ function validateAudioSource(token, source, diagnostics, options, elementName2)
|
|
|
1260
1300
|
}
|
|
1261
1301
|
if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
|
|
1262
1302
|
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must use HTTPS.`);
|
|
1263
|
-
|
|
1303
|
+
const isAllowedOrigin = options.allowedAudioOrigins?.some((allowedOrigin) => {
|
|
1304
|
+
try {
|
|
1305
|
+
return new URL(allowedOrigin).origin === parsed.origin;
|
|
1306
|
+
} catch {
|
|
1307
|
+
return allowedOrigin === parsed.origin;
|
|
1308
|
+
}
|
|
1309
|
+
}) ?? false;
|
|
1310
|
+
if (options.allowedAudioOrigins && !isAllowedOrigin)
|
|
1264
1311
|
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> origin "${parsed.origin}" is not allowed.`);
|
|
1265
|
-
else if (!options.allowExternalAudio)
|
|
1312
|
+
else if (!isAllowedOrigin && !options.allowExternalAudio)
|
|
1266
1313
|
addDiagnostic(
|
|
1267
1314
|
diagnostics,
|
|
1268
1315
|
source,
|
|
@@ -1392,28 +1439,40 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceCa
|
|
|
1392
1439
|
validateAudioSource(token, source, diagnostics, options, "audio");
|
|
1393
1440
|
}
|
|
1394
1441
|
if (name === "mstts:turn") {
|
|
1395
|
-
if (!attr(token, "voice")?.trim())
|
|
1396
|
-
addDiagnostic(
|
|
1442
|
+
if (!attr(token, "voice")?.trim() && !attr(token, "speaker")?.trim())
|
|
1443
|
+
addDiagnostic(
|
|
1444
|
+
diagnostics,
|
|
1445
|
+
source,
|
|
1446
|
+
token.start,
|
|
1447
|
+
'<mstts:turn> requires a non-empty "voice" or "speaker" attribute.'
|
|
1448
|
+
);
|
|
1397
1449
|
if (token.parentName?.toLowerCase() !== "mstts:dialog")
|
|
1398
1450
|
addDiagnostic(diagnostics, source, token.start, "<mstts:turn> is only allowed directly inside <mstts:dialog>.");
|
|
1399
1451
|
}
|
|
1400
1452
|
if (name === "mstts:backgroundaudio") {
|
|
1401
1453
|
validateAudioSource(token, source, diagnostics, options, "mstts:backgroundaudio");
|
|
1402
1454
|
const volume = attr(token, "volume");
|
|
1403
|
-
if (volume &&
|
|
1455
|
+
if (volume !== void 0 && (!/^\d+(?:\.\d+)?$/.test(volume.trim()) || Number(volume) > 100))
|
|
1404
1456
|
addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:backgroundaudio volume> value "${volume}".`);
|
|
1405
1457
|
for (const [attribute, value] of [
|
|
1406
1458
|
["fadein", attr(token, "fadein")],
|
|
1407
1459
|
["fadeout", attr(token, "fadeout")]
|
|
1408
1460
|
]) {
|
|
1409
|
-
if (value && !
|
|
1461
|
+
if (value !== void 0 && !isValidAzureBackgroundAudioDuration(value))
|
|
1410
1462
|
addDiagnostic(
|
|
1411
1463
|
diagnostics,
|
|
1412
1464
|
source,
|
|
1413
1465
|
token.start,
|
|
1414
|
-
`<mstts:backgroundaudio ${attribute}> must be
|
|
1466
|
+
`<mstts:backgroundaudio ${attribute}> must be between 0 and 10000 milliseconds, for example "500ms" or "10s".`
|
|
1415
1467
|
);
|
|
1416
1468
|
}
|
|
1469
|
+
if (token.parentName?.toLowerCase() !== "speak" || token.childElementIndex !== 0)
|
|
1470
|
+
addDiagnostic(
|
|
1471
|
+
diagnostics,
|
|
1472
|
+
source,
|
|
1473
|
+
token.start,
|
|
1474
|
+
"<mstts:backgroundaudio> must be the first element directly under <speak>."
|
|
1475
|
+
);
|
|
1417
1476
|
if (!token.selfClosing)
|
|
1418
1477
|
addDiagnostic(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
|
|
1419
1478
|
}
|
|
@@ -1445,6 +1504,16 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
1445
1504
|
const tokens = tokenizeElements(ssml);
|
|
1446
1505
|
const speak = tokens.find((token) => token.name.toLowerCase() === "speak");
|
|
1447
1506
|
const voices = tokens.filter((token) => token.name.toLowerCase() === "voice");
|
|
1507
|
+
const backgroundAudioTokens = tokens.filter((token) => token.name.toLowerCase() === "mstts:backgroundaudio");
|
|
1508
|
+
for (const [index, token] of backgroundAudioTokens.entries()) {
|
|
1509
|
+
if (index > 0)
|
|
1510
|
+
addDiagnostic(
|
|
1511
|
+
diagnostics,
|
|
1512
|
+
ssml,
|
|
1513
|
+
token.start,
|
|
1514
|
+
"An SSML document can contain at most one <mstts:backgroundaudio> element."
|
|
1515
|
+
);
|
|
1516
|
+
}
|
|
1448
1517
|
if (!speak || voices.length === 0)
|
|
1449
1518
|
addDiagnostic(
|
|
1450
1519
|
diagnostics,
|