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/README.md
CHANGED
|
@@ -91,7 +91,7 @@ Pages Router と App Router のどちらでも、Monaco を `next/dynamic` の `
|
|
|
91
91
|
"use client";
|
|
92
92
|
import dynamic from "next/dynamic";
|
|
93
93
|
const SsmlEditor = dynamic(
|
|
94
|
-
() => import("
|
|
94
|
+
() => import("ssml-builder-js/react").then(({ SsmlEditor }) => SsmlEditor),
|
|
95
95
|
{ ssr: false },
|
|
96
96
|
);
|
|
97
97
|
|
|
@@ -179,6 +179,8 @@ SSML の検証は、構文、Azure 固有の静的な意味、実サービスの
|
|
|
179
179
|
|
|
180
180
|
`voice`、`prosody`、`break`、`express-as`、`say-as`、`phoneme`、`audio`、`lang`、`mark` に加えて、`mstts:dialog`、`mstts:turn`、`mstts:backgroundaudio`、`mstts:ttsembedding`、`mstts:embedding`、`mstts:voiceconversion` を型付きで表現できます。`type: "custom"` と `name` を指定すれば、未定義の XML 要素や追加属性も扱えます。`mstts:` 要素を含むドキュメントを生成すると、必要な Azure Speech 名前空間が自動的に追加されます。
|
|
181
181
|
|
|
182
|
+
`mstts:turn` は `voice` またはマルチトーカー用の `speaker` を指定でき、`mstts:ttsembedding` は `speakerProfileId`、`mstts:embedding` は `id`、`mstts:voiceconversion` は `url` と `profile` を専用プロパティで指定できます。`mstts:backgroundaudio` は `<speak>` 直下の先頭要素として 1 文書に 1 つだけ配置し、`fadein`/`fadeout` は単位なしの生ミリ秒(0〜10000)で指定します。
|
|
183
|
+
|
|
182
184
|
`validateAzureSsml` は Azure Speech へ送信する前に実行する事前静的チェックです。返却される診断の `source` はパッケージ側の静的解析結果であることを示し、Azure Speech サービス側でのランタイム生成結果や実際の合成可否を表すものではありません。
|
|
183
185
|
|
|
184
186
|
翻訳などで本文だけを置き換える場合は、`mapSsmlTextNodes` に変換関数を渡します。変換関数には直近の親タグと祖先タグの `path` が渡され、戻り値は `string` または `Promise<string>` を指定できます。`validateAzureSsml` は音声、属性値、音声スタイル、文字数、`audio` URL/オリジンを検証します。
|
|
@@ -206,7 +208,7 @@ const diagnostics = validateAzureSsml(ssml, {
|
|
|
206
208
|
});
|
|
207
209
|
```
|
|
208
210
|
|
|
209
|
-
音声カタログは `AzureVoiceDefinition` の `name`、`locale`、`secondaryLocales`、`styles`、`supportedTags`、`unsupportedTags`、`models` で表現できます。`voiceDefinitions`(または `voiceCatalog`)を渡すと、組み込み台帳を外部定義で補完・上書きできます。`supportedTags`/`unsupportedTags` による音声別制約違反は `azure-unsupported-tag-for-voice` としてエラーになります。組み込み台帳にない音声は `azure-unknown-voice`(`unknownVoicePolicy` に従う)、登録済み音声への非対応スタイルは `azure-unsupported-style`、ロケール不一致は `azure-locale-mismatch` として区別されます。
|
|
211
|
+
音声カタログは `AzureVoiceDefinition` の `name`、`locale`、`secondaryLocales`、`styles`、`supportedTags`、`unsupportedTags`、`models`、`regions`、`status` で表現できます。`voiceDefinitions`(または `voiceCatalog`)を渡すと、組み込み台帳を外部定義で補完・上書きできます。`supportedTags`/`unsupportedTags` による音声別制約違反は `azure-unsupported-tag-for-voice` としてエラーになります。組み込み台帳にない音声は `azure-unknown-voice`(`unknownVoicePolicy` に従う)、登録済み音声への非対応スタイルは `azure-unsupported-style`、ロケール不一致は `azure-locale-mismatch` として区別されます。
|
|
210
212
|
|
|
211
213
|
`npm run sync:voices -- --regions eastus,japaneast` は Azure List Voices API をリージョンごとに取得し、重複を除いた TypeScript 音声定義と `azureVoiceCatalog.json`(生成日時、API バージョン、リージョン、収録数)を更新します。認証情報は `AZURE_SPEECH_KEY` と `AZURE_SPEECH_REGION(S)`、または CLI オプションで指定します。
|
|
212
214
|
|
|
@@ -245,6 +247,24 @@ Azure Speech は `<audio>` の URL を取得するため、任意の URL をそ
|
|
|
245
247
|
|
|
246
248
|
`editMode="visual"` を指定するかツールバーの **Visual** を選ぶと、XML を直接見ずに構造ツリーとパンくずから親要素を選択し、フォームで本文の変更、rate、pitch、emotion、pause、pronunciation の適用ができます。Azure の配置違反や属性エラーはビジュアル領域にも表示されます。`editMode="code"` で Monaco に戻ります。
|
|
247
249
|
|
|
250
|
+
Visual Editor と Code Editor の対応要素は次のとおりです。
|
|
251
|
+
|
|
252
|
+
| 要素 | Visual Editor | Code Editor |
|
|
253
|
+
| --- | --- | --- |
|
|
254
|
+
| `voice`、`prosody`、`break`、`express-as`、`say-as`、`phoneme` | フォーム操作・構造ツリー | 完全対応 |
|
|
255
|
+
| `mstts:dialog` / `mstts:turn` | 話者ターンの追加・音声/話者/本文編集 | 完全対応・補完あり |
|
|
256
|
+
| `mstts:backgroundaudio` | URL、音量、フェードイン/アウト編集 | 完全対応・補完あり |
|
|
257
|
+
| `mstts:ttsembedding`、`mstts:embedding`、`mstts:voiceconversion` | 構造ツリーで保持 | 完全対応 |
|
|
258
|
+
| 未定義の XML 要素 | 構造ツリーで保持 | `custom` として編集 |
|
|
259
|
+
|
|
260
|
+
Azure Speech のライフサイクル対応状況は次のとおりです。GA 要素は通常の静的検証対象、プレビュー要素・音声は Warning、非推奨要素・音声は Info の診断を返します。`AzureVoiceDefinition.status` と `AzureValidationOptions.tagStatuses` で外部カタログの状態も指定できます。
|
|
261
|
+
|
|
262
|
+
| 状態 | 診断 | 対象 |
|
|
263
|
+
| --- | --- | --- |
|
|
264
|
+
| GA | なし | 標準 SSML、`mstts:dialog`、`mstts:backgroundaudio` |
|
|
265
|
+
| Preview | Warning | `mstts:voiceconversion`、`previewTags` / `tagStatuses` に指定した要素、`status: "preview"` の音声 |
|
|
266
|
+
| Deprecated | Info | `deprecatedTags` / `tagStatuses`、`status: "deprecated"` の音声 |
|
|
267
|
+
|
|
248
268
|
`<mstts:express-as>` の `style` 属性補完と標準の感情メニューは、カーソルまたは選択範囲を囲む最内の `<voice name="...">` が対応するスタイルだけを表示します。音声名が未指定の場合は全候補を表示し、登録済みでスタイル非対応の音声や未登録の音声では候補がないことを表示します。`emotionStyles` を指定した場合、感情メニューではその値と登録済み音声の対応スタイルの共通部分を使用します。
|
|
249
269
|
|
|
250
270
|
```tsx
|
|
@@ -347,6 +367,19 @@ const audio = await client.synthesize(ssml);
|
|
|
347
367
|
|
|
348
368
|
帯域幅を抑える場合は `audio-24khz-48kbitrate-mono-mp3` または `audio-16khz-32kbitrate-mono-mp3` を指定できます。
|
|
349
369
|
|
|
370
|
+
最新の音声一覧は公開 API から取得できます。複数リージョンを指定した場合は音声名で重複排除され、各音声の `regions` と `metadata`(`voiceCount`、`generatedAt`、`apiVersion`、`regions`)が返ります。
|
|
371
|
+
|
|
372
|
+
```ts
|
|
373
|
+
import { fetchAzureVoiceCatalog } from "ssml-builder-js";
|
|
374
|
+
|
|
375
|
+
const catalog = await fetchAzureVoiceCatalog({
|
|
376
|
+
apiKey: process.env.AZURE_SPEECH_KEY!,
|
|
377
|
+
region: ["eastus", "japaneast"],
|
|
378
|
+
});
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
CLI では `npx ssml-builder sync-voices --region eastus --output ./azure-voices.json` を実行します。キーは `AZURE_SPEECH_KEY`(または `--key`)、リージョンは `AZURE_SPEECH_REGION(S)`(または `--region(s)`)から指定できます。
|
|
382
|
+
|
|
350
383
|
```ts
|
|
351
384
|
const client = new AzureTtsClient({
|
|
352
385
|
subscriptionKey: process.env.AZURE_SPEECH_KEY!,
|
|
@@ -378,8 +411,8 @@ Next.js Route Handler ではキーをブラウザへ渡さず、サーバー側
|
|
|
378
411
|
|
|
379
412
|
```ts
|
|
380
413
|
// app/api/synthesize/route.ts
|
|
381
|
-
import { AzureTtsClient, AzureTtsError } from "
|
|
382
|
-
import { validateAzureSsml, validateSsml } from "
|
|
414
|
+
import { AzureTtsClient, AzureTtsError } from "ssml-builder-js";
|
|
415
|
+
import { validateAzureSsml, validateSsml } from "ssml-builder-js/core";
|
|
383
416
|
|
|
384
417
|
export const runtime = "nodejs";
|
|
385
418
|
|
|
@@ -539,7 +572,7 @@ For both the Pages Router and App Router, keep Monaco in a client-only module wi
|
|
|
539
572
|
"use client";
|
|
540
573
|
import dynamic from "next/dynamic";
|
|
541
574
|
const SsmlEditor = dynamic(
|
|
542
|
-
() => import("
|
|
575
|
+
() => import("ssml-builder-js/react").then(({ SsmlEditor }) => SsmlEditor),
|
|
543
576
|
{ ssr: false },
|
|
544
577
|
);
|
|
545
578
|
|
|
@@ -627,6 +660,8 @@ SSML validation separates XML syntax, Azure-specific static semantics, and runti
|
|
|
627
660
|
|
|
628
661
|
Typed representations are available for elements such as `voice`, `prosody`, `break`, `express-as`, `say-as`, `phoneme`, `audio`, `lang`, and `mark`, plus `mstts:dialog`, `mstts:turn`, `mstts:backgroundaudio`, `mstts:ttsembedding`, `mstts:embedding`, and `mstts:voiceconversion`. Use `type: "custom"` and `name` to handle undefined XML elements or additional attributes. When a document contains `mstts:` elements, the required Azure Speech namespace is added automatically.
|
|
629
662
|
|
|
663
|
+
`mstts:turn` accepts either `voice` or the multi-talker `speaker` property. The typed extension properties include `speakerProfileId` for `mstts:ttsembedding`, `id` for `mstts:embedding`, and `url` plus `profile` for `mstts:voiceconversion`. `mstts:backgroundaudio` must be the first direct child element of `<speak>` and may appear only once per document; `fadein` and `fadeout` are raw milliseconds from 0 through 10000.
|
|
664
|
+
|
|
630
665
|
`validateAzureSsml` is a preflight static check performed by this package before sending SSML to Azure Speech. The `source` on each returned diagnostic identifies package-side static analysis; it is independent of Azure Speech's runtime generation result and does not guarantee that synthesis will succeed.
|
|
631
666
|
|
|
632
667
|
Use `mapSsmlTextNodes` to replace translatable content without changing tags, attributes, or nesting. The transform receives the immediate parent tag and ancestor `path`, and may return a `string` or a `Promise<string>`. The third argument supports `skipTags` and a `filter` callback; `phoneme`, `say-as`, and `sub` are skipped by default. The callback receives `parentTag`, decoded `parentAttributes`, `ancestorTags`, and `path`.
|
|
@@ -650,7 +685,7 @@ const diagnostics = validateAzureSsml(ssml, {
|
|
|
650
685
|
});
|
|
651
686
|
```
|
|
652
687
|
|
|
653
|
-
The catalog is represented by `AzureVoiceDefinition` with `name`, `locale`, optional `secondaryLocales`, `styles`, `supportedTags`, `unsupportedTags`, and `
|
|
688
|
+
The catalog is represented by `AzureVoiceDefinition` with `name`, `locale`, optional `secondaryLocales`, `styles`, `supportedTags`, `unsupportedTags`, `models`, `regions`, and `status`. Pass `voiceDefinitions` (or `voiceCatalog`) to supplement or override the built-in catalog with an external definition. A tag that violates `supportedTags` or `unsupportedTags` produces `azure-unsupported-tag-for-voice` with error severity. `customVoiceStyleMap` remains supported for backward compatibility and overrides styles for the named voice. Diagnostics distinguish an unregistered voice (`azure-unknown-voice`, controlled by `unknownVoicePolicy`), an unsupported style on a registered voice (`azure-unsupported-style`), and a locale mismatch (`azure-locale-mismatch`). The `<mstts:audioduration value="10s"/>` element accepts positive `ms` or `s` values and `hh:mm:ss[.fff]` clock values.
|
|
654
689
|
|
|
655
690
|
`npm run sync:voices -- --regions eastus,japaneast` fetches Azure's List Voices API for each region, deduplicates the results, and updates the generated TypeScript definitions plus `azureVoiceCatalog.json` with generation time, API version, regions, and voice count. Provide credentials through `AZURE_SPEECH_KEY` and `AZURE_SPEECH_REGION(S)`, or CLI options.
|
|
656
691
|
|
|
@@ -689,6 +724,24 @@ Azure Speech fetches `<audio>` URLs, so a server that accepts arbitrary user-pro
|
|
|
689
724
|
|
|
690
725
|
Set `editMode="visual"`, or choose **Visual** in the toolbar, to edit without viewing XML source. The structured editor provides a structure tree, parent breadcrumb selection, text editing, and form actions for rate, pitch, emotion, pause, and pronunciation. Azure placement and attribute diagnostics are shown in the visual area. Choose `editMode="code"` to return to Monaco.
|
|
691
726
|
|
|
727
|
+
The Visual Editor and Code Editor support the following elements:
|
|
728
|
+
|
|
729
|
+
| Element | Visual Editor | Code Editor |
|
|
730
|
+
| --- | --- | --- |
|
|
731
|
+
| `voice`, `prosody`, `break`, `express-as`, `say-as`, `phoneme` | Form actions and structure tree | Full support |
|
|
732
|
+
| `mstts:dialog` / `mstts:turn` | Add turns and edit voice, speaker, and text | Full support with completion |
|
|
733
|
+
| `mstts:backgroundaudio` | Edit URL, volume, fade-in, and fade-out | Full support with completion |
|
|
734
|
+
| `mstts:ttsembedding`, `mstts:embedding`, `mstts:voiceconversion` | Preserved in the structure tree | Full support |
|
|
735
|
+
| Unknown XML elements | Preserved in the structure tree | Editable as `custom` |
|
|
736
|
+
|
|
737
|
+
Azure lifecycle support is reported by static validation: GA elements produce no lifecycle diagnostic, preview elements and voices produce a Warning, and deprecated elements and voices produce an Info diagnostic. Use `AzureVoiceDefinition.status` and `AzureValidationOptions.tagStatuses` for external catalog metadata.
|
|
738
|
+
|
|
739
|
+
| Status | Diagnostic | Examples |
|
|
740
|
+
| --- | --- | --- |
|
|
741
|
+
| GA | None | Standard SSML, `mstts:dialog`, `mstts:backgroundaudio` |
|
|
742
|
+
| Preview | Warning | `mstts:voiceconversion`, tags listed in `previewTags` / `tagStatuses`, voices with `status: "preview"` |
|
|
743
|
+
| Deprecated | Info | Tags listed in `deprecatedTags` / `tagStatuses`, voices with `status: "deprecated"` |
|
|
744
|
+
|
|
692
745
|
Completion for the `<mstts:express-as>` `style` attribute and the built-in Emotion menu only show styles supported by the innermost `<voice name="...">` around the cursor or selection. When no voice name is available, all candidates are shown; a registered voice without supported styles or an explicitly unregistered voice displays an empty-state message. When `emotionStyles` is supplied, the Emotion menu uses its intersection with the registered voice's supported styles.
|
|
693
746
|
|
|
694
747
|
```tsx
|
|
@@ -791,6 +844,19 @@ const audio = await client.synthesize(ssml);
|
|
|
791
844
|
|
|
792
845
|
For lower bandwidth, choose `audio-24khz-48kbitrate-mono-mp3` or `audio-16khz-32kbitrate-mono-mp3`.
|
|
793
846
|
|
|
847
|
+
Fetch the current voice catalog from Azure with `fetchAzureVoiceCatalog`. Multiple regions are deduplicated by voice name, and each voice includes its available `regions`; the result also contains `metadata` with `voiceCount`, `generatedAt`, `apiVersion`, and `regions`.
|
|
848
|
+
|
|
849
|
+
```ts
|
|
850
|
+
import { fetchAzureVoiceCatalog } from "ssml-builder-js";
|
|
851
|
+
|
|
852
|
+
const catalog = await fetchAzureVoiceCatalog({
|
|
853
|
+
apiKey: process.env.AZURE_SPEECH_KEY!,
|
|
854
|
+
region: ["eastus", "japaneast"],
|
|
855
|
+
});
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
The public updater CLI is `npx ssml-builder sync-voices --region eastus --output ./azure-voices.json`. It reads the key from `AZURE_SPEECH_KEY` (or `--key`) and regions from `AZURE_SPEECH_REGION(S)` (or `--region(s)`).
|
|
859
|
+
|
|
794
860
|
```ts
|
|
795
861
|
const client = new AzureTtsClient({
|
|
796
862
|
subscriptionKey: process.env.AZURE_SPEECH_KEY!,
|
|
@@ -822,8 +888,8 @@ Keep the subscription key on the server by using a Next.js Route Handler (or an
|
|
|
822
888
|
|
|
823
889
|
```ts
|
|
824
890
|
// app/api/synthesize/route.ts
|
|
825
|
-
import { AzureTtsClient, AzureTtsError } from "
|
|
826
|
-
import { validateAzureSsml, validateSsml } from "
|
|
891
|
+
import { AzureTtsClient, AzureTtsError } from "ssml-builder-js";
|
|
892
|
+
import { validateAzureSsml, validateSsml } from "ssml-builder-js/core";
|
|
827
893
|
|
|
828
894
|
export const runtime = "nodejs";
|
|
829
895
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { writeFile } = require("node:fs/promises");
|
|
4
|
+
const { resolve } = require("node:path");
|
|
5
|
+
const { fetchAzureVoiceCatalog } = require("../dist/index.js");
|
|
6
|
+
|
|
7
|
+
function usage() {
|
|
8
|
+
return [
|
|
9
|
+
"Usage: ssml-builder sync-voices --region <region[,region...]> --output <file>",
|
|
10
|
+
"",
|
|
11
|
+
"Environment variables: AZURE_SPEECH_KEY, AZURE_SPEECH_REGION(S)",
|
|
12
|
+
].join("\n");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseArgs(args) {
|
|
16
|
+
const options = {};
|
|
17
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
18
|
+
const argument = args[index];
|
|
19
|
+
if (argument === "--region" || argument === "--regions") {
|
|
20
|
+
options.regions = (args[++index] || "")
|
|
21
|
+
.split(",")
|
|
22
|
+
.map((value) => value.trim())
|
|
23
|
+
.filter(Boolean);
|
|
24
|
+
} else if (argument === "--output") {
|
|
25
|
+
options.output = args[++index];
|
|
26
|
+
} else if (argument === "--key") {
|
|
27
|
+
options.apiKey = args[++index];
|
|
28
|
+
} else if (argument === "--help" || argument === "-h") {
|
|
29
|
+
console.log(usage());
|
|
30
|
+
process.exit(0);
|
|
31
|
+
} else {
|
|
32
|
+
throw new Error(`Unknown option: ${argument}\n\n${usage()}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return options;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function main() {
|
|
39
|
+
const [command, ...args] = process.argv.slice(2);
|
|
40
|
+
if (command !== "sync-voices") throw new Error(usage());
|
|
41
|
+
const options = parseArgs(args);
|
|
42
|
+
const regions =
|
|
43
|
+
options.regions ||
|
|
44
|
+
(process.env.AZURE_SPEECH_REGIONS || process.env.AZURE_SPEECH_REGION || "")
|
|
45
|
+
.split(",")
|
|
46
|
+
.map((value) => value.trim())
|
|
47
|
+
.filter(Boolean);
|
|
48
|
+
const apiKey = options.apiKey || process.env.AZURE_SPEECH_KEY;
|
|
49
|
+
const output = options.output;
|
|
50
|
+
if (!apiKey) throw new Error("Set AZURE_SPEECH_KEY or pass --key.");
|
|
51
|
+
if (regions.length === 0) throw new Error("Set AZURE_SPEECH_REGION(S) or pass --region(s).");
|
|
52
|
+
if (!output) throw new Error(`Pass --output <file>.\n\n${usage()}`);
|
|
53
|
+
|
|
54
|
+
const catalog = await fetchAzureVoiceCatalog({ apiKey, region: regions });
|
|
55
|
+
await writeFile(resolve(output), `${JSON.stringify(catalog, null, 2)}\n`, "utf8");
|
|
56
|
+
console.log(`Wrote ${catalog.metadata.voiceCount} Azure voice definitions to ${resolve(output)}.`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
main().catch((error) => {
|
|
60
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
61
|
+
process.exitCode = 1;
|
|
62
|
+
});
|
|
@@ -542,6 +542,7 @@ var ssmlPresets_exports = {};
|
|
|
542
542
|
__export(ssmlPresets_exports, {
|
|
543
543
|
AUDIO_DURATION_DESCRIPTIONS: () => AUDIO_DURATION_DESCRIPTIONS,
|
|
544
544
|
AUDIO_DURATION_PRESETS: () => AUDIO_DURATION_PRESETS,
|
|
545
|
+
BACKGROUND_AUDIO_DURATION_PRESETS: () => BACKGROUND_AUDIO_DURATION_PRESETS,
|
|
545
546
|
BREAK_STRENGTH_PRESETS: () => BREAK_STRENGTH_PRESETS,
|
|
546
547
|
BREAK_TIME_DESCRIPTIONS: () => BREAK_TIME_DESCRIPTIONS,
|
|
547
548
|
BREAK_TIME_PRESETS: () => BREAK_TIME_PRESETS,
|
|
@@ -623,6 +624,9 @@ var AZURE_VOICE_STYLE_MAP = {
|
|
|
623
624
|
],
|
|
624
625
|
"es-ES-ElviraNeural": [],
|
|
625
626
|
"fil-PH-AngeloNeural": [],
|
|
627
|
+
"fil-PH-Angelo:DragonHDLatestNeural": [],
|
|
628
|
+
"fil-PH-BlessicaNeural": [],
|
|
629
|
+
"fil-PH-Blessica:DragonHDLatestNeural": [],
|
|
626
630
|
"fr-FR-DeniseNeural": ["cheerful", "sad"],
|
|
627
631
|
"fr-FR-HenriNeural": ["cheerful", "sad"],
|
|
628
632
|
"id-ID-GadisNeural": [],
|
|
@@ -900,6 +904,7 @@ var SAY_AS_PRESETS = [
|
|
|
900
904
|
var LANGUAGE_PRESETS = ["ja-JP", "en-US", "de-DE", "fr-FR"];
|
|
901
905
|
var SILENCE_VALUE_PRESETS = ["300ms", "500ms", "1s"];
|
|
902
906
|
var AUDIO_DURATION_PRESETS = ["5s", "10s", "30s"];
|
|
907
|
+
var BACKGROUND_AUDIO_DURATION_PRESETS = ["0", "500", "1000", "3000", "10000"];
|
|
903
908
|
var SILENCE_TYPE_PRESETS = [
|
|
904
909
|
"Leading",
|
|
905
910
|
"Tailing",
|
|
@@ -961,8 +966,8 @@ var SSML_ATTRIBUTE_PRESETS = {
|
|
|
961
966
|
},
|
|
962
967
|
"mstts:backgroundaudio": {
|
|
963
968
|
volume: PROSODY_VOLUME_PRESETS,
|
|
964
|
-
fadein:
|
|
965
|
-
fadeout:
|
|
969
|
+
fadein: BACKGROUND_AUDIO_DURATION_PRESETS,
|
|
970
|
+
fadeout: BACKGROUND_AUDIO_DURATION_PRESETS
|
|
966
971
|
},
|
|
967
972
|
silence: {
|
|
968
973
|
type: SILENCE_TYPE_PRESETS,
|
|
@@ -1364,6 +1369,7 @@ var SSML_ATTRS = {
|
|
|
1364
1369
|
MSTTS_XMLNS: "xmlns:mstts",
|
|
1365
1370
|
NAME: "name",
|
|
1366
1371
|
VOICE: "voice",
|
|
1372
|
+
SPEAKER: "speaker",
|
|
1367
1373
|
EFFECT: "effect",
|
|
1368
1374
|
RATE: "rate",
|
|
1369
1375
|
PITCH: "pitch",
|
|
@@ -1394,6 +1400,11 @@ var SSML_ATTRS = {
|
|
|
1394
1400
|
ALIAS: "alias",
|
|
1395
1401
|
MARK: "mark",
|
|
1396
1402
|
URI: "uri",
|
|
1403
|
+
ID: "id",
|
|
1404
|
+
MODEL: "model",
|
|
1405
|
+
PROFILE: "profile",
|
|
1406
|
+
URL: "url",
|
|
1407
|
+
SPEAKER_PROFILE_ID: "speakerProfileId",
|
|
1397
1408
|
TYPE: "type",
|
|
1398
1409
|
VALUE: "value",
|
|
1399
1410
|
FADE_IN: "fadein",
|
|
@@ -1489,6 +1500,7 @@ function getAttributes(element) {
|
|
|
1489
1500
|
break;
|
|
1490
1501
|
case SSML_TAGS.MSTTS_TURN:
|
|
1491
1502
|
addAttribute(attributes, SSML_ATTRS.VOICE, element.voice);
|
|
1503
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER, element.speaker);
|
|
1492
1504
|
break;
|
|
1493
1505
|
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO:
|
|
1494
1506
|
addAttribute(attributes, SSML_ATTRS.SRC, element.src);
|
|
@@ -1497,9 +1509,18 @@ function getAttributes(element) {
|
|
|
1497
1509
|
addAttribute(attributes, SSML_ATTRS.FADE_OUT, element.fadeOut ?? element.fadeout);
|
|
1498
1510
|
break;
|
|
1499
1511
|
case SSML_TAGS.MSTTS_DIALOG:
|
|
1512
|
+
break;
|
|
1500
1513
|
case SSML_TAGS.MSTTS_TTS_EMBEDDING:
|
|
1514
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
|
|
1515
|
+
break;
|
|
1501
1516
|
case SSML_TAGS.MSTTS_EMBEDDING:
|
|
1517
|
+
addAttribute(attributes, SSML_ATTRS.ID, element.id);
|
|
1518
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
|
|
1519
|
+
break;
|
|
1502
1520
|
case SSML_TAGS.MSTTS_VOICE_CONVERSION:
|
|
1521
|
+
addAttribute(attributes, SSML_ATTRS.URL, element.url);
|
|
1522
|
+
addAttribute(attributes, SSML_ATTRS.PROFILE, element.profile);
|
|
1523
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
|
|
1503
1524
|
break;
|
|
1504
1525
|
case SSML_TAGS.PARAGRAPH:
|
|
1505
1526
|
case SSML_TAGS.SENTENCE:
|
|
@@ -2072,7 +2093,9 @@ function convertElement(node) {
|
|
|
2072
2093
|
case SSML_TAGS.MSTTS_TURN: {
|
|
2073
2094
|
const element = { type: SSML_TAGS.MSTTS_TURN };
|
|
2074
2095
|
const voice = readAttribute(attributes, SSML_ATTRS.VOICE);
|
|
2096
|
+
const speaker = readAttribute(attributes, SSML_ATTRS.SPEAKER);
|
|
2075
2097
|
if (voice !== void 0) element.voice = voice;
|
|
2098
|
+
if (speaker !== void 0) element.speaker = speaker;
|
|
2076
2099
|
return finishElement(element, node, attributes);
|
|
2077
2100
|
}
|
|
2078
2101
|
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO: {
|
|
@@ -2089,14 +2112,26 @@ function convertElement(node) {
|
|
|
2089
2112
|
}
|
|
2090
2113
|
case SSML_TAGS.MSTTS_TTS_EMBEDDING: {
|
|
2091
2114
|
const element = { type: SSML_TAGS.MSTTS_TTS_EMBEDDING };
|
|
2115
|
+
const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
|
|
2116
|
+
if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
|
|
2092
2117
|
return finishElement(element, node, attributes);
|
|
2093
2118
|
}
|
|
2094
2119
|
case SSML_TAGS.MSTTS_EMBEDDING: {
|
|
2095
2120
|
const element = { type: SSML_TAGS.MSTTS_EMBEDDING };
|
|
2121
|
+
const id = readAttribute(attributes, SSML_ATTRS.ID);
|
|
2122
|
+
const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
|
|
2123
|
+
if (id !== void 0) element.id = id;
|
|
2124
|
+
if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
|
|
2096
2125
|
return finishElement(element, node, attributes);
|
|
2097
2126
|
}
|
|
2098
2127
|
case SSML_TAGS.MSTTS_VOICE_CONVERSION: {
|
|
2099
2128
|
const element = { type: SSML_TAGS.MSTTS_VOICE_CONVERSION };
|
|
2129
|
+
const url = readAttribute(attributes, SSML_ATTRS.URL);
|
|
2130
|
+
const profile = readAttribute(attributes, SSML_ATTRS.PROFILE);
|
|
2131
|
+
const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
|
|
2132
|
+
if (url !== void 0) element.url = url;
|
|
2133
|
+
if (profile !== void 0) element.profile = profile;
|
|
2134
|
+
if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
|
|
2100
2135
|
return finishElement(element, node, attributes);
|
|
2101
2136
|
}
|
|
2102
2137
|
default: {
|
|
@@ -2278,6 +2313,9 @@ var AZURE_VOICE_DEFINITIONS = [
|
|
|
2278
2313
|
},
|
|
2279
2314
|
{ name: "es-ES-ElviraNeural", locale: "es-ES" },
|
|
2280
2315
|
{ name: "fil-PH-AngeloNeural", locale: "fil-PH" },
|
|
2316
|
+
{ name: "fil-PH-Angelo:DragonHDLatestNeural", locale: "fil-PH" },
|
|
2317
|
+
{ name: "fil-PH-BlessicaNeural", locale: "fil-PH" },
|
|
2318
|
+
{ name: "fil-PH-Blessica:DragonHDLatestNeural", locale: "fil-PH" },
|
|
2281
2319
|
{ name: "fr-FR-DeniseNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
|
|
2282
2320
|
{ name: "fr-FR-HenriNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
|
|
2283
2321
|
{ name: "id-ID-GadisNeural", locale: "id-ID" },
|
|
@@ -2372,6 +2410,18 @@ var ALLOWED_SILENCE_TYPES = /* @__PURE__ */ new Set([
|
|
|
2372
2410
|
"Enumerationcomma"
|
|
2373
2411
|
]);
|
|
2374
2412
|
var ALLOWED_VISEME_TYPES = /* @__PURE__ */ new Set(["redlips_front", "FacialExpression"]);
|
|
2413
|
+
var DEFAULT_PREVIEW_TAGS = /* @__PURE__ */ new Set(["mstts:voiceconversion"]);
|
|
2414
|
+
function featureStatusForTag(name, options) {
|
|
2415
|
+
const tagName = canonicalTagName(name);
|
|
2416
|
+
const configured = Object.entries(options.tagStatuses ?? {}).find(
|
|
2417
|
+
([candidate]) => canonicalTagName(candidate) === tagName
|
|
2418
|
+
)?.[1];
|
|
2419
|
+
if (configured) return configured;
|
|
2420
|
+
if ((options.previewTags ?? [...DEFAULT_PREVIEW_TAGS]).some((candidate) => canonicalTagName(candidate) === tagName))
|
|
2421
|
+
return "preview";
|
|
2422
|
+
if ((options.deprecatedTags ?? []).some((candidate) => canonicalTagName(candidate) === tagName)) return "deprecated";
|
|
2423
|
+
return void 0;
|
|
2424
|
+
}
|
|
2375
2425
|
function decodeAttribute(value) {
|
|
2376
2426
|
return value.replace(
|
|
2377
2427
|
/&(?:amp|apos|gt|lt|quot);/gi,
|
|
@@ -2431,11 +2481,14 @@ function tokenizeElements(source) {
|
|
|
2431
2481
|
}
|
|
2432
2482
|
const selfClosing = /\/\s*>$/.test(raw);
|
|
2433
2483
|
const parent = openElements[openElements.length - 1];
|
|
2484
|
+
const childElementIndex = parent?.childElementCount;
|
|
2485
|
+
if (parent) parent.childElementCount += 1;
|
|
2434
2486
|
const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
|
|
2435
2487
|
const tokenName = nameMatch[1];
|
|
2436
2488
|
const tokenVoiceName = tokenName.toLowerCase() === "voice" ? attributes.get("name") : tokenName.toLowerCase() === "mstts:turn" ? attributes.get("voice") ?? parentVoiceName : parentVoiceName;
|
|
2437
2489
|
tokens.push({
|
|
2438
2490
|
attributes,
|
|
2491
|
+
childElementIndex,
|
|
2439
2492
|
end,
|
|
2440
2493
|
name: tokenName,
|
|
2441
2494
|
parentName: parent?.name,
|
|
@@ -2445,6 +2498,7 @@ function tokenizeElements(source) {
|
|
|
2445
2498
|
});
|
|
2446
2499
|
if (!selfClosing) {
|
|
2447
2500
|
openElements.push({
|
|
2501
|
+
childElementCount: 0,
|
|
2448
2502
|
name: tokenName,
|
|
2449
2503
|
voiceName: tokenVoiceName
|
|
2450
2504
|
});
|
|
@@ -2483,6 +2537,12 @@ function isValidAzureAudioDuration(value) {
|
|
|
2483
2537
|
if (!clock) return false;
|
|
2484
2538
|
return Number(clock[1]) > 0 || Number(clock[2]) > 0 || Number(clock[3]) > 0 || Number(clock[4] ?? 0) > 0;
|
|
2485
2539
|
}
|
|
2540
|
+
function isValidAzureBackgroundAudioDuration(value) {
|
|
2541
|
+
const match = /^(\d+)$/.exec(value.trim());
|
|
2542
|
+
if (!match) return false;
|
|
2543
|
+
const milliseconds = Number(match[1]);
|
|
2544
|
+
return Number.isFinite(milliseconds) && milliseconds >= 0 && milliseconds <= 1e4;
|
|
2545
|
+
}
|
|
2486
2546
|
function attr(token, name) {
|
|
2487
2547
|
return token.attributes.get(name.toLowerCase());
|
|
2488
2548
|
}
|
|
@@ -2612,9 +2672,16 @@ function validateAudioSource(token, source, diagnostics, options, elementName2)
|
|
|
2612
2672
|
}
|
|
2613
2673
|
if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
|
|
2614
2674
|
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must use HTTPS.`);
|
|
2615
|
-
|
|
2675
|
+
const isAllowedOrigin = options.allowedAudioOrigins?.some((allowedOrigin) => {
|
|
2676
|
+
try {
|
|
2677
|
+
return new URL(allowedOrigin).origin === parsed.origin;
|
|
2678
|
+
} catch {
|
|
2679
|
+
return allowedOrigin === parsed.origin;
|
|
2680
|
+
}
|
|
2681
|
+
}) ?? false;
|
|
2682
|
+
if (options.allowedAudioOrigins && !isAllowedOrigin)
|
|
2616
2683
|
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> origin "${parsed.origin}" is not allowed.`);
|
|
2617
|
-
else if (!options.allowExternalAudio)
|
|
2684
|
+
else if (!isAllowedOrigin && !options.allowExternalAudio)
|
|
2618
2685
|
addDiagnostic(
|
|
2619
2686
|
diagnostics,
|
|
2620
2687
|
source,
|
|
@@ -2624,6 +2691,25 @@ function validateAudioSource(token, source, diagnostics, options, elementName2)
|
|
|
2624
2691
|
}
|
|
2625
2692
|
function validateElement(token, source, diagnostics, voiceName, options, voiceCatalog) {
|
|
2626
2693
|
const name = token.name.toLowerCase();
|
|
2694
|
+
const tagStatus = featureStatusForTag(token.name, options);
|
|
2695
|
+
if (tagStatus === "preview")
|
|
2696
|
+
addDiagnostic(
|
|
2697
|
+
diagnostics,
|
|
2698
|
+
source,
|
|
2699
|
+
token.start,
|
|
2700
|
+
`<${token.name}> is an Azure Speech preview feature and may change or require preview access.`,
|
|
2701
|
+
"warning",
|
|
2702
|
+
"azure-preview-tag"
|
|
2703
|
+
);
|
|
2704
|
+
if (tagStatus === "deprecated")
|
|
2705
|
+
addDiagnostic(
|
|
2706
|
+
diagnostics,
|
|
2707
|
+
source,
|
|
2708
|
+
token.start,
|
|
2709
|
+
`<${token.name}> is deprecated by Azure Speech; migrate to a supported alternative.`,
|
|
2710
|
+
"info",
|
|
2711
|
+
"azure-deprecated-tag"
|
|
2712
|
+
);
|
|
2627
2713
|
if (name === "voice" && !attr(token, "name")?.trim())
|
|
2628
2714
|
addDiagnostic(diagnostics, source, token.start, '<voice> requires a non-empty "name" attribute.');
|
|
2629
2715
|
if (name === "break") {
|
|
@@ -2744,28 +2830,40 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceCa
|
|
|
2744
2830
|
validateAudioSource(token, source, diagnostics, options, "audio");
|
|
2745
2831
|
}
|
|
2746
2832
|
if (name === "mstts:turn") {
|
|
2747
|
-
if (!attr(token, "voice")?.trim())
|
|
2748
|
-
addDiagnostic(
|
|
2833
|
+
if (!attr(token, "voice")?.trim() && !attr(token, "speaker")?.trim())
|
|
2834
|
+
addDiagnostic(
|
|
2835
|
+
diagnostics,
|
|
2836
|
+
source,
|
|
2837
|
+
token.start,
|
|
2838
|
+
'<mstts:turn> requires a non-empty "voice" or "speaker" attribute.'
|
|
2839
|
+
);
|
|
2749
2840
|
if (token.parentName?.toLowerCase() !== "mstts:dialog")
|
|
2750
2841
|
addDiagnostic(diagnostics, source, token.start, "<mstts:turn> is only allowed directly inside <mstts:dialog>.");
|
|
2751
2842
|
}
|
|
2752
2843
|
if (name === "mstts:backgroundaudio") {
|
|
2753
2844
|
validateAudioSource(token, source, diagnostics, options, "mstts:backgroundaudio");
|
|
2754
2845
|
const volume = attr(token, "volume");
|
|
2755
|
-
if (volume &&
|
|
2846
|
+
if (volume !== void 0 && (!/^\d+(?:\.\d+)?$/.test(volume.trim()) || Number(volume) > 100))
|
|
2756
2847
|
addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:backgroundaudio volume> value "${volume}".`);
|
|
2757
2848
|
for (const [attribute, value] of [
|
|
2758
2849
|
["fadein", attr(token, "fadein")],
|
|
2759
2850
|
["fadeout", attr(token, "fadeout")]
|
|
2760
2851
|
]) {
|
|
2761
|
-
if (value && !
|
|
2852
|
+
if (value !== void 0 && !isValidAzureBackgroundAudioDuration(value))
|
|
2762
2853
|
addDiagnostic(
|
|
2763
2854
|
diagnostics,
|
|
2764
2855
|
source,
|
|
2765
2856
|
token.start,
|
|
2766
|
-
`<mstts:backgroundaudio ${attribute}> must be
|
|
2857
|
+
`<mstts:backgroundaudio ${attribute}> must be between 0 and 10000 milliseconds, for example "500ms" or "10s".`
|
|
2767
2858
|
);
|
|
2768
2859
|
}
|
|
2860
|
+
if (token.parentName?.toLowerCase() !== "speak" || token.childElementIndex !== 0)
|
|
2861
|
+
addDiagnostic(
|
|
2862
|
+
diagnostics,
|
|
2863
|
+
source,
|
|
2864
|
+
token.start,
|
|
2865
|
+
"<mstts:backgroundaudio> must be the first element directly under <speak>."
|
|
2866
|
+
);
|
|
2769
2867
|
if (!token.selfClosing)
|
|
2770
2868
|
addDiagnostic(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
|
|
2771
2869
|
}
|
|
@@ -2797,6 +2895,16 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
2797
2895
|
const tokens = tokenizeElements(ssml);
|
|
2798
2896
|
const speak = tokens.find((token) => token.name.toLowerCase() === "speak");
|
|
2799
2897
|
const voices = tokens.filter((token) => token.name.toLowerCase() === "voice");
|
|
2898
|
+
const backgroundAudioTokens = tokens.filter((token) => token.name.toLowerCase() === "mstts:backgroundaudio");
|
|
2899
|
+
for (const [index, token] of backgroundAudioTokens.entries()) {
|
|
2900
|
+
if (index > 0)
|
|
2901
|
+
addDiagnostic(
|
|
2902
|
+
diagnostics,
|
|
2903
|
+
ssml,
|
|
2904
|
+
token.start,
|
|
2905
|
+
"An SSML document can contain at most one <mstts:backgroundaudio> element."
|
|
2906
|
+
);
|
|
2907
|
+
}
|
|
2800
2908
|
if (!speak || voices.length === 0)
|
|
2801
2909
|
addDiagnostic(
|
|
2802
2910
|
diagnostics,
|
|
@@ -2813,6 +2921,24 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
2813
2921
|
const name = attr(token, "name")?.trim();
|
|
2814
2922
|
const language = attr(token, "xml:lang")?.trim() || (speak ? attr(speak, "xml:lang")?.trim() : void 0);
|
|
2815
2923
|
const definition = name ? voiceCatalog.get(name.toLowerCase()) : void 0;
|
|
2924
|
+
if (name && definition?.status === "preview")
|
|
2925
|
+
addDiagnostic(
|
|
2926
|
+
diagnostics,
|
|
2927
|
+
ssml,
|
|
2928
|
+
token.start,
|
|
2929
|
+
`Voice "${name}" is an Azure Speech preview voice and may change or require preview access.`,
|
|
2930
|
+
"warning",
|
|
2931
|
+
"azure-preview-voice"
|
|
2932
|
+
);
|
|
2933
|
+
if (name && definition?.status === "deprecated")
|
|
2934
|
+
addDiagnostic(
|
|
2935
|
+
diagnostics,
|
|
2936
|
+
ssml,
|
|
2937
|
+
token.start,
|
|
2938
|
+
`Voice "${name}" is deprecated by Azure Speech; migrate to a supported voice.`,
|
|
2939
|
+
"info",
|
|
2940
|
+
"azure-deprecated-voice"
|
|
2941
|
+
);
|
|
2816
2942
|
if (name && !definition && policySeverity)
|
|
2817
2943
|
addDiagnostic(
|
|
2818
2944
|
diagnostics,
|
|
@@ -3654,7 +3780,7 @@ var SSML_COMPLETION_SNIPPETS = [
|
|
|
3654
3780
|
},
|
|
3655
3781
|
{
|
|
3656
3782
|
label: "mstts:backgroundaudio",
|
|
3657
|
-
insertText: `<mstts:backgroundaudio src="\${1:https://example.com/audio.mp3}" volume="\${2
|
|
3783
|
+
insertText: `<mstts:backgroundaudio src="\${1:https://example.com/audio.mp3}" volume="\${2:70}" fadein="\${3:1000}" fadeout="\${4:1000}" />`
|
|
3658
3784
|
},
|
|
3659
3785
|
{
|
|
3660
3786
|
label: "mstts:ttsembedding",
|
|
@@ -4058,18 +4184,18 @@ var SSML_TAG_DEFINITIONS = [
|
|
|
4058
4184
|
},
|
|
4059
4185
|
{
|
|
4060
4186
|
name: "volume",
|
|
4061
|
-
description: "The background audio volume
|
|
4062
|
-
example: "
|
|
4187
|
+
description: "The background audio volume from 0 to 100.",
|
|
4188
|
+
example: "70"
|
|
4063
4189
|
},
|
|
4064
4190
|
{
|
|
4065
4191
|
name: "fadein",
|
|
4066
|
-
description: "The fade-in duration
|
|
4067
|
-
example: "
|
|
4192
|
+
description: "The fade-in duration in milliseconds from 0 to 10000.",
|
|
4193
|
+
example: "1000"
|
|
4068
4194
|
},
|
|
4069
4195
|
{
|
|
4070
4196
|
name: "fadeout",
|
|
4071
|
-
description: "The fade-out duration
|
|
4072
|
-
example: "
|
|
4197
|
+
description: "The fade-out duration in milliseconds from 0 to 10000.",
|
|
4198
|
+
example: "1000"
|
|
4073
4199
|
}
|
|
4074
4200
|
]
|
|
4075
4201
|
},
|
|
@@ -4439,4 +4565,4 @@ export {
|
|
|
4439
4565
|
findSsmlHoverTarget,
|
|
4440
4566
|
formatSsmlHover
|
|
4441
4567
|
};
|
|
4442
|
-
//# sourceMappingURL=chunk-
|
|
4568
|
+
//# sourceMappingURL=chunk-FWWMWI2C.mjs.map
|