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/elements.mjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -108,6 +108,7 @@ var SSML_ATTRS = {
|
|
|
108
108
|
MSTTS_XMLNS: "xmlns:mstts",
|
|
109
109
|
NAME: "name",
|
|
110
110
|
VOICE: "voice",
|
|
111
|
+
SPEAKER: "speaker",
|
|
111
112
|
EFFECT: "effect",
|
|
112
113
|
RATE: "rate",
|
|
113
114
|
PITCH: "pitch",
|
|
@@ -138,6 +139,11 @@ var SSML_ATTRS = {
|
|
|
138
139
|
ALIAS: "alias",
|
|
139
140
|
MARK: "mark",
|
|
140
141
|
URI: "uri",
|
|
142
|
+
ID: "id",
|
|
143
|
+
MODEL: "model",
|
|
144
|
+
PROFILE: "profile",
|
|
145
|
+
URL: "url",
|
|
146
|
+
SPEAKER_PROFILE_ID: "speakerProfileId",
|
|
141
147
|
TYPE: "type",
|
|
142
148
|
VALUE: "value",
|
|
143
149
|
FADE_IN: "fadein",
|
|
@@ -235,6 +241,7 @@ function getAttributes(element) {
|
|
|
235
241
|
break;
|
|
236
242
|
case SSML_TAGS.MSTTS_TURN:
|
|
237
243
|
addAttribute(attributes, SSML_ATTRS.VOICE, element.voice);
|
|
244
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER, element.speaker);
|
|
238
245
|
break;
|
|
239
246
|
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO:
|
|
240
247
|
addAttribute(attributes, SSML_ATTRS.SRC, element.src);
|
|
@@ -243,9 +250,18 @@ function getAttributes(element) {
|
|
|
243
250
|
addAttribute(attributes, SSML_ATTRS.FADE_OUT, element.fadeOut ?? element.fadeout);
|
|
244
251
|
break;
|
|
245
252
|
case SSML_TAGS.MSTTS_DIALOG:
|
|
253
|
+
break;
|
|
246
254
|
case SSML_TAGS.MSTTS_TTS_EMBEDDING:
|
|
255
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
|
|
256
|
+
break;
|
|
247
257
|
case SSML_TAGS.MSTTS_EMBEDDING:
|
|
258
|
+
addAttribute(attributes, SSML_ATTRS.ID, element.id);
|
|
259
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
|
|
260
|
+
break;
|
|
248
261
|
case SSML_TAGS.MSTTS_VOICE_CONVERSION:
|
|
262
|
+
addAttribute(attributes, SSML_ATTRS.URL, element.url);
|
|
263
|
+
addAttribute(attributes, SSML_ATTRS.PROFILE, element.profile);
|
|
264
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
|
|
249
265
|
break;
|
|
250
266
|
case SSML_TAGS.PARAGRAPH:
|
|
251
267
|
case SSML_TAGS.SENTENCE:
|
|
@@ -820,7 +836,9 @@ function convertElement(node) {
|
|
|
820
836
|
case SSML_TAGS.MSTTS_TURN: {
|
|
821
837
|
const element = { type: SSML_TAGS.MSTTS_TURN };
|
|
822
838
|
const voice = readAttribute(attributes, SSML_ATTRS.VOICE);
|
|
839
|
+
const speaker = readAttribute(attributes, SSML_ATTRS.SPEAKER);
|
|
823
840
|
if (voice !== void 0) element.voice = voice;
|
|
841
|
+
if (speaker !== void 0) element.speaker = speaker;
|
|
824
842
|
return finishElement(element, node, attributes);
|
|
825
843
|
}
|
|
826
844
|
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO: {
|
|
@@ -837,14 +855,26 @@ function convertElement(node) {
|
|
|
837
855
|
}
|
|
838
856
|
case SSML_TAGS.MSTTS_TTS_EMBEDDING: {
|
|
839
857
|
const element = { type: SSML_TAGS.MSTTS_TTS_EMBEDDING };
|
|
858
|
+
const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
|
|
859
|
+
if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
|
|
840
860
|
return finishElement(element, node, attributes);
|
|
841
861
|
}
|
|
842
862
|
case SSML_TAGS.MSTTS_EMBEDDING: {
|
|
843
863
|
const element = { type: SSML_TAGS.MSTTS_EMBEDDING };
|
|
864
|
+
const id = readAttribute(attributes, SSML_ATTRS.ID);
|
|
865
|
+
const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
|
|
866
|
+
if (id !== void 0) element.id = id;
|
|
867
|
+
if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
|
|
844
868
|
return finishElement(element, node, attributes);
|
|
845
869
|
}
|
|
846
870
|
case SSML_TAGS.MSTTS_VOICE_CONVERSION: {
|
|
847
871
|
const element = { type: SSML_TAGS.MSTTS_VOICE_CONVERSION };
|
|
872
|
+
const url = readAttribute(attributes, SSML_ATTRS.URL);
|
|
873
|
+
const profile = readAttribute(attributes, SSML_ATTRS.PROFILE);
|
|
874
|
+
const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
|
|
875
|
+
if (url !== void 0) element.url = url;
|
|
876
|
+
if (profile !== void 0) element.profile = profile;
|
|
877
|
+
if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
|
|
848
878
|
return finishElement(element, node, attributes);
|
|
849
879
|
}
|
|
850
880
|
default: {
|
|
@@ -1220,6 +1250,7 @@ function elementAttributes(element) {
|
|
|
1220
1250
|
break;
|
|
1221
1251
|
case "mstts:turn":
|
|
1222
1252
|
addAttribute2(attributes, "voice", element.voice);
|
|
1253
|
+
addAttribute2(attributes, "speaker", element.speaker);
|
|
1223
1254
|
break;
|
|
1224
1255
|
case "mstts:backgroundaudio":
|
|
1225
1256
|
addAttribute2(attributes, "src", element.src);
|
|
@@ -1227,6 +1258,18 @@ function elementAttributes(element) {
|
|
|
1227
1258
|
addAttribute2(attributes, "fadein", element.fadeIn ?? element.fadein);
|
|
1228
1259
|
addAttribute2(attributes, "fadeout", element.fadeOut ?? element.fadeout);
|
|
1229
1260
|
break;
|
|
1261
|
+
case "mstts:ttsembedding":
|
|
1262
|
+
addAttribute2(attributes, "speakerProfileId", element.speakerProfileId);
|
|
1263
|
+
break;
|
|
1264
|
+
case "mstts:embedding":
|
|
1265
|
+
addAttribute2(attributes, "id", element.id);
|
|
1266
|
+
addAttribute2(attributes, "speakerProfileId", element.speakerProfileId);
|
|
1267
|
+
break;
|
|
1268
|
+
case "mstts:voiceconversion":
|
|
1269
|
+
addAttribute2(attributes, "url", element.url);
|
|
1270
|
+
addAttribute2(attributes, "profile", element.profile);
|
|
1271
|
+
addAttribute2(attributes, "speakerProfileId", element.speakerProfileId);
|
|
1272
|
+
break;
|
|
1230
1273
|
}
|
|
1231
1274
|
return Object.fromEntries(Object.entries(attributes).map(([name, value]) => [name, String(value)]));
|
|
1232
1275
|
}
|
|
@@ -1613,11 +1656,14 @@ function tokenizeElements(source) {
|
|
|
1613
1656
|
}
|
|
1614
1657
|
const selfClosing = /\/\s*>$/.test(raw);
|
|
1615
1658
|
const parent = openElements[openElements.length - 1];
|
|
1659
|
+
const childElementIndex = parent?.childElementCount;
|
|
1660
|
+
if (parent) parent.childElementCount += 1;
|
|
1616
1661
|
const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
|
|
1617
1662
|
const tokenName = nameMatch[1];
|
|
1618
1663
|
const tokenVoiceName = tokenName.toLowerCase() === "voice" ? attributes.get("name") : tokenName.toLowerCase() === "mstts:turn" ? attributes.get("voice") ?? parentVoiceName : parentVoiceName;
|
|
1619
1664
|
tokens.push({
|
|
1620
1665
|
attributes,
|
|
1666
|
+
childElementIndex,
|
|
1621
1667
|
end,
|
|
1622
1668
|
name: tokenName,
|
|
1623
1669
|
parentName: parent?.name,
|
|
@@ -1627,6 +1673,7 @@ function tokenizeElements(source) {
|
|
|
1627
1673
|
});
|
|
1628
1674
|
if (!selfClosing) {
|
|
1629
1675
|
openElements.push({
|
|
1676
|
+
childElementCount: 0,
|
|
1630
1677
|
name: tokenName,
|
|
1631
1678
|
voiceName: tokenVoiceName
|
|
1632
1679
|
});
|
|
@@ -1665,6 +1712,12 @@ function isValidAzureAudioDuration(value) {
|
|
|
1665
1712
|
if (!clock) return false;
|
|
1666
1713
|
return Number(clock[1]) > 0 || Number(clock[2]) > 0 || Number(clock[3]) > 0 || Number(clock[4] ?? 0) > 0;
|
|
1667
1714
|
}
|
|
1715
|
+
function isValidAzureBackgroundAudioDuration(value) {
|
|
1716
|
+
const match = /^(\d+(?:\.\d+)?)(ms|s)?$/i.exec(value.trim());
|
|
1717
|
+
if (!match) return false;
|
|
1718
|
+
const milliseconds = Number(match[1]) * (match[2]?.toLowerCase() === "s" ? 1e3 : 1);
|
|
1719
|
+
return Number.isFinite(milliseconds) && milliseconds >= 0 && milliseconds <= 1e4;
|
|
1720
|
+
}
|
|
1668
1721
|
function attr(token, name) {
|
|
1669
1722
|
return token.attributes.get(name.toLowerCase());
|
|
1670
1723
|
}
|
|
@@ -1804,9 +1857,16 @@ function validateAudioSource(token, source, diagnostics, options, elementName2)
|
|
|
1804
1857
|
}
|
|
1805
1858
|
if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
|
|
1806
1859
|
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must use HTTPS.`);
|
|
1807
|
-
|
|
1860
|
+
const isAllowedOrigin = options.allowedAudioOrigins?.some((allowedOrigin) => {
|
|
1861
|
+
try {
|
|
1862
|
+
return new URL(allowedOrigin).origin === parsed.origin;
|
|
1863
|
+
} catch {
|
|
1864
|
+
return allowedOrigin === parsed.origin;
|
|
1865
|
+
}
|
|
1866
|
+
}) ?? false;
|
|
1867
|
+
if (options.allowedAudioOrigins && !isAllowedOrigin)
|
|
1808
1868
|
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> origin "${parsed.origin}" is not allowed.`);
|
|
1809
|
-
else if (!options.allowExternalAudio)
|
|
1869
|
+
else if (!isAllowedOrigin && !options.allowExternalAudio)
|
|
1810
1870
|
addDiagnostic(
|
|
1811
1871
|
diagnostics,
|
|
1812
1872
|
source,
|
|
@@ -1936,28 +1996,40 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceCa
|
|
|
1936
1996
|
validateAudioSource(token, source, diagnostics, options, "audio");
|
|
1937
1997
|
}
|
|
1938
1998
|
if (name === "mstts:turn") {
|
|
1939
|
-
if (!attr(token, "voice")?.trim())
|
|
1940
|
-
addDiagnostic(
|
|
1999
|
+
if (!attr(token, "voice")?.trim() && !attr(token, "speaker")?.trim())
|
|
2000
|
+
addDiagnostic(
|
|
2001
|
+
diagnostics,
|
|
2002
|
+
source,
|
|
2003
|
+
token.start,
|
|
2004
|
+
'<mstts:turn> requires a non-empty "voice" or "speaker" attribute.'
|
|
2005
|
+
);
|
|
1941
2006
|
if (token.parentName?.toLowerCase() !== "mstts:dialog")
|
|
1942
2007
|
addDiagnostic(diagnostics, source, token.start, "<mstts:turn> is only allowed directly inside <mstts:dialog>.");
|
|
1943
2008
|
}
|
|
1944
2009
|
if (name === "mstts:backgroundaudio") {
|
|
1945
2010
|
validateAudioSource(token, source, diagnostics, options, "mstts:backgroundaudio");
|
|
1946
2011
|
const volume = attr(token, "volume");
|
|
1947
|
-
if (volume &&
|
|
2012
|
+
if (volume !== void 0 && (!/^\d+(?:\.\d+)?$/.test(volume.trim()) || Number(volume) > 100))
|
|
1948
2013
|
addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:backgroundaudio volume> value "${volume}".`);
|
|
1949
2014
|
for (const [attribute, value] of [
|
|
1950
2015
|
["fadein", attr(token, "fadein")],
|
|
1951
2016
|
["fadeout", attr(token, "fadeout")]
|
|
1952
2017
|
]) {
|
|
1953
|
-
if (value && !
|
|
2018
|
+
if (value !== void 0 && !isValidAzureBackgroundAudioDuration(value))
|
|
1954
2019
|
addDiagnostic(
|
|
1955
2020
|
diagnostics,
|
|
1956
2021
|
source,
|
|
1957
2022
|
token.start,
|
|
1958
|
-
`<mstts:backgroundaudio ${attribute}> must be
|
|
2023
|
+
`<mstts:backgroundaudio ${attribute}> must be between 0 and 10000 milliseconds, for example "500ms" or "10s".`
|
|
1959
2024
|
);
|
|
1960
2025
|
}
|
|
2026
|
+
if (token.parentName?.toLowerCase() !== "speak" || token.childElementIndex !== 0)
|
|
2027
|
+
addDiagnostic(
|
|
2028
|
+
diagnostics,
|
|
2029
|
+
source,
|
|
2030
|
+
token.start,
|
|
2031
|
+
"<mstts:backgroundaudio> must be the first element directly under <speak>."
|
|
2032
|
+
);
|
|
1961
2033
|
if (!token.selfClosing)
|
|
1962
2034
|
addDiagnostic(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
|
|
1963
2035
|
}
|
|
@@ -1989,6 +2061,16 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
1989
2061
|
const tokens = tokenizeElements(ssml);
|
|
1990
2062
|
const speak = tokens.find((token) => token.name.toLowerCase() === "speak");
|
|
1991
2063
|
const voices = tokens.filter((token) => token.name.toLowerCase() === "voice");
|
|
2064
|
+
const backgroundAudioTokens = tokens.filter((token) => token.name.toLowerCase() === "mstts:backgroundaudio");
|
|
2065
|
+
for (const [index, token] of backgroundAudioTokens.entries()) {
|
|
2066
|
+
if (index > 0)
|
|
2067
|
+
addDiagnostic(
|
|
2068
|
+
diagnostics,
|
|
2069
|
+
ssml,
|
|
2070
|
+
token.start,
|
|
2071
|
+
"An SSML document can contain at most one <mstts:backgroundaudio> element."
|
|
2072
|
+
);
|
|
2073
|
+
}
|
|
1992
2074
|
if (!speak || voices.length === 0)
|
|
1993
2075
|
addDiagnostic(
|
|
1994
2076
|
diagnostics,
|