diff --git a/src/compressor.js b/src/compressor.js
index a8df562..ee75e15 100644
--- a/src/compressor.js
+++ b/src/compressor.js
@@ -1,9 +1,10 @@
-const {exec, execSync} = require("child_process");
+const {spawn, execSync} = require("child_process");
const path = require("path");
-const {ipcRenderer, shell} = require("electron");
+const {ipcRenderer} = require("electron");
const os = require("os");
const si = require("systeminformation");
const {existsSync} = require("fs");
+const crypto = require("crypto");
document.addEventListener("translations-loaded", () => {
window.i18n.translatePage();
@@ -11,35 +12,74 @@ document.addEventListener("translations-loaded", () => {
let menuIsOpen = false;
-getId("menuIcon").addEventListener("click", () => {
+/**
+ * @param {string} id
+ */
+function getId(id) {
+ return document.getElementById(id);
+}
+
+const dom = {
+ menuIcon: getId("menuIcon"),
+ menu: getId("menu"),
+ fileInput: getId("fileInput"),
+ selectedFilesDiv: getId("selected-files"),
+ customFolderSelect: getId("custom-folder-select"),
+ customFolderPath: getId("custom-folder-path"),
+ compressBtn: getId("compress-btn"),
+ cancelBtn: getId("cancel-btn"),
+ compressionStatus: getId("compression-status"),
+ themeToggle: getId("themeToggle"),
+ outputFolderInput: getId("output-folder-input"),
+ preferenceWin: getId("preferenceWin"),
+ playlistWin: getId("playlistWin"),
+ aboutWin: getId("aboutWin"),
+ historyWin: getId("historyWin"),
+ homeWin: getId("homeWin"),
+ encoder: getId("encoder"),
+ compressionSpeed: getId("compression-speed"),
+ videoQuality: getId("video-quality"),
+ audioFormat: getId("audio-format"),
+ fileExtension: getId("file_extension"),
+ outputSuffix: getId("output-suffix"),
+};
+
+function openMenu() {
+ dom.menuIcon.style.transform = "rotate(90deg)";
+ menuIsOpen = true;
+ dom.menu.style.display = "flex";
+
+ setTimeout(() => {
+ dom.menu.style.opacity = "1";
+ }, 20);
+}
+
+function closeMenu() {
+ dom.menuIcon.style.transform = "rotate(0deg)";
+ menuIsOpen = false;
+ let count = 0;
+ let opacity = 1;
+ const fade = setInterval(() => {
+ if (count >= 10) {
+ dom.menu.style.display = "none";
+ clearInterval(fade);
+ } else {
+ opacity -= 0.1;
+ dom.menu.style.opacity = opacity.toFixed(2);
+ count++;
+ }
+ }, 50);
+}
+
+dom.menuIcon.addEventListener("click", () => {
if (menuIsOpen) {
- getId("menuIcon").style.transform = "rotate(0deg)";
- menuIsOpen = false;
- let count = 0;
- let opacity = 1;
- const fade = setInterval(() => {
- if (count >= 10) {
- getId("menu").style.display = "none";
- clearInterval(fade);
- } else {
- opacity -= 0.1;
- getId("menu").style.opacity = opacity.toFixed(3).toString();
- count++;
- }
- }, 50);
+ closeMenu();
} else {
- getId("menuIcon").style.transform = "rotate(90deg)";
- menuIsOpen = true;
-
- setTimeout(() => {
- getId("menu").style.display = "flex";
- getId("menu").style.opacity = "1";
- }, 150);
+ openMenu();
}
});
const ffmpeg = getFfmpegPath();
-
console.log(ffmpeg);
const vaapi_device = "/dev/dri/renderD128";
@@ -47,48 +87,42 @@ const vaapi_device = "/dev/dri/renderD128";
// Checking GPU
si.graphics().then((info) => {
console.log({gpuInfo: info});
- const gpuDevices = info.controllers;
+ const platform = os.platform();
+
+ const selectorMap = {
+ nvidia: ".nvidia_opt",
+ amf: platform === "win32" ? ".amf_opt" : ".vaapi_opt",
+ qsv:
+ platform === "win32"
+ ? ".qsv_opt"
+ : platform !== "darwin"
+ ? ".vaapi_opt"
+ : null,
+ videotoolbox: platform === "darwin" ? ".videotoolbox_opt" : null,
+ };
- gpuDevices.forEach((gpu) => {
- // NVIDIA
+ info.controllers.forEach((gpu) => {
const gpuName = gpu.vendor.toLowerCase();
const gpuModel = gpu.model.toLowerCase();
+ let selector = null;
if (gpuName.includes("nvidia") || gpuModel.includes("nvidia")) {
- document.querySelectorAll(".nvidia_opt").forEach((opt) => {
- opt.style.display = "block";
- });
+ selector = selectorMap.nvidia;
} else if (
gpuName.includes("advanced micro devices") ||
gpuModel.includes("amd")
) {
- if (os.platform() == "win32") {
- document.querySelectorAll(".amf_opt").forEach((opt) => {
- opt.style.display = "block";
- });
- } else {
- document.querySelectorAll(".vaapi_opt").forEach((opt) => {
- opt.style.display = "block";
- });
- }
+ selector = selectorMap.amf;
} else if (gpuName.includes("intel")) {
- if (os.platform() == "win32") {
- document.querySelectorAll(".qsv_opt").forEach((opt) => {
- opt.style.display = "block";
- });
- } else if (os.platform() != "darwin") {
- document.querySelectorAll(".vaapi_opt").forEach((opt) => {
- opt.style.display = "block";
- });
- }
- } else {
- if (os.platform() == "darwin") {
- document
- .querySelectorAll(".videotoolbox_opt")
- .forEach((opt) => {
- opt.style.display = "block";
- });
- }
+ selector = selectorMap.qsv;
+ } else if (platform === "darwin") {
+ selector = selectorMap.videotoolbox;
+ }
+
+ if (selector) {
+ document.querySelectorAll(selector).forEach((opt) => {
+ opt.style.display = "block";
+ });
}
});
});
@@ -99,17 +133,8 @@ let activeProcesses = new Set();
let currentItemId = "";
let isCancelled = false;
-/**
- * @param {string} id
- */
-function getId(id) {
- return document.getElementById(id);
-}
-
// File Handling
const dropZone = document.querySelector(".drop-zone");
-const fileInput = getId("fileInput");
-const selectedFilesDiv = getId("selected-files");
dropZone.addEventListener("dragover", (e) => {
e.preventDefault();
@@ -123,19 +148,16 @@ dropZone.addEventListener("dragleave", () => {
dropZone.addEventListener("drop", (e) => {
e.preventDefault();
dropZone.classList.remove("dragover");
- // @ts-ignore
- console.log(e.dataTransfer);
files = Array.from(e.dataTransfer.files);
updateSelectedFiles();
});
-fileInput.addEventListener("change", (e) => {
- // @ts-ignore
+dom.fileInput.addEventListener("change", (e) => {
files = Array.from(e.target.files);
updateSelectedFiles();
});
-getId("custom-folder-select").addEventListener("click", (e) => {
+dom.customFolderSelect.addEventListener("click", () => {
ipcRenderer.send("get-directory", "");
});
@@ -143,21 +165,24 @@ function updateSelectedFiles() {
const fileList = files
.map((f) => `${f.name} (${formatBytes(f.size)})
`)
.join("\n");
- selectedFilesDiv.innerHTML = fileList || "No files selected";
+ dom.selectedFilesDiv.innerHTML = fileList || "No files selected";
}
// Compression Logic
-getId("compress-btn").addEventListener("click", startCompression);
-getId("cancel-btn").addEventListener("click", cancelCompression);
+dom.compressBtn.addEventListener("click", startCompression);
+dom.cancelBtn.addEventListener("click", cancelCompression);
async function startCompression() {
if (files.length === 0) return alert("Please select files first!");
const settings = getEncoderSettings();
+ isCancelled = false; // Ensure clean state at the start
for (const file of files) {
- const itemId =
- "f" + Math.random().toFixed(10).toString().slice(2).toString();
+ // Check if cancellation happened before starting this file
+ if (isCancelled) break;
+
+ const itemId = "f" + crypto.randomUUID().replace(/-/g, "");
currentItemId = itemId;
const outputPath = generateOutputPath(file, settings);
@@ -166,7 +191,7 @@ async function startCompression() {
await compressVideo(file, settings, itemId, outputPath);
if (isCancelled) {
- isCancelled = false;
+ break; // Break the loop if cancelled during compression
} else {
updateProgress("success", "", itemId);
const fileSavedElement = document.createElement("b");
@@ -174,28 +199,36 @@ async function startCompression() {
fileSavedElement.onclick = () => {
ipcRenderer.send("show-file", outputPath);
};
- getId(itemId + "_prog").appendChild(fileSavedElement);
- currentItemId = "";
+ getId(itemId + "_prog")?.appendChild(fileSavedElement);
}
} catch (error) {
+ if (isCancelled) {
+ break; // Break loop if process was killed by cancel button
+ }
+
const errorElement = document.createElement("div");
errorElement.onclick = () => {
ipcRenderer.send("error_dialog", error.message);
};
errorElement.textContent = i18n.__("errorClickForDetails");
updateProgress("error", "", itemId);
- getId(itemId + "_prog").appendChild(errorElement);
- currentItemId = "";
+ getId(itemId + "_prog")?.appendChild(errorElement);
}
}
+
+ // Reset states when queue finishes or is broken
+ currentItemId = "";
+ isCancelled = false;
}
function cancelCompression() {
+ isCancelled = true;
+
activeProcesses.forEach((child) => {
child.stdin.write("q");
- isCancelled = true;
});
activeProcesses.clear();
+
updateProgress("error", "Cancelled", currentItemId);
}
@@ -203,22 +236,20 @@ function cancelCompression() {
* @param {File} file
*/
function generateOutputPath(file, settings) {
- console.log({settings});
const output_extension = settings.extension;
const parsed_file = path.parse(file.path);
+ const outputDir = settings.outputPath || parsed_file.dir;
- let outputDir = settings.outputPath || parsed_file.dir;
-
- if (output_extension == "unchanged") {
+ if (output_extension === "unchanged") {
return path.join(
outputDir,
- `${parsed_file.name}${settings.outputSuffix}${parsed_file.ext}`
+ `${parsed_file.name}${settings.outputSuffix}${parsed_file.ext}`,
);
}
return path.join(
outputDir,
- `${parsed_file.name}_compressed.${output_extension}`
+ `${parsed_file.name}_compressed.${output_extension}`,
);
}
@@ -229,65 +260,66 @@ function generateOutputPath(file, settings) {
* @param {string} outputPath
*/
async function compressVideo(file, settings, itemId, outputPath) {
- const command = buildFFmpegCommand(file, settings, outputPath);
-
- console.log("Command: " + command);
+ const args = buildFFmpegArgs(file, settings, outputPath);
+ console.log("Command: " + args.join(" "));
return new Promise((resolve, reject) => {
- const child = exec(command, (error) => {
- if (error) reject(error);
- else resolve();
- });
+ const child = spawn(ffmpeg, args);
activeProcesses.add(child);
- child.on("exit", (_code) => {
+ child.on("exit", () => {
activeProcesses.delete(child);
});
- let video_info = {
- duration: "",
- bitrate: "",
- };
+ let video_info = {duration: ""};
createProgressItem(
path.basename(file.path),
"progress",
`Starting...`,
- itemId
+ itemId,
);
child.stderr.on("data", (data) => {
- // console.log(data)
- const duration_match = data.match(/Duration:\s*([\d:.]+)/);
+ const dataStr = data.toString();
+
+ const duration_match = dataStr.match(/Duration:\s*([\d:.]+)/);
if (duration_match) {
video_info.duration = duration_match[1];
}
- // const bitrate_match = data.match(/bitrate:\s*([\d:.]+)/);
- // if (bitrate_match) {
- // // Bitrate in kb/s
- // video_info.bitrate = bitrate_match[1];
- // }
-
- const progressTime = data.match(/time=(\d+:\d+:\d+\.\d+)/);
-
+ const progressTime = dataStr.match(/time=(\d+:\d+:\d+\.\d+)/);
const totalSeconds = timeToSeconds(video_info.duration);
-
const currentSeconds =
progressTime && progressTime.length > 1
? timeToSeconds(progressTime[1])
: null;
- if (currentSeconds && !isCancelled) {
+ if (currentSeconds && totalSeconds > 0 && !isCancelled) {
const progress = Math.round(
- (currentSeconds / totalSeconds) * 100
+ (currentSeconds / totalSeconds) * 100,
);
-
- getId(
- itemId + "_prog"
- ).innerHTML = `