ssml-builder-js 2.9.0 → 2.11.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 +74 -8
- package/bin/sync-voices.js +62 -0
- package/dist/{chunk-JNJVTEL6.mjs → chunk-FWWMWI2C.mjs} +143 -17
- package/dist/chunk-FWWMWI2C.mjs.map +1 -0
- package/dist/{chunk-7LCSH4SZ.mjs → chunk-SNRI43QJ.mjs} +142 -8
- package/dist/chunk-SNRI43QJ.mjs.map +1 -0
- package/dist/core.d.mts +17 -2
- package/dist/core.d.ts +17 -2
- package/dist/core.js +141 -7
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +1 -1
- package/dist/elements.js +142 -16
- package/dist/elements.js.map +1 -1
- package/dist/elements.mjs +1 -1
- package/dist/index.d.mts +26 -1
- package/dist/index.d.ts +26 -1
- package/dist/index.js +221 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +80 -1
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +7 -0
- package/dist/react.d.ts +7 -0
- package/dist/react.js +247 -17
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +106 -2
- package/dist/react.mjs.map +1 -1
- package/package.json +5 -1
- package/dist/chunk-7LCSH4SZ.mjs.map +0 -1
- package/dist/chunk-JNJVTEL6.mjs.map +0 -1
package/dist/react.js
CHANGED
|
@@ -57,6 +57,7 @@ var ssmlPresets_exports = {};
|
|
|
57
57
|
__export(ssmlPresets_exports, {
|
|
58
58
|
AUDIO_DURATION_DESCRIPTIONS: () => AUDIO_DURATION_DESCRIPTIONS,
|
|
59
59
|
AUDIO_DURATION_PRESETS: () => AUDIO_DURATION_PRESETS,
|
|
60
|
+
BACKGROUND_AUDIO_DURATION_PRESETS: () => BACKGROUND_AUDIO_DURATION_PRESETS,
|
|
60
61
|
BREAK_STRENGTH_PRESETS: () => BREAK_STRENGTH_PRESETS,
|
|
61
62
|
BREAK_TIME_DESCRIPTIONS: () => BREAK_TIME_DESCRIPTIONS,
|
|
62
63
|
BREAK_TIME_PRESETS: () => BREAK_TIME_PRESETS,
|
|
@@ -138,6 +139,9 @@ var AZURE_VOICE_STYLE_MAP = {
|
|
|
138
139
|
],
|
|
139
140
|
"es-ES-ElviraNeural": [],
|
|
140
141
|
"fil-PH-AngeloNeural": [],
|
|
142
|
+
"fil-PH-Angelo:DragonHDLatestNeural": [],
|
|
143
|
+
"fil-PH-BlessicaNeural": [],
|
|
144
|
+
"fil-PH-Blessica:DragonHDLatestNeural": [],
|
|
141
145
|
"fr-FR-DeniseNeural": ["cheerful", "sad"],
|
|
142
146
|
"fr-FR-HenriNeural": ["cheerful", "sad"],
|
|
143
147
|
"id-ID-GadisNeural": [],
|
|
@@ -415,6 +419,7 @@ var SAY_AS_PRESETS = [
|
|
|
415
419
|
var LANGUAGE_PRESETS = ["ja-JP", "en-US", "de-DE", "fr-FR"];
|
|
416
420
|
var SILENCE_VALUE_PRESETS = ["300ms", "500ms", "1s"];
|
|
417
421
|
var AUDIO_DURATION_PRESETS = ["5s", "10s", "30s"];
|
|
422
|
+
var BACKGROUND_AUDIO_DURATION_PRESETS = ["0", "500", "1000", "3000", "10000"];
|
|
418
423
|
var SILENCE_TYPE_PRESETS = [
|
|
419
424
|
"Leading",
|
|
420
425
|
"Tailing",
|
|
@@ -476,8 +481,8 @@ var SSML_ATTRIBUTE_PRESETS = {
|
|
|
476
481
|
},
|
|
477
482
|
"mstts:backgroundaudio": {
|
|
478
483
|
volume: PROSODY_VOLUME_PRESETS,
|
|
479
|
-
fadein:
|
|
480
|
-
fadeout:
|
|
484
|
+
fadein: BACKGROUND_AUDIO_DURATION_PRESETS,
|
|
485
|
+
fadeout: BACKGROUND_AUDIO_DURATION_PRESETS
|
|
481
486
|
},
|
|
482
487
|
silence: {
|
|
483
488
|
type: SILENCE_TYPE_PRESETS,
|
|
@@ -1724,6 +1729,7 @@ var SSML_ATTRS = {
|
|
|
1724
1729
|
MSTTS_XMLNS: "xmlns:mstts",
|
|
1725
1730
|
NAME: "name",
|
|
1726
1731
|
VOICE: "voice",
|
|
1732
|
+
SPEAKER: "speaker",
|
|
1727
1733
|
EFFECT: "effect",
|
|
1728
1734
|
RATE: "rate",
|
|
1729
1735
|
PITCH: "pitch",
|
|
@@ -1754,6 +1760,11 @@ var SSML_ATTRS = {
|
|
|
1754
1760
|
ALIAS: "alias",
|
|
1755
1761
|
MARK: "mark",
|
|
1756
1762
|
URI: "uri",
|
|
1763
|
+
ID: "id",
|
|
1764
|
+
MODEL: "model",
|
|
1765
|
+
PROFILE: "profile",
|
|
1766
|
+
URL: "url",
|
|
1767
|
+
SPEAKER_PROFILE_ID: "speakerProfileId",
|
|
1757
1768
|
TYPE: "type",
|
|
1758
1769
|
VALUE: "value",
|
|
1759
1770
|
FADE_IN: "fadein",
|
|
@@ -1849,6 +1860,7 @@ function getAttributes(element) {
|
|
|
1849
1860
|
break;
|
|
1850
1861
|
case SSML_TAGS.MSTTS_TURN:
|
|
1851
1862
|
addAttribute(attributes, SSML_ATTRS.VOICE, element.voice);
|
|
1863
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER, element.speaker);
|
|
1852
1864
|
break;
|
|
1853
1865
|
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO:
|
|
1854
1866
|
addAttribute(attributes, SSML_ATTRS.SRC, element.src);
|
|
@@ -1857,9 +1869,18 @@ function getAttributes(element) {
|
|
|
1857
1869
|
addAttribute(attributes, SSML_ATTRS.FADE_OUT, element.fadeOut ?? element.fadeout);
|
|
1858
1870
|
break;
|
|
1859
1871
|
case SSML_TAGS.MSTTS_DIALOG:
|
|
1872
|
+
break;
|
|
1860
1873
|
case SSML_TAGS.MSTTS_TTS_EMBEDDING:
|
|
1874
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
|
|
1875
|
+
break;
|
|
1861
1876
|
case SSML_TAGS.MSTTS_EMBEDDING:
|
|
1877
|
+
addAttribute(attributes, SSML_ATTRS.ID, element.id);
|
|
1878
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
|
|
1879
|
+
break;
|
|
1862
1880
|
case SSML_TAGS.MSTTS_VOICE_CONVERSION:
|
|
1881
|
+
addAttribute(attributes, SSML_ATTRS.URL, element.url);
|
|
1882
|
+
addAttribute(attributes, SSML_ATTRS.PROFILE, element.profile);
|
|
1883
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
|
|
1863
1884
|
break;
|
|
1864
1885
|
case SSML_TAGS.PARAGRAPH:
|
|
1865
1886
|
case SSML_TAGS.SENTENCE:
|
|
@@ -2432,7 +2453,9 @@ function convertElement(node) {
|
|
|
2432
2453
|
case SSML_TAGS.MSTTS_TURN: {
|
|
2433
2454
|
const element = { type: SSML_TAGS.MSTTS_TURN };
|
|
2434
2455
|
const voice = readAttribute(attributes, SSML_ATTRS.VOICE);
|
|
2456
|
+
const speaker = readAttribute(attributes, SSML_ATTRS.SPEAKER);
|
|
2435
2457
|
if (voice !== void 0) element.voice = voice;
|
|
2458
|
+
if (speaker !== void 0) element.speaker = speaker;
|
|
2436
2459
|
return finishElement(element, node, attributes);
|
|
2437
2460
|
}
|
|
2438
2461
|
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO: {
|
|
@@ -2449,14 +2472,26 @@ function convertElement(node) {
|
|
|
2449
2472
|
}
|
|
2450
2473
|
case SSML_TAGS.MSTTS_TTS_EMBEDDING: {
|
|
2451
2474
|
const element = { type: SSML_TAGS.MSTTS_TTS_EMBEDDING };
|
|
2475
|
+
const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
|
|
2476
|
+
if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
|
|
2452
2477
|
return finishElement(element, node, attributes);
|
|
2453
2478
|
}
|
|
2454
2479
|
case SSML_TAGS.MSTTS_EMBEDDING: {
|
|
2455
2480
|
const element = { type: SSML_TAGS.MSTTS_EMBEDDING };
|
|
2481
|
+
const id = readAttribute(attributes, SSML_ATTRS.ID);
|
|
2482
|
+
const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
|
|
2483
|
+
if (id !== void 0) element.id = id;
|
|
2484
|
+
if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
|
|
2456
2485
|
return finishElement(element, node, attributes);
|
|
2457
2486
|
}
|
|
2458
2487
|
case SSML_TAGS.MSTTS_VOICE_CONVERSION: {
|
|
2459
2488
|
const element = { type: SSML_TAGS.MSTTS_VOICE_CONVERSION };
|
|
2489
|
+
const url = readAttribute(attributes, SSML_ATTRS.URL);
|
|
2490
|
+
const profile = readAttribute(attributes, SSML_ATTRS.PROFILE);
|
|
2491
|
+
const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
|
|
2492
|
+
if (url !== void 0) element.url = url;
|
|
2493
|
+
if (profile !== void 0) element.profile = profile;
|
|
2494
|
+
if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
|
|
2460
2495
|
return finishElement(element, node, attributes);
|
|
2461
2496
|
}
|
|
2462
2497
|
default: {
|
|
@@ -2638,6 +2673,9 @@ var AZURE_VOICE_DEFINITIONS = [
|
|
|
2638
2673
|
},
|
|
2639
2674
|
{ name: "es-ES-ElviraNeural", locale: "es-ES" },
|
|
2640
2675
|
{ name: "fil-PH-AngeloNeural", locale: "fil-PH" },
|
|
2676
|
+
{ name: "fil-PH-Angelo:DragonHDLatestNeural", locale: "fil-PH" },
|
|
2677
|
+
{ name: "fil-PH-BlessicaNeural", locale: "fil-PH" },
|
|
2678
|
+
{ name: "fil-PH-Blessica:DragonHDLatestNeural", locale: "fil-PH" },
|
|
2641
2679
|
{ name: "fr-FR-DeniseNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
|
|
2642
2680
|
{ name: "fr-FR-HenriNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
|
|
2643
2681
|
{ name: "id-ID-GadisNeural", locale: "id-ID" },
|
|
@@ -2732,6 +2770,18 @@ var ALLOWED_SILENCE_TYPES = /* @__PURE__ */ new Set([
|
|
|
2732
2770
|
"Enumerationcomma"
|
|
2733
2771
|
]);
|
|
2734
2772
|
var ALLOWED_VISEME_TYPES = /* @__PURE__ */ new Set(["redlips_front", "FacialExpression"]);
|
|
2773
|
+
var DEFAULT_PREVIEW_TAGS = /* @__PURE__ */ new Set(["mstts:voiceconversion"]);
|
|
2774
|
+
function featureStatusForTag(name, options) {
|
|
2775
|
+
const tagName = canonicalTagName(name);
|
|
2776
|
+
const configured = Object.entries(options.tagStatuses ?? {}).find(
|
|
2777
|
+
([candidate]) => canonicalTagName(candidate) === tagName
|
|
2778
|
+
)?.[1];
|
|
2779
|
+
if (configured) return configured;
|
|
2780
|
+
if ((options.previewTags ?? [...DEFAULT_PREVIEW_TAGS]).some((candidate) => canonicalTagName(candidate) === tagName))
|
|
2781
|
+
return "preview";
|
|
2782
|
+
if ((options.deprecatedTags ?? []).some((candidate) => canonicalTagName(candidate) === tagName)) return "deprecated";
|
|
2783
|
+
return void 0;
|
|
2784
|
+
}
|
|
2735
2785
|
function decodeAttribute(value) {
|
|
2736
2786
|
return value.replace(
|
|
2737
2787
|
/&(?:amp|apos|gt|lt|quot);/gi,
|
|
@@ -2791,11 +2841,14 @@ function tokenizeElements(source) {
|
|
|
2791
2841
|
}
|
|
2792
2842
|
const selfClosing = /\/\s*>$/.test(raw);
|
|
2793
2843
|
const parent = openElements[openElements.length - 1];
|
|
2844
|
+
const childElementIndex = parent?.childElementCount;
|
|
2845
|
+
if (parent) parent.childElementCount += 1;
|
|
2794
2846
|
const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
|
|
2795
2847
|
const tokenName = nameMatch[1];
|
|
2796
2848
|
const tokenVoiceName = tokenName.toLowerCase() === "voice" ? attributes.get("name") : tokenName.toLowerCase() === "mstts:turn" ? attributes.get("voice") ?? parentVoiceName : parentVoiceName;
|
|
2797
2849
|
tokens.push({
|
|
2798
2850
|
attributes,
|
|
2851
|
+
childElementIndex,
|
|
2799
2852
|
end,
|
|
2800
2853
|
name: tokenName,
|
|
2801
2854
|
parentName: parent?.name,
|
|
@@ -2805,6 +2858,7 @@ function tokenizeElements(source) {
|
|
|
2805
2858
|
});
|
|
2806
2859
|
if (!selfClosing) {
|
|
2807
2860
|
openElements.push({
|
|
2861
|
+
childElementCount: 0,
|
|
2808
2862
|
name: tokenName,
|
|
2809
2863
|
voiceName: tokenVoiceName
|
|
2810
2864
|
});
|
|
@@ -2843,6 +2897,12 @@ function isValidAzureAudioDuration(value) {
|
|
|
2843
2897
|
if (!clock) return false;
|
|
2844
2898
|
return Number(clock[1]) > 0 || Number(clock[2]) > 0 || Number(clock[3]) > 0 || Number(clock[4] ?? 0) > 0;
|
|
2845
2899
|
}
|
|
2900
|
+
function isValidAzureBackgroundAudioDuration(value) {
|
|
2901
|
+
const match = /^(\d+)$/.exec(value.trim());
|
|
2902
|
+
if (!match) return false;
|
|
2903
|
+
const milliseconds = Number(match[1]);
|
|
2904
|
+
return Number.isFinite(milliseconds) && milliseconds >= 0 && milliseconds <= 1e4;
|
|
2905
|
+
}
|
|
2846
2906
|
function attr(token, name) {
|
|
2847
2907
|
return token.attributes.get(name.toLowerCase());
|
|
2848
2908
|
}
|
|
@@ -2972,9 +3032,16 @@ function validateAudioSource(token, source, diagnostics, options, elementName2)
|
|
|
2972
3032
|
}
|
|
2973
3033
|
if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
|
|
2974
3034
|
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must use HTTPS.`);
|
|
2975
|
-
|
|
3035
|
+
const isAllowedOrigin = options.allowedAudioOrigins?.some((allowedOrigin) => {
|
|
3036
|
+
try {
|
|
3037
|
+
return new URL(allowedOrigin).origin === parsed.origin;
|
|
3038
|
+
} catch {
|
|
3039
|
+
return allowedOrigin === parsed.origin;
|
|
3040
|
+
}
|
|
3041
|
+
}) ?? false;
|
|
3042
|
+
if (options.allowedAudioOrigins && !isAllowedOrigin)
|
|
2976
3043
|
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> origin "${parsed.origin}" is not allowed.`);
|
|
2977
|
-
else if (!options.allowExternalAudio)
|
|
3044
|
+
else if (!isAllowedOrigin && !options.allowExternalAudio)
|
|
2978
3045
|
addDiagnostic(
|
|
2979
3046
|
diagnostics,
|
|
2980
3047
|
source,
|
|
@@ -2984,6 +3051,25 @@ function validateAudioSource(token, source, diagnostics, options, elementName2)
|
|
|
2984
3051
|
}
|
|
2985
3052
|
function validateElement(token, source, diagnostics, voiceName, options, voiceCatalog) {
|
|
2986
3053
|
const name = token.name.toLowerCase();
|
|
3054
|
+
const tagStatus = featureStatusForTag(token.name, options);
|
|
3055
|
+
if (tagStatus === "preview")
|
|
3056
|
+
addDiagnostic(
|
|
3057
|
+
diagnostics,
|
|
3058
|
+
source,
|
|
3059
|
+
token.start,
|
|
3060
|
+
`<${token.name}> is an Azure Speech preview feature and may change or require preview access.`,
|
|
3061
|
+
"warning",
|
|
3062
|
+
"azure-preview-tag"
|
|
3063
|
+
);
|
|
3064
|
+
if (tagStatus === "deprecated")
|
|
3065
|
+
addDiagnostic(
|
|
3066
|
+
diagnostics,
|
|
3067
|
+
source,
|
|
3068
|
+
token.start,
|
|
3069
|
+
`<${token.name}> is deprecated by Azure Speech; migrate to a supported alternative.`,
|
|
3070
|
+
"info",
|
|
3071
|
+
"azure-deprecated-tag"
|
|
3072
|
+
);
|
|
2987
3073
|
if (name === "voice" && !attr(token, "name")?.trim())
|
|
2988
3074
|
addDiagnostic(diagnostics, source, token.start, '<voice> requires a non-empty "name" attribute.');
|
|
2989
3075
|
if (name === "break") {
|
|
@@ -3104,28 +3190,40 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceCa
|
|
|
3104
3190
|
validateAudioSource(token, source, diagnostics, options, "audio");
|
|
3105
3191
|
}
|
|
3106
3192
|
if (name === "mstts:turn") {
|
|
3107
|
-
if (!attr(token, "voice")?.trim())
|
|
3108
|
-
addDiagnostic(
|
|
3193
|
+
if (!attr(token, "voice")?.trim() && !attr(token, "speaker")?.trim())
|
|
3194
|
+
addDiagnostic(
|
|
3195
|
+
diagnostics,
|
|
3196
|
+
source,
|
|
3197
|
+
token.start,
|
|
3198
|
+
'<mstts:turn> requires a non-empty "voice" or "speaker" attribute.'
|
|
3199
|
+
);
|
|
3109
3200
|
if (token.parentName?.toLowerCase() !== "mstts:dialog")
|
|
3110
3201
|
addDiagnostic(diagnostics, source, token.start, "<mstts:turn> is only allowed directly inside <mstts:dialog>.");
|
|
3111
3202
|
}
|
|
3112
3203
|
if (name === "mstts:backgroundaudio") {
|
|
3113
3204
|
validateAudioSource(token, source, diagnostics, options, "mstts:backgroundaudio");
|
|
3114
3205
|
const volume = attr(token, "volume");
|
|
3115
|
-
if (volume &&
|
|
3206
|
+
if (volume !== void 0 && (!/^\d+(?:\.\d+)?$/.test(volume.trim()) || Number(volume) > 100))
|
|
3116
3207
|
addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:backgroundaudio volume> value "${volume}".`);
|
|
3117
3208
|
for (const [attribute, value] of [
|
|
3118
3209
|
["fadein", attr(token, "fadein")],
|
|
3119
3210
|
["fadeout", attr(token, "fadeout")]
|
|
3120
3211
|
]) {
|
|
3121
|
-
if (value && !
|
|
3212
|
+
if (value !== void 0 && !isValidAzureBackgroundAudioDuration(value))
|
|
3122
3213
|
addDiagnostic(
|
|
3123
3214
|
diagnostics,
|
|
3124
3215
|
source,
|
|
3125
3216
|
token.start,
|
|
3126
|
-
`<mstts:backgroundaudio ${attribute}> must be
|
|
3217
|
+
`<mstts:backgroundaudio ${attribute}> must be between 0 and 10000 milliseconds, for example "500ms" or "10s".`
|
|
3127
3218
|
);
|
|
3128
3219
|
}
|
|
3220
|
+
if (token.parentName?.toLowerCase() !== "speak" || token.childElementIndex !== 0)
|
|
3221
|
+
addDiagnostic(
|
|
3222
|
+
diagnostics,
|
|
3223
|
+
source,
|
|
3224
|
+
token.start,
|
|
3225
|
+
"<mstts:backgroundaudio> must be the first element directly under <speak>."
|
|
3226
|
+
);
|
|
3129
3227
|
if (!token.selfClosing)
|
|
3130
3228
|
addDiagnostic(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
|
|
3131
3229
|
}
|
|
@@ -3157,6 +3255,16 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
3157
3255
|
const tokens = tokenizeElements(ssml);
|
|
3158
3256
|
const speak = tokens.find((token) => token.name.toLowerCase() === "speak");
|
|
3159
3257
|
const voices = tokens.filter((token) => token.name.toLowerCase() === "voice");
|
|
3258
|
+
const backgroundAudioTokens = tokens.filter((token) => token.name.toLowerCase() === "mstts:backgroundaudio");
|
|
3259
|
+
for (const [index, token] of backgroundAudioTokens.entries()) {
|
|
3260
|
+
if (index > 0)
|
|
3261
|
+
addDiagnostic(
|
|
3262
|
+
diagnostics,
|
|
3263
|
+
ssml,
|
|
3264
|
+
token.start,
|
|
3265
|
+
"An SSML document can contain at most one <mstts:backgroundaudio> element."
|
|
3266
|
+
);
|
|
3267
|
+
}
|
|
3160
3268
|
if (!speak || voices.length === 0)
|
|
3161
3269
|
addDiagnostic(
|
|
3162
3270
|
diagnostics,
|
|
@@ -3173,6 +3281,24 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
3173
3281
|
const name = attr(token, "name")?.trim();
|
|
3174
3282
|
const language = attr(token, "xml:lang")?.trim() || (speak ? attr(speak, "xml:lang")?.trim() : void 0);
|
|
3175
3283
|
const definition = name ? voiceCatalog.get(name.toLowerCase()) : void 0;
|
|
3284
|
+
if (name && definition?.status === "preview")
|
|
3285
|
+
addDiagnostic(
|
|
3286
|
+
diagnostics,
|
|
3287
|
+
ssml,
|
|
3288
|
+
token.start,
|
|
3289
|
+
`Voice "${name}" is an Azure Speech preview voice and may change or require preview access.`,
|
|
3290
|
+
"warning",
|
|
3291
|
+
"azure-preview-voice"
|
|
3292
|
+
);
|
|
3293
|
+
if (name && definition?.status === "deprecated")
|
|
3294
|
+
addDiagnostic(
|
|
3295
|
+
diagnostics,
|
|
3296
|
+
ssml,
|
|
3297
|
+
token.start,
|
|
3298
|
+
`Voice "${name}" is deprecated by Azure Speech; migrate to a supported voice.`,
|
|
3299
|
+
"info",
|
|
3300
|
+
"azure-deprecated-voice"
|
|
3301
|
+
);
|
|
3176
3302
|
if (name && !definition && policySeverity)
|
|
3177
3303
|
addDiagnostic(
|
|
3178
3304
|
diagnostics,
|
|
@@ -4860,7 +4986,7 @@ var SSML_COMPLETION_SNIPPETS = [
|
|
|
4860
4986
|
},
|
|
4861
4987
|
{
|
|
4862
4988
|
label: "mstts:backgroundaudio",
|
|
4863
|
-
insertText: `<mstts:backgroundaudio src="\${1:https://example.com/audio.mp3}" volume="\${2
|
|
4989
|
+
insertText: `<mstts:backgroundaudio src="\${1:https://example.com/audio.mp3}" volume="\${2:70}" fadein="\${3:1000}" fadeout="\${4:1000}" />`
|
|
4864
4990
|
},
|
|
4865
4991
|
{
|
|
4866
4992
|
label: "mstts:ttsembedding",
|
|
@@ -5561,18 +5687,18 @@ var SSML_TAG_DEFINITIONS = [
|
|
|
5561
5687
|
},
|
|
5562
5688
|
{
|
|
5563
5689
|
name: "volume",
|
|
5564
|
-
description: "The background audio volume
|
|
5565
|
-
example: "
|
|
5690
|
+
description: "The background audio volume from 0 to 100.",
|
|
5691
|
+
example: "70"
|
|
5566
5692
|
},
|
|
5567
5693
|
{
|
|
5568
5694
|
name: "fadein",
|
|
5569
|
-
description: "The fade-in duration
|
|
5570
|
-
example: "
|
|
5695
|
+
description: "The fade-in duration in milliseconds from 0 to 10000.",
|
|
5696
|
+
example: "1000"
|
|
5571
5697
|
},
|
|
5572
5698
|
{
|
|
5573
5699
|
name: "fadeout",
|
|
5574
|
-
description: "The fade-out duration
|
|
5575
|
-
example: "
|
|
5700
|
+
description: "The fade-out duration in milliseconds from 0 to 10000.",
|
|
5701
|
+
example: "1000"
|
|
5576
5702
|
}
|
|
5577
5703
|
]
|
|
5578
5704
|
},
|
|
@@ -6226,6 +6352,46 @@ function updateNodesAtPath(nodes, path, update) {
|
|
|
6226
6352
|
function updateTextAtPath(document2, path, value) {
|
|
6227
6353
|
return { ...document2, children: updateNodesAtPath(document2.children ?? [], path, () => value) };
|
|
6228
6354
|
}
|
|
6355
|
+
function getNodeAtPath(nodes, path) {
|
|
6356
|
+
let current;
|
|
6357
|
+
let currentNodes = nodes;
|
|
6358
|
+
for (const index of path) {
|
|
6359
|
+
current = currentNodes[index];
|
|
6360
|
+
if (current === void 0) return void 0;
|
|
6361
|
+
if (!isElement(current)) {
|
|
6362
|
+
currentNodes = [];
|
|
6363
|
+
} else {
|
|
6364
|
+
currentNodes = current.children ?? [];
|
|
6365
|
+
}
|
|
6366
|
+
}
|
|
6367
|
+
return current;
|
|
6368
|
+
}
|
|
6369
|
+
function updateElementAtPath(document2, path, update) {
|
|
6370
|
+
return {
|
|
6371
|
+
...document2,
|
|
6372
|
+
children: updateNodesAtPath(document2.children ?? [], path, (node) => isElement(node) ? update(node) : node)
|
|
6373
|
+
};
|
|
6374
|
+
}
|
|
6375
|
+
function updateElementText(element, value) {
|
|
6376
|
+
return { ...element, children: [value] };
|
|
6377
|
+
}
|
|
6378
|
+
function updateOptionalElementProperty(document2, path, property, value) {
|
|
6379
|
+
return updateElementAtPath(document2, path, (element) => {
|
|
6380
|
+
const next = { ...element };
|
|
6381
|
+
if (value.trim()) next[property] = value;
|
|
6382
|
+
else delete next[property];
|
|
6383
|
+
return next;
|
|
6384
|
+
});
|
|
6385
|
+
}
|
|
6386
|
+
function addDialogTurn(document2, path) {
|
|
6387
|
+
return updateElementAtPath(document2, path, (element) => ({
|
|
6388
|
+
...element,
|
|
6389
|
+
children: [
|
|
6390
|
+
...element.children ?? [],
|
|
6391
|
+
{ type: "mstts:turn", voice: "en-US-JennyNeural", children: ["New dialog turn"] }
|
|
6392
|
+
]
|
|
6393
|
+
}));
|
|
6394
|
+
}
|
|
6229
6395
|
function wrapTextAtPath(document2, path, start, end, tag, attributes) {
|
|
6230
6396
|
return {
|
|
6231
6397
|
...document2,
|
|
@@ -6277,6 +6443,7 @@ function VisualSsmlEditor({
|
|
|
6277
6443
|
const [selection, setSelection] = (0, import_react3.useState)({ start: 0, end: 0 });
|
|
6278
6444
|
const textLeaves = (0, import_react3.useMemo)(() => collectTextLeaves(document2.children ?? []), [document2]);
|
|
6279
6445
|
const selectedLeaf = textLeaves.find((leaf) => leaf.path.join(".") === selectedPath?.join(".")) ?? textLeaves[0];
|
|
6446
|
+
const selectedElement = selectedPath ? getNodeAtPath(document2.children ?? [], selectedPath) : void 0;
|
|
6280
6447
|
const diagnostics = (0, import_react3.useMemo)(() => validateAzureSsml(buildSsml(document2)), [document2]);
|
|
6281
6448
|
const commit = (nextDocument) => onChange?.(nextDocument);
|
|
6282
6449
|
const applyWrapper = (tag, attributes) => {
|
|
@@ -6294,7 +6461,70 @@ function VisualSsmlEditor({
|
|
|
6294
6461
|
selectedPath,
|
|
6295
6462
|
onSelect: setSelectedPath
|
|
6296
6463
|
}
|
|
6297
|
-
)))), /* @__PURE__ */ React.createElement("div", { className: "ssml-editor-visual-form" },
|
|
6464
|
+
)))), /* @__PURE__ */ React.createElement("div", { className: "ssml-editor-visual-form" }, selectedElement && isElement(selectedElement) && selectedElement.type === "mstts:dialog" ? /* @__PURE__ */ React.createElement("fieldset", null, /* @__PURE__ */ React.createElement("legend", null, "Dialog"), /* @__PURE__ */ React.createElement("p", null, "Add and edit speaker turns in this dialog."), /* @__PURE__ */ React.createElement(
|
|
6465
|
+
"button",
|
|
6466
|
+
{
|
|
6467
|
+
type: "button",
|
|
6468
|
+
disabled: readOnly,
|
|
6469
|
+
onClick: () => commit(addDialogTurn(document2, selectedPath ?? []))
|
|
6470
|
+
},
|
|
6471
|
+
"Add turn"
|
|
6472
|
+
)) : selectedElement && isElement(selectedElement) && selectedElement.type === "mstts:turn" ? /* @__PURE__ */ React.createElement("fieldset", null, /* @__PURE__ */ React.createElement("legend", null, "Dialog turn"), /* @__PURE__ */ React.createElement("label", null, "Voice", /* @__PURE__ */ React.createElement(
|
|
6473
|
+
"input",
|
|
6474
|
+
{
|
|
6475
|
+
value: selectedElement.voice ?? "",
|
|
6476
|
+
readOnly,
|
|
6477
|
+
onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "voice", event.target.value))
|
|
6478
|
+
}
|
|
6479
|
+
)), /* @__PURE__ */ React.createElement("label", null, "Speaker", /* @__PURE__ */ React.createElement(
|
|
6480
|
+
"input",
|
|
6481
|
+
{
|
|
6482
|
+
value: selectedElement.speaker ?? "",
|
|
6483
|
+
readOnly,
|
|
6484
|
+
onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "speaker", event.target.value))
|
|
6485
|
+
}
|
|
6486
|
+
)), /* @__PURE__ */ React.createElement("label", null, "Turn text", /* @__PURE__ */ React.createElement(
|
|
6487
|
+
"textarea",
|
|
6488
|
+
{
|
|
6489
|
+
value: selectedElement.children?.filter((child) => typeof child === "string").join("") ?? "",
|
|
6490
|
+
readOnly,
|
|
6491
|
+
onChange: (event) => commit(
|
|
6492
|
+
updateElementAtPath(
|
|
6493
|
+
document2,
|
|
6494
|
+
selectedPath ?? [],
|
|
6495
|
+
(element) => updateElementText(element, event.target.value)
|
|
6496
|
+
)
|
|
6497
|
+
)
|
|
6498
|
+
}
|
|
6499
|
+
))) : selectedElement && isElement(selectedElement) && selectedElement.type === "mstts:backgroundaudio" ? /* @__PURE__ */ React.createElement("fieldset", null, /* @__PURE__ */ React.createElement("legend", null, "Background audio"), /* @__PURE__ */ React.createElement("label", null, "Source URL", /* @__PURE__ */ React.createElement(
|
|
6500
|
+
"input",
|
|
6501
|
+
{
|
|
6502
|
+
value: selectedElement.src ?? "",
|
|
6503
|
+
readOnly,
|
|
6504
|
+
onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "src", event.target.value))
|
|
6505
|
+
}
|
|
6506
|
+
)), /* @__PURE__ */ React.createElement("label", null, "Volume", /* @__PURE__ */ React.createElement(
|
|
6507
|
+
"input",
|
|
6508
|
+
{
|
|
6509
|
+
value: selectedElement.volume === void 0 ? "" : String(selectedElement.volume),
|
|
6510
|
+
readOnly,
|
|
6511
|
+
onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "volume", event.target.value))
|
|
6512
|
+
}
|
|
6513
|
+
)), /* @__PURE__ */ React.createElement("label", null, "Fade in", /* @__PURE__ */ React.createElement(
|
|
6514
|
+
"input",
|
|
6515
|
+
{
|
|
6516
|
+
value: selectedElement.fadeIn === void 0 ? "" : String(selectedElement.fadeIn),
|
|
6517
|
+
readOnly,
|
|
6518
|
+
onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "fadeIn", event.target.value))
|
|
6519
|
+
}
|
|
6520
|
+
)), /* @__PURE__ */ React.createElement("label", null, "Fade out", /* @__PURE__ */ React.createElement(
|
|
6521
|
+
"input",
|
|
6522
|
+
{
|
|
6523
|
+
value: selectedElement.fadeOut === void 0 ? "" : String(selectedElement.fadeOut),
|
|
6524
|
+
readOnly,
|
|
6525
|
+
onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "fadeOut", event.target.value))
|
|
6526
|
+
}
|
|
6527
|
+
))) : selectedLeaf ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("label", null, "Text", /* @__PURE__ */ React.createElement(
|
|
6298
6528
|
"textarea",
|
|
6299
6529
|
{
|
|
6300
6530
|
value: selectedLeaf.value,
|