Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

全テキスト欄の文章をキャラ名とともに一括してテキスト書き出す機能 #787

10 changes: 10 additions & 0 deletions src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,16 @@ ipcMainHandle("SHOW_AUDIO_SAVE_DIALOG", async (_, { title, defaultPath }) => {
return result.filePath;
});

ipcMainHandle("SHOW_TEXT_SAVE_DIALOG", async (_, { title, defaultPath }) => {
const result = await dialog.showSaveDialog(win, {
title,
defaultPath,
filters: [{ name: "Text File", extensions: ["txt"] }],
properties: ["createDirectory"],
});
return result.filePath;
});

ipcMainHandle("SHOW_OPEN_DIRECTORY_DIALOG", async (_, { title }) => {
const result = await dialog.showOpenDialog(win, {
title,
Expand Down
42 changes: 42 additions & 0 deletions src/components/Dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,45 @@ export async function generateAndConnectAndSaveAudioWithDialog({
},
});
}

export async function connectAndExportTextWithDialog({
quasarDialog,
dispatch,
filePath,
encoding,
}: {
quasarDialog: QuasarDialog;
dispatch: Dispatch<AllActions>;
filePath?: string;
encoding?: EncodingType;
}): Promise<void> {
const result = await dispatch("CONNECT_AND_EXPORT_TEXT", {
filePath,
encoding,
});

if (
result === undefined ||
result.result === "SUCCESS" ||
result.result === "CANCELED"
)
return;

let msg = "";
switch (result.result) {
case "WRITE_ERROR":
msg =
"書き込みエラーによって失敗しました。空き容量があることや、書き込み権限があることをご確認ください。";
break;
}

quasarDialog({
title: "テキストの書き出しに失敗しました。",
message: msg,
ok: {
label: "閉じる",
flat: true,
textColor: "secondary",
},
});
}
18 changes: 18 additions & 0 deletions src/components/MenuBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
generateAndConnectAndSaveAudioWithDialog,
generateAndSaveAllAudioWithDialog,
generateAndSaveOneAudioWithDialog,
connectAndExportTextWithDialog,
} from "@/components/Dialog";

type MenuItemBase<T extends string> = {
Expand Down Expand Up @@ -147,6 +148,16 @@ export default defineComponent({
});
};

const connectAndExportText = async () => {
if (!uiLocked.value) {
await connectAndExportTextWithDialog({
quasarDialog: $q.dialog,
dispatch: store.dispatch,
encoding: store.state.savingSetting.fileEncoding,
});
}
};

const importTextFile = () => {
if (!uiLocked.value) {
store.dispatch("COMMAND_IMPORT_FROM_FILE", {});
Expand Down Expand Up @@ -231,6 +242,13 @@ export default defineComponent({
generateAndConnectAndSaveAllAudio();
},
},
{
type: "button",
label: "テキストを繋げて書き出し",
onClick: () => {
connectAndExportText();
},
},
{
type: "button",
label: "テキスト読み込み",
Expand Down
4 changes: 4 additions & 0 deletions src/electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ const api: Sandbox = {
return ipcRendererInvoke("SHOW_AUDIO_SAVE_DIALOG", { title, defaultPath });
},

showTextSaveDialog: ({ title, defaultPath }) => {
return ipcRendererInvoke("SHOW_TEXT_SAVE_DIALOG", { title, defaultPath });
},

showOpenDirectoryDialog: ({ title }) => {
return ipcRendererInvoke("SHOW_OPEN_DIRECTORY_DIALOG", { title });
},
Expand Down
81 changes: 81 additions & 0 deletions src/store/audio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1267,6 +1267,87 @@ export const audioStore: VoiceVoxStoreOptions<
return { result: "SUCCESS", path: filePath };
}
),
CONNECT_AND_EXPORT_TEXT: createUILockAction(
async (
{ state, dispatch, getters },
{ filePath, encoding }: { filePath?: string; encoding?: EncodingType }
): Promise<SaveResultObject> => {
const defaultFileName = buildProjectFileName(state, "txt");
if (state.savingSetting.fixedExportEnabled) {
filePath = path.join(
state.savingSetting.fixedExportDir,
defaultFileName
);
} else {
filePath ??= await window.electron.showTextSaveDialog({
title: "文章を全て繋げてテキストファイルに保存",
defaultPath: defaultFileName,
});
}

if (!filePath) {
return { result: "CANCELED", path: "" };
}

if (state.savingSetting.avoidOverwrite) {
let tail = 1;
const name = filePath.slice(0, filePath.length - 4);
while (await dispatch("CHECK_FILE_EXISTS", { file: filePath })) {
filePath = name + "[" + tail.toString() + "]" + ".wav";
tail += 1;
}
}

const characters = new Map<number, string>();

if (!getters.USER_ORDERED_CHARACTER_INFOS)
throw new Error("USER_ORDERED_CHARACTER_INFOS == undefined");

for (const characterInfo of getters.USER_ORDERED_CHARACTER_INFOS) {
for (const style of characterInfo.metas.styles) {
characters.set(style.styleId, characterInfo.metas.speakerName);
}
}

const texts: string[] = [];
for (const audioKey of state.audioKeys) {
const styleId = state.audioItems[audioKey].styleId;
const speakerName =
styleId !== undefined ? characters.get(styleId) + "," : "";

texts.push(speakerName + state.audioItems[audioKey].text);
}

const textBlob = ((): Blob => {
const text = texts.join("\n");
if (!encoding || encoding === "UTF-8") {
const bom = new Uint8Array([0xef, 0xbb, 0xbf]);
return new Blob([bom, text], {
type: "text/plain;charset=UTF-8",
});
}
const sjisArray = Encoding.convert(Encoding.stringToCode(text), {
to: "SJIS",
type: "arraybuffer",
});
return new Blob([new Uint8Array(sjisArray)], {
type: "text/plain;charset=Shift_JIS",
});
})();

try {
window.electron.writeFile({
filePath: filePath,
buffer: await textBlob.arrayBuffer(),
});
} catch (e) {
window.electron.logError(e);
return { result: "WRITE_ERROR", path: filePath };
}

return { result: "SUCCESS", path: filePath };
}
),
PLAY_AUDIO: createUILockAction(
async ({ commit, dispatch }, { audioKey }: { audioKey: string }) => {
const audioElem = audioElements[audioKey];
Expand Down
7 changes: 7 additions & 0 deletions src/store/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,13 @@ type AudioStoreTypes = {
}): SaveResultObject | undefined;
};

CONNECT_AND_EXPORT_TEXT: {
action(payload: {
filePath?: string;
encoding?: EncodingType;
}): SaveResultObject | undefined;
};

PLAY_AUDIO: {
action(payload: { audioKey: string }): boolean;
};
Expand Down
4 changes: 4 additions & 0 deletions src/type/preload.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export interface Sandbox {
title: string;
defaultPath?: string;
}): Promise<string | undefined>;
showTextSaveDialog(obj: {
title: string;
defaultPath?: string;
}): Promise<string | undefined>;
showOpenDirectoryDialog(obj: { title: string }): Promise<string | undefined>;
showProjectSaveDialog(obj: {
title: string;
Expand Down