diff --git a/common/decode-path.ts b/common/decode-path.ts new file mode 100644 index 0000000..de29c8e --- /dev/null +++ b/common/decode-path.ts @@ -0,0 +1,5 @@ +import path from "path"; + +export default function decodePath(filePath: string): string { + return path.normalize(decodeURIComponent(filePath)); +} diff --git a/common/get-directory-from-path.ts b/common/get-directory-from-path.ts new file mode 100644 index 0000000..bc27318 --- /dev/null +++ b/common/get-directory-from-path.ts @@ -0,0 +1,15 @@ +export default function getDirectoryFromPath(filePath: string): string { + // Define the path separator based on the operating system + const separator = filePath.includes("/") ? "/" : "\\"; + + // Split the file path by the path separator + const pathParts = filePath.split(separator); + + // Remove the last element to get the directory + pathParts.pop(); + + // Join the remaining parts back together to form the directory path + const directoryPath = pathParts.join(separator); + + return directoryPath || ""; +} diff --git a/common/get-file-name.ts b/common/get-file-name.ts new file mode 100644 index 0000000..4a826f5 --- /dev/null +++ b/common/get-file-name.ts @@ -0,0 +1,8 @@ +export default function getFilenameFromPath( + path: string, + withExtension: boolean = true, +) { + if (!path) return ""; + if (withExtension) return path.split("/").slice(-1)[0]; + return path.split("/").slice(-1)[0].split(".").slice(0, -1).join("."); +} diff --git a/common/sanitize-path.ts b/common/sanitize-path.ts new file mode 100644 index 0000000..73cf1f1 --- /dev/null +++ b/common/sanitize-path.ts @@ -0,0 +1,24 @@ +export function sanitizePath(filePath: string) { + // const protocolPrefix = "file://"; + + // Normalize the file path to use forward slashes (for Windows) + const normalizedFilePath = filePath.replace(/\\/g, "/"); + + // Split the file path into segments based on forward slashes + const pathSegments = normalizedFilePath.split("/"); + + // Encode each segment separately using encodeURIComponent + const encodedPathSegments = pathSegments.map((segment) => + encodeURIComponent(segment), + ); + + // Join the encoded segments back together with forward slashes + const encodedFilePath = encodedPathSegments.join("/"); + + // Combine the protocol prefix with the encoded file path to create the final file URL + const fileUrl = encodedFilePath; + console.log("🚀 => fileUrl:", fileUrl); + + // Return the final Electron file URL + return fileUrl; +} diff --git a/common/types/types.d.ts b/common/types/types.d.ts index 464d0d9..cf4c3eb 100644 --- a/common/types/types.d.ts +++ b/common/types/types.d.ts @@ -1,10 +1,12 @@ +import { ImageFormat } from "@electron/types/types"; + export type ImageUpscaylPayload = { imagePath: string; - outputPath?: string; + outputPath: string; scale: string; model: string; gpuId: string; - saveImageAs: string; + saveImageAs: ImageFormat; overwrite: boolean; compression: string; noImageProcessing: boolean; @@ -14,11 +16,14 @@ export type ImageUpscaylPayload = { export type DoubleUpscaylPayload = { model: string; + /** + * The path to the image to upscale. + */ imagePath: string; outputPath: string; scale: string; gpuId: string; - saveImageAs: string; + saveImageAs: ImageFormat; compression: string; noImageProcessing: boolean; customWidth: string; @@ -30,7 +35,7 @@ export type BatchUpscaylPayload = { outputPath: string; model: string; gpuId: string; - saveImageAs: string; + saveImageAs: ImageFormat; scale: string; compression: string; noImageProcessing: boolean; diff --git a/electron/commands/batch-upscayl.ts b/electron/commands/batch-upscayl.ts index 4d65342..9384ac6 100644 --- a/electron/commands/batch-upscayl.ts +++ b/electron/commands/batch-upscayl.ts @@ -3,9 +3,6 @@ import { getMainWindow } from "../main-window"; import { childProcesses, savedCustomModelsPath, - rememberOutputFolder, - setCompression, - setNoImageProcessing, setStopped, stopped, } from "../utils/config-variables"; @@ -16,55 +13,33 @@ import slash from "../utils/slash"; import { modelsPath } from "../utils/get-resource-paths"; import COMMAND from "../../common/commands"; import { BatchUpscaylPayload } from "../../common/types/types"; -import { ImageFormat } from "../types/types"; import showNotification from "../utils/show-notification"; import { DEFAULT_MODELS } from "../../common/models-list"; const batchUpscayl = async (event, payload: BatchUpscaylPayload) => { const mainWindow = getMainWindow(); if (!mainWindow) return; - // GET THE MODEL - const model = payload.model; - const gpuId = payload.gpuId; - const saveImageAs = payload.saveImageAs as ImageFormat; - console.log("PAYLOAD: ", payload); - // GET THE IMAGE DIRECTORY - let inputDir = payload.batchFolderPath; - // GET THE OUTPUT DIRECTORY - let outputFolderPath = payload.outputPath; - if (rememberOutputFolder === true && outputFolderPath) { - outputFolderPath = outputFolderPath; - } - // ! Don't do fetchLocalStorage() again, it causes the values to be reset - setNoImageProcessing(payload.noImageProcessing); - setCompression(parseInt(payload.compression)); - - const isDefaultModel = DEFAULT_MODELS.includes(model); const scale = payload.scale; const useCustomWidth = payload.useCustomWidth; const customWidth = useCustomWidth ? payload.customWidth : ""; - + const model = payload.model; + const gpuId = payload.gpuId; + const saveImageAs = payload.saveImageAs; + // GET THE IMAGE DIRECTORY + let inputDir = decodeURIComponent(payload.batchFolderPath); + // GET THE OUTPUT DIRECTORY + let outputFolderPath = decodeURIComponent(payload.outputPath); const outputFolderName = `upscayl_${saveImageAs}_${model}_${ useCustomWidth ? `${customWidth}px` : `${scale}x` }`; - outputFolderPath += slash + outputFolderName; + // CREATE THE OUTPUT DIRECTORY if (!fs.existsSync(outputFolderPath)) { fs.mkdirSync(outputFolderPath, { recursive: true }); } - // Delete .DS_Store files - fs.readdirSync(inputDir).forEach((file) => { - if ( - file === ".DS_Store" || - file.toLowerCase() === "desktop.ini" || - file.startsWith(".") - ) { - logit("🗑️ Deleting .DS_Store file"); - fs.unlinkSync(inputDir + slash + file); - } - }); + const isDefaultModel = DEFAULT_MODELS.includes(model); // UPSCALE const upscayl = spawnUpscayl( diff --git a/electron/commands/double-upscayl.ts b/electron/commands/double-upscayl.ts index 0696895..f196642 100644 --- a/electron/commands/double-upscayl.ts +++ b/electron/commands/double-upscayl.ts @@ -1,12 +1,8 @@ -import path, { parse } from "path"; +import { parse } from "path"; import { getMainWindow } from "../main-window"; import { childProcesses, savedCustomModelsPath, - savedOutputPath, - rememberOutputFolder, - setCompression, - setNoImageProcessing, setStopped, stopped, } from "../utils/config-variables"; @@ -23,38 +19,31 @@ import { DoubleUpscaylPayload } from "../../common/types/types"; import { ImageFormat } from "../types/types"; import showNotification from "../utils/show-notification"; import { DEFAULT_MODELS } from "../../common/models-list"; -import getModelScale from "../../common/check-model-scale"; +import getFilenameFromPath from "../../common/get-file-name"; +import decodePath from "../../common/decode-path"; +import getDirectoryFromPath from "../../common/get-directory-from-path"; const doubleUpscayl = async (event, payload: DoubleUpscaylPayload) => { const mainWindow = getMainWindow(); if (!mainWindow) return; - const model = payload.model as string; - const imagePath = payload.imagePath; - let inputDir = (imagePath.match(/(.*)[\/\\]/) || [""])[1]; - let outputDir = path.normalize(payload.outputPath); - - if (rememberOutputFolder === true && savedOutputPath) { - outputDir = savedOutputPath; - } + const compression = payload.compression; + const scale = parseInt(payload.scale) ** 2; + const useCustomWidth = payload.useCustomWidth; + const customWidth = useCustomWidth ? payload.customWidth : ""; + const model = payload.model; const gpuId = payload.gpuId as string; const saveImageAs = payload.saveImageAs as ImageFormat; - - setNoImageProcessing(payload.noImageProcessing); - setCompression(parseInt(payload.compression)); + const imagePath = decodePath(payload.imagePath); + let inputDir = getDirectoryFromPath(imagePath); + let outputDir = decodePath(payload.outputPath); + const fullfileName = getFilenameFromPath(imagePath); + const fileName = parse(fullfileName).name; const isDefaultModel = DEFAULT_MODELS.includes(model); // COPY IMAGE TO TMP FOLDER - const fullfileName = imagePath.split(slash).slice(-1)[0] as string; - const fileName = parse(fullfileName).name; - - const modelScale = getModelScale(model); - const scale = parseInt(payload.scale) ** 2; - const useCustomWidth = payload.useCustomWidth; - const customWidth = useCustomWidth ? payload.customWidth : ""; - const outFile = outputDir + slash + @@ -69,7 +58,7 @@ const doubleUpscayl = async (event, payload: DoubleUpscaylPayload) => { let upscayl = spawnUpscayl( getDoubleUpscaleArguments({ inputDir, - fullfileName, + fullfileName: decodeURIComponent(fullfileName), outFile, modelsPath: isDefaultModel ? modelsPath @@ -128,13 +117,7 @@ const doubleUpscayl = async (event, payload: DoubleUpscaylPayload) => { logit("💯 Done upscaling"); mainWindow.setProgressBar(-1); - mainWindow.webContents.send( - COMMAND.DOUBLE_UPSCAYL_DONE, - outFile.replace( - /([^/\\]+)$/i, - encodeURIComponent(outFile.match(/[^/\\]+$/i)![0]), - ), - ); + mainWindow.webContents.send(COMMAND.DOUBLE_UPSCAYL_DONE, outFile); showNotification("Upscayled", "Image upscayled successfully!"); } }; @@ -188,6 +171,7 @@ const doubleUpscayl = async (event, payload: DoubleUpscaylPayload) => { saveImageAs, scale: scale.toString(), customWidth, + compression, }), logit, ); diff --git a/electron/commands/image-upscayl.ts b/electron/commands/image-upscayl.ts index ea7e3e2..9373877 100644 --- a/electron/commands/image-upscayl.ts +++ b/electron/commands/image-upscayl.ts @@ -2,14 +2,8 @@ import fs from "fs"; import { modelsPath } from "../utils/get-resource-paths"; import COMMAND from "../../common/commands"; import { - savedCompression, savedCustomModelsPath, - savedBatchUpscaylFolderPath, - savedOutputPath, - rememberOutputFolder, setChildProcesses, - setCompression, - setNoImageProcessing, setStopped, stopped, } from "../utils/config-variables"; @@ -23,6 +17,9 @@ import { ImageUpscaylPayload } from "../../common/types/types"; import { ImageFormat } from "../types/types"; import showNotification from "../utils/show-notification"; import { DEFAULT_MODELS } from "../../common/models-list"; +import getFilenameFromPath from "../../common/get-file-name"; +import decodePath from "../../common/decode-path"; +import getDirectoryFromPath from "../../common/get-directory-from-path"; const imageUpscayl = async (event, payload: ImageUpscaylPayload) => { const mainWindow = getMainWindow(); @@ -32,34 +29,20 @@ const imageUpscayl = async (event, payload: ImageUpscaylPayload) => { return; } - setNoImageProcessing(payload.noImageProcessing); - setCompression(parseInt(payload.compression)); - // GET VARIABLES + const compression = payload.compression; + const scale = payload.scale; + const useCustomWidth = payload.useCustomWidth; + const customWidth = useCustomWidth ? payload.customWidth : ""; const model = payload.model as string; const gpuId = payload.gpuId as string; const saveImageAs = payload.saveImageAs as ImageFormat; const overwrite = payload.overwrite as boolean; - let inputDir = (payload.imagePath.match(/(.*)[\/\\]/)?.[1] || "") as string; - let outputDir: string | undefined = - savedBatchUpscaylFolderPath || (payload.outputPath as string); - if ( - rememberOutputFolder === true && - savedOutputPath && - savedOutputPath?.length > 0 - ) { - logit("🧠 Using saved output path"); - outputDir = savedOutputPath; - } - const isDefaultModel = DEFAULT_MODELS.includes(model); - logit("Is Default Model? : ", isDefaultModel); - const fullfileName = payload.imagePath.replace(/^.*[\\\/]/, "") as string; - const fileName = parse(fullfileName).name; - const fileExt = parse(fullfileName).ext; - const useCustomWidth = payload.useCustomWidth; - const customWidth = useCustomWidth ? payload.customWidth : ""; - - const scale = payload.scale; + const imagePath = decodePath(payload.imagePath); + let inputDir = getDirectoryFromPath(imagePath); + let outputDir = decodePath(payload.outputPath); + const fileNameWithExt = getFilenameFromPath(imagePath); + const fileName = parse(fileNameWithExt).name; const outFile = outputDir + @@ -71,17 +54,13 @@ const imageUpscayl = async (event, payload: ImageUpscaylPayload) => { "." + saveImageAs; + const isDefaultModel = DEFAULT_MODELS.includes(model); + // UPSCALE if (fs.existsSync(outFile) && !overwrite) { // 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]), - ), - ); + mainWindow.webContents.send(COMMAND.UPSCAYL_DONE, outFile); } else { logit( "✅ Upscayl Variables: ", @@ -90,18 +69,18 @@ const imageUpscayl = async (event, payload: ImageUpscaylPayload) => { gpuId, saveImageAs, inputDir, + fileNameWithExt, outputDir, - fullfileName, + outFile, fileName, scale, - outFile, - compression: savedCompression, + compression, }), ); const upscayl = spawnUpscayl( getSingleImageArguments({ - inputDir, - fullfileName, + inputDir: decodeURIComponent(inputDir), + fileNameWithExt: decodeURIComponent(fileNameWithExt), outFile, modelsPath: isDefaultModel ? modelsPath @@ -146,13 +125,7 @@ const imageUpscayl = async (event, payload: ImageUpscaylPayload) => { // Free up memory upscayl.kill(); mainWindow.setProgressBar(-1); - mainWindow.webContents.send( - COMMAND.UPSCAYL_DONE, - outFile.replace( - /([^/\\]+)$/i, - encodeURIComponent(outFile.match(/[^/\\]+$/i)![0]), - ), - ); + mainWindow.webContents.send(COMMAND.UPSCAYL_DONE, outFile); showNotification("Upscayl", "Image upscayled successfully!"); } }; diff --git a/electron/utils/config-variables.ts b/electron/utils/config-variables.ts index a6c74e6..7fbef85 100644 --- a/electron/utils/config-variables.ts +++ b/electron/utils/config-variables.ts @@ -40,12 +40,6 @@ export function setRememberOutputFolder(value: boolean): void { logit("💾 Updating Remember Output Folder: ", rememberOutputFolder); } -export let savedCompression = 0; -export function setCompression(value: number): void { - savedCompression = value; - logit("📐 Updating Compression: ", savedCompression); -} - export let stopped = false; export let childProcesses: { process: ChildProcessWithoutNullStreams; @@ -148,14 +142,7 @@ export function fetchLocalStorage(): void { setRememberOutputFolder(lastSaveOutputFolder); } }); - // GET IMAGE COMPRESSION (NUMBER) FROM LOCAL STORAGE - mainWindow.webContents - .executeJavaScript('localStorage.getItem("compression");', true) - .then((lastSavedCompression: string | null) => { - if (lastSavedCompression !== null) { - setCompression(parseInt(lastSavedCompression)); - } - }); + // GET PROCESS IMAGE (BOOLEAN) FROM LOCAL STORAGE mainWindow.webContents .executeJavaScript('localStorage.getItem("noImageProcessing");', true) diff --git a/electron/utils/get-arguments.ts b/electron/utils/get-arguments.ts index 03bb572..6d3408e 100644 --- a/electron/utils/get-arguments.ts +++ b/electron/utils/get-arguments.ts @@ -5,7 +5,7 @@ const slash: string = getPlatform() === "win" ? "\\" : "/"; export const getSingleImageArguments = ({ inputDir, - fullfileName, + fileNameWithExt, outFile, modelsPath, model, @@ -15,7 +15,7 @@ export const getSingleImageArguments = ({ customWidth, }: { inputDir: string; - fullfileName: string; + fileNameWithExt: string; outFile: string; modelsPath: string; model: string; @@ -29,7 +29,7 @@ export const getSingleImageArguments = ({ return [ // INPUT IMAGE "-i", - inputDir + slash + fullfileName, + inputDir + slash + fileNameWithExt, // OUTPUT IMAGE "-o", outFile, @@ -109,6 +109,7 @@ export const getDoubleUpscaleSecondPassArguments = ({ saveImageAs: ImageFormat; scale: string; customWidth: string; + compression: string; }) => { const modelScale = (parseInt(getModelScale(model)) ** 2).toString(); let includeScale = modelScale !== scale && !customWidth; diff --git a/package.json b/package.json index a07ed71..bf876c6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "upscayl", "private": true, - "version": "2.10.9", + "version": "2.11.0", "productName": "Upscayl", "author": { "name": "Nayam Amarshe", diff --git a/renderer/atoms/userSettingsAtom.ts b/renderer/atoms/userSettingsAtom.ts index e3aacd9..f704eb9 100644 --- a/renderer/atoms/userSettingsAtom.ts +++ b/renderer/atoms/userSettingsAtom.ts @@ -10,6 +10,10 @@ export const scaleAtom = atomWithStorage("scale", "4"); export const batchModeAtom = atom(false); +/** + * The path to the last folder the user saved an image to. + * Reset to "" if rememberOutputFolder is false. + */ export const savedOutputPathAtom = atomWithStorage( "savedOutputPath", null, diff --git a/renderer/components/settings-tab/index.tsx b/renderer/components/settings-tab/index.tsx index 23bdd47..a3ef051 100644 --- a/renderer/components/settings-tab/index.tsx +++ b/renderer/components/settings-tab/index.tsx @@ -9,12 +9,7 @@ import { DonateButton } from "./DonateButton"; import React, { useEffect, useState } from "react"; import { themeChange } from "theme-change"; import { useAtom, useAtomValue } from "jotai"; -import { - customModelsPathAtom, - noImageProcessingAtom, - overwriteAtom, - scaleAtom, -} from "../../atoms/userSettingsAtom"; +import { customModelsPathAtom, scaleAtom } from "../../atoms/userSettingsAtom"; import { modelsListAtom } from "../../atoms/modelsListAtom"; import useLog from "../hooks/useLog"; import { CompressionInput } from "./CompressionInput"; diff --git a/renderer/components/upscayl-tab/config/LeftPaneImageSteps.tsx b/renderer/components/upscayl-tab/config/LeftPaneImageSteps.tsx index b71a754..f19ea4e 100644 --- a/renderer/components/upscayl-tab/config/LeftPaneImageSteps.tsx +++ b/renderer/components/upscayl-tab/config/LeftPaneImageSteps.tsx @@ -334,7 +334,7 @@ function LeftPaneImageSteps({

)}