Skip to content

Commit

Permalink
Improve: データ準備を高速化 (VOICEVOX#2090)
Browse files Browse the repository at this point in the history
* Change: fast-base64を使う

* Refactor: asyncComputedに統一

* Improve: キャッシュを追加

* Change: useEngineIconsにする

* Code: コメントを足す

* Fix: 代入忘れ

* Change: computedにする

* Fix: 型を足す

* Add: コメントを追加

* Update src/components/Menu/MenuBar/MenuBar.vue

---------

Co-authored-by: Hiroshiba <[email protected]>
  • Loading branch information
sevenc-nanashi and Hiroshiba authored May 28, 2024
1 parent de5a726 commit a07c776
Show file tree
Hide file tree
Showing 10 changed files with 76 additions and 44 deletions.
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"electron-window-state": "5.0.3",
"encoding-japanese": "1.0.30",
"fast-array-diff": "1.1.0",
"fast-base64": "0.1.8",
"glob": "8.0.3",
"hotkeys-js": "3.13.6",
"immer": "9.0.21",
Expand Down
5 changes: 5 additions & 0 deletions src/@types/fast-base64.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// fast-base64の型定義が壊れているので、ここで型定義を追加する。
declare module "fast-base64" {
export function toBytes(base64: string): Promise<Uint8Array>;
export function toBase64(bytes: Uint8Array): Promise<string>;
}
11 changes: 2 additions & 9 deletions src/components/CharacterButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,11 @@
<script setup lang="ts">
import { debounce, QBtn } from "quasar";
import { computed, Ref, ref } from "vue";
import { base64ImageToUri } from "@/helpers/base64Helper";
import { useStore } from "@/store";
import { CharacterInfo, SpeakerId, Voice } from "@/type/preload";
import { formatCharacterStyleName } from "@/store/utility";
import { getDefaultStyle } from "@/domain/talk";
import { useEngineIcons } from "@/composables/useEngineIcons";
const props = withDefaults(
defineProps<{
Expand Down Expand Up @@ -275,14 +275,7 @@ const selectedStyleInfo = computed(() => {
return style;
});
const engineIcons = computed(() =>
Object.fromEntries(
store.state.engineIds.map((engineId) => [
engineId,
base64ImageToUri(store.state.engineManifests[engineId].icon),
]),
),
);
const engineIcons = useEngineIcons(() => store.state.engineManifests);
const getDefaultStyleWrapper = (speakerUuid: SpeakerId) =>
getDefaultStyle(
Expand Down
12 changes: 3 additions & 9 deletions src/components/Dialog/EngineManageDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -348,9 +348,9 @@
<script setup lang="ts">
import { computed, ref, watch } from "vue";
import { useStore } from "@/store";
import { base64ImageToUri } from "@/helpers/base64Helper";
import { EngineDirValidationResult, EngineId } from "@/type/preload";
import type { SupportedFeatures } from "@/openapi/models/SupportedFeatures";
import { useEngineIcons } from "@/composables/useEngineIcons";
type EngineLoaderType = "dir" | "vvpp";
Expand Down Expand Up @@ -398,15 +398,9 @@ const categorizedEngineIds = computed(() => {
});
const engineInfos = computed(() => store.state.engineInfos);
const engineStates = computed(() => store.state.engineStates);
const engineIcons = useEngineIcons(() => store.state.engineManifests);
const engineManifests = computed(() => store.state.engineManifests);
const engineIcons = computed(() =>
Object.fromEntries(
Object.entries(store.state.engineManifests).map(([id, manifest]) => [
id,
base64ImageToUri(manifest.icon),
]),
),
);
const engineVersions = ref<Record<EngineId, string>>({});
watch(
Expand Down
5 changes: 3 additions & 2 deletions src/components/Menu/MenuBar/MenuBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ import MenuButton from "../MenuButton.vue";
import TitleBarButtons from "./TitleBarButtons.vue";
import TitleBarEditorSwitcher from "./TitleBarEditorSwitcher.vue";
import { useStore } from "@/store";
import { base64ImageToUri } from "@/helpers/base64Helper";
import { HotkeyAction, useHotkeyManager } from "@/plugins/hotkeyPlugin";
import { useEngineIcons } from "@/composables/useEngineIcons";
const props = defineProps<{
/** 「ファイル」メニューのサブメニュー */
Expand Down Expand Up @@ -81,6 +81,7 @@ const isFullscreen = computed(() => store.getters.IS_FULLSCREEN);
const engineIds = computed(() => store.state.engineIds);
const engineInfos = computed(() => store.state.engineInfos);
const engineManifests = computed(() => store.state.engineManifests);
const engineIcons = useEngineIcons(engineManifests);
const enableMultiEngine = computed(() => store.state.enableMultiEngine);
const titleText = computed(
() =>
Expand Down Expand Up @@ -217,7 +218,7 @@ const engineSubMenuData = computed<MenuItemData[]>(() => {
label: engineInfo.name,
icon:
engineManifests.value[engineInfo.uuid] &&
base64ImageToUri(engineManifests.value[engineInfo.uuid].icon),
engineIcons.value[engineInfo.uuid],
subMenu: [
engineInfo.path && {
type: "button",
Expand Down
11 changes: 2 additions & 9 deletions src/components/Sing/CharacterMenuButton/MenuButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,9 @@ import { computed, ref } from "vue";
import { debounce } from "quasar";
import SelectedCharacter from "./SelectedCharacter.vue";
import { useStore } from "@/store";
import { base64ImageToUri } from "@/helpers/base64Helper";
import { SpeakerId, StyleId } from "@/type/preload";
import { getStyleDescription } from "@/sing/viewHelper";
import { useEngineIcons } from "@/composables/useEngineIcons";
const store = useStore();
const uiLocked = computed(() => store.getters.UI_LOCKED);
Expand Down Expand Up @@ -239,14 +239,7 @@ const selectedStyleId = computed(
// 複数エンジン
const isMultipleEngine = computed(() => store.state.engineIds.length > 1);
const engineIcons = computed(() =>
Object.fromEntries(
store.state.engineIds.map((engineId) => [
engineId,
base64ImageToUri(store.state.engineManifests[engineId].icon),
]),
),
);
const engineIcons = useEngineIcons(() => store.state.engineManifests);
</script>

<style scoped lang="scss">
Expand Down
27 changes: 27 additions & 0 deletions src/composables/useEngineIcons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { watch, ref, WatchSource } from "vue";
import { base64ImageToUri } from "@/helpers/base64Helper";
import { EngineManifest } from "@/openapi";
import { EngineId } from "@/type/preload";

export const useEngineIcons = (
engineManifests: WatchSource<Record<EngineId, EngineManifest>>,
) => {
const result = ref<Record<EngineId, string>>({});

watch(
engineManifests,
async (engineManifests) => {
const engineIcons: Record<EngineId, string> = {};
for (const [engineId, manifest] of Object.entries(engineManifests)) {
engineIcons[EngineId(engineId)] = await base64ImageToUri(manifest.icon);
}

result.value = engineIcons;
},
{
immediate: true,
},
);

return result;
};
21 changes: 15 additions & 6 deletions src/helpers/base64Helper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { Buffer } from "buffer";
import { toBytes } from "fast-base64";

// base64 -> uriのキャッシュ。
const cache = new Map<string, string>();

function detectImageTypeFromBase64(data: string): string {
switch (data[0]) {
Expand All @@ -15,12 +18,18 @@ function detectImageTypeFromBase64(data: string): string {
}
}

export const base64ToUri = (data: string, type: string) => {
const buffer = Buffer.from(data, "base64");
return URL.createObjectURL(new Blob([buffer.buffer], { type }));
export const base64ToUri = async (data: string, type: string) => {
const cached = cache.get(data);
if (cached) {
return cached;
}
const buffer = await toBytes(data);
const url = URL.createObjectURL(new Blob([buffer.buffer], { type }));
cache.set(data, url);
return url;
};

export function base64ImageToUri(image: string): string {
export async function base64ImageToUri(image: string): Promise<string> {
const mimeType = detectImageTypeFromBase64(image);
return base64ToUri(image, mimeType);
return await base64ToUri(image, mimeType);
}
21 changes: 12 additions & 9 deletions src/store/audio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,33 +290,36 @@ export const audioStore = createPartialStore<AudioStoreTypes>({
const instance = await dispatch("INSTANTIATE_ENGINE_CONNECTOR", {
engineId,
});
const getStyles = function (
const getStyles = async function (
speaker: Speaker,
speakerInfo: SpeakerInfo,
) {
const styles: StyleInfo[] = new Array(speaker.styles.length);
speaker.styles.forEach((style, i) => {
for (const [i, style] of speaker.styles.entries()) {
const styleInfo = speakerInfo.styleInfos.find(
(styleInfo) => style.id === styleInfo.id,
);
if (!styleInfo)
throw new Error(
`Not found the style id "${style.id}" of "${speaker.name}". `,
);
const voiceSamples = styleInfo.voiceSamples.map((voiceSample) => {
return base64ToUri(voiceSample, "audio/wav");
});
const voiceSamples = await Promise.all(
styleInfo.voiceSamples.map((voiceSample) => {
return base64ToUri(voiceSample, "audio/wav");
}),
);
styles[i] = {
styleName: style.name,
styleId: StyleId(style.id),
styleType: style.type,
engineId,
iconPath: base64ImageToUri(styleInfo.icon),
iconPath: await base64ImageToUri(styleInfo.icon),
portraitPath:
styleInfo.portrait && base64ImageToUri(styleInfo.portrait),
styleInfo.portrait &&
(await base64ImageToUri(styleInfo.portrait)),
voiceSamplePaths: voiceSamples,
};
});
}
return styles;
};
const getCharacterInfo = async (
Expand Down Expand Up @@ -372,7 +375,7 @@ export const audioStore = createPartialStore<AudioStoreTypes>({
]).then((styles) => styles.flat());

const characterInfo: CharacterInfo = {
portraitPath: base64ImageToUri(baseCharacterInfo.portrait),
portraitPath: await base64ImageToUri(baseCharacterInfo.portrait),
metas: {
speakerUuid: SpeakerId(baseSpeaker.speakerUuid),
speakerName: baseSpeaker.name,
Expand Down

0 comments on commit a07c776

Please sign in to comment.