1
0
mirror of https://github.com/upscayl/upscayl.git synced 2024-11-30 18:24:27 +01:00

Split modules

This commit is contained in:
Nayam Amarshe 2023-09-10 14:44:04 +05:30
parent 62dd93b921
commit 80ed1516aa
24 changed files with 1063 additions and 876 deletions

View File

@ -5,7 +5,7 @@
*/
import { join, dirname, resolve } from "path";
import { getPlatform } from "./getDeviceSpecs";
import { getPlatform } from "./get-device-specs";
import isDev from "electron-is-dev";
import { app } from "electron";

View File

@ -0,0 +1,51 @@
import { MessageBoxOptions, dialog } from "electron";
import mainWindow from "../main-window";
import {
getCustomModelsFolderPath,
setCustomModelsFolderPath,
} from "../utils/config-variables";
import logit from "../utils/logit";
import slash from "../utils/slash";
import COMMAND from "../constants/commands";
import getModels from "../utils/get-models";
const customModelsSelect = async (event, message) => {
if (!mainWindow) return;
const { canceled, filePaths: folderPaths } = await dialog.showOpenDialog({
properties: ["openDirectory"],
title: "Select Custom Models Folder",
defaultPath: getCustomModelsFolderPath(),
});
if (canceled) {
logit("🚫 Select Custom Models Folder Operation Cancelled");
return null;
} else {
setCustomModelsFolderPath(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(
COMMAND.CUSTOM_MODEL_FILES_LIST,
getModels(getCustomModelsFolderPath())
);
logit("📁 Custom Folder Path: ", getCustomModelsFolderPath());
return getCustomModelsFolderPath();
}
};
export default customModelsSelect;

View File

@ -0,0 +1,24 @@
import COMMAND from "../constants/commands";
import mainWindow from "../main-window";
import {
getCustomModelsFolderPath,
setCustomModelsFolderPath,
} from "../utils/config-variables";
import getModels from "../utils/get-models";
import logit from "../utils/logit";
const getModelsList = async (event, payload) => {
if (!mainWindow) return;
if (payload) {
setCustomModelsFolderPath(payload);
logit("📁 Custom Models Folder Path: ", getCustomModelsFolderPath());
mainWindow.webContents.send(
COMMAND.CUSTOM_MODEL_FILES_LIST,
getModels(payload)
);
}
};
export default getModelsList;

View File

@ -0,0 +1,179 @@
import fs from "fs";
import { modelsPath } from "../binaries";
import COMMAND from "../constants/commands";
import mainWindow from "../main-window";
import {
getCustomModelsFolderPath,
getFolderPath,
getOutputFolderPath,
getOverwrite,
getSaveOutputFolder,
getStop,
setChildProcesses,
setOverwrite,
setStop,
} from "../utils/config-variables";
import convertAndScale from "../utils/convert-and-scale";
import { getSingleImageArguments } from "../utils/get-arguments";
import logit from "../utils/logit";
import slash from "../utils/slash";
import { spawnUpscayl } from "../utils/spawn-upscayl";
import { parse } from "path";
import DEFAULT_MODELS from "../constants/models";
const imageUpscayl = async (event, payload) => {
if (!mainWindow) return;
setOverwrite(payload.overwrite);
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: string | undefined =
getFolderPath() || (payload.outputPath as string);
if (getSaveOutputFolder() === true && getOutputFolderPath()) {
outputDir = getOutputFolderPath();
}
const isDefaultModel = DEFAULT_MODELS.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;
// GET OVERWRITE SETTINGS FROM LOCAL STORAGE
mainWindow.webContents
.executeJavaScript('localStorage.getItem("overwrite");', true)
.then((lastSavedOverwrite: boolean | null) => {
if (lastSavedOverwrite !== null) {
console.log("Overwrite: ", lastSavedOverwrite);
setOverwrite(lastSavedOverwrite);
}
});
// UPSCALE
if (fs.existsSync(outFile) && getOverwrite() === false) {
// If already upscayled, just output that file
logit("✅ Already upscayled at: ", outFile);
mainWindow.webContents.send(
COMMAND.UPSCAYL_DONE,
outFile.replace(
/([^/\\]+)$/i,
encodeURIComponent(outFile.match(/[^/\\]+$/i)![0])
)
);
} else {
const upscayl = spawnUpscayl(
"realesrgan",
getSingleImageArguments(
inputDir,
fullfileName,
outFile,
isDefaultModel ? modelsPath : getCustomModelsFolderPath() ?? modelsPath,
model,
scale,
gpuId,
"png"
),
logit
);
setChildProcesses(upscayl);
setStop(false);
let isAlpha = false;
let failed = false;
const onData = (data: string) => {
if (!mainWindow) return;
logit("image upscayl: ", data.toString());
mainWindow.setProgressBar(parseFloat(data.slice(0, data.length)) / 100);
data = data.toString();
mainWindow.webContents.send(COMMAND.UPSCAYL_PROGRESS, data.toString());
if (data.includes("invalid gpu") || data.includes("failed")) {
logit("❌ INVALID GPU OR FAILED");
upscayl.kill();
failed = true;
}
if (data.includes("has alpha channel")) {
logit("📢 INCLUDES ALPHA CHANNEL, CHANGING OUTFILE NAME!");
isAlpha = true;
}
};
const onError = (data) => {
if (!mainWindow) return;
mainWindow.setProgressBar(-1);
mainWindow.webContents.send(COMMAND.UPSCAYL_PROGRESS, data.toString());
failed = true;
upscayl.kill();
return;
};
const onClose = async () => {
if (!failed && !getStop()) {
logit("💯 Done upscaling");
logit("♻ Scaling and converting now...");
mainWindow &&
mainWindow.webContents.send(COMMAND.SCALING_AND_CONVERTING);
// Free up memory
upscayl.kill();
try {
if (!mainWindow) return;
await convertAndScale(
inputDir + slash + fullfileName,
isAlpha ? outFile + ".png" : outFile,
outFile,
payload.scale,
saveImageAs,
onError
);
mainWindow.setProgressBar(-1);
mainWindow.webContents.send(
COMMAND.UPSCAYL_DONE,
outFile.replace(
/([^/\\]+)$/i,
encodeURIComponent(outFile.match(/[^/\\]+$/i)![0])
)
);
} catch (error) {
logit(
"❌ Error processing (scaling and converting) the image. Please report this error on GitHub.",
error
);
upscayl.kill();
mainWindow &&
mainWindow.webContents.send(
COMMAND.UPSCAYL_ERROR,
"Error processing (scaling and converting) the image. Please report this error on Upscayl GitHub Issues page."
);
}
}
};
upscayl.process.stderr.on("data", onData);
upscayl.process.on("error", onError);
upscayl.process.on("close", onClose);
}
};
export default imageUpscayl;

View File

@ -0,0 +1,9 @@
import { shell } from "electron";
import logit from "../utils/logit";
const openFolder = async (event, payload) => {
logit("📂 Opening Folder: ", payload);
shell.openPath(payload);
};
export default openFolder;

View File

@ -0,0 +1,56 @@
import { MessageBoxOptions, dialog } from "electron";
import mainWindow from "../main-window";
import { getImagePath, setImagePath } from "../utils/config-variables";
import logit from "../utils/logit";
const selectFile = async () => {
if (!mainWindow) return;
const { canceled, filePaths } = await dialog.showOpenDialog({
properties: ["openFile", "multiSelections"],
title: "Select Image",
defaultPath: getImagePath(),
});
if (canceled) {
logit("🚫 File Operation Cancelled");
return null;
} else {
setImagePath(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];
}
};
export default selectFile;

View File

@ -0,0 +1,21 @@
import { dialog } from "electron";
import { getFolderPath, setFolderPath } from "../utils/config-variables";
import logit from "../utils/logit";
const selectFolder = async (event, message) => {
const { canceled, filePaths: folderPaths } = await dialog.showOpenDialog({
properties: ["openDirectory"],
defaultPath: getFolderPath(),
});
if (canceled) {
logit("🚫 Select Folder Operation Cancelled");
return null;
} else {
setFolderPath(folderPaths[0]);
logit("📁 Selected Folder Path: ", getFolderPath());
return folderPaths[0];
}
};
export default selectFolder;

14
electron/commands/stop.ts Normal file
View File

@ -0,0 +1,14 @@
import mainWindow from "../main-window";
import { getChildProcesses, setStop } from "../utils/config-variables";
import logit from "../utils/logit";
const stop = async (event, payload) => {
setStop(true);
mainWindow && mainWindow.setProgressBar(-1);
getChildProcesses().forEach((child) => {
logit("🛑 Stopping Upscaling Process", child.process.pid);
child.kill();
});
};
export default stop;

View File

@ -1,4 +1,4 @@
const commands = {
const COMMAND = {
SELECT_FILE: "Select a File",
SELECT_FOLDER: "Select a Folder",
UPSCAYL: "Upscale the Image",
@ -27,4 +27,4 @@ const commands = {
UPSCAYL_ERROR: "Upscaling Error",
};
export default commands;
export default COMMAND;

View File

@ -0,0 +1,9 @@
const DEFAULT_MODELS = [
"realesrgan-x4plus",
"remacri",
"ultramix_balanced",
"ultrasharp",
"realesrgan-x4plus-anime",
];
export default DEFAULT_MODELS;

File diff suppressed because it is too large Load Diff

144
electron/main-window.ts Normal file
View File

@ -0,0 +1,144 @@
import prepareNext from "electron-next";
import { BrowserWindow, app, net, protocol, shell } from "electron";
import COMMAND from "./constants/commands";
import { getPlatform } from "./get-device-specs";
import { join } from "path";
import { execPath, modelsPath } from "./binaries";
import log from "electron-log";
import isDev from "electron-is-dev";
import { autoUpdater } from "electron-updater";
import {
setCustomModelsFolderPath,
setFolderPath,
setImagePath,
setOutputFolderPath,
setQuality,
setSaveOutputFolder,
} from "./utils/config-variables";
// Prepare the renderer once the app is ready
let _mainWindow: BrowserWindow | null = null;
const getMainWindow = () => {
if (!_mainWindow) {
_mainWindow = new BrowserWindow({
icon: join(__dirname, "build", "icon.png"),
width: 1300,
height: 940,
minHeight: 500,
minWidth: 500,
show: false,
backgroundColor: "#171717",
webPreferences: {
nodeIntegration: true,
nodeIntegrationInWorker: true,
webSecurity: false,
preload: join(__dirname, "preload.js"),
},
titleBarStyle: getPlatform() === "mac" ? "hiddenInset" : "default",
});
}
return _mainWindow;
};
const mainWindow = getMainWindow();
app.on("ready", async () => {
await prepareNext("./renderer");
log.info("🚀 UPSCAYL EXEC PATH: ", execPath("realesrgan"));
log.info("🚀 MODELS PATH: ", modelsPath);
const url = isDev
? "http://localhost:8000"
: (new URL("file:///").pathname = join(
__dirname,
"../renderer/out/index.html"
)).toString();
mainWindow.setMenuBarVisibility(false);
mainWindow.loadURL(url);
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: "deny" };
});
mainWindow.once("ready-to-show", () => {
if (!mainWindow) return;
mainWindow.show();
mainWindow.webContents.setZoomFactor(1);
});
app.whenReady().then(() => {
protocol.handle("file", (request) => {
const pathname = decodeURI(request.url.replace("file:///", ""));
return net.fetch(pathname);
});
});
if (!isDev) {
autoUpdater.checkForUpdates();
}
// <------------------------Save Last Paths----------------------------->
// GET LAST IMAGE PATH TO LOCAL STORAGE
mainWindow.webContents
.executeJavaScript('localStorage.getItem("lastImagePath");', true)
.then((lastImagePath: string | null) => {
if (lastImagePath && lastImagePath.length > 0) {
setImagePath(lastImagePath);
}
});
// GET LAST FOLDER PATH TO LOCAL STORAGE
mainWindow.webContents
.executeJavaScript('localStorage.getItem("lastFolderPath");', true)
.then((lastFolderPath: string | null) => {
if (lastFolderPath && lastFolderPath.length > 0) {
setFolderPath(lastFolderPath);
}
});
// GET LAST CUSTOM MODELS FOLDER PATH TO LOCAL STORAGE
mainWindow.webContents
.executeJavaScript(
'localStorage.getItem("lastCustomModelsFolderPath");',
true
)
.then((lastCustomModelsFolderPath: string | null) => {
if (lastCustomModelsFolderPath && lastCustomModelsFolderPath.length > 0) {
setCustomModelsFolderPath(lastCustomModelsFolderPath);
}
});
// GET LAST CUSTOM MODELS FOLDER PATH TO LOCAL STORAGE
mainWindow.webContents
.executeJavaScript('localStorage.getItem("lastOutputFolderPath");', true)
.then((lastOutputFolderPath: string | null) => {
if (lastOutputFolderPath && lastOutputFolderPath.length > 0) {
setOutputFolderPath(lastOutputFolderPath);
}
});
// GET LAST SAVE OUTPUT FOLDER (BOOLEAN) TO LOCAL STORAGE
mainWindow.webContents
.executeJavaScript('localStorage.getItem("rememberOutputFolder");', true)
.then((lastSaveOutputFolder: boolean | null) => {
if (lastSaveOutputFolder !== null) {
setSaveOutputFolder(lastSaveOutputFolder);
}
});
// GET IMAGE QUALITY (NUMBER) TO LOCAL STORAGE
mainWindow.webContents
.executeJavaScript('localStorage.getItem("quality");', true)
.then((lastSavedQuality: string | null) => {
if (lastSavedQuality !== null) {
if (parseInt(lastSavedQuality) === 100) {
setQuality(99);
} else {
setQuality(parseInt(lastSavedQuality));
}
}
});
mainWindow.webContents.send(COMMAND.OS, getPlatform());
});
export default mainWindow;

