@ssml-builder-js/ssml-editor-react 2.7.0 → 2.8.1
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/CHANGELOG.md +15 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +181 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +181 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/SsmlEditor.tsx +25 -2
- package/src/buttonVisibility.ts +1 -0
- package/src/constants/azureVoiceStyleMap.generated.ts +94 -0
- package/src/constants/ssmlPresets.ts +18 -2
- package/src/formatXml.ts +1 -0
- package/src/hooks/useSsmlEditorState.ts +3 -3
- package/src/locales.ts +14 -0
- package/src/ssmlCodeLens.ts +19 -4
- package/src/ssmlCompletion.ts +4 -0
- package/src/ssmlHover.ts +11 -0
- package/src/ssmlInsertions.ts +24 -1
- package/test/index.test.ts +21 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ssml-builder-js/ssml-editor-react",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.1",
|
|
4
4
|
"description": "React-based SSML editor component",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -52,6 +52,6 @@
|
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@ssml-builder-js/ssml-core": "^2.
|
|
55
|
+
"@ssml-builder-js/ssml-core": "^2.8.1"
|
|
56
56
|
}
|
|
57
57
|
}
|
package/src/SsmlEditor.tsx
CHANGED
|
@@ -6,6 +6,8 @@ import { isSsmlEditorButtonVisible, type SsmlEditorButton, type SsmlEditorButton
|
|
|
6
6
|
import {
|
|
7
7
|
BREAK_TIME_DESCRIPTIONS,
|
|
8
8
|
BREAK_TIME_PRESETS,
|
|
9
|
+
AUDIO_DURATION_DESCRIPTIONS,
|
|
10
|
+
AUDIO_DURATION_PRESETS,
|
|
9
11
|
EMPHASIS_LEVEL_DESCRIPTIONS,
|
|
10
12
|
EMPHASIS_LEVEL_PRESETS,
|
|
11
13
|
EXPRESS_AS_STYLE_DESCRIPTIONS,
|
|
@@ -39,7 +41,7 @@ import { useSsmlEditorState } from "./hooks/useSsmlEditorState";
|
|
|
39
41
|
import { useSsmlMonaco } from "./hooks/useSsmlMonaco";
|
|
40
42
|
import { editorStyles as styles } from "./styles/editorStyles";
|
|
41
43
|
const UNGROUPED_TOOLBAR_GROUP = "__ssml-editor-ungrouped__";
|
|
42
|
-
const TIMING_POPOVER_TAGS = new Set(["break", "mstts:silence"]);
|
|
44
|
+
const TIMING_POPOVER_TAGS = new Set(["break", "mstts:silence", "mstts:audioduration"]);
|
|
43
45
|
const PROSODY_POPOVER_TAGS = new Set(["prosody", "mstts:express-as", "voice", "emphasis"]);
|
|
44
46
|
const TEXT_POPOVER_TAGS = new Set(["sub", "say-as", "phoneme", "w", "lang"]);
|
|
45
47
|
|
|
@@ -365,13 +367,34 @@ export const SSML_INSERTIONS = [
|
|
|
365
367
|
mode: "insert",
|
|
366
368
|
}),
|
|
367
369
|
},
|
|
370
|
+
{
|
|
371
|
+
id: "mstts:audioduration",
|
|
372
|
+
icon: "◷",
|
|
373
|
+
tagName: "mstts:audioduration",
|
|
374
|
+
selfClosing: true,
|
|
375
|
+
labels: { ja: "音声長", en: "Audio duration" },
|
|
376
|
+
descriptions: {
|
|
377
|
+
ja: "合成音声の目標時間を指定します。",
|
|
378
|
+
en: "Sets the target duration of synthesized audio.",
|
|
379
|
+
},
|
|
380
|
+
parameterDescription: {
|
|
381
|
+
ja: "目標時間を選択します。",
|
|
382
|
+
en: "Selects the target duration.",
|
|
383
|
+
},
|
|
384
|
+
options: createInsertionOptions(AUDIO_DURATION_PRESETS, AUDIO_DURATION_DESCRIPTIONS),
|
|
385
|
+
createTemplate: (value) => ({
|
|
386
|
+
prefix: `<mstts:audioduration value="${value}"/>`,
|
|
387
|
+
suffix: "",
|
|
388
|
+
mode: "insert",
|
|
389
|
+
}),
|
|
390
|
+
},
|
|
368
391
|
] satisfies readonly SsmlInsertionDefinition[];
|
|
369
392
|
|
|
370
393
|
const DEFAULT_INSERTION_GROUPS = [
|
|
371
394
|
{
|
|
372
395
|
id: "pauses",
|
|
373
396
|
labels: { ja: "間・無音", en: "Pauses" },
|
|
374
|
-
insertionIds: ["break", "mstts:silence"],
|
|
397
|
+
insertionIds: ["break", "mstts:silence", "mstts:audioduration"],
|
|
375
398
|
},
|
|
376
399
|
{
|
|
377
400
|
id: "prosody",
|
package/src/buttonVisibility.ts
CHANGED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/** Generated by scripts/sync-azure-voices.ts. */
|
|
2
|
+
export const AZURE_VOICE_STYLE_MAP = {
|
|
3
|
+
"de-DE-ConradNeural": ["cheerful", "sad"],
|
|
4
|
+
"de-DE-KatjaNeural": ["cheerful", "sad"],
|
|
5
|
+
"en-US-AndrewNeural": ["empathetic", "relieved"],
|
|
6
|
+
"en-US-GuyNeural": [
|
|
7
|
+
"angry",
|
|
8
|
+
"cheerful",
|
|
9
|
+
"excited",
|
|
10
|
+
"friendly",
|
|
11
|
+
"hopeful",
|
|
12
|
+
"newscast",
|
|
13
|
+
"sad",
|
|
14
|
+
"shouting",
|
|
15
|
+
"terrified",
|
|
16
|
+
"unfriendly",
|
|
17
|
+
"whispering",
|
|
18
|
+
],
|
|
19
|
+
"en-US-JennyMultilingualNeural": [
|
|
20
|
+
"cheerful",
|
|
21
|
+
"empathetic",
|
|
22
|
+
"excited",
|
|
23
|
+
"friendly",
|
|
24
|
+
"hopeful",
|
|
25
|
+
"sad",
|
|
26
|
+
"shouting",
|
|
27
|
+
"terrified",
|
|
28
|
+
"unfriendly",
|
|
29
|
+
"whispering",
|
|
30
|
+
],
|
|
31
|
+
"en-US-JennyNeural": [
|
|
32
|
+
"assistant",
|
|
33
|
+
"chat",
|
|
34
|
+
"customerservice",
|
|
35
|
+
"newscast",
|
|
36
|
+
"cheerful",
|
|
37
|
+
"empathetic",
|
|
38
|
+
"excited",
|
|
39
|
+
"friendly",
|
|
40
|
+
"hopeful",
|
|
41
|
+
"sad",
|
|
42
|
+
"shouting",
|
|
43
|
+
"terrified",
|
|
44
|
+
"unfriendly",
|
|
45
|
+
"whispering",
|
|
46
|
+
],
|
|
47
|
+
"es-ES-ElviraNeural": [],
|
|
48
|
+
"fil-PH-AngeloNeural": [],
|
|
49
|
+
"fr-FR-DeniseNeural": ["cheerful", "sad"],
|
|
50
|
+
"fr-FR-HenriNeural": ["cheerful", "sad"],
|
|
51
|
+
"id-ID-GadisNeural": [],
|
|
52
|
+
"it-IT-ElsaNeural": ["cheerful", "sad"],
|
|
53
|
+
"ja-JP-KeitaNeural": ["chat"],
|
|
54
|
+
"ja-JP-MayuNeural": ["calm", "cheerful", "sad"],
|
|
55
|
+
"ja-JP-NanamiNeural": ["chat", "customerservice", "cheerful", "whispering", "sad"],
|
|
56
|
+
"ko-KR-SunHiNeural": ["cheerful", "sad"],
|
|
57
|
+
"ms-MY-YasminNeural": [],
|
|
58
|
+
"pt-BR-FranciscaNeural": ["calm"],
|
|
59
|
+
"ru-RU-SvetlanaNeural": ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"],
|
|
60
|
+
"th-TH-PremwadeeNeural": [],
|
|
61
|
+
"vi-VN-HoaiMyNeural": [],
|
|
62
|
+
"zh-CN-XiaoxiaoNeural": [
|
|
63
|
+
"assistant",
|
|
64
|
+
"chat",
|
|
65
|
+
"customerservice",
|
|
66
|
+
"newscast",
|
|
67
|
+
"cheerful",
|
|
68
|
+
"empathetic",
|
|
69
|
+
"excited",
|
|
70
|
+
"friendly",
|
|
71
|
+
"hopeful",
|
|
72
|
+
"sad",
|
|
73
|
+
"terrified",
|
|
74
|
+
"whispering",
|
|
75
|
+
"poetry-reading",
|
|
76
|
+
"sports_commentary",
|
|
77
|
+
"sports_commentary_excited",
|
|
78
|
+
"story",
|
|
79
|
+
],
|
|
80
|
+
"zh-CN-YunxiNeural": [
|
|
81
|
+
"narration-relaxed",
|
|
82
|
+
"embarrassed",
|
|
83
|
+
"fearful",
|
|
84
|
+
"sad",
|
|
85
|
+
"disgruntled",
|
|
86
|
+
"serious",
|
|
87
|
+
"angry",
|
|
88
|
+
"depressed",
|
|
89
|
+
"chat",
|
|
90
|
+
"cheerful",
|
|
91
|
+
"assistant",
|
|
92
|
+
],
|
|
93
|
+
"zh-TW-HsiaoChenNeural": [],
|
|
94
|
+
} as const satisfies Readonly<Record<string, readonly string[]>>;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
type SsmlPresetDescription = Readonly<Record<"ja" | "en", string>>;
|
|
2
2
|
|
|
3
|
+
// @ts-expect-error The Node strip-types test runner requires the explicit TypeScript extension.
|
|
4
|
+
import { AZURE_VOICE_STYLE_MAP } from "./azureVoiceStyleMap.generated.ts";
|
|
5
|
+
|
|
3
6
|
export interface SsmlPreset {
|
|
4
7
|
id: string;
|
|
5
8
|
label: string;
|
|
@@ -119,7 +122,7 @@ export function getExpressAsStyleCategory(style: string): ExpressAsStyleCategory
|
|
|
119
122
|
return "other";
|
|
120
123
|
}
|
|
121
124
|
|
|
122
|
-
|
|
125
|
+
const LEGACY_VOICE_STYLE_MAP = {
|
|
123
126
|
"ja-JP-MayuNeural": ["calm", "cheerful", "sad"],
|
|
124
127
|
"ja-JP-KeitaNeural": ["chat"],
|
|
125
128
|
"ja-JP-NanamiNeural": ["chat", "customerservice", "cheerful", "whispering", "sad"],
|
|
@@ -206,7 +209,11 @@ export const VOICE_STYLE_MAP = {
|
|
|
206
209
|
"ru-RU-SvetlanaNeural": ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"],
|
|
207
210
|
} as const satisfies Readonly<Record<string, readonly ExpressAsStyle[]>>;
|
|
208
211
|
|
|
209
|
-
|
|
212
|
+
/** Snapshot generated from Azure's List Voices API. The legacy map is only a safety fallback for an empty generated file. */
|
|
213
|
+
export const VOICE_STYLE_MAP =
|
|
214
|
+
Object.keys(AZURE_VOICE_STYLE_MAP).length > 0 ? AZURE_VOICE_STYLE_MAP : LEGACY_VOICE_STYLE_MAP;
|
|
215
|
+
|
|
216
|
+
const VOICE_STYLE_MAP_BY_NORMALIZED_NAME = new Map<string, readonly string[]>(
|
|
210
217
|
Object.entries(VOICE_STYLE_MAP).map(([voiceName, styles]) => [voiceName.toLowerCase(), styles]),
|
|
211
218
|
);
|
|
212
219
|
|
|
@@ -254,6 +261,7 @@ export const SAY_AS_PRESETS = [
|
|
|
254
261
|
] as const;
|
|
255
262
|
export const LANGUAGE_PRESETS = ["ja-JP", "en-US", "de-DE", "fr-FR"] as const;
|
|
256
263
|
export const SILENCE_VALUE_PRESETS = ["300ms", "500ms", "1s"] as const;
|
|
264
|
+
export const AUDIO_DURATION_PRESETS = ["5s", "10s", "30s"] as const;
|
|
257
265
|
export const SILENCE_TYPE_PRESETS = [
|
|
258
266
|
"Leading",
|
|
259
267
|
"Tailing",
|
|
@@ -262,6 +270,11 @@ export const SILENCE_TYPE_PRESETS = [
|
|
|
262
270
|
"Semicolon",
|
|
263
271
|
"Enumerationcomma",
|
|
264
272
|
] as const;
|
|
273
|
+
export const AUDIO_DURATION_DESCRIPTIONS = {
|
|
274
|
+
"5s": { ja: "5秒", en: "5 seconds" },
|
|
275
|
+
"10s": { ja: "10秒", en: "10 seconds" },
|
|
276
|
+
"30s": { ja: "30秒", en: "30 seconds" },
|
|
277
|
+
} as const;
|
|
265
278
|
export const PHONEME_ALPHABET_PRESETS = ["ipa", "sapi", "ups", "x-sampa"] as const;
|
|
266
279
|
export const VISEME_TYPE_PRESETS = ["redlips_front", "FacialExpression"] as const;
|
|
267
280
|
|
|
@@ -306,6 +319,9 @@ export const SSML_ATTRIBUTE_PRESETS = {
|
|
|
306
319
|
type: SILENCE_TYPE_PRESETS,
|
|
307
320
|
value: SILENCE_VALUE_PRESETS,
|
|
308
321
|
},
|
|
322
|
+
"mstts:audioduration": {
|
|
323
|
+
value: AUDIO_DURATION_PRESETS,
|
|
324
|
+
},
|
|
309
325
|
silence: {
|
|
310
326
|
type: SILENCE_TYPE_PRESETS,
|
|
311
327
|
value: SILENCE_VALUE_PRESETS,
|
package/src/formatXml.ts
CHANGED
|
@@ -24,7 +24,7 @@ import type { MonacoEditor, SsmlSyntaxError } from "../ssmlDiagnostics";
|
|
|
24
24
|
import type { SsmlCodeLensAction } from "../ssmlCodeLens";
|
|
25
25
|
import { SELECTION_OVERLAY_ABOVE_THRESHOLD_LINES } from "../constants/ui";
|
|
26
26
|
|
|
27
|
-
const TIMING_INSERTION_TAGS = new Set(["break", "mstts:silence"]);
|
|
27
|
+
const TIMING_INSERTION_TAGS = new Set(["break", "mstts:silence", "mstts:audioduration"]);
|
|
28
28
|
const PROSODY_INSERTION_TAGS = new Set(["prosody", "mstts:express-as", "voice", "emphasis"]);
|
|
29
29
|
const TEXT_INSERTION_TAGS = new Set(["sub", "say-as", "phoneme", "w", "lang"]);
|
|
30
30
|
|
|
@@ -53,8 +53,8 @@ export interface UseSsmlEditorStateOptions {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
interface PendingCodeLensAttribute {
|
|
56
|
-
insertionId: "rate" | "pitch" | "break";
|
|
57
|
-
attributeName: "rate" | "pitch" | "time";
|
|
56
|
+
insertionId: "rate" | "pitch" | "break" | "mstts:audioduration";
|
|
57
|
+
attributeName: "rate" | "pitch" | "time" | "value";
|
|
58
58
|
tagRange: { start: number; end: number };
|
|
59
59
|
modelVersionId: number;
|
|
60
60
|
}
|
package/src/locales.ts
CHANGED
|
@@ -273,6 +273,13 @@ export const SSML_HOVER_COPY: Readonly<Record<SsmlEditorLocale, SsmlHoverLocale>
|
|
|
273
273
|
type: { title: "type", description: "ビゼームイベントの形式。" },
|
|
274
274
|
},
|
|
275
275
|
},
|
|
276
|
+
"mstts:audioduration": {
|
|
277
|
+
title: "音声長",
|
|
278
|
+
description: "合成音声の目標時間を指定します。",
|
|
279
|
+
parameters: {
|
|
280
|
+
value: { title: "value", description: "目標時間。例: `10s`、`5000ms`、`00:00:10`。" },
|
|
281
|
+
},
|
|
282
|
+
},
|
|
276
283
|
},
|
|
277
284
|
},
|
|
278
285
|
en: {
|
|
@@ -430,6 +437,13 @@ export const SSML_HOVER_COPY: Readonly<Record<SsmlEditorLocale, SsmlHoverLocale>
|
|
|
430
437
|
type: { title: "type", description: "The viseme event format." },
|
|
431
438
|
},
|
|
432
439
|
},
|
|
440
|
+
"mstts:audioduration": {
|
|
441
|
+
title: "Audio duration",
|
|
442
|
+
description: "Sets the target duration of synthesized audio.",
|
|
443
|
+
parameters: {
|
|
444
|
+
value: { title: "value", description: "The target duration, such as `10s`, `5000ms`, or `00:00:10`." },
|
|
445
|
+
},
|
|
446
|
+
},
|
|
433
447
|
},
|
|
434
448
|
},
|
|
435
449
|
};
|
package/src/ssmlCodeLens.ts
CHANGED
|
@@ -5,8 +5,8 @@ import type { SsmlTagRange } from "./ssmlContext";
|
|
|
5
5
|
export type SsmlCodeLensAction =
|
|
6
6
|
| {
|
|
7
7
|
type: "attribute";
|
|
8
|
-
insertionId: "rate" | "pitch" | "break";
|
|
9
|
-
attributeName: "rate" | "pitch" | "time";
|
|
8
|
+
insertionId: "rate" | "pitch" | "break" | "mstts:audioduration";
|
|
9
|
+
attributeName: "rate" | "pitch" | "time" | "value";
|
|
10
10
|
tagRange: SsmlTagRange;
|
|
11
11
|
}
|
|
12
12
|
| {
|
|
@@ -139,7 +139,7 @@ export function registerSsmlCodeLens(
|
|
|
139
139
|
break;
|
|
140
140
|
}
|
|
141
141
|
const tag = source.slice(tagStart, tagEnd + 1);
|
|
142
|
-
const tagName = tag.match(/^<\s*(prosody|break)\b/i)?.[1]?.toLowerCase();
|
|
142
|
+
const tagName = tag.match(/^<\s*(prosody|break|mstts:audioduration)\b/i)?.[1]?.toLowerCase();
|
|
143
143
|
index = tagEnd + 1;
|
|
144
144
|
if (!tagName) {
|
|
145
145
|
continue;
|
|
@@ -171,7 +171,22 @@ export function registerSsmlCodeLens(
|
|
|
171
171
|
elementRange,
|
|
172
172
|
}),
|
|
173
173
|
);
|
|
174
|
-
} else if (/\/\s*>$/.test(tag)) {
|
|
174
|
+
} else if (tagName === "mstts:audioduration" && /\/\s*>$/.test(tag)) {
|
|
175
|
+
lenses.push(
|
|
176
|
+
createLens(
|
|
177
|
+
model,
|
|
178
|
+
tagStart,
|
|
179
|
+
tagEnd + 1,
|
|
180
|
+
`⚡ Duration: ${getAttributeValue(tag, "value") ?? "default"} (Click to edit)`,
|
|
181
|
+
{ type: "attribute", insertionId: "mstts:audioduration", attributeName: "value", tagRange },
|
|
182
|
+
),
|
|
183
|
+
createLens(model, tagStart, tagEnd + 1, "Delete", {
|
|
184
|
+
type: "delete",
|
|
185
|
+
tagRange,
|
|
186
|
+
elementRange,
|
|
187
|
+
}),
|
|
188
|
+
);
|
|
189
|
+
} else if (tagName === "break" && /\/\s*>$/.test(tag)) {
|
|
175
190
|
lenses.push(
|
|
176
191
|
createLens(
|
|
177
192
|
model,
|
package/src/ssmlCompletion.ts
CHANGED
|
@@ -42,6 +42,10 @@ const SSML_COMPLETION_SNIPPETS = [
|
|
|
42
42
|
label: "mstts:express-as",
|
|
43
43
|
insertText: `<mstts:express-as style="cheerful">\${1:text}</mstts:express-as>`,
|
|
44
44
|
},
|
|
45
|
+
{
|
|
46
|
+
label: "mstts:audioduration",
|
|
47
|
+
insertText: '<mstts:audioduration value="10s" />',
|
|
48
|
+
},
|
|
45
49
|
{
|
|
46
50
|
label: "sub",
|
|
47
51
|
insertText: `<sub alias="\${1:読み}">\${2:漢字}</sub>`,
|
package/src/ssmlHover.ts
CHANGED
|
@@ -328,6 +328,17 @@ const SSML_TAG_DEFINITIONS: readonly SsmlTagDefinition[] = [
|
|
|
328
328
|
},
|
|
329
329
|
],
|
|
330
330
|
},
|
|
331
|
+
{
|
|
332
|
+
name: "mstts:audioduration",
|
|
333
|
+
description: "Sets the target duration of synthesized audio.",
|
|
334
|
+
parameters: [
|
|
335
|
+
{
|
|
336
|
+
name: "value",
|
|
337
|
+
description: "The target duration, such as `10s`, `5000ms`, or `00:00:10`.",
|
|
338
|
+
example: "10s",
|
|
339
|
+
},
|
|
340
|
+
],
|
|
341
|
+
},
|
|
331
342
|
] as const;
|
|
332
343
|
|
|
333
344
|
export { SSML_TAG_DEFINITIONS };
|
package/src/ssmlInsertions.ts
CHANGED
|
@@ -2,6 +2,8 @@ import type { SsmlEditorLocalizedText } from "./locales";
|
|
|
2
2
|
import {
|
|
3
3
|
BREAK_TIME_DESCRIPTIONS,
|
|
4
4
|
BREAK_TIME_PRESETS,
|
|
5
|
+
AUDIO_DURATION_DESCRIPTIONS,
|
|
6
|
+
AUDIO_DURATION_PRESETS,
|
|
5
7
|
EMPHASIS_LEVEL_DESCRIPTIONS,
|
|
6
8
|
EMPHASIS_LEVEL_PRESETS,
|
|
7
9
|
EXPRESS_AS_STYLE_DESCRIPTIONS,
|
|
@@ -320,13 +322,34 @@ export const SSML_INSERTIONS = [
|
|
|
320
322
|
mode: "insert",
|
|
321
323
|
}),
|
|
322
324
|
},
|
|
325
|
+
{
|
|
326
|
+
id: "mstts:audioduration",
|
|
327
|
+
icon: "◷",
|
|
328
|
+
tagName: "mstts:audioduration",
|
|
329
|
+
selfClosing: true,
|
|
330
|
+
labels: { ja: "音声長", en: "Audio duration" },
|
|
331
|
+
descriptions: {
|
|
332
|
+
ja: "合成音声の目標時間を指定します。",
|
|
333
|
+
en: "Sets the target duration of synthesized audio.",
|
|
334
|
+
},
|
|
335
|
+
parameterDescription: {
|
|
336
|
+
ja: "目標時間を選択します。",
|
|
337
|
+
en: "Selects the target duration.",
|
|
338
|
+
},
|
|
339
|
+
options: createInsertionOptions(AUDIO_DURATION_PRESETS, AUDIO_DURATION_DESCRIPTIONS),
|
|
340
|
+
createTemplate: (value) => ({
|
|
341
|
+
prefix: `<mstts:audioduration value="${value}"/>`,
|
|
342
|
+
suffix: "",
|
|
343
|
+
mode: "insert",
|
|
344
|
+
}),
|
|
345
|
+
},
|
|
323
346
|
] satisfies readonly SsmlEditorInsertionDefinition[];
|
|
324
347
|
|
|
325
348
|
export const DEFAULT_INSERTION_GROUPS = [
|
|
326
349
|
{
|
|
327
350
|
id: "pauses",
|
|
328
351
|
labels: { ja: "間・無音", en: "Pauses" },
|
|
329
|
-
insertionIds: ["break", "mstts:silence"],
|
|
352
|
+
insertionIds: ["break", "mstts:silence", "mstts:audioduration"],
|
|
330
353
|
},
|
|
331
354
|
{
|
|
332
355
|
id: "prosody",
|
package/test/index.test.ts
CHANGED
|
@@ -80,7 +80,23 @@ test("provides attribute values from the active SSML tag and attribute", () => {
|
|
|
80
80
|
);
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
-
test("
|
|
83
|
+
test("provides the audioduration snippet and value completions", () => {
|
|
84
|
+
const tagSuggestions = getSuggestions("<");
|
|
85
|
+
assert.equal(
|
|
86
|
+
tagSuggestions.some(
|
|
87
|
+
(suggestion) =>
|
|
88
|
+
suggestion.label === "mstts:audioduration" && suggestion.insertText === '<mstts:audioduration value="10s" />',
|
|
89
|
+
),
|
|
90
|
+
true,
|
|
91
|
+
);
|
|
92
|
+
const valueSuggestions = getSuggestions('<mstts:audioduration value="');
|
|
93
|
+
assert.equal(
|
|
94
|
+
valueSuggestions.some((suggestion) => suggestion.label === "10s"),
|
|
95
|
+
true,
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("generates CodeLens actions for prosody, break, and audioduration tags", () => {
|
|
84
100
|
let provider: Parameters<Monaco["languages"]["registerCodeLensProvider"]>[1] | undefined;
|
|
85
101
|
let commandId: string | undefined;
|
|
86
102
|
let commandHandler: ((accessor: unknown, ...args: unknown[]) => void) | undefined;
|
|
@@ -102,7 +118,8 @@ test("generates CodeLens actions for prosody and break tags", () => {
|
|
|
102
118
|
const editor = {
|
|
103
119
|
addAction: () => ({ dispose() {} }),
|
|
104
120
|
} as never;
|
|
105
|
-
const source =
|
|
121
|
+
const source =
|
|
122
|
+
'<prosody rate="+10%" pitch="high">Hello</prosody><break time="500ms"/><mstts:audioduration value="10s"/>';
|
|
106
123
|
registerSsmlCodeLens(monaco, editor, () => undefined);
|
|
107
124
|
const model = {
|
|
108
125
|
getValue: () => source,
|
|
@@ -118,6 +135,8 @@ test("generates CodeLens actions for prosody and break tags", () => {
|
|
|
118
135
|
"Unwrap",
|
|
119
136
|
"⚡ Time: 500ms (Click to edit)",
|
|
120
137
|
"Delete",
|
|
138
|
+
"⚡ Duration: 10s (Click to edit)",
|
|
139
|
+
"Delete",
|
|
121
140
|
],
|
|
122
141
|
);
|
|
123
142
|
assert.equal(commandId, "ssml-editor.codeLens");
|