ssml-builder-js 2.8.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/README.md CHANGED
@@ -154,6 +154,8 @@ SSML の検証は、構文、Azure 固有の静的な意味、実サービスの
154
154
 
155
155
  翻訳などで本文だけを置き換える場合は、`mapSsmlTextNodes` に変換関数を渡します。変換関数には直近の親タグと祖先タグの `path` が渡され、戻り値は `string` または `Promise<string>` を指定できます。`validateAzureSsml` は音声、属性値、音声スタイル、文字数、`audio` URL/オリジンを検証します。
156
156
 
157
+ `mstts:audioduration` は `<mstts:audioduration value="10s"/>` の形式で扱えます。`value` には正の `ms`/`s` 値、または `hh:mm:ss`(ミリ秒を含む場合は `hh:mm:ss.fff`)を指定できます。
158
+
157
159
  `mapSsmlTextNodes` の第 3 引数で、翻訳対象外タグとコンテキストフィルターを指定できます。デフォルトでは `phoneme`、`say-as`、`sub` の本文を変換しません。`filter` には `parentTag`、`parentAttributes`、`ancestorTags`、`path` が渡されます。
158
160
 
159
161
  ```ts
@@ -192,6 +194,8 @@ const diagnostics = validateAzureSsml(ssml, {
192
194
 
193
195
  Azure の音声・スタイル一覧はサービス更新やリージョン差分があるため、組み込み一覧は固定の完全な台帳ではありません。新しい音声を完全に静的検証したい場合は、音声名だけでなくロケールとスタイルを `voiceDefinitions` に定義してください。`validateNestedVoices` のデフォルトは `true` です。`audio` の外部 URL はデフォルトで拒否されるため、利用する場合は `allowedAudioOrigins` に許可するオリジンを列挙するか、構成を理解した上で `allowExternalAudio: true` を指定してください。
194
196
 
197
+ Azure の音声カタログを更新する場合は、`AZURE_SPEECH_REGION` と `AZURE_SPEECH_KEY` を設定して `npm run sync:voices` を実行します。`packages/ssml-core` の音声定義とエディタのスタイル補完候補が同時に更新されます。
198
+
195
199
  ```ts
