1
0
mirror of https://github.com/upscayl/upscayl.git synced 2025-02-17 11:18:36 +01:00

Fix image, add quality slider and folder upscale

This commit is contained in:
Nayam Amarshe 2023-07-23 14:37:18 +05:30
parent 269d5d9603
commit 93c426a5b4
11 changed files with 1362 additions and 1479 deletions

View File

@ -49,6 +49,7 @@ let folderPath: string | undefined = undefined;
let customModelsFolderPath: string | undefined = undefined; let customModelsFolderPath: string | undefined = undefined;
let outputFolderPath: string | undefined = undefined; let outputFolderPath: string | undefined = undefined;
let saveOutputFolder = false; let saveOutputFolder = false;
let quality = 100;
let stopped = false; let stopped = false;
@ -157,6 +158,15 @@ app.on("ready", async () => {
saveOutputFolder = lastSaveOutputFolder; saveOutputFolder = lastSaveOutputFolder;
} }
}); });
// GET IMAGE QUALITY (NUMBER) TO LOCAL STORAGE
mainWindow.webContents
.executeJavaScript('localStorage.getItem("quality");', true)
.then((lastSavedQuality: string | null) => {
if (lastSavedQuality !== null) {
quality = parseInt(lastSavedQuality);
}
});
}); });
// Quit the app once all windows are closed // Quit the app once all windows are closed
@ -441,13 +451,34 @@ ipcMain.on(commands.DOUBLE_UPSCAYL, async (event, payload) => {
return; return;
}; };
const onClose2 = (code) => { const onClose2 = async (code) => {
if (!failed2 && !stopped) { if (!failed2 && !stopped) {
logit("💯 Done upscaling"); logit("💯 Done upscaling");
mainWindow.webContents.send( logit("♻ Scaling and converting now...");
commands.DOUBLE_UPSCAYL_DONE, const originalImage = await Jimp.read(inputDir + slash + fullfileName);
isAlpha ? outFile + ".png" : outFile 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);
}
} }
}; };
@ -580,6 +611,7 @@ ipcMain.on(commands.UPSCAYL, async (event, payload) => {
originalImage.getWidth() * parseInt(payload.scale), originalImage.getWidth() * parseInt(payload.scale),
originalImage.getHeight() * parseInt(payload.scale) originalImage.getHeight() * parseInt(payload.scale)
) )
.quality(100 - quality)
.write(isAlpha ? outFile + ".png" : outFile); .write(isAlpha ? outFile + ".png" : outFile);
mainWindow.setProgressBar(-1); mainWindow.setProgressBar(-1);
mainWindow.webContents.send( mainWindow.webContents.send(
@ -653,8 +685,9 @@ ipcMain.on(commands.FOLDER_UPSCAYL, async (event, payload) => {
data.toString() data.toString()
); );
if (data.includes("invalid gpu") || data.includes("failed")) { if (data.includes("invalid gpu") || data.includes("failed")) {
logit("❌ INVALID GPU OR FAILED"); logit("❌ INVALID GPU OR INVALID FILES IN FOLDER - FAILED");
failed = true; failed = true;
upscayl.kill();
} }
}; };
const onError = (data: any) => { const onError = (data: any) => {
@ -663,12 +696,15 @@ ipcMain.on(commands.FOLDER_UPSCAYL, async (event, payload) => {
data.toString() data.toString()
); );
failed = true; failed = true;
upscayl.kill();
return; return;
}; };
const onClose = () => { const onClose = () => {
if (!failed && !stopped) { if (!failed && !stopped) {
logit("💯 Done upscaling"); logit("💯 Done upscaling");
mainWindow.webContents.send(commands.FOLDER_UPSCAYL_DONE, outputDir); mainWindow.webContents.send(commands.FOLDER_UPSCAYL_DONE, outputDir);
} else {
upscayl.kill();
} }
}; };
@ -680,24 +716,6 @@ ipcMain.on(commands.FOLDER_UPSCAYL, async (event, payload) => {
//------------------------Auto-Update Code-----------------------------// //------------------------Auto-Update Code-----------------------------//
autoUpdater.autoInstallOnAppQuit = false; autoUpdater.autoInstallOnAppQuit = false;
// ! AUTO UPDATE STUFF
// autoUpdater.on("update-available", ({ version, releaseNotes, releaseName }) => {
// autoUpdater.autoInstallOnAppQuit = false;
// const dialogOpts = {
// type: "info",
// buttons: ["Sweet!"],
// title: "New Upscayl Update!",
// message: releaseName as string,
// detail: `Upscayl ${version} is available! It is being downloaded in the background. Please check GitHub for more details.`,
// };
// logit("📲 Update Available", releaseName, releaseNotes);
// dialog.showMessageBox(dialogOpts).then((returnValue) => {
// if (returnValue.response === 0) {
// logit("📲 Update Downloading");
// }
// });
// });
autoUpdater.on("update-downloaded", (event) => { autoUpdater.on("update-downloaded", (event) => {
autoUpdater.autoInstallOnAppQuit = false; autoUpdater.autoInstallOnAppQuit = false;
const dialogOpts: MessageBoxOptions = { const dialogOpts: MessageBoxOptions = {
@ -717,110 +735,3 @@ autoUpdater.on("update-downloaded", (event) => {
} }
}); });
}); });
//------------------------Video Upscayl-----------------------------//
// ipcMain.on(commands.UPSCAYL_VIDEO, async (event, payload) => {
// // Extract the model
// const model = payload.model;
// // Extract the Video Directory
// let videoFileName = payload.videoPath.replace(/^.*[\\\/]/, "");
// const justFileName = parse(videoFileName).name;
// let inputDir = payload.videoPath.match(/(.*)[\/\\]/)[1] || "";
// log.log("🚀 => file: index.ts => line 337 => inputDir", inputDir);
// // Set the output directory
// let outputDir = payload.outputPath + "_frames";
// log.log("🚀 => file: index.ts => line 340 => outputDir", outputDir);
// let frameExtractionPath = join(inputDir, justFileName + "_f");
// let frameUpscalePath = join(inputDir, justFileName + "_u");
// log.log(
// "🚀 => file: index.ts => line 342 => frameExtractionPath",
// frameExtractionPath,
// frameUpscalePath
// );
// if (!fs.existsSync(frameExtractionPath)) {
// fs.mkdirSync(frameExtractionPath, { recursive: true });
// }
// if (!fs.existsSync(frameUpscalePath)) {
// fs.mkdirSync(frameUpscalePath, { recursive: true });
// }
// let ffmpegProcess: ChildProcessWithoutNullStreams | null = null;
// ffmpegProcess = spawn(
// ffmpeg.path,
// [
// "-i",
// inputDir + slash + videoFileName,
// frameExtractionPath + slash + "out%d.png",
// ],
// {
// cwd: undefined,
// detached: false,
// }
// );
// let failed = false;
// ffmpegProcess?.stderr.on("data", (data: string) => {
// log.log("🚀 => file: index.ts:420 => data", data.toString());
// data = data.toString();
// mainWindow.webContents.send(
// commands.FFMPEG_VIDEO_PROGRESS,
// data.toString()
// );
// });
// ffmpegProcess?.on("error", (data: string) => {
// mainWindow.webContents.send(
// commands.FFMPEG_VIDEO_PROGRESS,
// data.toString()
// );
// failed = true;
// return;
// });
// // Send done comamnd when
// ffmpegProcess?.on("close", (code: number) => {
// if (failed !== true) {
// log.log("Frame extraction successful!");
// mainWindow.webContents.send(commands.FFMPEG_VIDEO_DONE, outputDir);
// // UPSCALE
// let upscayl: ChildProcessWithoutNullStreams | null = null;
// upscayl = spawn(
// execPath("realesrgan"),
// [
// "-i",
// frameExtractionPath,
// "-o",
// frameUpscalePath,
// "-s",
// 4,
// "-m",
// modelsPath,
// "-n",
// model,
// ],
// {
// cwd: undefined,
// detached: false,
// }
// );
// upscayl?.stderr.on("data", (data) => {
// log.log(
// "🚀 => upscayl.stderr.on => stderr.toString()",
// data.toString()
// );
// data = data.toString();
// mainWindow.webContents.send(
// commands.FFMPEG_VIDEO_PROGRESS,
// data.toString()
// );
// });
// }
// });
// });