View File

@ -1,5 +1,5 @@
import { ipcRenderer, contextBridge } from "electron";
import { getPlatform } from "./getDeviceSpecs";
import { getPlatform } from "./get-device-specs";
// 'ipcRenderer' will be available in index.js with the method 'window.electron'
contextBridge.exposeInMainWorld("electron", {

View File

@ -1,23 +0,0 @@
import { spawn } from "child_process";
import { execPath } from "./binaries";
function upscaylImage(
inputFile: string,
outFile: string,
modelsPath: string,
model: string
) {
// UPSCALE
let upscayl = spawn(
execPath("realesrgan"),
["-i", inputFile, "-o", outFile, "-s", "4", "-m", modelsPath, "-n", model],
{
cwd: undefined,
detached: false,
}
);
return upscayl;
}
module.exports = { upscaylImage };

View File

@ -0,0 +1,94 @@
import { ChildProcessWithoutNullStreams } from "child_process";
let _imagePath: string | undefined = undefined;
let _folderPath: string | undefined = undefined;
let _customModelsFolderPath: string | undefined = undefined;
let _outputFolderPath: string | undefined = undefined;
let _saveOutputFolder = false;
let _quality = 0;
let _overwrite = false;
let _stop = false;
let childProcesses: {
process: ChildProcessWithoutNullStreams;
kill: () => boolean;
}[] = [];
// GETTERS
export function getImagePath(): string | undefined {
return _imagePath;
}
export function setImagePath(value: string | undefined): void {
_imagePath = value;
}
export function getFolderPath(): string | undefined {
return _folderPath;
}
export function setFolderPath(value: string | undefined): void {
_folderPath = value;
}
export function getCustomModelsFolderPath(): string | undefined {
return _customModelsFolderPath;
}
export function setCustomModelsFolderPath(value: string | undefined): void {
_customModelsFolderPath = value;
}
export function getOutputFolderPath(): string | undefined {
return _outputFolderPath;
}
export function getStop(): boolean {
return _stop;
}
export function getChildProcesses(): {
process: ChildProcessWithoutNullStreams;
kill: () => boolean;
}[] {
return childProcesses;
}
// SETTERS
export function setOutputFolderPath(value: string | undefined): void {
_outputFolderPath = value;
}
export function getSaveOutputFolder(): boolean {
return _saveOutputFolder;
}
export function setSaveOutputFolder(value: boolean): void {
_saveOutputFolder = value;
}
export function getQuality(): number {
return _quality;
}
export function setQuality(value: number): void {
_quality = value;
}
export function getOverwrite(): boolean {
return _overwrite;
}
export function setOverwrite(value: boolean): void {
_overwrite = value;
}
export function setStop(value: boolean): void {
_stop = value;
}
export function setChildProcesses(value: {
process: ChildProcessWithoutNullStreams;
kill: () => boolean;
}): void {
childProcesses.push(value);
}

View File

@ -0,0 +1,45 @@
import sharp from "sharp";
import mainWindow from "../main-window";
import { getQuality } from "./config-variables";
import logit from "./logit";
const convertAndScale = async (
originalImagePath: string,
upscaledImagePath: string,
processedImagePath: string,
scale: string,
saveImageAs: string,
onError: (error: any) => void
) => {
const originalImage = await sharp(originalImagePath).metadata();
if (!mainWindow || !originalImage) {
throw new Error("Could not grab the original image!");
}
// Resize the image to the scale
const newImage = sharp(upscaledImagePath)
.resize(
originalImage.width && originalImage.width * parseInt(scale),
originalImage.height && originalImage.height * parseInt(scale)
)
.withMetadata(); // Keep metadata
// Change the output according to the saveImageAs
if (saveImageAs === "png") {
newImage.png({ quality: 100 - getQuality() });
} else if (saveImageAs === "jpg") {
console.log("Quality: ", getQuality());
newImage.jpeg({ quality: 100 - getQuality() });
}
// Save the image
const buffer = await newImage.toBuffer();
sharp(buffer)
.toFile(processedImagePath)
.then(() => {
logit("✅ Done converting to: ", upscaledImagePath);
})
.catch((error) => {
logit("❌ Error converting to: ", saveImageAs, error);
onError(error);
});
};
export default convertAndScale;

View File

@ -1,4 +1,4 @@
import { getPlatform } from "../getDeviceSpecs";
import { getPlatform } from "../get-device-specs";
const slash: string = getPlatform() === "win" ? "\\" : "/";
export const getSingleImageArguments = (

View File

@ -0,0 +1,56 @@
import fs from "fs";
import logit from "./logit";
import { MessageBoxOptions, dialog } from "electron";
const getModels = (folderPath: string | undefined) => {
let models: string[] = [];
let isValid = false;
if (!folderPath) {
logit("❌ Invalid Custom Model Folder Detected");
const options: MessageBoxOptions = {
type: "error",
title: "Invalid Folder",
message:
"The selected folder does not contain valid model files. Make sure you select the folder that ONLY contains '.param' and '.bin' files.",
buttons: ["OK"],
};
dialog.showMessageBoxSync(options);
return null;
}
// READ CUSTOM MODELS FOLDER
fs.readdirSync(folderPath).forEach((file) => {
// log.log("Files in Folder: ", file);
if (
file.endsWith(".param") ||
file.endsWith(".PARAM") ||
file.endsWith(".bin") ||
file.endsWith(".BIN")
) {
isValid = true;
const modelName = file.substring(0, file.lastIndexOf(".")) || file;
if (!models.includes(modelName)) {
models.push(modelName);
}
}
});
if (!isValid) {
logit("❌ Invalid Custom Model Folder Detected");
const options: MessageBoxOptions = {
type: "error",
title: "Invalid Folder",
message:
"The selected folder does not contain valid model files. Make sure you select the folder that ONLY contains '.param' and '.bin' files.",
buttons: ["OK"],
};
dialog.showMessageBoxSync(options);
return null;
}
logit("🔎 Detected Custom Models: ", models);
return models;
};
export default getModels;

11
electron/utils/logit.ts Normal file
View File

@ -0,0 +1,11 @@
import Logger from "electron-log";
import mainWindow from "../main-window";
import COMMAND from "../constants/commands";
const logit = (...args: any) => {
Logger.log(...args);
if (!mainWindow) return;
mainWindow.webContents.send(COMMAND.LOG, args.join(" "));
};
export default logit;

4
electron/utils/slash.ts Normal file
View File

@ -0,0 +1,4 @@
import { getPlatform } from "../get-device-specs";
const slash: string = getPlatform() === "win" ? "\\" : "/";
export default slash;

View File

@ -1,5 +1,5 @@
import { spawn } from "child_process";
import { execPath } from "./binaries";
import { execPath } from "../binaries";
export const spawnUpscayl = (
binaryName: string,

View File

@ -1,5 +1,5 @@
import React from "react";
import commands from "../../../electron/commands";
import commands from "../../../electron/constants/commands";
type CustomModelsFolderSelectProps = {
customModelsPath: string;

View File

@ -1,6 +1,6 @@
"use client";
import { useState, useEffect, useCallback } from "react";
import commands from "../../electron/commands";
import commands from "../../electron/constants/commands";
import { ReactCompareSlider } from "react-compare-slider";
import Header from "../components/Header";
import Footer from "../components/Footer";