From bb4e0b165a1cab13d4cdf1db2bd2e81315f6dce0 Mon Sep 17 00:00:00 2001 From: Tksn07 <38491690+Tksn07@users.noreply.github.com> Date: Thu, 28 Dec 2023 14:01:14 +0900 Subject: [PATCH 01/19] =?UTF-8?q?build=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E3=81=AE=E6=AF=94=E8=BC=83=E3=82=92=E7=B7=A9=E3=81=84=E7=AD=89?= =?UTF-8?q?=E4=BE=A1=E6=AF=94=E8=BC=83=E3=81=AB=E5=A4=89=E6=9B=B4=20(#1679?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build/splitNsisArchive.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/splitNsisArchive.js b/build/splitNsisArchive.js index e8366a8a72..1d12decc29 100644 --- a/build/splitNsisArchive.js +++ b/build/splitNsisArchive.js @@ -13,13 +13,13 @@ const createIni = (sizes, hashes) => { // target: electron-builder.Target exports.default = async function (target) { const projectName = process.env.npm_package_name; - if (projectName === undefined) { + if (projectName == undefined) { const ErrorMessage = "Project name is undefined."; console.error(ErrorMessage); throw ErrorMessage; } const projectVersion = process.env.npm_package_version; - if (projectVersion === undefined) { + if (projectVersion == undefined) { const ErrorMessage = "Project version is undefined."; console.error(ErrorMessage); throw ErrorMessage; From fb340815ebb1b2afcc516d54625fffc10cc36f97 Mon Sep 17 00:00:00 2001 From: Tksn07 <38491690+Tksn07@users.noreply.github.com> Date: Sat, 30 Dec 2023 16:43:41 +0900 Subject: [PATCH 02/19] =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=9D=E3=83=BC?= =?UTF-8?q?=E3=83=8D=E3=83=B3=E3=83=88=E7=BE=A4=E3=81=AE=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=20(#1681)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * コンポーネント群の修正 * 残りコンポーネント修正 --- src/components/AccentPhrase.vue | 2 +- src/components/AudioInfo.vue | 8 ++++---- src/components/CharacterOrderDialog.vue | 4 ++-- src/components/CharacterPortrait.vue | 4 ++-- src/components/DefaultStyleListDialog.vue | 3 +-- src/components/DefaultStyleSelectDialog.vue | 2 +- src/components/Dialog.ts | 2 +- src/components/EngineManageDialog.vue | 2 +- src/components/FileNamePatternDialog.vue | 2 +- src/components/HeaderBarCustomDialog.vue | 2 +- src/components/HotkeySettingDialog.vue | 4 ++-- src/components/MenuBar.vue | 3 +-- 12 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/components/AccentPhrase.vue b/src/components/AccentPhrase.vue index deca4c9a6e..87cd278905 100644 --- a/src/components/AccentPhrase.vue +++ b/src/components/AccentPhrase.vue @@ -322,7 +322,7 @@ const isEditableMora = (vowel: string, moraIndex: number) => { if (props.selectedDetail == "accent") { // クリック時の動作はそのアクセント句の読み変更。 // よって、いずれかのモーラがhoverされているならそのアクセント句を強調表示する。 - return hoveredMoraIndex.value !== undefined; + return hoveredMoraIndex.value != undefined; } if (props.selectedDetail == "pitch") { // クリック時の動作は無声化/有声化の切り替え。 diff --git a/src/components/AudioInfo.vue b/src/components/AudioInfo.vue index 7c0c60da1d..e893e1655f 100644 --- a/src/components/AudioInfo.vue +++ b/src/components/AudioInfo.vue @@ -479,7 +479,7 @@ const handleParameterChange = ( parameter: Parameter, inputValue: string | number | null ) => { - if (inputValue === null) throw new Error("inputValue is null"); + if (inputValue == null) throw new Error("inputValue is null"); const value = adjustSliderValue( parameter.label + "入力", inputValue.toString(), @@ -760,7 +760,7 @@ const setPresetByScroll = (event: WheelEvent) => { event.preventDefault(); const presetNumber = selectablePresetList.value.length; - if (presetNumber === 0 || presetNumber === undefined) return; + if (presetNumber === 0 || presetNumber == undefined) return; const nowIndex = selectablePresetList.value.findIndex( (value) => value.key == presetSelectModel.value.key @@ -770,7 +770,7 @@ const setPresetByScroll = (event: WheelEvent) => { const newIndex = isUp ? nowIndex + 1 : nowIndex - 1; if (newIndex < 0 || presetNumber <= newIndex) return; - if (selectablePresetList.value[newIndex] === undefined) return; + if (selectablePresetList.value[newIndex] == undefined) return; changePreset(selectablePresetList.value[newIndex].key); }; @@ -889,7 +889,7 @@ const updatePreset = async (fullApply: boolean) => { const key = presetList.value.find( (preset) => preset.label === presetName.value )?.key; - if (key === undefined) return; + if (key == undefined) return; const title = presetName.value; const newPreset = createPresetData(title); diff --git a/src/components/CharacterOrderDialog.vue b/src/components/CharacterOrderDialog.vue index f13d401aac..d190328212 100644 --- a/src/components/CharacterOrderDialog.vue +++ b/src/components/CharacterOrderDialog.vue @@ -181,7 +181,7 @@ watch( // FIXME: 不明なキャラを無視しているので、不明キャラの順番が保存時にリセットされてしまう characterOrder.value = store.state.userCharacterOrder .map((speakerUuid) => characterInfosMap.value[speakerUuid]) - .filter((info) => info !== undefined) as CharacterInfo[]; + .filter((info) => info != undefined) as CharacterInfo[]; // 含まれていないキャラクターを足す const notIncludesCharacterInfos = props.characterInfos.filter( @@ -237,7 +237,7 @@ const togglePlayOrStop = ( index: number ) => { if ( - playing.value === undefined || + playing.value == undefined || speakerUuid !== playing.value.speakerUuid || styleInfo.styleId !== playing.value.styleId || index !== playing.value.index diff --git a/src/components/CharacterPortrait.vue b/src/components/CharacterPortrait.vue index 334035ee4c..fcf913c560 100644 --- a/src/components/CharacterPortrait.vue +++ b/src/components/CharacterPortrait.vue @@ -29,8 +29,8 @@ const characterInfo = computed(() => { const styleId = audioItem?.voice.styleId; if ( - engineId === undefined || - styleId === undefined || + engineId == undefined || + styleId == undefined || !store.state.engineIds.some((id) => id === engineId) ) return undefined; diff --git a/src/components/DefaultStyleListDialog.vue b/src/components/DefaultStyleListDialog.vue index 7738669840..c097890649 100644 --- a/src/components/DefaultStyleListDialog.vue +++ b/src/components/DefaultStyleListDialog.vue @@ -106,7 +106,6 @@ import { useStore } from "@/store"; import { DEFAULT_STYLE_NAME } from "@/store/utility"; import { CharacterInfo, SpeakerId, StyleInfo } from "@/type/preload"; import DefaultStyleSelectDialog from "@/components/DefaultStyleSelectDialog.vue"; - const props = defineProps<{ modelValue: boolean; @@ -164,7 +163,7 @@ watch([() => props.modelValue], async ([newValue]) => { if (newValue) { speakerWithMultipleStyles.value = store.state.userCharacterOrder .map((speakerUuid) => characterInfosMap.value[speakerUuid]) - .filter((characterInfo) => characterInfo !== undefined) + .filter((characterInfo) => characterInfo != undefined) .filter( (characterInfo) => characterInfo.metas.styles.length > 1 ) as CharacterInfo[]; diff --git a/src/components/DefaultStyleSelectDialog.vue b/src/components/DefaultStyleSelectDialog.vue index bbbbd14c2a..957968cf11 100644 --- a/src/components/DefaultStyleSelectDialog.vue +++ b/src/components/DefaultStyleSelectDialog.vue @@ -173,7 +173,7 @@ const selectStyleIndex = (styleIndex: number) => { // 音声を再生する。同じ話者/styleIndexだったら停止する。 const selectedStyleInfo = props.characterInfo.metas.styles[styleIndex]; if ( - playing.value !== undefined && + playing.value != undefined && playing.value.styleId === selectedStyleInfo.styleId ) { stop(); diff --git a/src/components/Dialog.ts b/src/components/Dialog.ts index 517024e26d..ff681c4c17 100644 --- a/src/components/Dialog.ts +++ b/src/components/Dialog.ts @@ -260,7 +260,7 @@ export async function generateAndConnectAndSaveAudioWithDialog({ dispatch ); - if (result === undefined || result.result === "CANCELED") return; + if (result == undefined || result.result === "CANCELED") return; if (result.result === "SUCCESS") { if (disableNotifyOnGenerate) return; diff --git a/src/components/EngineManageDialog.vue b/src/components/EngineManageDialog.vue index 089bfc6e22..3c8740bcfd 100644 --- a/src/components/EngineManageDialog.vue +++ b/src/components/EngineManageDialog.vue @@ -367,7 +367,7 @@ const engineManageDialogOpenedComputed = computed({ set: (val) => emit("update:modelValue", val), }); const uiLockedState = ref(null); // ダイアログ内でstore.getters.UI_LOCKEDは常にtrueなので独自に管理 -const uiLocked = computed(() => uiLockedState.value !== null); +const uiLocked = computed(() => uiLockedState.value != null); const isAddingEngine = ref(false); const engineLoaderType = ref("vvpp"); diff --git a/src/components/FileNamePatternDialog.vue b/src/components/FileNamePatternDialog.vue index c58ef954dd..b329e02657 100644 --- a/src/components/FileNamePatternDialog.vue +++ b/src/components/FileNamePatternDialog.vue @@ -127,7 +127,7 @@ const errorMessage = computed(() => { } const result: string[] = []; - if (invalidChar.value !== undefined) { + if (invalidChar.value != undefined) { result.push(`使用できない文字が含まれています:「${invalidChar.value}」`); } if (previewFileName.value.includes("$")) { diff --git a/src/components/HeaderBarCustomDialog.vue b/src/components/HeaderBarCustomDialog.vue index bdad7c0e52..0e58a5276a 100644 --- a/src/components/HeaderBarCustomDialog.vue +++ b/src/components/HeaderBarCustomDialog.vue @@ -198,7 +198,7 @@ watch( if (oldData.length < newData.length) { selectedButton.value = newData[newData.length - 1]; } else if ( - selectedButton.value !== undefined && + selectedButton.value != undefined && oldData.includes(selectedButton.value) && !newData.includes(selectedButton.value) ) { diff --git a/src/components/HotkeySettingDialog.vue b/src/components/HotkeySettingDialog.vue index fdd98b0ba3..bdf34aa4b3 100644 --- a/src/components/HotkeySettingDialog.vue +++ b/src/components/HotkeySettingDialog.vue @@ -361,7 +361,7 @@ const resetHotkey = async (action: string) => { .getDefaultHotkeySettings() .then((defaultSettings: HotkeySetting[]) => { const setting = defaultSettings.find((value) => value.action == action); - if (setting === undefined) { + if (setting == undefined) { return; } // デフォルトが未設定でない場合は、衝突チェックを行う @@ -370,7 +370,7 @@ const resetHotkey = async (action: string) => { (item) => item.combination == setting.combination && item.action != action ); - if (duplicated !== undefined) { + if (duplicated != undefined) { openHotkeyDialog(action); lastRecord.value = duplicated.combination; return; diff --git a/src/components/MenuBar.vue b/src/components/MenuBar.vue index 5b5650e9d9..ea72b36190 100644 --- a/src/components/MenuBar.vue +++ b/src/components/MenuBar.vue @@ -102,11 +102,10 @@ const engineIds = computed(() => store.state.engineIds); const engineInfos = computed(() => store.state.engineInfos); const engineManifests = computed(() => store.state.engineManifests); const enableMultiEngine = computed(() => store.state.enableMultiEngine); - const titleText = computed( () => (isEdited.value ? "*" : "") + - (projectName.value !== undefined ? projectName.value + " - " : "") + + (projectName.value != undefined ? projectName.value + " - " : "") + "VOICEVOX" + (currentVersion.value ? " - Ver. " + currentVersion.value : "") + (isMultiEngineOffMode.value ? " - マルチエンジンオフ" : "") + From 7ddfaeb42ba37a7ca4990f73024f6534492a343c Mon Sep 17 00:00:00 2001 From: Nanashi Date: Sat, 6 Jan 2024 04:30:12 +0900 Subject: [PATCH 03/19] =?UTF-8?q?=E8=A4=87=E6=95=B0=E9=81=B8=E6=8A=9E?= =?UTF-8?q?=EF=BC=9A=E9=81=B8=E6=8A=9E=E4=B8=AD=E3=81=AE=E8=83=8C=E6=99=AF?= =?UTF-8?q?=E3=82=92=E6=BF=83=E3=81=8F=E3=81=99=E3=82=8B=20(#1683)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Change: 複数選択の背景を濃く * Add: キャラクターボタンに白背景を追加 * Change: 複数選択時だけハイライトするように * Fix: Shift+上下のバグを修正 * (デバッグ用) * Fix: typo修正 * Fix: Shift+上下のバグを修正 * Code: コメントを追加 * Change: ハイライトをcssクラスで制御するように * Fix: 条件をselectedと同じに * Code: TODOを追加 * selected-highlightへ * Update src/components/AudioCell.vue --------- Co-authored-by: Hiroshiba --- src/components/AudioCell.vue | 21 +++++++++++++++------ src/components/CharacterButton.vue | 2 ++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/components/AudioCell.vue b/src/components/AudioCell.vue index 14123c541c..26b76b6bd2 100644 --- a/src/components/AudioCell.vue +++ b/src/components/AudioCell.vue @@ -5,7 +5,13 @@ tabindex="-1" :class="{ active: isActiveAudioCell, + // selectedクラスはテストで使われているので残す。 + // TODO: テストをこのクラスに依存しないようにして、このクラスを消す。 selected: isSelectedAudioCell && isMultiSelectEnabled, + 'selected-highlight': + isSelectedAudioCell && + isMultiSelectEnabled && + selectedAudioKeys.length > 1, }" @keydown.prevent.up="moveUpCell" @keydown.prevent.down="moveDownCell" @@ -282,8 +288,9 @@ const selectedVoice = computed({ const isActiveAudioCell = computed( () => props.audioKey === store.getters.ACTIVE_AUDIO_KEY ); +const selectedAudioKeys = computed(() => store.getters.SELECTED_AUDIO_KEYS); const isSelectedAudioCell = computed(() => - store.getters.SELECTED_AUDIO_KEYS.includes(props.audioKey) + selectedAudioKeys.value.includes(props.audioKey) ); const audioTextBuffer = ref(audioItem.value.text); @@ -410,15 +417,17 @@ const moveCell = (offset: number) => (e?: KeyboardEvent) => { if (e && e.isComposing) return; const index = audioKeys.value.indexOf(props.audioKey) + offset; if (index >= 0 && index < audioKeys.value.length) { - const selectedAudioKeys = store.getters.SELECTED_AUDIO_KEYS; if (isMultiSelectEnabled.value && e?.shiftKey) { + // focusCellをemitする前にselectedAudioKeysを保存しておく。 + // (focusCellでselectedAudioKeysが変更されるため) + const selectedAudioKeysBefore = selectedAudioKeys.value; emit("focusCell", { audioKey: audioKeys.value[index], focusTarget: "root", }); store.dispatch("SET_SELECTED_AUDIO_KEYS", { audioKeys: [ - ...selectedAudioKeys, + ...selectedAudioKeysBefore, props.audioKey, audioKeys.value[index], ], @@ -652,8 +661,8 @@ const isMultipleEngine = computed(() => store.state.engineIds.length > 1); // divはフォーカスするとデフォルトで青い枠が出るので消す outline: none; } - &.selected { - background-color: rgba(colors.$active-point-focus-rgb, 0.5); + &.selected-highlight { + background-color: colors.$active-point-focus; } &:first-child { @@ -702,7 +711,7 @@ const isMultipleEngine = computed(() => store.state.engineIds.length > 1); padding-left: 5px; } - &.q-field--filled.q-field--highlighted :deep(.q-field__control):before { + &.q-field--filled.q-field--highlighted :deep(.q-field__control)::before { background-color: rgba(colors.$display-rgb, 0.08); } } diff --git a/src/components/CharacterButton.vue b/src/components/CharacterButton.vue index e7d048b45c..ba3364ec0d 100644 --- a/src/components/CharacterButton.vue +++ b/src/components/CharacterButton.vue @@ -347,6 +347,8 @@ const updateMenuHeight = () => { font-size: 0; height: fit-content; + background: colors.$background; + .icon-container { height: 2rem; width: 2rem; From f692df03f90f372ec592cec73b6c01255face156 Mon Sep 17 00:00:00 2001 From: Nanashi Date: Sun, 7 Jan 2024 06:26:11 +0900 Subject: [PATCH 04/19] =?UTF-8?q?Improve:=20e2e=E3=83=86=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=82=92=E6=94=B9=E5=96=84=20(#1685)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Improve: e2eテストを改善 * Change: ランダム文字列を辞書テストに使う用に * Change: type -> fill * Add: flaky対策を追加 * Fix: flaky対策の空白入力部分を修正 * Code: コメントを補足 Co-authored-by: Hiroshiba * Refactor: 辞書周りを整理 * Update tests/e2e/browser/辞書ダイアログ.spec.ts --------- Co-authored-by: Hiroshiba --- ...5\200\244\345\244\211\346\233\264.spec.ts" | 2 +- .../\351\201\270\346\212\236.spec.ts" | 33 +++++++-- ...3\202\242\343\203\255\343\202\260.spec.ts" | 69 +++++++++++++------ 3 files changed, 76 insertions(+), 28 deletions(-) diff --git "a/tests/e2e/browser/\350\244\207\346\225\260\351\201\270\346\212\236/\345\200\244\345\244\211\346\233\264.spec.ts" "b/tests/e2e/browser/\350\244\207\346\225\260\351\201\270\346\212\236/\345\200\244\345\244\211\346\233\264.spec.ts" index bec6822e3f..ee4d0a07a7 100644 --- "a/tests/e2e/browser/\350\244\207\346\225\260\351\201\270\346\212\236/\345\200\244\345\244\211\346\233\264.spec.ts" +++ "b/tests/e2e/browser/\350\244\207\346\225\260\351\201\270\346\212\236/\345\200\244\345\244\211\346\233\264.spec.ts" @@ -118,7 +118,7 @@ test("複数選択:AudioInfo操作", async ({ page }) => { for (const parameter of parameters) { const input = parameter.locator("label input"); - await input.type("2\n"); + await input.fill("2"); await page.waitForTimeout(100); } diff --git "a/tests/e2e/browser/\350\244\207\346\225\260\351\201\270\346\212\236/\351\201\270\346\212\236.spec.ts" "b/tests/e2e/browser/\350\244\207\346\225\260\351\201\270\346\212\236/\351\201\270\346\212\236.spec.ts" index 04768b8402..acfed4c3df 100644 --- "a/tests/e2e/browser/\350\244\207\346\225\260\351\201\270\346\212\236/\351\201\270\346\212\236.spec.ts" +++ "b/tests/e2e/browser/\350\244\207\346\225\260\351\201\270\346\212\236/\351\201\270\346\212\236.spec.ts" @@ -156,28 +156,51 @@ test("複数選択:キーボード", async ({ page }) => { // Shift+上で上方向を選択範囲にする await page.keyboard.down("Shift"); await page.keyboard.press("ArrowUp"); + await page.keyboard.press("ArrowUp"); await page.keyboard.up("Shift"); await page.waitForTimeout(100); selectedStatus = await getSelectedStatus(page); - expect(selectedStatus.active).toBe(3); - expect(selectedStatus.selected).toEqual([3, 4]); + expect(selectedStatus.active).toBe(2); + expect(selectedStatus.selected).toEqual([2, 3, 4]); // ただの上で上方向をactiveにして他の選択を解除する await page.keyboard.press("ArrowUp"); await page.waitForTimeout(100); selectedStatus = await getSelectedStatus(page); - expect(selectedStatus.active).toBe(2); - expect(selectedStatus.selected).toEqual([2]); + expect(selectedStatus.active).toBe(1); + expect(selectedStatus.selected).toEqual([1]); - // EnterでactiveのAudioCellのテキストフィールドにフォーカスし、複数選択を解除する + // Shift+下で下方向を選択範囲にする await page.keyboard.down("Shift"); await page.keyboard.press("ArrowDown"); + await page.keyboard.press("ArrowDown"); + await page.keyboard.up("Shift"); + await page.waitForTimeout(100); + + selectedStatus = await getSelectedStatus(page); + expect(selectedStatus.active).toBe(3); + expect(selectedStatus.selected).toEqual([1, 2, 3]); + + // ただの下で下方向をactiveにして他の選択を解除する + + await page.keyboard.press("ArrowDown"); + await page.waitForTimeout(100); + + selectedStatus = await getSelectedStatus(page); + expect(selectedStatus.active).toBe(4); + expect(selectedStatus.selected).toEqual([4]); + + // EnterでactiveのAudioCellのテキストフィールドにフォーカスし、複数選択を解除する + + await page.keyboard.down("Shift"); + await page.keyboard.press("ArrowUp"); await page.keyboard.up("Shift"); await page.keyboard.press("Enter"); await page.waitForTimeout(100); + selectedStatus = await getSelectedStatus(page); expect(selectedStatus.active).toBe(3); expect(selectedStatus.selected).toEqual([3]); diff --git "a/tests/e2e/browser/\350\276\236\346\233\270\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts" "b/tests/e2e/browser/\350\276\236\346\233\270\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts" index 16fb91bade..19b24a1a59 100644 --- "a/tests/e2e/browser/\350\276\236\346\233\270\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts" +++ "b/tests/e2e/browser/\350\276\236\346\233\270\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts" @@ -4,17 +4,29 @@ import { getNewestQuasarDialog } from "../locators"; test.beforeEach(gotoHome); -// 「abs」を入力して読み方を確認する -async function validateAbsYomi( - page: Page, - expectedText: string -): Promise { - await page.locator(".audio-cell input").last().fill("abs"); - await page.waitForTimeout(100); - await page.locator(".audio-cell input").last().press("Enter"); - await page.waitForTimeout(500); - const text = (await page.locator(".text-cell").allInnerTexts()).join(""); - expect(text).toBe(expectedText); +// 読み方を確認する。 +// エンジン起動直後など、たまに読みが反映されないことがあるので、 +// 一度空にする -> テキストが消えたことを確認(消えてなかったらもう一度Enter)-> +// 再度入力する -> 読み方が表示されたことを確認(表示されてなかったらもう一度Enter) +// という流れで読み方を確認する。 +async function getYomi(page: Page, inputText: string): Promise { + const audioCellInput = page.locator(".audio-cell input").last(); + await audioCellInput.fill(""); + let text = ""; + do { + await page.waitForTimeout(100); + await audioCellInput.press("Enter"); + text = (await page.locator(".text-cell").allInnerTexts()).join(""); + } while (text.length > 0); + + await audioCellInput.fill(inputText); + do { + await page.waitForTimeout(100); + await audioCellInput.press("Enter"); + text = (await page.locator(".text-cell").allInnerTexts()).join(""); + } while (text.length === 0); + + return text; } async function openDictDialog(page: Page): Promise { @@ -42,8 +54,15 @@ test("「設定」→「読み方&アクセント辞書」で「読み方& }) => { test.skip(!process.env.CI, "環境変数CIが未設定のためスキップします"); await navigateToMain(page); - // アルファベットを入力し、読み方を確認 - await validateAbsYomi(page, "エエビイエス"); + + // テスト用にランダムな文字列を生成 + const randomString = Math.random().toString(36).slice(-8); + const zenkakuRandomString = randomString.replace(/[\u0021-\u007e]/g, (s) => { + return String.fromCharCode(s.charCodeAt(0) + 0xfee0); + }); + + // 文字列を入力して読み方を記憶する + const yomi = await getYomi(page, randomString); // 読み方の設定画面を開く await openDictDialog(page); @@ -54,12 +73,12 @@ test("「設定」→「読み方&アクセント辞書」で「読み方& .locator(".word-editor .row") .filter({ hasText: "単語" }) .locator(".q-field__native"); - wordInputTag.evaluate((e: HTMLInputElement) => { - e.value = "abs"; + wordInputTag.evaluate((e: HTMLInputElement, rs: string) => { + e.value = rs; e.dispatchEvent(new Event("input")); - }); + }, randomString); await page.waitForTimeout(100); - await validateInputTag(page, wordInputTag, "abs"); + await validateInputTag(page, wordInputTag, zenkakuRandomString); const yomiInputTag = page .locator(".word-editor .row") @@ -67,11 +86,11 @@ test("「設定」→「読み方&アクセント辞書」で「読み方& .locator(".q-field__native"); await yomiInputTag.evaluate((e: HTMLInputElement) => { - e.value = "アブス"; + e.value = "テスト"; e.dispatchEvent(new Event("input")); }); await page.waitForTimeout(100); - await validateInputTag(page, yomiInputTag, "アブス"); + await validateInputTag(page, yomiInputTag, "テスト"); // 保存して設定画面を閉じる await page.getByText("保存", { exact: true }).click(); @@ -84,11 +103,15 @@ test("「設定」→「読み方&アクセント辞書」で「読み方& // 辞書が登録されているかどうかを確認 await page.getByRole("button").filter({ hasText: "add" }).click(); await page.waitForTimeout(100); - await validateAbsYomi(page, "アブス"); + const yomi2 = await getYomi(page, randomString); + expect(yomi2).toBe("テスト"); // もう一度設定を開き辞書からabsを削除 await openDictDialog(page); - await page.getByRole("listitem").filter({ hasText: "abs" }).click(); + await page + .getByRole("listitem") + .filter({ hasText: zenkakuRandomString }) + .click(); await page.waitForTimeout(100); await page .locator(".word-list-header") @@ -109,7 +132,9 @@ test("「設定」→「読み方&アクセント辞書」で「読み方& await page.waitForTimeout(100); // 辞書から削除されていることを確認 + // (=最初の読み方と同じになっていることを確認) await page.getByRole("button").filter({ hasText: "add" }).click(); await page.waitForTimeout(100); - await validateAbsYomi(page, "エエビイエス"); + const yomi3 = await getYomi(page, randomString); + expect(yomi3).toBe(yomi); }); From 1e7bed700eeed3b4777f85bcc3d6977d44c2408e Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Tue, 9 Jan 2024 07:28:00 +0900 Subject: [PATCH 05/19] =?UTF-8?q?=E8=A8=AD=E5=AE=9A=E5=80=A4=E3=82=92?= =?UTF-8?q?=E5=A2=97=E3=82=84=E3=81=99=E3=81=A8=E3=81=8D=E3=81=AE=E6=89=8B?= =?UTF-8?q?=E9=96=93=E3=82=92=E6=B8=9B=E3=82=89=E3=81=99=20(#1694)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 設定値を増やすときの手間を減らす * Update src/store/setting.ts Co-authored-by: Nanashi. * Update src/store/setting.ts Co-authored-by: Nanashi. * 変更 --------- Co-authored-by: Nanashi. --- src/components/SettingDialog.vue | 30 ++++-- src/store/setting.ts | 138 ++++++++------------------ src/store/type.ts | 55 +++-------- src/type/preload.ts | 163 ++++++++++++++++--------------- src/type/utility.ts | 6 ++ src/views/EditorHome.vue | 5 +- 6 files changed, 171 insertions(+), 226 deletions(-) create mode 100644 src/type/utility.ts diff --git a/src/components/SettingDialog.vue b/src/components/SettingDialog.vue index f518e4d051..03a1a62f4f 100644 --- a/src/components/SettingDialog.vue +++ b/src/components/SettingDialog.vue @@ -1041,18 +1041,25 @@ const availableThemeNameComputed = computed(() => { const editorFont = computed(() => store.state.editorFont); const changeEditorFont = (editorFont: EditorFontType) => { - store.dispatch("SET_EDITOR_FONT", { editorFont }); + store.dispatch("SET_ROOT_MISC_SETTING", { + key: "editorFont", + value: editorFont, + }); }; const enableMultiEngine = computed(() => store.state.enableMultiEngine); const setEnableMultiEngine = (enableMultiEngine: boolean) => { - store.dispatch("SET_ENABLE_MULTI_ENGINE", { enableMultiEngine }); + store.dispatch("SET_ROOT_MISC_SETTING", { + key: "enableMultiEngine", + value: enableMultiEngine, + }); }; const showTextLineNumber = computed(() => store.state.showTextLineNumber); const changeShowTextLineNumber = (showTextLineNumber: boolean) => { - store.dispatch("SET_SHOW_TEXT_LINE_NUMBER", { - showTextLineNumber, + store.dispatch("SET_ROOT_MISC_SETTING", { + key: "showTextLineNumber", + value: showTextLineNumber, }); }; @@ -1063,8 +1070,9 @@ const showAddAudioItemButton = computed( const changeShowAddAudioItemButton = async ( showAddAudioItemButton: boolean ) => { - store.dispatch("SET_SHOW_ADD_AUDIO_ITEM_BUTTON", { - showAddAudioItemButton, + store.dispatch("SET_ROOT_MISC_SETTING", { + key: "showAddAudioItemButton", + value: showAddAudioItemButton, }); // 設定をオフにする場合はヒントを表示 @@ -1076,8 +1084,9 @@ const changeShowAddAudioItemButton = async ( }); if (result === "CANCEL") { // キャンセルしたら設定を元に戻す - store.dispatch("SET_SHOW_ADD_AUDIO_ITEM_BUTTON", { - showAddAudioItemButton: true, + store.dispatch("SET_ROOT_MISC_SETTING", { + key: "showAddAudioItemButton", + value: true, }); } } @@ -1272,7 +1281,10 @@ const splitTextWhenPaste = computed(() => store.state.splitTextWhenPaste); const changeSplitTextWhenPaste = ( splitTextWhenPaste: SplitTextWhenPasteType ) => { - store.dispatch("SET_SPLIT_TEXT_WHEN_PASTE", { splitTextWhenPaste }); + store.dispatch("SET_ROOT_MISC_SETTING", { + key: "splitTextWhenPaste", + value: splitTextWhenPaste, + }); }; const showsFilePatternEditDialog = ref(false); diff --git a/src/store/setting.ts b/src/store/setting.ts index 98a7f609ec..9d3381d713 100644 --- a/src/store/setting.ts +++ b/src/store/setting.ts @@ -15,7 +15,9 @@ import { ToolbarSetting, EngineId, ConfirmedTips, + RootMiscSetting, } from "@/type/preload"; +import { IsEqual } from "@/type/utility"; const hotkeyFunctionCache: Record HotkeyReturnType> = {}; @@ -89,22 +91,6 @@ export const settingStore = createPartialStore({ }); } - dispatch("SET_EDITOR_FONT", { - editorFont: await window.electron.getSetting("editorFont"), - }); - - dispatch("SET_SHOW_TEXT_LINE_NUMBER", { - showTextLineNumber: await window.electron.getSetting( - "showTextLineNumber" - ), - }); - - dispatch("SET_SHOW_ADD_AUDIO_ITEM_BUTTON", { - showAddAudioItemButton: await window.electron.getSetting( - "showAddAudioItemButton" - ), - }); - dispatch("SET_ACCEPT_RETRIEVE_TELEMETRY", { acceptRetrieveTelemetry: await window.electron.getSetting( "acceptRetrieveTelemetry" @@ -129,16 +115,6 @@ export const settingStore = createPartialStore({ ), }); - commit("SET_SPLIT_TEXT_WHEN_PASTE", { - splitTextWhenPaste: await window.electron.getSetting( - "splitTextWhenPaste" - ), - }); - - commit("SET_SPLITTER_POSITION", { - splitterPosition: await window.electron.getSetting("splitterPosition"), - }); - commit("SET_CONFIRMED_TIPS", { confirmedTips: await window.electron.getSetting("confirmedTips"), }); @@ -157,11 +133,30 @@ export const settingStore = createPartialStore({ }); } - commit("SET_ENABLE_MULTI_ENGINE", { - enableMultiEngine: await window.electron.getSetting( - "enableMultiEngine" - ), - }); + const rootMiscSettingKeys = [ + "editorFont", + "showTextLineNumber", + "showAddAudioItemButton", + "splitTextWhenPaste", + "splitterPosition", + "enableMultiEngine", + ] as const; + + // rootMiscSettingKeysに値を足し忘れていたときに型エラーを出す検出用コード + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const _: IsEqual< + keyof RootMiscSetting, + typeof rootMiscSettingKeys[number] + > = true; + + for (const key of rootMiscSettingKeys) { + commit("SET_ROOT_MISC_SETTING", { + // Vuexの型処理でUnionが解かれてしまうのを迂回している + // FIXME: このワークアラウンドをなくす + key: key as never, + value: await window.electron.getSetting(key), + }); + } }, }, @@ -225,6 +220,20 @@ export const settingStore = createPartialStore({ }, }, + SET_ROOT_MISC_SETTING: { + mutation(state, { key, value }) { + // Vuexの型処理でUnionが解かれてしまうのを迂回している + // FIXME: このワークアラウンドをなくす + state[key as never] = value; + }, + action({ commit }, { key, value }) { + window.electron.setSetting(key, value); + // Vuexの型処理でUnionが解かれてしまうのを迂回している + // FIXME: このワークアラウンドをなくす + commit("SET_ROOT_MISC_SETTING", { key: key as never, value }); + }, + }, + SET_THEME_SETTING: { mutation( state, @@ -285,43 +294,6 @@ export const settingStore = createPartialStore({ }, }, - SET_EDITOR_FONT: { - mutation(state, { editorFont }) { - state.editorFont = editorFont; - }, - action({ commit }, { editorFont }) { - window.electron.setSetting("editorFont", editorFont); - commit("SET_EDITOR_FONT", { editorFont }); - }, - }, - - SET_SHOW_TEXT_LINE_NUMBER: { - mutation(state, { showTextLineNumber }) { - state.showTextLineNumber = showTextLineNumber; - }, - action({ commit }, { showTextLineNumber }) { - window.electron.setSetting("showTextLineNumber", showTextLineNumber); - commit("SET_SHOW_TEXT_LINE_NUMBER", { - showTextLineNumber, - }); - }, - }, - - SET_SHOW_ADD_AUDIO_ITEM_BUTTON: { - mutation(state, { showAddAudioItemButton }) { - state.showAddAudioItemButton = showAddAudioItemButton; - }, - action({ commit }, { showAddAudioItemButton }) { - window.electron.setSetting( - "showAddAudioItemButton", - showAddAudioItemButton - ); - commit("SET_SHOW_ADD_AUDIO_ITEM_BUTTON", { - showAddAudioItemButton, - }); - }, - }, - SET_ACCEPT_RETRIEVE_TELEMETRY: { mutation(state, { acceptRetrieveTelemetry }) { state.acceptRetrieveTelemetry = acceptRetrieveTelemetry; @@ -366,26 +338,6 @@ export const settingStore = createPartialStore({ }, }, - SET_SPLIT_TEXT_WHEN_PASTE: { - mutation(state, { splitTextWhenPaste }) { - state.splitTextWhenPaste = splitTextWhenPaste; - }, - action({ commit }, { splitTextWhenPaste }) { - window.electron.setSetting("splitTextWhenPaste", splitTextWhenPaste); - commit("SET_SPLIT_TEXT_WHEN_PASTE", { splitTextWhenPaste }); - }, - }, - - SET_SPLITTER_POSITION: { - mutation(state, { splitterPosition }) { - state.splitterPosition = splitterPosition; - }, - action({ commit }, { splitterPosition }) { - window.electron.setSetting("splitterPosition", splitterPosition); - commit("SET_SPLITTER_POSITION", { splitterPosition }); - }, - }, - SET_CONFIRMED_TIPS: { mutation(state, { confirmedTips }) { state.confirmedTips = confirmedTips; @@ -436,16 +388,6 @@ export const settingStore = createPartialStore({ }, }, - SET_ENABLE_MULTI_ENGINE: { - mutation(state, { enableMultiEngine }) { - state.enableMultiEngine = enableMultiEngine; - }, - action({ commit }, { enableMultiEngine }) { - window.electron.setSetting("enableMultiEngine", enableMultiEngine); - commit("SET_ENABLE_MULTI_ENGINE", { enableMultiEngine }); - }, - }, - CHANGE_USE_GPU: { /** * CPU/GPUモードを切り替えようとする。 diff --git a/src/store/type.ts b/src/store/type.ts index b7006cda73..22dae4cc20 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -33,11 +33,8 @@ import { MorphingInfo, ActivePointScrollMode, EngineInfo, - SplitTextWhenPasteType, - SplitterPosition, ConfirmedTips, EngineDirValidationResult, - EditorFontType, EngineSettings, MorphableTargetInfoTable, EngineSetting, @@ -48,6 +45,7 @@ import { StyleId, AudioKey, PresetKey, + RootMiscSetting, } from "@/type/preload"; import { IEngineConnectorFactory } from "@/infrastructures/EngineConnector"; import { @@ -1040,17 +1038,19 @@ export type SettingStoreState = { engineInfos: Record; engineManifests: Record; themeSetting: ThemeSetting; - editorFont: EditorFontType; - showTextLineNumber: boolean; - showAddAudioItemButton: boolean; acceptRetrieveTelemetry: AcceptRetrieveTelemetryStatus; experimentalSetting: ExperimentalSetting; - splitTextWhenPaste: SplitTextWhenPasteType; - splitterPosition: SplitterPosition; confirmedTips: ConfirmedTips; engineSettings: EngineSettings; - enableMultiEngine: boolean; -}; +} & RootMiscSetting; + +// keyとvalueの型を連動するようにしたPayloadを作る +type KeyValuePayload = K extends keyof R + ? { + key: K; + value: R[K]; + } + : never; export type SettingStoreTypes = { HYDRATE_SETTING_STORE: { @@ -1072,26 +1072,16 @@ export type SettingStoreTypes = { action(payload: { data: ToolbarSetting }): void; }; + SET_ROOT_MISC_SETTING: { + mutation: KeyValuePayload; + action(payload: KeyValuePayload): void; + }; + SET_THEME_SETTING: { mutation: { currentTheme: string; themes?: ThemeConf[] }; action(payload: { currentTheme: string }): void; }; - SET_EDITOR_FONT: { - mutation: { editorFont: EditorFontType }; - action(payload: { editorFont: EditorFontType }): void; - }; - - SET_SHOW_TEXT_LINE_NUMBER: { - mutation: { showTextLineNumber: boolean }; - action(payload: { showTextLineNumber: boolean }): void; - }; - - SET_SHOW_ADD_AUDIO_ITEM_BUTTON: { - mutation: { showAddAudioItemButton: boolean }; - action(payload: { showAddAudioItemButton: boolean }): void; - }; - SET_ACCEPT_RETRIEVE_TELEMETRY: { mutation: { acceptRetrieveTelemetry: AcceptRetrieveTelemetryStatus }; action(payload: { @@ -1109,16 +1099,6 @@ export type SettingStoreTypes = { action(payload: { experimentalSetting: ExperimentalSetting }): void; }; - SET_SPLIT_TEXT_WHEN_PASTE: { - mutation: { splitTextWhenPaste: SplitTextWhenPasteType }; - action(payload: { splitTextWhenPaste: SplitTextWhenPasteType }): void; - }; - - SET_SPLITTER_POSITION: { - mutation: { splitterPosition: SplitterPosition }; - action(payload: { splitterPosition: SplitterPosition }): void; - }; - SET_CONFIRMED_TIPS: { mutation: { confirmedTips: ConfirmedTips }; action(payload: { confirmedTips: ConfirmedTips }): void; @@ -1140,11 +1120,6 @@ export type SettingStoreTypes = { }): Promise; }; - SET_ENABLE_MULTI_ENGINE: { - mutation: { enableMultiEngine: boolean }; - action(payload: { enableMultiEngine: boolean }): void; - }; - CHANGE_USE_GPU: { action(payload: { useGpu: boolean; engineId: EngineId }): Promise; }; diff --git a/src/type/preload.ts b/src/type/preload.ts index ef3820ffb7..eca6fb8554 100644 --- a/src/type/preload.ts +++ b/src/type/preload.ts @@ -508,92 +508,101 @@ export type ConfirmedTips = { notifyOnGenerate: boolean; // 音声書き出し時の通知 }; -export const configSchema = z.object({ - inheritAudioInfo: z.boolean().default(true), - activePointScrollMode: z.enum(["CONTINUOUSLY", "PAGE", "OFF"]).default("OFF"), - savingSetting: z - .object({ - fileEncoding: z.enum(["UTF-8", "Shift_JIS"]).default("UTF-8"), - fileNamePattern: z.string().default(""), - fixedExportEnabled: z.boolean().default(false), - avoidOverwrite: z.boolean().default(false), - fixedExportDir: z.string().default(""), - exportLab: z.boolean().default(false), - exportText: z.boolean().default(false), - outputStereo: z.boolean().default(false), - audioOutputDevice: z.string().default(""), - }) - .default({}), - hotkeySettings: hotkeySettingSchema.array().default(defaultHotkeySettings), - toolbarSetting: toolbarSettingSchema - .array() - .default(defaultToolbarButtonSetting), - engineSettings: z.record(engineIdSchema, engineSettingSchema).default({}), - userCharacterOrder: speakerIdSchema.array().default([]), - defaultStyleIds: z - .object({ - engineId: engineIdSchema - .or(z.literal(EngineId("00000000-0000-0000-0000-000000000000"))) - .default(EngineId("00000000-0000-0000-0000-000000000000")), - speakerUuid: speakerIdSchema, - defaultStyleId: styleIdSchema, - }) - .array() - .default([]), - presets: z - .object({ - items: z - .record( - presetKeySchema, - z.object({ - name: z.string(), - speedScale: z.number(), - pitchScale: z.number(), - intonationScale: z.number(), - volumeScale: z.number(), - prePhonemeLength: z.number(), - postPhonemeLength: z.number(), - morphingInfo: z - .object({ - rate: z.number(), - targetEngineId: engineIdSchema, - targetSpeakerId: speakerIdSchema, - targetStyleId: styleIdSchema, - }) - .optional(), - }) - ) - .default({}), - keys: presetKeySchema.array().default([]), - }) - .default({}), - defaultPresetKeys: z.record(voiceIdSchema, presetKeySchema).default({}), - currentTheme: z.string().default("Default"), +// ルート直下にある雑多な設定値 +export const rootMiscSettingSchema = z.object({ editorFont: z.enum(["default", "os"]).default("default"), showTextLineNumber: z.boolean().default(false), showAddAudioItemButton: z.boolean().default(true), - experimentalSetting: experimentalSettingSchema.default({}), - acceptRetrieveTelemetry: z - .enum(["Unconfirmed", "Accepted", "Refused"]) - .default("Unconfirmed"), - acceptTerms: z - .enum(["Unconfirmed", "Accepted", "Rejected"]) - .default("Unconfirmed"), splitTextWhenPaste: z .enum(["PERIOD_AND_NEW_LINE", "NEW_LINE", "OFF"]) .default("PERIOD_AND_NEW_LINE"), splitterPosition: splitterPositionSchema.default({}), - confirmedTips: z - .object({ - tweakableSliderByScroll: z.boolean().default(false), - engineStartedOnAltPort: z.boolean().default(false), - notifyOnGenerate: z.boolean().default(false), - }) - .default({}), - registeredEngineDirs: z.string().array().default([]), - recentlyUsedProjects: z.string().array().default([]), enableMultiEngine: z.boolean().default(false), }); +export type RootMiscSetting = z.infer; + +export const configSchema = z + .object({ + inheritAudioInfo: z.boolean().default(true), + activePointScrollMode: z + .enum(["CONTINUOUSLY", "PAGE", "OFF"]) + .default("OFF"), + savingSetting: z + .object({ + fileEncoding: z.enum(["UTF-8", "Shift_JIS"]).default("UTF-8"), + fileNamePattern: z.string().default(""), + fixedExportEnabled: z.boolean().default(false), + avoidOverwrite: z.boolean().default(false), + fixedExportDir: z.string().default(""), + exportLab: z.boolean().default(false), + exportText: z.boolean().default(false), + outputStereo: z.boolean().default(false), + audioOutputDevice: z.string().default(""), + }) + .default({}), + hotkeySettings: hotkeySettingSchema.array().default(defaultHotkeySettings), + toolbarSetting: toolbarSettingSchema + .array() + .default(defaultToolbarButtonSetting), + engineSettings: z.record(engineIdSchema, engineSettingSchema).default({}), + userCharacterOrder: speakerIdSchema.array().default([]), + defaultStyleIds: z + .object({ + engineId: engineIdSchema + .or(z.literal(EngineId("00000000-0000-0000-0000-000000000000"))) + .default(EngineId("00000000-0000-0000-0000-000000000000")), + speakerUuid: speakerIdSchema, + defaultStyleId: styleIdSchema, + }) + .array() + .default([]), + presets: z + .object({ + items: z + .record( + presetKeySchema, + z.object({ + name: z.string(), + speedScale: z.number(), + pitchScale: z.number(), + intonationScale: z.number(), + volumeScale: z.number(), + prePhonemeLength: z.number(), + postPhonemeLength: z.number(), + morphingInfo: z + .object({ + rate: z.number(), + targetEngineId: engineIdSchema, + targetSpeakerId: speakerIdSchema, + targetStyleId: styleIdSchema, + }) + .optional(), + }) + ) + .default({}), + keys: presetKeySchema.array().default([]), + }) + .default({}), + defaultPresetKeys: z.record(voiceIdSchema, presetKeySchema).default({}), + currentTheme: z.string().default("Default"), + experimentalSetting: experimentalSettingSchema.default({}), + acceptRetrieveTelemetry: z + .enum(["Unconfirmed", "Accepted", "Refused"]) + .default("Unconfirmed"), + acceptTerms: z + .enum(["Unconfirmed", "Accepted", "Rejected"]) + .default("Unconfirmed"), + confirmedTips: z + .object({ + tweakableSliderByScroll: z.boolean().default(false), + engineStartedOnAltPort: z.boolean().default(false), + notifyOnGenerate: z.boolean().default(false), + }) + .default({}), + registeredEngineDirs: z.string().array().default([]), + recentlyUsedProjects: z.string().array().default([]), + }) + .merge(rootMiscSettingSchema); export type ConfigType = z.infer; export const envEngineInfoSchema = z.object({ diff --git a/src/type/utility.ts b/src/type/utility.ts new file mode 100644 index 0000000000..365c30db99 --- /dev/null +++ b/src/type/utility.ts @@ -0,0 +1,6 @@ +// XとYが同じ型かどうかを判定する +export type IsEqual = (() => T extends X ? 1 : 2) extends < + T +>() => T extends Y ? 1 : 2 + ? true + : false; diff --git a/src/views/EditorHome.vue b/src/views/EditorHome.vue index a34c16c412..827f55ab75 100644 --- a/src/views/EditorHome.vue +++ b/src/views/EditorHome.vue @@ -358,8 +358,9 @@ const updateSplitterPosition = async ( ...splitterPosition.value, [propertyName]: newValue, }; - store.dispatch("SET_SPLITTER_POSITION", { - splitterPosition: newSplitterPosition, + await store.dispatch("SET_ROOT_MISC_SETTING", { + key: "splitterPosition", + value: newSplitterPosition, }); }; From 3152e372ad18a33f46710bce65ce8bd2beaf285f Mon Sep 17 00:00:00 2001 From: Nanashi Date: Wed, 10 Jan 2024 01:37:24 +0900 Subject: [PATCH 06/19] =?UTF-8?q?Code:=20schema=E7=B3=BB=E3=81=AE=E5=91=BD?= =?UTF-8?q?=E5=90=8D=E8=A6=8F=E5=89=87=E3=82=92=E7=B5=B1=E4=B8=80=20(#1697?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Code: schema系の命名規則を統一 * Add: ドキュメントに追記 * Fix: 日本語を修正 --- ...55\350\250\210\346\226\271\351\207\235.md" | 19 ++++++++-- src/background/engineManager.ts | 6 +-- src/background/vvppManager.ts | 23 +++++------ src/browser/sandbox.ts | 8 ++-- src/components/AudioDetail.vue | 4 +- src/components/HeaderBar.vue | 6 +-- src/components/HeaderBarCustomDialog.vue | 4 +- src/components/HotkeySettingDialog.vue | 6 +-- src/components/MenuBar.vue | 4 +- src/components/SettingDialog.vue | 8 ++-- src/shared/ConfigManager.ts | 10 ++--- src/store/setting.ts | 31 ++++++++------- src/store/type.ts | 38 +++++++++---------- src/type/ipc.ts | 16 ++++---- src/type/preload.ts | 34 +++++++++-------- src/views/EditorHome.vue | 10 ++--- tests/unit/background/VvppManager.spec.ts | 4 +- 17 files changed, 125 insertions(+), 106 deletions(-) diff --git "a/docs/\347\264\260\343\201\213\343\201\204\350\250\255\350\250\210\346\226\271\351\207\235.md" "b/docs/\347\264\260\343\201\213\343\201\204\350\250\255\350\250\210\346\226\271\351\207\235.md" index 11155d84d4..3c999d57b6 100644 --- "a/docs/\347\264\260\343\201\213\343\201\204\350\250\255\350\250\210\346\226\271\351\207\235.md" +++ "b/docs/\347\264\260\343\201\213\343\201\204\350\250\255\350\250\210\346\226\271\351\207\235.md" @@ -13,15 +13,28 @@ SpeakerId は話者が持つ ID で、世界で唯一かつ不変。エンジン StyleId はスタイルごとの ID で、エンジンごとに唯一であれば良いです。 声を一意に決めるには、(EngineId, SpeakerId, StyleId)の3組が揃っている必要がある、という仕様を目指しています。 -現状は、音声合成APIに SpeakerId 引数が無いため、(EngineId, StyleId)の2組で一意に声が決まっています。 +現状は、音声合成 API に SpeakerId 引数が無いため、(EngineId, StyleId)の2組で一意に声が決まっています。 現状は StyleId はエンジンごとに唯一である必要がありますが、話者ごとに唯一であれば良いという仕様を目指しています。 VOICEVOX は歴史的経緯により、 SpeakerId と StyleId が混同していることがあります。 特に型が整数値になっている SpeakerId は StyleId と混同している場合があります。 (エンジン API の SpeakerId 引数に StyleId を渡したりなど。) -StyleId は現在整数値型になっていますが、将来的にはUuidにしたいと考えています。 +StyleId は現在整数値型になっていますが、将来的には Uuid にしたいと考えています。 -## シングルファイルコンポーネント(SFC、`.vue`ファイル)のtemplate、script、styleの順序 +## シングルファイルコンポーネント(SFC、`.vue`ファイル)の template、script、style の順序 ` @@ -562,6 +563,12 @@ const newUpdateResult = useFetchNewUpdateInfos( () => window.electron.getAppInfos().then((obj) => obj.version), // アプリのバージョン import.meta.env.VITE_LATEST_UPDATE_INFOS_URL ); +const handleSkipThisVersionClick = (version: string) => { + store.dispatch("SET_ROOT_MISC_SETTING", { + key: "skipUpdateVersion", + value: version, + }); +}; // ソフトウェアを初期化 const isCompletedInitialStartup = ref(false); @@ -638,6 +645,17 @@ onMounted(async () => { document.addEventListener("keyup", item); }); + // 設定の読み込みを待機する + // FIXME: 設定が必要な処理はINIT_VUEXを実行しているApp.vueで行うべき + let vuexReadyTimeout = 0; + while (!store.state.isVuexReady) { + if (vuexReadyTimeout >= 15000) { + throw new Error("Vuexが準備できませんでした"); + } + await new Promise((resolve) => setTimeout(resolve, 300)); + vuexReadyTimeout += 300; + } + isAcceptRetrieveTelemetryDialogOpenComputed.value = store.state.acceptRetrieveTelemetry === "Unconfirmed"; @@ -645,12 +663,16 @@ onMounted(async () => { import.meta.env.MODE !== "development" && store.state.acceptTerms !== "Accepted"; + // アップデート通知ダイアログ if (newUpdateResult.value.status === "updateAvailable") { - const skipUpdateVersion = await window.electron.getSetting( - "skipUpdateVersion" - ); - if ( - semver.valid(skipUpdateVersion) == undefined || + const skipUpdateVersion = store.state.skipUpdateVersion ?? "0.0.0"; + if (semver.valid(skipUpdateVersion) == undefined) { + // 処理を止めるほどではないので警告だけ + store.dispatch( + "LOG_WARN", + `skipUpdateVersionが不正です: ${skipUpdateVersion}` + ); + } else if ( semver.gt(newUpdateResult.value.latestVersion, skipUpdateVersion) ) { isUpdateNotificationDialogOpenComputed.value = true; From 49e2c7cfcd81fedf982021e6a5d1afc1088a4ef1 Mon Sep 17 00:00:00 2001 From: Nanashi Date: Wed, 17 Jan 2024 19:13:41 +0900 Subject: [PATCH 16/19] =?UTF-8?q?Add:=20VRT=E3=82=92=E8=BF=BD=E5=8A=A0=20(?= =?UTF-8?q?#1688)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add: voicevox_engineをsubmoduleとして追加 * Add: とりあえず動くように * Add: submodules指定を追加 * Change: OS名をファイル名から削除 * Fix: アイコンを修正 * Change: osをartifactの名前に追加 * Change: スクショをGitHub Actionsで更新するように * [update-snapshots] * [update snapshots] * Fix: electronとbrowserが逆だったので修正 [update snapshots] * Change: コミットを一つのjobに分離 * Add: needsを追加 [update snapshots] * Change: 検出方法を変更 [update snapshots] * (スナップショットを更新) [skip ci] * Delete: 使われてないスクショを削除 * Add: メッセージを追加 * Add: 説明を追加 * Code: コメントを変更 * Add: ヒントを追加 * Fix: pull_requestイベントで動くように * Change: テスト用アセットを変更 [update snapshots] * (スナップショットを更新) [skip ci] * Fix: markdownlintのエラーを修正 * Change: skip ciしないように * Change: モック画像を変更 [update snapshots] * Update: playwrightを更新 * Fix: ファイル名を指定 [update snapshots] * (スナップショットを更新) * (CI用) * Add: 1%の誤差は許容するように * Update: スナップショットを更新 [update snapshots] * (CI用) * Change: 許容誤差を0.1%に * Delete: xvfb-runを削除 * Change: Windowsに限定 * Improve: READMEの記述を改善 Co-authored-by: Hiroshiba * Change: スタイル名を一意に Co-authored-by: Hiroshiba * Delete: 不要な指定を削除 * Change: updateSnapshots -> shouldUpdateSnapshots * (スクショ更新) [update-snapshots] * to shouldUpdateSnapshots [update snapshots] --------- Co-authored-by: github-actions[bot] Co-authored-by: Hiroshiba Co-authored-by: Hiroshiba Kazuyuki --- .github/workflows/test.yml | 63 +++++++- README.md | 15 ++ package-lock.json | 46 +++--- package.json | 2 +- playwright.config.ts | 3 + tests/e2e/browser/assets/icon_1.png | Bin 0 -> 6816 bytes tests/e2e/browser/assets/icon_2.png | Bin 0 -> 9031 bytes tests/e2e/browser/assets/icon_3.png | Bin 0 -> 21472 bytes tests/e2e/browser/assets/icon_4.png | Bin 0 -> 21178 bytes tests/e2e/browser/assets/portrait_1.png | Bin 0 -> 7316 bytes tests/e2e/browser/assets/portrait_2.png | Bin 0 -> 8555 bytes tests/e2e/browser/assets/portrait_3.png | Bin 0 -> 21800 bytes tests/e2e/browser/assets/portrait_4.png | Bin 0 -> 19786 bytes ...3\203\247\343\203\203\343\203\210.spec.ts" | 142 ++++++++++++++++++ ...347\224\273\351\235\242-browser-win32.png" | Bin 0 -> 47801 bytes 15 files changed, 242 insertions(+), 29 deletions(-) create mode 100644 tests/e2e/browser/assets/icon_1.png create mode 100644 tests/e2e/browser/assets/icon_2.png create mode 100644 tests/e2e/browser/assets/icon_3.png create mode 100644 tests/e2e/browser/assets/icon_4.png create mode 100644 tests/e2e/browser/assets/portrait_1.png create mode 100644 tests/e2e/browser/assets/portrait_2.png create mode 100644 tests/e2e/browser/assets/portrait_3.png create mode 100644 tests/e2e/browser/assets/portrait_4.png create mode 100644 "tests/e2e/browser/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.ts" create mode 100644 "tests/e2e/browser/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.ts-snapshots/\343\203\241\343\202\244\343\203\263\347\224\273\351\235\242-browser-win32.png" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7e6ecfb964..aa28c70ed0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -84,16 +84,34 @@ jobs: sed -i -e 's|"executionArgs": \[\],|"executionArgs": ["--port=50021"],|' .env.test cp .env.test .env + - name: Check if commit message includes [update snapshots] + id: check-whether-to-update-snapshots + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const commits = ${{ toJson(github.event.commits) }}; + if (!commits) { + // pull_request などでコミットがない場合はスキップ + core.setOutput("shouldUpdateSnapshots", false); + process.exit(0); + } + const shouldUpdateSnapshots = commits.some((commit) => + commit.message.toLowerCase().includes("[update snapshots]") + ); + core.setOutput("shouldUpdateSnapshots", shouldUpdateSnapshots); + console.log(`shouldUpdateSnapshots: ${shouldUpdateSnapshots}`); + - name: Run npm run test:browser-e2e run: | if [ -n "${{ runner.debug }}" ]; then export DEBUG="pw:browser*" fi - if [[ ${{ matrix.os }} == ubuntu-* ]]; then - xvfb-run --auto-servernum npm run test:browser-e2e - else - npm run test:browser-e2e + ARGS="" + if [[ ${{ steps.check-whether-to-update-snapshots.outputs.shouldUpdateSnapshots }} == 'true' ]]; then + ARGS="--update-snapshots" fi + npm run test:browser-e2e -- $ARGS - name: Run npm run test:electron-e2e run: | @@ -110,9 +128,44 @@ jobs: if: failure() uses: actions/upload-artifact@v3 with: - name: playwright-report + name: playwright-report-${{ matrix.os }} path: playwright-report + - name: Upload updated snapshots to artifact + if: steps.check-whether-to-update-snapshots.outputs.shouldUpdateSnapshots == 'true' + uses: actions/upload-artifact@v4 + with: + name: updated-snapshots-${{ matrix.os }} + path: tests/e2e/browser/*-snapshots/* + + commit-snapshots: + runs-on: ubuntu-latest + permissions: + contents: write + needs: + - e2e-test + steps: + - uses: actions/checkout@v3 + + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + pattern: updated-snapshots-* + path: tests/e2e/browser + merge-multiple: true + + - name: Commit updated snapshots + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + if [ -n "$(git status --porcelain)" ]; then + git add tests/e2e/browser + git commit -m "(スナップショットを更新)" + git push + else + echo "No changes to commit" + fi + lint: runs-on: ubuntu-latest steps: diff --git a/README.md b/README.md index 5e0839031e..43c20fa13d 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,21 @@ npx playwright codegen http://localhost:5173/#/home --viewport-size=800,600 詳細は [Playwright ドキュメントの Test generator](https://playwright.dev/docs/codegen-intro) を参照してください。 +#### スクリーンショットの更新 + +ブラウザ End to End テストでは Visual Regression Testing を行っています。 +以下の手順でスクリーンショットを更新できます: + +1. フォークしたリポジトリの設定で GitHub Actions を有効にします。 +2. リポジトリの設定の Actions > General > Workflow permissions で Read and write permissions を選択します。 +3. `[update snapshots]` という文字列をコミットメッセージに含めてコミットします。 + + ```bash + git commit -m "UIを変更 [update snapshots]" + ``` + +4. Github Workflow が完了すると、更新されたスクリーンショットがコミットされます。 + ### Electron End to End テスト Electron の機能が必要な、エンジン起動・終了などを含めた End to End テストを実行します。 diff --git a/package-lock.json b/package-lock.json index 43d22c7914..9e1aba68ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,7 +40,7 @@ }, "devDependencies": { "@openapitools/openapi-generator-cli": "2.7.0", - "@playwright/test": "1.39.0", + "@playwright/test": "1.40.1", "@quasar/vite-plugin": "1.3.0", "@types/async-lock": "1.4.0", "@types/clone-deep": "4.0.1", @@ -1510,12 +1510,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.39.0.tgz", - "integrity": "sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==", + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.1.tgz", + "integrity": "sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw==", "dev": true, "dependencies": { - "playwright": "1.39.0" + "playwright": "1.40.1" }, "bin": { "playwright": "cli.js" @@ -9821,12 +9821,12 @@ } }, "node_modules/playwright": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.39.0.tgz", - "integrity": "sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==", + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.1.tgz", + "integrity": "sha512-2eHI7IioIpQ0bS1Ovg/HszsN/XKNwEG1kbzSDDmADpclKc7CyqkHw7Mg2JCz/bbCxg25QUPcjksoMW7JcIFQmw==", "dev": true, "dependencies": { - "playwright-core": "1.39.0" + "playwright-core": "1.40.1" }, "bin": { "playwright": "cli.js" @@ -9839,9 +9839,9 @@ } }, "node_modules/playwright-core": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz", - "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==", + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.1.tgz", + "integrity": "sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==", "dev": true, "bin": { "playwright-core": "cli.js" @@ -13899,12 +13899,12 @@ "optional": true }, "@playwright/test": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.39.0.tgz", - "integrity": "sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==", + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.1.tgz", + "integrity": "sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw==", "dev": true, "requires": { - "playwright": "1.39.0" + "playwright": "1.40.1" } }, "@quasar/extras": { @@ -20519,19 +20519,19 @@ } }, "playwright": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.39.0.tgz", - "integrity": "sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==", + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.1.tgz", + "integrity": "sha512-2eHI7IioIpQ0bS1Ovg/HszsN/XKNwEG1kbzSDDmADpclKc7CyqkHw7Mg2JCz/bbCxg25QUPcjksoMW7JcIFQmw==", "dev": true, "requires": { "fsevents": "2.3.2", - "playwright-core": "1.39.0" + "playwright-core": "1.40.1" } }, "playwright-core": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz", - "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==", + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.1.tgz", + "integrity": "sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==", "dev": true }, "plist": { diff --git a/package.json b/package.json index 003459ec56..66bbff6279 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ }, "devDependencies": { "@openapitools/openapi-generator-cli": "2.7.0", - "@playwright/test": "1.39.0", + "@playwright/test": "1.40.1", "@quasar/vite-plugin": "1.3.0", "@types/async-lock": "1.4.0", "@types/clone-deep": "4.0.1", diff --git a/playwright.config.ts b/playwright.config.ts index 2557cfb96e..8b0e6e768c 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -60,6 +60,9 @@ const config: PlaywrightTestConfig = { * For example in `await expect(locator).toHaveText();` */ timeout: 5 * 1000, + toHaveScreenshot: { + maxDiffPixelRatio: 0.001, + }, }, // ファイルシステムが関連してくるので、Electronテストでは並列化しない fullyParallel: !isElectron, diff --git a/tests/e2e/browser/assets/icon_1.png b/tests/e2e/browser/assets/icon_1.png new file mode 100644 index 0000000000000000000000000000000000000000..ffd29cc5196663b95e74fda71fadd7c223bb6fbb GIT binary patch literal 6816 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 C&$Pe* literal 0 HcmV?d00001 diff --git a/tests/e2e/browser/assets/icon_4.png b/tests/e2e/browser/assets/icon_4.png new file mode 100644 index 0000000000000000000000000000000000000000..5616d057b7f0333858eec97bad1cc4cf9616af7e GIT binary patch literal 21178 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 literal 0 HcmV?d00001 diff --git a/tests/e2e/browser/assets/portrait_1.png b/tests/e2e/browser/assets/portrait_1.png new file mode 100644 index 0000000000000000000000000000000000000000..74af491ccb22a442aec8064cc9ffc06f9305b35a GIT binary patch literal 7316 zcmeHMS6CCuwnl6S(za5hOA~2QLkGcv0qGD*2t}k5P)I@%L_~@k=}oC3og@$-MS1`Q zrISE_L_jwphALh9%|7Sc@4NT?KHP`>FfVJZS+mxf|C?De|4U1AV|G>nRwgDUcHq6+ z)=W&N+Dv1eD7C7GV6$>?H@dmG2O3~V;ycQCNdwR(3LMX<%JY~a)FwL2lF>h z_`GJwgS+s3=hcH4il2vDx!Z3;(N2}|KL@x+x(0k7@FdAaYK-_nb)*Ig5oWk9k<24d z2)ejN{!1-(_kMQQLM9~3-|*WfGa<++GVVHtck;U1P#mw4eNN=lh7P1m)DHWb;&FEK z^D(T_B2K{v%>y*)9?ZtmvQag=X!*_iU}5`P8Xd6S?S956)y73-=&1W&T3Nm z9puhmW^KDDJ`dyi+4Y{&|8>5Wu+SKt^4<5_STW&!ceOW`+^Lm;Pv4rneiEqQdkZKy z+0nR?uKO_cycD`VdMyJ+DATDtY>D_lixcb7!vt48^z=aQ+X-c=Yl&eeJJhSID`N#s zBi@ziEi1c{0(q@t(~Op+ff^?JFZF73w`ilF(|Hvu8U_;z8i}wHvNv8cgl)Kr*&hr% zFr^NQMC*KCeGXk--?b@vVoOSugg{e~l1IUbo<^s+9k%-lc_U)%beo+3AIP1seZ<=` zZ(u}4Sz*FfNJ~2N=7pWG?5I^_kl>GybDSk`y;AQ^pwpQXrcNnvq% zW5#Matf_25O%0{11AcRZ0R4@Ek|PG?6<ijo;wY{rITQ>`L*gD6!IyW*B^^y=(e=I7KyMRmS5mh+R7c<)yM_TD zA&6gtx1(-k&!zQBb66v+t}%-j>UX49*P!%se30@vDml)!IU+QgeSXPyXOGwAVDip0 z$ieUS%Qd2e-nacJov(Aupe(2dnvxbGkIwHh=f_;#RSl`rs~wg=!CtIK=)9wrB@Ca8 zX*YBz&~-9aS4!pKR3j2uQrL};1wsGTVn4+_;fs(=)O6AU->MCg(WtX4>|0eBfCw+E zqe#`1>lsf2jbV1(vg$9^rqZWH5R#itz+5#M)I-mv4}ExK_O=;l=Zh${zzh}m)C;c8 zaTJOk|3R$r`15<<)79_8$hpO@@;WP~#;I#*TlPE#xzE?38F)fF8Ya-(now*Y+rI+S z$&NZt%c*hiNFN_ZgF-LTFH|X9`VIN^#pb=wvxU-yp6SYI7;LTR!r#X;t&6G@{* zQ{|;NZoJY|8$=3$zH;QL4qE+vYIg70F&4qx;heDJeC<=;z|sTi>o3(V>$cc@awl}T zZ-R%X+b>%KeBYSs22SF+gk0-M;8NpM=l+w`#docv)1F7cIu62LAns36 zu+35Q*XHc{AgWqri>!#1ncf|l+FMnM31Tp(@=7xOD*_=)sOlQd(|xBs?RAG(?3JoH zv*Vb}_J^-)+ijBY7(@cQll6$i&6DECYaYI(^J%Lu!*fZekVAhq@Ob4i$X}rhfhUTi zj+YIQnT6;S3L@-89J#r`*_c*U3yr~%jFyt646QY8h_x+{tvV|u3%z5n_d9!$+Y5;N zrPjNS!Yf{Ike3bez+$TXW-NBwrE8bQ)1m=d-g5C(Rg_y&gS&HB&O` z^}cWoCA3*%@V=npdI`tDUvhwSVRyR@IaFYW^~RuTfM}ub!9@upw`B9hkt3r7n~Vf> z<8Q8f3`gwa)baHT!WCs&LpxUr*Z(%@=>h0;eUmacGS4snx}_{!GNZPHs^!Eq?+OJT ztSUq6Fjiui_@GNV>{?aduSL%)GDk)`%awH2j2#&wwh6h_kt64s{oh-kKFKq)TxcRb zy;}1H^t|&uZO3p*Y%VIm^$(|8x82x>g<8~HctN7|)yY@T`VpMesCd!q*FWn6Dh~_r zwtZFH4&CWL98TV$_ZGsF$}Z9C{IcM9iy3R{sw3!Z?QGZn6>Vn!U((Bf)M%MKpf9~< zH>LkvYRN9uY^(?ybdtQD6x)kRiVdwv&f00UCS7|TH0Om_#73tUM^_G-j6M{_I=h~*&U_C2O0aEp}4gXE7rGL-7j`6J`%KSvJ;)<_AJ`AaB!SmXgx-NN9Mj<%|)tf zN(mL5MacdPCzNrXnQ31^ul@K-zgspBcJl3sNngRvUZ#_+=&d_$>^)vP{kpWh9SfT4 zT%z^D#^;Q{j4g|pT`PkbP%vx$&fXo$IZIV$XMD9yP#-#$B3)+FTufSUJC=cnmiyt$d z1`zVIlKXmHnTVDLZ>iSUJxG=@bla9r-q#=4`wo^u2U&Z`~r(e2x*>OA*8S}t^;3fSL8L9V!0aZFR0}L zejZC)9IDOWfa{NbzjdgsJu^YguIn|`M#dOtnEk*d-$I*}h%+N~u4%^lk)5sVHQs+Y z5^~pjkU6S1b1tkFRqqUajKihByWnqA#Q;^H4fu*wFF5P%^`~Rqf1&7!ivhqEp!QwT z4T1J#vjtpwX<=vYCoxRTlM+>hU%}wfrq9Q21XjCs!Gn1d)ui?;^KGj0k1zJxmE9_j zUOy++3<-`Oe-qnm3|;JPrZD?GS-N;UpVl|dyM#i24}-FuJp)o_EBL%8lD5?(&8s<= zHxSa%kg}zgyrz#}oh&n~oBI*zuX^W6gPMan!*u5A!NO_w0%yOITI|bx(#9gq$#|dB z*cp!xBU10DOfZ@Y7Sxc}+x%`3}riCBx;qmzRKhA)>hv74iT{2l;S`NWkmvHL*%*e)D1)+!D ze~a3`U#9r`E1dx~L7;zrdsjT=cKOC$T#6Y_ zC%pbC#V#xHV1vrVHkl)a>02odhno?Ru|7rRLjv(lfb+N9_N=430B`I&W{GiD+{4{N z)8}cEQ3Z@jN6wUlXI7>g%G+y|&&%{1y6x@2UDgGzj40G8ghT7LS>$I$hUB(fOB7we z)IxzF2~1=YaX(IBk!j35TUA+{4Xw?BYE_Vg7KP9ENMGjgkea9f5CDnH%yPpAY`wC| zRrwPYntgp@m<>co(ESl8qf@Ua8kX!=d#!78Jd;vqbej5@pLKhD{)D^U&k&|F+?Itl8D|b|APtcl-@2 zBOT}2PH8h4{vPXE&ZJ}H#lJIF^y-(se9`0j1fG94ml67OdU#IDg0f5#KHEPQZOjm4 zh9f-AZMZW5VCEM>pqOnIlx`63C#W$*>6LJunQ{yCTugSad>TKm7h#1tCOb5Lvy~vA zHX>JNHqYw-)nk?mYTDF|iE9prE=v1FXfrknEEhOz#dp!8*WU&e^Mng(QIRdP&a+6|)g#7=7-Rw<{K~8^@Q{0qq=A9jbird@{^NOJ^N5+dBQTLz= zmJ4&Pr4OPGN!g|$e@v%^8w==cj!E-eF3}yo5)D7S>6R^l(LTu7=pYSo0&g@Kn^2sdmN7vv(!R z@eM8&ZuVfoTj$S!;Ki_+@Yb_@mR?c*a#}7lqf;?<({UM3GsAZgQua~LfW}&dD;@Rt zI0i5lMKWL&o2!{pW@_~#J})l#OK5pJ(To_!S?Ew27O?qp+B6Wy#DN1?XHiMLU{@W8 z7^XRsm!Lbm_u=~3H5Kc8&GG8jA%(cpU%&F9o~&hK6*sHfq}txn<9bKR0qY+II3eE9 z3xChrwE^;J7Do`urpy#|gb;{ry_&wz)s*#jt`(L zki0OT&nr#G{EDNYc(i)6)|=3x3F0vdtF$1^F4K!C)Bm_+{Cjg4-m`$t!6re7hE2Xx zAFjKvS;rnc+oX?Ka;9Lo`4eMJ&s~ashFKJj887BEX&&5*x6m|p7b z7>*>rfG8ITJ}w=}hT*nUht48r!T)MEA`Xp6`Gr9ybC2|ep|K;`m#t3}ef<}11>phD z^%;%Lo;`5(QM&>rDS3>qG%V+f%f$fF^y|rfZ0w;bq zlqE|9i5>e~qUD0M#~=T@M-zB;DL}8gU@xPcsQaq~9Yhuw4h1}TgF7|$iYV|(ZGI(0 za(W^1KwNN@sF%NfpTo}ad)A>oZTJ2YU8Vtwk|tecCqxSJO@0MdR+gRNqE%)-rNQol zkFOB;a1?O~V78M!wB!#hR(;`q^EKN2S4zFIb`flL3i&=`EG*OA4#2dqLQZ03NbcgM$)>z>DWX;*_Q5bltT?QPq zAx^7o+B;HaVtChkVE?)=1-p|DOD=2csb_vZ*}v1NFZI#v;8*yI@kfmIpFla5Ewpkh+yk2l`6j{+&qiR1}X8kI0)y$iwyh@7A1QIk~w13BN&h3 z`=rxSQsEg2j@zs~{^NdKi(3&$kKxn!RCCeP!IUJTUwpshiX5GiK??AR4xAiK7*Q`NQ_QdbLBKqOaO9|ibJz+Hl)Bl`ELS_D5W;Om}E+7;c3r3l1LmbnqKaBxGkn06#1uy9?Qk!ZTdt=-}?_vimpPlHXj&zjX-FyC{8x zV~=mWO`4EI2ZoK89UK(7BYi~0eH#@Gq8kg3qhgTJj30RqSq16f7iEN<$*H3K;A1e! zE~7RFhAvZ=sI8d-q#+D|=kIBMDq5lhvu$DJCq(bI0Dt}wqyEEd@4L|Ehbc9u&4`vtLmDm+aC~3$}Z3-baxcd53orJ1~rVOr57m9XXQ< zGgfn8xeArr&*B_e>)rsUG1(l!&NkUz1_CT~PC2yr1KY1<( zg{cu@bWYHe#{sv81ixB|#itVentFjw0jYttihxl#t9I^L^tI^#v{(~`qh`~;c?LmlRleMHX;q{ zkj&KcE3z|s2+wtqCrm}YQ))>yMx4^!@ozMhZ3ie!|Ii)(&_4g-#{S>YUyTLNYd!dw meFRee!`}UOgy(=K=Y`9?yaz7U4l>wACg2_O+qFh+&;JYTixW`* literal 0 HcmV?d00001 diff --git a/tests/e2e/browser/assets/portrait_2.png b/tests/e2e/browser/assets/portrait_2.png new file mode 100644 index 0000000000000000000000000000000000000000..23970a6c67453ea010e837b97319d8497bdf0eff GIT binary patch literal 8555 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 Z@~~2u`0A6uvrrirJYD@<);T3K0RU>pS%Uxo literal 0 HcmV?d00001 diff --git a/tests/e2e/browser/assets/portrait_4.png b/tests/e2e/browser/assets/portrait_4.png new file mode 100644 index 0000000000000000000000000000000000000000..0d529a93d905b670170ba2f442f7e13388137b80 GIT binary patch literal 19786 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 { + if (!speakerImages) { + const assetsPath = path.resolve(__dirname, "assets"); + const images = await fs.readdir(assetsPath); + const icons = images.filter((image) => image.startsWith("icon")); + icons.sort( + (a, b) => + parseInt(a.split(".")[0].split("_")[1]) - + parseInt(b.split(".")[0].split("_")[1]) + ); + speakerImages = await Promise.all( + icons.map(async (iconPath) => { + const portraitPath = iconPath.replace("icon_", "portrait_"); + const portrait = await fs.readFile( + path.join(assetsPath, portraitPath), + "base64" + ); + const icon = await fs.readFile( + path.join(assetsPath, iconPath), + "base64" + ); + + return { portrait, icon }; + }) + ); + } + return speakerImages; +} + +test.beforeEach(async ({ page }) => { + let speakers: Speaker[]; + const speakerImages = await getSpeakerImages(); + // Voicevox Nemo EngineでもVoicevox Engineでも同じ結果が選られるように、 + // GET /speakers、GET /speaker_infoの話者名、スタイル名、画像を差し替える。 + await page.route(/\/speakers$/, async (route) => { + const response = await route.fetch(); + const json: Speaker[] = await response + .json() + .then((json) => json.map(SpeakerFromJSON)); + let i = 0; + for (const speaker of json) { + i++; + speaker.name = `Speaker ${i}`; + let j = 0; + for (const style of speaker.styles) { + j++; + style.name = `Style ${i}-${j}`; + } + } + speakers = json; + await route.fulfill({ + status: 200, + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + body: JSON.stringify(json.map(SpeakerToJSON)), + }); + }); + await page.route(/\/speaker_info\?/, async (route) => { + if (!speakers) { + // Unreachableのはず + throw new Error("speakers is not initialized"); + } + const url = new URL(route.request().url()); + const speakerUuid = url.searchParams.get("speaker_uuid"); + if (!speakerUuid) { + throw new Error("speaker_uuid is not set"); + } + const response = await route.fetch(); + const json: SpeakerInfo = await response.json().then(SpeakerInfoFromJSON); + const speakerIndex = speakers.findIndex( + (speaker) => speaker.speakerUuid === speakerUuid + ); + if (speakerIndex === -1) { + throw new Error(`speaker_uuid=${speakerUuid} is not found`); + } + const image = speakerImages[speakerIndex % speakerImages.length]; + json.portrait = image.portrait; + for (const style of json.styleInfos) { + style.icon = image.icon; + if ("portrait" in style) { + delete style.portrait; + } + } + await route.fulfill({ + status: 200, + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + body: JSON.stringify(SpeakerInfoToJSON(json)), + }); + }); +}); +test.beforeEach(gotoHome); + +test("メイン画面の表示", async ({ page }) => { + test.skip(process.platform !== "win32", "Windows以外のためスキップします"); + await navigateToMain(page); + + // eslint-disable-next-line no-constant-condition + while (true) { + await page.locator(".audio-cell:nth-child(1) .q-field").click(); + await page.waitForTimeout(100); + if ( + (await page + .locator(".character-portrait-wrapper .character-name") + .innerText()) !== "(表示エラー)" && + (await page.locator(".character-portrait-wrapper .loading").count()) === 0 + ) { + break; + } + } + await expect(page).toHaveScreenshot("メイン画面.png"); +}); diff --git "a/tests/e2e/browser/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.ts-snapshots/\343\203\241\343\202\244\343\203\263\347\224\273\351\235\242-browser-win32.png" "b/tests/e2e/browser/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.ts-snapshots/\343\203\241\343\202\244\343\203\263\347\224\273\351\235\242-browser-win32.png" new file mode 100644 index 0000000000000000000000000000000000000000..96794db997650f97453f710e6bf0b2da87171231 GIT binary patch literal 47801 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

)CD2h%UCGZ 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 literal 0 HcmV?d00001 From f17eb8ec6338abf81a6620f709ddc3e50ebc008d Mon Sep 17 00:00:00 2001 From: sabonerune <102559104+sabonerune@users.noreply.github.com> Date: Thu, 18 Jan 2024 01:48:36 +0900 Subject: [PATCH 17/19] =?UTF-8?q?fix:=20=E8=BF=BD=E5=8A=A0=E3=82=A8?= =?UTF-8?q?=E3=83=B3=E3=82=B8=E3=83=B3=E3=81=A7=E3=81=AEUpdateInfo?= =?UTF-8?q?=E3=81=AE=E5=9E=8B=E3=82=A8=E3=83=A9=E3=83=BC=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=20(#1718)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/help/UpdateInfo.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/help/UpdateInfo.vue b/src/components/help/UpdateInfo.vue index 9694dbbd72..b33bc2e2df 100644 --- a/src/components/help/UpdateInfo.vue +++ b/src/components/help/UpdateInfo.vue @@ -42,8 +42,8 @@ import { UpdateInfo } from "@/type/preload"; const props = defineProps<{ - latestVersion: string | undefined; - downloadLink: string; + latestVersion?: string; + downloadLink?: string; updateInfos: UpdateInfo[]; isUpdateAvailable: boolean; }>(); From 8f1b395c339af817edce3fe6dc26c88cd556c354 Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Thu, 18 Jan 2024 03:41:46 +0900 Subject: [PATCH 18/19] =?UTF-8?q?=E3=82=A2=E3=83=83=E3=83=97=E3=83=87?= =?UTF-8?q?=E3=83=BC=E3=83=88=E9=80=9A=E7=9F=A5=E3=81=AEe2e=E3=83=86?= =?UTF-8?q?=E3=82=B9=E3=83=88=20(#1716)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * e2eテスト追加 * バグフィックス --- README.md | 2 +- src/type/utility.ts | 9 +++ src/views/EditorHome.vue | 7 +- ...3\202\242\343\203\255\343\202\260.spec.ts" | 73 +++++++++++++++++++ 4 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 "tests/e2e/browser/\343\202\242\343\203\203\343\203\227\343\203\207\343\203\274\343\203\210\351\200\232\347\237\245\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts" diff --git a/README.md b/README.md index 43c20fa13d..40b7c8b5bb 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ npm run test-watch:browser-e2e -- --headed # テスト中の UI を表示 ``` Playwright を使用しているためテストパターンを生成することもできます。 -ブラウザ版を起動している状態で以下のコマンドを実行してください。 +**ブラウザ版を起動している状態で**以下のコマンドを実行してください。 ```bash npx playwright codegen http://localhost:5173/#/home --viewport-size=800,600 diff --git a/src/type/utility.ts b/src/type/utility.ts index 365c30db99..b01f91120a 100644 --- a/src/type/utility.ts +++ b/src/type/utility.ts @@ -4,3 +4,12 @@ export type IsEqual = (() => T extends X ? 1 : 2) extends < >() => T extends Y ? 1 : 2 ? true : false; + +// undefinedかnullでないことを保証する +export function assertNonNullable( + value: T +): asserts value is NonNullable { + if (value == undefined) { + throw new Error("Value is null or undefined"); + } +} diff --git a/src/views/EditorHome.vue b/src/views/EditorHome.vue index 1395dfae6c..bf5873808f 100644 --- a/src/views/EditorHome.vue +++ b/src/views/EditorHome.vue @@ -856,7 +856,12 @@ const isAcceptRetrieveTelemetryDialogOpenComputed = computed({ // アップデート通知 const isUpdateNotificationDialogOpenComputed = computed({ - get: () => store.state.isUpdateNotificationDialogOpen, + get: () => + !store.state.isAcceptTermsDialogOpen && + !store.state.isCharacterOrderDialogOpen && + !store.state.isDefaultStyleSelectDialogOpen && + !store.state.isAcceptRetrieveTelemetryDialogOpen && + store.state.isUpdateNotificationDialogOpen, set: (val) => store.dispatch("SET_DIALOG_OPEN", { isUpdateNotificationDialogOpen: val, diff --git "a/tests/e2e/browser/\343\202\242\343\203\203\343\203\227\343\203\207\343\203\274\343\203\210\351\200\232\347\237\245\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts" "b/tests/e2e/browser/\343\202\242\343\203\203\343\203\227\343\203\207\343\203\274\343\203\210\351\200\232\347\237\245\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts" new file mode 100644 index 0000000000..30f16bc503 --- /dev/null +++ "b/tests/e2e/browser/\343\202\242\343\203\203\343\203\227\343\203\207\343\203\274\343\203\210\351\200\232\347\237\245\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts" @@ -0,0 +1,73 @@ +import { test, expect } from "@playwright/test"; +import dotenv from "dotenv"; +import semver from "semver"; +import { navigateToMain, gotoHome } from "../navigators"; +import { getNewestQuasarDialog } from "../locators"; +import { UpdateInfo } from "@/type/preload"; +import { assertNonNullable } from "@/type/utility"; + +// アップデート通知が出る環境にする +test.beforeEach(async ({ page }) => { + dotenv.config(); + + // 動作環境より新しいバージョン + const latestVersion = semver.inc( + process.env.VITE_APP_VERSION ?? process.env.npm_package_version ?? "0.0.0", + "major" + ); + assertNonNullable(latestVersion); + + // アップデート情報を返すAPIのモック + if (process.env.VITE_LATEST_UPDATE_INFOS_URL == undefined) { + throw new Error("VITE_LATEST_UPDATE_INFOS_URL is not defined"); + } + page.route(process.env.VITE_LATEST_UPDATE_INFOS_URL, (route) => { + const updateInfos: UpdateInfo[] = [ + { + version: latestVersion, + descriptions: [], + contributors: [], + }, + ]; + route.fulfill({ + status: 200, + body: JSON.stringify(updateInfos), + }); + }); +}); + +test.beforeEach(async ({ page }) => { + await gotoHome({ page }); + + await navigateToMain(page); + await page.waitForTimeout(100); +}); + +test("アップデートが通知されたりスキップしたりできる", async ({ page }) => { + await page.waitForTimeout(500); + + // 通知されている + const dialog = getNewestQuasarDialog(page); + await expect(dialog.getByText("アップデートのお知らせ")).toBeVisible(); + + // 普通に閉じると消える + await dialog.getByRole("button", { name: "閉じる" }).click(); + await page.waitForTimeout(500); + await expect(dialog).not.toBeVisible(); + + // 再度開くとまた表示される + await page.reload(); + await expect(dialog.getByText("アップデートのお知らせ")).toBeVisible(); + + // スキップすると消える + await dialog + .getByRole("button", { name: "このバージョンをスキップ" }) + .click(); + await page.waitForTimeout(500); + await expect(dialog).not.toBeVisible(); + + // 再度開いても表示されない(スキップされた) + await page.reload(); + await page.waitForTimeout(5000); // エンジン読み込み待機 + await expect(dialog).not.toBeVisible(); +}); From ac5956c8536f6cdb68aaee7678788ac7135810de Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Thu, 18 Jan 2024 08:18:54 +0900 Subject: [PATCH 19/19] =?UTF-8?q?openapi=E3=81=AE=E6=9B=B4=E6=96=B0=20(#17?= =?UTF-8?q?20)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 + openapi.json | 2 +- openapitools.json | 2 +- src/openapi/.openapi-generator/FILES | 7 +- src/openapi/.openapi-generator/VERSION | 2 +- src/openapi/apis/DefaultApi.ts | 512 ++++++++++++------ src/openapi/models/BaseLibraryInfo.ts | 118 ++++ src/openapi/models/CorsPolicyMode.ts | 38 ++ src/openapi/models/DownloadableLibraryInfo.ts | 118 ++++ src/openapi/models/InstalledLibraryInfo.ts | 127 +++++ src/openapi/models/ParseKanaBadRequest.ts | 2 +- src/openapi/models/Speaker.ts | 2 +- src/openapi/models/SpeakerStyle.ts | 2 +- src/openapi/models/ValidationError.ts | 15 +- src/openapi/models/ValidationErrorLocInner.ts | 44 ++ src/openapi/models/WordTypes.ts | 4 +- src/openapi/models/index.ts | 7 +- src/openapi/runtime.ts | 2 +- 18 files changed, 820 insertions(+), 192 deletions(-) create mode 100644 src/openapi/models/BaseLibraryInfo.ts create mode 100644 src/openapi/models/CorsPolicyMode.ts create mode 100644 src/openapi/models/DownloadableLibraryInfo.ts create mode 100644 src/openapi/models/InstalledLibraryInfo.ts create mode 100644 src/openapi/models/ValidationErrorLocInner.ts diff --git a/README.md b/README.md index 40b7c8b5bb..118044e815 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,14 @@ npx openapi-generator-cli generate \ npm run fmt ``` +### OpanAPI generator のバージョンアップ + +新しいバージョンの確認・インストールは次のコマンドで行えます。 + +```bash +npx openapi-generator-cli version-manager list +``` + ## VS Code でのデバッグ実行 npm scripts の `serve` や `electron:serve` などの開発ビルド下では、ビルドに使用している vite で sourcemap を出力するため、ソースコードと出力されたコードの対応付けが行われます。 diff --git a/openapi.json b/openapi.json index 2686664d2e..8a072f829f 100644 --- a/openapi.json +++ b/openapi.json @@ -1 +1 @@ -{"openapi":"3.0.2","info":{"title":"VOICEVOX Engine","description":"VOICEVOXの音声合成エンジンです。","version":"latest"},"paths":{"/audio_query":{"post":{"tags":["クエリ作成"],"summary":"音声合成用のクエリを作成する","description":"クエリの初期値を得ます。ここで得られたクエリはそのまま音声合成に利用できます。各値の意味は`Schemas`を参照してください。","operationId":"audio_query_audio_query_post","parameters":[{"required":true,"schema":{"title":"Text","type":"string"},"name":"text","in":"query"},{"required":true,"schema":{"title":"Speaker","type":"integer"},"name":"speaker","in":"query"},{"required":false,"schema":{"title":"Core Version","type":"string"},"name":"core_version","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudioQuery"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/audio_query_from_preset":{"post":{"tags":["クエリ作成"],"summary":"音声合成用のクエリをプリセットを用いて作成する","description":"クエリの初期値を得ます。ここで得られたクエリはそのまま音声合成に利用できます。各値の意味は`Schemas`を参照してください。","operationId":"audio_query_from_preset_audio_query_from_preset_post","parameters":[{"required":true,"schema":{"title":"Text","type":"string"},"name":"text","in":"query"},{"required":true,"schema":{"title":"Preset Id","type":"integer"},"name":"preset_id","in":"query"},{"required":false,"schema":{"title":"Core Version","type":"string"},"name":"core_version","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudioQuery"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/accent_phrases":{"post":{"tags":["クエリ編集"],"summary":"テキストからアクセント句を得る","description":"テキストからアクセント句を得ます。\nis_kanaが`true`のとき、テキストは次のようなAquesTalkライクな記法に従う読み仮名として処理されます。デフォルトは`false`です。\n* 全てのカナはカタカナで記述される\n* アクセント句は`/`または`、`で区切る。`、`で区切った場合に限り無音区間が挿入される。\n* カナの手前に`_`を入れるとそのカナは無声化される\n* アクセント位置を`'`で指定する。全てのアクセント句にはアクセント位置を1つ指定する必要がある。\n* アクセント句末に`?`(全角)を入れることにより疑問文の発音ができる。","operationId":"accent_phrases_accent_phrases_post","parameters":[{"required":true,"schema":{"title":"Text","type":"string"},"name":"text","in":"query"},{"required":true,"schema":{"title":"Speaker","type":"integer"},"name":"speaker","in":"query"},{"required":false,"schema":{"title":"Is Kana","type":"boolean","default":false},"name":"is_kana","in":"query"},{"required":false,"schema":{"title":"Core Version","type":"string"},"name":"core_version","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Accent Phrases Accent Phrases Post","type":"array","items":{"$ref":"#/components/schemas/AccentPhrase"}}}}},"400":{"description":"読み仮名のパースに失敗","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParseKanaBadRequest"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/mora_data":{"post":{"tags":["クエリ編集"],"summary":"アクセント句から音高・音素長を得る","operationId":"mora_data_mora_data_post","parameters":[{"required":true,"schema":{"title":"Speaker","type":"integer"},"name":"speaker","in":"query"},{"required":false,"schema":{"title":"Core Version","type":"string"},"name":"core_version","in":"query"}],"requestBody":{"content":{"application/json":{"schema":{"title":"Accent Phrases","type":"array","items":{"$ref":"#/components/schemas/AccentPhrase"}}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Mora Data Mora Data Post","type":"array","items":{"$ref":"#/components/schemas/AccentPhrase"}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/mora_length":{"post":{"tags":["クエリ編集"],"summary":"アクセント句から音素長を得る","operationId":"mora_length_mora_length_post","parameters":[{"required":true,"schema":{"title":"Speaker","type":"integer"},"name":"speaker","in":"query"},{"required":false,"schema":{"title":"Core Version","type":"string"},"name":"core_version","in":"query"}],"requestBody":{"content":{"application/json":{"schema":{"title":"Accent Phrases","type":"array","items":{"$ref":"#/components/schemas/AccentPhrase"}}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Mora Length Mora Length Post","type":"array","items":{"$ref":"#/components/schemas/AccentPhrase"}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/mora_pitch":{"post":{"tags":["クエリ編集"],"summary":"アクセント句から音高を得る","operationId":"mora_pitch_mora_pitch_post","parameters":[{"required":true,"schema":{"title":"Speaker","type":"integer"},"name":"speaker","in":"query"},{"required":false,"schema":{"title":"Core Version","type":"string"},"name":"core_version","in":"query"}],"requestBody":{"content":{"application/json":{"schema":{"title":"Accent Phrases","type":"array","items":{"$ref":"#/components/schemas/AccentPhrase"}}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Mora Pitch Mora Pitch Post","type":"array","items":{"$ref":"#/components/schemas/AccentPhrase"}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/synthesis":{"post":{"tags":["音声合成"],"summary":"音声合成する","operationId":"synthesis_synthesis_post","parameters":[{"required":true,"schema":{"title":"Speaker","type":"integer"},"name":"speaker","in":"query"},{"description":"疑問系のテキストが与えられたら語尾を自動調整する","required":false,"schema":{"title":"Enable Interrogative Upspeak","type":"boolean","description":"疑問系のテキストが与えられたら語尾を自動調整する","default":true},"name":"enable_interrogative_upspeak","in":"query"},{"required":false,"schema":{"title":"Core Version","type":"string"},"name":"core_version","in":"query"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudioQuery"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"audio/wav":{"schema":{"type":"string","format":"binary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cancellable_synthesis":{"post":{"tags":["音声合成"],"summary":"音声合成する(キャンセル可能)","operationId":"cancellable_synthesis_cancellable_synthesis_post","parameters":[{"required":true,"schema":{"title":"Speaker","type":"integer"},"name":"speaker","in":"query"},{"required":false,"schema":{"title":"Core Version","type":"string"},"name":"core_version","in":"query"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudioQuery"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"audio/wav":{"schema":{"type":"string","format":"binary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/multi_synthesis":{"post":{"tags":["音声合成"],"summary":"複数まとめて音声合成する","operationId":"multi_synthesis_multi_synthesis_post","parameters":[{"required":true,"schema":{"title":"Speaker","type":"integer"},"name":"speaker","in":"query"},{"required":false,"schema":{"title":"Core Version","type":"string"},"name":"core_version","in":"query"}],"requestBody":{"content":{"application/json":{"schema":{"title":"Queries","type":"array","items":{"$ref":"#/components/schemas/AudioQuery"}}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/zip":{"schema":{"type":"string","format":"binary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/morphable_targets":{"post":{"tags":["音声合成"],"summary":"指定した話者に対してエンジン内の話者がモーフィングが可能か判定する","description":"指定されたベース話者に対してエンジン内の各話者がモーフィング機能を利用可能か返します。\nモーフィングの許可/禁止は`/speakers`の`speaker.supported_features.synthesis_morphing`に記載されています。\nプロパティが存在しない場合は、モーフィングが許可されているとみなします。\n返り値の話者はstring型なので注意。","operationId":"morphable_targets_morphable_targets_post","parameters":[{"required":false,"schema":{"title":"Core Version","type":"string"},"name":"core_version","in":"query"}],"requestBody":{"content":{"application/json":{"schema":{"title":"Base Speakers","type":"array","items":{"type":"integer"}}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Morphable Targets Morphable Targets Post","type":"array","items":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/MorphableTargetInfo"}}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/synthesis_morphing":{"post":{"tags":["音声合成"],"summary":"2人の話者でモーフィングした音声を合成する","description":"指定された2人の話者で音声を合成、指定した割合でモーフィングした音声を得ます。\nモーフィングの割合は`morph_rate`で指定でき、0.0でベースの話者、1.0でターゲットの話者に近づきます。","operationId":"_synthesis_morphing_synthesis_morphing_post","parameters":[{"required":true,"schema":{"title":"Base Speaker","type":"integer"},"name":"base_speaker","in":"query"},{"required":true,"schema":{"title":"Target Speaker","type":"integer"},"name":"target_speaker","in":"query"},{"required":true,"schema":{"title":"Morph Rate","maximum":1.0,"minimum":0.0,"type":"number"},"name":"morph_rate","in":"query"},{"required":false,"schema":{"title":"Core Version","type":"string"},"name":"core_version","in":"query"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudioQuery"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"audio/wav":{"schema":{"type":"string","format":"binary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/connect_waves":{"post":{"tags":["その他"],"summary":"base64エンコードされた複数のwavデータを一つに結合する","description":"base64エンコードされたwavデータを一纏めにし、wavファイルで返します。","operationId":"connect_waves_connect_waves_post","requestBody":{"content":{"application/json":{"schema":{"title":"Waves","type":"array","items":{"type":"string"}}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"audio/wav":{"schema":{"type":"string","format":"binary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/presets":{"get":{"tags":["その他"],"summary":"Get Presets","description":"エンジンが保持しているプリセットの設定を返します\n\nReturns\n-------\npresets: List[Preset]\n プリセットのリスト","operationId":"get_presets_presets_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Get Presets Presets Get","type":"array","items":{"$ref":"#/components/schemas/Preset"}}}}}}}},"/add_preset":{"post":{"tags":["その他"],"summary":"Add Preset","description":"新しいプリセットを追加します\n\nParameters\n-------\npreset: Preset\n 新しいプリセット。\n プリセットIDが既存のものと重複している場合は、新規のプリセットIDが採番されます。\n\nReturns\n-------\nid: int\n 追加したプリセットのプリセットID","operationId":"add_preset_add_preset_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Preset"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Add Preset Add Preset Post","type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/update_preset":{"post":{"tags":["その他"],"summary":"Update Preset","description":"既存のプリセットを更新します\n\nParameters\n-------\npreset: Preset\n 更新するプリセット。\n プリセットIDが更新対象と一致している必要があります。\n\nReturns\n-------\nid: int\n 更新したプリセットのプリセットID","operationId":"update_preset_update_preset_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Preset"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Update Preset Update Preset Post","type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/delete_preset":{"post":{"tags":["その他"],"summary":"Delete Preset","description":"既存のプリセットを削除します\n\nParameters\n-------\nid: int\n 削除するプリセットのプリセットID","operationId":"delete_preset_delete_preset_post","parameters":[{"required":true,"schema":{"title":"Id","type":"integer"},"name":"id","in":"query"}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/version":{"get":{"tags":["その他"],"summary":"Version","operationId":"version_version_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/core_versions":{"get":{"tags":["その他"],"summary":"Core Versions","operationId":"core_versions_core_versions_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Core Versions Core Versions Get","type":"array","items":{"type":"string"}}}}}}}},"/speakers":{"get":{"tags":["その他"],"summary":"Speakers","operationId":"speakers_speakers_get","parameters":[{"required":false,"schema":{"title":"Core Version","type":"string"},"name":"core_version","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Speakers Speakers Get","type":"array","items":{"$ref":"#/components/schemas/Speaker"}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/speaker_info":{"get":{"tags":["その他"],"summary":"Speaker Info","description":"指定されたspeaker_uuidに関する情報をjson形式で返します。\n画像や音声はbase64エンコードされたものが返されます。\n\nReturns\n-------\nret_data: SpeakerInfo","operationId":"speaker_info_speaker_info_get","parameters":[{"required":true,"schema":{"title":"Speaker Uuid","type":"string"},"name":"speaker_uuid","in":"query"},{"required":false,"schema":{"title":"Core Version","type":"string"},"name":"core_version","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeakerInfo"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/downloadable_libraries":{"get":{"tags":["音声ライブラリ管理"],"summary":"Downloadable Libraries","description":"ダウンロード可能な音声ライブラリの情報を返します。\n\nReturns\n-------\nret_data: List[DownloadableLibrary]","operationId":"downloadable_libraries_downloadable_libraries_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Downloadable Libraries Downloadable Libraries Get","type":"array","items":{"$ref":"#/components/schemas/DownloadableLibrary"}}}}}}}},"/installed_libraries":{"get":{"tags":["音声ライブラリ管理"],"summary":"Installed Libraries","description":"インストールした音声ライブラリの情報を返します。\n\nReturns\n-------\nret_data: List[DownloadableLibrary]","operationId":"installed_libraries_installed_libraries_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Installed Libraries Installed Libraries Get","type":"object","additionalProperties":{"$ref":"#/components/schemas/InstalledLibrary"}}}}}}}},"/install_library/{library_uuid}":{"post":{"tags":["音声ライブラリ管理"],"summary":"Install Library","description":"音声ライブラリをインストールします。\n音声ライブラリのZIPファイルをリクエストボディとして送信してください。\n\nParameters\n----------\nlibrary_uuid: str\n 音声ライブラリのID","operationId":"install_library_install_library__library_uuid__post","parameters":[{"required":true,"schema":{"title":"Library Uuid","type":"string"},"name":"library_uuid","in":"path"}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/uninstall_library/{library_uuid}":{"post":{"tags":["音声ライブラリ管理"],"summary":"Uninstall Library","description":"音声ライブラリをアンインストールします。\n\nParameters\n----------\nlibrary_uuid: str\n 音声ライブラリのID","operationId":"uninstall_library_uninstall_library__library_uuid__post","parameters":[{"required":true,"schema":{"title":"Library Uuid","type":"string"},"name":"library_uuid","in":"path"}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/initialize_speaker":{"post":{"tags":["その他"],"summary":"Initialize Speaker","description":"指定されたspeaker_idの話者を初期化します。\n実行しなくても他のAPIは使用できますが、初回実行時に時間がかかることがあります。","operationId":"initialize_speaker_initialize_speaker_post","parameters":[{"required":true,"schema":{"title":"Speaker","type":"integer"},"name":"speaker","in":"query"},{"description":"既に初期化済みの話者の再初期化をスキップするかどうか","required":false,"schema":{"title":"Skip Reinit","type":"boolean","description":"既に初期化済みの話者の再初期化をスキップするかどうか","default":false},"name":"skip_reinit","in":"query"},{"required":false,"schema":{"title":"Core Version","type":"string"},"name":"core_version","in":"query"}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/is_initialized_speaker":{"get":{"tags":["その他"],"summary":"Is Initialized Speaker","description":"指定されたspeaker_idの話者が初期化されているかどうかを返します。","operationId":"is_initialized_speaker_is_initialized_speaker_get","parameters":[{"required":true,"schema":{"title":"Speaker","type":"integer"},"name":"speaker","in":"query"},{"required":false,"schema":{"title":"Core Version","type":"string"},"name":"core_version","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Is Initialized Speaker Is Initialized Speaker Get","type":"boolean"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/user_dict":{"get":{"tags":["ユーザー辞書"],"summary":"Get User Dict Words","description":"ユーザー辞書に登録されている単語の一覧を返します。\n単語の表層形(surface)は正規化済みの物を返します。\n\nReturns\n-------\nDict[str, UserDictWord]\n 単語のUUIDとその詳細","operationId":"get_user_dict_words_user_dict_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Get User Dict Words User Dict Get","type":"object","additionalProperties":{"$ref":"#/components/schemas/UserDictWord"}}}}}}}},"/user_dict_word":{"post":{"tags":["ユーザー辞書"],"summary":"Add User Dict Word","description":"ユーザー辞書に言葉を追加します。\n\nParameters\n----------\nsurface : str\n 言葉の表層形\npronunciation: str\n 言葉の発音(カタカナ)\naccent_type: int\n アクセント型(音が下がる場所を指す)\nword_type: WordTypes, optional\n PROPER_NOUN(固有名詞)、COMMON_NOUN(普通名詞)、VERB(動詞)、ADJECTIVE(形容詞)、SUFFIX(語尾)のいずれか\npriority: int, optional\n 単語の優先度(0から10までの整数)\n 数字が大きいほど優先度が高くなる\n 1から9までの値を指定することを推奨","operationId":"add_user_dict_word_user_dict_word_post","parameters":[{"required":true,"schema":{"title":"Surface","type":"string"},"name":"surface","in":"query"},{"required":true,"schema":{"title":"Pronunciation","type":"string"},"name":"pronunciation","in":"query"},{"required":true,"schema":{"title":"Accent Type","type":"integer"},"name":"accent_type","in":"query"},{"required":false,"schema":{"$ref":"#/components/schemas/WordTypes"},"name":"word_type","in":"query"},{"required":false,"schema":{"title":"Priority","maximum":10.0,"minimum":0.0,"type":"integer"},"name":"priority","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Add User Dict Word User Dict Word Post","type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/user_dict_word/{word_uuid}":{"put":{"tags":["ユーザー辞書"],"summary":"Rewrite User Dict Word","description":"ユーザー辞書に登録されている言葉を更新します。\n\nParameters\n----------\nsurface : str\n 言葉の表層形\npronunciation: str\n 言葉の発音(カタカナ)\naccent_type: int\n アクセント型(音が下がる場所を指す)\nword_uuid: str\n 更新する言葉のUUID\nword_type: WordTypes, optional\n PROPER_NOUN(固有名詞)、COMMON_NOUN(普通名詞)、VERB(動詞)、ADJECTIVE(形容詞)、SUFFIX(語尾)のいずれか\npriority: int, optional\n 単語の優先度(0から10までの整数)\n 数字が大きいほど優先度が高くなる\n 1から9までの値を指定することを推奨","operationId":"rewrite_user_dict_word_user_dict_word__word_uuid__put","parameters":[{"required":true,"schema":{"title":"Word Uuid","type":"string"},"name":"word_uuid","in":"path"},{"required":true,"schema":{"title":"Surface","type":"string"},"name":"surface","in":"query"},{"required":true,"schema":{"title":"Pronunciation","type":"string"},"name":"pronunciation","in":"query"},{"required":true,"schema":{"title":"Accent Type","type":"integer"},"name":"accent_type","in":"query"},{"required":false,"schema":{"$ref":"#/components/schemas/WordTypes"},"name":"word_type","in":"query"},{"required":false,"schema":{"title":"Priority","maximum":10.0,"minimum":0.0,"type":"integer"},"name":"priority","in":"query"}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["ユーザー辞書"],"summary":"Delete User Dict Word","description":"ユーザー辞書に登録されている言葉を削除します。\n\nParameters\n----------\nword_uuid: str\n 削除する言葉のUUID","operationId":"delete_user_dict_word_user_dict_word__word_uuid__delete","parameters":[{"required":true,"schema":{"title":"Word Uuid","type":"string"},"name":"word_uuid","in":"path"}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/import_user_dict":{"post":{"tags":["ユーザー辞書"],"summary":"Import User Dict Words","description":"他のユーザー辞書をインポートします。\n\nParameters\n----------\nimport_dict_data: Dict[str, UserDictWord]\n インポートするユーザー辞書のデータ\noverride: bool\n 重複したエントリがあった場合、上書きするかどうか","operationId":"import_user_dict_words_import_user_dict_post","parameters":[{"required":true,"schema":{"title":"Override","type":"boolean"},"name":"override","in":"query"}],"requestBody":{"content":{"application/json":{"schema":{"title":"Import Dict Data","type":"object","additionalProperties":{"$ref":"#/components/schemas/UserDictWord"}}}},"required":true},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/supported_devices":{"get":{"tags":["その他"],"summary":"Supported Devices","operationId":"supported_devices_supported_devices_get","parameters":[{"required":false,"schema":{"title":"Core Version","type":"string"},"name":"core_version","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportedDevicesInfo"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/engine_manifest":{"get":{"tags":["その他"],"summary":"Engine Manifest","operationId":"engine_manifest_engine_manifest_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EngineManifest"}}}}}}},"/validate_kana":{"post":{"tags":["その他"],"summary":"テキストがAquesTalkライクな記法に従っているか判定する","description":"テキストがAquesTalkライクな記法に従っているかどうかを判定します。\n従っていない場合はエラーが返ります。\n\nParameters\n----------\ntext: str\n 判定する対象の文字列","operationId":"validate_kana_validate_kana_post","parameters":[{"required":true,"schema":{"title":"Text","type":"string"},"name":"text","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Validate Kana Validate Kana Post","type":"boolean"}}}},"400":{"description":"テキストが不正です","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParseKanaBadRequest"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/setting":{"get":{"tags":["設定"],"summary":"Setting Get","operationId":"setting_get_setting_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}},"post":{"tags":["設定"],"summary":"Setting Post","operationId":"setting_post_setting_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_setting_post_setting_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AccentPhrase":{"title":"AccentPhrase","required":["moras","accent"],"type":"object","properties":{"moras":{"title":"モーラのリスト","type":"array","items":{"$ref":"#/components/schemas/Mora"}},"accent":{"title":"アクセント箇所","type":"integer"},"pause_mora":{"title":"後ろに無音を付けるかどうか","allOf":[{"$ref":"#/components/schemas/Mora"}]},"is_interrogative":{"title":"疑問系かどうか","type":"boolean","default":false}},"description":"アクセント句ごとの情報"},"AudioQuery":{"title":"AudioQuery","required":["accent_phrases","speedScale","pitchScale","intonationScale","volumeScale","prePhonemeLength","postPhonemeLength","outputSamplingRate","outputStereo"],"type":"object","properties":{"accent_phrases":{"title":"アクセント句のリスト","type":"array","items":{"$ref":"#/components/schemas/AccentPhrase"}},"speedScale":{"title":"全体の話速","type":"number"},"pitchScale":{"title":"全体の音高","type":"number"},"intonationScale":{"title":"全体の抑揚","type":"number"},"volumeScale":{"title":"全体の音量","type":"number"},"prePhonemeLength":{"title":"音声の前の無音時間","type":"number"},"postPhonemeLength":{"title":"音声の後の無音時間","type":"number"},"outputSamplingRate":{"title":"音声データの出力サンプリングレート","type":"integer"},"outputStereo":{"title":"音声データをステレオ出力するか否か","type":"boolean"},"kana":{"title":"[読み取り専用]AquesTalkライクな読み仮名。音声合成クエリとしては無視される","type":"string"}},"description":"音声合成用のクエリ"},"Body_setting_post_setting_post":{"title":"Body_setting_post_setting_post","type":"object","properties":{"cors_policy_mode":{"title":"Cors Policy Mode","type":"string"},"allow_origin":{"title":"Allow Origin","type":"string"}}},"DownloadableLibrary":{"title":"DownloadableLibrary","required":["name","uuid","version","download_url","bytes","speakers"],"type":"object","properties":{"name":{"title":"音声ライブラリの名前","type":"string"},"uuid":{"title":"音声ライブラリのUUID","type":"string"},"version":{"title":"音声ライブラリのバージョン","type":"string"},"download_url":{"title":"音声ライブラリのダウンロードURL","type":"string"},"bytes":{"title":"音声ライブラリのバイト数","type":"integer"},"speakers":{"title":"音声ライブラリに含まれる話者のリスト","type":"array","items":{"$ref":"#/components/schemas/LibrarySpeaker"}}},"description":"ダウンロード可能な音声ライブラリの情報"},"EngineManifest":{"title":"EngineManifest","required":["manifest_version","name","brand_name","uuid","url","icon","default_sampling_rate","terms_of_service","update_infos","dependency_licenses","supported_features"],"type":"object","properties":{"manifest_version":{"title":"マニフェストのバージョン","type":"string"},"name":{"title":"エンジン名","type":"string"},"brand_name":{"title":"ブランド名","type":"string"},"uuid":{"title":"エンジンのUUID","type":"string"},"url":{"title":"エンジンのURL","type":"string"},"icon":{"title":"エンジンのアイコンをBASE64エンコードしたもの","type":"string"},"default_sampling_rate":{"title":"デフォルトのサンプリング周波数","type":"integer"},"terms_of_service":{"title":"エンジンの利用規約","type":"string"},"update_infos":{"title":"エンジンのアップデート情報","type":"array","items":{"$ref":"#/components/schemas/UpdateInfo"}},"dependency_licenses":{"title":"依存関係のライセンス情報","type":"array","items":{"$ref":"#/components/schemas/LicenseInfo"}},"supported_vvlib_manifest_version":{"title":"エンジンが対応するvvlibのバージョン","type":"string"},"supported_features":{"title":"エンジンが持つ機能","allOf":[{"$ref":"#/components/schemas/SupportedFeatures"}]}},"description":"エンジン自体に関する情報"},"HTTPValidationError":{"title":"HTTPValidationError","type":"object","properties":{"detail":{"title":"Detail","type":"array","items":{"$ref":"#/components/schemas/ValidationError"}}}},"InstalledLibrary":{"title":"InstalledLibrary","required":["name","uuid","version","download_url","bytes","speakers","uninstallable"],"type":"object","properties":{"name":{"title":"音声ライブラリの名前","type":"string"},"uuid":{"title":"音声ライブラリのUUID","type":"string"},"version":{"title":"音声ライブラリのバージョン","type":"string"},"download_url":{"title":"音声ライブラリのダウンロードURL","type":"string"},"bytes":{"title":"音声ライブラリのバイト数","type":"integer"},"speakers":{"title":"音声ライブラリに含まれる話者のリスト","type":"array","items":{"$ref":"#/components/schemas/LibrarySpeaker"}},"uninstallable":{"title":"アンインストール可能かどうか","type":"boolean"}},"description":"インストール済み音声ライブラリの情報"},"LibrarySpeaker":{"title":"LibrarySpeaker","required":["speaker","speaker_info"],"type":"object","properties":{"speaker":{"title":"話者情報","allOf":[{"$ref":"#/components/schemas/Speaker"}]},"speaker_info":{"title":"話者の追加情報","allOf":[{"$ref":"#/components/schemas/SpeakerInfo"}]}},"description":"音声ライブラリに含まれる話者の情報"},"LicenseInfo":{"title":"LicenseInfo","required":["name","text"],"type":"object","properties":{"name":{"title":"依存ライブラリ名","type":"string"},"version":{"title":"依存ライブラリのバージョン","type":"string"},"license":{"title":"依存ライブラリのライセンス名","type":"string"},"text":{"title":"依存ライブラリのライセンス本文","type":"string"}},"description":"依存ライブラリのライセンス情報"},"Mora":{"title":"Mora","required":["text","vowel","vowel_length","pitch"],"type":"object","properties":{"text":{"title":"文字","type":"string"},"consonant":{"title":"子音の音素","type":"string"},"consonant_length":{"title":"子音の音長","type":"number"},"vowel":{"title":"母音の音素","type":"string"},"vowel_length":{"title":"母音の音長","type":"number"},"pitch":{"title":"音高","type":"number"}},"description":"モーラ(子音+母音)ごとの情報"},"MorphableTargetInfo":{"title":"MorphableTargetInfo","required":["is_morphable"],"type":"object","properties":{"is_morphable":{"title":"指定した話者に対してモーフィングの可否","type":"boolean"}}},"ParseKanaBadRequest":{"title":"ParseKanaBadRequest","required":["text","error_name","error_args"],"type":"object","properties":{"text":{"title":"エラーメッセージ","type":"string"},"error_name":{"title":"エラー名","type":"string","description":"|name|description|\n|---|---|\n| UNKNOWN_TEXT | 判別できない読み仮名があります: {text} |\n| ACCENT_TOP | 句頭にアクセントは置けません: {text} |\n| ACCENT_TWICE | 1つのアクセント句に二つ以上のアクセントは置けません: {text} |\n| ACCENT_NOTFOUND | アクセントを指定していないアクセント句があります: {text} |\n| EMPTY_PHRASE | {position}番目のアクセント句が空白です |\n| INTERROGATION_MARK_NOT_AT_END | アクセント句末以外に「?」は置けません: {text} |\n| INFINITE_LOOP | 処理時に無限ループになってしまいました...バグ報告をお願いします。 |"},"error_args":{"title":"エラーを起こした箇所","type":"object","additionalProperties":{"type":"string"}}}},"Preset":{"title":"Preset","required":["id","name","speaker_uuid","style_id","speedScale","pitchScale","intonationScale","volumeScale","prePhonemeLength","postPhonemeLength"],"type":"object","properties":{"id":{"title":"プリセットID","type":"integer"},"name":{"title":"プリセット名","type":"string"},"speaker_uuid":{"title":"スピーカーのUUID","type":"string"},"style_id":{"title":"スタイルID","type":"integer"},"speedScale":{"title":"全体の話速","type":"number"},"pitchScale":{"title":"全体の音高","type":"number"},"intonationScale":{"title":"全体の抑揚","type":"number"},"volumeScale":{"title":"全体の音量","type":"number"},"prePhonemeLength":{"title":"音声の前の無音時間","type":"number"},"postPhonemeLength":{"title":"音声の後の無音時間","type":"number"}},"description":"プリセット情報"},"Speaker":{"title":"Speaker","required":["name","speaker_uuid","styles"],"type":"object","properties":{"supported_features":{"title":"スピーカーの対応機能","allOf":[{"$ref":"#/components/schemas/SpeakerSupportedFeatures"}]},"name":{"title":"名前","type":"string"},"speaker_uuid":{"title":"スピーカーのUUID","type":"string"},"styles":{"title":"スピーカースタイルの一覧","type":"array","items":{"$ref":"#/components/schemas/SpeakerStyle"}},"version":{"title":"Version","type":"string","default":"スピーカーのバージョン"}},"description":"スピーカー情報"},"SpeakerInfo":{"title":"SpeakerInfo","required":["policy","portrait","style_infos"],"type":"object","properties":{"policy":{"title":"policy.md","type":"string"},"portrait":{"title":"portrait.pngをbase64エンコードしたもの","type":"string"},"style_infos":{"title":"スタイルの追加情報","type":"array","items":{"$ref":"#/components/schemas/StyleInfo"}}},"description":"話者の追加情報"},"SpeakerStyle":{"title":"SpeakerStyle","required":["name","id"],"type":"object","properties":{"name":{"title":"スタイル名","type":"string"},"id":{"title":"スタイルID","type":"integer"}},"description":"スピーカーのスタイル情報"},"SpeakerSupportPermittedSynthesisMorphing":{"title":"SpeakerSupportPermittedSynthesisMorphing","enum":["ALL","SELF_ONLY","NOTHING"],"type":"string","description":"An enumeration."},"SpeakerSupportedFeatures":{"title":"SpeakerSupportedFeatures","type":"object","properties":{"permitted_synthesis_morphing":{"title":"モーフィング機能への対応","allOf":[{"$ref":"#/components/schemas/SpeakerSupportPermittedSynthesisMorphing"}],"default":"ALL"}},"description":"話者の対応機能の情報"},"StyleInfo":{"title":"StyleInfo","required":["id","icon","voice_samples"],"type":"object","properties":{"id":{"title":"スタイルID","type":"integer"},"icon":{"title":"当該スタイルのアイコンをbase64エンコードしたもの","type":"string"},"portrait":{"title":"当該スタイルのportrait.pngをbase64エンコードしたもの","type":"string"},"voice_samples":{"title":"voice_sampleのwavファイルをbase64エンコードしたもの","type":"array","items":{"type":"string"}}},"description":"スタイルの追加情報"},"SupportedDevicesInfo":{"title":"SupportedDevicesInfo","required":["cpu","cuda","dml"],"type":"object","properties":{"cpu":{"title":"CPUに対応しているか","type":"boolean"},"cuda":{"title":"CUDA(Nvidia GPU)に対応しているか","type":"boolean"},"dml":{"title":"DirectML(Nvidia GPU/Radeon GPU等)に対応しているか","type":"boolean"}},"description":"対応しているデバイスの情報"},"SupportedFeatures":{"title":"SupportedFeatures","required":["adjust_mora_pitch","adjust_phoneme_length","adjust_speed_scale","adjust_pitch_scale","adjust_intonation_scale","adjust_volume_scale","interrogative_upspeak","synthesis_morphing"],"type":"object","properties":{"adjust_mora_pitch":{"title":"モーラごとの音高の調整","type":"boolean"},"adjust_phoneme_length":{"title":"音素ごとの長さの調整","type":"boolean"},"adjust_speed_scale":{"title":"全体の話速の調整","type":"boolean"},"adjust_pitch_scale":{"title":"全体の音高の調整","type":"boolean"},"adjust_intonation_scale":{"title":"全体の抑揚の調整","type":"boolean"},"adjust_volume_scale":{"title":"全体の音量の調整","type":"boolean"},"interrogative_upspeak":{"title":"疑問文の自動調整","type":"boolean"},"synthesis_morphing":{"title":"2人の話者でモーフィングした音声を合成","type":"boolean"},"manage_library":{"title":"音声ライブラリのインストール・アンインストール","type":"boolean"}},"description":"エンジンが持つ機能の一覧"},"UpdateInfo":{"title":"UpdateInfo","required":["version","descriptions"],"type":"object","properties":{"version":{"title":"エンジンのバージョン名","type":"string"},"descriptions":{"title":"アップデートの詳細についての説明","type":"array","items":{"type":"string"}},"contributors":{"title":"貢献者名","type":"array","items":{"type":"string"}}},"description":"エンジンのアップデート情報"},"UserDictWord":{"title":"UserDictWord","required":["surface","priority","part_of_speech","part_of_speech_detail_1","part_of_speech_detail_2","part_of_speech_detail_3","inflectional_type","inflectional_form","stem","yomi","pronunciation","accent_type","accent_associative_rule"],"type":"object","properties":{"surface":{"title":"表層形","type":"string"},"priority":{"title":"優先度","maximum":10.0,"minimum":0.0,"type":"integer"},"context_id":{"title":"文脈ID","type":"integer","default":1348},"part_of_speech":{"title":"品詞","type":"string"},"part_of_speech_detail_1":{"title":"品詞細分類1","type":"string"},"part_of_speech_detail_2":{"title":"品詞細分類2","type":"string"},"part_of_speech_detail_3":{"title":"品詞細分類3","type":"string"},"inflectional_type":{"title":"活用型","type":"string"},"inflectional_form":{"title":"活用形","type":"string"},"stem":{"title":"原形","type":"string"},"yomi":{"title":"読み","type":"string"},"pronunciation":{"title":"発音","type":"string"},"accent_type":{"title":"アクセント型","type":"integer"},"mora_count":{"title":"モーラ数","type":"integer"},"accent_associative_rule":{"title":"アクセント結合規則","type":"string"}},"description":"辞書のコンパイルに使われる情報"},"ValidationError":{"title":"ValidationError","required":["loc","msg","type"],"type":"object","properties":{"loc":{"title":"Location","type":"array","items":{"type":"string"}},"msg":{"title":"Message","type":"string"},"type":{"title":"Error Type","type":"string"}}},"WordTypes":{"title":"WordTypes","enum":["PROPER_NOUN","COMMON_NOUN","VERB","ADJECTIVE","SUFFIX"],"type":"string","description":"\n fastapiでword_type引数を検証する時に使用するクラス\n "},"VvlibManifest":{"title":"VvlibManifest","description":"vvlib(VOICEVOX Library)に関する情報","type":"object","properties":{"manifest_version":{"title":"マニフェストバージョン","type":"string"},"name":{"title":"音声ライブラリ名","type":"string"},"version":{"title":"音声ライブラリバージョン","type":"string"},"uuid":{"title":"音声ライブラリのUUID","type":"string"},"brand_name":{"title":"エンジンのブランド名","type":"string"},"engine_name":{"title":"エンジン名","type":"string"},"engine_uuid":{"title":"エンジンのUUID","type":"string"}},"required":["manifest_version","name","version","uuid","brand_name","engine_name","engine_uuid"]}}}} \ No newline at end of file +{"openapi":"3.1.0","info":{"title":"VOICEVOX Engine","description":"VOICEVOXの音声合成エンジンです。","version":"latest"},"paths":{"/audio_query":{"post":{"tags":["クエリ作成"],"summary":"音声合成用のクエリを作成する","description":"音声合成用のクエリの初期値を得ます。ここで得られたクエリはそのまま音声合成に利用できます。各値の意味は`Schemas`を参照してください。","operationId":"audio_query_audio_query_post","parameters":[{"required":true,"schema":{"type":"string","title":"Text"},"name":"text","in":"query"},{"required":false,"schema":{"type":"integer","title":"Style Id"},"name":"style_id","in":"query"},{"required":false,"deprecated":true,"schema":{"type":"integer","title":"Speaker"},"name":"speaker","in":"query"},{"required":false,"schema":{"type":"string","title":"Core Version"},"name":"core_version","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudioQuery"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/audio_query_from_preset":{"post":{"tags":["クエリ作成"],"summary":"音声合成用のクエリをプリセットを用いて作成する","description":"音声合成用のクエリの初期値を得ます。ここで得られたクエリはそのまま音声合成に利用できます。各値の意味は`Schemas`を参照してください。","operationId":"audio_query_from_preset_audio_query_from_preset_post","parameters":[{"required":true,"schema":{"type":"string","title":"Text"},"name":"text","in":"query"},{"required":true,"schema":{"type":"integer","title":"Preset Id"},"name":"preset_id","in":"query"},{"required":false,"schema":{"type":"string","title":"Core Version"},"name":"core_version","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudioQuery"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/accent_phrases":{"post":{"tags":["クエリ編集"],"summary":"テキストからアクセント句を得る","description":"テキストからアクセント句を得ます。\nis_kanaが`true`のとき、テキストは次のAquesTalk 風記法で解釈されます。デフォルトは`false`です。\n* 全てのカナはカタカナで記述される\n* アクセント句は`/`または`、`で区切る。`、`で区切った場合に限り無音区間が挿入される。\n* カナの手前に`_`を入れるとそのカナは無声化される\n* アクセント位置を`'`で指定する。全てのアクセント句にはアクセント位置を1つ指定する必要がある。\n* アクセント句末に`?`(全角)を入れることにより疑問文の発音ができる。","operationId":"accent_phrases_accent_phrases_post","parameters":[{"required":true,"schema":{"type":"string","title":"Text"},"name":"text","in":"query"},{"required":false,"schema":{"type":"integer","title":"Style Id"},"name":"style_id","in":"query"},{"required":false,"deprecated":true,"schema":{"type":"integer","title":"Speaker"},"name":"speaker","in":"query"},{"required":false,"schema":{"type":"boolean","title":"Is Kana","default":false},"name":"is_kana","in":"query"},{"required":false,"schema":{"type":"string","title":"Core Version"},"name":"core_version","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/AccentPhrase"},"type":"array","title":"Response Accent Phrases Accent Phrases Post"}}}},"400":{"description":"読み仮名のパースに失敗","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParseKanaBadRequest"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/mora_data":{"post":{"tags":["クエリ編集"],"summary":"アクセント句から音高・音素長を得る","operationId":"mora_data_mora_data_post","parameters":[{"required":false,"schema":{"type":"integer","title":"Style Id"},"name":"style_id","in":"query"},{"required":false,"deprecated":true,"schema":{"type":"integer","title":"Speaker"},"name":"speaker","in":"query"},{"required":false,"schema":{"type":"string","title":"Core Version"},"name":"core_version","in":"query"}],"requestBody":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/AccentPhrase"},"type":"array","title":"Accent Phrases"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/AccentPhrase"},"type":"array","title":"Response Mora Data Mora Data Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/mora_length":{"post":{"tags":["クエリ編集"],"summary":"アクセント句から音素長を得る","operationId":"mora_length_mora_length_post","parameters":[{"required":false,"schema":{"type":"integer","title":"Style Id"},"name":"style_id","in":"query"},{"required":false,"deprecated":true,"schema":{"type":"integer","title":"Speaker"},"name":"speaker","in":"query"},{"required":false,"schema":{"type":"string","title":"Core Version"},"name":"core_version","in":"query"}],"requestBody":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/AccentPhrase"},"type":"array","title":"Accent Phrases"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/AccentPhrase"},"type":"array","title":"Response Mora Length Mora Length Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/mora_pitch":{"post":{"tags":["クエリ編集"],"summary":"アクセント句から音高を得る","operationId":"mora_pitch_mora_pitch_post","parameters":[{"required":false,"schema":{"type":"integer","title":"Style Id"},"name":"style_id","in":"query"},{"required":false,"deprecated":true,"schema":{"type":"integer","title":"Speaker"},"name":"speaker","in":"query"},{"required":false,"schema":{"type":"string","title":"Core Version"},"name":"core_version","in":"query"}],"requestBody":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/AccentPhrase"},"type":"array","title":"Accent Phrases"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/AccentPhrase"},"type":"array","title":"Response Mora Pitch Mora Pitch Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/synthesis":{"post":{"tags":["音声合成"],"summary":"音声合成する","operationId":"synthesis_synthesis_post","parameters":[{"required":false,"schema":{"type":"integer","title":"Style Id"},"name":"style_id","in":"query"},{"required":false,"deprecated":true,"schema":{"type":"integer","title":"Speaker"},"name":"speaker","in":"query"},{"description":"疑問系のテキストが与えられたら語尾を自動調整する","required":false,"schema":{"type":"boolean","title":"Enable Interrogative Upspeak","description":"疑問系のテキストが与えられたら語尾を自動調整する","default":true},"name":"enable_interrogative_upspeak","in":"query"},{"required":false,"schema":{"type":"string","title":"Core Version"},"name":"core_version","in":"query"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudioQuery"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"audio/wav":{"schema":{"type":"string","format":"binary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cancellable_synthesis":{"post":{"tags":["音声合成"],"summary":"音声合成する(キャンセル可能)","operationId":"cancellable_synthesis_cancellable_synthesis_post","parameters":[{"required":false,"schema":{"type":"integer","title":"Style Id"},"name":"style_id","in":"query"},{"required":false,"deprecated":true,"schema":{"type":"integer","title":"Speaker"},"name":"speaker","in":"query"},{"required":false,"schema":{"type":"string","title":"Core Version"},"name":"core_version","in":"query"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudioQuery"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"audio/wav":{"schema":{"type":"string","format":"binary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/multi_synthesis":{"post":{"tags":["音声合成"],"summary":"複数まとめて音声合成する","operationId":"multi_synthesis_multi_synthesis_post","parameters":[{"required":false,"schema":{"type":"integer","title":"Style Id"},"name":"style_id","in":"query"},{"required":false,"deprecated":true,"schema":{"type":"integer","title":"Speaker"},"name":"speaker","in":"query"},{"required":false,"schema":{"type":"string","title":"Core Version"},"name":"core_version","in":"query"}],"requestBody":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/AudioQuery"},"type":"array","title":"Queries"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/zip":{"schema":{"type":"string","format":"binary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/morphable_targets":{"post":{"tags":["音声合成"],"summary":"指定したスタイルに対してエンジン内の話者がモーフィングが可能か判定する","description":"指定されたベーススタイルに対してエンジン内の各話者がモーフィング機能を利用可能か返します。\nモーフィングの許可/禁止は`/speakers`の`speaker.supported_features.synthesis_morphing`に記載されています。\nプロパティが存在しない場合は、モーフィングが許可されているとみなします。\n返り値の話者はstring型なので注意。","operationId":"morphable_targets_morphable_targets_post","parameters":[{"required":false,"schema":{"type":"string","title":"Core Version"},"name":"core_version","in":"query"}],"requestBody":{"content":{"application/json":{"schema":{"items":{"type":"integer"},"type":"array","title":"Base Style Ids"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"additionalProperties":{"$ref":"#/components/schemas/MorphableTargetInfo"},"type":"object"},"type":"array","title":"Response Morphable Targets Morphable Targets Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/synthesis_morphing":{"post":{"tags":["音声合成"],"summary":"2種類のスタイルでモーフィングした音声を合成する","description":"指定された2種類のスタイルで音声を合成、指定した割合でモーフィングした音声を得ます。\nモーフィングの割合は`morph_rate`で指定でき、0.0でベースのスタイル、1.0でターゲットのスタイルに近づきます。","operationId":"_synthesis_morphing_synthesis_morphing_post","parameters":[{"required":false,"schema":{"type":"integer","title":"Base Style Id"},"name":"base_style_id","in":"query"},{"required":false,"deprecated":true,"schema":{"type":"integer","title":"Base Speaker"},"name":"base_speaker","in":"query"},{"required":false,"schema":{"type":"integer","title":"Target Style Id"},"name":"target_style_id","in":"query"},{"required":false,"deprecated":true,"schema":{"type":"integer","title":"Target Speaker"},"name":"target_speaker","in":"query"},{"required":true,"schema":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Morph Rate"},"name":"morph_rate","in":"query"},{"required":false,"schema":{"type":"string","title":"Core Version"},"name":"core_version","in":"query"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudioQuery"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"audio/wav":{"schema":{"type":"string","format":"binary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/connect_waves":{"post":{"tags":["その他"],"summary":"base64エンコードされた複数のwavデータを一つに結合する","description":"base64エンコードされたwavデータを一纏めにし、wavファイルで返します。","operationId":"connect_waves_connect_waves_post","requestBody":{"content":{"application/json":{"schema":{"items":{"type":"string"},"type":"array","title":"Waves"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"audio/wav":{"schema":{"type":"string","format":"binary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/presets":{"get":{"tags":["その他"],"summary":"Get Presets","description":"エンジンが保持しているプリセットの設定を返します\n\nReturns\n-------\npresets: list[Preset]\n プリセットのリスト","operationId":"get_presets_presets_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/Preset"},"type":"array","title":"Response Get Presets Presets Get"}}}}}}},"/add_preset":{"post":{"tags":["その他"],"summary":"Add Preset","description":"新しいプリセットを追加します\n\nParameters\n-------\npreset: Preset\n 新しいプリセット。\n プリセットIDが既存のものと重複している場合は、新規のプリセットIDが採番されます。\n\nReturns\n-------\nid: int\n 追加したプリセットのプリセットID","operationId":"add_preset_add_preset_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Preset"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"integer","title":"Response Add Preset Add Preset Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/update_preset":{"post":{"tags":["その他"],"summary":"Update Preset","description":"既存のプリセットを更新します\n\nParameters\n-------\npreset: Preset\n 更新するプリセット。\n プリセットIDが更新対象と一致している必要があります。\n\nReturns\n-------\nid: int\n 更新したプリセットのプリセットID","operationId":"update_preset_update_preset_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Preset"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"integer","title":"Response Update Preset Update Preset Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/delete_preset":{"post":{"tags":["その他"],"summary":"Delete Preset","description":"既存のプリセットを削除します\n\nParameters\n-------\nid: int\n 削除するプリセットのプリセットID","operationId":"delete_preset_delete_preset_post","parameters":[{"required":true,"schema":{"type":"integer","title":"Id"},"name":"id","in":"query"}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/version":{"get":{"tags":["その他"],"summary":"Version","operationId":"version_version_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"string","title":"Response Version Version Get"}}}}}}},"/core_versions":{"get":{"tags":["その他"],"summary":"Core Versions","operationId":"core_versions_core_versions_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"type":"string"},"type":"array","title":"Response Core Versions Core Versions Get"}}}}}}},"/speakers":{"get":{"tags":["その他"],"summary":"Speakers","operationId":"speakers_speakers_get","parameters":[{"required":false,"schema":{"type":"string","title":"Core Version"},"name":"core_version","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/Speaker"},"type":"array","title":"Response Speakers Speakers Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/speaker_info":{"get":{"tags":["その他"],"summary":"Speaker Info","description":"指定されたspeaker_uuidに関する情報をjson形式で返します。\n画像や音声はbase64エンコードされたものが返されます。\n\nReturns\n-------\nret_data: SpeakerInfo","operationId":"speaker_info_speaker_info_get","parameters":[{"required":true,"schema":{"type":"string","title":"Speaker Uuid"},"name":"speaker_uuid","in":"query"},{"required":false,"schema":{"type":"string","title":"Core Version"},"name":"core_version","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeakerInfo"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/downloadable_libraries":{"get":{"tags":["音声ライブラリ管理"],"summary":"Downloadable Libraries","description":"ダウンロード可能な音声ライブラリの情報を返します。\n\nReturns\n-------\nret_data: list[DownloadableLibrary]","operationId":"downloadable_libraries_downloadable_libraries_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/DownloadableLibraryInfo"},"type":"array","title":"Response Downloadable Libraries Downloadable Libraries Get"}}}}}}},"/installed_libraries":{"get":{"tags":["音声ライブラリ管理"],"summary":"Installed Libraries","description":"インストールした音声ライブラリの情報を返します。\n\nReturns\n-------\nret_data: dict[str, InstalledLibrary]","operationId":"installed_libraries_installed_libraries_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"$ref":"#/components/schemas/InstalledLibraryInfo"},"type":"object","title":"Response Installed Libraries Installed Libraries Get"}}}}}}},"/install_library/{library_uuid}":{"post":{"tags":["音声ライブラリ管理"],"summary":"Install Library","description":"音声ライブラリをインストールします。\n音声ライブラリのZIPファイルをリクエストボディとして送信してください。\n\nParameters\n----------\nlibrary_uuid: str\n 音声ライブラリのID","operationId":"install_library_install_library__library_uuid__post","parameters":[{"required":true,"schema":{"type":"string","title":"Library Uuid"},"name":"library_uuid","in":"path"}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/uninstall_library/{library_uuid}":{"post":{"tags":["音声ライブラリ管理"],"summary":"Uninstall Library","description":"音声ライブラリをアンインストールします。\n\nParameters\n----------\nlibrary_uuid: str\n 音声ライブラリのID","operationId":"uninstall_library_uninstall_library__library_uuid__post","parameters":[{"required":true,"schema":{"type":"string","title":"Library Uuid"},"name":"library_uuid","in":"path"}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/initialize_style_id":{"post":{"tags":["その他"],"summary":"Initialize Style Id","description":"指定されたstyle_idのスタイルを初期化します。\n実行しなくても他のAPIは使用できますが、初回実行時に時間がかかることがあります。","operationId":"initialize_style_id_initialize_style_id_post","parameters":[{"required":true,"schema":{"type":"integer","title":"Style Id"},"name":"style_id","in":"query"},{"description":"既に初期化済みのスタイルの再初期化をスキップするかどうか","required":false,"schema":{"type":"boolean","title":"Skip Reinit","description":"既に初期化済みのスタイルの再初期化をスキップするかどうか","default":false},"name":"skip_reinit","in":"query"},{"required":false,"schema":{"type":"string","title":"Core Version"},"name":"core_version","in":"query"}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/is_initialized_style_id":{"get":{"tags":["その他"],"summary":"Is Initialized Style Id","description":"指定されたstyle_idのスタイルが初期化されているかどうかを返します。","operationId":"is_initialized_style_id_is_initialized_style_id_get","parameters":[{"required":true,"schema":{"type":"integer","title":"Style Id"},"name":"style_id","in":"query"},{"required":false,"schema":{"type":"string","title":"Core Version"},"name":"core_version","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"boolean","title":"Response Is Initialized Style Id Is Initialized Style Id Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/initialize_speaker":{"post":{"tags":["その他"],"summary":"Initialize Speaker","description":"こちらのAPIは非推奨です。`initialize_style_id`を利用してください。","operationId":"initialize_speaker_initialize_speaker_post","parameters":[{"required":true,"schema":{"type":"integer","title":"Speaker"},"name":"speaker","in":"query"},{"description":"既に初期化済みの話者の再初期化をスキップするかどうか","required":false,"schema":{"type":"boolean","title":"Skip Reinit","description":"既に初期化済みの話者の再初期化をスキップするかどうか","default":false},"name":"skip_reinit","in":"query"},{"required":false,"schema":{"type":"string","title":"Core Version"},"name":"core_version","in":"query"}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"deprecated":true}},"/is_initialized_speaker":{"get":{"tags":["その他"],"summary":"Is Initialized Speaker","description":"こちらのAPIは非推奨です。`is_initialize_style_id`を利用してください。","operationId":"is_initialized_speaker_is_initialized_speaker_get","parameters":[{"required":true,"schema":{"type":"integer","title":"Speaker"},"name":"speaker","in":"query"},{"required":false,"schema":{"type":"string","title":"Core Version"},"name":"core_version","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"boolean","title":"Response Is Initialized Speaker Is Initialized Speaker Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"deprecated":true}},"/user_dict":{"get":{"tags":["ユーザー辞書"],"summary":"Get User Dict Words","description":"ユーザー辞書に登録されている単語の一覧を返します。\n単語の表層形(surface)は正規化済みの物を返します。\n\nReturns\n-------\ndict[str, UserDictWord]\n 単語のUUIDとその詳細","operationId":"get_user_dict_words_user_dict_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"$ref":"#/components/schemas/UserDictWord"},"type":"object","title":"Response Get User Dict Words User Dict Get"}}}}}}},"/user_dict_word":{"post":{"tags":["ユーザー辞書"],"summary":"Add User Dict Word","description":"ユーザー辞書に言葉を追加します。\n\nParameters\n----------\nsurface : str\n 言葉の表層形\npronunciation: str\n 言葉の発音(カタカナ)\naccent_type: int\n アクセント型(音が下がる場所を指す)\nword_type: WordTypes, optional\n PROPER_NOUN(固有名詞)、COMMON_NOUN(普通名詞)、VERB(動詞)、ADJECTIVE(形容詞)、SUFFIX(語尾)のいずれか\npriority: int, optional\n 単語の優先度(0から10までの整数)\n 数字が大きいほど優先度が高くなる\n 1から9までの値を指定することを推奨","operationId":"add_user_dict_word_user_dict_word_post","parameters":[{"required":true,"schema":{"type":"string","title":"Surface"},"name":"surface","in":"query"},{"required":true,"schema":{"type":"string","title":"Pronunciation"},"name":"pronunciation","in":"query"},{"required":true,"schema":{"type":"integer","title":"Accent Type"},"name":"accent_type","in":"query"},{"required":false,"schema":{"$ref":"#/components/schemas/WordTypes"},"name":"word_type","in":"query"},{"required":false,"schema":{"type":"integer","maximum":10.0,"minimum":0.0,"title":"Priority"},"name":"priority","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"string","title":"Response Add User Dict Word User Dict Word Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/user_dict_word/{word_uuid}":{"put":{"tags":["ユーザー辞書"],"summary":"Rewrite User Dict Word","description":"ユーザー辞書に登録されている言葉を更新します。\n\nParameters\n----------\nsurface : str\n 言葉の表層形\npronunciation: str\n 言葉の発音(カタカナ)\naccent_type: int\n アクセント型(音が下がる場所を指す)\nword_uuid: str\n 更新する言葉のUUID\nword_type: WordTypes, optional\n PROPER_NOUN(固有名詞)、COMMON_NOUN(普通名詞)、VERB(動詞)、ADJECTIVE(形容詞)、SUFFIX(語尾)のいずれか\npriority: int, optional\n 単語の優先度(0から10までの整数)\n 数字が大きいほど優先度が高くなる\n 1から9までの値を指定することを推奨","operationId":"rewrite_user_dict_word_user_dict_word__word_uuid__put","parameters":[{"required":true,"schema":{"type":"string","title":"Word Uuid"},"name":"word_uuid","in":"path"},{"required":true,"schema":{"type":"string","title":"Surface"},"name":"surface","in":"query"},{"required":true,"schema":{"type":"string","title":"Pronunciation"},"name":"pronunciation","in":"query"},{"required":true,"schema":{"type":"integer","title":"Accent Type"},"name":"accent_type","in":"query"},{"required":false,"schema":{"$ref":"#/components/schemas/WordTypes"},"name":"word_type","in":"query"},{"required":false,"schema":{"type":"integer","maximum":10.0,"minimum":0.0,"title":"Priority"},"name":"priority","in":"query"}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["ユーザー辞書"],"summary":"Delete User Dict Word","description":"ユーザー辞書に登録されている言葉を削除します。\n\nParameters\n----------\nword_uuid: str\n 削除する言葉のUUID","operationId":"delete_user_dict_word_user_dict_word__word_uuid__delete","parameters":[{"required":true,"schema":{"type":"string","title":"Word Uuid"},"name":"word_uuid","in":"path"}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/import_user_dict":{"post":{"tags":["ユーザー辞書"],"summary":"Import User Dict Words","description":"他のユーザー辞書をインポートします。\n\nParameters\n----------\nimport_dict_data: dict[str, UserDictWord]\n インポートするユーザー辞書のデータ\noverride: bool\n 重複したエントリがあった場合、上書きするかどうか","operationId":"import_user_dict_words_import_user_dict_post","parameters":[{"required":true,"schema":{"type":"boolean","title":"Override"},"name":"override","in":"query"}],"requestBody":{"content":{"application/json":{"schema":{"additionalProperties":{"$ref":"#/components/schemas/UserDictWord"},"type":"object","title":"Import Dict Data"}}},"required":true},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/supported_devices":{"get":{"tags":["その他"],"summary":"Supported Devices","operationId":"supported_devices_supported_devices_get","parameters":[{"required":false,"schema":{"type":"string","title":"Core Version"},"name":"core_version","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportedDevicesInfo"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/engine_manifest":{"get":{"tags":["その他"],"summary":"Engine Manifest","operationId":"engine_manifest_engine_manifest_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EngineManifest"}}}}}}},"/validate_kana":{"post":{"tags":["その他"],"summary":"テキストがAquesTalk 風記法に従っているか判定する","description":"テキストがAquesTalk 風記法に従っているかどうかを判定します。\n従っていない場合はエラーが返ります。\n\nParameters\n----------\ntext: str\n 判定する対象の文字列","operationId":"validate_kana_validate_kana_post","parameters":[{"required":true,"schema":{"type":"string","title":"Text"},"name":"text","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"boolean","title":"Response Validate Kana Validate Kana Post"}}}},"400":{"description":"テキストが不正です","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParseKanaBadRequest"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/setting":{"get":{"tags":["設定"],"summary":"Setting Get","description":"設定ページを返します。","operationId":"setting_get_setting_get","responses":{"200":{"description":"Successful Response"}}},"post":{"tags":["設定"],"summary":"Setting Post","description":"設定を更新します。","operationId":"setting_post_setting_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_setting_post_setting_post"}}},"required":true},"responses":{"200":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AccentPhrase":{"properties":{"moras":{"items":{"$ref":"#/components/schemas/Mora"},"type":"array","title":"モーラのリスト"},"accent":{"type":"integer","title":"アクセント箇所"},"pause_mora":{"allOf":[{"$ref":"#/components/schemas/Mora"}],"title":"後ろに無音を付けるかどうか"},"is_interrogative":{"type":"boolean","title":"疑問系かどうか","default":false}},"type":"object","required":["moras","accent"],"title":"AccentPhrase","description":"アクセント句ごとの情報"},"AudioQuery":{"properties":{"accent_phrases":{"items":{"$ref":"#/components/schemas/AccentPhrase"},"type":"array","title":"アクセント句のリスト"},"speedScale":{"type":"number","title":"全体の話速"},"pitchScale":{"type":"number","title":"全体の音高"},"intonationScale":{"type":"number","title":"全体の抑揚"},"volumeScale":{"type":"number","title":"全体の音量"},"prePhonemeLength":{"type":"number","title":"音声の前の無音時間"},"postPhonemeLength":{"type":"number","title":"音声の後の無音時間"},"outputSamplingRate":{"type":"integer","title":"音声データの出力サンプリングレート"},"outputStereo":{"type":"boolean","title":"音声データをステレオ出力するか否か"},"kana":{"type":"string","title":"[読み取り専用]AquesTalk 風記法によるテキスト。音声合成用のクエリとしては無視される"}},"type":"object","required":["accent_phrases","speedScale","pitchScale","intonationScale","volumeScale","prePhonemeLength","postPhonemeLength","outputSamplingRate","outputStereo"],"title":"AudioQuery","description":"音声合成用のクエリ"},"Body_setting_post_setting_post":{"properties":{"cors_policy_mode":{"$ref":"#/components/schemas/CorsPolicyMode"},"allow_origin":{"type":"string","title":"Allow Origin"}},"type":"object","required":["cors_policy_mode"],"title":"Body_setting_post_setting_post"},"CorsPolicyMode":{"type":"string","enum":["all","localapps"],"title":"CorsPolicyMode","description":"CORSの許可モード"},"DownloadableLibraryInfo":{"properties":{"name":{"type":"string","title":"音声ライブラリの名前"},"uuid":{"type":"string","title":"音声ライブラリのUUID"},"version":{"type":"string","title":"音声ライブラリのバージョン"},"download_url":{"type":"string","title":"音声ライブラリのダウンロードURL"},"bytes":{"type":"integer","title":"音声ライブラリのバイト数"},"speakers":{"items":{"$ref":"#/components/schemas/LibrarySpeaker"},"type":"array","title":"音声ライブラリに含まれる話者のリスト"}},"type":"object","required":["name","uuid","version","download_url","bytes","speakers"],"title":"DownloadableLibraryInfo","description":"ダウンロード可能な音声ライブラリの情報"},"EngineManifest":{"properties":{"manifest_version":{"type":"string","title":"マニフェストのバージョン"},"name":{"type":"string","title":"エンジン名"},"brand_name":{"type":"string","title":"ブランド名"},"uuid":{"type":"string","title":"エンジンのUUID"},"url":{"type":"string","title":"エンジンのURL"},"icon":{"type":"string","title":"エンジンのアイコンをBASE64エンコードしたもの"},"default_sampling_rate":{"type":"integer","title":"デフォルトのサンプリング周波数"},"terms_of_service":{"type":"string","title":"エンジンの利用規約"},"update_infos":{"items":{"$ref":"#/components/schemas/UpdateInfo"},"type":"array","title":"エンジンのアップデート情報"},"dependency_licenses":{"items":{"$ref":"#/components/schemas/LicenseInfo"},"type":"array","title":"依存関係のライセンス情報"},"supported_vvlib_manifest_version":{"type":"string","title":"エンジンが対応するvvlibのバージョン"},"supported_features":{"allOf":[{"$ref":"#/components/schemas/SupportedFeatures"}],"title":"エンジンが持つ機能"}},"type":"object","required":["manifest_version","name","brand_name","uuid","url","icon","default_sampling_rate","terms_of_service","update_infos","dependency_licenses","supported_features"],"title":"EngineManifest","description":"エンジン自体に関する情報"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"InstalledLibraryInfo":{"properties":{"name":{"type":"string","title":"音声ライブラリの名前"},"uuid":{"type":"string","title":"音声ライブラリのUUID"},"version":{"type":"string","title":"音声ライブラリのバージョン"},"download_url":{"type":"string","title":"音声ライブラリのダウンロードURL"},"bytes":{"type":"integer","title":"音声ライブラリのバイト数"},"speakers":{"items":{"$ref":"#/components/schemas/LibrarySpeaker"},"type":"array","title":"音声ライブラリに含まれる話者のリスト"},"uninstallable":{"type":"boolean","title":"アンインストール可能かどうか"}},"type":"object","required":["name","uuid","version","download_url","bytes","speakers","uninstallable"],"title":"InstalledLibraryInfo","description":"インストール済み音声ライブラリの情報"},"LibrarySpeaker":{"properties":{"speaker":{"allOf":[{"$ref":"#/components/schemas/Speaker"}],"title":"話者情報"},"speaker_info":{"allOf":[{"$ref":"#/components/schemas/SpeakerInfo"}],"title":"話者の追加情報"}},"type":"object","required":["speaker","speaker_info"],"title":"LibrarySpeaker","description":"音声ライブラリに含まれる話者の情報"},"LicenseInfo":{"properties":{"name":{"type":"string","title":"依存ライブラリ名"},"version":{"type":"string","title":"依存ライブラリのバージョン"},"license":{"type":"string","title":"依存ライブラリのライセンス名"},"text":{"type":"string","title":"依存ライブラリのライセンス本文"}},"type":"object","required":["name","text"],"title":"LicenseInfo","description":"依存ライブラリのライセンス情報"},"Mora":{"properties":{"text":{"type":"string","title":"文字"},"consonant":{"type":"string","title":"子音の音素"},"consonant_length":{"type":"number","title":"子音の音長"},"vowel":{"type":"string","title":"母音の音素"},"vowel_length":{"type":"number","title":"母音の音長"},"pitch":{"type":"number","title":"音高"}},"type":"object","required":["text","vowel","vowel_length","pitch"],"title":"Mora","description":"モーラ(子音+母音)ごとの情報"},"MorphableTargetInfo":{"properties":{"is_morphable":{"type":"boolean","title":"指定した話者に対してモーフィングの可否"}},"type":"object","required":["is_morphable"],"title":"MorphableTargetInfo"},"ParseKanaBadRequest":{"properties":{"text":{"type":"string","title":"エラーメッセージ"},"error_name":{"type":"string","title":"エラー名","description":"|name|description|\n|---|---|\n| UNKNOWN_TEXT | 判別できない読み仮名があります: {text} |\n| ACCENT_TOP | 句頭にアクセントは置けません: {text} |\n| ACCENT_TWICE | 1つのアクセント句に二つ以上のアクセントは置けません: {text} |\n| ACCENT_NOTFOUND | アクセントを指定していないアクセント句があります: {text} |\n| EMPTY_PHRASE | {position}番目のアクセント句が空白です |\n| INTERROGATION_MARK_NOT_AT_END | アクセント句末以外に「?」は置けません: {text} |\n| INFINITE_LOOP | 処理時に無限ループになってしまいました...バグ報告をお願いします。 |"},"error_args":{"additionalProperties":{"type":"string"},"type":"object","title":"エラーを起こした箇所"}},"type":"object","required":["text","error_name","error_args"],"title":"ParseKanaBadRequest"},"Preset":{"properties":{"id":{"type":"integer","title":"プリセットID"},"name":{"type":"string","title":"プリセット名"},"speaker_uuid":{"type":"string","title":"話者のUUID"},"style_id":{"type":"integer","title":"スタイルID"},"speedScale":{"type":"number","title":"全体の話速"},"pitchScale":{"type":"number","title":"全体の音高"},"intonationScale":{"type":"number","title":"全体の抑揚"},"volumeScale":{"type":"number","title":"全体の音量"},"prePhonemeLength":{"type":"number","title":"音声の前の無音時間"},"postPhonemeLength":{"type":"number","title":"音声の後の無音時間"}},"type":"object","required":["id","name","speaker_uuid","style_id","speedScale","pitchScale","intonationScale","volumeScale","prePhonemeLength","postPhonemeLength"],"title":"Preset","description":"プリセット情報"},"Speaker":{"properties":{"supported_features":{"allOf":[{"$ref":"#/components/schemas/SpeakerSupportedFeatures"}],"title":"話者の対応機能"},"name":{"type":"string","title":"名前"},"speaker_uuid":{"type":"string","title":"話者のUUID"},"styles":{"items":{"$ref":"#/components/schemas/SpeakerStyle"},"type":"array","title":"スタイルの一覧"},"version":{"type":"string","title":"Version","default":"話者のバージョン"}},"type":"object","required":["name","speaker_uuid","styles"],"title":"Speaker","description":"話者情報"},"SpeakerInfo":{"properties":{"policy":{"type":"string","title":"policy.md"},"portrait":{"type":"string","title":"portrait.pngをbase64エンコードしたもの"},"style_infos":{"items":{"$ref":"#/components/schemas/StyleInfo"},"type":"array","title":"スタイルの追加情報"}},"type":"object","required":["policy","portrait","style_infos"],"title":"SpeakerInfo","description":"話者の追加情報"},"SpeakerStyle":{"properties":{"name":{"type":"string","title":"スタイル名"},"id":{"type":"integer","title":"スタイルID"}},"type":"object","required":["name","id"],"title":"SpeakerStyle","description":"話者のスタイル情報"},"SpeakerSupportPermittedSynthesisMorphing":{"type":"string","enum":["ALL","SELF_ONLY","NOTHING"],"title":"SpeakerSupportPermittedSynthesisMorphing","description":"An enumeration."},"SpeakerSupportedFeatures":{"properties":{"permitted_synthesis_morphing":{"allOf":[{"$ref":"#/components/schemas/SpeakerSupportPermittedSynthesisMorphing"}],"title":"モーフィング機能への対応","default":"ALL"}},"type":"object","title":"SpeakerSupportedFeatures","description":"話者の対応機能の情報"},"StyleInfo":{"properties":{"id":{"type":"integer","title":"スタイルID"},"icon":{"type":"string","title":"当該スタイルのアイコンをbase64エンコードしたもの"},"portrait":{"type":"string","title":"当該スタイルのportrait.pngをbase64エンコードしたもの"},"voice_samples":{"items":{"type":"string"},"type":"array","title":"voice_sampleのwavファイルをbase64エンコードしたもの"}},"type":"object","required":["id","icon","voice_samples"],"title":"StyleInfo","description":"スタイルの追加情報"},"SupportedDevicesInfo":{"properties":{"cpu":{"type":"boolean","title":"CPUに対応しているか"},"cuda":{"type":"boolean","title":"CUDA(Nvidia GPU)に対応しているか"},"dml":{"type":"boolean","title":"DirectML(Nvidia GPU/Radeon GPU等)に対応しているか"}},"type":"object","required":["cpu","cuda","dml"],"title":"SupportedDevicesInfo","description":"対応しているデバイスの情報"},"SupportedFeatures":{"properties":{"adjust_mora_pitch":{"type":"boolean","title":"モーラごとの音高の調整"},"adjust_phoneme_length":{"type":"boolean","title":"音素ごとの長さの調整"},"adjust_speed_scale":{"type":"boolean","title":"全体の話速の調整"},"adjust_pitch_scale":{"type":"boolean","title":"全体の音高の調整"},"adjust_intonation_scale":{"type":"boolean","title":"全体の抑揚の調整"},"adjust_volume_scale":{"type":"boolean","title":"全体の音量の調整"},"interrogative_upspeak":{"type":"boolean","title":"疑問文の自動調整"},"synthesis_morphing":{"type":"boolean","title":"2種類のスタイルでモーフィングした音声を合成"},"manage_library":{"type":"boolean","title":"音声ライブラリのインストール・アンインストール"}},"type":"object","required":["adjust_mora_pitch","adjust_phoneme_length","adjust_speed_scale","adjust_pitch_scale","adjust_intonation_scale","adjust_volume_scale","interrogative_upspeak","synthesis_morphing"],"title":"SupportedFeatures","description":"エンジンが持つ機能の一覧"},"UpdateInfo":{"properties":{"version":{"type":"string","title":"エンジンのバージョン名"},"descriptions":{"items":{"type":"string"},"type":"array","title":"アップデートの詳細についての説明"},"contributors":{"items":{"type":"string"},"type":"array","title":"貢献者名"}},"type":"object","required":["version","descriptions"],"title":"UpdateInfo","description":"エンジンのアップデート情報"},"UserDictWord":{"properties":{"surface":{"type":"string","title":"表層形"},"priority":{"type":"integer","maximum":10.0,"minimum":0.0,"title":"優先度"},"context_id":{"type":"integer","title":"文脈ID","default":1348},"part_of_speech":{"type":"string","title":"品詞"},"part_of_speech_detail_1":{"type":"string","title":"品詞細分類1"},"part_of_speech_detail_2":{"type":"string","title":"品詞細分類2"},"part_of_speech_detail_3":{"type":"string","title":"品詞細分類3"},"inflectional_type":{"type":"string","title":"活用型"},"inflectional_form":{"type":"string","title":"活用形"},"stem":{"type":"string","title":"原形"},"yomi":{"type":"string","title":"読み"},"pronunciation":{"type":"string","title":"発音"},"accent_type":{"type":"integer","title":"アクセント型"},"mora_count":{"type":"integer","title":"モーラ数"},"accent_associative_rule":{"type":"string","title":"アクセント結合規則"}},"type":"object","required":["surface","priority","part_of_speech","part_of_speech_detail_1","part_of_speech_detail_2","part_of_speech_detail_3","inflectional_type","inflectional_form","stem","yomi","pronunciation","accent_type","accent_associative_rule"],"title":"UserDictWord","description":"辞書のコンパイルに使われる情報"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"WordTypes":{"type":"string","enum":["PROPER_NOUN","COMMON_NOUN","VERB","ADJECTIVE","SUFFIX"],"title":"WordTypes","description":"fastapiでword_type引数を検証する時に使用するクラス"},"VvlibManifest":{"title":"VvlibManifest","description":"vvlib(VOICEVOX Library)に関する情報","type":"object","properties":{"manifest_version":{"title":"マニフェストバージョン","type":"string"},"name":{"title":"音声ライブラリ名","type":"string"},"version":{"title":"音声ライブラリバージョン","type":"string"},"uuid":{"title":"音声ライブラリのUUID","type":"string"},"brand_name":{"title":"エンジンのブランド名","type":"string"},"engine_name":{"title":"エンジン名","type":"string"},"engine_uuid":{"title":"エンジンのUUID","type":"string"}},"required":["manifest_version","name","version","uuid","brand_name","engine_name","engine_uuid"]},"BaseLibraryInfo":{"title":"BaseLibraryInfo","description":"音声ライブラリの情報","type":"object","properties":{"name":{"title":"音声ライブラリの名前","type":"string"},"uuid":{"title":"音声ライブラリのUUID","type":"string"},"version":{"title":"音声ライブラリのバージョン","type":"string"},"download_url":{"title":"音声ライブラリのダウンロードURL","type":"string"},"bytes":{"title":"音声ライブラリのバイト数","type":"integer"},"speakers":{"title":"音声ライブラリに含まれる話者のリスト","type":"array","items":{"$ref":"#/components/schemas/LibrarySpeaker"}}},"required":["name","uuid","version","download_url","bytes","speakers"]}}}} \ No newline at end of file diff --git a/openapitools.json b/openapitools.json index 06ac150787..64f2cbb541 100644 --- a/openapitools.json +++ b/openapitools.json @@ -2,6 +2,6 @@ "$schema": "node_modules/@openapitools/openapi-generator-cli/config.schema.json", "spaces": 2, "generator-cli": { - "version": "7.0.0" + "version": "7.2.0" } } diff --git a/src/openapi/.openapi-generator/FILES b/src/openapi/.openapi-generator/FILES index 3e91276466..6e64b2d764 100644 --- a/src/openapi/.openapi-generator/FILES +++ b/src/openapi/.openapi-generator/FILES @@ -3,10 +3,12 @@ apis/index.ts index.ts models/AccentPhrase.ts models/AudioQuery.ts -models/DownloadableLibrary.ts +models/BaseLibraryInfo.ts +models/CorsPolicyMode.ts +models/DownloadableLibraryInfo.ts models/EngineManifest.ts models/HTTPValidationError.ts -models/InstalledLibrary.ts +models/InstalledLibraryInfo.ts models/LibrarySpeaker.ts models/LicenseInfo.ts models/Mora.ts @@ -24,6 +26,7 @@ models/SupportedFeatures.ts models/UpdateInfo.ts models/UserDictWord.ts models/ValidationError.ts +models/ValidationErrorLocInner.ts models/VvlibManifest.ts models/WordTypes.ts models/index.ts diff --git a/src/openapi/.openapi-generator/VERSION b/src/openapi/.openapi-generator/VERSION index 4122521804..4b49d9bb63 100644 --- a/src/openapi/.openapi-generator/VERSION +++ b/src/openapi/.openapi-generator/VERSION @@ -1 +1 @@ -7.0.0 \ No newline at end of file +7.2.0 \ No newline at end of file diff --git a/src/openapi/apis/DefaultApi.ts b/src/openapi/apis/DefaultApi.ts index 74459d9de3..7535b0a077 100644 --- a/src/openapi/apis/DefaultApi.ts +++ b/src/openapi/apis/DefaultApi.ts @@ -17,10 +17,11 @@ import * as runtime from '../runtime'; import type { AccentPhrase, AudioQuery, - DownloadableLibrary, + CorsPolicyMode, + DownloadableLibraryInfo, EngineManifest, HTTPValidationError, - InstalledLibrary, + InstalledLibraryInfo, MorphableTargetInfo, ParseKanaBadRequest, Preset, @@ -35,14 +36,16 @@ import { AccentPhraseToJSON, AudioQueryFromJSON, AudioQueryToJSON, - DownloadableLibraryFromJSON, - DownloadableLibraryToJSON, + CorsPolicyModeFromJSON, + CorsPolicyModeToJSON, + DownloadableLibraryInfoFromJSON, + DownloadableLibraryInfoToJSON, EngineManifestFromJSON, EngineManifestToJSON, HTTPValidationErrorFromJSON, HTTPValidationErrorToJSON, - InstalledLibraryFromJSON, - InstalledLibraryToJSON, + InstalledLibraryInfoFromJSON, + InstalledLibraryInfoToJSON, MorphableTargetInfoFromJSON, MorphableTargetInfoToJSON, ParseKanaBadRequestFromJSON, @@ -63,7 +66,8 @@ import { export interface AccentPhrasesAccentPhrasesPostRequest { text: string; - speaker: number; + styleId?: number; + speaker?: number; isKana?: boolean; coreVersion?: string; } @@ -82,7 +86,8 @@ export interface AddUserDictWordUserDictWordPostRequest { export interface AudioQueryAudioQueryPostRequest { text: string; - speaker: number; + styleId?: number; + speaker?: number; coreVersion?: string; } @@ -93,8 +98,9 @@ export interface AudioQueryFromPresetAudioQueryFromPresetPostRequest { } export interface CancellableSynthesisCancellableSynthesisPostRequest { - speaker: number; audioQuery: AudioQuery; + styleId?: number; + speaker?: number; coreVersion?: string; } @@ -112,7 +118,7 @@ export interface DeleteUserDictWordUserDictWordWordUuidDeleteRequest { export interface ImportUserDictWordsImportUserDictPostRequest { override: boolean; - requestBody: { [key: string]: UserDictWord; }; + requestBody: { [key: string]: UserDictWord; } | null; } export interface InitializeSpeakerInitializeSpeakerPostRequest { @@ -121,6 +127,12 @@ export interface InitializeSpeakerInitializeSpeakerPostRequest { coreVersion?: string; } +export interface InitializeStyleIdInitializeStyleIdPostRequest { + styleId: number; + skipReinit?: boolean; + coreVersion?: string; +} + export interface InstallLibraryInstallLibraryLibraryUuidPostRequest { libraryUuid: string; } @@ -130,21 +142,29 @@ export interface IsInitializedSpeakerIsInitializedSpeakerGetRequest { coreVersion?: string; } +export interface IsInitializedStyleIdIsInitializedStyleIdGetRequest { + styleId: number; + coreVersion?: string; +} + export interface MoraDataMoraDataPostRequest { - speaker: number; accentPhrase: Array; + styleId?: number; + speaker?: number; coreVersion?: string; } export interface MoraLengthMoraLengthPostRequest { - speaker: number; accentPhrase: Array; + styleId?: number; + speaker?: number; coreVersion?: string; } export interface MoraPitchMoraPitchPostRequest { - speaker: number; accentPhrase: Array; + styleId?: number; + speaker?: number; coreVersion?: string; } @@ -154,8 +174,9 @@ export interface MorphableTargetsMorphableTargetsPostRequest { } export interface MultiSynthesisMultiSynthesisPostRequest { - speaker: number; audioQuery: Array; + styleId?: number; + speaker?: number; coreVersion?: string; } @@ -169,7 +190,7 @@ export interface RewriteUserDictWordUserDictWordWordUuidPutRequest { } export interface SettingPostSettingPostRequest { - corsPolicyMode?: string; + corsPolicyMode: CorsPolicyMode; allowOrigin?: string; } @@ -187,16 +208,19 @@ export interface SupportedDevicesSupportedDevicesGetRequest { } export interface SynthesisMorphingSynthesisMorphingPostRequest { - baseSpeaker: number; - targetSpeaker: number; morphRate: number; audioQuery: AudioQuery; + baseStyleId?: number; + baseSpeaker?: number; + targetStyleId?: number; + targetSpeaker?: number; coreVersion?: string; } export interface SynthesisSynthesisPostRequest { - speaker: number; audioQuery: AudioQuery; + styleId?: number; + speaker?: number; enableInterrogativeUpspeak?: boolean; coreVersion?: string; } @@ -221,10 +245,11 @@ export interface ValidateKanaValidateKanaPostRequest { */ export interface DefaultApiInterface { /** - * テキストからアクセント句を得ます。 is_kanaが`true`のとき、テキストは次のようなAquesTalkライクな記法に従う読み仮名として処理されます。デフォルトは`false`です。 * 全てのカナはカタカナで記述される * アクセント句は`/`または`、`で区切る。`、`で区切った場合に限り無音区間が挿入される。 * カナの手前に`_`を入れるとそのカナは無声化される * アクセント位置を`\'`で指定する。全てのアクセント句にはアクセント位置を1つ指定する必要がある。 * アクセント句末に`?`(全角)を入れることにより疑問文の発音ができる。 + * テキストからアクセント句を得ます。 is_kanaが`true`のとき、テキストは次のAquesTalk 風記法で解釈されます。デフォルトは`false`です。 * 全てのカナはカタカナで記述される * アクセント句は`/`または`、`で区切る。`、`で区切った場合に限り無音区間が挿入される。 * カナの手前に`_`を入れるとそのカナは無声化される * アクセント位置を`\'`で指定する。全てのアクセント句にはアクセント位置を1つ指定する必要がある。 * アクセント句末に`?`(全角)を入れることにより疑問文の発音ができる。 * @summary テキストからアクセント句を得る * @param {string} text - * @param {number} speaker + * @param {number} [styleId] + * @param {number} [speaker] * @param {boolean} [isKana] * @param {string} [coreVersion] * @param {*} [options] Override http request option. @@ -234,7 +259,7 @@ export interface DefaultApiInterface { accentPhrasesAccentPhrasesPostRaw(requestParameters: AccentPhrasesAccentPhrasesPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>>; /** - * テキストからアクセント句を得ます。 is_kanaが`true`のとき、テキストは次のようなAquesTalkライクな記法に従う読み仮名として処理されます。デフォルトは`false`です。 * 全てのカナはカタカナで記述される * アクセント句は`/`または`、`で区切る。`、`で区切った場合に限り無音区間が挿入される。 * カナの手前に`_`を入れるとそのカナは無声化される * アクセント位置を`\'`で指定する。全てのアクセント句にはアクセント位置を1つ指定する必要がある。 * アクセント句末に`?`(全角)を入れることにより疑問文の発音ができる。 + * テキストからアクセント句を得ます。 is_kanaが`true`のとき、テキストは次のAquesTalk 風記法で解釈されます。デフォルトは`false`です。 * 全てのカナはカタカナで記述される * アクセント句は`/`または`、`で区切る。`、`で区切った場合に限り無音区間が挿入される。 * カナの手前に`_`を入れるとそのカナは無声化される * アクセント位置を`\'`で指定する。全てのアクセント句にはアクセント位置を1つ指定する必要がある。 * アクセント句末に`?`(全角)を入れることにより疑問文の発音ができる。 * テキストからアクセント句を得る */ accentPhrasesAccentPhrasesPost(requestParameters: AccentPhrasesAccentPhrasesPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; @@ -276,10 +301,11 @@ export interface DefaultApiInterface { addUserDictWordUserDictWordPost(requestParameters: AddUserDictWordUserDictWordPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; /** - * クエリの初期値を得ます。ここで得られたクエリはそのまま音声合成に利用できます。各値の意味は`Schemas`を参照してください。 + * 音声合成用のクエリの初期値を得ます。ここで得られたクエリはそのまま音声合成に利用できます。各値の意味は`Schemas`を参照してください。 * @summary 音声合成用のクエリを作成する * @param {string} text - * @param {number} speaker + * @param {number} [styleId] + * @param {number} [speaker] * @param {string} [coreVersion] * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -288,13 +314,13 @@ export interface DefaultApiInterface { audioQueryAudioQueryPostRaw(requestParameters: AudioQueryAudioQueryPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; /** - * クエリの初期値を得ます。ここで得られたクエリはそのまま音声合成に利用できます。各値の意味は`Schemas`を参照してください。 + * 音声合成用のクエリの初期値を得ます。ここで得られたクエリはそのまま音声合成に利用できます。各値の意味は`Schemas`を参照してください。 * 音声合成用のクエリを作成する */ audioQueryAudioQueryPost(requestParameters: AudioQueryAudioQueryPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; /** - * クエリの初期値を得ます。ここで得られたクエリはそのまま音声合成に利用できます。各値の意味は`Schemas`を参照してください。 + * 音声合成用のクエリの初期値を得ます。ここで得られたクエリはそのまま音声合成に利用できます。各値の意味は`Schemas`を参照してください。 * @summary 音声合成用のクエリをプリセットを用いて作成する * @param {string} text * @param {number} presetId @@ -306,7 +332,7 @@ export interface DefaultApiInterface { audioQueryFromPresetAudioQueryFromPresetPostRaw(requestParameters: AudioQueryFromPresetAudioQueryFromPresetPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; /** - * クエリの初期値を得ます。ここで得られたクエリはそのまま音声合成に利用できます。各値の意味は`Schemas`を参照してください。 + * 音声合成用のクエリの初期値を得ます。ここで得られたクエリはそのまま音声合成に利用できます。各値の意味は`Schemas`を参照してください。 * 音声合成用のクエリをプリセットを用いて作成する */ audioQueryFromPresetAudioQueryFromPresetPost(requestParameters: AudioQueryFromPresetAudioQueryFromPresetPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; @@ -314,8 +340,9 @@ export interface DefaultApiInterface { /** * * @summary 音声合成する(キャンセル可能) - * @param {number} speaker * @param {AudioQuery} audioQuery + * @param {number} [styleId] + * @param {number} [speaker] * @param {string} [coreVersion] * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -391,19 +418,19 @@ export interface DefaultApiInterface { deleteUserDictWordUserDictWordWordUuidDelete(requestParameters: DeleteUserDictWordUserDictWordWordUuidDeleteRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; /** - * ダウンロード可能な音声ライブラリの情報を返します。 Returns ------- ret_data: List[DownloadableLibrary] + * ダウンロード可能な音声ライブラリの情報を返します。 Returns ------- ret_data: list[DownloadableLibrary] * @summary Downloadable Libraries * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof DefaultApiInterface */ - downloadableLibrariesDownloadableLibrariesGetRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>>; + downloadableLibrariesDownloadableLibrariesGetRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>>; /** - * ダウンロード可能な音声ライブラリの情報を返します。 Returns ------- ret_data: List[DownloadableLibrary] + * ダウンロード可能な音声ライブラリの情報を返します。 Returns ------- ret_data: list[DownloadableLibrary] * Downloadable Libraries */ - downloadableLibrariesDownloadableLibrariesGet(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; + downloadableLibrariesDownloadableLibrariesGet(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; /** * @@ -420,7 +447,7 @@ export interface DefaultApiInterface { engineManifestEngineManifestGet(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; /** - * エンジンが保持しているプリセットの設定を返します Returns ------- presets: List[Preset] プリセットのリスト + * エンジンが保持しているプリセットの設定を返します Returns ------- presets: list[Preset] プリセットのリスト * @summary Get Presets * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -429,13 +456,13 @@ export interface DefaultApiInterface { getPresetsPresetsGetRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>>; /** - * エンジンが保持しているプリセットの設定を返します Returns ------- presets: List[Preset] プリセットのリスト + * エンジンが保持しているプリセットの設定を返します Returns ------- presets: list[Preset] プリセットのリスト * Get Presets */ getPresetsPresetsGet(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; /** - * ユーザー辞書に登録されている単語の一覧を返します。 単語の表層形(surface)は正規化済みの物を返します。 Returns ------- Dict[str, UserDictWord] 単語のUUIDとその詳細 + * ユーザー辞書に登録されている単語の一覧を返します。 単語の表層形(surface)は正規化済みの物を返します。 Returns ------- dict[str, UserDictWord] 単語のUUIDとその詳細 * @summary Get User Dict Words * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -444,13 +471,13 @@ export interface DefaultApiInterface { getUserDictWordsUserDictGetRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; /** - * ユーザー辞書に登録されている単語の一覧を返します。 単語の表層形(surface)は正規化済みの物を返します。 Returns ------- Dict[str, UserDictWord] 単語のUUIDとその詳細 + * ユーザー辞書に登録されている単語の一覧を返します。 単語の表層形(surface)は正規化済みの物を返します。 Returns ------- dict[str, UserDictWord] 単語のUUIDとその詳細 * Get User Dict Words */ getUserDictWordsUserDictGet(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<{ [key: string]: UserDictWord; }>; /** - * 他のユーザー辞書をインポートします。 Parameters ---------- import_dict_data: Dict[str, UserDictWord] インポートするユーザー辞書のデータ override: bool 重複したエントリがあった場合、上書きするかどうか + * 他のユーザー辞書をインポートします。 Parameters ---------- import_dict_data: dict[str, UserDictWord] インポートするユーザー辞書のデータ override: bool 重複したエントリがあった場合、上書きするかどうか * @summary Import User Dict Words * @param {boolean} override * @param {{ [key: string]: UserDictWord; }} requestBody @@ -461,29 +488,49 @@ export interface DefaultApiInterface { importUserDictWordsImportUserDictPostRaw(requestParameters: ImportUserDictWordsImportUserDictPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; /** - * 他のユーザー辞書をインポートします。 Parameters ---------- import_dict_data: Dict[str, UserDictWord] インポートするユーザー辞書のデータ override: bool 重複したエントリがあった場合、上書きするかどうか + * 他のユーザー辞書をインポートします。 Parameters ---------- import_dict_data: dict[str, UserDictWord] インポートするユーザー辞書のデータ override: bool 重複したエントリがあった場合、上書きするかどうか * Import User Dict Words */ importUserDictWordsImportUserDictPost(requestParameters: ImportUserDictWordsImportUserDictPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; /** - * 指定されたspeaker_idの話者を初期化します。 実行しなくても他のAPIは使用できますが、初回実行時に時間がかかることがあります。 + * こちらのAPIは非推奨です。`initialize_style_id`を利用してください。 * @summary Initialize Speaker * @param {number} speaker * @param {boolean} [skipReinit] 既に初期化済みの話者の再初期化をスキップするかどうか * @param {string} [coreVersion] * @param {*} [options] Override http request option. + * @deprecated * @throws {RequiredError} * @memberof DefaultApiInterface */ initializeSpeakerInitializeSpeakerPostRaw(requestParameters: InitializeSpeakerInitializeSpeakerPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; /** - * 指定されたspeaker_idの話者を初期化します。 実行しなくても他のAPIは使用できますが、初回実行時に時間がかかることがあります。 + * こちらのAPIは非推奨です。`initialize_style_id`を利用してください。 * Initialize Speaker + * @deprecated */ initializeSpeakerInitializeSpeakerPost(requestParameters: InitializeSpeakerInitializeSpeakerPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; + /** + * 指定されたstyle_idのスタイルを初期化します。 実行しなくても他のAPIは使用できますが、初回実行時に時間がかかることがあります。 + * @summary Initialize Style Id + * @param {number} styleId + * @param {boolean} [skipReinit] 既に初期化済みのスタイルの再初期化をスキップするかどうか + * @param {string} [coreVersion] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApiInterface + */ + initializeStyleIdInitializeStyleIdPostRaw(requestParameters: InitializeStyleIdInitializeStyleIdPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; + + /** + * 指定されたstyle_idのスタイルを初期化します。 実行しなくても他のAPIは使用できますが、初回実行時に時間がかかることがあります。 + * Initialize Style Id + */ + initializeStyleIdInitializeStyleIdPost(requestParameters: InitializeStyleIdInitializeStyleIdPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; + /** * 音声ライブラリをインストールします。 音声ライブラリのZIPファイルをリクエストボディとして送信してください。 Parameters ---------- library_uuid: str 音声ライブラリのID * @summary Install Library @@ -501,42 +548,62 @@ export interface DefaultApiInterface { installLibraryInstallLibraryLibraryUuidPost(requestParameters: InstallLibraryInstallLibraryLibraryUuidPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; /** - * インストールした音声ライブラリの情報を返します。 Returns ------- ret_data: List[DownloadableLibrary] + * インストールした音声ライブラリの情報を返します。 Returns ------- ret_data: dict[str, InstalledLibrary] * @summary Installed Libraries * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof DefaultApiInterface */ - installedLibrariesInstalledLibrariesGetRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; + installedLibrariesInstalledLibrariesGetRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; /** - * インストールした音声ライブラリの情報を返します。 Returns ------- ret_data: List[DownloadableLibrary] + * インストールした音声ライブラリの情報を返します。 Returns ------- ret_data: dict[str, InstalledLibrary] * Installed Libraries */ - installedLibrariesInstalledLibrariesGet(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<{ [key: string]: InstalledLibrary; }>; + installedLibrariesInstalledLibrariesGet(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<{ [key: string]: InstalledLibraryInfo; }>; /** - * 指定されたspeaker_idの話者が初期化されているかどうかを返します。 + * こちらのAPIは非推奨です。`is_initialize_style_id`を利用してください。 * @summary Is Initialized Speaker * @param {number} speaker * @param {string} [coreVersion] * @param {*} [options] Override http request option. + * @deprecated * @throws {RequiredError} * @memberof DefaultApiInterface */ isInitializedSpeakerIsInitializedSpeakerGetRaw(requestParameters: IsInitializedSpeakerIsInitializedSpeakerGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; /** - * 指定されたspeaker_idの話者が初期化されているかどうかを返します。 + * こちらのAPIは非推奨です。`is_initialize_style_id`を利用してください。 * Is Initialized Speaker + * @deprecated */ isInitializedSpeakerIsInitializedSpeakerGet(requestParameters: IsInitializedSpeakerIsInitializedSpeakerGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; + /** + * 指定されたstyle_idのスタイルが初期化されているかどうかを返します。 + * @summary Is Initialized Style Id + * @param {number} styleId + * @param {string} [coreVersion] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApiInterface + */ + isInitializedStyleIdIsInitializedStyleIdGetRaw(requestParameters: IsInitializedStyleIdIsInitializedStyleIdGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; + + /** + * 指定されたstyle_idのスタイルが初期化されているかどうかを返します。 + * Is Initialized Style Id + */ + isInitializedStyleIdIsInitializedStyleIdGet(requestParameters: IsInitializedStyleIdIsInitializedStyleIdGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; + /** * * @summary アクセント句から音高・音素長を得る - * @param {number} speaker * @param {Array} accentPhrase + * @param {number} [styleId] + * @param {number} [speaker] * @param {string} [coreVersion] * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -552,8 +619,9 @@ export interface DefaultApiInterface { /** * * @summary アクセント句から音素長を得る - * @param {number} speaker * @param {Array} accentPhrase + * @param {number} [styleId] + * @param {number} [speaker] * @param {string} [coreVersion] * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -569,8 +637,9 @@ export interface DefaultApiInterface { /** * * @summary アクセント句から音高を得る - * @param {number} speaker * @param {Array} accentPhrase + * @param {number} [styleId] + * @param {number} [speaker] * @param {string} [coreVersion] * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -584,8 +653,8 @@ export interface DefaultApiInterface { moraPitchMoraPitchPost(requestParameters: MoraPitchMoraPitchPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; /** - * 指定されたベース話者に対してエンジン内の各話者がモーフィング機能を利用可能か返します。 モーフィングの許可/禁止は`/speakers`の`speaker.supported_features.synthesis_morphing`に記載されています。 プロパティが存在しない場合は、モーフィングが許可されているとみなします。 返り値の話者はstring型なので注意。 - * @summary 指定した話者に対してエンジン内の話者がモーフィングが可能か判定する + * 指定されたベーススタイルに対してエンジン内の各話者がモーフィング機能を利用可能か返します。 モーフィングの許可/禁止は`/speakers`の`speaker.supported_features.synthesis_morphing`に記載されています。 プロパティが存在しない場合は、モーフィングが許可されているとみなします。 返り値の話者はstring型なので注意。 + * @summary 指定したスタイルに対してエンジン内の話者がモーフィングが可能か判定する * @param {Array} requestBody * @param {string} [coreVersion] * @param {*} [options] Override http request option. @@ -595,16 +664,17 @@ export interface DefaultApiInterface { morphableTargetsMorphableTargetsPostRaw(requestParameters: MorphableTargetsMorphableTargetsPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>>; /** - * 指定されたベース話者に対してエンジン内の各話者がモーフィング機能を利用可能か返します。 モーフィングの許可/禁止は`/speakers`の`speaker.supported_features.synthesis_morphing`に記載されています。 プロパティが存在しない場合は、モーフィングが許可されているとみなします。 返り値の話者はstring型なので注意。 - * 指定した話者に対してエンジン内の話者がモーフィングが可能か判定する + * 指定されたベーススタイルに対してエンジン内の各話者がモーフィング機能を利用可能か返します。 モーフィングの許可/禁止は`/speakers`の`speaker.supported_features.synthesis_morphing`に記載されています。 プロパティが存在しない場合は、モーフィングが許可されているとみなします。 返り値の話者はstring型なので注意。 + * 指定したスタイルに対してエンジン内の話者がモーフィングが可能か判定する */ morphableTargetsMorphableTargetsPost(requestParameters: MorphableTargetsMorphableTargetsPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; /** * * @summary 複数まとめて音声合成する - * @param {number} speaker * @param {Array} audioQuery + * @param {number} [styleId] + * @param {number} [speaker] * @param {string} [coreVersion] * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -639,34 +709,36 @@ export interface DefaultApiInterface { rewriteUserDictWordUserDictWordWordUuidPut(requestParameters: RewriteUserDictWordUserDictWordWordUuidPutRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; /** - * + * 設定ページを返します。 * @summary Setting Get * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof DefaultApiInterface */ - settingGetSettingGetRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; + settingGetSettingGetRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; /** + * 設定ページを返します。 * Setting Get */ - settingGetSettingGet(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; + settingGetSettingGet(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; /** - * + * 設定を更新します。 * @summary Setting Post - * @param {string} [corsPolicyMode] + * @param {CorsPolicyMode} corsPolicyMode * @param {string} [allowOrigin] * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof DefaultApiInterface */ - settingPostSettingPostRaw(requestParameters: SettingPostSettingPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; + settingPostSettingPostRaw(requestParameters: SettingPostSettingPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; /** + * 設定を更新します。 * Setting Post */ - settingPostSettingPost(requestParameters: SettingPostSettingPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; + settingPostSettingPost(requestParameters: SettingPostSettingPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; /** * 指定されたspeaker_uuidに関する情報をjson形式で返します。 画像や音声はbase64エンコードされたものが返されます。 Returns ------- ret_data: SpeakerInfo @@ -716,12 +788,14 @@ export interface DefaultApiInterface { supportedDevicesSupportedDevicesGet(requestParameters: SupportedDevicesSupportedDevicesGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; /** - * 指定された2人の話者で音声を合成、指定した割合でモーフィングした音声を得ます。 モーフィングの割合は`morph_rate`で指定でき、0.0でベースの話者、1.0でターゲットの話者に近づきます。 - * @summary 2人の話者でモーフィングした音声を合成する - * @param {number} baseSpeaker - * @param {number} targetSpeaker + * 指定された2種類のスタイルで音声を合成、指定した割合でモーフィングした音声を得ます。 モーフィングの割合は`morph_rate`で指定でき、0.0でベースのスタイル、1.0でターゲットのスタイルに近づきます。 + * @summary 2種類のスタイルでモーフィングした音声を合成する * @param {number} morphRate * @param {AudioQuery} audioQuery + * @param {number} [baseStyleId] + * @param {number} [baseSpeaker] + * @param {number} [targetStyleId] + * @param {number} [targetSpeaker] * @param {string} [coreVersion] * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -730,16 +804,17 @@ export interface DefaultApiInterface { synthesisMorphingSynthesisMorphingPostRaw(requestParameters: SynthesisMorphingSynthesisMorphingPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; /** - * 指定された2人の話者で音声を合成、指定した割合でモーフィングした音声を得ます。 モーフィングの割合は`morph_rate`で指定でき、0.0でベースの話者、1.0でターゲットの話者に近づきます。 - * 2人の話者でモーフィングした音声を合成する + * 指定された2種類のスタイルで音声を合成、指定した割合でモーフィングした音声を得ます。 モーフィングの割合は`morph_rate`で指定でき、0.0でベースのスタイル、1.0でターゲットのスタイルに近づきます。 + * 2種類のスタイルでモーフィングした音声を合成する */ synthesisMorphingSynthesisMorphingPost(requestParameters: SynthesisMorphingSynthesisMorphingPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; /** * * @summary 音声合成する - * @param {number} speaker * @param {AudioQuery} audioQuery + * @param {number} [styleId] + * @param {number} [speaker] * @param {boolean} [enableInterrogativeUpspeak] 疑問系のテキストが与えられたら語尾を自動調整する * @param {string} [coreVersion] * @param {*} [options] Override http request option. @@ -786,8 +861,8 @@ export interface DefaultApiInterface { updatePresetUpdatePresetPost(requestParameters: UpdatePresetUpdatePresetPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; /** - * テキストがAquesTalkライクな記法に従っているかどうかを判定します。 従っていない場合はエラーが返ります。 Parameters ---------- text: str 判定する対象の文字列 - * @summary テキストがAquesTalkライクな記法に従っているか判定する + * テキストがAquesTalk 風記法に従っているかどうかを判定します。 従っていない場合はエラーが返ります。 Parameters ---------- text: str 判定する対象の文字列 + * @summary テキストがAquesTalk 風記法に従っているか判定する * @param {string} text * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -796,8 +871,8 @@ export interface DefaultApiInterface { validateKanaValidateKanaPostRaw(requestParameters: ValidateKanaValidateKanaPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; /** - * テキストがAquesTalkライクな記法に従っているかどうかを判定します。 従っていない場合はエラーが返ります。 Parameters ---------- text: str 判定する対象の文字列 - * テキストがAquesTalkライクな記法に従っているか判定する + * テキストがAquesTalk 風記法に従っているかどうかを判定します。 従っていない場合はエラーが返ります。 Parameters ---------- text: str 判定する対象の文字列 + * テキストがAquesTalk 風記法に従っているか判定する */ validateKanaValidateKanaPost(requestParameters: ValidateKanaValidateKanaPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; @@ -808,12 +883,12 @@ export interface DefaultApiInterface { * @throws {RequiredError} * @memberof DefaultApiInterface */ - versionVersionGetRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; + versionVersionGetRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; /** * Version */ - versionVersionGet(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; + versionVersionGet(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; } @@ -823,7 +898,7 @@ export interface DefaultApiInterface { export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { /** - * テキストからアクセント句を得ます。 is_kanaが`true`のとき、テキストは次のようなAquesTalkライクな記法に従う読み仮名として処理されます。デフォルトは`false`です。 * 全てのカナはカタカナで記述される * アクセント句は`/`または`、`で区切る。`、`で区切った場合に限り無音区間が挿入される。 * カナの手前に`_`を入れるとそのカナは無声化される * アクセント位置を`\'`で指定する。全てのアクセント句にはアクセント位置を1つ指定する必要がある。 * アクセント句末に`?`(全角)を入れることにより疑問文の発音ができる。 + * テキストからアクセント句を得ます。 is_kanaが`true`のとき、テキストは次のAquesTalk 風記法で解釈されます。デフォルトは`false`です。 * 全てのカナはカタカナで記述される * アクセント句は`/`または`、`で区切る。`、`で区切った場合に限り無音区間が挿入される。 * カナの手前に`_`を入れるとそのカナは無声化される * アクセント位置を`\'`で指定する。全てのアクセント句にはアクセント位置を1つ指定する必要がある。 * アクセント句末に`?`(全角)を入れることにより疑問文の発音ができる。 * テキストからアクセント句を得る */ async accentPhrasesAccentPhrasesPostRaw(requestParameters: AccentPhrasesAccentPhrasesPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>> { @@ -831,16 +906,16 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { throw new runtime.RequiredError('text','Required parameter requestParameters.text was null or undefined when calling accentPhrasesAccentPhrasesPost.'); } - if (requestParameters.speaker === null || requestParameters.speaker === undefined) { - throw new runtime.RequiredError('speaker','Required parameter requestParameters.speaker was null or undefined when calling accentPhrasesAccentPhrasesPost.'); - } - const queryParameters: any = {}; if (requestParameters.text !== undefined) { queryParameters['text'] = requestParameters.text; } + if (requestParameters.styleId !== undefined) { + queryParameters['style_id'] = requestParameters.styleId; + } + if (requestParameters.speaker !== undefined) { queryParameters['speaker'] = requestParameters.speaker; } @@ -866,7 +941,7 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { } /** - * テキストからアクセント句を得ます。 is_kanaが`true`のとき、テキストは次のようなAquesTalkライクな記法に従う読み仮名として処理されます。デフォルトは`false`です。 * 全てのカナはカタカナで記述される * アクセント句は`/`または`、`で区切る。`、`で区切った場合に限り無音区間が挿入される。 * カナの手前に`_`を入れるとそのカナは無声化される * アクセント位置を`\'`で指定する。全てのアクセント句にはアクセント位置を1つ指定する必要がある。 * アクセント句末に`?`(全角)を入れることにより疑問文の発音ができる。 + * テキストからアクセント句を得ます。 is_kanaが`true`のとき、テキストは次のAquesTalk 風記法で解釈されます。デフォルトは`false`です。 * 全てのカナはカタカナで記述される * アクセント句は`/`または`、`で区切る。`、`で区切った場合に限り無音区間が挿入される。 * カナの手前に`_`を入れるとそのカナは無声化される * アクセント位置を`\'`で指定する。全てのアクセント句にはアクセント位置を1つ指定する必要がある。 * アクセント句末に`?`(全角)を入れることにより疑問文の発音ができる。 * テキストからアクセント句を得る */ async accentPhrasesAccentPhrasesPost(requestParameters: AccentPhrasesAccentPhrasesPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { @@ -978,7 +1053,7 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { } /** - * クエリの初期値を得ます。ここで得られたクエリはそのまま音声合成に利用できます。各値の意味は`Schemas`を参照してください。 + * 音声合成用のクエリの初期値を得ます。ここで得られたクエリはそのまま音声合成に利用できます。各値の意味は`Schemas`を参照してください。 * 音声合成用のクエリを作成する */ async audioQueryAudioQueryPostRaw(requestParameters: AudioQueryAudioQueryPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { @@ -986,16 +1061,16 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { throw new runtime.RequiredError('text','Required parameter requestParameters.text was null or undefined when calling audioQueryAudioQueryPost.'); } - if (requestParameters.speaker === null || requestParameters.speaker === undefined) { - throw new runtime.RequiredError('speaker','Required parameter requestParameters.speaker was null or undefined when calling audioQueryAudioQueryPost.'); - } - const queryParameters: any = {}; if (requestParameters.text !== undefined) { queryParameters['text'] = requestParameters.text; } + if (requestParameters.styleId !== undefined) { + queryParameters['style_id'] = requestParameters.styleId; + } + if (requestParameters.speaker !== undefined) { queryParameters['speaker'] = requestParameters.speaker; } @@ -1017,7 +1092,7 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { } /** - * クエリの初期値を得ます。ここで得られたクエリはそのまま音声合成に利用できます。各値の意味は`Schemas`を参照してください。 + * 音声合成用のクエリの初期値を得ます。ここで得られたクエリはそのまま音声合成に利用できます。各値の意味は`Schemas`を参照してください。 * 音声合成用のクエリを作成する */ async audioQueryAudioQueryPost(requestParameters: AudioQueryAudioQueryPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { @@ -1026,7 +1101,7 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { } /** - * クエリの初期値を得ます。ここで得られたクエリはそのまま音声合成に利用できます。各値の意味は`Schemas`を参照してください。 + * 音声合成用のクエリの初期値を得ます。ここで得られたクエリはそのまま音声合成に利用できます。各値の意味は`Schemas`を参照してください。 * 音声合成用のクエリをプリセットを用いて作成する */ async audioQueryFromPresetAudioQueryFromPresetPostRaw(requestParameters: AudioQueryFromPresetAudioQueryFromPresetPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { @@ -1065,7 +1140,7 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { } /** - * クエリの初期値を得ます。ここで得られたクエリはそのまま音声合成に利用できます。各値の意味は`Schemas`を参照してください。 + * 音声合成用のクエリの初期値を得ます。ここで得られたクエリはそのまま音声合成に利用できます。各値の意味は`Schemas`を参照してください。 * 音声合成用のクエリをプリセットを用いて作成する */ async audioQueryFromPresetAudioQueryFromPresetPost(requestParameters: AudioQueryFromPresetAudioQueryFromPresetPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { @@ -1077,16 +1152,16 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { * 音声合成する(キャンセル可能) */ async cancellableSynthesisCancellableSynthesisPostRaw(requestParameters: CancellableSynthesisCancellableSynthesisPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - if (requestParameters.speaker === null || requestParameters.speaker === undefined) { - throw new runtime.RequiredError('speaker','Required parameter requestParameters.speaker was null or undefined when calling cancellableSynthesisCancellableSynthesisPost.'); - } - if (requestParameters.audioQuery === null || requestParameters.audioQuery === undefined) { throw new runtime.RequiredError('audioQuery','Required parameter requestParameters.audioQuery was null or undefined when calling cancellableSynthesisCancellableSynthesisPost.'); } const queryParameters: any = {}; + if (requestParameters.styleId !== undefined) { + queryParameters['style_id'] = requestParameters.styleId; + } + if (requestParameters.speaker !== undefined) { queryParameters['speaker'] = requestParameters.speaker; } @@ -1246,10 +1321,10 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { } /** - * ダウンロード可能な音声ライブラリの情報を返します。 Returns ------- ret_data: List[DownloadableLibrary] + * ダウンロード可能な音声ライブラリの情報を返します。 Returns ------- ret_data: list[DownloadableLibrary] * Downloadable Libraries */ - async downloadableLibrariesDownloadableLibrariesGetRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>> { + async downloadableLibrariesDownloadableLibrariesGetRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>> { const queryParameters: any = {}; const headerParameters: runtime.HTTPHeaders = {}; @@ -1261,14 +1336,14 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { query: queryParameters, }, initOverrides); - return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(DownloadableLibraryFromJSON)); + return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(DownloadableLibraryInfoFromJSON)); } /** - * ダウンロード可能な音声ライブラリの情報を返します。 Returns ------- ret_data: List[DownloadableLibrary] + * ダウンロード可能な音声ライブラリの情報を返します。 Returns ------- ret_data: list[DownloadableLibrary] * Downloadable Libraries */ - async downloadableLibrariesDownloadableLibrariesGet(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + async downloadableLibrariesDownloadableLibrariesGet(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { const response = await this.downloadableLibrariesDownloadableLibrariesGetRaw(initOverrides); return await response.value(); } @@ -1300,7 +1375,7 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { } /** - * エンジンが保持しているプリセットの設定を返します Returns ------- presets: List[Preset] プリセットのリスト + * エンジンが保持しているプリセットの設定を返します Returns ------- presets: list[Preset] プリセットのリスト * Get Presets */ async getPresetsPresetsGetRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>> { @@ -1319,7 +1394,7 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { } /** - * エンジンが保持しているプリセットの設定を返します Returns ------- presets: List[Preset] プリセットのリスト + * エンジンが保持しているプリセットの設定を返します Returns ------- presets: list[Preset] プリセットのリスト * Get Presets */ async getPresetsPresetsGet(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { @@ -1328,7 +1403,7 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { } /** - * ユーザー辞書に登録されている単語の一覧を返します。 単語の表層形(surface)は正規化済みの物を返します。 Returns ------- Dict[str, UserDictWord] 単語のUUIDとその詳細 + * ユーザー辞書に登録されている単語の一覧を返します。 単語の表層形(surface)は正規化済みの物を返します。 Returns ------- dict[str, UserDictWord] 単語のUUIDとその詳細 * Get User Dict Words */ async getUserDictWordsUserDictGetRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { @@ -1347,7 +1422,7 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { } /** - * ユーザー辞書に登録されている単語の一覧を返します。 単語の表層形(surface)は正規化済みの物を返します。 Returns ------- Dict[str, UserDictWord] 単語のUUIDとその詳細 + * ユーザー辞書に登録されている単語の一覧を返します。 単語の表層形(surface)は正規化済みの物を返します。 Returns ------- dict[str, UserDictWord] 単語のUUIDとその詳細 * Get User Dict Words */ async getUserDictWordsUserDictGet(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<{ [key: string]: UserDictWord; }> { @@ -1356,7 +1431,7 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { } /** - * 他のユーザー辞書をインポートします。 Parameters ---------- import_dict_data: Dict[str, UserDictWord] インポートするユーザー辞書のデータ override: bool 重複したエントリがあった場合、上書きするかどうか + * 他のユーザー辞書をインポートします。 Parameters ---------- import_dict_data: dict[str, UserDictWord] インポートするユーザー辞書のデータ override: bool 重複したエントリがあった場合、上書きするかどうか * Import User Dict Words */ async importUserDictWordsImportUserDictPostRaw(requestParameters: ImportUserDictWordsImportUserDictPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { @@ -1390,7 +1465,7 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { } /** - * 他のユーザー辞書をインポートします。 Parameters ---------- import_dict_data: Dict[str, UserDictWord] インポートするユーザー辞書のデータ override: bool 重複したエントリがあった場合、上書きするかどうか + * 他のユーザー辞書をインポートします。 Parameters ---------- import_dict_data: dict[str, UserDictWord] インポートするユーザー辞書のデータ override: bool 重複したエントリがあった場合、上書きするかどうか * Import User Dict Words */ async importUserDictWordsImportUserDictPost(requestParameters: ImportUserDictWordsImportUserDictPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { @@ -1398,8 +1473,9 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { } /** - * 指定されたspeaker_idの話者を初期化します。 実行しなくても他のAPIは使用できますが、初回実行時に時間がかかることがあります。 + * こちらのAPIは非推奨です。`initialize_style_id`を利用してください。 * Initialize Speaker + * @deprecated */ async initializeSpeakerInitializeSpeakerPostRaw(requestParameters: InitializeSpeakerInitializeSpeakerPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { if (requestParameters.speaker === null || requestParameters.speaker === undefined) { @@ -1433,13 +1509,57 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { } /** - * 指定されたspeaker_idの話者を初期化します。 実行しなくても他のAPIは使用できますが、初回実行時に時間がかかることがあります。 + * こちらのAPIは非推奨です。`initialize_style_id`を利用してください。 * Initialize Speaker + * @deprecated */ async initializeSpeakerInitializeSpeakerPost(requestParameters: InitializeSpeakerInitializeSpeakerPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { await this.initializeSpeakerInitializeSpeakerPostRaw(requestParameters, initOverrides); } + /** + * 指定されたstyle_idのスタイルを初期化します。 実行しなくても他のAPIは使用できますが、初回実行時に時間がかかることがあります。 + * Initialize Style Id + */ + async initializeStyleIdInitializeStyleIdPostRaw(requestParameters: InitializeStyleIdInitializeStyleIdPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters.styleId === null || requestParameters.styleId === undefined) { + throw new runtime.RequiredError('styleId','Required parameter requestParameters.styleId was null or undefined when calling initializeStyleIdInitializeStyleIdPost.'); + } + + const queryParameters: any = {}; + + if (requestParameters.styleId !== undefined) { + queryParameters['style_id'] = requestParameters.styleId; + } + + if (requestParameters.skipReinit !== undefined) { + queryParameters['skip_reinit'] = requestParameters.skipReinit; + } + + if (requestParameters.coreVersion !== undefined) { + queryParameters['core_version'] = requestParameters.coreVersion; + } + + const headerParameters: runtime.HTTPHeaders = {}; + + const response = await this.request({ + path: `/initialize_style_id`, + method: 'POST', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.VoidApiResponse(response); + } + + /** + * 指定されたstyle_idのスタイルを初期化します。 実行しなくても他のAPIは使用できますが、初回実行時に時間がかかることがあります。 + * Initialize Style Id + */ + async initializeStyleIdInitializeStyleIdPost(requestParameters: InitializeStyleIdInitializeStyleIdPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + await this.initializeStyleIdInitializeStyleIdPostRaw(requestParameters, initOverrides); + } + /** * 音声ライブラリをインストールします。 音声ライブラリのZIPファイルをリクエストボディとして送信してください。 Parameters ---------- library_uuid: str 音声ライブラリのID * Install Library @@ -1472,10 +1592,10 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { } /** - * インストールした音声ライブラリの情報を返します。 Returns ------- ret_data: List[DownloadableLibrary] + * インストールした音声ライブラリの情報を返します。 Returns ------- ret_data: dict[str, InstalledLibrary] * Installed Libraries */ - async installedLibrariesInstalledLibrariesGetRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + async installedLibrariesInstalledLibrariesGetRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { const queryParameters: any = {}; const headerParameters: runtime.HTTPHeaders = {}; @@ -1487,21 +1607,22 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { query: queryParameters, }, initOverrides); - return new runtime.JSONApiResponse(response, (jsonValue) => runtime.mapValues(jsonValue, InstalledLibraryFromJSON)); + return new runtime.JSONApiResponse(response, (jsonValue) => runtime.mapValues(jsonValue, InstalledLibraryInfoFromJSON)); } /** - * インストールした音声ライブラリの情報を返します。 Returns ------- ret_data: List[DownloadableLibrary] + * インストールした音声ライブラリの情報を返します。 Returns ------- ret_data: dict[str, InstalledLibrary] * Installed Libraries */ - async installedLibrariesInstalledLibrariesGet(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<{ [key: string]: InstalledLibrary; }> { + async installedLibrariesInstalledLibrariesGet(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<{ [key: string]: InstalledLibraryInfo; }> { const response = await this.installedLibrariesInstalledLibrariesGetRaw(initOverrides); return await response.value(); } /** - * 指定されたspeaker_idの話者が初期化されているかどうかを返します。 + * こちらのAPIは非推奨です。`is_initialize_style_id`を利用してください。 * Is Initialized Speaker + * @deprecated */ async isInitializedSpeakerIsInitializedSpeakerGetRaw(requestParameters: IsInitializedSpeakerIsInitializedSpeakerGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { if (requestParameters.speaker === null || requestParameters.speaker === undefined) { @@ -1535,8 +1656,9 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { } /** - * 指定されたspeaker_idの話者が初期化されているかどうかを返します。 + * こちらのAPIは非推奨です。`is_initialize_style_id`を利用してください。 * Is Initialized Speaker + * @deprecated */ async isInitializedSpeakerIsInitializedSpeakerGet(requestParameters: IsInitializedSpeakerIsInitializedSpeakerGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { const response = await this.isInitializedSpeakerIsInitializedSpeakerGetRaw(requestParameters, initOverrides); @@ -1544,19 +1666,63 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { } /** - * アクセント句から音高・音素長を得る + * 指定されたstyle_idのスタイルが初期化されているかどうかを返します。 + * Is Initialized Style Id */ - async moraDataMoraDataPostRaw(requestParameters: MoraDataMoraDataPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>> { - if (requestParameters.speaker === null || requestParameters.speaker === undefined) { - throw new runtime.RequiredError('speaker','Required parameter requestParameters.speaker was null or undefined when calling moraDataMoraDataPost.'); + async isInitializedStyleIdIsInitializedStyleIdGetRaw(requestParameters: IsInitializedStyleIdIsInitializedStyleIdGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters.styleId === null || requestParameters.styleId === undefined) { + throw new runtime.RequiredError('styleId','Required parameter requestParameters.styleId was null or undefined when calling isInitializedStyleIdIsInitializedStyleIdGet.'); + } + + const queryParameters: any = {}; + + if (requestParameters.styleId !== undefined) { + queryParameters['style_id'] = requestParameters.styleId; + } + + if (requestParameters.coreVersion !== undefined) { + queryParameters['core_version'] = requestParameters.coreVersion; + } + + const headerParameters: runtime.HTTPHeaders = {}; + + const response = await this.request({ + path: `/is_initialized_style_id`, + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + if (this.isJsonMime(response.headers.get('content-type'))) { + return new runtime.JSONApiResponse(response); + } else { + return new runtime.TextApiResponse(response) as any; } + } + + /** + * 指定されたstyle_idのスタイルが初期化されているかどうかを返します。 + * Is Initialized Style Id + */ + async isInitializedStyleIdIsInitializedStyleIdGet(requestParameters: IsInitializedStyleIdIsInitializedStyleIdGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.isInitializedStyleIdIsInitializedStyleIdGetRaw(requestParameters, initOverrides); + return await response.value(); + } + /** + * アクセント句から音高・音素長を得る + */ + async moraDataMoraDataPostRaw(requestParameters: MoraDataMoraDataPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>> { if (requestParameters.accentPhrase === null || requestParameters.accentPhrase === undefined) { throw new runtime.RequiredError('accentPhrase','Required parameter requestParameters.accentPhrase was null or undefined when calling moraDataMoraDataPost.'); } const queryParameters: any = {}; + if (requestParameters.styleId !== undefined) { + queryParameters['style_id'] = requestParameters.styleId; + } + if (requestParameters.speaker !== undefined) { queryParameters['speaker'] = requestParameters.speaker; } @@ -1592,16 +1758,16 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { * アクセント句から音素長を得る */ async moraLengthMoraLengthPostRaw(requestParameters: MoraLengthMoraLengthPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>> { - if (requestParameters.speaker === null || requestParameters.speaker === undefined) { - throw new runtime.RequiredError('speaker','Required parameter requestParameters.speaker was null or undefined when calling moraLengthMoraLengthPost.'); - } - if (requestParameters.accentPhrase === null || requestParameters.accentPhrase === undefined) { throw new runtime.RequiredError('accentPhrase','Required parameter requestParameters.accentPhrase was null or undefined when calling moraLengthMoraLengthPost.'); } const queryParameters: any = {}; + if (requestParameters.styleId !== undefined) { + queryParameters['style_id'] = requestParameters.styleId; + } + if (requestParameters.speaker !== undefined) { queryParameters['speaker'] = requestParameters.speaker; } @@ -1637,16 +1803,16 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { * アクセント句から音高を得る */ async moraPitchMoraPitchPostRaw(requestParameters: MoraPitchMoraPitchPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>> { - if (requestParameters.speaker === null || requestParameters.speaker === undefined) { - throw new runtime.RequiredError('speaker','Required parameter requestParameters.speaker was null or undefined when calling moraPitchMoraPitchPost.'); - } - if (requestParameters.accentPhrase === null || requestParameters.accentPhrase === undefined) { throw new runtime.RequiredError('accentPhrase','Required parameter requestParameters.accentPhrase was null or undefined when calling moraPitchMoraPitchPost.'); } const queryParameters: any = {}; + if (requestParameters.styleId !== undefined) { + queryParameters['style_id'] = requestParameters.styleId; + } + if (requestParameters.speaker !== undefined) { queryParameters['speaker'] = requestParameters.speaker; } @@ -1679,8 +1845,8 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { } /** - * 指定されたベース話者に対してエンジン内の各話者がモーフィング機能を利用可能か返します。 モーフィングの許可/禁止は`/speakers`の`speaker.supported_features.synthesis_morphing`に記載されています。 プロパティが存在しない場合は、モーフィングが許可されているとみなします。 返り値の話者はstring型なので注意。 - * 指定した話者に対してエンジン内の話者がモーフィングが可能か判定する + * 指定されたベーススタイルに対してエンジン内の各話者がモーフィング機能を利用可能か返します。 モーフィングの許可/禁止は`/speakers`の`speaker.supported_features.synthesis_morphing`に記載されています。 プロパティが存在しない場合は、モーフィングが許可されているとみなします。 返り値の話者はstring型なので注意。 + * 指定したスタイルに対してエンジン内の話者がモーフィングが可能か判定する */ async morphableTargetsMorphableTargetsPostRaw(requestParameters: MorphableTargetsMorphableTargetsPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>> { if (requestParameters.requestBody === null || requestParameters.requestBody === undefined) { @@ -1709,8 +1875,8 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { } /** - * 指定されたベース話者に対してエンジン内の各話者がモーフィング機能を利用可能か返します。 モーフィングの許可/禁止は`/speakers`の`speaker.supported_features.synthesis_morphing`に記載されています。 プロパティが存在しない場合は、モーフィングが許可されているとみなします。 返り値の話者はstring型なので注意。 - * 指定した話者に対してエンジン内の話者がモーフィングが可能か判定する + * 指定されたベーススタイルに対してエンジン内の各話者がモーフィング機能を利用可能か返します。 モーフィングの許可/禁止は`/speakers`の`speaker.supported_features.synthesis_morphing`に記載されています。 プロパティが存在しない場合は、モーフィングが許可されているとみなします。 返り値の話者はstring型なので注意。 + * 指定したスタイルに対してエンジン内の話者がモーフィングが可能か判定する */ async morphableTargetsMorphableTargetsPost(requestParameters: MorphableTargetsMorphableTargetsPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { const response = await this.morphableTargetsMorphableTargetsPostRaw(requestParameters, initOverrides); @@ -1721,16 +1887,16 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { * 複数まとめて音声合成する */ async multiSynthesisMultiSynthesisPostRaw(requestParameters: MultiSynthesisMultiSynthesisPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - if (requestParameters.speaker === null || requestParameters.speaker === undefined) { - throw new runtime.RequiredError('speaker','Required parameter requestParameters.speaker was null or undefined when calling multiSynthesisMultiSynthesisPost.'); - } - if (requestParameters.audioQuery === null || requestParameters.audioQuery === undefined) { throw new runtime.RequiredError('audioQuery','Required parameter requestParameters.audioQuery was null or undefined when calling multiSynthesisMultiSynthesisPost.'); } const queryParameters: any = {}; + if (requestParameters.styleId !== undefined) { + queryParameters['style_id'] = requestParameters.styleId; + } + if (requestParameters.speaker !== undefined) { queryParameters['speaker'] = requestParameters.speaker; } @@ -1826,9 +1992,10 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { } /** + * 設定ページを返します。 * Setting Get */ - async settingGetSettingGetRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + async settingGetSettingGetRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { const queryParameters: any = {}; const headerParameters: runtime.HTTPHeaders = {}; @@ -1840,25 +2007,26 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { query: queryParameters, }, initOverrides); - if (this.isJsonMime(response.headers.get('content-type'))) { - return new runtime.JSONApiResponse(response); - } else { - return new runtime.TextApiResponse(response) as any; - } + return new runtime.VoidApiResponse(response); } /** + * 設定ページを返します。 * Setting Get */ - async settingGetSettingGet(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.settingGetSettingGetRaw(initOverrides); - return await response.value(); + async settingGetSettingGet(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + await this.settingGetSettingGetRaw(initOverrides); } /** + * 設定を更新します。 * Setting Post */ - async settingPostSettingPostRaw(requestParameters: SettingPostSettingPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + async settingPostSettingPostRaw(requestParameters: SettingPostSettingPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters.corsPolicyMode === null || requestParameters.corsPolicyMode === undefined) { + throw new runtime.RequiredError('corsPolicyMode','Required parameter requestParameters.corsPolicyMode was null or undefined when calling settingPostSettingPost.'); + } + const queryParameters: any = {}; const headerParameters: runtime.HTTPHeaders = {}; @@ -1878,8 +2046,8 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { } if (requestParameters.corsPolicyMode !== undefined) { - formParams.append('cors_policy_mode', requestParameters.corsPolicyMode as any); - } + formParams.append('cors_policy_mode', new Blob([JSON.stringify(CorsPolicyModeToJSON(requestParameters.corsPolicyMode))], { type: "application/json", })); + } if (requestParameters.allowOrigin !== undefined) { formParams.append('allow_origin', requestParameters.allowOrigin as any); @@ -1893,19 +2061,15 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { body: formParams, }, initOverrides); - if (this.isJsonMime(response.headers.get('content-type'))) { - return new runtime.JSONApiResponse(response); - } else { - return new runtime.TextApiResponse(response) as any; - } + return new runtime.VoidApiResponse(response); } /** + * 設定を更新します。 * Setting Post */ - async settingPostSettingPost(requestParameters: SettingPostSettingPostRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.settingPostSettingPostRaw(requestParameters, initOverrides); - return await response.value(); + async settingPostSettingPost(requestParameters: SettingPostSettingPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + await this.settingPostSettingPostRaw(requestParameters, initOverrides); } /** @@ -2009,18 +2173,10 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { } /** - * 指定された2人の話者で音声を合成、指定した割合でモーフィングした音声を得ます。 モーフィングの割合は`morph_rate`で指定でき、0.0でベースの話者、1.0でターゲットの話者に近づきます。 - * 2人の話者でモーフィングした音声を合成する + * 指定された2種類のスタイルで音声を合成、指定した割合でモーフィングした音声を得ます。 モーフィングの割合は`morph_rate`で指定でき、0.0でベースのスタイル、1.0でターゲットのスタイルに近づきます。 + * 2種類のスタイルでモーフィングした音声を合成する */ async synthesisMorphingSynthesisMorphingPostRaw(requestParameters: SynthesisMorphingSynthesisMorphingPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - if (requestParameters.baseSpeaker === null || requestParameters.baseSpeaker === undefined) { - throw new runtime.RequiredError('baseSpeaker','Required parameter requestParameters.baseSpeaker was null or undefined when calling synthesisMorphingSynthesisMorphingPost.'); - } - - if (requestParameters.targetSpeaker === null || requestParameters.targetSpeaker === undefined) { - throw new runtime.RequiredError('targetSpeaker','Required parameter requestParameters.targetSpeaker was null or undefined when calling synthesisMorphingSynthesisMorphingPost.'); - } - if (requestParameters.morphRate === null || requestParameters.morphRate === undefined) { throw new runtime.RequiredError('morphRate','Required parameter requestParameters.morphRate was null or undefined when calling synthesisMorphingSynthesisMorphingPost.'); } @@ -2031,10 +2187,18 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { const queryParameters: any = {}; + if (requestParameters.baseStyleId !== undefined) { + queryParameters['base_style_id'] = requestParameters.baseStyleId; + } + if (requestParameters.baseSpeaker !== undefined) { queryParameters['base_speaker'] = requestParameters.baseSpeaker; } + if (requestParameters.targetStyleId !== undefined) { + queryParameters['target_style_id'] = requestParameters.targetStyleId; + } + if (requestParameters.targetSpeaker !== undefined) { queryParameters['target_speaker'] = requestParameters.targetSpeaker; } @@ -2063,8 +2227,8 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { } /** - * 指定された2人の話者で音声を合成、指定した割合でモーフィングした音声を得ます。 モーフィングの割合は`morph_rate`で指定でき、0.0でベースの話者、1.0でターゲットの話者に近づきます。 - * 2人の話者でモーフィングした音声を合成する + * 指定された2種類のスタイルで音声を合成、指定した割合でモーフィングした音声を得ます。 モーフィングの割合は`morph_rate`で指定でき、0.0でベースのスタイル、1.0でターゲットのスタイルに近づきます。 + * 2種類のスタイルでモーフィングした音声を合成する */ async synthesisMorphingSynthesisMorphingPost(requestParameters: SynthesisMorphingSynthesisMorphingPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { const response = await this.synthesisMorphingSynthesisMorphingPostRaw(requestParameters, initOverrides); @@ -2075,16 +2239,16 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { * 音声合成する */ async synthesisSynthesisPostRaw(requestParameters: SynthesisSynthesisPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - if (requestParameters.speaker === null || requestParameters.speaker === undefined) { - throw new runtime.RequiredError('speaker','Required parameter requestParameters.speaker was null or undefined when calling synthesisSynthesisPost.'); - } - if (requestParameters.audioQuery === null || requestParameters.audioQuery === undefined) { throw new runtime.RequiredError('audioQuery','Required parameter requestParameters.audioQuery was null or undefined when calling synthesisSynthesisPost.'); } const queryParameters: any = {}; + if (requestParameters.styleId !== undefined) { + queryParameters['style_id'] = requestParameters.styleId; + } + if (requestParameters.speaker !== undefined) { queryParameters['speaker'] = requestParameters.speaker; } @@ -2191,8 +2355,8 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { } /** - * テキストがAquesTalkライクな記法に従っているかどうかを判定します。 従っていない場合はエラーが返ります。 Parameters ---------- text: str 判定する対象の文字列 - * テキストがAquesTalkライクな記法に従っているか判定する + * テキストがAquesTalk 風記法に従っているかどうかを判定します。 従っていない場合はエラーが返ります。 Parameters ---------- text: str 判定する対象の文字列 + * テキストがAquesTalk 風記法に従っているか判定する */ async validateKanaValidateKanaPostRaw(requestParameters: ValidateKanaValidateKanaPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { if (requestParameters.text === null || requestParameters.text === undefined) { @@ -2222,8 +2386,8 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { } /** - * テキストがAquesTalkライクな記法に従っているかどうかを判定します。 従っていない場合はエラーが返ります。 Parameters ---------- text: str 判定する対象の文字列 - * テキストがAquesTalkライクな記法に従っているか判定する + * テキストがAquesTalk 風記法に従っているかどうかを判定します。 従っていない場合はエラーが返ります。 Parameters ---------- text: str 判定する対象の文字列 + * テキストがAquesTalk 風記法に従っているか判定する */ async validateKanaValidateKanaPost(requestParameters: ValidateKanaValidateKanaPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { const response = await this.validateKanaValidateKanaPostRaw(requestParameters, initOverrides); @@ -2233,7 +2397,7 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { /** * Version */ - async versionVersionGetRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + async versionVersionGetRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { const queryParameters: any = {}; const headerParameters: runtime.HTTPHeaders = {}; @@ -2246,7 +2410,7 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { }, initOverrides); if (this.isJsonMime(response.headers.get('content-type'))) { - return new runtime.JSONApiResponse(response); + return new runtime.JSONApiResponse(response); } else { return new runtime.TextApiResponse(response) as any; } @@ -2255,7 +2419,7 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { /** * Version */ - async versionVersionGet(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + async versionVersionGet(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { const response = await this.versionVersionGetRaw(initOverrides); return await response.value(); } diff --git a/src/openapi/models/BaseLibraryInfo.ts b/src/openapi/models/BaseLibraryInfo.ts new file mode 100644 index 0000000000..9592d82b2b --- /dev/null +++ b/src/openapi/models/BaseLibraryInfo.ts @@ -0,0 +1,118 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * VOICEVOX Engine + * VOICEVOXの音声合成エンジンです。 + * + * The version of the OpenAPI document: latest + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +import type { LibrarySpeaker } from './LibrarySpeaker'; +import { + LibrarySpeakerFromJSON, + LibrarySpeakerFromJSONTyped, + LibrarySpeakerToJSON, +} from './LibrarySpeaker'; + +/** + * 音声ライブラリの情報 + * @export + * @interface BaseLibraryInfo + */ +export interface BaseLibraryInfo { + /** + * + * @type {string} + * @memberof BaseLibraryInfo + */ + name: string; + /** + * + * @type {string} + * @memberof BaseLibraryInfo + */ + uuid: string; + /** + * + * @type {string} + * @memberof BaseLibraryInfo + */ + version: string; + /** + * + * @type {string} + * @memberof BaseLibraryInfo + */ + downloadUrl: string; + /** + * + * @type {number} + * @memberof BaseLibraryInfo + */ + bytes: number; + /** + * + * @type {Array} + * @memberof BaseLibraryInfo + */ + speakers: Array; +} + +/** + * Check if a given object implements the BaseLibraryInfo interface. + */ +export function instanceOfBaseLibraryInfo(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "name" in value; + isInstance = isInstance && "uuid" in value; + isInstance = isInstance && "version" in value; + isInstance = isInstance && "downloadUrl" in value; + isInstance = isInstance && "bytes" in value; + isInstance = isInstance && "speakers" in value; + + return isInstance; +} + +export function BaseLibraryInfoFromJSON(json: any): BaseLibraryInfo { + return BaseLibraryInfoFromJSONTyped(json, false); +} + +export function BaseLibraryInfoFromJSONTyped(json: any, ignoreDiscriminator: boolean): BaseLibraryInfo { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'name': json['name'], + 'uuid': json['uuid'], + 'version': json['version'], + 'downloadUrl': json['download_url'], + 'bytes': json['bytes'], + 'speakers': ((json['speakers'] as Array).map(LibrarySpeakerFromJSON)), + }; +} + +export function BaseLibraryInfoToJSON(value?: BaseLibraryInfo | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'name': value.name, + 'uuid': value.uuid, + 'version': value.version, + 'download_url': value.downloadUrl, + 'bytes': value.bytes, + 'speakers': ((value.speakers as Array).map(LibrarySpeakerToJSON)), + }; +} + diff --git a/src/openapi/models/CorsPolicyMode.ts b/src/openapi/models/CorsPolicyMode.ts new file mode 100644 index 0000000000..29b5dbcdeb --- /dev/null +++ b/src/openapi/models/CorsPolicyMode.ts @@ -0,0 +1,38 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * VOICEVOX Engine + * VOICEVOXの音声合成エンジンです。 + * + * The version of the OpenAPI document: latest + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +/** + * CORSの許可モード + * @export + */ +export const CorsPolicyMode = { + All: 'all', + Localapps: 'localapps' +} as const; +export type CorsPolicyMode = typeof CorsPolicyMode[keyof typeof CorsPolicyMode]; + + +export function CorsPolicyModeFromJSON(json: any): CorsPolicyMode { + return CorsPolicyModeFromJSONTyped(json, false); +} + +export function CorsPolicyModeFromJSONTyped(json: any, ignoreDiscriminator: boolean): CorsPolicyMode { + return json as CorsPolicyMode; +} + +export function CorsPolicyModeToJSON(value?: CorsPolicyMode | null): any { + return value as any; +} + diff --git a/src/openapi/models/DownloadableLibraryInfo.ts b/src/openapi/models/DownloadableLibraryInfo.ts new file mode 100644 index 0000000000..1016e9c940 --- /dev/null +++ b/src/openapi/models/DownloadableLibraryInfo.ts @@ -0,0 +1,118 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * VOICEVOX Engine + * VOICEVOXの音声合成エンジンです。 + * + * The version of the OpenAPI document: latest + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +import type { LibrarySpeaker } from './LibrarySpeaker'; +import { + LibrarySpeakerFromJSON, + LibrarySpeakerFromJSONTyped, + LibrarySpeakerToJSON, +} from './LibrarySpeaker'; + +/** + * ダウンロード可能な音声ライブラリの情報 + * @export + * @interface DownloadableLibraryInfo + */ +export interface DownloadableLibraryInfo { + /** + * + * @type {string} + * @memberof DownloadableLibraryInfo + */ + name: string; + /** + * + * @type {string} + * @memberof DownloadableLibraryInfo + */ + uuid: string; + /** + * + * @type {string} + * @memberof DownloadableLibraryInfo + */ + version: string; + /** + * + * @type {string} + * @memberof DownloadableLibraryInfo + */ + downloadUrl: string; + /** + * + * @type {number} + * @memberof DownloadableLibraryInfo + */ + bytes: number; + /** + * + * @type {Array} + * @memberof DownloadableLibraryInfo + */ + speakers: Array; +} + +/** + * Check if a given object implements the DownloadableLibraryInfo interface. + */ +export function instanceOfDownloadableLibraryInfo(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "name" in value; + isInstance = isInstance && "uuid" in value; + isInstance = isInstance && "version" in value; + isInstance = isInstance && "downloadUrl" in value; + isInstance = isInstance && "bytes" in value; + isInstance = isInstance && "speakers" in value; + + return isInstance; +} + +export function DownloadableLibraryInfoFromJSON(json: any): DownloadableLibraryInfo { + return DownloadableLibraryInfoFromJSONTyped(json, false); +} + +export function DownloadableLibraryInfoFromJSONTyped(json: any, ignoreDiscriminator: boolean): DownloadableLibraryInfo { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'name': json['name'], + 'uuid': json['uuid'], + 'version': json['version'], + 'downloadUrl': json['download_url'], + 'bytes': json['bytes'], + 'speakers': ((json['speakers'] as Array).map(LibrarySpeakerFromJSON)), + }; +} + +export function DownloadableLibraryInfoToJSON(value?: DownloadableLibraryInfo | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'name': value.name, + 'uuid': value.uuid, + 'version': value.version, + 'download_url': value.downloadUrl, + 'bytes': value.bytes, + 'speakers': ((value.speakers as Array).map(LibrarySpeakerToJSON)), + }; +} + diff --git a/src/openapi/models/InstalledLibraryInfo.ts b/src/openapi/models/InstalledLibraryInfo.ts new file mode 100644 index 0000000000..b65eabcd1d --- /dev/null +++ b/src/openapi/models/InstalledLibraryInfo.ts @@ -0,0 +1,127 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * VOICEVOX Engine + * VOICEVOXの音声合成エンジンです。 + * + * The version of the OpenAPI document: latest + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +import type { LibrarySpeaker } from './LibrarySpeaker'; +import { + LibrarySpeakerFromJSON, + LibrarySpeakerFromJSONTyped, + LibrarySpeakerToJSON, +} from './LibrarySpeaker'; + +/** + * インストール済み音声ライブラリの情報 + * @export + * @interface InstalledLibraryInfo + */ +export interface InstalledLibraryInfo { + /** + * + * @type {string} + * @memberof InstalledLibraryInfo + */ + name: string; + /** + * + * @type {string} + * @memberof InstalledLibraryInfo + */ + uuid: string; + /** + * + * @type {string} + * @memberof InstalledLibraryInfo + */ + version: string; + /** + * + * @type {string} + * @memberof InstalledLibraryInfo + */ + downloadUrl: string; + /** + * + * @type {number} + * @memberof InstalledLibraryInfo + */ + bytes: number; + /** + * + * @type {Array} + * @memberof InstalledLibraryInfo + */ + speakers: Array; + /** + * + * @type {boolean} + * @memberof InstalledLibraryInfo + */ + uninstallable: boolean; +} + +/** + * Check if a given object implements the InstalledLibraryInfo interface. + */ +export function instanceOfInstalledLibraryInfo(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "name" in value; + isInstance = isInstance && "uuid" in value; + isInstance = isInstance && "version" in value; + isInstance = isInstance && "downloadUrl" in value; + isInstance = isInstance && "bytes" in value; + isInstance = isInstance && "speakers" in value; + isInstance = isInstance && "uninstallable" in value; + + return isInstance; +} + +export function InstalledLibraryInfoFromJSON(json: any): InstalledLibraryInfo { + return InstalledLibraryInfoFromJSONTyped(json, false); +} + +export function InstalledLibraryInfoFromJSONTyped(json: any, ignoreDiscriminator: boolean): InstalledLibraryInfo { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'name': json['name'], + 'uuid': json['uuid'], + 'version': json['version'], + 'downloadUrl': json['download_url'], + 'bytes': json['bytes'], + 'speakers': ((json['speakers'] as Array).map(LibrarySpeakerFromJSON)), + 'uninstallable': json['uninstallable'], + }; +} + +export function InstalledLibraryInfoToJSON(value?: InstalledLibraryInfo | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'name': value.name, + 'uuid': value.uuid, + 'version': value.version, + 'download_url': value.downloadUrl, + 'bytes': value.bytes, + 'speakers': ((value.speakers as Array).map(LibrarySpeakerToJSON)), + 'uninstallable': value.uninstallable, + }; +} + diff --git a/src/openapi/models/ParseKanaBadRequest.ts b/src/openapi/models/ParseKanaBadRequest.ts index 8951aeaf87..8cedd507a4 100644 --- a/src/openapi/models/ParseKanaBadRequest.ts +++ b/src/openapi/models/ParseKanaBadRequest.ts @@ -44,7 +44,7 @@ export interface ParseKanaBadRequest { * @type {{ [key: string]: string; }} * @memberof ParseKanaBadRequest */ - errorArgs: { [key: string]: string; }; + errorArgs: { [key: string]: string; } | null; } /** diff --git a/src/openapi/models/Speaker.ts b/src/openapi/models/Speaker.ts index d136f63709..83255477f1 100644 --- a/src/openapi/models/Speaker.ts +++ b/src/openapi/models/Speaker.ts @@ -27,7 +27,7 @@ import { } from './SpeakerSupportedFeatures'; /** - * スピーカー情報 + * 話者情報 * @export * @interface Speaker */ diff --git a/src/openapi/models/SpeakerStyle.ts b/src/openapi/models/SpeakerStyle.ts index 5d3403cc43..99dcb9bfb3 100644 --- a/src/openapi/models/SpeakerStyle.ts +++ b/src/openapi/models/SpeakerStyle.ts @@ -14,7 +14,7 @@ import { exists, mapValues } from '../runtime'; /** - * スピーカーのスタイル情報 + * 話者のスタイル情報 * @export * @interface SpeakerStyle */ diff --git a/src/openapi/models/ValidationError.ts b/src/openapi/models/ValidationError.ts index 28fb693213..11f534f675 100644 --- a/src/openapi/models/ValidationError.ts +++ b/src/openapi/models/ValidationError.ts @@ -13,6 +13,13 @@ */ import { exists, mapValues } from '../runtime'; +import type { ValidationErrorLocInner } from './ValidationErrorLocInner'; +import { + ValidationErrorLocInnerFromJSON, + ValidationErrorLocInnerFromJSONTyped, + ValidationErrorLocInnerToJSON, +} from './ValidationErrorLocInner'; + /** * * @export @@ -21,10 +28,10 @@ import { exists, mapValues } from '../runtime'; export interface ValidationError { /** * - * @type {Array} + * @type {Array} * @memberof ValidationError */ - loc: Array; + loc: Array; /** * * @type {string} @@ -61,7 +68,7 @@ export function ValidationErrorFromJSONTyped(json: any, ignoreDiscriminator: boo } return { - 'loc': json['loc'], + 'loc': ((json['loc'] as Array).map(ValidationErrorLocInnerFromJSON)), 'msg': json['msg'], 'type': json['type'], }; @@ -76,7 +83,7 @@ export function ValidationErrorToJSON(value?: ValidationError | null): any { } return { - 'loc': value.loc, + 'loc': ((value.loc as Array).map(ValidationErrorLocInnerToJSON)), 'msg': value.msg, 'type': value.type, }; diff --git a/src/openapi/models/ValidationErrorLocInner.ts b/src/openapi/models/ValidationErrorLocInner.ts new file mode 100644 index 0000000000..340b6f1683 --- /dev/null +++ b/src/openapi/models/ValidationErrorLocInner.ts @@ -0,0 +1,44 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * VOICEVOX Engine + * VOICEVOXの音声合成エンジンです。 + * + * The version of the OpenAPI document: latest + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +/** + * + * @export + * @interface ValidationErrorLocInner + */ +export interface ValidationErrorLocInner { +} + +/** + * Check if a given object implements the ValidationErrorLocInner interface. + */ +export function instanceOfValidationErrorLocInner(value: object): boolean { + let isInstance = true; + + return isInstance; +} + +export function ValidationErrorLocInnerFromJSON(json: any): ValidationErrorLocInner { + return ValidationErrorLocInnerFromJSONTyped(json, false); +} + +export function ValidationErrorLocInnerFromJSONTyped(json: any, ignoreDiscriminator: boolean): ValidationErrorLocInner { + return json; +} + +export function ValidationErrorLocInnerToJSON(value?: ValidationErrorLocInner | null): any { + return value; +} + diff --git a/src/openapi/models/WordTypes.ts b/src/openapi/models/WordTypes.ts index a59aa50cde..317f9ecafd 100644 --- a/src/openapi/models/WordTypes.ts +++ b/src/openapi/models/WordTypes.ts @@ -14,9 +14,7 @@ /** - * - * fastapiでword_type引数を検証する時に使用するクラス - * + * fastapiでword_type引数を検証する時に使用するクラス * @export */ export const WordTypes = { diff --git a/src/openapi/models/index.ts b/src/openapi/models/index.ts index df0f9356b4..c79197add4 100644 --- a/src/openapi/models/index.ts +++ b/src/openapi/models/index.ts @@ -2,10 +2,12 @@ /* eslint-disable */ export * from './AccentPhrase'; export * from './AudioQuery'; -export * from './DownloadableLibrary'; +export * from './BaseLibraryInfo'; +export * from './CorsPolicyMode'; +export * from './DownloadableLibraryInfo'; export * from './EngineManifest'; export * from './HTTPValidationError'; -export * from './InstalledLibrary'; +export * from './InstalledLibraryInfo'; export * from './LibrarySpeaker'; export * from './LicenseInfo'; export * from './Mora'; @@ -23,5 +25,6 @@ export * from './SupportedFeatures'; export * from './UpdateInfo'; export * from './UserDictWord'; export * from './ValidationError'; +export * from './ValidationErrorLocInner'; export * from './VvlibManifest'; export * from './WordTypes'; diff --git a/src/openapi/runtime.ts b/src/openapi/runtime.ts index 648e65834c..865b082d87 100644 --- a/src/openapi/runtime.ts +++ b/src/openapi/runtime.ts @@ -91,7 +91,7 @@ export const DefaultConfig = new Configuration(); */ export class BaseAPI { - private static readonly jsonRegex = new RegExp('^(:?application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(:?;.*)?$', 'i'); + private static readonly jsonRegex = new RegExp('^(:?application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(:?;.*)?$', 'i'); private middleware: Middleware[]; constructor(protected configuration = DefaultConfig) {