196
200
  const diagnostics = validateAzureSsml(ssml, {
197
201
  customVoiceStyleMap: { "my-custom-voice": ["narration"] },
@@ -204,7 +208,7 @@ Azure Speech は `<audio>` の URL を取得するため、任意の URL をそ
204
208
 
205
209
  ## `ssml-editor-react` の利用方法
206
210
 
207
- `SsmlEditor` は `SsmlDocument` を受け取り、ツールバーと本文の表示エリアだけを表示するシンプルなコンポーネントです。ツールバーから選択範囲の速度、音量、ピッチなどの設定、元に戻す・やり直す操作ができます。音声の選択と表示はアプリ側で行います。本文の編集には Monaco Editor を使用し、変更時に SSML の構文を検証します。構文エラーはエディター上のマーカーとエラーメッセージで表示されます。XML のタグ名やパラメータへホバーすると SSML の説明を確認できます。テキストを選択すると、選択文字数と試聴を行うフローティングアクションが表示されます。`enableCodeLens`(デフォルトは `true`)が有効な場合、`prosody` と `break` タグの上に属性編集やタグ操作の CodeLens が表示されます。`showDecorations` が有効な場合、`break` と `prosody` のタグに間やピッチ変化を示すインラインバッジが表示され、Monaco のインライン装飾も有効になります。生成された SSML は `onSsmlChange` で受け取り、アプリ側で自由に表示できます。`SsmlEditorRef` を `ref` に渡すと、全体、選択範囲、または現在行の SSML を取得できます。画面表示は日本語(デフォルト)と英語に対応しています。
211
+ `SsmlEditor` は `SsmlDocument` を受け取り、ツールバーと本文の表示エリアだけを表示するシンプルなコンポーネントです。ツールバーから選択範囲の速度、音量、ピッチなどの設定、元に戻す・やり直す操作ができます。音声の選択と表示はアプリ側で行います。本文の編集には Monaco Editor を使用し、変更時に SSML の構文を検証します。構文エラーはエディター上のマーカーとエラーメッセージで表示されます。XML のタグ名やパラメータへホバーすると SSML の説明を確認できます。テキストを選択すると、選択文字数と試聴を行うフローティングアクションが表示されます。`enableCodeLens`(デフォルトは `true`)が有効な場合、`prosody`、`break`、`mstts:audioduration` タグの上に属性編集やタグ操作の CodeLens が表示されます。`showDecorations` が有効な場合、`break` と `prosody` のタグに間やピッチ変化を示すインラインバッジが表示され、Monaco のインライン装飾も有効になります。生成された SSML は `onSsmlChange` で受け取り、アプリ側で自由に表示できます。`SsmlEditorRef` を `ref` に渡すと、全体、選択範囲、または現在行の SSML を取得できます。画面表示は日本語(デフォルト)と英語に対応しています。
208
212
 
209
213
  `<mstts:express-as>` の `style` 属性補完と標準の感情メニューは、カーソルまたは選択範囲を囲む最内の `<voice name="...">` が対応するスタイルだけを表示します。音声名が未指定の場合は全候補を表示し、登録済みでスタイル非対応の音声や未登録の音声では候補がないことを表示します。`emotionStyles` を指定した場合、感情メニューではその値と登録済み音声の対応スタイルの共通部分を使用します。
210
214
 
@@ -249,8 +253,8 @@ export function App() {
249
253
  - `showToolbarIcons`: ツールバーのアイコン表示(デフォルトは `true`)
250
254
  - `showToolbarLabels`: ツールバーの文字による説明表示(デフォルトは `false`)。省略時はアイコンにホバーすると説明が表示されます
251
255
  - `showDecorations`: 本文中のインライン装飾(バッジや Inlay Hints)の表示(デフォルトは `false`)。ツールバーの「装飾」スイッチで表示・非表示を切り替えられます
252
- - `enableCodeLens`: `prosody` と `break` タグの CodeLens クイックコントローラーを表示するか(デフォルトは `true`)
253
- - `buttonVisibility`: ツールバーボタンごとの表示設定。`help`、`break`、`emphasis`、`rate`、`pitch`、`volume`、`emotion`、`say-as`、`lang`、`mstts:silence`、`undo`、`redo`、`clearAll`、`format`、`decorations`、カスタム挿入 ID を指定でき、未指定のボタンは表示されます
256
+ - `enableCodeLens`: `prosody`、`break`、`mstts:audioduration` タグの CodeLens クイックコントローラーを表示するか(デフォルトは `true`)
257
+ - `buttonVisibility`: ツールバーボタンごとの表示設定。`help`、`break`、`emphasis`、`rate`、`pitch`、`volume`、`emotion`、`say-as`、`lang`、`mstts:silence`、`mstts:audioduration`、`undo`、`redo`、`clearAll`、`format`、`decorations`、カスタム挿入 ID を指定でき、未指定のボタンは表示されます
254
258
  - `editorOptions` / `settings`: Monaco の設定。`height`、`minHeight`、`readOnly`、`theme`(`system` / `light` / `dark`)、`fontSize`、`wordWrap`、`lineNumbers`、`minimap`、`automaticLayout` を指定できます。これらは同名のトップレベル props でも指定できます
255
259
  - `loadingFallback`: Monaco の読み込み中に表示する React ノード
256
260
  - `toolbarOrder`: ツールバー全体のボタン ID の表示順。指定されていないボタンは後ろに続きます
@@ -263,10 +267,10 @@ export function App() {
263
267
  - `toolbarClassName` / `toolbarStyle`: ツールバーのクラス名とインラインスタイル
264
268
  - `displayClassName` / `displayStyle`: 本文表示エリアのクラス名とインラインスタイル
265
269
  - ツールバーの「フォーマット」ボタンで本文の XML を整形できます
266
- - 挿入専用の要素(`break`、`mstts:silence`、カスタム挿入の `mode: "insert"`)は、本文内で独立した行になるよう自動的に改行されます。選択範囲を囲む要素はインラインのまま挿入されます
270
+ - 挿入専用の要素(`break`、`mstts:silence`、`mstts:audioduration`、カスタム挿入の `mode: "insert"`)は、本文内で独立した行になるよう自動的に改行されます。選択範囲を囲む要素はインラインのまま挿入されます
267
271
  - 本文を変更すると SSML 構文を検証し、エラー箇所をエディター上に表示します
268
272
 
269
- 標準の挿入メニューには `break`、`emphasis`、`rate`、`pitch`、`volume`、`emotion`、`say-as`、`lang`、`mstts:silence` が含まれます。これらの定義は `SSML_INSERTIONS` から参照できます。カスタム挿入定義は配列または ID をキーにしたオブジェクトで指定でき、`createSsmlEditorInsertionDefinition` でタグ名と任意の 1 属性を持つ定義を作成できます。任意の属性や複数属性が必要な場合は `SsmlEditorInsertionDefinition` の `createTemplate` を実装してください。
273
+ 標準の挿入メニューには `break`、`emphasis`、`rate`、`pitch`、`volume`、`emotion`、`say-as`、`lang`、`mstts:silence`、`mstts:audioduration` が含まれます。これらの定義は `SSML_INSERTIONS` から参照できます。カスタム挿入定義は配列または ID をキーにしたオブジェクトで指定でき、`createSsmlEditorInsertionDefinition` でタグ名と任意の 1 属性を持つ定義を作成できます。任意の属性や複数属性が必要な場合は `SsmlEditorInsertionDefinition` の `createTemplate` を実装してください。
270
274
 
271
275
  「説明」ボタンを押すと、各コントロール、ボタン、設定の説明を表示できます。ボタンの設定はアコーディオンで表示され、デフォルトでは閉じています。アコーディオンのタイトルにはボタンの説明と生成される XML のタグ名が表示され、各設定の意味を確認できます。「全てクリア」ボタンは `voice` 要素を保持したまま、それ以外の XML 要素を削除して本文を残します。ドキュメントの `version`、`lang`、その他の属性も保持されます。
272
276
 
@@ -579,7 +583,7 @@ const diagnostics = validateAzureSsml(ssml, {
579
583
  });
580
584
  ```
581
585
 
582
- The catalog is represented by `AzureVoiceDefinition` with `name`, `locale`, optional `secondaryLocales`, and optional `styles`. Pass `voiceDefinitions` (or `voiceCatalog`) to supplement or override the built-in catalog with an external definition. `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`).
586
+ The catalog is represented by `AzureVoiceDefinition` with `name`, `locale`, optional `secondaryLocales`, and optional `styles`. Pass `voiceDefinitions` (or `voiceCatalog`) to supplement or override the built-in catalog with an external definition. `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.
583
587
 
584
588
  ```ts
585
589
  const diagnostics = validateAzureSsml(ssml, {
@@ -598,6 +602,8 @@ const diagnostics = validateAzureSsml(ssml, {
598
602
 
599
603
  Azure's voice and style catalog changes over time and can differ by region, so the built-in catalog is a fixed snapshot rather than a complete live catalog. For complete static validation of a new voice, define its name, locale, and supported styles in `voiceDefinitions`. `validateNestedVoices` defaults to `true`. External `<audio>` URLs are blocked by default; provide `allowedAudioOrigins` or explicitly set `allowExternalAudio: true` only when the deployment is configured to control those requests.
600
604
 
605
+ To refresh the built-in voice catalog, set `AZURE_SPEECH_REGION` and `AZURE_SPEECH_KEY`, then run `npm run sync:voices`. The command updates the core voice definitions and editor style-completion map together.
606
+
601
607
  ```ts
602
608
  const diagnostics = validateAzureSsml(ssml, {
603
609
  customVoiceStyleMap: { "my-custom-voice": ["narration"] },
@@ -655,8 +661,8 @@ export function App() {
655
661
  - `showToolbarIcons`: Whether to show toolbar icons (defaults to `true`)
656
662
  - `showToolbarLabels`: Whether to show text labels on the toolbar (defaults to `false`); when omitted, hover over an icon to see its description
657
663
  - `showDecorations`: Whether inline decorations such as badges and inlay hints are shown in the text (defaults to `false`); use the **Decorations** toolbar switch to toggle them at runtime
658
- - `enableCodeLens`: Whether CodeLens quick controls for `prosody` and `break` tags are shown (defaults to `true`)
659
- - `buttonVisibility`: Per-toolbar-button visibility settings for `help`, `break`, `emphasis`, `rate`, `pitch`, `volume`, `emotion`, `say-as`, `lang`, `mstts:silence`, `undo`, `redo`, `clearAll`, `format`, `decorations`, and custom insertion IDs; unspecified buttons are shown
664
+ - `enableCodeLens`: Whether CodeLens quick controls for `prosody`, `break`, and `mstts:audioduration` tags are shown (defaults to `true`)
665
+ - `buttonVisibility`: Per-toolbar-button visibility settings for `help`, `break`, `emphasis`, `rate`, `pitch`, `volume`, `emotion`, `say-as`, `lang`, `mstts:silence`, `mstts:audioduration`, `undo`, `redo`, `clearAll`, `format`, `decorations`, and custom insertion IDs; unspecified buttons are shown
660
666
  - `editorOptions` / `settings`: Monaco settings for `height`, `minHeight`, `readOnly`, `theme` (`system` / `light` / `dark`), `fontSize`, `wordWrap`, `lineNumbers`, `minimap`, and `automaticLayout`. The same settings can also be supplied as top-level props
661
667
  - `loadingFallback`: A React node displayed while Monaco is loading
662
668
  - `toolbarOrder`: Display order for all toolbar button IDs; unlisted buttons follow
@@ -669,10 +675,10 @@ export function App() {
669
675
  - `toolbarClassName` / `toolbarStyle`: A class name and inline styles for the toolbar
670
676
  - `displayClassName` / `displayStyle`: A class name and inline styles for the text display area
671
677
  - Use the **Format** button to format the XML in the text display area
672
- - Standalone elements (`break`, `mstts:silence`, and custom insertions with `mode: "insert"`) are automatically placed on separate lines; elements that wrap a selection remain inline
678
+ - Standalone elements (`break`, `mstts:silence`, `mstts:audioduration`, and custom insertions with `mode: "insert"`) are automatically placed on separate lines; elements that wrap a selection remain inline
673
679
  - Changing the text validates SSML syntax and displays errors in the editor
674
680
 
675
- The built-in insertion menus are `break`, `emphasis`, `rate`, `pitch`, `volume`, `emotion`, `say-as`, `lang`, and `mstts:silence`. Their definitions are available through `SSML_INSERTIONS`. Custom insertion definitions can be supplied as an array or an object keyed by ID. Use `createSsmlEditorInsertionDefinition` to create a definition from a tag and one optional attribute; for arbitrary or multiple attributes, implement `createTemplate` on `SsmlEditorInsertionDefinition`.
681
+ The built-in insertion menus are `break`, `emphasis`, `rate`, `pitch`, `volume`, `emotion`, `say-as`, `lang`, `mstts:silence`, and `mstts:audioduration`. Their definitions are available through `SSML_INSERTIONS`. Custom insertion definitions can be supplied as an array or an object keyed by ID. Use `createSsmlEditorInsertionDefinition` to create a definition from a tag and one optional attribute; for arbitrary or multiple attributes, implement `createTemplate` on `SsmlEditorInsertionDefinition`.
676
682
 
677
683
  Click the **Description** button to see descriptions of each control, button, and setting. Button settings are shown in accordions that are closed by default, with the button description and generated XML tag name as the accordion title and the meaning of each setting inside. The **Clear all** button preserves `voice` elements, removes the other XML elements, and leaves the text in place. The document's `version`, `lang`, and other attributes are also preserved.
678
684
 
@@ -213,6 +213,13 @@ var SSML_HOVER_COPY = {
213
213
  parameters: {
214
214
  type: { title: "type", description: "\u30D3\u30BC\u30FC\u30E0\u30A4\u30D9\u30F3\u30C8\u306E\u5F62\u5F0F\u3002" }
215
215
  }
216
+ },
217
+ "mstts:audioduration": {
218
+ title: "\u97F3\u58F0\u9577",
219
+ description: "\u5408\u6210\u97F3\u58F0\u306E\u76EE\u6A19\u6642\u9593\u3092\u6307\u5B9A\u3057\u307E\u3059\u3002",
220
+ parameters: {
221
+ value: { title: "value", description: "\u76EE\u6A19\u6642\u9593\u3002\u4F8B: `10s`\u3001`5000ms`\u3001`00:00:10`\u3002" }
222
+ }
216
223
  }
217
224
  }
218
225
  },
@@ -370,6 +377,13 @@ var SSML_HOVER_COPY = {
370
377
  parameters: {
371
378
  type: { title: "type", description: "The viseme event format." }
372
379
  }
380
+ },
381
+ "mstts:audioduration": {
382
+ title: "Audio duration",
383
+ description: "Sets the target duration of synthesized audio.",
384
+ parameters: {
385
+ value: { title: "value", description: "The target duration, such as `10s`, `5000ms`, or `00:00:10`." }
386
+ }
373
387
  }
374
388
  }
375
389
  }
@@ -526,6 +540,8 @@ function findSsmlVoiceContext(source, offset) {
526
540
  // packages/ssml-editor-react/src/constants/ssmlPresets.ts
527
541
  var ssmlPresets_exports = {};
528
542
  __export(ssmlPresets_exports, {
543
+ AUDIO_DURATION_DESCRIPTIONS: () => AUDIO_DURATION_DESCRIPTIONS,
544
+ AUDIO_DURATION_PRESETS: () => AUDIO_DURATION_PRESETS,
529
545
  BREAK_STRENGTH_PRESETS: () => BREAK_STRENGTH_PRESETS,
530
546
  BREAK_TIME_DESCRIPTIONS: () => BREAK_TIME_DESCRIPTIONS,
531
547
  BREAK_TIME_PRESETS: () => BREAK_TIME_PRESETS,
@@ -558,6 +574,103 @@ __export(ssmlPresets_exports, {
558
574
  getExpressAsStyleCategory: () => getExpressAsStyleCategory,
559
575
  resolveExpressAsStyles: () => resolveExpressAsStyles
560
576
  });
577
+
578
+ // packages/ssml-editor-react/src/constants/azureVoiceStyleMap.generated.ts
579
+ var AZURE_VOICE_STYLE_MAP = {
580
+ "de-DE-ConradNeural": ["cheerful", "sad"],
581
+ "de-DE-KatjaNeural": ["cheerful", "sad"],
582
+ "en-US-AndrewNeural": ["empathetic", "relieved"],
583
+ "en-US-GuyNeural": [
584
+ "angry",
585
+ "cheerful",
586
+ "excited",
587
+ "friendly",
588
+ "hopeful",
589
+ "newscast",
590
+ "sad",
591
+ "shouting",
592
+ "terrified",
593
+ "unfriendly",
594
+ "whispering"
595
+ ],
596
+ "en-US-JennyMultilingualNeural": [
597
+ "cheerful",
598
+ "empathetic",
599
+ "excited",
600
+ "friendly",
601
+ "hopeful",
602
+ "sad",
603
+ "shouting",
604
+ "terrified",
605
+ "unfriendly",
606
+ "whispering"
607
+ ],
608
+ "en-US-JennyNeural": [
609
+ "assistant",
610
+ "chat",
611
+ "customerservice",
612
+ "newscast",
613
+ "cheerful",
614
+ "empathetic",
615
+ "excited",
616
+ "friendly",
617
+ "hopeful",
618
+ "sad",
619
+ "shouting",
620
+ "terrified",
621
+ "unfriendly",
622
+ "whispering"
623
+ ],
624
+ "es-ES-ElviraNeural": [],
625
+ "fil-PH-AngeloNeural": [],
626
+ "fr-FR-DeniseNeural": ["cheerful", "sad"],
627
+ "fr-FR-HenriNeural": ["cheerful", "sad"],
628
+ "id-ID-GadisNeural": [],
629
+ "it-IT-ElsaNeural": ["cheerful", "sad"],
630
+ "ja-JP-KeitaNeural": ["chat"],
631
+ "ja-JP-MayuNeural": ["calm", "cheerful", "sad"],
632
+ "ja-JP-NanamiNeural": ["chat", "customerservice", "cheerful", "whispering", "sad"],
633
+ "ko-KR-SunHiNeural": ["cheerful", "sad"],
634
+ "ms-MY-YasminNeural": [],
635
+ "pt-BR-FranciscaNeural": ["calm"],
636
+ "ru-RU-SvetlanaNeural": ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"],
637
+ "th-TH-PremwadeeNeural": [],
638
+ "vi-VN-HoaiMyNeural": [],
639
+ "zh-CN-XiaoxiaoNeural": [
640
+ "assistant",
641
+ "chat",
642
+ "customerservice",
643
+ "newscast",
644
+ "cheerful",
645
+ "empathetic",
646
+ "excited",
647
+ "friendly",
648
+ "hopeful",
649
+ "sad",
650
+ "terrified",
651
+ "whispering",
652
+ "poetry-reading",
653
+ "sports_commentary",
654
+ "sports_commentary_excited",
655
+ "story"
656
+ ],
657
+ "zh-CN-YunxiNeural": [
658
+ "narration-relaxed",
659
+ "embarrassed",
660
+ "fearful",
661
+ "sad",
662
+ "disgruntled",
663
+ "serious",
664
+ "angry",
665
+ "depressed",
666
+ "chat",
667
+ "cheerful",
668
+ "assistant"
669
+ ],
670
+ "zh-TW-HsiaoChenNeural": []
671
+ };
672
+
673
+ // packages/ssml-editor-react/src/constants/ssmlPresets.ts
561
674
  var SSML_PRESETS = [
562
675
  {
563
676
  id: "basic",
@@ -657,7 +770,7 @@ function getExpressAsStyleCategory(style) {
657
770
  }
658
771
  return "other";
659
772
  }
660
- var VOICE_STYLE_MAP = {
773
+ var LEGACY_VOICE_STYLE_MAP = {
661
774
  "ja-JP-MayuNeural": ["calm", "cheerful", "sad"],
662
775
  "ja-JP-KeitaNeural": ["chat"],
663
776
  "ja-JP-NanamiNeural": ["chat", "customerservice", "cheerful", "whispering", "sad"],
@@ -743,6 +856,7 @@ var VOICE_STYLE_MAP = {
743
856
  "de-DE-ConradNeural": ["cheerful", "sad"],
744
857
  "ru-RU-SvetlanaNeural": ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"]
745
858
  };
859
+ var VOICE_STYLE_MAP = Object.keys(AZURE_VOICE_STYLE_MAP).length > 0 ? AZURE_VOICE_STYLE_MAP : LEGACY_VOICE_STYLE_MAP;
746
860
  var VOICE_STYLE_MAP_BY_NORMALIZED_NAME = new Map(
747
861
  Object.entries(VOICE_STYLE_MAP).map(([voiceName, styles]) => [voiceName.toLowerCase(), styles])
748
862
  );
@@ -785,6 +899,7 @@ var SAY_AS_PRESETS = [
785
899
  ];
786
900
  var LANGUAGE_PRESETS = ["ja-JP", "en-US", "de-DE", "fr-FR"];
787
901
  var SILENCE_VALUE_PRESETS = ["300ms", "500ms", "1s"];
902
+ var AUDIO_DURATION_PRESETS = ["5s", "10s", "30s"];
788
903
  var SILENCE_TYPE_PRESETS = [
789
904
  "Leading",
790
905
  "Tailing",
@@ -793,6 +908,11 @@ var SILENCE_TYPE_PRESETS = [
793
908
  "Semicolon",
794
909
  "Enumerationcomma"
795
910
  ];
911
+ var AUDIO_DURATION_DESCRIPTIONS = {
912
+ "5s": { ja: "5\u79D2", en: "5 seconds" },
913
+ "10s": { ja: "10\u79D2", en: "10 seconds" },
914
+ "30s": { ja: "30\u79D2", en: "30 seconds" }
915
+ };
796
916
  var PHONEME_ALPHABET_PRESETS = ["ipa", "sapi", "ups", "x-sampa"];
797
917
  var VISEME_TYPE_PRESETS = ["redlips_front", "FacialExpression"];
798
918
  var SSML_ATTRIBUTE_PRESETS = {
@@ -836,6 +956,9 @@ var SSML_ATTRIBUTE_PRESETS = {
836
956
  type: SILENCE_TYPE_PRESETS,
837
957
  value: SILENCE_VALUE_PRESETS
838
958
  },
959
+ "mstts:audioduration": {
960
+ value: AUDIO_DURATION_PRESETS
961
+ },
839
962
  silence: {
840
963
  type: SILENCE_TYPE_PRESETS,
841
964
  value: SILENCE_VALUE_PRESETS
@@ -1219,7 +1342,8 @@ var SSML_TAGS = {
1219
1342
  MSTTS_SILENCE: "mstts:silence",
1220
1343
  SILENCE: "silence",
1221
1344
  MSTTS_VISEME: "mstts:viseme",
1222
- VISEME: "viseme"
1345
+ VISEME: "viseme",
1346
+ MSTTS_AUDIO_DURATION: "mstts:audioduration"
1223
1347
  };
1224
1348
  var SSML_ATTRS = {
1225
1349
  VERSION: "version",
@@ -1346,6 +1470,9 @@ function getAttributes(element) {
1346
1470
  case SSML_TAGS.VISEME:
1347
1471
  addAttribute(attributes, SSML_ATTRS.TYPE, element.typeValue ?? element.visemeType);
1348
1472
  break;
1473
+ case SSML_TAGS.MSTTS_AUDIO_DURATION:
1474
+ addAttribute(attributes, SSML_ATTRS.VALUE, element.value);
1475
+ break;
1349
1476
  case SSML_TAGS.PARAGRAPH:
1350
1477
  case SSML_TAGS.SENTENCE:
1351
1478
  case SSML_TAGS.WORD:
@@ -1370,6 +1497,8 @@ function getTagName(element) {
1370
1497
  case SSML_TAGS.VISEME:
1371
1498
  case SSML_TAGS.MSTTS_VISEME:
1372
1499
  return SSML_TAGS.MSTTS_VISEME;
1500
+ case SSML_TAGS.MSTTS_AUDIO_DURATION:
1501
+ return SSML_TAGS.MSTTS_AUDIO_DURATION;
1373
1502
  case "element":
1374
1503
  case "custom":
1375
1504
  return element.name;
@@ -1902,6 +2031,12 @@ function convertElement(node) {
1902
2031
  if (typeValue !== void 0) element.typeValue = typeValue;
1903
2032
  return finishElement(element, node, attributes);
1904
2033
  }
2034
+ case SSML_TAGS.MSTTS_AUDIO_DURATION: {
2035
+ const element = { type: SSML_TAGS.MSTTS_AUDIO_DURATION };
2036
+ const value = readAttribute(attributes, SSML_ATTRS.VALUE);
2037
+ if (value !== void 0) element.value = value;
2038
+ return finishElement(element, node, attributes);
2039
+ }
1905
2040
  default: {
1906
2041
  const element = {
1907
2042
  name: node.name,
@@ -2082,6 +2217,7 @@ var INTRINSICALLY_EMPTY_ELEMENTS = /* @__PURE__ */ new Set([
2082
2217
  "lexicon",
2083
2218
  "mark",
2084
2219
  "mstts:silence",
2220
+ "mstts:audioduration",
2085
2221
  "mstts:viseme",
2086
2222
  "silence",
2087
2223
  "viseme"
@@ -2802,6 +2938,10 @@ var SSML_COMPLETION_SNIPPETS = [
2802
2938
  label: "mstts:express-as",
2803
2939
  insertText: `<mstts:express-as style="cheerful">\${1:text}</mstts:express-as>`
2804
2940
  },
2941
+ {
2942
+ label: "mstts:audioduration",
2943
+ insertText: '<mstts:audioduration value="10s" />'
2944
+ },
2805
2945
  {
2806
2946
  label: "sub",
2807
2947
  insertText: `<sub alias="\${1:\u8AAD\u307F}">\${2:\u6F22\u5B57}</sub>`
@@ -3161,6 +3301,17 @@ var SSML_TAG_DEFINITIONS = [
3161
3301
  values: VISEME_TYPE_PRESETS2
3162
3302
  }
3163
3303
  ]
3304
+ },
3305
+ {
3306
+ name: "mstts:audioduration",
3307
+ description: "Sets the target duration of synthesized audio.",
3308
+ parameters: [
3309
+ {
3310
+ name: "value",
3311
+ description: "The target duration, such as `10s`, `5000ms`, or `00:00:10`.",
3312
+ example: "10s"
3313
+ }
3314
+ ]
3164
3315
  }
3165
3316
  ];
3166
3317
  var definitionsByName = /* @__PURE__ */ new Map();
@@ -3477,6 +3628,8 @@ export {
3477
3628
  SAY_AS_PRESETS,
3478
3629
  LANGUAGE_PRESETS,
3479
3630
  SILENCE_VALUE_PRESETS,
3631
+ AUDIO_DURATION_PRESETS,
3632
+ AUDIO_DURATION_DESCRIPTIONS,
3480
3633
  SSML_ATTRIBUTE_PRESETS,
3481
3634
  BREAK_TIME_DESCRIPTIONS,
3482
3635
  EMPHASIS_LEVEL_DESCRIPTIONS,
@@ -3510,4 +3663,4 @@ export {
3510
3663
  findSsmlHoverTarget,
3511
3664
  formatSsmlHover
3512
3665
  };
3513
- //# sourceMappingURL=chunk-4RQETJUI.mjs.map
3666
+ //# sourceMappingURL=chunk-MWSDFGXW.mjs.map