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.d.ts
CHANGED
|
@@ -109,6 +109,7 @@ interface SsmlDialogNode extends SsmlElementBase {
|
|
|
109
109
|
interface SsmlTurnNode extends SsmlElementBase {
|
|
110
110
|
type: "mstts:turn";
|
|
111
111
|
voice?: string;
|
|
112
|
+
speaker?: string;
|
|
112
113
|
}
|
|
113
114
|
interface SsmlBackgroundAudioNode extends SsmlElementBase {
|
|
114
115
|
type: "mstts:backgroundaudio";
|
|
@@ -122,12 +123,18 @@ interface SsmlBackgroundAudioNode extends SsmlElementBase {
|
|
|
122
123
|
}
|
|
123
124
|
interface MsttsTtsEmbeddingElement extends SsmlElementBase {
|
|
124
125
|
type: "mstts:ttsembedding";
|
|
126
|
+
speakerProfileId?: string;
|
|
125
127
|
}
|
|
126
128
|
interface MsttsEmbeddingElement extends SsmlElementBase {
|
|
127
129
|
type: "mstts:embedding";
|
|
130
|
+
id?: string;
|
|
131
|
+
speakerProfileId?: string;
|
|
128
132
|
}
|
|
129
133
|
interface MsttsVoiceConversionElement extends SsmlElementBase {
|
|
130
134
|
type: "mstts:voiceconversion";
|
|
135
|
+
url?: string;
|
|
136
|
+
profile?: string;
|
|
137
|
+
speakerProfileId?: string;
|
|
131
138
|
}
|
|
132
139
|
type SsmlTtsEmbeddingNode = MsttsTtsEmbeddingElement;
|
|
133
140
|
type SsmlEmbeddingNode = MsttsEmbeddingElement;
|
package/dist/core.js
CHANGED
|
@@ -94,6 +94,7 @@ var SSML_ATTRS = {
|
|
|
94
94
|
MSTTS_XMLNS: "xmlns:mstts",
|
|
95
95
|
NAME: "name",
|
|
96
96
|
VOICE: "voice",
|
|
97
|
+
SPEAKER: "speaker",
|
|
97
98
|
EFFECT: "effect",
|
|
98
99
|
RATE: "rate",
|
|
99
100
|
PITCH: "pitch",
|
|
@@ -124,6 +125,11 @@ var SSML_ATTRS = {
|
|
|
124
125
|
ALIAS: "alias",
|
|
125
126
|
MARK: "mark",
|
|
126
127
|
URI: "uri",
|
|
128
|
+
ID: "id",
|
|
129
|
+
MODEL: "model",
|
|
130
|
+
PROFILE: "profile",
|
|
131
|
+
URL: "url",
|
|
132
|
+
SPEAKER_PROFILE_ID: "speakerProfileId",
|
|
127
133
|
TYPE: "type",
|
|
128
134
|
VALUE: "value",
|
|
129
135
|
FADE_IN: "fadein",
|
|
@@ -221,6 +227,7 @@ function getAttributes(element) {
|
|
|
221
227
|
break;
|
|
222
228
|
case SSML_TAGS.MSTTS_TURN:
|
|
223
229
|
addAttribute(attributes, SSML_ATTRS.VOICE, element.voice);
|
|
230
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER, element.speaker);
|
|
224
231
|
break;
|
|
225
232
|
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO:
|
|
226
233
|
addAttribute(attributes, SSML_ATTRS.SRC, element.src);
|
|
@@ -229,9 +236,18 @@ function getAttributes(element) {
|
|
|
229
236
|
addAttribute(attributes, SSML_ATTRS.FADE_OUT, element.fadeOut ?? element.fadeout);
|
|
230
237
|
break;
|
|
231
238
|
case SSML_TAGS.MSTTS_DIALOG:
|
|
239
|
+
break;
|
|
232
240
|
case SSML_TAGS.MSTTS_TTS_EMBEDDING:
|
|
241
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
|
|
242
|
+
break;
|
|
233
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;
|
|
234
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);
|
|
235
251
|
break;
|
|
236
252
|
case SSML_TAGS.PARAGRAPH:
|
|
237
253
|
case SSML_TAGS.SENTENCE:
|
|
@@ -806,7 +822,9 @@ function convertElement(node) {
|
|
|
806
822
|
case SSML_TAGS.MSTTS_TURN: {
|
|
807
823
|
const element = { type: SSML_TAGS.MSTTS_TURN };
|
|
808
824
|
const voice = readAttribute(attributes, SSML_ATTRS.VOICE);
|
|
825
|
+
const speaker = readAttribute(attributes, SSML_ATTRS.SPEAKER);
|
|
809
826
|
if (voice !== void 0) element.voice = voice;
|
|
827
|
+
if (speaker !== void 0) element.speaker = speaker;
|
|
810
828
|
return finishElement(element, node, attributes);
|
|
811
829
|
}
|
|
812
830
|
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO: {
|
|
@@ -823,14 +841,26 @@ function convertElement(node) {
|
|
|
823
841
|
}
|
|
824
842
|
case SSML_TAGS.MSTTS_TTS_EMBEDDING: {
|
|
825
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;
|
|
826
846
|
return finishElement(element, node, attributes);
|
|
827
847
|
}
|
|
828
848
|
case SSML_TAGS.MSTTS_EMBEDDING: {
|
|
829
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;
|
|
830
854
|
return finishElement(element, node, attributes);
|
|
831
855
|
}
|
|
832
856
|
case SSML_TAGS.MSTTS_VOICE_CONVERSION: {
|
|
833
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;
|
|
834
864
|
return finishElement(element, node, attributes);
|
|
835
865
|
}
|
|
836
866
|
default: {
|
|
@@ -1206,6 +1236,7 @@ function elementAttributes(element) {
|
|
|
1206
1236
|
break;
|
|
1207
1237
|
case "mstts:turn":
|
|
1208
1238
|
addAttribute2(attributes, "voice", element.voice);
|
|
1239
|
+
addAttribute2(attributes, "speaker", element.speaker);
|
|
1209
1240
|
break;
|
|
1210
1241
|
case "mstts:backgroundaudio":
|
|
1211
1242
|
addAttribute2(attributes, "src", element.src);
|
|
@@ -1213,6 +1244,18 @@ function elementAttributes(element) {
|
|
|
1213
1244
|
addAttribute2(attributes, "fadein", element.fadeIn ?? element.fadein);
|
|
1214
1245
|
addAttribute2(attributes, "fadeout", element.fadeOut ?? element.fadeout);
|
|
1215
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;
|
|
1216
1259
|
}
|
|
1217
1260
|
return Object.fromEntries(Object.entries(attributes).map(([name, value]) => [name, String(value)]));
|
|
1218
1261
|
}
|
|
@@ -1599,11 +1642,14 @@ function tokenizeElements(source) {
|
|
|
1599
1642
|
}
|
|
1600
1643
|
const selfClosing = /\/\s*>$/.test(raw);
|
|
1601
1644
|
const parent = openElements[openElements.length - 1];
|
|
1645
|
+
const childElementIndex = parent?.childElementCount;
|
|
1646
|
+
if (parent) parent.childElementCount += 1;
|
|
1602
1647
|
const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
|
|
1603
1648
|
const tokenName = nameMatch[1];
|
|
1604
1649
|
const tokenVoiceName = tokenName.toLowerCase() === "voice" ? attributes.get("name") : tokenName.toLowerCase() === "mstts:turn" ? attributes.get("voice") ?? parentVoiceName : parentVoiceName;
|
|
1605
1650
|
tokens.push({
|
|
1606
1651
|
attributes,
|
|
1652
|
+
childElementIndex,
|
|
1607
1653
|
end,
|
|
1608
1654
|
name: tokenName,
|
|
1609
1655
|
parentName: parent?.name,
|
|
@@ -1613,6 +1659,7 @@ function tokenizeElements(source) {
|
|
|
1613
1659
|
});
|
|
1614
1660
|
if (!selfClosing) {
|
|
1615
1661
|
openElements.push({
|
|
1662
|
+
childElementCount: 0,
|
|
1616
1663
|
name: tokenName,
|
|
1617
1664
|
voiceName: tokenVoiceName
|
|
1618
1665
|
});
|
|
@@ -1651,6 +1698,12 @@ function isValidAzureAudioDuration(value) {
|
|
|
1651
1698
|
if (!clock) return false;
|
|
1652
1699
|
return Number(clock[1]) > 0 || Number(clock[2]) > 0 || Number(clock[3]) > 0 || Number(clock[4] ?? 0) > 0;
|
|
1653
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
|
+
}
|
|
1654
1707
|
function attr(token, name) {
|
|
1655
1708
|
return token.attributes.get(name.toLowerCase());
|
|
1656
1709
|
}
|
|
@@ -1790,9 +1843,16 @@ function validateAudioSource(token, source, diagnostics, options, elementName2)
|
|
|
1790
1843
|
}
|
|
1791
1844
|
if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
|
|
1792
1845
|
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must use HTTPS.`);
|
|
1793
|
-
|
|
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)
|
|
1794
1854
|
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> origin "${parsed.origin}" is not allowed.`);
|
|
1795
|
-
else if (!options.allowExternalAudio)
|
|
1855
|
+
else if (!isAllowedOrigin && !options.allowExternalAudio)
|
|
1796
1856
|
addDiagnostic(
|
|
1797
1857
|
diagnostics,
|
|
1798
1858
|
source,
|
|
@@ -1922,28 +1982,40 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceCa
|
|
|
1922
1982
|
validateAudioSource(token, source, diagnostics, options, "audio");
|
|
1923
1983
|
}
|
|
1924
1984
|
if (name === "mstts:turn") {
|
|
1925
|
-
if (!attr(token, "voice")?.trim())
|
|
1926
|
-
addDiagnostic(
|
|
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
|
+
);
|
|
1927
1992
|
if (token.parentName?.toLowerCase() !== "mstts:dialog")
|
|
1928
1993
|
addDiagnostic(diagnostics, source, token.start, "<mstts:turn> is only allowed directly inside <mstts:dialog>.");
|
|
1929
1994
|
}
|
|
1930
1995
|
if (name === "mstts:backgroundaudio") {
|
|
1931
1996
|
validateAudioSource(token, source, diagnostics, options, "mstts:backgroundaudio");
|
|
1932
1997
|
const volume = attr(token, "volume");
|
|
1933
|
-
if (volume &&
|
|
1998
|
+
if (volume !== void 0 && (!/^\d+(?:\.\d+)?$/.test(volume.trim()) || Number(volume) > 100))
|
|
1934
1999
|
addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:backgroundaudio volume> value "${volume}".`);
|
|
1935
2000
|
for (const [attribute, value] of [
|
|
1936
2001
|
["fadein", attr(token, "fadein")],
|
|
1937
2002
|
["fadeout", attr(token, "fadeout")]
|
|
1938
2003
|
]) {
|
|
1939
|
-
if (value && !
|
|
2004
|
+
if (value !== void 0 && !isValidAzureBackgroundAudioDuration(value))
|
|
1940
2005
|
addDiagnostic(
|
|
1941
2006
|
diagnostics,
|
|
1942
2007
|
source,
|
|
1943
2008
|
token.start,
|
|
1944
|
-
`<mstts:backgroundaudio ${attribute}> must be
|
|
2009
|
+
`<mstts:backgroundaudio ${attribute}> must be between 0 and 10000 milliseconds, for example "500ms" or "10s".`
|
|
1945
2010
|
);
|
|
1946
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
|
+
);
|
|
1947
2019
|
if (!token.selfClosing)
|
|
1948
2020
|
addDiagnostic(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
|
|
1949
2021
|
}
|
|
@@ -1975,6 +2047,16 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
1975
2047
|
const tokens = tokenizeElements(ssml);
|
|
1976
2048
|
const speak = tokens.find((token) => token.name.toLowerCase() === "speak");
|
|
1977
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
|
+
}
|
|
1978
2060
|
if (!speak || voices.length === 0)
|
|
1979
2061
|
addDiagnostic(
|
|
1980
2062
|
diagnostics,
|