From 28114efdc604e98b69ae63d331ec28fcc757aeca Mon Sep 17 00:00:00 2001 From: Nayam Amarshe <25067102+NayamAmarshe@users.noreply.github.com> Date: Sun, 6 Aug 2023 18:19:22 +0530 Subject: [PATCH] Refactor code --- electron/index.ts | 563 +++--------------- electron/utils/listener/customModelsSelect.ts | 61 ++ electron/utils/listener/doubleUpscayl.ts | 198 ++++++ electron/utils/listener/folderUpscayl.ts | 106 ++++ electron/utils/listener/getModelsList.ts | 29 + electron/utils/listener/imageUpscayl.ts | 162 +++++ electron/utils/listener/selectFile.ts | 59 ++ electron/utils/listener/selectFolder.ts | 25 + main/utils/listener/doubleUpscayl.js | 139 +++++ main/utils/listener/folderUpscayl.js | 73 +++ 10 files changed, 925 insertions(+), 490 deletions(-) create mode 100644 electron/utils/listener/customModelsSelect.ts create mode 100644 electron/utils/listener/doubleUpscayl.ts create mode 100644 electron/utils/listener/folderUpscayl.ts create mode 100644 electron/utils/listener/getModelsList.ts create mode 100644 electron/utils/listener/imageUpscayl.ts create mode 100644 electron/utils/listener/selectFile.ts create mode 100644 electron/utils/listener/selectFolder.ts create mode 100644 main/utils/listener/doubleUpscayl.js create mode 100644 main/utils/listener/folderUpscayl.js diff --git a/electron/index.ts b/electron/index.ts index 9ba936c..282939b 100644 --- a/electron/index.ts +++ b/electron/index.ts @@ -1,18 +1,10 @@ -import { - getBatchArguments, - getDoubleUpscaleArguments, - getDoubleUpscaleSecondPassArguments, - getSingleImageArguments, -} from "./utils/getArguments"; // Native import { autoUpdater } from "electron-updater"; import getPlatform from "./getPlatform"; -// import ffmpeg from "upscayl-ffmpeg"; -import { join, parse } from "path"; +import { join } from "path"; import log from "electron-log"; import { format } from "url"; import fs from "fs"; -import Jimp from "jimp"; import { execPath, modelsPath } from "./binaries"; // Packages @@ -26,11 +18,17 @@ import { protocol, } from "electron"; -import { spawnUpscayl } from "./upscayl"; import prepareNext from "electron-next"; import isDev from "electron-is-dev"; import commands from "./commands"; import { ChildProcessWithoutNullStreams } from "child_process"; +import doubleUpscayl from "./utils/listener/doubleUpscayl"; +import folderUpscayl from "./utils/listener/folderUpscayl"; +import imageUpscayl from "./utils/listener/imageUpscayl"; +import customModelsSelect from "./utils/listener/customModelsSelect"; +import getModelsList from "./utils/listener/getModelsList"; +import selectFile from "./utils/listener/selectFile"; +import selectFolder from "./utils/listener/selectFolder"; let childProcesses: { process: ChildProcessWithoutNullStreams; @@ -57,7 +55,7 @@ let stopped = false; const slash: string = getPlatform() === "win" ? "\\" : "/"; // Prepare the renderer once the app is ready -let mainWindow: BrowserWindow; +let mainWindow: BrowserWindow | null = null; app.on("ready", async () => { await prepareNext("./renderer"); @@ -96,6 +94,7 @@ app.on("ready", async () => { }); mainWindow.once("ready-to-show", () => { + if (!mainWindow) return; mainWindow.show(); mainWindow.webContents.setZoomFactor(1); }); @@ -111,6 +110,7 @@ app.on("ready", async () => { autoUpdater.checkForUpdates(); } + // <------------------------Save Last Paths-----------------------------> // GET LAST IMAGE PATH TO LOCAL STORAGE mainWindow.webContents .executeJavaScript('localStorage.getItem("lastImagePath");', true) @@ -119,7 +119,6 @@ app.on("ready", async () => { imagePath = lastImagePath; } }); - // GET LAST FOLDER PATH TO LOCAL STORAGE mainWindow.webContents .executeJavaScript('localStorage.getItem("lastFolderPath");', true) @@ -128,7 +127,6 @@ app.on("ready", async () => { folderPath = lastFolderPath; } }); - // GET LAST CUSTOM MODELS FOLDER PATH TO LOCAL STORAGE mainWindow.webContents .executeJavaScript( @@ -140,7 +138,6 @@ app.on("ready", async () => { customModelsFolderPath = lastCustomModelsFolderPath; } }); - // GET LAST CUSTOM MODELS FOLDER PATH TO LOCAL STORAGE mainWindow.webContents .executeJavaScript('localStorage.getItem("lastOutputFolderPath");', true) @@ -149,7 +146,6 @@ app.on("ready", async () => { outputFolderPath = lastOutputFolderPath; } }); - // GET LAST SAVE OUTPUT FOLDER (BOOLEAN) TO LOCAL STORAGE mainWindow.webContents .executeJavaScript('localStorage.getItem("rememberOutputFolder");', true) @@ -158,7 +154,6 @@ app.on("ready", async () => { saveOutputFolder = lastSaveOutputFolder; } }); - // GET IMAGE QUALITY (NUMBER) TO LOCAL STORAGE mainWindow.webContents .executeJavaScript('localStorage.getItem("quality");', true) @@ -176,6 +171,7 @@ log.log("🚃 App Path: ", app.getAppPath()); const logit = (...args: any) => { log.log(...args); + if (!mainWindow) return; mainWindow.webContents.send(commands.LOG, args.join(" ")); }; @@ -188,73 +184,7 @@ const defaultModels = [ "realesrgan-x4plus-anime", ]; -//------------------------Select File-----------------------------// // ! DONT FORGET TO RESTART THE APP WHEN YOU CHANGE CODE HERE -ipcMain.handle(commands.SELECT_FILE, async () => { - const { canceled, filePaths } = await dialog.showOpenDialog({ - properties: ["openFile", "multiSelections"], - title: "Select Image", - defaultPath: imagePath, - }); - - if (canceled) { - logit("🚫 File Operation Cancelled"); - return null; - } else { - imagePath = filePaths[0]; - - let isValid = false; - // READ SELECTED FILES - filePaths.forEach((file) => { - // log.log("Files in Folder: ", file); - if ( - file.endsWith(".png") || - file.endsWith(".jpg") || - file.endsWith(".jpeg") || - file.endsWith(".webp") || - file.endsWith(".JPG") || - file.endsWith(".PNG") || - file.endsWith(".JPEG") || - file.endsWith(".WEBP") - ) { - isValid = true; - } - }); - - if (!isValid) { - logit("❌ Invalid File Detected"); - const options: MessageBoxOptions = { - type: "error", - title: "Invalid File", - message: - "The selected file is not a valid image. Make sure you select a '.png', '.jpg', or '.webp' file.", - }; - dialog.showMessageBoxSync(mainWindow, options); - return null; - } - - logit("📄 Selected File Path: ", filePaths[0]); - // CREATE input AND upscaled FOLDER - return filePaths[0]; - } -}); - -//------------------------Select Folder-----------------------------// -ipcMain.handle(commands.SELECT_FOLDER, async (event, message) => { - const { canceled, filePaths: folderPaths } = await dialog.showOpenDialog({ - properties: ["openDirectory"], - defaultPath: folderPath, - }); - - if (canceled) { - logit("🚫 Select Folder Operation Cancelled"); - return null; - } else { - folderPath = folderPaths[0]; - logit("📁 Selected Folder Path: ", folderPath); - return folderPaths[0]; - } -}); //------------------------Get Model Names-----------------------------// const getModels = (folderPath: string) => { @@ -295,58 +225,6 @@ const getModels = (folderPath: string) => { return models; }; -ipcMain.on(commands.GET_MODELS_LIST, async (event, payload) => { - if (payload) { - customModelsFolderPath = payload; - - logit("📁 Custom Models Folder Path: ", customModelsFolderPath); - - mainWindow.webContents.send( - commands.CUSTOM_MODEL_FILES_LIST, - getModels(payload) - ); - } -}); - -//------------------------Select Custom Models Folder---------------------// -ipcMain.handle(commands.SELECT_CUSTOM_MODEL_FOLDER, async (event, message) => { - const { canceled, filePaths: folderPaths } = await dialog.showOpenDialog({ - properties: ["openDirectory"], - title: "Select Custom Models Folder", - defaultPath: customModelsFolderPath, - }); - if (canceled) { - logit("🚫 Select Custom Models Folder Operation Cancelled"); - return null; - } else { - customModelsFolderPath = folderPaths[0]; - - if ( - !folderPaths[0].endsWith(slash + "models") && - !folderPaths[0].endsWith(slash + "models" + slash) - ) { - logit("❌ Invalid Custom Models Folder Detected: Not a 'models' folder"); - const options: MessageBoxOptions = { - type: "error", - title: "Invalid Folder", - message: - "Please make sure that the folder name is 'models' and nothing else.", - buttons: ["OK"], - }; - dialog.showMessageBoxSync(options); - return null; - } - - mainWindow.webContents.send( - commands.CUSTOM_MODEL_FILES_LIST, - getModels(customModelsFolderPath) - ); - - logit("📁 Custom Folder Path: ", customModelsFolderPath); - return customModelsFolderPath; - } -}); - //------------------------Open Folder-----------------------------// ipcMain.on(commands.OPEN_FOLDER, async (event, payload) => { logit("📂 Opening Folder: ", payload); @@ -363,363 +241,68 @@ ipcMain.on(commands.STOP, async (event, payload) => { }); }); -//------------------------Image Upscayl-----------------------------// -ipcMain.on(commands.UPSCAYL, async (event, payload) => { - const model = payload.model as string; - const gpuId = payload.gpuId as string; - const saveImageAs = payload.saveImageAs as string; - - let inputDir = (payload.imagePath.match(/(.*)[\/\\]/)[1] || "") as string; - let outputDir = folderPath || (payload.outputPath as string); - - if (saveOutputFolder === true && outputFolderPath) { - outputDir = outputFolderPath; - } - - const isDefaultModel = defaultModels.includes(model); - - const fullfileName = payload.imagePath.replace(/^.*[\\\/]/, "") as string; - const fileName = parse(fullfileName).name; - const fileExt = parse(fullfileName).ext; - - let scale = "4"; - if (model.includes("x2")) { - scale = "2"; - } else if (model.includes("x3")) { - scale = "3"; - } else { - scale = "4"; - } - - const outFile = - outputDir + - slash + - fileName + - "_upscayl_" + - payload.scale + - "x_" + - model + - "." + - saveImageAs; - - // UPSCALE - if (fs.existsSync(outFile)) { - // If already upscayled, just output that file - logit("✅ Already upscayled at: ", outFile); - mainWindow.webContents.send(commands.UPSCAYL_DONE, outFile); - } else { - const upscayl = spawnUpscayl( - "realesrgan", - getSingleImageArguments( - inputDir, - fullfileName, - outFile, - isDefaultModel ? modelsPath : customModelsFolderPath ?? modelsPath, - model, - scale, - gpuId, - saveImageAs - ), - logit - ); - - childProcesses.push(upscayl); - - stopped = false; - let isAlpha = false; - let failed = false; - - const onData = (data: string) => { - logit("image upscayl: ", data.toString()); - mainWindow.setProgressBar(parseFloat(data.slice(0, data.length)) / 100); - data = data.toString(); - mainWindow.webContents.send(commands.UPSCAYL_PROGRESS, data.toString()); - if (data.includes("invalid gpu") || data.includes("failed")) { - logit("❌ INVALID GPU OR FAILED"); - failed = true; - } - if (data.includes("has alpha channel")) { - logit("📢 INCLUDES ALPHA CHANNEL, CHANGING OUTFILE NAME!"); - isAlpha = true; - } - }; - const onError = (data) => { - mainWindow.webContents.send(commands.UPSCAYL_PROGRESS, data.toString()); - failed = true; - return; - }; - const onClose = async () => { - if (!failed && !stopped) { - logit("💯 Done upscaling"); - logit("♻ Scaling and converting now..."); - const originalImage = await Jimp.read(inputDir + slash + fullfileName); - try { - const newImage = await Jimp.read( - isAlpha ? outFile + ".png" : outFile - ); - try { - newImage - .scaleToFit( - originalImage.getWidth() * parseInt(payload.scale), - originalImage.getHeight() * parseInt(payload.scale) - ) - .quality(100 - quality) - .write(isAlpha ? outFile + ".png" : outFile); - mainWindow.setProgressBar(-1); - mainWindow.webContents.send( - commands.UPSCAYL_DONE, - isAlpha ? outFile + ".png" : outFile - ); - } catch (error) { - logit("❌ Error converting to PNG: ", error); - onError(error); - } - } catch (error) { - logit("❌ Error reading original image metadata", error); - onError(error); - } - } - }; - - upscayl.process.stderr.on("data", onData); - upscayl.process.on("error", onError); - upscayl.process.on("close", onClose); - } -}); - -//------------------------Double Upscayl-----------------------------// -ipcMain.on(commands.DOUBLE_UPSCAYL, async (event, payload) => { - const model = payload.model as string; - let inputDir = (payload.imagePath.match(/(.*)[\/\\]/)[1] || "") as string; - let outputDir = payload.outputPath as string; - - if (saveOutputFolder === true && outputFolderPath) { - outputDir = outputFolderPath; - } - const gpuId = payload.gpuId as string; - const saveImageAs = payload.saveImageAs as string; - - const isDefaultModel = defaultModels.includes(model); - - // COPY IMAGE TO TMP FOLDER - - const fullfileName = payload.imagePath.split(slash).slice(-1)[0] as string; - const fileName = parse(fullfileName).name; - const outFile = - outputDir + slash + fileName + "_upscayl_16x_" + model + "." + saveImageAs; - - let scale = "4"; - if (model.includes("x2")) { - scale = "2"; - } else if (model.includes("x3")) { - scale = "3"; - } else { - scale = "4"; - } - - // UPSCALE - let upscayl = spawnUpscayl( - "realesrgan", - getDoubleUpscaleArguments( - inputDir, - fullfileName, - outFile, - isDefaultModel ? modelsPath : customModelsFolderPath ?? modelsPath, - model, - gpuId, - saveImageAs, - scale - ), - logit - ); - - childProcesses.push(upscayl); - - stopped = false; - let failed = false; - let isAlpha = false; - let failed2 = false; - - const onData = (data) => { - // CONVERT DATA TO STRING - data = data.toString(); - // SEND UPSCAYL PROGRESS TO RENDERER - mainWindow.webContents.send(commands.DOUBLE_UPSCAYL_PROGRESS, data); - // IF PROGRESS HAS ERROR, UPSCAYL FAILED - if (data.includes("invalid gpu") || data.includes("failed")) { - failed = true; - } - if (data.includes("has alpha channel")) { - isAlpha = true; - } - }; - - const onError = (data) => { - data.toString(); - // SEND UPSCAYL PROGRESS TO RENDERER - mainWindow.webContents.send(commands.DOUBLE_UPSCAYL_PROGRESS, data); - // SET FAILED TO TRUE - failed = true; - return; - }; - - const onData2 = (data) => { - // CONVERT DATA TO STRING - data = data.toString(); - // SEND UPSCAYL PROGRESS TO RENDERER - mainWindow.webContents.send(commands.DOUBLE_UPSCAYL_PROGRESS, data); - // IF PROGRESS HAS ERROR, UPSCAYL FAILED - if (data.includes("invalid gpu") || data.includes("failed")) { - failed2 = true; - } - }; - - const onError2 = (data) => { - data.toString(); - // SEND UPSCAYL PROGRESS TO RENDERER - mainWindow.webContents.send(commands.DOUBLE_UPSCAYL_PROGRESS, data); - // SET FAILED TO TRUE - failed2 = true; - return; - }; - - const onClose2 = async (code) => { - if (!failed2 && !stopped) { - logit("💯 Done upscaling"); - logit("♻ Scaling and converting now..."); - const originalImage = await Jimp.read(inputDir + slash + fullfileName); - try { - const newImage = await Jimp.read(isAlpha ? outFile + ".png" : outFile); - try { - newImage - .scaleToFit( - originalImage.getWidth() * parseInt(payload.scale), - originalImage.getHeight() * parseInt(payload.scale) - ) - .quality(100 - quality) - .write(isAlpha ? outFile + ".png" : outFile); - mainWindow.setProgressBar(-1); - mainWindow.webContents.send( - commands.DOUBLE_UPSCAYL_DONE, - isAlpha ? outFile + ".png" : outFile - ); - } catch (error) { - logit("❌ Error converting to PNG: ", error); - onError(error); - } - } catch (error) { - logit("❌ Error reading original image metadata", error); - onError(error); - } - } - }; - - upscayl.process.stderr.on("data", onData); - upscayl.process.on("error", onError); - upscayl.process.on("close", (code) => { - // IF NOT FAILED - if (!failed && !stopped) { - // UPSCALE - let upscayl2 = spawnUpscayl( - "realesrgan", - getDoubleUpscaleSecondPassArguments( - isAlpha, - outFile, - isDefaultModel ? modelsPath : customModelsFolderPath ?? modelsPath, - model, - gpuId, - saveImageAs, - scale - ), - logit - ); - - childProcesses.push(upscayl2); - - upscayl2.process.stderr.on("data", onData2); - upscayl2.process.on("error", onError2); - upscayl2.process.on("close", onClose2); - } +if (mainWindow) { + selectFolder({ + folderPath, + logit, }); -}); - -//------------------------Upscayl Folder-----------------------------// -ipcMain.on(commands.FOLDER_UPSCAYL, async (event, payload) => { - // GET THE MODEL - const model = payload.model; - const gpuId = payload.gpuId; - const saveImageAs = payload.saveImageAs; - const scale = payload.scale as string; - - // GET THE IMAGE DIRECTORY - let inputDir = payload.batchFolderPath; - // GET THE OUTPUT DIRECTORY - let outputDir = payload.outputPath; - - if (saveOutputFolder === true && outputFolderPath) { - outputDir = outputFolderPath; - } - - if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }); - } - - const isDefaultModel = defaultModels.includes(model); - - // UPSCALE - const upscayl = spawnUpscayl( - "realesrgan", - getBatchArguments( - inputDir, - outputDir, - isDefaultModel ? modelsPath : customModelsFolderPath ?? modelsPath, - model, - gpuId, - saveImageAs, - scale - ), - logit - ); - - childProcesses.push(upscayl); - - stopped = false; - let failed = false; - - const onData = (data: any) => { - data = data.toString(); - mainWindow.webContents.send( - commands.FOLDER_UPSCAYL_PROGRESS, - data.toString() - ); - if (data.includes("invalid gpu") || data.includes("failed")) { - logit("❌ INVALID GPU OR INVALID FILES IN FOLDER - FAILED"); - failed = true; - upscayl.kill(); - } - }; - const onError = (data: any) => { - mainWindow.webContents.send( - commands.FOLDER_UPSCAYL_PROGRESS, - data.toString() - ); - failed = true; - upscayl.kill(); - return; - }; - const onClose = () => { - if (!failed && !stopped) { - logit("💯 Done upscaling"); - mainWindow.webContents.send(commands.FOLDER_UPSCAYL_DONE, outputDir); - } else { - upscayl.kill(); - } - }; - - upscayl.process.stderr.on("data", onData); - upscayl.process.on("error", onError); - upscayl.process.on("close", onClose); -}); + selectFile({ + mainWindow, + imagePath, + logit, + }); + getModelsList({ + mainWindow, + customModelsFolderPath, + logit, + getModels, + }); + customModelsSelect({ + mainWindow, + customModelsFolderPath, + logit, + slash, + getModels, + }); + imageUpscayl({ + mainWindow, + slash, + logit, + childProcesses, + stopped, + modelsPath, + customModelsFolderPath, + saveOutputFolder, + outputFolderPath, + quality, + defaultModels, + folderPath, + }); + folderUpscayl({ + mainWindow, + logit, + childProcesses, + stopped, + modelsPath, + customModelsFolderPath, + saveOutputFolder, + outputFolderPath, + defaultModels, + }); + doubleUpscayl({ + mainWindow, + slash, + logit, + childProcesses, + stopped, + modelsPath, + customModelsFolderPath, + saveOutputFolder, + outputFolderPath, + quality, + defaultModels, + }); +} //------------------------Auto-Update Code-----------------------------// autoUpdater.autoInstallOnAppQuit = false; diff --git a/electron/utils/listener/customModelsSelect.ts b/electron/utils/listener/customModelsSelect.ts new file mode 100644 index 0000000..028a5c5 --- /dev/null +++ b/electron/utils/listener/customModelsSelect.ts @@ -0,0 +1,61 @@ +import { MessageBoxOptions, dialog, ipcMain } from "electron"; +import commands from "../../commands"; + +export type CustomModelsSelectProps = { + mainWindow: Electron.BrowserWindow; + customModelsFolderPath: string | undefined; + logit: (message: string, ...optionalParams: any[]) => void; + slash: string; + getModels: (folderPath: string) => string[] | null; +}; + +export default function ({ + mainWindow, + customModelsFolderPath, + logit, + slash, + getModels, +}: CustomModelsSelectProps) { + ipcMain.handle( + commands.SELECT_CUSTOM_MODEL_FOLDER, + async (event, message) => { + const { canceled, filePaths: folderPaths } = await dialog.showOpenDialog({ + properties: ["openDirectory"], + title: "Select Custom Models Folder", + defaultPath: customModelsFolderPath, + }); + if (canceled) { + logit("🚫 Select Custom Models Folder Operation Cancelled"); + return null; + } else { + customModelsFolderPath = folderPaths[0]; + + if ( + !folderPaths[0].endsWith(slash + "models") && + !folderPaths[0].endsWith(slash + "models" + slash) + ) { + logit( + "❌ Invalid Custom Models Folder Detected: Not a 'models' folder" + ); + const options: MessageBoxOptions = { + type: "error", + title: "Invalid Folder", + message: + "Please make sure that the folder name is 'models' and nothing else.", + buttons: ["OK"], + }; + dialog.showMessageBoxSync(options); + return null; + } + + mainWindow.webContents.send( + commands.CUSTOM_MODEL_FILES_LIST, + getModels(customModelsFolderPath) + ); + + logit("📁 Custom Folder Path: ", customModelsFolderPath); + return customModelsFolderPath; + } + } + ); +} diff --git a/electron/utils/listener/doubleUpscayl.ts b/electron/utils/listener/doubleUpscayl.ts new file mode 100644 index 0000000..3e6e4eb --- /dev/null +++ b/electron/utils/listener/doubleUpscayl.ts @@ -0,0 +1,198 @@ +import { ipcMain } from "electron"; +import commands from "../../commands"; +import { parse } from "path"; +import { spawnUpscayl } from "../../upscayl"; +import { + getDoubleUpscaleArguments, + getDoubleUpscaleSecondPassArguments, +} from "../getArguments"; +import Jimp from "jimp"; + +export default function ({ + mainWindow, + slash, + logit, + childProcesses, + stopped, + modelsPath, + customModelsFolderPath, + saveOutputFolder, + outputFolderPath, + quality, + defaultModels, +}: { + mainWindow: Electron.BrowserWindow; + slash: string; + logit: (message: string, ...optionalParams: any[]) => void; + childProcesses: any[]; + stopped: boolean; + modelsPath: string; + customModelsFolderPath: string | undefined; + saveOutputFolder: boolean; + outputFolderPath: string | undefined; + quality: number; + defaultModels: string[]; +}) { + ipcMain.on(commands.DOUBLE_UPSCAYL, async (event, payload) => { + const model = payload.model as string; + let inputDir = (payload.imagePath.match(/(.*)[\/\\]/)[1] || "") as string; + let outputDir = payload.outputPath as string; + + if (saveOutputFolder === true && outputFolderPath) { + outputDir = outputFolderPath; + } + const gpuId = payload.gpuId as string; + const saveImageAs = payload.saveImageAs as string; + + const isDefaultModel = defaultModels.includes(model); + + // COPY IMAGE TO TMP FOLDER + + const fullfileName = payload.imagePath.split(slash).slice(-1)[0] as string; + const fileName = parse(fullfileName).name; + const outFile = + outputDir + + slash + + fileName + + "_upscayl_16x_" + + model + + "." + + saveImageAs; + + let scale = "4"; + if (model.includes("x2")) { + scale = "2"; + } else if (model.includes("x3")) { + scale = "3"; + } else { + scale = "4"; + } + + // UPSCALE + let upscayl = spawnUpscayl( + "realesrgan", + getDoubleUpscaleArguments( + inputDir, + fullfileName, + outFile, + isDefaultModel ? modelsPath : customModelsFolderPath ?? modelsPath, + model, + gpuId, + saveImageAs, + scale + ), + logit + ); + + childProcesses.push(upscayl); + + stopped = false; + let failed = false; + let isAlpha = false; + let failed2 = false; + + const onData = (data) => { + // CONVERT DATA TO STRING + data = data.toString(); + // SEND UPSCAYL PROGRESS TO RENDERER + mainWindow.webContents.send(commands.DOUBLE_UPSCAYL_PROGRESS, data); + // IF PROGRESS HAS ERROR, UPSCAYL FAILED + if (data.includes("invalid gpu") || data.includes("failed")) { + failed = true; + } + if (data.includes("has alpha channel")) { + isAlpha = true; + } + }; + + const onError = (data) => { + data.toString(); + // SEND UPSCAYL PROGRESS TO RENDERER + mainWindow.webContents.send(commands.DOUBLE_UPSCAYL_PROGRESS, data); + // SET FAILED TO TRUE + failed = true; + return; + }; + + const onData2 = (data) => { + // CONVERT DATA TO STRING + data = data.toString(); + // SEND UPSCAYL PROGRESS TO RENDERER + mainWindow.webContents.send(commands.DOUBLE_UPSCAYL_PROGRESS, data); + // IF PROGRESS HAS ERROR, UPSCAYL FAILED + if (data.includes("invalid gpu") || data.includes("failed")) { + failed2 = true; + } + }; + + const onError2 = (data) => { + data.toString(); + // SEND UPSCAYL PROGRESS TO RENDERER + mainWindow.webContents.send(commands.DOUBLE_UPSCAYL_PROGRESS, data); + // SET FAILED TO TRUE + failed2 = true; + return; + }; + + const onClose2 = async (code) => { + if (!failed2 && !stopped) { + logit("💯 Done upscaling"); + logit("♻ Scaling and converting now..."); + const originalImage = await Jimp.read(inputDir + slash + fullfileName); + try { + const newImage = await Jimp.read( + isAlpha ? outFile + ".png" : outFile + ); + try { + newImage + .scaleToFit( + originalImage.getWidth() * parseInt(payload.scale), + originalImage.getHeight() * parseInt(payload.scale) + ) + .quality(100 - quality) + .write(isAlpha ? outFile + ".png" : outFile); + mainWindow.setProgressBar(-1); + mainWindow.webContents.send( + commands.DOUBLE_UPSCAYL_DONE, + isAlpha ? outFile + ".png" : outFile + ); + } catch (error) { + logit("❌ Error converting to PNG: ", error); + onError(error); + } + } catch (error) { + logit("❌ Error reading original image metadata", error); + onError(error); + } + } + }; + + upscayl.process.stderr.on("data", onData); + upscayl.process.on("error", onError); + upscayl.process.on("close", (code) => { + // IF NOT FAILED + if (!failed && !stopped) { + // UPSCALE + let upscayl2 = spawnUpscayl( + "realesrgan", + getDoubleUpscaleSecondPassArguments( + isAlpha, + outFile, + isDefaultModel ? modelsPath : customModelsFolderPath ?? modelsPath, + model, + gpuId, + saveImageAs, + scale + ), + logit + ); + + childProcesses.push(upscayl2); + + upscayl2.process.stderr.on("data", onData2); + upscayl2.process.on("error", onError2); + upscayl2.process.on("close", onClose2); + } + }); + }); +} diff --git a/electron/utils/listener/folderUpscayl.ts b/electron/utils/listener/folderUpscayl.ts new file mode 100644 index 0000000..7d59bfb --- /dev/null +++ b/electron/utils/listener/folderUpscayl.ts @@ -0,0 +1,106 @@ +import { ipcMain } from "electron"; +import commands from "../../commands"; +import { spawnUpscayl } from "../../upscayl"; +import { getBatchArguments } from "../getArguments"; +import fs from "fs"; + +export type FolderUpscaylProps = { + mainWindow: Electron.BrowserWindow; + logit: (message: string, ...optionalParams: any[]) => void; + childProcesses: any[]; + stopped: boolean; + modelsPath: string; + customModelsFolderPath: string | undefined; + saveOutputFolder: boolean; + outputFolderPath: string | undefined; + defaultModels: string[]; +}; + +export default function ({ + mainWindow, + logit, + childProcesses, + stopped, + modelsPath, + customModelsFolderPath, + saveOutputFolder, + outputFolderPath, + defaultModels, +}: FolderUpscaylProps) { + ipcMain.on(commands.FOLDER_UPSCAYL, async (event, payload) => { + // GET THE MODEL + const model = payload.model; + const gpuId = payload.gpuId; + const saveImageAs = payload.saveImageAs; + const scale = payload.scale as string; + + // GET THE IMAGE DIRECTORY + let inputDir = payload.batchFolderPath; + // GET THE OUTPUT DIRECTORY + let outputDir = payload.outputPath; + + if (saveOutputFolder === true && outputFolderPath) { + outputDir = outputFolderPath; + } + + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } + + const isDefaultModel = defaultModels.includes(model); + + // UPSCALE + const upscayl = spawnUpscayl( + "realesrgan", + getBatchArguments( + inputDir, + outputDir, + isDefaultModel ? modelsPath : customModelsFolderPath ?? modelsPath, + model, + gpuId, + saveImageAs, + scale + ), + logit + ); + + childProcesses.push(upscayl); + + stopped = false; + let failed = false; + + const onData = (data: any) => { + data = data.toString(); + mainWindow.webContents.send( + commands.FOLDER_UPSCAYL_PROGRESS, + data.toString() + ); + if (data.includes("invalid gpu") || data.includes("failed")) { + logit("❌ INVALID GPU OR INVALID FILES IN FOLDER - FAILED"); + failed = true; + upscayl.kill(); + } + }; + const onError = (data: any) => { + mainWindow.webContents.send( + commands.FOLDER_UPSCAYL_PROGRESS, + data.toString() + ); + failed = true; + upscayl.kill(); + return; + }; + const onClose = () => { + if (!failed && !stopped) { + logit("💯 Done upscaling"); + mainWindow.webContents.send(commands.FOLDER_UPSCAYL_DONE, outputDir); + } else { + upscayl.kill(); + } + }; + + upscayl.process.stderr.on("data", onData); + upscayl.process.on("error", onError); + upscayl.process.on("close", onClose); + }); +} diff --git a/electron/utils/listener/getModelsList.ts b/electron/utils/listener/getModelsList.ts new file mode 100644 index 0000000..d9d62ed --- /dev/null +++ b/electron/utils/listener/getModelsList.ts @@ -0,0 +1,29 @@ +import { ipcMain } from "electron"; +import commands from "../../commands"; + +export type GetModelsListProps = { + mainWindow: Electron.BrowserWindow; + customModelsFolderPath: string | undefined; + logit: (message: string, ...optionalParams: any[]) => void; + getModels: (folderPath: string) => string[] | null; +}; + +export default function ({ + mainWindow, + customModelsFolderPath, + logit, + getModels, +}: GetModelsListProps) { + ipcMain.on(commands.GET_MODELS_LIST, async (event, payload) => { + if (payload) { + customModelsFolderPath = payload; + + logit("📁 Custom Models Folder Path: ", customModelsFolderPath); + + mainWindow.webContents.send( + commands.CUSTOM_MODEL_FILES_LIST, + getModels(payload) + ); + } + }); +} diff --git a/electron/utils/listener/imageUpscayl.ts b/electron/utils/listener/imageUpscayl.ts new file mode 100644 index 0000000..2c5e37d --- /dev/null +++ b/electron/utils/listener/imageUpscayl.ts @@ -0,0 +1,162 @@ +import { ipcMain } from "electron"; +import commands from "../../commands"; +import { parse } from "path"; +import { spawnUpscayl } from "../../upscayl"; +import Jimp from "jimp"; +import { getSingleImageArguments } from "../getArguments"; +import fs from "fs"; + +export type ImageUpscaylProps = { + mainWindow: Electron.BrowserWindow; + slash: string; + logit: (...args: any) => void; + childProcesses: any[]; + stopped: boolean; + modelsPath: string; + customModelsFolderPath: string | undefined; + saveOutputFolder: boolean; + outputFolderPath: string | undefined; + quality: number; + defaultModels: string[]; + folderPath: string | undefined; +}; + +export default function ({ + mainWindow, + slash, + logit, + childProcesses, + stopped, + modelsPath, + customModelsFolderPath, + saveOutputFolder, + outputFolderPath, + quality, + defaultModels, + folderPath, +}: ImageUpscaylProps) { + ipcMain.on(commands.UPSCAYL, async (event, payload) => { + const model = payload.model as string; + const gpuId = payload.gpuId as string; + const saveImageAs = payload.saveImageAs as string; + + let inputDir = (payload.imagePath.match(/(.*)[\/\\]/)[1] || "") as string; + let outputDir = folderPath || (payload.outputPath as string); + + if (saveOutputFolder === true && outputFolderPath) { + outputDir = outputFolderPath; + } + + const isDefaultModel = defaultModels.includes(model); + + const fullfileName = payload.imagePath.replace(/^.*[\\\/]/, "") as string; + const fileName = parse(fullfileName).name; + const fileExt = parse(fullfileName).ext; + + let scale = "4"; + if (model.includes("x2")) { + scale = "2"; + } else if (model.includes("x3")) { + scale = "3"; + } else { + scale = "4"; + } + + const outFile = + outputDir + + slash + + fileName + + "_upscayl_" + + payload.scale + + "x_" + + model + + "." + + saveImageAs; + + // UPSCALE + if (fs.existsSync(outFile)) { + // If already upscayled, just output that file + logit("✅ Already upscayled at: ", outFile); + mainWindow.webContents.send(commands.UPSCAYL_DONE, outFile); + } else { + const upscayl = spawnUpscayl( + "realesrgan", + getSingleImageArguments( + inputDir, + fullfileName, + outFile, + isDefaultModel ? modelsPath : customModelsFolderPath ?? modelsPath, + model, + scale, + gpuId, + saveImageAs + ), + logit + ); + + childProcesses.push(upscayl); + + stopped = false; + let isAlpha = false; + let failed = false; + + const onData = (data: string) => { + logit("image upscayl: ", data.toString()); + mainWindow.setProgressBar(parseFloat(data.slice(0, data.length)) / 100); + data = data.toString(); + mainWindow.webContents.send(commands.UPSCAYL_PROGRESS, data.toString()); + if (data.includes("invalid gpu") || data.includes("failed")) { + logit("❌ INVALID GPU OR FAILED"); + failed = true; + } + if (data.includes("has alpha channel")) { + logit("📢 INCLUDES ALPHA CHANNEL, CHANGING OUTFILE NAME!"); + isAlpha = true; + } + }; + const onError = (data) => { + mainWindow.webContents.send(commands.UPSCAYL_PROGRESS, data.toString()); + failed = true; + return; + }; + const onClose = async () => { + if (!failed && !stopped) { + logit("💯 Done upscaling"); + logit("♻ Scaling and converting now..."); + const originalImage = await Jimp.read( + inputDir + slash + fullfileName + ); + try { + const newImage = await Jimp.read( + isAlpha ? outFile + ".png" : outFile + ); + try { + newImage + .scaleToFit( + originalImage.getWidth() * parseInt(payload.scale), + originalImage.getHeight() * parseInt(payload.scale) + ) + .quality(100 - quality) + .write(isAlpha ? outFile + ".png" : outFile); + mainWindow.setProgressBar(-1); + mainWindow.webContents.send( + commands.UPSCAYL_DONE, + isAlpha ? outFile + ".png" : outFile + ); + } catch (error) { + logit("❌ Error converting to PNG: ", error); + onError(error); + } + } catch (error) { + logit("❌ Error reading original image metadata", error); + onError(error); + } + } + }; + + upscayl.process.stderr.on("data", onData); + upscayl.process.on("error", onError); + upscayl.process.on("close", onClose); + } + }); +} diff --git a/electron/utils/listener/selectFile.ts b/electron/utils/listener/selectFile.ts new file mode 100644 index 0000000..46f50ed --- /dev/null +++ b/electron/utils/listener/selectFile.ts @@ -0,0 +1,59 @@ +import { MessageBoxOptions, dialog, ipcMain } from "electron"; +import commands from "../../commands"; + +export type SelectFileProps = { + mainWindow: Electron.BrowserWindow; + imagePath: string; + logit: (...args: any) => void; +}; + +export default function ({ mainWindow, imagePath, logit }) { + ipcMain.handle(commands.SELECT_FILE, async () => { + const { canceled, filePaths } = await dialog.showOpenDialog({ + properties: ["openFile", "multiSelections"], + title: "Select Image", + defaultPath: imagePath, + }); + + if (canceled) { + logit("🚫 File Operation Cancelled"); + return null; + } else { + imagePath = filePaths[0]; + + let isValid = false; + // READ SELECTED FILES + filePaths.forEach((file) => { + // log.log("Files in Folder: ", file); + if ( + file.endsWith(".png") || + file.endsWith(".jpg") || + file.endsWith(".jpeg") || + file.endsWith(".webp") || + file.endsWith(".JPG") || + file.endsWith(".PNG") || + file.endsWith(".JPEG") || + file.endsWith(".WEBP") + ) { + isValid = true; + } + }); + + if (!isValid) { + logit("❌ Invalid File Detected"); + const options: MessageBoxOptions = { + type: "error", + title: "Invalid File", + message: + "The selected file is not a valid image. Make sure you select a '.png', '.jpg', or '.webp' file.", + }; + dialog.showMessageBoxSync(mainWindow, options); + return null; + } + + logit("📄 Selected File Path: ", filePaths[0]); + // CREATE input AND upscaled FOLDER + return filePaths[0]; + } + }); +} diff --git a/electron/utils/listener/selectFolder.ts b/electron/utils/listener/selectFolder.ts new file mode 100644 index 0000000..ef7f576 --- /dev/null +++ b/electron/utils/listener/selectFolder.ts @@ -0,0 +1,25 @@ +import { dialog, ipcMain } from "electron"; +import commands from "../../commands"; + +export type SelectFolderProps = { + folderPath: string; + logit: (...args: any[]) => void; +}; + +export default function ({ folderPath, logit }) { + ipcMain.handle(commands.SELECT_FOLDER, async (event, message) => { + const { canceled, filePaths: folderPaths } = await dialog.showOpenDialog({ + properties: ["openDirectory"], + defaultPath: folderPath, + }); + + if (canceled) { + logit("🚫 Select Folder Operation Cancelled"); + return null; + } else { + folderPath = folderPaths[0]; + logit("📁 Selected Folder Path: ", folderPath); + return folderPaths[0]; + } + }); +} diff --git a/main/utils/listener/doubleUpscayl.js b/main/utils/listener/doubleUpscayl.js new file mode 100644 index 0000000..b5ab77e --- /dev/null +++ b/main/utils/listener/doubleUpscayl.js @@ -0,0 +1,139 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const electron_1 = require("electron"); +const commands_1 = __importDefault(require("../../commands")); +const path_1 = require("path"); +const upscayl_1 = require("../../upscayl"); +const getArguments_1 = require("../getArguments"); +const jimp_1 = __importDefault(require("jimp")); +function default_1(mainWindow, slash, logit, childProcesses, stopped, modelsPath, customModelsFolderPath, saveOutputFolder, outputFolderPath, quality, defaultModels) { + return electron_1.ipcMain.on(commands_1.default.DOUBLE_UPSCAYL, (event, payload) => __awaiter(this, void 0, void 0, function* () { + const model = payload.model; + let inputDir = (payload.imagePath.match(/(.*)[\/\\]/)[1] || ""); + let outputDir = payload.outputPath; + if (saveOutputFolder === true && outputFolderPath) { + outputDir = outputFolderPath; + } + const gpuId = payload.gpuId; + const saveImageAs = payload.saveImageAs; + const isDefaultModel = defaultModels.includes(model); + // COPY IMAGE TO TMP FOLDER + const fullfileName = payload.imagePath.split(slash).slice(-1)[0]; + const fileName = (0, path_1.parse)(fullfileName).name; + const outFile = outputDir + + slash + + fileName + + "_upscayl_16x_" + + model + + "." + + saveImageAs; + let scale = "4"; + if (model.includes("x2")) { + scale = "2"; + } + else if (model.includes("x3")) { + scale = "3"; + } + else { + scale = "4"; + } + // UPSCALE + let upscayl = (0, upscayl_1.spawnUpscayl)("realesrgan", (0, getArguments_1.getDoubleUpscaleArguments)(inputDir, fullfileName, outFile, isDefaultModel ? modelsPath : customModelsFolderPath !== null && customModelsFolderPath !== void 0 ? customModelsFolderPath : modelsPath, model, gpuId, saveImageAs, scale), logit); + childProcesses.push(upscayl); + stopped = false; + let failed = false; + let isAlpha = false; + let failed2 = false; + const onData = (data) => { + // CONVERT DATA TO STRING + data = data.toString(); + // SEND UPSCAYL PROGRESS TO RENDERER + mainWindow.webContents.send(commands_1.default.DOUBLE_UPSCAYL_PROGRESS, data); + // IF PROGRESS HAS ERROR, UPSCAYL FAILED + if (data.includes("invalid gpu") || data.includes("failed")) { + failed = true; + } + if (data.includes("has alpha channel")) { + isAlpha = true; + } + }; + const onError = (data) => { + data.toString(); + // SEND UPSCAYL PROGRESS TO RENDERER + mainWindow.webContents.send(commands_1.default.DOUBLE_UPSCAYL_PROGRESS, data); + // SET FAILED TO TRUE + failed = true; + return; + }; + const onData2 = (data) => { + // CONVERT DATA TO STRING + data = data.toString(); + // SEND UPSCAYL PROGRESS TO RENDERER + mainWindow.webContents.send(commands_1.default.DOUBLE_UPSCAYL_PROGRESS, data); + // IF PROGRESS HAS ERROR, UPSCAYL FAILED + if (data.includes("invalid gpu") || data.includes("failed")) { + failed2 = true; + } + }; + const onError2 = (data) => { + data.toString(); + // SEND UPSCAYL PROGRESS TO RENDERER + mainWindow.webContents.send(commands_1.default.DOUBLE_UPSCAYL_PROGRESS, data); + // SET FAILED TO TRUE + failed2 = true; + return; + }; + const onClose2 = (code) => __awaiter(this, void 0, void 0, function* () { + if (!failed2 && !stopped) { + logit("💯 Done upscaling"); + logit("♻ Scaling and converting now..."); + const originalImage = yield jimp_1.default.read(inputDir + slash + fullfileName); + try { + const newImage = yield jimp_1.default.read(isAlpha ? outFile + ".png" : outFile); + try { + newImage + .scaleToFit(originalImage.getWidth() * parseInt(payload.scale), originalImage.getHeight() * parseInt(payload.scale)) + .quality(100 - quality) + .write(isAlpha ? outFile + ".png" : outFile); + mainWindow.setProgressBar(-1); + mainWindow.webContents.send(commands_1.default.DOUBLE_UPSCAYL_DONE, isAlpha ? outFile + ".png" : outFile); + } + catch (error) { + logit("❌ Error converting to PNG: ", error); + onError(error); + } + } + catch (error) { + logit("❌ Error reading original image metadata", error); + onError(error); + } + } + }); + upscayl.process.stderr.on("data", onData); + upscayl.process.on("error", onError); + upscayl.process.on("close", (code) => { + // IF NOT FAILED + if (!failed && !stopped) { + // UPSCALE + let upscayl2 = (0, upscayl_1.spawnUpscayl)("realesrgan", (0, getArguments_1.getDoubleUpscaleSecondPassArguments)(isAlpha, outFile, isDefaultModel ? modelsPath : customModelsFolderPath !== null && customModelsFolderPath !== void 0 ? customModelsFolderPath : modelsPath, model, gpuId, saveImageAs, scale), logit); + childProcesses.push(upscayl2); + upscayl2.process.stderr.on("data", onData2); + upscayl2.process.on("error", onError2); + upscayl2.process.on("close", onClose2); + } + }); + })); +} +exports.default = default_1; diff --git a/main/utils/listener/folderUpscayl.js b/main/utils/listener/folderUpscayl.js new file mode 100644 index 0000000..be8702d --- /dev/null +++ b/main/utils/listener/folderUpscayl.js @@ -0,0 +1,73 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const electron_1 = require("electron"); +const commands_1 = __importDefault(require("../../commands")); +const upscayl_1 = require("../../upscayl"); +const getArguments_1 = require("../getArguments"); +const fs_1 = __importDefault(require("fs")); +function default_1(mainWindow, logit, childProcesses, stopped, modelsPath, customModelsFolderPath, saveOutputFolder, outputFolderPath, defaultModels) { + //------------------------Upscayl Folder-----------------------------// + electron_1.ipcMain.on(commands_1.default.FOLDER_UPSCAYL, (event, payload) => __awaiter(this, void 0, void 0, function* () { + // GET THE MODEL + const model = payload.model; + const gpuId = payload.gpuId; + const saveImageAs = payload.saveImageAs; + const scale = payload.scale; + // GET THE IMAGE DIRECTORY + let inputDir = payload.batchFolderPath; + // GET THE OUTPUT DIRECTORY + let outputDir = payload.outputPath; + if (saveOutputFolder === true && outputFolderPath) { + outputDir = outputFolderPath; + } + if (!fs_1.default.existsSync(outputDir)) { + fs_1.default.mkdirSync(outputDir, { recursive: true }); + } + const isDefaultModel = defaultModels.includes(model); + // UPSCALE + const upscayl = (0, upscayl_1.spawnUpscayl)("realesrgan", (0, getArguments_1.getBatchArguments)(inputDir, outputDir, isDefaultModel ? modelsPath : customModelsFolderPath !== null && customModelsFolderPath !== void 0 ? customModelsFolderPath : modelsPath, model, gpuId, saveImageAs, scale), logit); + childProcesses.push(upscayl); + stopped = false; + let failed = false; + const onData = (data) => { + data = data.toString(); + mainWindow.webContents.send(commands_1.default.FOLDER_UPSCAYL_PROGRESS, data.toString()); + if (data.includes("invalid gpu") || data.includes("failed")) { + logit("❌ INVALID GPU OR INVALID FILES IN FOLDER - FAILED"); + failed = true; + upscayl.kill(); + } + }; + const onError = (data) => { + mainWindow.webContents.send(commands_1.default.FOLDER_UPSCAYL_PROGRESS, data.toString()); + failed = true; + upscayl.kill(); + return; + }; + const onClose = () => { + if (!failed && !stopped) { + logit("💯 Done upscaling"); + mainWindow.webContents.send(commands_1.default.FOLDER_UPSCAYL_DONE, outputDir); + } + else { + upscayl.kill(); + } + }; + upscayl.process.stderr.on("data", onData); + upscayl.process.on("error", onError); + upscayl.process.on("close", onClose); + })); +} +exports.default = default_1;