const {exec} = require("child_process");
const path = require("path");
const {ipcRenderer, shell} = require("electron");
const os = require("os");
const si = require("systeminformation")
let menuIsOpen = false;
getId("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);
} else {
getId("menuIcon").style.transform = "rotate(90deg)";
menuIsOpen = true;
setTimeout(() => {
getId("menu").style.display = "flex";
getId("menu").style.opacity = "1";
}, 150);
}
});
let ffmpeg;
if (os.platform() === "win32") {
ffmpeg = `"${__dirname}\\..\\ffmpeg.exe"`;
} else {
ffmpeg = `"${__dirname}/../ffmpeg"`;
}
const vaapi_device = "/dev/dri/renderD128"
// Checking GPU
si.graphics().then((info) => {
console.log({gpuInfo: info})
const gpuDevices = info.controllers;
gpuDevices.forEach((gpu) => {
// NVIDIA
const gpuName = gpu.vendor.toLowerCase()
const gpuModel = gpu.model.toLowerCase()
if (gpuName.includes("nvidia") || gpuModel.includes("nvidia")) {
document.querySelectorAll(".nvidia_opt").forEach((opt) => {
opt.style.display = "block"
})
} 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"
})
}
} 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"
})
}
}
})
})
/** @type {File[]} */
let files = [];
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();
dropZone.classList.add("dragover");
});
dropZone.addEventListener("dragleave", () => {
dropZone.classList.remove("dragover");
});
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
files = Array.from(e.target.files);
updateSelectedFiles();
});
getId("custom-folder-select").addEventListener("click", (e) => {
ipcRenderer.send("get-directory", "")
})
function updateSelectedFiles() {
const fileList = files
.map((f) => `${f.name} (${formatBytes(f.size)})
`)
.join("\n");
selectedFilesDiv.innerHTML = fileList || "No files selected";
}
// Compression Logic
getId("compress-btn").addEventListener("click", startCompression);
getId("cancel-btn").addEventListener("click", cancelCompression);
async function startCompression() {
if (files.length === 0) return alert("Please select files first!");
const settings = getEncoderSettings();
for (const file of files) {
const itemId =
"f" + Math.random().toFixed(10).toString().slice(2).toString();
currentItemId = itemId;
const outputPath = generateOutputPath(file, settings);
try {
await compressVideo(file, settings, itemId, outputPath);
if (isCancelled) {
isCancelled = false;
} else {
updateProgress("success", "", itemId);
const fileSavedElement = document.createElement("b")
fileSavedElement.textContent = "File saved. Click to open"
fileSavedElement.onclick = () => {
shell.showItemInFolder(outputPath)
}
getId(itemId + "_prog").appendChild(fileSavedElement)
currentItemId = ""
}
} catch (error) {
const errorElement = document.createElement("div")
errorElement.onclick = () => {
ipcRenderer.send("error_dialog", error.message)
}
errorElement.textContent = "Error. Click for details"
updateProgress("error", "", itemId);
getId(itemId + "_prog").appendChild(errorElement)
currentItemId = ""
}
}
}
function cancelCompression() {
activeProcesses.forEach((child) => {
child.stdin.write("q")
isCancelled = true;
});
activeProcesses.clear();
updateProgress("error", "Cancelled", currentItemId);
}
/**
* @param {File} file
*/
function generateOutputPath(file, settings) {
console.log({settings})
const output_extension = settings.extension
const parsed_file = path.parse(file.path)
let outputDir = settings.outputPath || parsed_file.dir
if (output_extension == "unchanged") {
return path.join(outputDir, `${parsed_file.name}${settings.outputSuffix}${parsed_file.ext}`);
}
return path.join(outputDir, `${parsed_file.name}_compressed.${output_extension}`);
}
/**
* @param {File} file
* @param {{ encoder: any; speed: any; videoQuality: any; audioQuality?: any; audioFormat: string, extension: string }} settings
* @param {string} itemId
* @param {string} outputPath
*/
async function compressVideo(file, settings, itemId, outputPath) {
const command = buildFFmpegCommand(file, settings, outputPath);
console.log("Command: " + command)
return new Promise((resolve, reject) => {
const child = exec(command, (error) => {
if (error) reject(error);
else resolve();
});
activeProcesses.add(child);
child.on("exit", (_code) => {
activeProcesses.delete(child)
});
let video_info = {
duration: "",
bitrate: "",
};
createProgressItem(
path.basename(file.path),
"progress",
`Starting...`,
itemId
);
child.stderr.on("data", (data) => {
// console.log(data)
const duration_match = data.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 totalSeconds = timeToSeconds(video_info.duration);
const currentSeconds =
progressTime && progressTime.length > 1
? timeToSeconds(progressTime[1])
: null;
if (currentSeconds && !isCancelled) {
const progress = Math.round(
(currentSeconds / totalSeconds) * 100
);
getId(
itemId + "_prog"
).innerHTML = `