From 1f9749fbe91c08e80b3c9a7edc3c100ec97ba3d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=89=9B=E8=92=A1?= <53455523+gobosan@users.noreply.github.com> Date: Wed, 9 Feb 2022 08:59:33 +0900 Subject: [PATCH 01/32] =?UTF-8?q?=E3=83=98=E3=83=AB=E3=83=97=E3=81=A7?= =?UTF-8?q?=E3=82=A2=E3=83=83=E3=83=97=E3=83=87=E3=83=BC=E3=83=88=E3=82=92?= =?UTF-8?q?=E7=A2=BA=E8=AA=8D=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E3=81=99=E3=82=8B=20(#690)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add the ability to check for updates * fix * add wording * fix * fix * fix * fix * fix * fix * remove unnecessary logs * remove logger * エラーを投げる --- src/components/UpdateInfo.vue | 85 +++++++++++++++++++++++++++++++++++ src/store/project.ts | 9 +++- 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/src/components/UpdateInfo.vue b/src/components/UpdateInfo.vue index 25ab63ed30..ee63fd2971 100644 --- a/src/components/UpdateInfo.vue +++ b/src/components/UpdateInfo.vue @@ -13,6 +13,11 @@ import { useStore } from "@/store"; import { computed, defineComponent, ref } from "@vue/runtime-core"; import { UpdateInfo } from "../type/preload"; +import { + VersionType, + versionTextParse, + baseVersionIsLow, +} from "@/store/project"; export default defineComponent({ setup() { @@ -21,10 +26,90 @@ export default defineComponent({ const infos = ref(); store.dispatch("GET_UPDATE_INFOS").then((obj) => (infos.value = obj)); + let isCheckingFailed = ref(false); + + let isCheckingFinished = ref(false); + + const currentVersion = ref(""); + const latestVersion = ref(""); + window.electron + .getAppInfos() + .then((obj) => { + currentVersion.value = obj.version; + }) + .then(() => { + fetch("https://api.github.com/repos/VOICEVOX/voicevox/releases", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + }) + .then((response) => { + if (!response.ok) { + isCheckingFailed.value = true; + } else { + return response.json(); + } + }) + .then((json) => { + const obj = json.find( + (item: { prerelease: boolean; tag_name: string }) => { + return ( + !item.prerelease && + baseVersionIsLow( + versionTextParse(currentVersion.value) as VersionType, + versionTextParse(item.tag_name) as VersionType + ) + ); + } + ); + obj ? (latestVersion.value = obj.tag_name) : undefined; + isCheckingFinished.value = true; + }) + .catch((err) => { + throw new Error(err); + }); + }) + .catch(() => { + isCheckingFailed.value = true; + }); + + const isCheckingFailedComputed = computed(() => { + return isCheckingFailed.value; + }); + + const isCheckingFinishedComputed = computed(() => { + return isCheckingFinished.value; + }); + + const isUpdateAvailable = computed(() => { + return isCheckingFinished.value && latestVersion.value !== ""; + }); + const html = computed(() => { if (!infos.value) return ""; let html = ""; + + if (isUpdateAvailable.value) { + html += `

アップデートがあります!

`; + html += `

最新版のダウンロードページ

`; + html += `https://voicevox.hiroshiba.jp/`; + } else if (isCheckingFinishedComputed.value && !isUpdateAvailable.value) { + html += `

お使いの VOICEBOX は最新です!

`; + } else if ( + !isCheckingFinishedComputed.value && + !isCheckingFailedComputed.value + ) { + html += `

アップデートを確認中です…

`; + } else { + html += `

アップデートの確認に失敗しました…

`; + } + + html += `
`; + html += `

アップデート履歴

`; + for (const info of infos.value) { const version: string = info.version; const descriptions: string[] = info.descriptions; diff --git a/src/store/project.ts b/src/store/project.ts index fe7d3e55b6..5f9f762dff 100755 --- a/src/store/project.ts +++ b/src/store/project.ts @@ -404,7 +404,9 @@ interface ProjectType { export type VersionType = [number, number, number]; -const versionTextParse = (appVersionText: string): VersionType | undefined => { +export const versionTextParse = ( + appVersionText: string +): VersionType | undefined => { const textArray = appVersionText.split("."); if (textArray.length !== 3) return undefined; const appVersion = textArray.map(Number) as VersionType; @@ -412,7 +414,10 @@ const versionTextParse = (appVersionText: string): VersionType | undefined => { return appVersion; }; -const baseVersionIsLow = (base: VersionType, target: VersionType): boolean => { +export const baseVersionIsLow = ( + base: VersionType, + target: VersionType +): boolean => { let result = false; for (let i = 0; i < 3; i++) { if (base[i] > target[i]) { From bc99cee44e744b778a5a013bdfe01dbf9dbeb76f Mon Sep 17 00:00:00 2001 From: Yosshi999 Date: Sun, 13 Feb 2022 14:42:38 +0900 Subject: [PATCH 02/32] =?UTF-8?q?=E8=AA=AD=E7=82=B9=E3=82=92=E5=90=AB?= =?UTF-8?q?=E3=82=93=E3=81=A0=E8=AA=AD=E3=81=BF=E4=BB=AE=E5=90=8D=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E3=81=AB=E5=AF=BE=E5=BF=9C=20(#692)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 読点を含んだ読み仮名修正に対応 * change regex pattern to support the phrase with a single char --- src/store/audio.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/store/audio.ts b/src/store/audio.ts index 1118e260d6..cb5525ac6e 100644 --- a/src/store/audio.ts +++ b/src/store/audio.ts @@ -1632,9 +1632,13 @@ export const audioCommandStore: VoiceVoxStoreOptions< let newAccentPhrasesSegment: AccentPhrase[] | undefined = undefined; - // ひらがな(U+3041~U+3094)とカタカナ(U+30A1~U+30F4)と全角長音(U+30FC)のみで構成される場合、 - // 「読み仮名」としてこれを処理する - const kanaRegex = /^[\u3041-\u3094\u30A1-\u30F4\u30FC]+$/; + // 以下の文字のみで構成される場合、「読み仮名」としてこれを処理する + // * ひらがな(U+3041~U+3094) + // * カタカナ(U+30A1~U+30F4) + // * 全角長音(U+30FC) + // * 読点(U+3001) + // * クエスチョン(U+FF1F) + const kanaRegex = /^[\u3041-\u3094\u30A1-\u30F4\u30FC\u3001\uFF1F]+$/; if (kanaRegex.test(newPronunciation)) { // ひらがなが混ざっている場合はカタカナに変換 const katakana = newPronunciation.replace(/[\u3041-\u3094]/g, (s) => { @@ -1650,10 +1654,17 @@ export const audioCommandStore: VoiceVoxStoreOptions< .replace(/(?<=[ン]ー*)ー/g, "ン") .replace(/(?<=[ッ]ー*)ー/g, "ッ"); - // アクセントを末尾につけaccent phraseの生成をリクエスト + // アクセントを各句の末尾につける + // 文中に「?、」「、」がある場合は、そこで句切りとみなす + const pureKatakanaWithAccent = pureKatakana.replace( + /(?、|、|(?<=[^?、])$|?$)/g, + "'$1" + ); + + // accent phraseの生成をリクエスト // 判別できない読み仮名が混じっていた場合400エラーが帰るのでfallback newAccentPhrasesSegment = await dispatch("FETCH_ACCENT_PHRASES", { - text: pureKatakana + "'", + text: pureKatakanaWithAccent, styleId, isKana: true, }).catch( From 7b963fb85d2d2e0cfbb273edea3fa5857793f758 Mon Sep 17 00:00:00 2001 From: Segu <51497552+Segu-g@users.noreply.github.com> Date: Sun, 13 Feb 2022 14:44:03 +0900 Subject: [PATCH 03/32] =?UTF-8?q?state=E3=81=AB=E9=96=93=E9=81=95=E3=81=88?= =?UTF-8?q?=E3=81=A6store=E3=81=8C=E5=85=A5=E3=81=A3=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=82=8B=E7=8A=B6=E6=85=8B=E3=82=92=E4=BF=AE=E6=AD=A3=20(#699)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/store/index.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/store/index.ts b/src/store/index.ts index 93f3c38b54..93a986e30d 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -14,7 +14,12 @@ import { VoiceVoxStoreOptions, } from "./type"; import { commandStoreState, commandStore } from "./command"; -import { audioStoreState, audioStore, audioCommandStore } from "./audio"; +import { + audioStoreState, + audioStore, + audioCommandStore, + audioCommandStoreState, +} from "./audio"; import { projectStoreState, projectStore } from "./project"; import { uiStoreState, uiStore } from "./ui"; import { settingStoreState, settingStore } from "./setting"; @@ -156,7 +161,7 @@ export const store = createStore({ ...commandStoreState, ...projectStoreState, ...settingStoreState, - ...audioCommandStore, + ...audioCommandStoreState, ...indexStoreState, ...presetStoreState, ...proxyStoreState, From 788dedf1280cd924a0fe62992760bd70d1e784fb Mon Sep 17 00:00:00 2001 From: Segu <51497552+Segu-g@users.noreply.github.com> Date: Sat, 19 Feb 2022 01:59:04 +0900 Subject: [PATCH 04/32] =?UTF-8?q?=E3=82=BB=E3=83=AB=E3=81=AE=E3=83=89?= =?UTF-8?q?=E3=83=A9=E3=83=83=E3=82=B0=E5=8F=AF=E8=83=BD=E3=81=AA=E7=AF=84?= =?UTF-8?q?=E5=9B=B2=E3=82=92=E5=A4=89=E6=9B=B4=20(#703)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix drag handle * fix style from m-1rem to p-0.4rem m-0.2rem --- src/components/AudioCell.vue | 15 +++++++++------ src/views/Home.vue | 7 ++++++- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/components/AudioCell.vue b/src/components/AudioCell.vue index a02839395c..eb48060623 100644 --- a/src/components/AudioCell.vue +++ b/src/components/AudioCell.vue @@ -7,11 +7,7 @@ size="sm" class="absolute active-arrow" /> - + -
- -
(); // DaD - const enableReorderCell = computed( - () => store.state.experimentalSetting.enableReorderCell - ); - const updateAudioKeys = (audioKeys: string[]) => store.dispatch("COMMAND_SET_AUDIO_KEYS", { audioKeys }); const itemKey = (key: string) => key; @@ -539,7 +525,6 @@ export default defineComponent({ uiLocked, addAudioCellRef, activeAudioKey, - enableReorderCell, itemKey, updateAudioKeys, addAudioItem, diff --git a/tests/unit/store/Vuex.spec.ts b/tests/unit/store/Vuex.spec.ts index 10c570a9cf..d80840bb6a 100644 --- a/tests/unit/store/Vuex.spec.ts +++ b/tests/unit/store/Vuex.spec.ts @@ -74,7 +74,6 @@ describe("store/vuex.js test", () => { experimentalSetting: { enablePreset: false, enableInterrogativeUpspeak: false, - enableReorderCell: false, }, }, getters: { @@ -164,6 +163,5 @@ describe("store/vuex.js test", () => { store.state.experimentalSetting.enableInterrogativeUpspeak, false ); - assert.equal(store.state.experimentalSetting.enableReorderCell, false); }); }); From c4f1cfd2d57aa958f1dedf8f1fcb7f813b7048ca Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Sat, 26 Feb 2022 08:55:20 +0900 Subject: [PATCH 08/32] =?UTF-8?q?=E3=82=B9=E3=83=A9=E3=82=A4=E3=83=80?= =?UTF-8?q?=E3=83=BC=E3=82=AF=E3=83=AA=E3=83=83=E3=82=AF=E3=81=A7=E5=86=8D?= =?UTF-8?q?=E7=94=9F=E4=BD=8D=E7=BD=AE=E6=8C=87=E5=AE=9A=E3=81=AB=E3=81=AA?= =?UTF-8?q?=E3=81=A3=E3=81=A6=E3=81=97=E3=81=BE=E3=81=86=E3=81=AE=E3=82=92?= =?UTF-8?q?=E7=9B=B4=E3=81=99=20(#710)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/AudioAccent.vue | 3 +++ src/components/AudioDetail.vue | 35 ++++++++++++++++--------------- src/components/AudioParameter.vue | 3 +++ 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/components/AudioAccent.vue b/src/components/AudioAccent.vue index 7630134227..2c0ca4bce5 100644 --- a/src/components/AudioAccent.vue +++ b/src/components/AudioAccent.vue @@ -21,6 +21,9 @@ @update:model-value=" previewAccentSlider.qSliderProps['onUpdate:modelValue'] " + @click.stop=" + undefined; // クリックでアクセント句が選択されないように + " @change="previewAccentSlider.qSliderProps.onChange" @wheel="previewAccentSlider.qSliderProps.onWheel" @pan="previewAccentSlider.qSliderProps.onPan" diff --git a/src/components/AudioDetail.vue b/src/components/AudioDetail.vue index 992004db22..68500f715e 100644 --- a/src/components/AudioDetail.vue +++ b/src/components/AudioDetail.vue @@ -148,7 +148,10 @@ }" @mouseover="handleHoverText(true, accentPhraseIndex, moraIndex)" @mouseleave="handleHoverText(false, accentPhraseIndex, moraIndex)" - @click="handleChangeVoicing(mora, accentPhraseIndex, moraIndex)" + @click.stop=" + uiLocked || + handleChangeVoicing(mora, accentPhraseIndex, moraIndex) + " > {{ getHoveredText(mora, accentPhraseIndex, moraIndex) }}
{{ accentPhrase.pauseMora.text }}
-1 - ) { - let data = 0; - if (mora.pitch == 0) { - if (lastPitches.value[accentPhraseIndex][moraIndex] == 0) { - // 元々無声だった場合、適当な値を代入 - data = 5.5; - } else { - data = lastPitches.value[accentPhraseIndex][moraIndex]; - } + if ( + selectedDetail.value == "pitch" && + unvoicableVowels.indexOf(mora.vowel) > -1 + ) { + let data = 0; + if (mora.pitch == 0) { + if (lastPitches.value[accentPhraseIndex][moraIndex] == 0) { + // 元々無声だった場合、適当な値を代入 + data = 5.5; + } else { + data = lastPitches.value[accentPhraseIndex][moraIndex]; } - changeMoraData(accentPhraseIndex, moraIndex, data, "voicing"); } + changeMoraData(accentPhraseIndex, moraIndex, data, "voicing"); } }; diff --git a/src/components/AudioParameter.vue b/src/components/AudioParameter.vue index 4d6e9fbd91..54a481a13f 100644 --- a/src/components/AudioParameter.vue +++ b/src/components/AudioParameter.vue @@ -25,6 +25,9 @@ :disable="previewSlider.qSliderProps.disable.value" :model-value="previewSlider.qSliderProps.modelValue.value" @update:model-value="previewSlider.qSliderProps['onUpdate:modelValue']" + @click.stop=" + undefined; // クリックでアクセント句が選択されないように + " @change="previewSlider.qSliderProps.onChange" @wheel="previewSlider.qSliderProps.onWheel" @pan="previewSlider.qSliderProps.onPan" From bc193c85341a52edb9017d5404a542940c0fb923 Mon Sep 17 00:00:00 2001 From: Yuto Ashida Date: Sun, 27 Feb 2022 03:19:10 +0900 Subject: [PATCH 09/32] =?UTF-8?q?AquesTalk=E8=A8=98=E6=B3=95=E7=AD=89?= =?UTF-8?q?=E3=81=AE=E3=81=9F=E3=82=81=E3=81=AE=E9=96=A2=E6=95=B0=E5=88=87?= =?UTF-8?q?=E3=82=8A=E5=87=BA=E3=81=97=E3=81=A8=E3=83=AA=E3=83=95=E3=82=A1?= =?UTF-8?q?=E3=82=AF=E3=82=BF=E3=83=AA=E3=83=B3=E3=82=B0=20(#713)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/store/audio.ts | 29 ++++++++++------------------- src/store/utility.ts | 29 +++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/src/store/audio.ts b/src/store/audio.ts index cb03ffdb54..15314952c3 100644 --- a/src/store/audio.ts +++ b/src/store/audio.ts @@ -28,7 +28,13 @@ import { } from "@/type/preload"; import Encoding from "encoding-japanese"; import { PromiseType } from "./vuex"; -import { buildProjectFileName, sanitizeFileName } from "./utility"; +import { + buildProjectFileName, + convertHiraToKana, + convertLongVowel, + createKanaRegex, + sanitizeFileName, +} from "./utility"; async function generateUniqueIdAndQuery( state: State, @@ -1676,27 +1682,12 @@ export const audioCommandStore: VoiceVoxStoreOptions< let newAccentPhrasesSegment: AccentPhrase[] | undefined = undefined; - // 以下の文字のみで構成される場合、「読み仮名」としてこれを処理する - // * ひらがな(U+3041~U+3094) - // * カタカナ(U+30A1~U+30F4) - // * 全角長音(U+30FC) - // * 読点(U+3001) - // * クエスチョン(U+FF1F) - const kanaRegex = /^[\u3041-\u3094\u30A1-\u30F4\u30FC\u3001\uFF1F]+$/; + const kanaRegex = createKanaRegex(true); if (kanaRegex.test(newPronunciation)) { // ひらがなが混ざっている場合はカタカナに変換 - const katakana = newPronunciation.replace(/[\u3041-\u3094]/g, (s) => { - return String.fromCharCode(s.charCodeAt(0) + 0x60); - }); + const katakana = convertHiraToKana(newPronunciation); // 長音を適切な音に変換 - const pureKatakana = katakana - .replace(/(?<=[アカサタナハマヤラワャァガザダバパ]ー*)ー/g, "ア") - .replace(/(?<=[イキシチニヒミリィギジヂビピ]ー*)ー/g, "イ") - .replace(/(?<=[ウクスツヌフムユルュゥヴグズヅブプ]ー*)ー/g, "ウ") - .replace(/(?<=[エケセテネヘメレェゲゼデベペ]ー*)ー/g, "エ") - .replace(/(?<=[オコソトノホモヨロヲョォゴゾドボポ]ー*)ー/g, "オ") - .replace(/(?<=[ン]ー*)ー/g, "ン") - .replace(/(?<=[ッ]ー*)ー/g, "ッ"); + const pureKatakana = convertLongVowel(katakana); // アクセントを各句の末尾につける // 文中に「?、」「、」がある場合は、そこで句切りとみなす diff --git a/src/store/utility.ts b/src/store/utility.ts index 56a0d80763..c9aad029b2 100644 --- a/src/store/utility.ts +++ b/src/store/utility.ts @@ -60,3 +60,32 @@ export const getToolbarButtonName = (tag: ToolbarButtonTagType): string => { }; return tag2NameObj[tag]; }; + +export const createKanaRegex = (includeSeparation?: boolean): RegExp => { + // 以下の文字のみで構成される場合、「読み仮名」としてこれを処理する + // includeSeparationがtrueの時は、読点(U+3001)とクエスチョン(U+FF1F)も含む + // * ひらがな(U+3041~U+3094) + // * カタカナ(U+30A1~U+30F4) + // * 全角長音(U+30FC) + if (includeSeparation) { + return /^[\u3041-\u3094\u30A1-\u30F4\u30FC\u3001\uFF1F]+$/; + } + return /^[\u3041-\u3094\u30A1-\u30F4\u30FC]+$/; +}; + +export const convertHiraToKana = (text: string): string => { + return text.replace(/[\u3041-\u3094]/g, (s) => { + return String.fromCharCode(s.charCodeAt(0) + 0x60); + }); +}; + +export const convertLongVowel = (text: string): string => { + return text + .replace(/(?<=[アカサタナハマヤラワャァガザダバパ]ー*)ー/g, "ア") + .replace(/(?<=[イキシチニヒミリィギジヂビピ]ー*)ー/g, "イ") + .replace(/(?<=[ウクスツヌフムユルュゥヴグズヅブプ]ー*)ー/g, "ウ") + .replace(/(?<=[エケセテネヘメレェゲゼデベペ]ー*)ー/g, "エ") + .replace(/(?<=[オコソトノホモヨロヲョォゴゾドボポ]ー*)ー/g, "オ") + .replace(/(?<=[ン]ー*)ー/g, "ン") + .replace(/(?<=[ッ]ー*)ー/g, "ッ"); +}; From 0745431dcd8da1a5d5cf4b23e9866f1f1125afd2 Mon Sep 17 00:00:00 2001 From: Yuto Ashida Date: Sun, 27 Feb 2022 03:27:51 +0900 Subject: [PATCH 10/32] =?UTF-8?q?Audio=20Store=E3=81=AE=E3=83=AA=E3=83=95?= =?UTF-8?q?=E3=82=A1=E3=82=AF=E3=82=BF=E3=83=AA=E3=83=B3=E3=82=B0=20(#712)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * separate generate audio function * separate get audio cache function * refactor html audio element * separate play audio function --- src/store/audio.ts | 58 +++++++++++++++++++++++++++++++++---------- src/store/type.ts | 18 +++++++++++++- src/type/globals.d.ts | 6 +++++ 3 files changed, 68 insertions(+), 14 deletions(-) diff --git a/src/store/audio.ts b/src/store/audio.ts index 15314952c3..b1ca45db2c 100644 --- a/src/store/audio.ts +++ b/src/store/audio.ts @@ -667,8 +667,17 @@ export const audioStore: VoiceVoxStoreOptions< ) { commit("SET_AUDIO_PLAY_START_POINT", { startPoint }); }, - async GET_AUDIO_CACHE({ state }, { audioKey }: { audioKey: string }) { + async GET_AUDIO_CACHE( + { state, dispatch }, + { audioKey }: { audioKey: string } + ) { const audioItem = state.audioItems[audioKey]; + return dispatch("GET_AUDIO_CACHE_FROM_AUDIO_ITEM", { audioItem }); + }, + async GET_AUDIO_CACHE_FROM_AUDIO_ITEM( + { state }, + { audioItem }: { audioItem: AudioItem } + ) { const [id] = await generateUniqueIdAndQuery(state, audioItem); if (Object.prototype.hasOwnProperty.call(audioBlobCache, id)) { @@ -909,16 +918,21 @@ export const audioStore: VoiceVoxStoreOptions< }); } ), - GENERATE_AUDIO: createUILockAction( - async ({ dispatch, state }, { audioKey }: { audioKey: string }) => { + async GENERATE_AUDIO( + { dispatch, state }, + { audioKey }: { audioKey: string } + ) { + const audioItem: AudioItem = JSON.parse( + JSON.stringify(state.audioItems[audioKey]) + ); + return dispatch("GENERATE_AUDIO_FROM_AUDIO_ITEM", { audioItem }); + }, + GENERATE_AUDIO_FROM_AUDIO_ITEM: createUILockAction( + async ({ dispatch, state }, { audioItem }: { audioItem: AudioItem }) => { const engineInfo = state.engineInfos[0]; // TODO: 複数エンジン対応 if (!engineInfo) throw new Error(`No such engineInfo registered: index == 0`); - const audioItem: AudioItem = JSON.parse( - JSON.stringify(state.audioItems[audioKey]) - ); - const [id, audioQuery] = await generateUniqueIdAndQuery( state, audioItem @@ -1246,9 +1260,7 @@ export const audioStore: VoiceVoxStoreOptions< { state, commit, dispatch }, { audioKey }: { audioKey: string } ) => { - const audioElem = audioElements[audioKey] as HTMLAudioElement & { - setSinkId(deviceID: string): Promise; // setSinkIdを認識してくれないため - }; + const audioElem = audioElements[audioKey]; audioElem.pause(); // 音声用意 @@ -1267,7 +1279,6 @@ export const audioStore: VoiceVoxStoreOptions< throw new Error(); } } - audioElem.src = URL.createObjectURL(blob); const accentPhraseOffsets = await dispatch("GET_AUDIO_PLAY_OFFSETS", { audioKey, }); @@ -1281,6 +1292,23 @@ export const audioStore: VoiceVoxStoreOptions< audioElem.currentTime = startTime + 10e-6; } + return dispatch("PLAY_AUDIO_BLOB", { + audioBlob: blob, + audioElem, + audioKey, + }); + } + ), + PLAY_AUDIO_BLOB: createUILockAction( + async ( + { state, commit }, + { + audioBlob, + audioElem, + audioKey, + }: { audioBlob: Blob; audioElem: HTMLAudioElement; audioKey?: string } + ) => { + audioElem.src = URL.createObjectURL(audioBlob); audioElem .setSinkId(state.savingSetting.audioOutputDevice) .catch((err) => { @@ -1298,7 +1326,9 @@ export const audioStore: VoiceVoxStoreOptions< // 再生終了時にresolveされるPromiseを返す const played = async () => { - commit("SET_AUDIO_NOW_PLAYING", { audioKey, nowPlaying: true }); + if (audioKey) { + commit("SET_AUDIO_NOW_PLAYING", { audioKey, nowPlaying: true }); + } }; audioElem.addEventListener("play", played); @@ -1311,7 +1341,9 @@ export const audioStore: VoiceVoxStoreOptions< }).finally(async () => { audioElem.removeEventListener("play", played); audioElem.removeEventListener("pause", paused); - commit("SET_AUDIO_NOW_PLAYING", { audioKey, nowPlaying: false }); + if (audioKey) { + commit("SET_AUDIO_NOW_PLAYING", { audioKey, nowPlaying: false }); + } }); audioElem.play(); diff --git a/src/store/type.ts b/src/store/type.ts index 86929b33e4..1f27d2b7af 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -200,6 +200,10 @@ type AudioStoreTypes = { action(payload: { audioKey: string }): Promise; }; + GET_AUDIO_CACHE_FROM_AUDIO_ITEM: { + action(payload: { audioItem: AudioItem }): Promise; + }; + SET_AUDIO_TEXT: { mutation: { audioKey: string; text: string }; }; @@ -299,7 +303,11 @@ type AudioStoreTypes = { }; GENERATE_AUDIO: { - action(payload: { audioKey: string }): Blob | null; + action(payload: { audioKey: string }): Promise; + }; + + GENERATE_AUDIO_FROM_AUDIO_ITEM: { + action(payload: { audioItem: AudioItem }): Blob | null; }; CONNECT_AUDIO: { @@ -332,6 +340,14 @@ type AudioStoreTypes = { action(payload: { audioKey: string }): boolean; }; + PLAY_AUDIO_BLOB: { + action(payload: { + audioBlob: Blob; + audioElem: HTMLAudioElement; + audioKey?: string; + }): boolean; + }; + STOP_AUDIO: { action(payload: { audioKey: string }): void; }; diff --git a/src/type/globals.d.ts b/src/type/globals.d.ts index 99f2ca085e..300dba2983 100644 --- a/src/type/globals.d.ts +++ b/src/type/globals.d.ts @@ -1,2 +1,8 @@ // Include global variables to build immer source code export * from "immer/src/types/globals"; + +declare global { + interface HTMLAudioElement { + setSinkId(deviceID: string): Promise; // setSinkIdを認識してくれないため + } +} From 87d6876e4caf0b9084ea2982a1a44b9b6a6e5c99 Mon Sep 17 00:00:00 2001 From: Yuto Ashida Date: Sun, 27 Feb 2022 03:42:37 +0900 Subject: [PATCH 11/32] =?UTF-8?q?OpenAPI=E3=81=AE=E5=AE=9A=E7=BE=A9?= =?UTF-8?q?=E3=82=92=E6=9B=B4=E6=96=B0=20(#711)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update openapi * fix get speakers error --- openapi.json | 2 +- src/openapi/.openapi-generator/FILES | 2 + src/openapi/apis/DefaultApi.ts | 426 ++++++++++++++++++++- src/openapi/models/AccentPhrase.ts | 2 +- src/openapi/models/AudioQuery.ts | 2 +- src/openapi/models/HTTPValidationError.ts | 2 +- src/openapi/models/Mora.ts | 2 +- src/openapi/models/ParseKanaBadRequest.ts | 2 +- src/openapi/models/Preset.ts | 2 +- src/openapi/models/Speaker.ts | 2 +- src/openapi/models/SpeakerInfo.ts | 2 +- src/openapi/models/SpeakerStyle.ts | 2 +- src/openapi/models/StyleInfo.ts | 2 +- src/openapi/models/SupportedDevicesInfo.ts | 64 ++++ src/openapi/models/UserDictWord.ts | 160 ++++++++ src/openapi/models/ValidationError.ts | 2 +- src/openapi/models/index.ts | 2 + src/openapi/runtime.ts | 2 +- src/store/audio.ts | 3 +- 19 files changed, 663 insertions(+), 20 deletions(-) create mode 100644 src/openapi/models/SupportedDevicesInfo.ts create mode 100644 src/openapi/models/UserDictWord.ts diff --git a/openapi.json b/openapi.json index 70d3b1a1e1..c69a47c134 100644 --- a/openapi.json +++ b/openapi.json @@ -1 +1 @@ -{"openapi":"3.0.2","info":{"title":"VOICEVOX ENGINE","description":"VOICEVOXの音声合成エンジンです。","version":"0.10.2"},"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"}],"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"}],"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"}],"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"}],"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"}],"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"}],"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"}],"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"}],"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"}],"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"}}}}}}},"/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"}],"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"}}}}}}}},"/version":{"get":{"tags":["その他"],"summary":"Version","operationId":"version_version_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/speakers":{"get":{"tags":["その他"],"summary":"Speakers","operationId":"speakers_speakers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Speakers Speakers Get","type":"array","items":{"$ref":"#/components/schemas/Speaker"}}}}}}}},"/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"}],"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"}}}}}}}},"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":"音声合成用のクエリ"},"HTTPValidationError":{"title":"HTTPValidationError","type":"object","properties":{"detail":{"title":"Detail","type":"array","items":{"$ref":"#/components/schemas/ValidationError"}}}},"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":"モーラ(子音+母音)ごとの情報"},"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":{"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":"スピーカーのスタイル情報"},"StyleInfo":{"title":"StyleInfo","required":["id","icon","voice_samples"],"type":"object","properties":{"id":{"title":"スタイルID","type":"integer"},"icon":{"title":"当該スタイルのアイコンをbase64エンコードしたもの","type":"string"},"voice_samples":{"title":"voice_sampleのwavファイルをbase64エンコードしたもの","type":"array","items":{"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"}}}}}} \ No newline at end of file +{"openapi":"3.0.2","info":{"title":"VOICEVOX ENGINE","description":"VOICEVOXの音声合成エンジンです。","version":"0.10.4"},"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"}],"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"}}}}}}},"/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"}}}}}}}},"/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"}}}}}}},"/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 アクセント型(音が下がる場所を指す)","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"}],"responses":{"204":{"description":"Successful Response"},"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","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"}],"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"}}}}}}},"/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"}}}}}}}},"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":"音声合成用のクエリ"},"HTTPValidationError":{"title":"HTTPValidationError","type":"object","properties":{"detail":{"title":"Detail","type":"array","items":{"$ref":"#/components/schemas/ValidationError"}}}},"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":"モーラ(子音+母音)ごとの情報"},"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":{"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":"スピーカーのスタイル情報"},"StyleInfo":{"title":"StyleInfo","required":["id","icon","voice_samples"],"type":"object","properties":{"id":{"title":"スタイルID","type":"integer"},"icon":{"title":"当該スタイルのアイコンをbase64エンコードしたもの","type":"string"},"voice_samples":{"title":"voice_sampleのwavファイルをbase64エンコードしたもの","type":"array","items":{"type":"string"}}},"description":"スタイルの追加情報"},"SupportedDevicesInfo":{"title":"SupportedDevicesInfo","required":["cpu","cuda"],"type":"object","properties":{"cpu":{"title":"CPUに対応しているか","type":"boolean"},"cuda":{"title":"CUDA(GPU)に対応しているか","type":"boolean"}},"description":"対応しているデバイスの情報"},"UserDictWord":{"title":"UserDictWord","required":["surface","cost","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"},"cost":{"title":"コストの値","type":"integer"},"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"}}}}}} \ No newline at end of file diff --git a/src/openapi/.openapi-generator/FILES b/src/openapi/.openapi-generator/FILES index c2795e12b5..9a07f61461 100644 --- a/src/openapi/.openapi-generator/FILES +++ b/src/openapi/.openapi-generator/FILES @@ -11,6 +11,8 @@ models/Speaker.ts models/SpeakerInfo.ts models/SpeakerStyle.ts models/StyleInfo.ts +models/SupportedDevicesInfo.ts +models/UserDictWord.ts models/ValidationError.ts models/index.ts runtime.ts diff --git a/src/openapi/apis/DefaultApi.ts b/src/openapi/apis/DefaultApi.ts index 362d3dc035..a4f89511db 100644 --- a/src/openapi/apis/DefaultApi.ts +++ b/src/openapi/apis/DefaultApi.ts @@ -4,7 +4,7 @@ * VOICEVOX ENGINE * VOICEVOXの音声合成エンジンです。 * - * The version of the OpenAPI document: 0.10.2 + * The version of the OpenAPI document: 0.10.4 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). @@ -36,22 +36,37 @@ import { SpeakerInfo, SpeakerInfoFromJSON, SpeakerInfoToJSON, + SupportedDevicesInfo, + SupportedDevicesInfoFromJSON, + SupportedDevicesInfoToJSON, + UserDictWord, + UserDictWordFromJSON, + UserDictWordToJSON, } from '../models'; export interface AccentPhrasesAccentPhrasesPostRequest { text: string; speaker: number; isKana?: boolean; + coreVersion?: string; +} + +export interface AddUserDictWordUserDictWordPostRequest { + surface: string; + pronunciation: string; + accentType: number; } export interface AudioQueryAudioQueryPostRequest { text: string; speaker: number; + coreVersion?: string; } export interface AudioQueryFromPresetAudioQueryFromPresetPostRequest { text: string; presetId: number; + coreVersion?: string; } export interface CancellableSynthesisCancellableSynthesisPostRequest { @@ -63,28 +78,52 @@ export interface ConnectWavesConnectWavesPostRequest { requestBody: Array; } +export interface DeleteUserDictWordUserDictWordWordUuidDeleteRequest { + wordUuid: string; +} + export interface MoraDataMoraDataPostRequest { speaker: number; accentPhrase: Array; + coreVersion?: string; } export interface MoraLengthMoraLengthPostRequest { speaker: number; accentPhrase: Array; + coreVersion?: string; } export interface MoraPitchMoraPitchPostRequest { speaker: number; accentPhrase: Array; + coreVersion?: string; } export interface MultiSynthesisMultiSynthesisPostRequest { speaker: number; audioQuery: Array; + coreVersion?: string; +} + +export interface RewriteUserDictWordUserDictWordWordUuidPutRequest { + wordUuid: string; + surface: string; + pronunciation: string; + accentType: number; } export interface SpeakerInfoSpeakerInfoGetRequest { speakerUuid: string; + coreVersion?: string; +} + +export interface SpeakersSpeakersGetRequest { + coreVersion?: string; +} + +export interface SupportedDevicesSupportedDevicesGetRequest { + coreVersion?: string; } export interface SynthesisMorphingSynthesisMorphingPostRequest { @@ -92,12 +131,14 @@ export interface SynthesisMorphingSynthesisMorphingPostRequest { targetSpeaker: number; morphRate: number; audioQuery: AudioQuery; + coreVersion?: string; } export interface SynthesisSynthesisPostRequest { speaker: number; audioQuery: AudioQuery; enableInterrogativeUpspeak?: boolean; + coreVersion?: string; } /** @@ -113,6 +154,7 @@ export interface DefaultApiInterface { * @param {string} text * @param {number} speaker * @param {boolean} [isKana] + * @param {string} [coreVersion] * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof DefaultApiInterface @@ -125,11 +167,30 @@ export interface DefaultApiInterface { */ accentPhrasesAccentPhrasesPost(requestParameters: AccentPhrasesAccentPhrasesPostRequest, initOverrides?: RequestInit): Promise>; + /** + * ユーザ辞書に言葉を追加します。 Parameters ---------- surface : str 言葉の表層形 pronunciation: str 言葉の発音(カタカナ) accent_type: int アクセント型(音が下がる場所を指す) + * @summary Add User Dict Word + * @param {string} surface + * @param {string} pronunciation + * @param {number} accentType + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApiInterface + */ + addUserDictWordUserDictWordPostRaw(requestParameters: AddUserDictWordUserDictWordPostRequest, initOverrides?: RequestInit): Promise>; + + /** + * ユーザ辞書に言葉を追加します。 Parameters ---------- surface : str 言葉の表層形 pronunciation: str 言葉の発音(カタカナ) accent_type: int アクセント型(音が下がる場所を指す) + * Add User Dict Word + */ + addUserDictWordUserDictWordPost(requestParameters: AddUserDictWordUserDictWordPostRequest, initOverrides?: RequestInit): Promise; + /** * クエリの初期値を得ます。ここで得られたクエリはそのまま音声合成に利用できます。各値の意味は`Schemas`を参照してください。 * @summary 音声合成用のクエリを作成する * @param {string} text * @param {number} speaker + * @param {string} [coreVersion] * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof DefaultApiInterface @@ -147,6 +208,7 @@ export interface DefaultApiInterface { * @summary 音声合成用のクエリをプリセットを用いて作成する * @param {string} text * @param {number} presetId + * @param {string} [coreVersion] * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof DefaultApiInterface @@ -191,6 +253,36 @@ export interface DefaultApiInterface { */ connectWavesConnectWavesPost(requestParameters: ConnectWavesConnectWavesPostRequest, initOverrides?: RequestInit): Promise; + /** + * + * @summary Core Versions + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApiInterface + */ + coreVersionsCoreVersionsGetRaw(initOverrides?: RequestInit): Promise>>; + + /** + * Core Versions + */ + coreVersionsCoreVersionsGet(initOverrides?: RequestInit): Promise>; + + /** + * ユーザ辞書に登録されている言葉を削除します。 Parameters ---------- word_uuid: str 削除する言葉のUUID + * @summary Delete User Dict Word + * @param {string} wordUuid + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApiInterface + */ + deleteUserDictWordUserDictWordWordUuidDeleteRaw(requestParameters: DeleteUserDictWordUserDictWordWordUuidDeleteRequest, initOverrides?: RequestInit): Promise>; + + /** + * ユーザ辞書に登録されている言葉を削除します。 Parameters ---------- word_uuid: str 削除する言葉のUUID + * Delete User Dict Word + */ + deleteUserDictWordUserDictWordWordUuidDelete(requestParameters: DeleteUserDictWordUserDictWordWordUuidDeleteRequest, initOverrides?: RequestInit): Promise; + /** * エンジンが保持しているプリセットの設定を返します Returns ------- presets: List[Preset] プリセットのリスト * @summary Get Presets @@ -206,11 +298,27 @@ export interface DefaultApiInterface { */ getPresetsPresetsGet(initOverrides?: RequestInit): Promise>; + /** + * ユーザ辞書に登録されている単語の一覧を返します。 単語の表層形(surface)は正規化済みの物を返します。 Returns ------- Dict[str, UserDictWord] 単語のUUIDとその詳細 + * @summary Get User Dict Words + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApiInterface + */ + getUserDictWordsUserDictGetRaw(initOverrides?: RequestInit): Promise>; + + /** + * ユーザ辞書に登録されている単語の一覧を返します。 単語の表層形(surface)は正規化済みの物を返します。 Returns ------- Dict[str, UserDictWord] 単語のUUIDとその詳細 + * Get User Dict Words + */ + getUserDictWordsUserDictGet(initOverrides?: RequestInit): Promise<{ [key: string]: UserDictWord; }>; + /** * * @summary アクセント句から音高・音素長を得る * @param {number} speaker * @param {Array} accentPhrase + * @param {string} [coreVersion] * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof DefaultApiInterface @@ -227,6 +335,7 @@ export interface DefaultApiInterface { * @summary アクセント句から音素長を得る * @param {number} speaker * @param {Array} accentPhrase + * @param {string} [coreVersion] * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof DefaultApiInterface @@ -243,6 +352,7 @@ export interface DefaultApiInterface { * @summary アクセント句から音高を得る * @param {number} speaker * @param {Array} accentPhrase + * @param {string} [coreVersion] * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof DefaultApiInterface @@ -259,6 +369,7 @@ export interface DefaultApiInterface { * @summary 複数まとめて音声合成する * @param {number} speaker * @param {Array} audioQuery + * @param {string} [coreVersion] * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof DefaultApiInterface @@ -270,10 +381,30 @@ export interface DefaultApiInterface { */ multiSynthesisMultiSynthesisPost(requestParameters: MultiSynthesisMultiSynthesisPostRequest, initOverrides?: RequestInit): Promise; + /** + * ユーザ辞書に登録されている言葉を更新します。 Parameters ---------- surface : str 言葉の表層形 pronunciation: str 言葉の発音(カタカナ) accent_type: int アクセント型(音が下がる場所を指す) word_uuid: str 更新する言葉のUUID + * @summary Rewrite User Dict Word + * @param {string} wordUuid + * @param {string} surface + * @param {string} pronunciation + * @param {number} accentType + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApiInterface + */ + rewriteUserDictWordUserDictWordWordUuidPutRaw(requestParameters: RewriteUserDictWordUserDictWordWordUuidPutRequest, initOverrides?: RequestInit): Promise>; + + /** + * ユーザ辞書に登録されている言葉を更新します。 Parameters ---------- surface : str 言葉の表層形 pronunciation: str 言葉の発音(カタカナ) accent_type: int アクセント型(音が下がる場所を指す) word_uuid: str 更新する言葉のUUID + * Rewrite User Dict Word + */ + rewriteUserDictWordUserDictWordWordUuidPut(requestParameters: RewriteUserDictWordUserDictWordWordUuidPutRequest, initOverrides?: RequestInit): Promise; + /** * 指定されたspeaker_uuidに関する情報をjson形式で返します。 画像や音声はbase64エンコードされたものが返されます。 Returns ------- ret_data: SpeakerInfo * @summary Speaker Info * @param {string} speakerUuid + * @param {string} [coreVersion] * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof DefaultApiInterface @@ -289,16 +420,32 @@ export interface DefaultApiInterface { /** * * @summary Speakers + * @param {string} [coreVersion] * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof DefaultApiInterface */ - speakersSpeakersGetRaw(initOverrides?: RequestInit): Promise>>; + speakersSpeakersGetRaw(requestParameters: SpeakersSpeakersGetRequest, initOverrides?: RequestInit): Promise>>; /** * Speakers */ - speakersSpeakersGet(initOverrides?: RequestInit): Promise>; + speakersSpeakersGet(requestParameters: SpeakersSpeakersGetRequest, initOverrides?: RequestInit): Promise>; + + /** + * + * @summary Supported Devices + * @param {string} [coreVersion] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApiInterface + */ + supportedDevicesSupportedDevicesGetRaw(requestParameters: SupportedDevicesSupportedDevicesGetRequest, initOverrides?: RequestInit): Promise>; + + /** + * Supported Devices + */ + supportedDevicesSupportedDevicesGet(requestParameters: SupportedDevicesSupportedDevicesGetRequest, initOverrides?: RequestInit): Promise; /** * 指定された2人の話者で音声を合成、指定した割合でモーフィングした音声を得ます。 モーフィングの割合は`morph_rate`で指定でき、0.0でベースの話者、1.0でターゲットの話者に近づきます。 @@ -307,6 +454,7 @@ export interface DefaultApiInterface { * @param {number} targetSpeaker * @param {number} morphRate * @param {AudioQuery} audioQuery + * @param {string} [coreVersion] * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof DefaultApiInterface @@ -325,6 +473,7 @@ export interface DefaultApiInterface { * @param {number} speaker * @param {AudioQuery} audioQuery * @param {boolean} [enableInterrogativeUpspeak] 疑問系のテキストが与えられたら語尾を自動調整する + * @param {string} [coreVersion] * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof DefaultApiInterface @@ -384,6 +533,10 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { queryParameters['is_kana'] = requestParameters.isKana; } + if (requestParameters.coreVersion !== undefined) { + queryParameters['core_version'] = requestParameters.coreVersion; + } + const headerParameters: runtime.HTTPHeaders = {}; const response = await this.request({ @@ -405,6 +558,57 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { return await response.value(); } + /** + * ユーザ辞書に言葉を追加します。 Parameters ---------- surface : str 言葉の表層形 pronunciation: str 言葉の発音(カタカナ) accent_type: int アクセント型(音が下がる場所を指す) + * Add User Dict Word + */ + async addUserDictWordUserDictWordPostRaw(requestParameters: AddUserDictWordUserDictWordPostRequest, initOverrides?: RequestInit): Promise> { + if (requestParameters.surface === null || requestParameters.surface === undefined) { + throw new runtime.RequiredError('surface','Required parameter requestParameters.surface was null or undefined when calling addUserDictWordUserDictWordPost.'); + } + + if (requestParameters.pronunciation === null || requestParameters.pronunciation === undefined) { + throw new runtime.RequiredError('pronunciation','Required parameter requestParameters.pronunciation was null or undefined when calling addUserDictWordUserDictWordPost.'); + } + + if (requestParameters.accentType === null || requestParameters.accentType === undefined) { + throw new runtime.RequiredError('accentType','Required parameter requestParameters.accentType was null or undefined when calling addUserDictWordUserDictWordPost.'); + } + + const queryParameters: any = {}; + + if (requestParameters.surface !== undefined) { + queryParameters['surface'] = requestParameters.surface; + } + + if (requestParameters.pronunciation !== undefined) { + queryParameters['pronunciation'] = requestParameters.pronunciation; + } + + if (requestParameters.accentType !== undefined) { + queryParameters['accent_type'] = requestParameters.accentType; + } + + const headerParameters: runtime.HTTPHeaders = {}; + + const response = await this.request({ + path: `/user_dict_word`, + method: 'POST', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.VoidApiResponse(response); + } + + /** + * ユーザ辞書に言葉を追加します。 Parameters ---------- surface : str 言葉の表層形 pronunciation: str 言葉の発音(カタカナ) accent_type: int アクセント型(音が下がる場所を指す) + * Add User Dict Word + */ + async addUserDictWordUserDictWordPost(requestParameters: AddUserDictWordUserDictWordPostRequest, initOverrides?: RequestInit): Promise { + await this.addUserDictWordUserDictWordPostRaw(requestParameters, initOverrides); + } + /** * クエリの初期値を得ます。ここで得られたクエリはそのまま音声合成に利用できます。各値の意味は`Schemas`を参照してください。 * 音声合成用のクエリを作成する @@ -428,6 +632,10 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { queryParameters['speaker'] = requestParameters.speaker; } + if (requestParameters.coreVersion !== undefined) { + queryParameters['core_version'] = requestParameters.coreVersion; + } + const headerParameters: runtime.HTTPHeaders = {}; const response = await this.request({ @@ -472,6 +680,10 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { queryParameters['preset_id'] = requestParameters.presetId; } + if (requestParameters.coreVersion !== undefined) { + queryParameters['core_version'] = requestParameters.coreVersion; + } + const headerParameters: runtime.HTTPHeaders = {}; const response = await this.request({ @@ -569,6 +781,63 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { return await response.value(); } + /** + * Core Versions + */ + async coreVersionsCoreVersionsGetRaw(initOverrides?: RequestInit): Promise>> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + const response = await this.request({ + path: `/core_versions`, + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.JSONApiResponse(response); + } + + /** + * Core Versions + */ + async coreVersionsCoreVersionsGet(initOverrides?: RequestInit): Promise> { + const response = await this.coreVersionsCoreVersionsGetRaw(initOverrides); + return await response.value(); + } + + /** + * ユーザ辞書に登録されている言葉を削除します。 Parameters ---------- word_uuid: str 削除する言葉のUUID + * Delete User Dict Word + */ + async deleteUserDictWordUserDictWordWordUuidDeleteRaw(requestParameters: DeleteUserDictWordUserDictWordWordUuidDeleteRequest, initOverrides?: RequestInit): Promise> { + if (requestParameters.wordUuid === null || requestParameters.wordUuid === undefined) { + throw new runtime.RequiredError('wordUuid','Required parameter requestParameters.wordUuid was null or undefined when calling deleteUserDictWordUserDictWordWordUuidDelete.'); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + const response = await this.request({ + path: `/user_dict_word/{word_uuid}`.replace(`{${"word_uuid"}}`, encodeURIComponent(String(requestParameters.wordUuid))), + method: 'DELETE', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.VoidApiResponse(response); + } + + /** + * ユーザ辞書に登録されている言葉を削除します。 Parameters ---------- word_uuid: str 削除する言葉のUUID + * Delete User Dict Word + */ + async deleteUserDictWordUserDictWordWordUuidDelete(requestParameters: DeleteUserDictWordUserDictWordWordUuidDeleteRequest, initOverrides?: RequestInit): Promise { + await this.deleteUserDictWordUserDictWordWordUuidDeleteRaw(requestParameters, initOverrides); + } + /** * エンジンが保持しているプリセットの設定を返します Returns ------- presets: List[Preset] プリセットのリスト * Get Presets @@ -597,6 +866,34 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { return await response.value(); } + /** + * ユーザ辞書に登録されている単語の一覧を返します。 単語の表層形(surface)は正規化済みの物を返します。 Returns ------- Dict[str, UserDictWord] 単語のUUIDとその詳細 + * Get User Dict Words + */ + async getUserDictWordsUserDictGetRaw(initOverrides?: RequestInit): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + const response = await this.request({ + path: `/user_dict`, + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => runtime.mapValues(jsonValue, UserDictWordFromJSON)); + } + + /** + * ユーザ辞書に登録されている単語の一覧を返します。 単語の表層形(surface)は正規化済みの物を返します。 Returns ------- Dict[str, UserDictWord] 単語のUUIDとその詳細 + * Get User Dict Words + */ + async getUserDictWordsUserDictGet(initOverrides?: RequestInit): Promise<{ [key: string]: UserDictWord; }> { + const response = await this.getUserDictWordsUserDictGetRaw(initOverrides); + return await response.value(); + } + /** * アクセント句から音高・音素長を得る */ @@ -615,6 +912,10 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { queryParameters['speaker'] = requestParameters.speaker; } + if (requestParameters.coreVersion !== undefined) { + queryParameters['core_version'] = requestParameters.coreVersion; + } + const headerParameters: runtime.HTTPHeaders = {}; headerParameters['Content-Type'] = 'application/json'; @@ -656,6 +957,10 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { queryParameters['speaker'] = requestParameters.speaker; } + if (requestParameters.coreVersion !== undefined) { + queryParameters['core_version'] = requestParameters.coreVersion; + } + const headerParameters: runtime.HTTPHeaders = {}; headerParameters['Content-Type'] = 'application/json'; @@ -697,6 +1002,10 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { queryParameters['speaker'] = requestParameters.speaker; } + if (requestParameters.coreVersion !== undefined) { + queryParameters['core_version'] = requestParameters.coreVersion; + } + const headerParameters: runtime.HTTPHeaders = {}; headerParameters['Content-Type'] = 'application/json'; @@ -738,6 +1047,10 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { queryParameters['speaker'] = requestParameters.speaker; } + if (requestParameters.coreVersion !== undefined) { + queryParameters['core_version'] = requestParameters.coreVersion; + } + const headerParameters: runtime.HTTPHeaders = {}; headerParameters['Content-Type'] = 'application/json'; @@ -761,6 +1074,61 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { return await response.value(); } + /** + * ユーザ辞書に登録されている言葉を更新します。 Parameters ---------- surface : str 言葉の表層形 pronunciation: str 言葉の発音(カタカナ) accent_type: int アクセント型(音が下がる場所を指す) word_uuid: str 更新する言葉のUUID + * Rewrite User Dict Word + */ + async rewriteUserDictWordUserDictWordWordUuidPutRaw(requestParameters: RewriteUserDictWordUserDictWordWordUuidPutRequest, initOverrides?: RequestInit): Promise> { + if (requestParameters.wordUuid === null || requestParameters.wordUuid === undefined) { + throw new runtime.RequiredError('wordUuid','Required parameter requestParameters.wordUuid was null or undefined when calling rewriteUserDictWordUserDictWordWordUuidPut.'); + } + + if (requestParameters.surface === null || requestParameters.surface === undefined) { + throw new runtime.RequiredError('surface','Required parameter requestParameters.surface was null or undefined when calling rewriteUserDictWordUserDictWordWordUuidPut.'); + } + + if (requestParameters.pronunciation === null || requestParameters.pronunciation === undefined) { + throw new runtime.RequiredError('pronunciation','Required parameter requestParameters.pronunciation was null or undefined when calling rewriteUserDictWordUserDictWordWordUuidPut.'); + } + + if (requestParameters.accentType === null || requestParameters.accentType === undefined) { + throw new runtime.RequiredError('accentType','Required parameter requestParameters.accentType was null or undefined when calling rewriteUserDictWordUserDictWordWordUuidPut.'); + } + + const queryParameters: any = {}; + + if (requestParameters.surface !== undefined) { + queryParameters['surface'] = requestParameters.surface; + } + + if (requestParameters.pronunciation !== undefined) { + queryParameters['pronunciation'] = requestParameters.pronunciation; + } + + if (requestParameters.accentType !== undefined) { + queryParameters['accent_type'] = requestParameters.accentType; + } + + const headerParameters: runtime.HTTPHeaders = {}; + + const response = await this.request({ + path: `/user_dict_word/{word_uuid}`.replace(`{${"word_uuid"}}`, encodeURIComponent(String(requestParameters.wordUuid))), + method: 'PUT', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.VoidApiResponse(response); + } + + /** + * ユーザ辞書に登録されている言葉を更新します。 Parameters ---------- surface : str 言葉の表層形 pronunciation: str 言葉の発音(カタカナ) accent_type: int アクセント型(音が下がる場所を指す) word_uuid: str 更新する言葉のUUID + * Rewrite User Dict Word + */ + async rewriteUserDictWordUserDictWordWordUuidPut(requestParameters: RewriteUserDictWordUserDictWordWordUuidPutRequest, initOverrides?: RequestInit): Promise { + await this.rewriteUserDictWordUserDictWordWordUuidPutRaw(requestParameters, initOverrides); + } + /** * 指定されたspeaker_uuidに関する情報をjson形式で返します。 画像や音声はbase64エンコードされたものが返されます。 Returns ------- ret_data: SpeakerInfo * Speaker Info @@ -776,6 +1144,10 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { queryParameters['speaker_uuid'] = requestParameters.speakerUuid; } + if (requestParameters.coreVersion !== undefined) { + queryParameters['core_version'] = requestParameters.coreVersion; + } + const headerParameters: runtime.HTTPHeaders = {}; const response = await this.request({ @@ -800,9 +1172,13 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { /** * Speakers */ - async speakersSpeakersGetRaw(initOverrides?: RequestInit): Promise>> { + async speakersSpeakersGetRaw(requestParameters: SpeakersSpeakersGetRequest, initOverrides?: RequestInit): Promise>> { const queryParameters: any = {}; + if (requestParameters.coreVersion !== undefined) { + queryParameters['core_version'] = requestParameters.coreVersion; + } + const headerParameters: runtime.HTTPHeaders = {}; const response = await this.request({ @@ -818,8 +1194,38 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { /** * Speakers */ - async speakersSpeakersGet(initOverrides?: RequestInit): Promise> { - const response = await this.speakersSpeakersGetRaw(initOverrides); + async speakersSpeakersGet(requestParameters: SpeakersSpeakersGetRequest, initOverrides?: RequestInit): Promise> { + const response = await this.speakersSpeakersGetRaw(requestParameters, initOverrides); + return await response.value(); + } + + /** + * Supported Devices + */ + async supportedDevicesSupportedDevicesGetRaw(requestParameters: SupportedDevicesSupportedDevicesGetRequest, initOverrides?: RequestInit): Promise> { + const queryParameters: any = {}; + + if (requestParameters.coreVersion !== undefined) { + queryParameters['core_version'] = requestParameters.coreVersion; + } + + const headerParameters: runtime.HTTPHeaders = {}; + + const response = await this.request({ + path: `/supported_devices`, + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => SupportedDevicesInfoFromJSON(jsonValue)); + } + + /** + * Supported Devices + */ + async supportedDevicesSupportedDevicesGet(requestParameters: SupportedDevicesSupportedDevicesGetRequest, initOverrides?: RequestInit): Promise { + const response = await this.supportedDevicesSupportedDevicesGetRaw(requestParameters, initOverrides); return await response.value(); } @@ -858,6 +1264,10 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { queryParameters['morph_rate'] = requestParameters.morphRate; } + if (requestParameters.coreVersion !== undefined) { + queryParameters['core_version'] = requestParameters.coreVersion; + } + const headerParameters: runtime.HTTPHeaders = {}; headerParameters['Content-Type'] = 'application/json'; @@ -904,6 +1314,10 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { queryParameters['enable_interrogative_upspeak'] = requestParameters.enableInterrogativeUpspeak; } + if (requestParameters.coreVersion !== undefined) { + queryParameters['core_version'] = requestParameters.coreVersion; + } + const headerParameters: runtime.HTTPHeaders = {}; headerParameters['Content-Type'] = 'application/json'; diff --git a/src/openapi/models/AccentPhrase.ts b/src/openapi/models/AccentPhrase.ts index 4fee061836..1f84864ff5 100644 --- a/src/openapi/models/AccentPhrase.ts +++ b/src/openapi/models/AccentPhrase.ts @@ -4,7 +4,7 @@ * VOICEVOX ENGINE * VOICEVOXの音声合成エンジンです。 * - * The version of the OpenAPI document: 0.10.2 + * The version of the OpenAPI document: 0.10.4 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/src/openapi/models/AudioQuery.ts b/src/openapi/models/AudioQuery.ts index 9ca6010153..fdff626db7 100644 --- a/src/openapi/models/AudioQuery.ts +++ b/src/openapi/models/AudioQuery.ts @@ -4,7 +4,7 @@ * VOICEVOX ENGINE * VOICEVOXの音声合成エンジンです。 * - * The version of the OpenAPI document: 0.10.2 + * The version of the OpenAPI document: 0.10.4 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/src/openapi/models/HTTPValidationError.ts b/src/openapi/models/HTTPValidationError.ts index 4155858c26..68bcd071a5 100644 --- a/src/openapi/models/HTTPValidationError.ts +++ b/src/openapi/models/HTTPValidationError.ts @@ -4,7 +4,7 @@ * VOICEVOX ENGINE * VOICEVOXの音声合成エンジンです。 * - * The version of the OpenAPI document: 0.10.2 + * The version of the OpenAPI document: 0.10.4 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/src/openapi/models/Mora.ts b/src/openapi/models/Mora.ts index 8c516a54da..ee36c7cb3f 100644 --- a/src/openapi/models/Mora.ts +++ b/src/openapi/models/Mora.ts @@ -4,7 +4,7 @@ * VOICEVOX ENGINE * VOICEVOXの音声合成エンジンです。 * - * The version of the OpenAPI document: 0.10.2 + * The version of the OpenAPI document: 0.10.4 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/src/openapi/models/ParseKanaBadRequest.ts b/src/openapi/models/ParseKanaBadRequest.ts index 0499d7f58e..03f4b81cc6 100644 --- a/src/openapi/models/ParseKanaBadRequest.ts +++ b/src/openapi/models/ParseKanaBadRequest.ts @@ -4,7 +4,7 @@ * VOICEVOX ENGINE * VOICEVOXの音声合成エンジンです。 * - * The version of the OpenAPI document: 0.10.2 + * The version of the OpenAPI document: 0.10.4 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/src/openapi/models/Preset.ts b/src/openapi/models/Preset.ts index 68010a5963..95aa5f0d24 100644 --- a/src/openapi/models/Preset.ts +++ b/src/openapi/models/Preset.ts @@ -4,7 +4,7 @@ * VOICEVOX ENGINE * VOICEVOXの音声合成エンジンです。 * - * The version of the OpenAPI document: 0.10.2 + * The version of the OpenAPI document: 0.10.4 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/src/openapi/models/Speaker.ts b/src/openapi/models/Speaker.ts index dd29616783..938f31f5ec 100644 --- a/src/openapi/models/Speaker.ts +++ b/src/openapi/models/Speaker.ts @@ -4,7 +4,7 @@ * VOICEVOX ENGINE * VOICEVOXの音声合成エンジンです。 * - * The version of the OpenAPI document: 0.10.2 + * The version of the OpenAPI document: 0.10.4 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/src/openapi/models/SpeakerInfo.ts b/src/openapi/models/SpeakerInfo.ts index 8a81a7b215..534d9e91fc 100644 --- a/src/openapi/models/SpeakerInfo.ts +++ b/src/openapi/models/SpeakerInfo.ts @@ -4,7 +4,7 @@ * VOICEVOX ENGINE * VOICEVOXの音声合成エンジンです。 * - * The version of the OpenAPI document: 0.10.2 + * The version of the OpenAPI document: 0.10.4 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/src/openapi/models/SpeakerStyle.ts b/src/openapi/models/SpeakerStyle.ts index 144f80576d..796f08c24d 100644 --- a/src/openapi/models/SpeakerStyle.ts +++ b/src/openapi/models/SpeakerStyle.ts @@ -4,7 +4,7 @@ * VOICEVOX ENGINE * VOICEVOXの音声合成エンジンです。 * - * The version of the OpenAPI document: 0.10.2 + * The version of the OpenAPI document: 0.10.4 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/src/openapi/models/StyleInfo.ts b/src/openapi/models/StyleInfo.ts index 4b38a63c45..b1a3a39fda 100644 --- a/src/openapi/models/StyleInfo.ts +++ b/src/openapi/models/StyleInfo.ts @@ -4,7 +4,7 @@ * VOICEVOX ENGINE * VOICEVOXの音声合成エンジンです。 * - * The version of the OpenAPI document: 0.10.2 + * The version of the OpenAPI document: 0.10.4 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/src/openapi/models/SupportedDevicesInfo.ts b/src/openapi/models/SupportedDevicesInfo.ts new file mode 100644 index 0000000000..31028a224f --- /dev/null +++ b/src/openapi/models/SupportedDevicesInfo.ts @@ -0,0 +1,64 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * VOICEVOX ENGINE + * VOICEVOXの音声合成エンジンです。 + * + * The version of the OpenAPI document: 0.10.4 + * + * + * 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 SupportedDevicesInfo + */ +export interface SupportedDevicesInfo { + /** + * + * @type {boolean} + * @memberof SupportedDevicesInfo + */ + cpu: boolean; + /** + * + * @type {boolean} + * @memberof SupportedDevicesInfo + */ + cuda: boolean; +} + +export function SupportedDevicesInfoFromJSON(json: any): SupportedDevicesInfo { + return SupportedDevicesInfoFromJSONTyped(json, false); +} + +export function SupportedDevicesInfoFromJSONTyped(json: any, ignoreDiscriminator: boolean): SupportedDevicesInfo { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'cpu': json['cpu'], + 'cuda': json['cuda'], + }; +} + +export function SupportedDevicesInfoToJSON(value?: SupportedDevicesInfo | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'cpu': value.cpu, + 'cuda': value.cuda, + }; +} + diff --git a/src/openapi/models/UserDictWord.ts b/src/openapi/models/UserDictWord.ts new file mode 100644 index 0000000000..d18407c386 --- /dev/null +++ b/src/openapi/models/UserDictWord.ts @@ -0,0 +1,160 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * VOICEVOX ENGINE + * VOICEVOXの音声合成エンジンです。 + * + * The version of the OpenAPI document: 0.10.4 + * + * + * 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 UserDictWord + */ +export interface UserDictWord { + /** + * + * @type {string} + * @memberof UserDictWord + */ + surface: string; + /** + * + * @type {number} + * @memberof UserDictWord + */ + cost: number; + /** + * + * @type {string} + * @memberof UserDictWord + */ + partOfSpeech: string; + /** + * + * @type {string} + * @memberof UserDictWord + */ + partOfSpeechDetail1: string; + /** + * + * @type {string} + * @memberof UserDictWord + */ + partOfSpeechDetail2: string; + /** + * + * @type {string} + * @memberof UserDictWord + */ + partOfSpeechDetail3: string; + /** + * + * @type {string} + * @memberof UserDictWord + */ + inflectionalType: string; + /** + * + * @type {string} + * @memberof UserDictWord + */ + inflectionalForm: string; + /** + * + * @type {string} + * @memberof UserDictWord + */ + stem: string; + /** + * + * @type {string} + * @memberof UserDictWord + */ + yomi: string; + /** + * + * @type {string} + * @memberof UserDictWord + */ + pronunciation: string; + /** + * + * @type {number} + * @memberof UserDictWord + */ + accentType: number; + /** + * + * @type {number} + * @memberof UserDictWord + */ + moraCount?: number; + /** + * + * @type {string} + * @memberof UserDictWord + */ + accentAssociativeRule: string; +} + +export function UserDictWordFromJSON(json: any): UserDictWord { + return UserDictWordFromJSONTyped(json, false); +} + +export function UserDictWordFromJSONTyped(json: any, ignoreDiscriminator: boolean): UserDictWord { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'surface': json['surface'], + 'cost': json['cost'], + 'partOfSpeech': json['part_of_speech'], + 'partOfSpeechDetail1': json['part_of_speech_detail_1'], + 'partOfSpeechDetail2': json['part_of_speech_detail_2'], + 'partOfSpeechDetail3': json['part_of_speech_detail_3'], + 'inflectionalType': json['inflectional_type'], + 'inflectionalForm': json['inflectional_form'], + 'stem': json['stem'], + 'yomi': json['yomi'], + 'pronunciation': json['pronunciation'], + 'accentType': json['accent_type'], + 'moraCount': !exists(json, 'mora_count') ? undefined : json['mora_count'], + 'accentAssociativeRule': json['accent_associative_rule'], + }; +} + +export function UserDictWordToJSON(value?: UserDictWord | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'surface': value.surface, + 'cost': value.cost, + 'part_of_speech': value.partOfSpeech, + 'part_of_speech_detail_1': value.partOfSpeechDetail1, + 'part_of_speech_detail_2': value.partOfSpeechDetail2, + 'part_of_speech_detail_3': value.partOfSpeechDetail3, + 'inflectional_type': value.inflectionalType, + 'inflectional_form': value.inflectionalForm, + 'stem': value.stem, + 'yomi': value.yomi, + 'pronunciation': value.pronunciation, + 'accent_type': value.accentType, + 'mora_count': value.moraCount, + 'accent_associative_rule': value.accentAssociativeRule, + }; +} + diff --git a/src/openapi/models/ValidationError.ts b/src/openapi/models/ValidationError.ts index 99ee89df50..75f0a6197f 100644 --- a/src/openapi/models/ValidationError.ts +++ b/src/openapi/models/ValidationError.ts @@ -4,7 +4,7 @@ * VOICEVOX ENGINE * VOICEVOXの音声合成エンジンです。 * - * The version of the OpenAPI document: 0.10.2 + * The version of the OpenAPI document: 0.10.4 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/src/openapi/models/index.ts b/src/openapi/models/index.ts index cf6b9162c5..1915bba089 100644 --- a/src/openapi/models/index.ts +++ b/src/openapi/models/index.ts @@ -10,4 +10,6 @@ export * from './Speaker'; export * from './SpeakerInfo'; export * from './SpeakerStyle'; export * from './StyleInfo'; +export * from './SupportedDevicesInfo'; +export * from './UserDictWord'; export * from './ValidationError'; diff --git a/src/openapi/runtime.ts b/src/openapi/runtime.ts index 5a6eb75ddd..73ad1ea9b3 100644 --- a/src/openapi/runtime.ts +++ b/src/openapi/runtime.ts @@ -4,7 +4,7 @@ * VOICEVOX ENGINE * VOICEVOXの音声合成エンジンです。 * - * The version of the OpenAPI document: 0.10.2 + * The version of the OpenAPI document: 0.10.4 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/src/store/audio.ts b/src/store/audio.ts index b1ca45db2c..15f9b3da51 100644 --- a/src/store/audio.ts +++ b/src/store/audio.ts @@ -503,7 +503,8 @@ export const audioStore: VoiceVoxStoreOptions< const speakers = await dispatch("INVOKE_ENGINE_CONNECTOR", { engineKey: engineInfo.key, action: "speakersSpeakersGet", - payload: [], + // 連想配列が第一引数になければ失敗する + payload: [{}], }) .then(toDispatchResponse("speakersSpeakersGet")) .catch((error) => { From c29f56f0bc2bff5274d764414f6f876362c007ea Mon Sep 17 00:00:00 2001 From: Yuto Ashida Date: Sun, 27 Feb 2022 20:12:45 +0900 Subject: [PATCH 12/32] =?UTF-8?q?OpenAPI=E3=81=AE=E5=AE=9A=E7=BE=A9?= =?UTF-8?q?=E3=82=92=E6=9B=B4=E6=96=B0=20(#714)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openapi.json | 2 +- src/openapi/apis/DefaultApi.ts | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/openapi.json b/openapi.json index c69a47c134..d112915d93 100644 --- a/openapi.json +++ b/openapi.json @@ -1 +1 @@ -{"openapi":"3.0.2","info":{"title":"VOICEVOX ENGINE","description":"VOICEVOXの音声合成エンジンです。","version":"0.10.4"},"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"}],"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"}}}}}}},"/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"}}}}}}}},"/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"}}}}}}},"/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 アクセント型(音が下がる場所を指す)","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"}],"responses":{"204":{"description":"Successful Response"},"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","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"}],"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"}}}}}}},"/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"}}}}}}}},"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":"音声合成用のクエリ"},"HTTPValidationError":{"title":"HTTPValidationError","type":"object","properties":{"detail":{"title":"Detail","type":"array","items":{"$ref":"#/components/schemas/ValidationError"}}}},"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":"モーラ(子音+母音)ごとの情報"},"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":{"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":"スピーカーのスタイル情報"},"StyleInfo":{"title":"StyleInfo","required":["id","icon","voice_samples"],"type":"object","properties":{"id":{"title":"スタイルID","type":"integer"},"icon":{"title":"当該スタイルのアイコンをbase64エンコードしたもの","type":"string"},"voice_samples":{"title":"voice_sampleのwavファイルをbase64エンコードしたもの","type":"array","items":{"type":"string"}}},"description":"スタイルの追加情報"},"SupportedDevicesInfo":{"title":"SupportedDevicesInfo","required":["cpu","cuda"],"type":"object","properties":{"cpu":{"title":"CPUに対応しているか","type":"boolean"},"cuda":{"title":"CUDA(GPU)に対応しているか","type":"boolean"}},"description":"対応しているデバイスの情報"},"UserDictWord":{"title":"UserDictWord","required":["surface","cost","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"},"cost":{"title":"コストの値","type":"integer"},"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"}}}}}} \ No newline at end of file +{"openapi":"3.0.2","info":{"title":"VOICEVOX ENGINE","description":"VOICEVOXの音声合成エンジンです。","version":"0.10.4"},"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"}],"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"}}}}}}},"/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"}}}}}}}},"/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"}}}}}}},"/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 アクセント型(音が下がる場所を指す)","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"}],"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","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"}],"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"}}}}}}},"/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"}}}}}}}},"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":"音声合成用のクエリ"},"HTTPValidationError":{"title":"HTTPValidationError","type":"object","properties":{"detail":{"title":"Detail","type":"array","items":{"$ref":"#/components/schemas/ValidationError"}}}},"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":"モーラ(子音+母音)ごとの情報"},"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":{"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":"スピーカーのスタイル情報"},"StyleInfo":{"title":"StyleInfo","required":["id","icon","voice_samples"],"type":"object","properties":{"id":{"title":"スタイルID","type":"integer"},"icon":{"title":"当該スタイルのアイコンをbase64エンコードしたもの","type":"string"},"voice_samples":{"title":"voice_sampleのwavファイルをbase64エンコードしたもの","type":"array","items":{"type":"string"}}},"description":"スタイルの追加情報"},"SupportedDevicesInfo":{"title":"SupportedDevicesInfo","required":["cpu","cuda"],"type":"object","properties":{"cpu":{"title":"CPUに対応しているか","type":"boolean"},"cuda":{"title":"CUDA(GPU)に対応しているか","type":"boolean"}},"description":"対応しているデバイスの情報"},"UserDictWord":{"title":"UserDictWord","required":["surface","cost","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"},"cost":{"title":"コストの値","type":"integer"},"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"}}}}}} \ No newline at end of file diff --git a/src/openapi/apis/DefaultApi.ts b/src/openapi/apis/DefaultApi.ts index a4f89511db..9ade6cfab0 100644 --- a/src/openapi/apis/DefaultApi.ts +++ b/src/openapi/apis/DefaultApi.ts @@ -177,13 +177,13 @@ export interface DefaultApiInterface { * @throws {RequiredError} * @memberof DefaultApiInterface */ - addUserDictWordUserDictWordPostRaw(requestParameters: AddUserDictWordUserDictWordPostRequest, initOverrides?: RequestInit): Promise>; + addUserDictWordUserDictWordPostRaw(requestParameters: AddUserDictWordUserDictWordPostRequest, initOverrides?: RequestInit): Promise>; /** * ユーザ辞書に言葉を追加します。 Parameters ---------- surface : str 言葉の表層形 pronunciation: str 言葉の発音(カタカナ) accent_type: int アクセント型(音が下がる場所を指す) * Add User Dict Word */ - addUserDictWordUserDictWordPost(requestParameters: AddUserDictWordUserDictWordPostRequest, initOverrides?: RequestInit): Promise; + addUserDictWordUserDictWordPost(requestParameters: AddUserDictWordUserDictWordPostRequest, initOverrides?: RequestInit): Promise; /** * クエリの初期値を得ます。ここで得られたクエリはそのまま音声合成に利用できます。各値の意味は`Schemas`を参照してください。 @@ -562,7 +562,7 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { * ユーザ辞書に言葉を追加します。 Parameters ---------- surface : str 言葉の表層形 pronunciation: str 言葉の発音(カタカナ) accent_type: int アクセント型(音が下がる場所を指す) * Add User Dict Word */ - async addUserDictWordUserDictWordPostRaw(requestParameters: AddUserDictWordUserDictWordPostRequest, initOverrides?: RequestInit): Promise> { + async addUserDictWordUserDictWordPostRaw(requestParameters: AddUserDictWordUserDictWordPostRequest, initOverrides?: RequestInit): Promise> { if (requestParameters.surface === null || requestParameters.surface === undefined) { throw new runtime.RequiredError('surface','Required parameter requestParameters.surface was null or undefined when calling addUserDictWordUserDictWordPost.'); } @@ -598,15 +598,16 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface { query: queryParameters, }, initOverrides); - return new runtime.VoidApiResponse(response); + return new runtime.TextApiResponse(response) as any; } /** * ユーザ辞書に言葉を追加します。 Parameters ---------- surface : str 言葉の表層形 pronunciation: str 言葉の発音(カタカナ) accent_type: int アクセント型(音が下がる場所を指す) * Add User Dict Word */ - async addUserDictWordUserDictWordPost(requestParameters: AddUserDictWordUserDictWordPostRequest, initOverrides?: RequestInit): Promise { - await this.addUserDictWordUserDictWordPostRaw(requestParameters, initOverrides); + async addUserDictWordUserDictWordPost(requestParameters: AddUserDictWordUserDictWordPostRequest, initOverrides?: RequestInit): Promise { + const response = await this.addUserDictWordUserDictWordPostRaw(requestParameters, initOverrides); + return await response.value(); } /** From 12efd7abd779cb30de00dc715542050e02ef64c9 Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Mon, 28 Feb 2022 00:46:06 +0900 Subject: [PATCH 13/32] =?UTF-8?q?=E3=83=87=E3=83=95=E3=82=A9=E3=83=AB?= =?UTF-8?q?=E3=83=88=E3=82=B9=E3=82=BF=E3=82=A4=E3=83=AB=E9=81=B8=E6=8A=9E?= =?UTF-8?q?=E3=83=80=E3=82=A4=E3=82=A2=E3=83=AD=E3=82=B0=E3=81=AE=E4=BB=95?= =?UTF-8?q?=E6=A7=98=E5=A4=89=E6=9B=B4=20(#706)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * デフォルトスタイル選択の仕様変更 * 名称修正 --- src/components/AudioCell.vue | 4 --- src/components/DefaultStyleSelectDialog.vue | 21 ++++++++++---- src/components/MenuBar.vue | 2 +- src/store/index.ts | 32 ++++++++++++--------- src/views/Home.vue | 10 ++++--- 5 files changed, 41 insertions(+), 28 deletions(-) diff --git a/src/components/AudioCell.vue b/src/components/AudioCell.vue index eb48060623..eac1a01c85 100644 --- a/src/components/AudioCell.vue +++ b/src/components/AudioCell.vue @@ -385,9 +385,6 @@ export default defineComponent({ textfield.value.focus(); }; - // キャラクター選択 - const isOpenedCharacterList = ref(false); - return { characterInfos, audioItem, @@ -420,7 +417,6 @@ export default defineComponent({ textfield, focusTextField, blurCell, - isOpenedCharacterList, }; }, }); diff --git a/src/components/DefaultStyleSelectDialog.vue b/src/components/DefaultStyleSelectDialog.vue index 4efe678e4a..75c5fad898 100644 --- a/src/components/DefaultStyleSelectDialog.vue +++ b/src/components/DefaultStyleSelectDialog.vue @@ -131,7 +131,8 @@ round outline :icon=" - style.styleId === playing?.styleId && + playing != undefined && + style.styleId === playing.styleId && voiceSampleIndex === playing.index ? 'stop' : 'play_arrow' @@ -141,7 +142,8 @@ @mouseenter="isHoverableStyleItem = false" @mouseleave="isHoverableStyleItem = true" @click.stop=" - style.styleId === playing?.styleId && + playing != undefined && + style.styleId === playing.styleId && voiceSampleIndex === playing.index ? stop() : play(style, voiceSampleIndex) @@ -199,10 +201,17 @@ export default defineComponent({ set: (val) => emit("update:modelValue", val), }); - // アップデートで増えたキャラ・スタイルがあれば、それらに対して起動時にデフォルトスタイル選択・試聴を問うための変数 + // 複数スタイルあるキャラクター + const multiStyleCharacterInfos = computed(() => { + return props.characterInfos.filter( + (characterInfo) => characterInfo.metas.styles.length > 1 + ); + }); + + // アップデートで増えたスタイルがあれば、それらに対して起動時にデフォルトスタイル選択を問うための変数 // その他の場合は、characterInfosと同じになる // FIXME: 現状はスタイルが増えてもデフォルトスタイルを問えないので、そこを改修しなければならない - const showCharacterInfos = ref(props.characterInfos); + const showCharacterInfos = ref(multiStyleCharacterInfos.value); const isFirstTime = ref(false); const selectedStyleIndexes = ref<(number | undefined)[]>([]); @@ -214,7 +223,7 @@ export default defineComponent({ if (!oldValue && newValue) { showCharacterInfos.value = []; selectedStyleIndexes.value = await Promise.all( - props.characterInfos.map(async (info) => { + multiStyleCharacterInfos.value.map(async (info) => { const styles = info.metas.styles; const isUnsetDefaultStyleId = await store.dispatch( "IS_UNSET_DEFAULT_STYLE_ID", @@ -237,7 +246,7 @@ export default defineComponent({ }) ); if (!isFirstTime.value) { - showCharacterInfos.value = props.characterInfos; + showCharacterInfos.value = multiStyleCharacterInfos.value; } else { selectedStyleIndexes.value = showCharacterInfos.value.map( (info) => { diff --git a/src/components/MenuBar.vue b/src/components/MenuBar.vue index d054277ed2..608cf5c82d 100644 --- a/src/components/MenuBar.vue +++ b/src/components/MenuBar.vue @@ -300,7 +300,7 @@ export default defineComponent({ }, { type: "button", - label: "デフォルトスタイル・試聴", + label: "デフォルトスタイル", onClick() { store.dispatch("IS_DEFAULT_STYLE_SELECT_DIALOG_OPEN", { isDefaultStyleSelectDialogOpen: true, diff --git a/src/store/index.ts b/src/store/index.ts index c1e5400153..39c2fcb3cb 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -112,21 +112,27 @@ export const indexStore: VoiceVoxStoreOptions< return await window.electron.isUnsetDefaultStyleId(speakerUuid); }, async LOAD_DEFAULT_STYLE_IDS({ commit, state }) { - const storeDefaultStyleIds = await window.electron.getDefaultStyleIds(); - if (storeDefaultStyleIds.length === 0) { - const characterInfos = await state.characterInfos; - if (characterInfos == undefined) - throw new Error("state.characterInfos == undefined"); - const defaultStyleIds = characterInfos.map((info) => ({ + let defaultStyleIds = await window.electron.getDefaultStyleIds(); + + if (!state.characterInfos) throw new Error("characterInfos is undefined"); + + // デフォルトスタイルが設定されていない場合は0をセットする + // FIXME: 保存しているものとstateのものが異なってしまうので良くない。デフォルトスタイルが未設定の場合はAudioCellsを表示しないようにすべき + const unsetCharacterInfos = state.characterInfos.filter( + (characterInfo) => + !defaultStyleIds.some( + (styleId) => styleId.speakerUuid == characterInfo.metas.speakerUuid + ) + ); + defaultStyleIds = [ + ...defaultStyleIds, + ...unsetCharacterInfos.map((info) => ({ speakerUuid: info.metas.speakerUuid, defaultStyleId: info.metas.styles[0].styleId, - })); - commit("SET_DEFAULT_STYLE_IDS", { defaultStyleIds }); - } else { - commit("SET_DEFAULT_STYLE_IDS", { - defaultStyleIds: storeDefaultStyleIds, - }); - } + })), + ]; + + commit("SET_DEFAULT_STYLE_IDS", { defaultStyleIds }); }, async SET_DEFAULT_STYLE_IDS({ commit }, defaultStyleIds) { commit("SET_DEFAULT_STYLE_IDS", { defaultStyleIds }); diff --git a/src/views/Home.vue b/src/views/Home.vue index 4d52f3f4bd..00c225d617 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -396,13 +396,15 @@ export default defineComponent({ await store.dispatch("LOAD_CHARACTER"); await store.dispatch("LOAD_DEFAULT_STYLE_IDS"); + // スタイルが複数あって未選択なキャラがいる場合はデフォルトスタイル選択ダイアログを表示 let isUnsetDefaultStyleIds = false; if (characterInfos.value == undefined) throw new Error(); for (const info of characterInfos.value) { - isUnsetDefaultStyleIds ||= await store.dispatch( - "IS_UNSET_DEFAULT_STYLE_ID", - { speakerUuid: info.metas.speakerUuid } - ); + isUnsetDefaultStyleIds ||= + info.metas.styles.length > 1 && + (await store.dispatch("IS_UNSET_DEFAULT_STYLE_ID", { + speakerUuid: info.metas.speakerUuid, + })); } isDefaultStyleSelectDialogOpenComputed.value = isUnsetDefaultStyleIds; From 9444d8493d14f66274422273b766cedbf6474c1a Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Mon, 28 Feb 2022 01:39:08 +0900 Subject: [PATCH 14/32] =?UTF-8?q?GTM=E6=83=85=E5=A0=B1=E3=82=92=E6=9B=B8?= =?UTF-8?q?=E3=81=8D=E6=8F=9B=E3=81=88=E3=82=8B=20(#717)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yml | 46 ++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 33bb8b67e6..2618d7b468 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,8 @@ env: VOICEVOX_ENGINE_REPO_URL: "https://github.com/VOICEVOX/voicevox_engine" VOICEVOX_ENGINE_VERSION: 0.10.4 VOICEVOX_RESOURCE_VERSION: 0.10.0 - VOICEVOX_EDITOR_VERSION: |- # releaseのときはタグが、それ以外は0.0.0がバージョン名に + VOICEVOX_EDITOR_VERSION: + |- # releaseのときはタグが、それ以外は0.0.0がバージョン名に ${{ github.event.release.tag_name != '' && github.event.release.tag_name || '0.0.0' }} jobs: @@ -37,7 +38,7 @@ jobs: - artifact_name: linux-noengine-prepackage artifact_path: dist_electron/linux-unpacked package_name: voicevox - linux_artifact_name: 'VOICEVOX.${ext}' + linux_artifact_name: "VOICEVOX.${ext}" linux_executable_name: voicevox sed_name: sed os: ubuntu-18.04 @@ -45,7 +46,7 @@ jobs: - artifact_name: linux-noengine-cpu-prepackage artifact_path: dist_electron/linux-unpacked package_name: voicevox-cpu - linux_artifact_name: 'VOICEVOX.${ext}' + linux_artifact_name: "VOICEVOX.${ext}" linux_executable_name: voicevox sed_name: sed os: ubuntu-18.04 @@ -53,21 +54,21 @@ jobs: - artifact_name: windows-noengine-prepackage artifact_path: dist_electron/win-unpacked package_name: voicevox - nsis_web_artifact_name: 'VOICEVOX Web Setup ${version}.${ext}' + nsis_web_artifact_name: "VOICEVOX Web Setup ${version}.${ext}" sed_name: sed os: windows-2019 # Windows CPU - artifact_name: windows-noengine-cpu-prepackage artifact_path: dist_electron/win-unpacked package_name: voicevox-cpu - nsis_web_artifact_name: 'VOICEVOX-CPU Web Setup ${version}.${ext}' + nsis_web_artifact_name: "VOICEVOX-CPU Web Setup ${version}.${ext}" sed_name: sed os: windows-2019 # macOS CPU - artifact_name: macos-noengine-cpu-prepackage artifact_path: dist_electron/mac package_name: voicevox-cpu - macos_artifact_name: 'VOICEVOX.${ext}' + macos_artifact_name: "VOICEVOX.${ext}" sed_name: gsed os: macos-11 @@ -156,13 +157,6 @@ jobs: run: | df -h - - name: Overwrite .env.production for Linux and macOS - if: startsWith(matrix.os, 'ubuntu-') || startsWith(matrix.os, 'macos-') - shell: bash - run: | - echo 'DEFAULT_ENGINE_INFOS=[{"key":"074fc39e-678b-4c13-8916-ffca8d505d1d","executionEnabled":true,"executionFilePath":"./run","host":"http://127.0.0.1:50021"}]' >> .env.production - echo "VUE_APP_GTM_CONTAINER_ID=GTM-DUMMY" >> .env.production - - name: Checkout Product Version Resource uses: actions/checkout@v2 with: @@ -222,6 +216,19 @@ jobs: cp resource/editor/PRIVACYPOLICY.md public/privacyPolicy.md + - name: Overwrite .env.production for Linux and macOS + if: startsWith(matrix.os, 'ubuntu-') || startsWith(matrix.os, 'macos-') + shell: bash + run: | + "${{ matrix.sed_name }}" -i 's|run.exe|./run|g' .env.production + + - name: Replace .env.production infomations + shell: bash + run: | + # GTM ID + gtm_id=$(jq -r '.VUE_APP_GTM_CONTAINER_ID' resource/editor/metas.json) + "${{ matrix.sed_name }}" -i 's/VUE_APP_GTM_CONTAINER_ID=.*/VUE_APP_GTM_CONTAINER_ID='"$gtm_id"'/' .env.production + - name: Generate public/licenses.json shell: bash run: npm run license:generate -- -o public/licenses.json @@ -248,7 +255,6 @@ jobs: path: | ${{ matrix.artifact_path }} - build-engine-prepackage: env: cache-version: v2 @@ -482,7 +488,6 @@ jobs: name: ${{ matrix.artifact_name }}-zip path: "${{ matrix.compressed_artifact_name }}-${{ env.VOICEVOX_EDITOR_VERSION }}.zip" - build-distributable: if: github.event.release.tag_name != '' # If release needs: [build-engine-prepackage] @@ -505,7 +510,7 @@ jobs: - artifact_name: linux-nvidia-appimage engine_artifact_name: linux-nvidia-prepackage package_name: voicevox - linux_artifact_name: 'VOICEVOX.${ext}' + linux_artifact_name: "VOICEVOX.${ext}" linux_executable_name: voicevox sed_name: sed os: ubuntu-18.04 @@ -513,7 +518,7 @@ jobs: - artifact_name: linux-cpu-appimage engine_artifact_name: linux-cpu-prepackage package_name: voicevox-cpu - linux_artifact_name: 'VOICEVOX.${ext}' + linux_artifact_name: "VOICEVOX.${ext}" linux_executable_name: voicevox sed_name: sed os: ubuntu-18.04 @@ -521,21 +526,21 @@ jobs: - artifact_name: windows-nvidia-nsis-web engine_artifact_name: windows-nvidia-prepackage package_name: voicevox - nsis_web_artifact_name: 'VOICEVOX Web Setup ${version}.${ext}' + nsis_web_artifact_name: "VOICEVOX Web Setup ${version}.${ext}" sed_name: sed os: windows-2019 # Windows CPU - artifact_name: windows-cpu-nsis-web engine_artifact_name: windows-cpu-prepackage package_name: voicevox-cpu - nsis_web_artifact_name: 'VOICEVOX-CPU Web Setup ${version}.${ext}' + nsis_web_artifact_name: "VOICEVOX-CPU Web Setup ${version}.${ext}" sed_name: sed os: windows-2019 # macOS CPU - artifact_name: macos-cpu-dmg engine_artifact_name: macos-cpu-prepackage package_name: voicevox-cpu - macos_artifact_name: 'VOICEVOX ${version}.${ext}' + macos_artifact_name: "VOICEVOX ${version}.${ext}" macos_executable_name: VOICEVOX sed_name: gsed os: macos-11 @@ -713,7 +718,6 @@ jobs: path: | nsis-web-artifact/* - upload-distributable-to-release: if: github.event.release.tag_name != '' # If release needs: [build-distributable] From 8b23db3bfe119679896a3abb93e270877d8ab74d Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Mon, 28 Feb 2022 02:01:39 +0900 Subject: [PATCH 15/32] =?UTF-8?q?=E3=82=AD=E3=83=A3=E3=83=A9=E3=82=AF?= =?UTF-8?q?=E3=82=BF=E3=83=BC=E4=B8=A6=E3=81=B3=E6=9B=BF=E3=81=88=E3=83=80?= =?UTF-8?q?=E3=82=A4=E3=82=A2=E3=83=AD=E3=82=B0=E3=81=AE=E8=BF=BD=E5=8A=A0?= =?UTF-8?q?=20(#707)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * star キャラ表示順序変更画面 stash 順序を保存できるように 一旦完成 スタイルが複数無いキャラはデフォルトスタイル選択ダイアログに表示されないようにする 表示順序 userCharacterOrder a a a a a character-order * デフォルトスタイル・試聴 * fix * a --- src/background.ts | 16 + src/components/AudioCell.vue | 20 +- src/components/CharacterOrderDialog.vue | 537 ++++++++++++++++++++++++ src/components/MenuBar.vue | 12 + src/electron/preload.ts | 8 + src/store/audio.ts | 29 +- src/store/index.ts | 24 ++ src/store/type.ts | 24 ++ src/store/ui.ts | 26 ++ src/type/ipc.d.ts | 10 + src/type/preload.d.ts | 2 + src/views/Home.vue | 28 +- tests/unit/store/Vuex.spec.ts | 3 + 13 files changed, 721 insertions(+), 18 deletions(-) create mode 100644 src/components/CharacterOrderDialog.vue diff --git a/src/background.ts b/src/background.ts index 5e1905ff3d..5f952a0fba 100644 --- a/src/background.ts +++ b/src/background.ts @@ -190,6 +190,7 @@ const store = new Store<{ presets: PresetConfig; hotkeySettings: HotkeySetting[]; toolbarSetting: ToolbarSetting; + userCharacterOrder: string[]; defaultStyleIds: DefaultStyleId[]; currentTheme: string; experimentalSetting: ExperimentalSetting; @@ -261,6 +262,13 @@ const store = new Store<{ }, default: defaultToolbarButtonSetting, }, + userCharacterOrder: { + type: "array", + items: { + type: "string", + }, + default: [], + }, defaultStyleIds: { type: "array", items: { @@ -928,6 +936,14 @@ ipcMainHandle("SAVING_PRESETS", (_, { newPresets }) => { return store.get("presets"); }); +ipcMainHandle("GET_USER_CHARACTER_ORDER", () => { + return store.get("userCharacterOrder"); +}); + +ipcMainHandle("SET_USER_CHARACTER_ORDER", (_, userCharacterOrder) => { + store.set("userCharacterOrder", userCharacterOrder); +}); + ipcMainHandle("IS_UNSET_DEFAULT_STYLE_ID", (_, speakerUuid) => { const defaultStyleIds = store.get("defaultStyleIds"); return !defaultStyleIds.find((style) => style.speakerUuid === speakerUuid); diff --git a/src/components/AudioCell.vue b/src/components/AudioCell.vue index eac1a01c85..3862443ba8 100644 --- a/src/components/AudioCell.vue +++ b/src/components/AudioCell.vue @@ -17,7 +17,7 @@ > @@ -172,7 +172,9 @@ export default defineComponent({ setup(props, { emit }) { const store = useStore(); - const characterInfos = computed(() => store.state.characterInfos); + const userOrderedCharacterInfos = computed( + () => store.getters.USER_ORDERED_CHARACTER_INFOS + ); const audioItem = computed(() => store.state.audioItems[props.audioKey]); const nowPlaying = computed( () => store.state.audioStates[props.audioKey].nowPlaying @@ -184,9 +186,9 @@ export default defineComponent({ const uiLocked = computed(() => store.getters.UI_LOCKED); const selectedCharacterInfo = computed(() => - store.state.characterInfos !== undefined && + userOrderedCharacterInfos.value !== undefined && audioItem.value.styleId !== undefined - ? store.state.characterInfos.find((info) => + ? userOrderedCharacterInfos.value.find((info) => info.metas.styles.find( (style) => style.styleId === audioItem.value.styleId ) @@ -200,12 +202,14 @@ export default defineComponent({ ); const subMenuOpenFlags = ref( - [...Array(characterInfos.value?.length)].map(() => false) + [...Array(userOrderedCharacterInfos.value?.length)].map(() => false) ); const reassignSubMenuOpen = debounce((idx: number) => { if (subMenuOpenFlags.value[idx]) return; - const arr = [...Array(characterInfos.value?.length)].map(() => false); + const arr = [...Array(userOrderedCharacterInfos.value?.length)].map( + () => false + ); arr[idx] = true; subMenuOpenFlags.value = arr; }, 100); @@ -248,7 +252,7 @@ export default defineComponent({ }); }; const getDefaultStyle = (speakerUuid: string) => { - const characterInfo = characterInfos.value?.find( + const characterInfo = userOrderedCharacterInfos.value?.find( (info) => info.metas.speakerUuid === speakerUuid ); const defaultStyleId = store.state.defaultStyleIds.find( @@ -386,7 +390,7 @@ export default defineComponent({ }; return { - characterInfos, + userOrderedCharacterInfos, audioItem, deleteButtonEnable, uiLocked, diff --git a/src/components/CharacterOrderDialog.vue b/src/components/CharacterOrderDialog.vue new file mode 100644 index 0000000000..db31ee34e1 --- /dev/null +++ b/src/components/CharacterOrderDialog.vue @@ -0,0 +1,537 @@ + + + + + diff --git a/src/components/MenuBar.vue b/src/components/MenuBar.vue index 608cf5c82d..a8b3d311ce 100644 --- a/src/components/MenuBar.vue +++ b/src/components/MenuBar.vue @@ -181,6 +181,9 @@ export default defineComponent({ store.dispatch("IS_TOOLBAR_SETTING_DIALOG_OPEN", { isToolbarSettingDialogOpen: false, }); + store.dispatch("IS_CHARACTER_ORDER_DIALOG_OPEN", { + isCharacterOrderDialogOpen: false, + }); store.dispatch("IS_DEFAULT_STYLE_SELECT_DIALOG_OPEN", { isDefaultStyleSelectDialogOpen: false, }); @@ -298,6 +301,15 @@ export default defineComponent({ }); }, }, + { + type: "button", + label: "キャラクター並び替え・試聴", + onClick() { + store.dispatch("IS_CHARACTER_ORDER_DIALOG_OPEN", { + isCharacterOrderDialogOpen: true, + }); + }, + }, { type: "button", label: "デフォルトスタイル", diff --git a/src/electron/preload.ts b/src/electron/preload.ts index 66cc50f544..f0c9afb6ab 100644 --- a/src/electron/preload.ts +++ b/src/electron/preload.ts @@ -216,6 +216,14 @@ const api: Sandbox = { return ipcRenderer.invoke("TOOLBAR_SETTING", { newData }); }, + getUserCharacterOrder: async () => { + return await ipcRendererInvoke("GET_USER_CHARACTER_ORDER"); + }, + + setUserCharacterOrder: async (userCharacterOrder) => { + await ipcRendererInvoke("SET_USER_CHARACTER_ORDER", userCharacterOrder); + }, + isUnsetDefaultStyleId: async (speakerUuid: string) => { return await ipcRendererInvoke("IS_UNSET_DEFAULT_STYLE_ID", speakerUuid); }, diff --git a/src/store/audio.ts b/src/store/audio.ts index 15f9b3da51..036d7d7bdc 100644 --- a/src/store/audio.ts +++ b/src/store/audio.ts @@ -65,17 +65,17 @@ async function generateUniqueIdAndQuery( function parseTextFile( body: string, defaultStyleIds: DefaultStyleId[], - characterInfos?: CharacterInfo[] + userOrderedCharacterInfos: CharacterInfo[] ): AudioItem[] { const characters = new Map(); { const uuid2StyleIds = new Map(); - for (const defaultStyleId of defaultStyleIds || []) { + for (const defaultStyleId of defaultStyleIds) { const speakerUuid = defaultStyleId.speakerUuid; const styleId = defaultStyleId.defaultStyleId; uuid2StyleIds.set(speakerUuid, styleId); } - for (const characterInfo of characterInfos || []) { + for (const characterInfo of userOrderedCharacterInfos) { const uuid = characterInfo.metas.speakerUuid; const styleId = uuid2StyleIds.get(uuid) ?? characterInfo.metas.styles[0].styleId; @@ -87,7 +87,7 @@ function parseTextFile( const audioItems: AudioItem[] = []; const seps = [",", "\r\n", "\n"]; - let lastStyleId = 0; + let lastStyleId = userOrderedCharacterInfos[0].metas.styles[0].styleId; for (const splittedText of body.split(new RegExp(`${seps.join("|")}`, "g"))) { const styleId = characters.get(splittedText); if (styleId !== undefined) { @@ -175,6 +175,14 @@ export const audioStore: VoiceVoxStoreOptions< ? audioElements[state._activeAudioKey]?.currentTime : undefined; }, + USER_ORDERED_CHARACTER_INFOS: (state) => { + const characterInfos = state.characterInfos?.slice(); + return characterInfos?.sort( + (a, b) => + state.userCharacterOrder.indexOf(a.metas.speakerUuid) - + state.userCharacterOrder.indexOf(b.metas.speakerUuid) + ); + }, }, mutations: { @@ -597,16 +605,17 @@ export const audioStore: VoiceVoxStoreOptions< //baseAudioItemのうち、textとstyleIdは別途与えられるので引き継がない if (state.defaultStyleIds == undefined) throw new Error("state.defaultStyleIds == undefined"); - if (state.characterInfos == undefined) + if (getters.USER_ORDERED_CHARACTER_INFOS == undefined) throw new Error("state.characterInfos == undefined"); - const characterInfos = state.characterInfos; + const userOrderedCharacterInfos = getters.USER_ORDERED_CHARACTER_INFOS; const text = payload.text ?? ""; const styleId = payload.styleId ?? state.defaultStyleIds[ state.defaultStyleIds.findIndex( - (x) => x.speakerUuid === characterInfos[0].metas.speakerUuid // FIXME: defaultStyleIds内にspeakerUuidがない場合がある + (x) => + x.speakerUuid === userOrderedCharacterInfos[0].metas.speakerUuid // FIXME: defaultStyleIds内にspeakerUuidがない場合がある ) ].defaultStyleId; const baseAudioItem = payload.baseAudioItem; @@ -1880,7 +1889,7 @@ export const audioCommandStore: VoiceVoxStoreOptions< }, COMMAND_IMPORT_FROM_FILE: createUILockAction( async ( - { state, commit, dispatch }, + { state, commit, dispatch, getters }, { filePath }: { filePath?: string } ) => { if (!filePath) { @@ -1905,10 +1914,12 @@ export const audioCommandStore: VoiceVoxStoreOptions< : undefined; } + if (!getters.USER_ORDERED_CHARACTER_INFOS) + throw new Error("USER_ORDERED_CHARACTER_INFOS == undefined"); for (const { text, styleId } of parseTextFile( body, state.defaultStyleIds, - state.characterInfos + getters.USER_ORDERED_CHARACTER_INFOS )) { //パラメータ引き継ぎがONの場合は話速等のパラメータを引き継いでテキスト欄を作成する //パラメータ引き継ぎがOFFの場合、baseAudioItemがundefinedになっているのでパラメータ引き継ぎは行われない diff --git a/src/store/index.ts b/src/store/index.ts index 39c2fcb3cb..b73afa1ebe 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -35,6 +35,7 @@ export const storeKey: InjectionKey< export const indexStoreState: IndexStoreState = { defaultStyleIds: [], + userCharacterOrder: [], }; export const indexStore: VoiceVoxStoreOptions< @@ -70,6 +71,9 @@ export const indexStore: VoiceVoxStoreOptions< } } }, + SET_USER_CHARACTER_ORDER(state, { userCharacterOrder }) { + state.userCharacterOrder = userCharacterOrder; + }, }, actions: { async GET_HOW_TO_USE_TEXT() { @@ -108,6 +112,26 @@ export const indexStore: VoiceVoxStoreOptions< LOG_INFO(_, ...params: unknown[]) { window.electron.logInfo(...params); }, + async LOAD_USER_CHARACTER_ORDER({ commit }) { + const userCharacterOrder = await window.electron.getUserCharacterOrder(); + commit("SET_USER_CHARACTER_ORDER", { userCharacterOrder }); + }, + async SET_USER_CHARACTER_ORDER({ commit }, userCharacterOrder) { + commit("SET_USER_CHARACTER_ORDER", { userCharacterOrder }); + await window.electron.setUserCharacterOrder(userCharacterOrder); + }, + GET_NEW_CHARACTERS({ state }) { + if (!state.characterInfos) throw new Error("characterInfos is undefined"); + + // キャラクター表示順序に含まれていなければ新規キャラとみなす + const allSpeakerUuid = state.characterInfos.map( + (characterInfo) => characterInfo.metas.speakerUuid + ); + const newSpeakerUuid = allSpeakerUuid.filter( + (speakerUuid) => !state.userCharacterOrder.includes(speakerUuid) + ); + return newSpeakerUuid; + }, async IS_UNSET_DEFAULT_STYLE_ID(_, { speakerUuid }) { return await window.electron.isUnsetDefaultStyleId(speakerUuid); }, diff --git a/src/store/type.ts b/src/store/type.ts index 1f27d2b7af..fa3e0ee198 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -127,6 +127,10 @@ type AudioStoreTypes = { mutation: { characterInfos: CharacterInfo[] }; }; + USER_ORDERED_CHARACTER_INFOS: { + getter: CharacterInfo[] | undefined; + }; + GENERATE_AUDIO_KEY: { action(): string; }; @@ -614,6 +618,7 @@ export type CommandActions = StoreType; export type IndexStoreState = { defaultStyleIds: DefaultStyleId[]; + userCharacterOrder: string[]; }; type IndexStoreTypes = { @@ -662,6 +667,19 @@ type IndexStoreTypes = { action(payload: DefaultStyleId[]): void; }; + LOAD_USER_CHARACTER_ORDER: { + action(): Promise; + }; + + SET_USER_CHARACTER_ORDER: { + mutation: { userCharacterOrder: string[] }; + action(payload: string[]): void; + }; + + GET_NEW_CHARACTERS: { + action(): string[]; + }; + SHOW_WARNING_DIALOG: { action(payload: { title: string; @@ -827,6 +845,7 @@ export type UiStoreState = { activePointScrollMode: ActivePointScrollMode; isHelpDialogOpen: boolean; isSettingDialogOpen: boolean; + isCharacterOrderDialogOpen: boolean; isDefaultStyleSelectDialogOpen: boolean; isHotkeySettingDialogOpen: boolean; isToolbarSettingDialogOpen: boolean; @@ -908,6 +927,11 @@ type UiStoreTypes = { action(): void; }; + IS_CHARACTER_ORDER_DIALOG_OPEN: { + mutation: { isCharacterOrderDialogOpen: boolean }; + action(payload: { isCharacterOrderDialogOpen: boolean }): void; + }; + IS_DEFAULT_STYLE_SELECT_DIALOG_OPEN: { mutation: { isDefaultStyleSelectDialogOpen: boolean }; action(payload: { isDefaultStyleSelectDialogOpen: boolean }): void; diff --git a/src/store/ui.ts b/src/store/ui.ts index 0cd184ffb5..c16f255aaf 100644 --- a/src/store/ui.ts +++ b/src/store/ui.ts @@ -37,6 +37,7 @@ export const uiStoreState: UiStoreState = { isSettingDialogOpen: false, isHotkeySettingDialogOpen: false, isToolbarSettingDialogOpen: false, + isCharacterOrderDialogOpen: false, isDefaultStyleSelectDialogOpen: false, isAcceptRetrieveTelemetryDialogOpen: false, isAcceptTermsDialogOpen: false, @@ -96,6 +97,12 @@ export const uiStore: VoiceVoxStoreOptions = ) { state.isToolbarSettingDialogOpen = isToolbarSettingDialogOpen; }, + IS_CHARACTER_ORDER_DIALOG_OPEN( + state, + { isCharacterOrderDialogOpen }: { isCharacterOrderDialogOpen: boolean } + ) { + state.isCharacterOrderDialogOpen = isCharacterOrderDialogOpen; + }, IS_DEFAULT_STYLE_SELECT_DIALOG_OPEN( state, { @@ -243,6 +250,25 @@ export const uiStore: VoiceVoxStoreOptions = ON_VUEX_READY() { window.electron.vuexReady(); }, + async IS_CHARACTER_ORDER_DIALOG_OPEN( + { state, commit }, + { isCharacterOrderDialogOpen } + ) { + if (state.isCharacterOrderDialogOpen === isCharacterOrderDialogOpen) + return; + + if (isCharacterOrderDialogOpen) { + commit("LOCK_UI"); + commit("LOCK_MENUBAR"); + } else { + commit("UNLOCK_UI"); + commit("UNLOCK_MENUBAR"); + } + + commit("IS_CHARACTER_ORDER_DIALOG_OPEN", { + isCharacterOrderDialogOpen, + }); + }, async IS_DEFAULT_STYLE_SELECT_DIALOG_OPEN( { state, commit }, { isDefaultStyleSelectDialogOpen } diff --git a/src/type/ipc.d.ts b/src/type/ipc.d.ts index 5d2365aa3b..67a2491737 100644 --- a/src/type/ipc.d.ts +++ b/src/type/ipc.d.ts @@ -198,6 +198,16 @@ type IpcIHData = { return: import("@/type/preload").ToolbarSetting; }; + GET_USER_CHARACTER_ORDER: { + args: []; + return: string[]; + }; + + SET_USER_CHARACTER_ORDER: { + args: [string[]]; + return: void; + }; + IS_UNSET_DEFAULT_STYLE_ID: { args: [speakerUuid: string]; return: boolean; diff --git a/src/type/preload.d.ts b/src/type/preload.d.ts index 0f18530fe6..2f1e7e2a03 100644 --- a/src/type/preload.d.ts +++ b/src/type/preload.d.ts @@ -67,6 +67,8 @@ export interface Sandbox { presetItems: Record; presetKeys: string[]; }): Promise; + getUserCharacterOrder(): Promise; + setUserCharacterOrder(userCharacterOrder: string[]): Promise; isUnsetDefaultStyleId(speakerUuid: string): Promise; getDefaultStyleIds(): Promise; setDefaultStyleIds( diff --git a/src/views/Home.vue b/src/views/Home.vue index 00c225d617..c271f9c8f2 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -125,6 +125,11 @@ + 0; + // スタイルが複数あって未選択なキャラがいる場合はデフォルトスタイル選択ダイアログを表示 let isUnsetDefaultStyleIds = false; if (characterInfos.value == undefined) throw new Error(); @@ -477,11 +489,23 @@ export default defineComponent({ }), }); - // デフォルトスタイル選択 + // キャラクター並び替え const characterInfos = computed(() => store.state.characterInfos); + const isCharacterOrderDialogOpenComputed = computed({ + get: () => + !store.state.isAcceptTermsDialogOpen && + store.state.isCharacterOrderDialogOpen, + set: (val) => + store.dispatch("IS_CHARACTER_ORDER_DIALOG_OPEN", { + isCharacterOrderDialogOpen: val, + }), + }); + + // デフォルトスタイル選択 const isDefaultStyleSelectDialogOpenComputed = computed({ get: () => !store.state.isAcceptTermsDialogOpen && + !store.state.isCharacterOrderDialogOpen && store.state.isDefaultStyleSelectDialogOpen, set: (val) => store.dispatch("IS_DEFAULT_STYLE_SELECT_DIALOG_OPEN", { @@ -492,6 +516,7 @@ export default defineComponent({ const isAcceptRetrieveTelemetryDialogOpenComputed = computed({ get: () => !store.state.isAcceptTermsDialogOpen && + !store.state.isCharacterOrderDialogOpen && !store.state.isDefaultStyleSelectDialogOpen && store.state.isAcceptRetrieveTelemetryDialogOpen, set: (val) => @@ -550,6 +575,7 @@ export default defineComponent({ isHotkeySettingDialogOpenComputed, isToolbarSettingDialogOpenComputed, characterInfos, + isCharacterOrderDialogOpenComputed, isDefaultStyleSelectDialogOpenComputed, isAcceptRetrieveTelemetryDialogOpenComputed, isAcceptTermsDialogOpenComputed, diff --git a/tests/unit/store/Vuex.spec.ts b/tests/unit/store/Vuex.spec.ts index d80840bb6a..7636467800 100644 --- a/tests/unit/store/Vuex.spec.ts +++ b/tests/unit/store/Vuex.spec.ts @@ -19,6 +19,7 @@ describe("store/vuex.js test", () => { state: { engineState: "STARTING", defaultStyleIds: [], + userCharacterOrder: [], audioItems: {}, audioKeys: [], audioStates: {}, @@ -35,6 +36,7 @@ describe("store/vuex.js test", () => { isSettingDialogOpen: false, isHotkeySettingDialogOpen: false, isToolbarSettingDialogOpen: false, + isCharacterOrderDialogOpen: false, isDefaultStyleSelectDialogOpen: false, isAcceptRetrieveTelemetryDialogOpen: false, isAcceptTermsDialogOpen: false, @@ -136,6 +138,7 @@ describe("store/vuex.js test", () => { assert.equal(store.state.isHelpDialogOpen, false); assert.equal(store.state.isSettingDialogOpen, false); assert.equal(store.state.isHotkeySettingDialogOpen, false); + assert.equal(store.state.isCharacterOrderDialogOpen, false); assert.equal(store.state.isDefaultStyleSelectDialogOpen, false); assert.equal(store.state.isAcceptRetrieveTelemetryDialogOpen, false); assert.equal(store.state.isAcceptTermsDialogOpen, false); From a7c2b33b15e76f8ec7850bcca2796c106c474961 Mon Sep 17 00:00:00 2001 From: Yuto Ashida Date: Mon, 28 Feb 2022 03:37:08 +0900 Subject: [PATCH 16/32] =?UTF-8?q?=E8=BE=9E=E6=9B=B8=E3=81=AE=E7=B7=A8?= =?UTF-8?q?=E9=9B=86UI=E3=82=92=E8=BF=BD=E5=8A=A0=20(#716)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add dictionary manage dialog * add word list * refactor engine info * fix miss (remove "root") * add accent phrase table * add play button * fix design * add save button * make word selectable * hook discard or not dialog * reset all if close dictionary manage dialog * fix miss * move loading dict process * make word rewritable and fix error * fix miss of display accent and set yomi * fix miss of set yomi * fix computed * set selected id when add user dict word * error handling * convert surface * dict -> word * refactor close process * add remove button * add new button * fix miss * add reset button * improve type * sort word list * fix test * remove form, use key press event * add comment * add fixme comment --- src/components/DictionaryManageDialog.vue | 794 ++++++++++++++++++++++ src/components/MenuBar.vue | 9 + src/store/audio.ts | 2 +- src/store/type.ts | 6 + src/store/ui.ts | 28 + src/views/Home.vue | 13 + tests/unit/store/Vuex.spec.ts | 2 + 7 files changed, 853 insertions(+), 1 deletion(-) create mode 100644 src/components/DictionaryManageDialog.vue diff --git a/src/components/DictionaryManageDialog.vue b/src/components/DictionaryManageDialog.vue new file mode 100644 index 0000000000..3715c56109 --- /dev/null +++ b/src/components/DictionaryManageDialog.vue @@ -0,0 +1,794 @@ + + + + + diff --git a/src/components/MenuBar.vue b/src/components/MenuBar.vue index a8b3d311ce..cde84c751b 100644 --- a/src/components/MenuBar.vue +++ b/src/components/MenuBar.vue @@ -319,6 +319,15 @@ export default defineComponent({ }); }, }, + { + type: "button", + label: "辞書管理", + onClick() { + store.dispatch("IS_DICTIONARY_MANAGE_DIALOG_OPEN", { + isDictionaryManageDialogOpen: true, + }); + }, + }, { type: "separator" }, { type: "button", diff --git a/src/store/audio.ts b/src/store/audio.ts index 036d7d7bdc..5dadf31257 100644 --- a/src/store/audio.ts +++ b/src/store/audio.ts @@ -2300,7 +2300,7 @@ export const audioCommandStore: VoiceVoxStoreOptions< }; // FIXME: ProxyStoreのactionとVuexの組み合わせでReturnValueの型付けが中途半端になり、Promiseになってしまっている -const toDispatchResponse = +export const toDispatchResponse = (_: T) => ( // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/src/store/type.ts b/src/store/type.ts index fa3e0ee198..6b892c7ac0 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -851,6 +851,7 @@ export type UiStoreState = { isToolbarSettingDialogOpen: boolean; isAcceptRetrieveTelemetryDialogOpen: boolean; isAcceptTermsDialogOpen: boolean; + isDictionaryManageDialogOpen: boolean; isMaximized: boolean; isPinned: boolean; isFullscreen: boolean; @@ -923,6 +924,11 @@ type UiStoreTypes = { action(payload: { isAcceptTermsDialogOpen: boolean }): void; }; + IS_DICTIONARY_MANAGE_DIALOG_OPEN: { + mutation: { isDictionaryManageDialogOpen: boolean }; + action(payload: { isDictionaryManageDialogOpen: boolean }): void; + }; + ON_VUEX_READY: { action(): void; }; diff --git a/src/store/ui.ts b/src/store/ui.ts index c16f255aaf..52fa38390a 100644 --- a/src/store/ui.ts +++ b/src/store/ui.ts @@ -41,6 +41,7 @@ export const uiStoreState: UiStoreState = { isDefaultStyleSelectDialogOpen: false, isAcceptRetrieveTelemetryDialogOpen: false, isAcceptTermsDialogOpen: false, + isDictionaryManageDialogOpen: false, isMaximized: false, isPinned: false, isFullscreen: false, @@ -111,6 +112,14 @@ export const uiStore: VoiceVoxStoreOptions = ) { state.isDefaultStyleSelectDialogOpen = isDefaultStyleSelectDialogOpen; }, + IS_DICTIONARY_MANAGE_DIALOG_OPEN( + state, + { + isDictionaryManageDialogOpen, + }: { isDictionaryManageDialogOpen: boolean } + ) { + state.isDictionaryManageDialogOpen = isDictionaryManageDialogOpen; + }, IS_ACCEPT_RETRIEVE_TELEMETRY_DIALOG_OPEN( state, { isAcceptRetrieveTelemetryDialogOpen } @@ -291,6 +300,25 @@ export const uiStore: VoiceVoxStoreOptions = isDefaultStyleSelectDialogOpen, }); }, + async IS_DICTIONARY_MANAGE_DIALOG_OPEN( + { state, commit }, + { isDictionaryManageDialogOpen } + ) { + if (state.isDictionaryManageDialogOpen === isDictionaryManageDialogOpen) + return; + + if (isDictionaryManageDialogOpen) { + commit("LOCK_UI"); + commit("LOCK_MENUBAR"); + } else { + commit("UNLOCK_UI"); + commit("UNLOCK_MENUBAR"); + } + + commit("IS_DICTIONARY_MANAGE_DIALOG_OPEN", { + isDictionaryManageDialogOpen, + }); + }, async IS_ACCEPT_RETRIEVE_TELEMETRY_DIALOG_OPEN( { state, commit }, { isAcceptRetrieveTelemetryDialogOpen } diff --git a/src/views/Home.vue b/src/views/Home.vue index c271f9c8f2..8b97838aed 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -135,6 +135,7 @@ :characterInfos="characterInfos" v-model="isDefaultStyleSelectDialogOpenComputed" /> + @@ -166,6 +167,7 @@ import DefaultStyleSelectDialog from "@/components/DefaultStyleSelectDialog.vue" import CharacterOrderDialog from "@/components/CharacterOrderDialog.vue"; import AcceptRetrieveTelemetryDialog from "@/components/AcceptRetrieveTelemetryDialog.vue"; import AcceptTermsDialog from "@/components/AcceptTermsDialog.vue"; +import DictionaryManageDialog from "@/components/DictionaryManageDialog.vue"; import { AudioItem } from "@/store/type"; import { QResizeObserver } from "quasar"; import path from "path"; @@ -191,6 +193,7 @@ export default defineComponent({ CharacterOrderDialog, AcceptRetrieveTelemetryDialog, AcceptTermsDialog, + DictionaryManageDialog, }, setup() { @@ -513,6 +516,15 @@ export default defineComponent({ }), }); + // 辞書管理 + const isDictionaryManageDialogOpenComputed = computed({ + get: () => store.state.isDictionaryManageDialogOpen, + set: (val) => + store.dispatch("IS_DICTIONARY_MANAGE_DIALOG_OPEN", { + isDictionaryManageDialogOpen: val, + }), + }); + const isAcceptRetrieveTelemetryDialogOpenComputed = computed({ get: () => !store.state.isAcceptTermsDialogOpen && @@ -577,6 +589,7 @@ export default defineComponent({ characterInfos, isCharacterOrderDialogOpenComputed, isDefaultStyleSelectDialogOpenComputed, + isDictionaryManageDialogOpenComputed, isAcceptRetrieveTelemetryDialogOpenComputed, isAcceptTermsDialogOpenComputed, dragEventCounter, diff --git a/tests/unit/store/Vuex.spec.ts b/tests/unit/store/Vuex.spec.ts index 7636467800..31fd0b2714 100644 --- a/tests/unit/store/Vuex.spec.ts +++ b/tests/unit/store/Vuex.spec.ts @@ -38,6 +38,7 @@ describe("store/vuex.js test", () => { isToolbarSettingDialogOpen: false, isCharacterOrderDialogOpen: false, isDefaultStyleSelectDialogOpen: false, + isDictionaryManageDialogOpen: false, isAcceptRetrieveTelemetryDialogOpen: false, isAcceptTermsDialogOpen: false, isMaximized: false, @@ -140,6 +141,7 @@ describe("store/vuex.js test", () => { assert.equal(store.state.isHotkeySettingDialogOpen, false); assert.equal(store.state.isCharacterOrderDialogOpen, false); assert.equal(store.state.isDefaultStyleSelectDialogOpen, false); + assert.equal(store.state.isDictionaryManageDialogOpen, false); assert.equal(store.state.isAcceptRetrieveTelemetryDialogOpen, false); assert.equal(store.state.isAcceptTermsDialogOpen, false); assert.equal(store.state.isMaximized, false); From e6654712eaff879d402e7589a7dd288dd697e353 Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Mon, 28 Feb 2022 07:17:07 +0900 Subject: [PATCH 17/32] =?UTF-8?q?=E8=BE=9E=E6=9B=B8=E7=99=BB=E9=8C=B2UI?= =?UTF-8?q?=E3=81=AE=E3=83=87=E3=82=B6=E3=82=A4=E3=83=B3=E3=81=A1=E3=82=87?= =?UTF-8?q?=E3=81=A3=E3=81=A8=E4=BF=AE=E6=AD=A3)=20(#718)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit っと修正 --- src/components/DictionaryManageDialog.vue | 133 +++++++++++++++------- src/components/MenuBar.vue | 2 +- src/styles/_index.scss | 7 ++ src/views/Home.vue | 2 +- 4 files changed, 101 insertions(+), 43 deletions(-) diff --git a/src/components/DictionaryManageDialog.vue b/src/components/DictionaryManageDialog.vue index 3715c56109..b3a1f09590 100644 --- a/src/components/DictionaryManageDialog.vue +++ b/src/components/DictionaryManageDialog.vue @@ -11,7 +11,9 @@ - 辞書管理 + 読み方&アクセント辞書
-
単語一覧
+
+
+
単語一覧
+
+ 削除 +
+
+ 編集 +
+
+ 追加 +
+
@@ -51,18 +86,10 @@ -
- 新規登録 -
-
+ + +
単語
アクセント調整
- 語尾のアクセントを考慮するため、「は(ワ)」が自動で挿入されます。 + 語尾のアクセントを考慮するため、「が」が自動で挿入されます。
@@ -119,7 +146,9 @@
{{ mora.text }} @@ -144,7 +173,7 @@ v-if="moraIndex < accentPhrase.moras.length - 1" class="splitter-cell" :style="{ - 'grid-column': `${moraIndex * 2 + 2} / span 1`, + gridColumn: `${moraIndex * 2 + 2} / span 1`, }" /> @@ -152,6 +181,7 @@
+ リセット - 削除キャンセル
@@ -285,9 +314,11 @@ export default defineComponent({ }; const closeDialogProcess = () => { dictionaryManageDialogOpenedComputed.value = false; - resetSelect(); + cancel(); }; + const wordEditing = ref(false); + const surfaceInput = ref(); const yomiInput = ref(); const yomiFocusWhenEnter = (event: KeyboardEvent) => { @@ -314,9 +345,14 @@ export default defineComponent({ setYomi(userDict.value[id].yomi, true); }; const newWord = () => { + wordEditing.value = true; resetSelect(); surfaceInput.value?.focus(); }; + const cancel = () => { + wordEditing.value = false; + resetSelect(); + }; const kanaRegex = createKanaRegex(); const isOnlyHiraOrKana = ref(true); @@ -349,7 +385,7 @@ export default defineComponent({ isOnlyHiraOrKana.value = !text.length || kanaRegex.test(text); // 読みが変更されていない場合は、アクセントフレーズに変更を加えない // ただし、読みが同じで違う単語が存在する場合が考えられるので、changeWordフラグを考慮する - // 「ワ」が自動挿入されるので、それを考慮してsliceしている + // 「ガ」が自動挿入されるので、それを考慮してsliceしている if ( text == accentPhrase.value?.moras @@ -365,7 +401,7 @@ export default defineComponent({ text = convertLongVowel(text); accentPhrase.value = ( await store.dispatch("FETCH_ACCENT_PHRASES", { - text: text + "ワ'", + text: text + "ガ'", styleId: 0, isKana: true, }) @@ -452,7 +488,7 @@ export default defineComponent({ }; // accent phraseにあるaccentと実際に登録するアクセントには差が生まれる - // アクセントが自動追加される「ワ」に指定されている場合、 + // アクセントが自動追加される「ガ」に指定されている場合、 // 実際に登録するaccentの値は0となるので、そうなるように処理する const computeRegisteredAccent = () => { if (!accentPhrase.value) throw new Error(); @@ -461,7 +497,7 @@ export default defineComponent({ return accent; }; // computeの逆 - // 辞書から得たaccentが0の場合に、自動で追加される「ワ」の位置にアクセントを表示させるように処理する + // 辞書から得たaccentが0の場合に、自動で追加される「ガ」の位置にアクセントを表示させるように処理する const computeDisplayAccent = () => { if (!accentPhrase.value || !selectedId.value) throw new Error(); let accent = userDict.value[selectedId.value].accentType; @@ -488,11 +524,7 @@ export default defineComponent({ throw new Error(`No such engineInfo registered: index == 0`); if (!accentPhrase.value) throw new Error(`accentPhrase === undefined`); const accent = computeRegisteredAccent(); - // selected idがあった場合でも、surfaceそのものに変更があれば、rewriteではなくaddする - if ( - selectedId.value && - userDict.value[selectedId.value].surface === surface.value - ) { + if (selectedId.value) { try { await store.dispatch("INVOKE_ENGINE_CONNECTOR", { engineKey: engineInfo.key, @@ -520,7 +552,7 @@ export default defineComponent({ } } else { try { - selectedId.value = await store + await store .dispatch("INVOKE_ENGINE_CONNECTOR", { engineKey: engineInfo.key, action: "addUserDictWordUserDictWordPost", @@ -547,6 +579,7 @@ export default defineComponent({ } } await loadingDictProcess(); + wordEditing.value = false; }; const isDeletable = computed(() => !!selectedId.value); const deleteWord = () => { @@ -590,7 +623,7 @@ export default defineComponent({ }); return; } - resetSelect(); + selectedId.value = ""; await loadingDictProcess(); }); }; @@ -646,6 +679,7 @@ export default defineComponent({ userDict, closeDialogProcess, loadingDict, + wordEditing, surfaceInput, yomiInput, yomiFocusWhenEnter, @@ -655,6 +689,7 @@ export default defineComponent({ yomi, selectWord, newWord, + cancel, isOnlyHiraOrKana, setSurface, setYomi, @@ -681,6 +716,19 @@ export default defineComponent({ .word-list-col { border-right: solid 1px colors.$setting-item; + position: relative; // オーバーレイのため +} + +.word-list-header { + margin: 1rem; + + display: flex; + gap: 0.5rem; + align-items: center; + justify-content: space-between; + .word-list-title { + flex-grow: 1; + } } .word-list { @@ -691,19 +739,14 @@ export default defineComponent({ vars.$window-border-width + 46px + 52px} ); width: 100%; - overflow-y: scroll; + overflow-y: auto; } .active-word { background: rgba(colors.$primary-rgb, 0.4); } -.new-word-button { - width: 100%; -} - .loading-dict { - background-color: rgba(colors.$display-dark-rgb, 0.15); position: absolute; inset: 0; z-index: 10; @@ -720,6 +763,14 @@ export default defineComponent({ } } +.word-list-disable-overlay { + background-color: rgba($color: #000000, $alpha: 0.4); + width: 100%; + height: 100%; + position: absolute; + z-index: 10; +} + .word-input { padding-left: 10px; width: calc(66vw - 80px); diff --git a/src/components/MenuBar.vue b/src/components/MenuBar.vue index cde84c751b..85bc801916 100644 --- a/src/components/MenuBar.vue +++ b/src/components/MenuBar.vue @@ -321,7 +321,7 @@ export default defineComponent({ }, { type: "button", - label: "辞書管理", + label: "読み方&アクセント辞書", onClick() { store.dispatch("IS_DICTIONARY_MANAGE_DIALOG_OPEN", { isDictionaryManageDialogOpen: true, diff --git a/src/styles/_index.scss b/src/styles/_index.scss index 67ce15755e..36a40c7497 100644 --- a/src/styles/_index.scss +++ b/src/styles/_index.scss @@ -68,6 +68,13 @@ img { background-color: colors.$background; } +// 無効時の色 +.q-btn { + &.disabled { + opacity: 0.6 !important; + } +} + // ダイアログ .q-dialog, #q-loading { diff --git a/src/views/Home.vue b/src/views/Home.vue index 8b97838aed..3c5d19a1e8 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -516,7 +516,7 @@ export default defineComponent({ }), }); - // 辞書管理 + // 読み方&アクセント辞書 const isDictionaryManageDialogOpenComputed = computed({ get: () => store.state.isDictionaryManageDialogOpen, set: (val) => From 39d7018d576e801280f4aa7a6ea7af46b26aac8e Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Mon, 28 Feb 2022 07:22:54 +0900 Subject: [PATCH 18/32] =?UTF-8?q?resource=E3=81=A8engine=E3=81=AE=E3=83=90?= =?UTF-8?q?=E3=83=BC=E3=82=B8=E3=83=A7=E3=83=B3=E3=82=92=E4=B8=8A=E3=81=92?= =?UTF-8?q?=E3=82=8B=20(#719)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2618d7b468..89386efd61 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,8 +11,8 @@ on: env: VOICEVOX_ENGINE_REPO_URL: "https://github.com/VOICEVOX/voicevox_engine" - VOICEVOX_ENGINE_VERSION: 0.10.4 - VOICEVOX_RESOURCE_VERSION: 0.10.0 + VOICEVOX_ENGINE_VERSION: 0.11.0 + VOICEVOX_RESOURCE_VERSION: 0.11.0 VOICEVOX_EDITOR_VERSION: |- # releaseのときはタグが、それ以外は0.0.0がバージョン名に ${{ github.event.release.tag_name != '' && github.event.release.tag_name || '0.0.0' }} From 66c7b77482f712553701ef77cc391178105ef917 Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Mon, 28 Feb 2022 21:47:15 +0900 Subject: [PATCH 19/32] Merge 0.11.2 (#726) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * To 0.11.0 (#720) * アプデ情報の情報整理 * GTM情報を書き換える * デザイン調整 * アプデ情報追加 * update htu * 辞書読み上げを最初のキャラに * electron builderでは、.envファイルが読み込まれないっぽい?のでフォールバック (#722) * Hotfix: バイナリでエンジン設定が読み出せず、エンジンが起動しない問題の原因を修正 (#721) (#723) * use process.env.DEFAULT_ENGINE_INFOS after dotenv.config * fix comment * fix comment * to 0.11.2 (#725) Co-authored-by: aoirint --- .github/workflows/build.yml | 2 +- public/howtouse.md | 25 ++++++++++++- public/res/dict01.png | Bin 0 -> 30606 bytes public/res/dict02.png | Bin 0 -> 53240 bytes public/updateInfos.json | 32 ++++++++++++++++ src/background.ts | 24 ++++++------ src/components/AudioDetail.vue | 8 ++-- src/components/DictionaryManageDialog.vue | 12 ++++-- src/components/HowToUse.vue | 1 + src/components/UpdateInfo.vue | 43 +++++----------------- src/styles/_index.scss | 8 ++-- 11 files changed, 96 insertions(+), 59 deletions(-) create mode 100644 public/res/dict01.png create mode 100644 public/res/dict02.png diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 89386efd61..cb85dfb69e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ on: env: VOICEVOX_ENGINE_REPO_URL: "https://github.com/VOICEVOX/voicevox_engine" - VOICEVOX_ENGINE_VERSION: 0.11.0 + VOICEVOX_ENGINE_VERSION: 0.11.1 VOICEVOX_RESOURCE_VERSION: 0.11.0 VOICEVOX_EDITOR_VERSION: |- # releaseのときはタグが、それ以外は0.0.0がバージョン名に diff --git a/public/howtouse.md b/public/howtouse.md index 3d3f515324..b955360cd6 100644 --- a/public/howtouse.md +++ b/public/howtouse.md @@ -61,6 +61,8 @@ 左にあるアイコンをクリックしてほかのキャラクターアイコンが表示されている様子。 +キャラクターの表示順序は「キャラクター並び替え」で変更できます。 + ## テキスト欄の並び替え テキスト欄周辺をドラッグすることで、テキスト欄の順番を並び替えられます。 @@ -201,11 +203,30 @@ ツールバーカスタマイズ画面。 -## デフォルトスタイル・試聴 +## キャラクターの並び替え・試聴 -「設定」の「デフォルトスタイル」で、キャラクターごとのデフォルトのスタイルを変更することができます。 +「設定」の「キャラクターの並び替え」で、キャラクターの表示順序を変更することができます。 また、キャラクターごとのサンプルボイスを試聴することもできます。 +## デフォルトスタイル + +「設定」の「デフォルトスタイル」で、キャラクターごとのデフォルトのスタイルを変更することができます。 + +## 読み方&アクセント辞書 + +難しい単語や新しい単語は正しい読みにならないことがありますが、辞書機能を使って読み方を登録しておくことができます。 +辞書機能は「設定」の「読み方&アクセント辞書」で利用できます。 + +読み方&アクセント辞書画面を開くと、左に登録した単語のリストが表示されます。 +「追加」ボタンで新規に単語を登録できます。 + +読み方&アクセント辞書の単語リスト画面。 + +「単語」に登録したいテキストを、「読み」にそのテキストの読み方をひらがなかカタカナで入力してください。 +「アクセント調整」で自然になるアクセントを登録できます。 + +単語と読みとアクセントの登録画面。 + ## オプション 「設定」の「オプション」でいろいろな設定を変更することができます。 diff --git a/public/res/dict01.png b/public/res/dict01.png new file mode 100644 index 0000000000000000000000000000000000000000..6c7b7dd182074f3909e683e89f6b0358b80e1b3b GIT binary patch literal 30606 zcmeFZcT`hb_b!ZwBb*meETAX|SV4M`DiFbf0)ljqCPYA~hYkr4JshNiNEc}$AT<=} zgeWaai_&{2hCnC*LJ~;58$IXv{_c41xc3|1xPRR5yL+g}PS)CM%{AAY&wS>ZJNmwX z&S9=ITx@J?hwt9GY0SpP!N!NcjMxv})!N5E%Jo>Kn4Y;5v!D$1&|3Xi0m zm7El$uBxagNjW&k0{^HeJyKPR@pLNU{Yp1MJ|epn4VT1a^=It2lz4!SZrWO3tzl3W|>$WaXS4 zotzvWIms)76_u3aRTZ3-73EZ%

Ecoa9xmDqdA_HoT$i4fNLbpC5QD$j{01k<%l# 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 LIi9CwbNk-_ZO7JO literal 0 HcmV?d00001 diff --git a/public/res/dict02.png b/public/res/dict02.png new file mode 100644 index 0000000000000000000000000000000000000000..c0dea82fb78e5088211b30e125d9e74ce95a6ddb GIT binary patch literal 53240 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 HZsqqM2we8Q literal 0 HcmV?d00001 diff --git a/public/updateInfos.json b/public/updateInfos.json index 7c0c5b7dd5..e540811b93 100644 --- a/public/updateInfos.json +++ b/public/updateInfos.json @@ -1,4 +1,36 @@ [ + { + "version": "0.11.2", + "descriptions": ["エンジンが起動しない問題を修正", "スタイルの順番を修正"], + "contributors": ["aoirint"] + }, + { + "version": "0.11.0", + "descriptions": [ + "キャラクター「玄野武宏」「白上虎太郎」「青山龍星」「冥鳴ひまり」を追加", + "読み方&アクセント辞書機能の追加", + "キャラクター並び替え機能の追加", + "ヘルプからアップデートの有無を確認可能に", + "利用規約の同意を促すダイアログを追加", + "UXの向上", + "開発環境の向上", + "バグ修正" + ], + "contributors": [ + "aoirint", + "gobosan", + "Hiroshiba", + "HyodaKazuaki", + "Lapis256", + "Oyaki122", + "PickledChair", + "Segu-g", + "takana-v", + "xuzijian629", + "y-chan", + "Yosshi999" + ] + }, { "version": "0.10.4", "descriptions": ["途中に疑問形があるとおかしくなる問題を修正"], diff --git a/src/background.ts b/src/background.ts index 5f952a0fba..87b92dbcb8 100644 --- a/src/background.ts +++ b/src/background.ts @@ -59,16 +59,6 @@ if (isDevelopment) { ); } -const engineInfos: EngineInfo[] = (() => { - const defaultEngineInfosEnv = process.env.DEFAULT_ENGINE_INFOS; - - if (defaultEngineInfosEnv) { - return JSON.parse(defaultEngineInfosEnv) as EngineInfo[]; - } - - return []; -})(); - let win: BrowserWindow; // 多重起動防止 @@ -84,15 +74,27 @@ process.on("unhandledRejection", (reason) => { log.error(reason); }); -// 設定 +// .envから設定をprocess.envに読み込み const appDirPath = path.dirname(app.getPath("exe")); const envPath = path.join(appDirPath, ".env"); dotenv.config({ path: envPath }); + protocol.registerSchemesAsPrivileged([ { scheme: "app", privileges: { secure: true, standard: true, stream: true } }, ]); const isMac = process.platform === "darwin"; + +const engineInfos: EngineInfo[] = (() => { + const defaultEngineInfosEnv = process.env.DEFAULT_ENGINE_INFOS; + + if (defaultEngineInfosEnv) { + return JSON.parse(defaultEngineInfosEnv) as EngineInfo[]; + } + + return []; +})(); + const defaultHotkeySettings: HotkeySetting[] = [ { action: "音声書き出し", diff --git a/src/components/AudioDetail.vue b/src/components/AudioDetail.vue index 68500f715e..ab798193a6 100644 --- a/src/components/AudioDetail.vue +++ b/src/components/AudioDetail.vue @@ -786,10 +786,10 @@ $pitch-label-height: 24px; .accent-phrase-table { flex-grow: 1; align-self: stretch; - margin-left: 5px; - margin-right: 5px; - margin-bottom: 5px; - padding-left: 5px; + margin-left: 4px; + margin-right: 4px; + margin-bottom: 4px; + padding-left: 4px; display: flex; overflow-x: scroll; diff --git a/src/components/DictionaryManageDialog.vue b/src/components/DictionaryManageDialog.vue index b3a1f09590..0261c0ecb9 100644 --- a/src/components/DictionaryManageDialog.vue +++ b/src/components/DictionaryManageDialog.vue @@ -354,6 +354,12 @@ export default defineComponent({ resetSelect(); }; + const styleId = computed(() => { + if (!store.getters.USER_ORDERED_CHARACTER_INFOS) return 0; + return store.getters.USER_ORDERED_CHARACTER_INFOS[0].metas.styles[0] + .styleId; + }); + const kanaRegex = createKanaRegex(); const isOnlyHiraOrKana = ref(true); const accentPhrase = ref(); @@ -402,7 +408,7 @@ export default defineComponent({ accentPhrase.value = ( await store.dispatch("FETCH_ACCENT_PHRASES", { text: text + "ガ'", - styleId: 0, + styleId: styleId.value, isKana: true, }) )[0]; @@ -427,7 +433,7 @@ export default defineComponent({ accentPhrase.value = ( await store.dispatch("FETCH_MORA_DATA", { accentPhrases: [accentPhrase.value], - styleId: 0, + styleId: styleId.value, }) )[0]; } @@ -453,7 +459,7 @@ export default defineComponent({ const audioItem: AudioItem = { text: yomi.value, - styleId: 0, + styleId: styleId.value, query, }; diff --git a/src/components/HowToUse.vue b/src/components/HowToUse.vue index 33930f83f5..4effdb2397 100644 --- a/src/components/HowToUse.vue +++ b/src/components/HowToUse.vue @@ -34,5 +34,6 @@ export default defineComponent({ .markdown :deep(img) { border: 1px solid #333; vertical-align: middle; + margin-bottom: 1rem; } diff --git a/src/components/UpdateInfo.vue b/src/components/UpdateInfo.vue index ee63fd2971..a682392328 100644 --- a/src/components/UpdateInfo.vue +++ b/src/components/UpdateInfo.vue @@ -23,13 +23,12 @@ export default defineComponent({ setup() { const store = useStore(); - const infos = ref(); - store.dispatch("GET_UPDATE_INFOS").then((obj) => (infos.value = obj)); - - let isCheckingFailed = ref(false); + const updateInfos = ref(); + store.dispatch("GET_UPDATE_INFOS").then((obj) => (updateInfos.value = obj)); let isCheckingFinished = ref(false); + // 最新版があるか調べる const currentVersion = ref(""); const latestVersion = ref(""); window.electron @@ -46,11 +45,8 @@ export default defineComponent({ }, }) .then((response) => { - if (!response.ok) { - isCheckingFailed.value = true; - } else { - return response.json(); - } + if (!response.ok) throw new Error("Network response was not ok."); + return response.json(); }) .then((json) => { const obj = json.find( @@ -70,47 +66,26 @@ export default defineComponent({ .catch((err) => { throw new Error(err); }); - }) - .catch(() => { - isCheckingFailed.value = true; }); - const isCheckingFailedComputed = computed(() => { - return isCheckingFailed.value; - }); - - const isCheckingFinishedComputed = computed(() => { - return isCheckingFinished.value; - }); - const isUpdateAvailable = computed(() => { return isCheckingFinished.value && latestVersion.value !== ""; }); const html = computed(() => { - if (!infos.value) return ""; + if (!updateInfos.value) return ""; let html = ""; if (isUpdateAvailable.value) { - html += `

アップデートがあります!

`; - html += `

最新版のダウンロードページ

`; - html += `
https://voicevox.hiroshiba.jp/`; - } else if (isCheckingFinishedComputed.value && !isUpdateAvailable.value) { - html += `

お使いの VOICEBOX は最新です!

`; - } else if ( - !isCheckingFinishedComputed.value && - !isCheckingFailedComputed.value - ) { - html += `

アップデートを確認中です…

`; - } else { - html += `

アップデートの確認に失敗しました…

`; + html += `

最新バージョン ${latestVersion.value} が見つかりました

`; + html += `ダウンロードページ`; } html += `
`; html += `

アップデート履歴

`; - for (const info of infos.value) { + for (const info of updateInfos.value) { const version: string = info.version; const descriptions: string[] = info.descriptions; const contributors: string[] = info.contributors; diff --git a/src/styles/_index.scss b/src/styles/_index.scss index 36a40c7497..415fc9df1a 100644 --- a/src/styles/_index.scss +++ b/src/styles/_index.scss @@ -16,16 +16,16 @@ img { // スクロールバーのデザイン ::-webkit-scrollbar { - width: 15px; - height: 15px; + width: 12px; + height: 12px; background-color: rgba(colors.$primary-light-rgb, 0.2); border-radius: 5px; } ::-webkit-scrollbar-thumb { - background-color: rgba(colors.$primary-light-rgb, 0.5); + background-color: rgba(colors.$primary-light-rgb, 0.6); border-radius: 5px; &:hover { - background-color: rgba(colors.$primary-light-rgb, 0.6); + background-color: rgba(colors.$primary-light-rgb, 0.7); } &:active { background-color: rgba(colors.$primary-light-rgb, 0.8); From dfe6c75a3dc6985c3f1bbf4fe9d4a8fb4bb95cfa Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Wed, 2 Mar 2022 01:40:28 +0900 Subject: [PATCH 20/32] Merge 0.11.3 (#737) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * To 0.11.0 (#720) * アプデ情報の情報整理 * GTM情報を書き換える * デザイン調整 * アプデ情報追加 * update htu * 辞書読み上げを最初のキャラに * electron builderでは、.envファイルが読み込まれないっぽい?のでフォールバック (#722) * Hotfix: バイナリでエンジン設定が読み出せず、エンジンが起動しない問題の原因を修正 (#721) (#723) * use process.env.DEFAULT_ENGINE_INFOS after dotenv.config * fix comment * fix comment * to 0.11.2 (#725) * [HOTFIX]再生位置を指定するコードを移動させる (#733) * move part of setting current time * Update src/store/audio.ts Co-authored-by: Hiroshiba * [HOSTFIX] #730を修正 (#735) * 辞書UIの状態遷移を整理 (#736) * 辞書UIの状態遷移を整理 * closeDialog * selectWord * to-0.11.3 Co-authored-by: aoirint Co-authored-by: Yuto Ashida Co-authored-by: madosuki --- .github/workflows/build.yml | 2 +- public/updateInfos.json | 10 +++ src/components/DictionaryManageDialog.vue | 87 +++++++++++++++-------- src/store/audio.ts | 36 +++++----- src/views/Home.vue | 2 + 5 files changed, 90 insertions(+), 47 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cb85dfb69e..3bc4827b1c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ on: env: VOICEVOX_ENGINE_REPO_URL: "https://github.com/VOICEVOX/voicevox_engine" - VOICEVOX_ENGINE_VERSION: 0.11.1 + VOICEVOX_ENGINE_VERSION: 0.11.3 VOICEVOX_RESOURCE_VERSION: 0.11.0 VOICEVOX_EDITOR_VERSION: |- # releaseのときはタグが、それ以外は0.0.0がバージョン名に diff --git a/public/updateInfos.json b/public/updateInfos.json index e540811b93..481a4cdfdc 100644 --- a/public/updateInfos.json +++ b/public/updateInfos.json @@ -1,4 +1,14 @@ [ + { + "version": "0.11.3", + "descriptions": [ + "再生位置を指定できない問題を修正", + "テキストを範囲選択できない問題を修正", + "辞書UIのバグを修正", + "辞書登録できない問題を修正" + ], + "contributors": ["Hiroshiba", "madosuki", "y-chan"] + }, { "version": "0.11.2", "descriptions": ["エンジンが起動しない問題を修正", "スタイルの順番を修正"], diff --git a/src/components/DictionaryManageDialog.vue b/src/components/DictionaryManageDialog.vue index 0261c0ecb9..2e968eff39 100644 --- a/src/components/DictionaryManageDialog.vue +++ b/src/components/DictionaryManageDialog.vue @@ -21,7 +21,7 @@ flat icon="close" color="display" - @click="discardOrNotDialog(closeDialogProcess)" + @click="discardOrNotDialog(closeDialog)" /> @@ -51,7 +51,7 @@ outline text-color="display" class="text-no-wrap text-bold" - @click="wordEditing = true" + @click="editWord" :disable="uiLocked || !selectedId" >編集 @@ -305,17 +305,9 @@ export default defineComponent({ watch(dictionaryManageDialogOpenedComputed, async (newValue) => { if (newValue) { await loadingDictProcess(); + toInitialState(); } }); - const resetSelect = () => { - selectedId.value = ""; - surface.value = ""; - setYomi(""); - }; - const closeDialogProcess = () => { - dictionaryManageDialogOpenedComputed.value = false; - cancel(); - }; const wordEditing = ref(false); @@ -339,20 +331,12 @@ export default defineComponent({ const selectedId = ref(""); const surface = ref(""); const yomi = ref(""); - const selectWord = (id: string) => { - selectedId.value = id; - surface.value = userDict.value[id].surface; - setYomi(userDict.value[id].yomi, true); - }; - const newWord = () => { - wordEditing.value = true; - resetSelect(); - surfaceInput.value?.focus(); - }; - const cancel = () => { - wordEditing.value = false; - resetSelect(); - }; + + const styleId = computed(() => { + if (!store.getters.USER_ORDERED_CHARACTER_INFOS) return 0; + return store.getters.USER_ORDERED_CHARACTER_INFOS[0].metas.styles[0] + .styleId; + }); const styleId = computed(() => { if (!store.getters.USER_ORDERED_CHARACTER_INFOS) return 0; @@ -511,6 +495,7 @@ export default defineComponent({ return accent; }; + // 操作(ステートの移動) const isWordChanged = computed(() => { if (selectedId.value === "") { return surface.value && yomi.value && accentPhrase.value; @@ -585,7 +570,7 @@ export default defineComponent({ } } await loadingDictProcess(); - wordEditing.value = false; + toInitialState(); }; const isDeletable = computed(() => !!selectedId.value); const deleteWord = () => { @@ -629,8 +614,8 @@ export default defineComponent({ }); return; } - selectedId.value = ""; await loadingDictProcess(); + toInitialState(); }); }; const resetWord = () => { @@ -650,7 +635,7 @@ export default defineComponent({ textColor: "display", }, }).onOk(() => { - selectWord(selectedId.value); + toWordEditingState(); }); }; const discardOrNotDialog = (okCallback: () => void) => { @@ -676,6 +661,49 @@ export default defineComponent({ okCallback(); } }; + const newWord = () => { + selectedId.value = ""; + surface.value = ""; + setYomi(""); + editWord(); + }; + const editWord = () => { + toWordEditingState(); + }; + const selectWord = (id: string) => { + selectedId.value = id; + surface.value = userDict.value[id].surface; + setYomi(userDict.value[id].yomi, true); + toWordSelectedState(); + }; + const cancel = () => { + toInitialState(); + }; + const closeDialog = () => { + toDialogClosedState(); + }; + + // ステートの移動 + // 初期状態 + const toInitialState = () => { + wordEditing.value = false; + selectedId.value = ""; + surface.value = ""; + setYomi(""); + }; + // 単語が選択されているだけの状態 + const toWordSelectedState = () => { + wordEditing.value = false; + }; + // 単語が編集されている状態 + const toWordEditingState = () => { + wordEditing.value = true; + surfaceInput.value?.focus(); + }; + // ダイアログが閉じている状態 + const toDialogClosedState = () => { + dictionaryManageDialogOpenedComputed.value = false; + }; return { dictionaryManageDialogOpenedComputed, @@ -683,7 +711,6 @@ export default defineComponent({ nowGenerating, nowPlaying, userDict, - closeDialogProcess, loadingDict, wordEditing, surfaceInput, @@ -695,6 +722,7 @@ export default defineComponent({ yomi, selectWord, newWord, + editWord, cancel, isOnlyHiraOrKana, setSurface, @@ -710,6 +738,7 @@ export default defineComponent({ saveWord, deleteWord, resetWord, + closeDialog, discardOrNotDialog, }; }, diff --git a/src/store/audio.ts b/src/store/audio.ts index 5dadf31257..4d49f88c7a 100644 --- a/src/store/audio.ts +++ b/src/store/audio.ts @@ -1266,10 +1266,7 @@ export const audioStore: VoiceVoxStoreOptions< } ), PLAY_AUDIO: createUILockAction( - async ( - { state, commit, dispatch }, - { audioKey }: { audioKey: string } - ) => { + async ({ commit, dispatch }, { audioKey }: { audioKey: string }) => { const audioElem = audioElements[audioKey]; audioElem.pause(); @@ -1289,18 +1286,6 @@ export const audioStore: VoiceVoxStoreOptions< throw new Error(); } } - const accentPhraseOffsets = await dispatch("GET_AUDIO_PLAY_OFFSETS", { - audioKey, - }); - if (accentPhraseOffsets.length === 0) { - audioElem.currentTime = 0; - } else { - const startTime = accentPhraseOffsets[state.audioPlayStartPoint ?? 0]; - if (startTime === undefined) throw Error("startTime === undefined"); - // 小さい値が切り捨てられることでフォーカスされるアクセントフレーズが一瞬元に戻るので、 - // 再生に影響のない程度かつ切り捨てられない値を加算する - audioElem.currentTime = startTime + 10e-6; - } return dispatch("PLAY_AUDIO_BLOB", { audioBlob: blob, @@ -1311,7 +1296,7 @@ export const audioStore: VoiceVoxStoreOptions< ), PLAY_AUDIO_BLOB: createUILockAction( async ( - { state, commit }, + { state, commit, dispatch }, { audioBlob, audioElem, @@ -1319,6 +1304,23 @@ export const audioStore: VoiceVoxStoreOptions< }: { audioBlob: Blob; audioElem: HTMLAudioElement; audioKey?: string } ) => { audioElem.src = URL.createObjectURL(audioBlob); + // 途中再生用の処理 + if (audioKey) { + const accentPhraseOffsets = await dispatch("GET_AUDIO_PLAY_OFFSETS", { + audioKey, + }); + if (accentPhraseOffsets.length === 0) { + audioElem.currentTime = 0; + } else { + const startTime = + accentPhraseOffsets[state.audioPlayStartPoint ?? 0]; + if (startTime === undefined) throw Error("startTime === undefined"); + // 小さい値が切り捨てられることでフォーカスされるアクセントフレーズが一瞬元に戻るので、 + // 再生に影響のない程度かつ切り捨てられない値を加算する + audioElem.currentTime = startTime + 10e-6; + } + } + audioElem .setSinkId(state.savingSetting.audioOutputDevice) .catch((err) => { diff --git a/src/views/Home.vue b/src/views/Home.vue index 3c5d19a1e8..ae10f12190 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -74,6 +74,8 @@ @update:modelValue="updateAudioKeys" :itemKey="itemKey" ghost-class="ghost" + filter="input" + :preventOnFilter="false" >