+
+ {{ audioDuration.toFixed(2) }}s
+
store.state.showAudioLength);
+
+const audioDuration = computed(() => {
+ if (!audioItem.value?.query) return undefined;
+ const engineId = audioItem.value.voice.engineId;
+ const supportedFeatures =
+ store.state.engineManifests[engineId]?.supportedFeatures;
+ if (!supportedFeatures?.adjustPhonemeLength) return undefined;
+
+ return calculateAudioLength(audioItem.value.query);
+});
+
const pushAudioTextIfNeeded = async (event?: KeyboardEvent) => {
if (event && event.isComposing) return;
if (!willRemove.value && isChangeFlag.value && !willFocusOrBlur.value) {
@@ -751,4 +771,12 @@ const isMultipleEngine = computed(() => store.state.engineIds.length > 1);
z-index: 1;
cursor: default;
}
+
+.audio-length {
+ color: colors.$display;
+ opacity: 0.6;
+ white-space: nowrap;
+ font-size: 0.85rem;
+ user-select: none;
+}
diff --git a/src/store/audioGenerate.ts b/src/store/audioGenerate.ts
index f3d267c3c2..a9e18e8c5f 100644
--- a/src/store/audioGenerate.ts
+++ b/src/store/audioGenerate.ts
@@ -172,3 +172,27 @@ export function handlePossiblyNotMorphableError(e: unknown) {
return;
}
}
+
+/**
+ * AudioQueryから音声の長さを計算する(秒)
+ */
+export function calculateAudioLength(audioQuery: EditorAudioQuery) {
+ if (audioQuery.accentPhrases.length === 0) return 0;
+
+ let length = 0;
+ length += audioQuery.prePhonemeLength;
+ audioQuery.accentPhrases.forEach((accentPhrase) => {
+ accentPhrase.moras.forEach((mora) => {
+ if (mora.consonantLength != undefined) {
+ length += mora.consonantLength;
+ }
+ length += mora.vowelLength;
+ });
+ if (accentPhrase.pauseMora != undefined) {
+ length +=
+ accentPhrase.pauseMora.vowelLength * audioQuery.pauseLengthScale;
+ }
+ });
+ length += audioQuery.postPhonemeLength;
+ return length / audioQuery.speedScale;
+}
diff --git a/src/store/setting.ts b/src/store/setting.ts
index 0ff8f8a556..7026e50196 100644
--- a/src/store/setting.ts
+++ b/src/store/setting.ts
@@ -76,6 +76,7 @@ export const settingStoreState: SettingStoreState = {
playheadPositionDisplayFormat: "MINUTES_SECONDS",
enableKatakanaEnglish: true,
enableMultiSelect: true,
+ showAudioLength: false,
};
export const settingStore = createPartialStore({
@@ -156,6 +157,7 @@ export const settingStore = createPartialStore({
"openedEditor",
"enableKatakanaEnglish",
"enableMultiSelect",
+ "showAudioLength",
] as const;
// rootMiscSettingKeysに値を足し忘れていたときに型エラーを出す検出用コード
diff --git a/src/type/preload.ts b/src/type/preload.ts
index 4114d8ec9c..f535d9ffe4 100644
--- a/src/type/preload.ts
+++ b/src/type/preload.ts
@@ -206,10 +206,6 @@ export type SplitTextWhenPasteType = "PERIOD_AND_NEW_LINE" | "NEW_LINE" | "OFF";
export type EditorFontType = "default" | "os";
-export type SavingSetting = ConfigType["savingSetting"];
-
-export type EngineSettings = Record;
-
export const engineSettingSchema = z.object({
useGpu: z.boolean().default(false),
outputSamplingRate: z
@@ -218,6 +214,25 @@ export const engineSettingSchema = z.object({
});
export type EngineSettingType = z.infer;
+export const savingSettingSchema = z
+ .object({
+ fileEncoding: z.enum(["UTF-8", "Shift_JIS"]).default("UTF-8"),
+ fileNamePattern: z.string().default(""), // NOTE: ファイル名パターンは拡張子を含まない
+ fixedExportEnabled: z.boolean().default(false),
+ avoidOverwrite: z.boolean().default(false),
+ fixedExportDir: z.string().default(""),
+ exportLab: z.boolean().default(false),
+ exportText: z.boolean().default(false),
+ outputStereo: z.boolean().default(false),
+ audioOutputDevice: z.string().default(""),
+ songTrackFileNamePattern: z.string().default(""),
+ })
+ .prefault({});
+
+export type SavingSetting = z.infer;
+
+export type EngineSettings = Record;
+
export type DefaultStyleId = {
engineId: EngineId;
speakerUuid: SpeakerId;
@@ -405,6 +420,7 @@ export const rootMiscSettingSchema = z.object({
.default("MINUTES_SECONDS"), // 再生ヘッド位置の表示モード
enableKatakanaEnglish: z.boolean().default(true), // 未知の英単語をカタカナ読みに変換するかどうか
enableMultiSelect: z.boolean().default(true), // 複数選択を有効にするかどうか
+ showAudioLength: z.boolean().default(false), // 音声の長さを表示するかどうか
});
export type RootMiscSettingType = z.infer;
@@ -414,20 +430,7 @@ export function getConfigSchema({ isMac }: { isMac: boolean }) {
activePointScrollMode: z
.enum(["CONTINUOUSLY", "PAGE", "OFF"])
.default("OFF"),
- savingSetting: z
- .object({
- fileEncoding: z.enum(["UTF-8", "Shift_JIS"]).default("UTF-8"),
- fileNamePattern: z.string().default(""), // NOTE: ファイル名パターンは拡張子を含まない
- fixedExportEnabled: z.boolean().default(false),
- avoidOverwrite: z.boolean().default(false),
- fixedExportDir: z.string().default(""),
- exportLab: z.boolean().default(false),
- exportText: z.boolean().default(false),
- outputStereo: z.boolean().default(false),
- audioOutputDevice: z.string().default(""),
- songTrackFileNamePattern: z.string().default(""),
- })
- .prefault({}),
+ savingSetting: savingSettingSchema,
hotkeySettings: hotkeySettingSchema
.array()
.default(getDefaultHotkeySettings({ isMac })),
diff --git "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts" "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts"
index da9f82ecd6..46c952a8fd 100644
--- "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts"
+++ "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts"
@@ -10,7 +10,7 @@ test("スクリーンショット", async ({ page }) => {
await page.waitForTimeout(500);
// スクリーンショット撮影とスクロールを繰り返す
- for (let i = 0; i < 5; i++) {
+ for (let i = 0; i < 6; i++) {
await expect(page).toHaveScreenshot(`スクリーンショット_${i}.png`);
await page.mouse.wheel(0, 500);
await page.waitForTimeout(300);
diff --git "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-0-browser-win32.png" "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-0-browser-win32.png"
index 204641e884..1f2c10fb50 100644
Binary files "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-0-browser-win32.png" and "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-0-browser-win32.png" differ
diff --git "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-1-browser-win32.png" "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-1-browser-win32.png"
index f1ee679514..34d043e77b 100644
Binary files "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-1-browser-win32.png" and "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-1-browser-win32.png" differ
diff --git "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-2-browser-win32.png" "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-2-browser-win32.png"
index f3d8fd44b1..e4b387b2e9 100644
Binary files "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-2-browser-win32.png" and "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-2-browser-win32.png" differ
diff --git "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-3-browser-win32.png" "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-3-browser-win32.png"
index 377a41e12b..32cefc10cd 100644
Binary files "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-3-browser-win32.png" and "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-3-browser-win32.png" differ
diff --git "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-4-browser-win32.png" "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-4-browser-win32.png"
index bd69a8cff0..bb5816310b 100644
Binary files "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-4-browser-win32.png" and "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-4-browser-win32.png" differ
diff --git "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-5-browser-win32.png" "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-5-browser-win32.png"
new file mode 100644
index 0000000000..e436b6685c
Binary files /dev/null and "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-5-browser-win32.png" differ
diff --git a/tests/unit/backend/common/__snapshots__/configManager.spec.ts.snap b/tests/unit/backend/common/__snapshots__/configManager.spec.ts.snap
index 31edbbd37f..44d39ce2e2 100644
--- a/tests/unit/backend/common/__snapshots__/configManager.spec.ts.snap
+++ b/tests/unit/backend/common/__snapshots__/configManager.spec.ts.snap
@@ -225,6 +225,7 @@ exports[`0.13.0からマイグレーションできる 1`] = `
},
"shouldApplyDefaultPresetOnVoiceChanged": false,
"showAddAudioItemButton": true,
+ "showAudioLength": false,
"showSingCharacterPortrait": true,
"showTextLineNumber": false,
"splitTextWhenPaste": "PERIOD_AND_NEW_LINE",
diff --git a/tests/unit/store/audioGenerate.spec.ts b/tests/unit/store/audioGenerate.spec.ts
new file mode 100644
index 0000000000..b1efe3f648
--- /dev/null
+++ b/tests/unit/store/audioGenerate.spec.ts
@@ -0,0 +1,171 @@
+import { describe, test, expect } from "vitest";
+import { calculateAudioLength } from "@/store/audioGenerate";
+import { EditorAudioQuery } from "@/store/type";
+
+const baseEditorAudioQuery: EditorAudioQuery = {
+ accentPhrases: [],
+ speedScale: 1,
+ pitchScale: 0,
+ intonationScale: 1,
+ volumeScale: 1,
+ prePhonemeLength: 0.1,
+ postPhonemeLength: 0.1,
+ pauseLengthScale: 1,
+ outputSamplingRate: 24000,
+ outputStereo: false,
+ kana: "",
+};
+
+describe("audioGenerate", () => {
+ describe("calculateAudioLength", () => {
+ test("アクセント句がない場合は0を返すこと", () => {
+ const audioQuery: EditorAudioQuery = {
+ ...baseEditorAudioQuery,
+ accentPhrases: [],
+ };
+
+ expect(calculateAudioLength(audioQuery)).toBe(0);
+ });
+
+ test("アクセント句がある場合の計算が正しいこと", () => {
+ const audioQuery: EditorAudioQuery = {
+ ...baseEditorAudioQuery,
+ accentPhrases: [
+ {
+ moras: [
+ {
+ text: "あ",
+ vowel: "a",
+ vowelLength: 0.2,
+ pitch: 0,
+ consonant: "a",
+ consonantLength: 0.1,
+ }, // 0.3
+ {
+ text: "い",
+ vowel: "i",
+ vowelLength: 0.2,
+ pitch: 0,
+ consonant: undefined,
+ consonantLength: undefined,
+ }, // 0.2
+ ],
+ accent: 1,
+ pauseMora: undefined,
+ },
+ ],
+ };
+
+ // 0.1(pre) + 0.3 + 0.2 + 0.1(post) = 0.7
+ expect(calculateAudioLength(audioQuery)).toBeCloseTo(0.7);
+ });
+
+ test("speedScaleが反映されること", () => {
+ const audioQuery: EditorAudioQuery = {
+ ...baseEditorAudioQuery,
+ accentPhrases: [
+ {
+ moras: [
+ {
+ text: "あ",
+ vowel: "a",
+ vowelLength: 0.2,
+ pitch: 0,
+ consonant: "a",
+ consonantLength: 0.1,
+ },
+ ],
+ accent: 1,
+ pauseMora: undefined,
+ },
+ ],
+ speedScale: 2, // 2倍速
+ };
+
+ // (0.1(pre) + 0.3 + 0.1(post)) / 2 = 0.25
+ expect(calculateAudioLength(audioQuery)).toBeCloseTo(0.25);
+ });
+
+ test("ポーズがある場合の計算が正しいこと", () => {
+ const audioQuery: EditorAudioQuery = {
+ ...baseEditorAudioQuery,
+ accentPhrases: [
+ {
+ moras: [
+ {
+ text: "あ",
+ vowel: "a",
+ vowelLength: 0.2,
+ pitch: 0,
+ consonant: "a",
+ consonantLength: 0.1,
+ },
+ ],
+ accent: 1,
+ pauseMora: {
+ text: "、",
+ vowel: "pau",
+ vowelLength: 0.5,
+ pitch: 0,
+ }, // 0.5 * 1.5 = 0.75
+ },
+ ],
+ pauseLengthScale: 1.5,
+ };
+
+ // 0.1 + 0.3 + 0.75 + 0.1 = 1.25
+ expect(calculateAudioLength(audioQuery)).toBeCloseTo(1.25);
+ });
+ test("prePhonemeLengthが反映されること", () => {
+ const audioQuery: EditorAudioQuery = {
+ ...baseEditorAudioQuery,
+ accentPhrases: [
+ {
+ moras: [
+ {
+ text: "あ",
+ vowel: "a",
+ vowelLength: 0.2,
+ pitch: 0,
+ consonant: "a",
+ consonantLength: 0.1,
+ },
+ ],
+ accent: 1,
+ pauseMora: undefined,
+ },
+ ],
+ prePhonemeLength: 0.5,
+ };
+
+ // 0.5(pre) + 0.3 + 0.1(post) = 0.9
+ expect(calculateAudioLength(audioQuery)).toBeCloseTo(0.9);
+ });
+
+ test("postPhonemeLengthが反映されること", () => {
+ const audioQuery: EditorAudioQuery = {
+ ...baseEditorAudioQuery,
+ accentPhrases: [
+ {
+ moras: [
+ {
+ text: "あ",
+ vowel: "a",
+ vowelLength: 0.2,
+ pitch: 0,
+ consonant: "a",
+ consonantLength: 0.1,
+ },
+ ],
+ accent: 1,
+ pauseMora: undefined,
+ },
+ ],
+ postPhonemeLength: 0.5,
+ };
+
+ // 0.1(pre) + 0.3 + 0.5(post) = 0.9
+ expect(calculateAudioLength(audioQuery)).toBeCloseTo(0.9);
+ });
+ });
+});