1
0
mirror of https://github.com/upscayl/upscayl.git synced 2024-09-23 19:08:25 +02:00

Refactor code and remove config vars to fix ASCII path decoding

This commit is contained in:
Nayam Amarshe 2024-04-25 00:59:51 +05:30
parent 618b46f553
commit 5feabea516
15 changed files with 142 additions and 159 deletions

5
common/decode-path.ts Normal file
View File

@ -0,0 +1,5 @@
import path from "path";
export default function decodePath(filePath: string): string {
return path.normalize(decodeURIComponent(filePath));
}

View File

@ -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 || "";
}

8
common/get-file-name.ts Normal file
View File

@ -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(".");
}

24
common/sanitize-path.ts Normal file
View File

@ -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;
}

View File

@ -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;

View File

@ -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(

View File

@ -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,
);

View File

@ -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!");
}
};

View File

@ -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)

View File

@ -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;

View File

@ -1,7 +1,7 @@
{
"name": "upscayl",
"private": true,
"version": "2.10.9",
"version": "2.11.0",
"productName": "Upscayl",
"author": {
"name": "Nayam Amarshe",

View File

@ -10,6 +10,10 @@ export const scaleAtom = atomWithStorage<string>("scale", "4");
export const batchModeAtom = atom<boolean>(false);
/**
* The path to the last folder the user saved an image to.
* Reset to "" if rememberOutputFolder is false.
*/
export const savedOutputPathAtom = atomWithStorage<string | null>(
"savedOutputPath",
null,

View File

@ -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";

View File

@ -334,7 +334,7 @@ function LeftPaneImageSteps({
</p>
)}
<button
className="btn btn-accent"
className="btn btn-secondary"
onClick={
progress.length > 0 || !outputPath
? () =>

View File

@ -1,5 +1,5 @@
"use client";
import { useState, useEffect, useCallback } from "react";
import { useState, useEffect, useCallback, useMemo } from "react";
import COMMAND from "../../common/commands";
import { ReactCompareSlider } from "react-compare-slider";
import Header from "../components/Header";
@ -40,16 +40,13 @@ import {
import { NewsModal } from "@/components/NewsModal";
import { newsAtom, showNewsModalAtom } from "@/atoms/newsAtom";
import matter from "gray-matter";
import {
ChevronLeftIcon,
ChevronRightIcon,
PanelLeftCloseIcon,
PanelRightCloseIcon,
} from "lucide-react";
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import { useToast } from "@/components/ui/use-toast";
import { ToastAction } from "@/components/ui/toast";
import Logo from "@/components/icons/Logo";
import { sanitizePath } from "@common/sanitize-path";
import getDirectoryFromPath from "@common/get-directory-from-path";
const Home = () => {
const allowedFileTypes = ["png", "jpg", "jpeg", "webp"];
@ -103,6 +100,16 @@ const Home = () => {
const { logit } = useLog();
const { toast } = useToast();
const sanitizedImagePath = useMemo(
() => sanitizePath(imagePath),
[imagePath],
);
const sanitizedUpscaledImagePath = useMemo(
() => sanitizePath(upscaledImagePath),
[upscaledImagePath],
);
const handleMouseMoveCompare = (e: React.MouseEvent) => {
const { left, top, height, width } =
e.currentTarget.getBoundingClientRect();
@ -129,7 +136,7 @@ const Home = () => {
if (data.includes("Invalid GPU")) {
toast({
title: "GPU Error",
description: `GPU error occurred. Please read the wiki for troubleshooting! ${data})`,
description: `Ran into an issue with the GPU. Please read the wiki for troubleshooting! (${data})`,
action: (
<div className="flex flex-col gap-2">
<ToastAction
@ -345,7 +352,7 @@ const Home = () => {
setIsLoading(false);
}, []);
// * HANDLERS
// HANDLERS
const resetImagePaths = () => {
logit("🔄 Resetting image paths");
setDimensions({
@ -393,7 +400,7 @@ const Home = () => {
if (path === null) return;
logit("🖼 Selected Image Path: ", path);
setImagePath(path);
var dirname = path.match(/(.*)[\/\\]/)[1] || "";
var dirname = getDirectoryFromPath(path);
logit("📁 Selected Image Directory: ", dirname);
if (!featureFlags.APP_STORE_BUILD) {
if (!rememberOutputFolder) {
@ -478,7 +485,7 @@ const Home = () => {
} else {
logit("🖼 Setting image path: ", filePath);
setImagePath(filePath);
var dirname = filePath.match(/(.*)[\/\\]/)[1] || "";
var dirname = getDirectoryFromPath(filePath);
logit("🗂 Setting output path: ", dirname);
if (!featureFlags.APP_STORE_BUILD) {
if (!rememberOutputFolder) {
@ -506,7 +513,7 @@ const Home = () => {
});
} else {
setImagePath(filePath);
var dirname = filePath.match(/(.*)[\/\\]/)[1] || "";
var dirname = getDirectoryFromPath(filePath);
logit("🗂 Setting output path: ", dirname);
if (!rememberOutputFolder) {
setOutputPath(dirname);
@ -748,7 +755,7 @@ const Home = () => {
hideZoomOptions={true}
/>
<img
src={"file:///" + imagePath}
src={"file:///" + sanitizePath(imagePath)}
onLoad={(e: any) => {
setDimensions({
width: e.target.naturalWidth,
@ -851,7 +858,7 @@ const Home = () => {
<img
/* USE REGEX TO GET THE FILENAME AND ENCODE IT INTO PROPER FORM IN ORDER TO AVOID ERRORS DUE TO SPECIAL CHARACTERS */
src={"file:///" + imagePath}
src={"file:///" + sanitizedImagePath}
alt="Original"
onMouseMove={handleMouseMove}
style={{
@ -870,7 +877,7 @@ const Home = () => {
</p>
<img
/* USE REGEX TO GET THE FILENAME AND ENCODE IT INTO PROPER FORM IN ORDER TO AVOID ERRORS DUE TO SPECIAL CHARACTERS */
src={"file:///" + upscaledImagePath}
src={"file:///" + sanitizedUpscaledImagePath}
alt="Upscayl"
style={{
objectFit: "contain",