Refactor compressor.js

pull/356/head
aandrew-me 8 months ago
parent 59d1f138e0
commit 88d6045d73

@ -5,111 +5,41 @@ const os = require("os");
const si = require("systeminformation");
const {existsSync} = require("fs");
document.addEventListener("translations-loaded", () => {
window.i18n.translatePage();
});
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);
}
});
const ffmpeg = `"${getFfmpegPath()}"`;
console.log(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";
});
}
}
});
});
const VAAPI_DEVICE = "/dev/dri/renderD128";
const FFMPEG_PATH = `"${getFfmpegPath()}"`;
console.log(`FFmpeg Path: ${FFMPEG_PATH}`);
/** @type {File[]} */
let files = [];
let activeProcesses = new Set();
let currentItemId = "";
let isCancelled = false;
let menuIsOpen = false;
/**
* @param {string} id
*/
function getId(id) {
return document.getElementById(id);
}
(function init() {
setupTheme();
detectHardware();
})();
document.addEventListener("translations-loaded", () => {
window.i18n.translatePage();
});
getId("menuIcon").addEventListener("click", toggleMenu);
getId("preferenceWin").addEventListener("click", () =>
navigateTo("/preferences.html")
);
getId("playlistWin").addEventListener("click", () =>
navigateToWin("/playlist.html")
);
getId("aboutWin").addEventListener("click", () => navigateTo("/about.html"));
getId("historyWin").addEventListener("click", () =>
navigateTo("/history.html")
);
getId("homeWin").addEventListener("click", () => navigateToWin("/index.html"));
// File Handling
const dropZone = document.querySelector(".drop-zone");
const fileInput = getId("fileInput");
const selectedFilesDiv = getId("selected-files");
dropZone.addEventListener("dragover", (e) => {
e.preventDefault();
@ -124,7 +54,6 @@ dropZone.addEventListener("drop", (e) => {
e.preventDefault();
dropZone.classList.remove("dragover");
// @ts-ignore
console.log(e.dataTransfer);
files = Array.from(e.dataTransfer.files);
updateSelectedFiles();
});
@ -135,20 +64,38 @@ fileInput.addEventListener("change", (e) => {
updateSelectedFiles();
});
getId("custom-folder-select").addEventListener("click", (e) => {
getId("compress-btn").addEventListener("click", startCompression);
getId("cancel-btn").addEventListener("click", cancelCompression);
getId("custom-folder-select").addEventListener("click", () => {
ipcRenderer.send("get-directory", "");
});
function updateSelectedFiles() {
const fileList = files
.map((f) => `${f.name} (${formatBytes(f.size)})<br/>`)
.join("\n");
selectedFilesDiv.innerHTML = fileList || "No files selected";
}
getId("output-folder-input").addEventListener("change", (e) => {
const checked = e.target.checked;
const selectBtn = getId("custom-folder-select");
const pathDisplay = getId("custom-folder-path");
// Compression Logic
getId("compress-btn").addEventListener("click", startCompression);
getId("cancel-btn").addEventListener("click", cancelCompression);
if (!checked) {
selectBtn.style.display = "block";
} else {
selectBtn.style.display = "none";
pathDisplay.textContent = "";
pathDisplay.style.display = "none";
}
});
getId("themeToggle").addEventListener("change", () => {
const theme = getId("themeToggle").value;
document.documentElement.setAttribute("theme", theme);
localStorage.setItem("theme", theme);
});
ipcRenderer.on("directory-path", (_event, msg) => {
let customFolderPathItem = getId("custom-folder-path");
customFolderPathItem.textContent = msg;
customFolderPathItem.style.display = "inline";
});
async function startCompression() {
if (files.length === 0) return alert("Please select files first!");
@ -156,8 +103,7 @@ async function startCompression() {
const settings = getEncoderSettings();
for (const file of files) {
const itemId =
"f" + Math.random().toFixed(10).toString().slice(2).toString();
const itemId = "f" + Math.random().toFixed(10).toString().slice(2);
currentItemId = itemId;
const outputPath = generateOutputPath(file, settings);
@ -168,24 +114,10 @@ async function startCompression() {
if (isCancelled) {
isCancelled = false;
} else {
updateProgress("success", "", itemId);
const fileSavedElement = document.createElement("b");
fileSavedElement.textContent = i18n.__("fileSavedClickToOpen");
fileSavedElement.onclick = () => {
ipcRenderer.send("show-file", outputPath);
};
getId(itemId + "_prog").appendChild(fileSavedElement);
currentItemId = "";
handleCompressionSuccess(itemId, outputPath);
}
} catch (error) {
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 = "";
handleCompressionError(itemId, error);
}
}
}
@ -201,36 +133,12 @@ function cancelCompression() {
/**
* @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 {Object} 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) => {
@ -240,14 +148,9 @@ async function compressVideo(file, settings, itemId, outputPath) {
});
activeProcesses.add(child);
child.on("exit", (_code) => {
activeProcesses.delete(child);
});
child.on("exit", () => activeProcesses.delete(child));
let video_info = {
duration: "",
bitrate: "",
};
let video_info = {duration: "", bitrate: ""};
createProgressItem(
path.basename(file.path),
@ -257,22 +160,11 @@ async function compressVideo(file, settings, itemId, outputPath) {
);
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];
// }
if (duration_match) video_info.duration = duration_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])
@ -282,7 +174,6 @@ async function compressVideo(file, settings, itemId, outputPath) {
const progress = Math.round(
(currentSeconds / totalSeconds) * 100
);
getId(
itemId + "_prog"
).innerHTML = `<progress class="progressBarCompress" min=0 max=100 value=${progress}>`;
@ -291,17 +182,31 @@ async function compressVideo(file, settings, itemId, outputPath) {
});
}
/**
* @param {File} file
* @param {{ encoder: string; speed: string; videoQuality: string; audioQuality: string; audioFormat: string }} settings
* @param {string} outputPath
*/
function handleCompressionSuccess(itemId, outputPath) {
updateProgress("success", "", itemId);
const fileSavedElement = document.createElement("b");
fileSavedElement.textContent = i18n.__("fileSavedClickToOpen");
fileSavedElement.onclick = () => ipcRenderer.send("show-file", outputPath);
getId(itemId + "_prog").appendChild(fileSavedElement);
currentItemId = "";
}
function handleCompressionError(itemId, error) {
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 = "";
}
function buildFFmpegCommand(file, settings, outputPath) {
const inputPath = file.path;
console.log("Output path: " + outputPath);
const args = ["-hide_banner", "-y", "-stats", "-i", `"${inputPath}"`];
const vQuality = parseInt(settings.videoQuality).toString();
switch (settings.encoder) {
case "copy":
@ -316,7 +221,7 @@ function buildFFmpegCommand(file, settings, outputPath) {
"-vf",
"format=yuv420p",
"-crf",
parseInt(settings.videoQuality).toString()
vQuality
);
break;
case "x265":
@ -328,11 +233,10 @@ function buildFFmpegCommand(file, settings, outputPath) {
"-preset",
settings.speed,
"-crf",
parseInt(settings.videoQuality).toString()
vQuality
);
break;
// Intel windows
case "qsv":
case "qsv": // Intel Windows
args.push(
"-c:v",
"h264_qsv",
@ -341,36 +245,34 @@ function buildFFmpegCommand(file, settings, outputPath) {
"-preset",
settings.speed,
"-global_quality",
parseInt(settings.videoQuality).toString()
vQuality
);
break;
// Linux amd and intel
case "vaapi":
case "vaapi": // Linux AMD/Intel
args.push(
"-vaapi_device",
vaapi_device,
VAAPI_DEVICE,
"-vf",
"format=nv12,hwupload",
"-c:v",
"h264_vaapi",
"-qp",
parseInt(settings.videoQuality).toString()
vQuality
);
break;
case "hevc_vaapi":
args.push(
"-vaapi_device",
vaapi_device,
VAAPI_DEVICE,
"-vf",
"format=nv12,hwupload",
"-c:v",
"hevc_vaapi",
"-qp",
parseInt(settings.videoQuality).toString()
vQuality
);
break;
// Nvidia windows and linux
case "nvenc":
case "nvenc": // Nvidia
args.push(
"-c:v",
"h264_nvenc",
@ -381,135 +283,95 @@ function buildFFmpegCommand(file, settings, outputPath) {
"-rc",
"vbr",
"-cq",
parseInt(settings.videoQuality).toString()
vQuality
);
break;
// Amd windows
case "hevc_amf":
let amf_hevc_quality = "balanced";
if (settings.speed == "slow") {
amf_hevc_quality = "quality";
} else if (settings.speed == "fast") {
amf_hevc_quality = "speed";
}
case "hevc_amf": // AMD Windows HEVC
const amfHevcQuality = getAmfQuality(settings.speed);
args.push(
"-c:v",
"hevc_amf",
"-vf",
"format=yuv420p",
"-quality",
amf_hevc_quality,
amfHevcQuality,
"-rc",
"cqp",
"-qp_i",
parseInt(settings.videoQuality).toString(),
vQuality,
"-qp_p",
parseInt(settings.videoQuality).toString()
vQuality
);
break;
case "amf":
let amf_quality = "balanced";
if (settings.speed == "slow") {
amf_quality = "quality";
} else if (settings.speed == "fast") {
amf_quality = "speed";
}
case "amf": // AMD Windows H264
const amfQuality = getAmfQuality(settings.speed);
args.push(
"-c:v",
"h264_amf",
"-vf",
"format=yuv420p",
"-quality",
amf_quality,
amfQuality,
"-rc",
"cqp",
"-qp_i",
parseInt(settings.videoQuality).toString(),
vQuality,
"-qp_p",
parseInt(settings.videoQuality).toString(),
vQuality,
"-qp_b",
parseInt(settings.videoQuality).toString()
vQuality
);
break;
case "videotoolbox":
case "videotoolbox": // macOS
args.push(
"-c:v",
"-vf",
"format=yuv420p",
"h264_videotoolbox",
"-q:v",
parseInt(settings.videoQuality).toString()
vQuality
);
break;
}
// args.push("-vf", "scale=trunc(iw*1/2)*2:trunc(ih*1/2)*2,format=yuv420p");
args.push("-c:a", settings.audioFormat, `"${outputPath}"`);
return `${ffmpeg} ${args.join(" ")}`;
return `${FFMPEG_PATH} ${args.join(" ")}`;
}
/**
*
* @returns {{ encoder: string; speed: string; videoQuality: string; audioQuality?: string; audioFormat: string, extension: string, outputPath:string }} settings
*/
function getEncoderSettings() {
return {
// @ts-ignore
encoder: getId("encoder").value,
// @ts-ignore
speed: getId("compression-speed").value,
// @ts-ignore
videoQuality: getId("video-quality").value,
// @ts-ignore
audioFormat: getId("audio-format").value,
// @ts-ignore
extension: getId("file_extension").value,
outputPath: getId("custom-folder-path").textContent,
// @ts-ignore
outputSuffix: getId("output-suffix").value,
};
}
/**
* @param {string | number} speed
*/
function getNvencPreset(speed) {
const presets = {fast: "p3", medium: "p4", slow: "p5"};
return presets[speed] || "p4";
}
/**
* @param {string} status
* @param {string} data
* @param {string} itemId
*/
function updateProgress(status, data, itemId) {
if (status == "success" || status == "error") {
const item = getId("itemId");
function getAmfQuality(speed) {
if (speed === "slow") return "quality";
if (speed === "fast") return "speed";
return "balanced";
}
if (item) {
getId(itemId).classList.remove("progress");
getId(itemId).classList.add(status);
}
}
function getId(id) {
return document.getElementById(id);
}
if (itemId) {
getId(itemId + "_prog").textContent = data;
}
function updateSelectedFiles() {
const fileList = files
.map((f) => `${f.name} (${formatBytes(f.size)})<br/>`)
.join("\n");
getId("selected-files").innerHTML = fileList || "No files selected";
}
/**
* @param {string} filename
* @param {string} status
* @param {string} data
* @param {string} itemId
*/
function createProgressItem(filename, status, data, itemId) {
const statusElement = getId("compression-status");
const newStatus = document.createElement("div");
@ -523,9 +385,104 @@ function createProgressItem(filename, status, data, itemId) {
statusElement.append(newStatus);
}
/**
* @param {any} bytes
*/
function updateProgress(status, data, itemId) {
if (status === "success" || status === "error") {
const item = getId(itemId);
if (item) {
item.classList.remove("progress");
item.classList.add(status);
}
}
if (itemId) {
getId(itemId + "_prog").textContent = data;
}
}
function toggleMenu() {
if (menuIsOpen) {
closeMenu();
} else {
getId("menuIcon").style.transform = "rotate(90deg)";
menuIsOpen = true;
setTimeout(() => {
getId("menu").style.display = "flex";
getId("menu").style.opacity = "1";
}, 150);
}
}
function closeMenu() {
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);
}
function navigateTo(url) {
closeMenu();
ipcRenderer.send("load-page", __dirname + url);
}
function navigateToWin(url) {
closeMenu();
ipcRenderer.send("load-win", __dirname + url);
}
function setupTheme() {
const storageTheme = localStorage.getItem("theme");
if (storageTheme) {
document.documentElement.setAttribute("theme", storageTheme);
getId("themeToggle").value = storageTheme;
}
}
function detectHardware() {
si.graphics().then((info) => {
console.log({gpuInfo: info});
const gpuDevices = info.controllers;
const platform = os.platform();
gpuDevices.forEach((gpu) => {
const gpuName = gpu.vendor.toLowerCase();
const gpuModel = gpu.model.toLowerCase();
const isNvidia =
gpuName.includes("nvidia") || gpuModel.includes("nvidia");
const isAMD =
gpuName.includes("advanced micro devices") ||
gpuModel.includes("amd");
const isIntel = gpuName.includes("intel");
if (isNvidia) {
showOptions(".nvidia_opt");
} else if (isAMD) {
if (platform === "win32") showOptions(".amf_opt");
else showOptions(".vaapi_opt");
} else if (isIntel) {
if (platform === "win32") showOptions(".qsv_opt");
else if (platform !== "darwin") showOptions(".vaapi_opt");
} else {
if (platform === "darwin") showOptions(".videotoolbox_opt");
}
});
});
}
function showOptions(selector) {
document.querySelectorAll(selector).forEach((opt) => {
opt.style.display = "block";
});
}
function formatBytes(bytes) {
const units = ["B", "KB", "MB", "GB"];
let size = bytes;
@ -537,18 +494,30 @@ function formatBytes(bytes) {
return `${size.toFixed(2)} ${units[unitIndex]}`;
}
/**
* @param {string} timeStr
*/
function timeToSeconds(timeStr) {
if (!timeStr) {
return 0;
}
if (!timeStr) return 0;
const [hh, mm, ss] = timeStr.split(":").map(parseFloat);
return hh * 3600 + mm * 60 + ss;
}
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}`
);
}
function getFfmpegPath() {
if (
process.env.YTDOWNLOADER_FFMPEG_PATH &&
@ -572,79 +541,3 @@ function getFfmpegPath() {
return path.join(__dirname, "..", "ffmpeg", "bin", "ffmpeg");
}
}
getId("themeToggle").addEventListener("change", () => {
document.documentElement.setAttribute("theme", getId("themeToggle").value);
localStorage.setItem("theme", getId("themeToggle").value);
});
getId("output-folder-input").addEventListener("change", (e) => {
const checked = e.target.checked;
if (!checked) {
getId("custom-folder-select").style.display = "block";
} else {
getId("custom-folder-select").style.display = "none";
getId("custom-folder-path").textContent = "";
getId("custom-folder-path").style.display = "none";
}
});
const storageTheme = localStorage.getItem("theme");
if (storageTheme) {
document.documentElement.setAttribute("theme", storageTheme);
getId("themeToggle").value = storageTheme;
}
ipcRenderer.on("directory-path", (_event, msg) => {
let customFolderPathItem = getId("custom-folder-path");
customFolderPathItem.textContent = msg;
customFolderPathItem.style.display = "inline";
});
function closeMenu() {
getId("menuIcon").style.transform = "rotate(0deg)";
let count = 0;
let opacity = 1;
const fade = setInterval(() => {
if (count >= 10) {
clearInterval(fade);
} else {
opacity -= 0.1;
getId("menu").style.opacity = String(opacity);
count++;
}
}, 50);
}
// Menu
getId("preferenceWin").addEventListener("click", () => {
closeMenu();
menuIsOpen = false;
ipcRenderer.send("load-page", __dirname + "/preferences.html");
});
getId("playlistWin").addEventListener("click", () => {
closeMenu();
menuIsOpen = false;
ipcRenderer.send("load-win", __dirname + "/playlist.html");
});
getId("aboutWin").addEventListener("click", () => {
closeMenu();
menuIsOpen = false;
ipcRenderer.send("load-page", __dirname + "/about.html");
});
getId("historyWin").addEventListener("click", () => {
closeMenu();
menuIsOpen = false;
ipcRenderer.send("load-page", __dirname + "/history.html");
});
getId("homeWin").addEventListener("click", () => {
closeMenu();
menuIsOpen = false;
ipcRenderer.send("load-win", __dirname + "/index.html");
});
Loading…
Cancel
Save