2511
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{ {
"name": "upscayl", "name": "upscayl",
"private": true, "private": true,
"version": "2.5.5", "version": "2.7.5",
"productName": "Upscayl", "productName": "Upscayl",
"homepage": "https://github.com/TGS963/upscayl", "homepage": "https://github.com/TGS963/upscayl",
"contributors": [ "contributors": [
@ -160,34 +160,30 @@
"@types/react": "^18.0.37", "@types/react": "^18.0.37",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"electron": "^23.2.4", "electron": "^25.3.1",
"electron-builder": "^24.2.1", "electron-builder": "^24.4.0",
"next": "^13.3.0", "next": "^13.3.0",
"postcss": "^8.4.23", "postcss": "^8.4.23",
"prettier": "^2.8.7", "prettier": "^3.0.0",
"prettier-plugin-tailwindcss": "^0.1.13", "prettier-plugin-tailwindcss": "^0.4.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"tailwindcss": "^3.3.1", "tailwindcss": "^3.3.1",
"typescript": "^4.8.4" "typescript": "^4.8.4"
}, },
"dependencies": { "dependencies": {
"app-root-dir": "^1.0.2", "daisyui": "^3.3.1",
"daisyui": "^2.51.4",
"electron-is-dev": "^2.0.0", "electron-is-dev": "^2.0.0",
"electron-log": "^5.0.0-beta.16", "electron-log": "^5.0.0-beta.16",
"electron-next": "^3.1.5", "electron-next": "^3.1.5",
"electron-updater": "^5.3.0", "electron-updater": "^6.1.1",
"image-size": "^1.0.2",
"jimp": "^0.22.8", "jimp": "^0.22.8",
"jotai": "^2.0.4", "jotai": "^2.2.2",
"react-compare-slider": "^2.2.0", "react-compare-slider": "^2.2.0",
"react-dropzone": "^14.2.3", "react-select": "^5.7.4",
"react-image-zoom": "^1.3.1", "react-tooltip": "^5.18.1",
"react-select": "^5.6.0", "tailwind-scrollbar": "^3.0.4",
"react-tooltip": "^4.5.0", "theme-change": "^2.5.0"
"tailwind-scrollbar": "^2.0.1",
"theme-change": "^2.2.0"
}, },
"volta": { "volta": {
"node": "16.17.0" "node": "16.17.0"

View File

@ -10,13 +10,10 @@ export function ImageScaleSelect({ scale, setScale }: ImageScaleSelectProps) {
<div> <div>
<div className="flex flex-row gap-1"> <div className="flex flex-row gap-1">
<p className="text-sm font-medium">IMAGE SCALE</p> <p className="text-sm font-medium">IMAGE SCALE</p>
<a
href="https://github.com/upscayl/upscayl/wiki/Guide#scale-option" <p className="badge-primary badge text-[10px] font-medium">
target="_blank"> EXPERIMENTAL
<p className="badge-primary badge text-[10px] font-medium"> </p>
EXPERIMENTAL
</p>
</a>
</div> </div>
<input <input
type="range" type="range"

View File

@ -0,0 +1,28 @@
import React from "react";
type QualityInputProps = {
quality: number;
handleQualityChange: (arg: any) => void;
};
export function QualityInput({
quality,
handleQualityChange,
}: QualityInputProps) {
return (
<div className="flex flex-col gap-2">
<p className="text-sm font-medium uppercase">
Image Compression ({quality}%)
</p>
<input
type="range"
placeholder="Type here"
className="range range-primary w-full max-w-xs"
min={0}
max={100}
value={quality}
onChange={handleQualityChange}
/>
</div>
);
}

View File

@ -12,12 +12,15 @@ import { useAtom, useAtomValue } from "jotai";
import { customModelsPathAtom, scaleAtom } from "../../atoms/userSettingsAtom"; import { customModelsPathAtom, scaleAtom } from "../../atoms/userSettingsAtom";
import { modelsListAtom } from "../../atoms/modelsListAtom"; import { modelsListAtom } from "../../atoms/modelsListAtom";
import useLog from "../hooks/useLog"; import useLog from "../hooks/useLog";
import { QualityInput } from "./QualityInput";
interface IProps { interface IProps {
batchMode: boolean; batchMode: boolean;
setModel: React.Dispatch<React.SetStateAction<string>>; setModel: React.Dispatch<React.SetStateAction<string>>;
saveImageAs: string; saveImageAs: string;
setSaveImageAs: React.Dispatch<React.SetStateAction<string>>; setSaveImageAs: React.Dispatch<React.SetStateAction<string>>;
quality: number;
setQuality: React.Dispatch<React.SetStateAction<number>>;
gpuId: string; gpuId: string;
setGpuId: React.Dispatch<React.SetStateAction<string>>; setGpuId: React.Dispatch<React.SetStateAction<string>>;
logData: string[]; logData: string[];
@ -26,6 +29,8 @@ interface IProps {
function SettingsTab({ function SettingsTab({
batchMode, batchMode,
setModel, setModel,
quality,
setQuality,
gpuId, gpuId,
setGpuId, setGpuId,
saveImageAs, saveImageAs,
@ -113,6 +118,11 @@ function SettingsTab({
localStorage.setItem("saveImageAs", format); localStorage.setItem("saveImageAs", format);
}; };
const handleQualityChange = (e) => {
setQuality(e.target.value);
localStorage.setItem("quality", e.target.value);
};
const handleGpuIdChange = (e) => { const handleGpuIdChange = (e) => {
setGpuId(e.target.value); setGpuId(e.target.value);
localStorage.setItem("gpuId", e.target.value); localStorage.setItem("gpuId", e.target.value);
@ -139,9 +149,30 @@ function SettingsTab({
<DonateButton /> <DonateButton />
</div> </div>
<LogArea
copyOnClickHandler={copyOnClickHandler}
isCopied={isCopied}
logData={logData}
/>
{/* THEME SELECTOR */} {/* THEME SELECTOR */}
<ThemeSelect /> <ThemeSelect />
{/* IMAGE FORMAT BUTTONS */}
<ImageFormatSelect
batchMode={batchMode}
saveImageAs={saveImageAs}
setExportType={setExportType}
/>
{/* IMAGE SCALE */}
<ImageScaleSelect scale={scale} setScale={setScale} />
<QualityInput
quality={quality}
handleQualityChange={handleQualityChange}
/>
<SaveOutputFolderToggle <SaveOutputFolderToggle
rememberOutputFolder={rememberOutputFolder} rememberOutputFolder={rememberOutputFolder}
setRememberOutputFolder={setRememberOutputFolder} setRememberOutputFolder={setRememberOutputFolder}
@ -155,22 +186,6 @@ function SettingsTab({
customModelsPath={customModelsPath} customModelsPath={customModelsPath}
setCustomModelsPath={setCustomModelsPath} setCustomModelsPath={setCustomModelsPath}
/> />
{/* IMAGE FORMAT BUTTONS */}
<ImageFormatSelect
batchMode={batchMode}
saveImageAs={saveImageAs}
setExportType={setExportType}
/>
{/* IMAGE SCALE */}
<ImageScaleSelect scale={scale} setScale={setScale} />
<LogArea
copyOnClickHandler={copyOnClickHandler}
isCopied={isCopied}
logData={logData}
/>
</div> </div>
); );
} }

View File

@ -1,7 +1,7 @@
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import Select from "react-select"; import Select from "react-select";
import ReactTooltip from "react-tooltip"; import { Tooltip } from "react-tooltip";
import { themeChange } from "theme-change"; import { themeChange } from "theme-change";
import { modelsListAtom } from "../../../atoms/modelsListAtom"; import { modelsListAtom } from "../../../atoms/modelsListAtom";
import useLog from "../../hooks/useLog"; import useLog from "../../hooks/useLog";
@ -140,13 +140,14 @@ function LeftPaneImageSteps({
onClick={() => setBatchMode((oldValue) => !oldValue)}></input> onClick={() => setBatchMode((oldValue) => !oldValue)}></input>
<p <p
className="mr-1 inline-block cursor-help text-sm" className="mr-1 inline-block cursor-help text-sm"
data-tip="This will let you Upscayl all files in a folder at once"> data-tooltip-id="tooltip"
data-tooltip-content="This will let you Upscayl all files in a folder at once">
Batch Upscayl Batch Upscayl
</p> </p>
</div> </div>
{/* STEP 1 */} {/* STEP 1 */}
<div data-tip={imagePath}> <div data-tooltip-id="tooltip" data-tooltip-content={imagePath}>
<p className="step-heading">Step 1</p> <p className="step-heading">Step 1</p>
<button <button
className="btn-primary btn" className="btn-primary btn"
@ -198,7 +199,8 @@ function LeftPaneImageSteps({
</p> </p>
<button <button
className="badge-info badge cursor-help" className="badge-info badge cursor-help"
data-tip="Enable this option to get a 16x upscayl (we just run upscayl twice). Note that this may not always work properly with all images, for example, images with really large resolutions."> data-tooltip-id="tooltip"
data-tooltip-content="Enable this option to get a 16x upscayl (we just run upscayl twice). Note that this may not always work properly with all images, for example, images with really large resolutions.">
i i
</button> </button>
</div> </div>
@ -206,7 +208,10 @@ function LeftPaneImageSteps({
</div> </div>
{/* STEP 3 */} {/* STEP 3 */}
<div className="animate-step-in" data-tip={outputPath}> <div
className="animate-step-in"
data-tooltip-content={outputPath}
data-tooltip-id="tooltip">
<p className="step-heading">Step 3</p> <p className="step-heading">Step 3</p>
<p className="mb-2 text-sm"> <p className="mb-2 text-sm">
Defaults to {!batchMode ? "Image's" : "Folder's"} path Defaults to {!batchMode ? "Image's" : "Folder's"} path
@ -239,7 +244,7 @@ function LeftPaneImageSteps({
</button> </button>
</div> </div>
<ReactTooltip class="max-w-sm" /> <Tooltip className="max-w-sm" id="tooltip" />
</div> </div>
); );
} }

View File

@ -1,6 +1,5 @@
import React from "react"; import React from "react";
import Select from "react-select"; import Select from "react-select";
import ReactTooltip from "react-tooltip";
interface IProps { interface IProps {
progress: string; progress: string;
@ -101,8 +100,6 @@ function LeftPaneVideoSteps({
{progress.length > 0 ? "Upscayling⏳" : "Upscayl"} {progress.length > 0 ? "Upscayling⏳" : "Upscayl"}
</button> </button>
</div> </div>
<ReactTooltip class="max-w-sm" />
</div> </div>
); );
} }

View File

@ -1,15 +1,22 @@
import React from "react"; import React from "react";
import Spinner from "../../icons/Spinner"; import Spinner from "../../icons/Spinner";
function ProgressBar({ progress, doubleUpscaylCounter, stopHandler }) { function ProgressBar({
progress,
doubleUpscaylCounter,
stopHandler,
batchMode,
}) {
return ( return (
<div className="absolute flex h-full w-full flex-col items-center justify-center bg-base-300/50 backdrop-blur-lg"> <div className="absolute flex h-full w-full flex-col items-center justify-center bg-base-300/50 backdrop-blur-lg">
<div className="flex flex-col items-center gap-2"> <div className="flex flex-col items-center gap-2">
<Spinner /> <Spinner />
<p className="rounded-full bg-base-300 px-2 py-1 font-bold"> <p className="rounded-full bg-base-300 px-2 py-1 font-bold">
{doubleUpscaylCounter > 0 {batchMode && "In Progress"}
? `${progress}\nPass ${doubleUpscaylCounter}` {!batchMode &&
: `${progress}`} (doubleUpscaylCounter > 0
? `${progress}\nPass ${doubleUpscaylCounter}`
: `${progress}`)}
</p> </p>
<p className="rounded-full bg-base-300 px-2 py-1 text-sm font-medium"> <p className="rounded-full bg-base-300 px-2 py-1 text-sm font-medium">
Doing the Upscayl magic... Doing the Upscayl magic...

View File

@ -1,10 +1,8 @@
import "../styles/globals.css"; import "../styles/globals.css";
import Head from "next/head"; import Head from "next/head";
import { AppProps } from "next/app"; import { AppProps } from "next/app";
import { useEffect } from "react";
import { themeChange } from "theme-change";
import log from "electron-log/renderer";
import { Provider } from "jotai"; import { Provider } from "jotai";
import "react-tooltip/dist/react-tooltip.css";
const MyApp = ({ Component, pageProps }: AppProps) => { const MyApp = ({ Component, pageProps }: AppProps) => {
return ( return (

View File

@ -33,6 +33,7 @@ const Home = () => {
const [videoPath, setVideoPath] = useState(""); const [videoPath, setVideoPath] = useState("");
const [upscaledVideoPath, setUpscaledVideoPath] = useState(""); const [upscaledVideoPath, setUpscaledVideoPath] = useState("");
const [doubleUpscaylCounter, setDoubleUpscaylCounter] = useState(0); const [doubleUpscaylCounter, setDoubleUpscaylCounter] = useState(0);
const [quality, setQuality] = useState(0);
const [gpuId, setGpuId] = useState(""); const [gpuId, setGpuId] = useState("");
const [saveImageAs, setSaveImageAs] = useState("png"); const [saveImageAs, setSaveImageAs] = useState("png");
const [zoomAmount, setZoomAmount] = useState("100%"); const [zoomAmount, setZoomAmount] = useState("100%");
@ -556,6 +557,8 @@ const Home = () => {
<SettingsTab <SettingsTab
batchMode={batchMode} batchMode={batchMode}
setModel={setModel} setModel={setModel}
quality={quality}
setQuality={setQuality}
gpuId={gpuId} gpuId={gpuId}
setGpuId={setGpuId} setGpuId={setGpuId}
saveImageAs={saveImageAs} saveImageAs={saveImageAs}
@ -580,6 +583,7 @@ const Home = () => {
upscaledBatchFolderPath.length === 0 && upscaledBatchFolderPath.length === 0 &&
upscaledVideoPath.length === 0 ? ( upscaledVideoPath.length === 0 ? (
<ProgressBar <ProgressBar
batchMode={batchMode}
progress={progress} progress={progress}
doubleUpscaylCounter={doubleUpscaylCounter} doubleUpscaylCounter={doubleUpscaylCounter}
stopHandler={stopHandler} stopHandler={stopHandler}