diff --git a/src/compressor.js b/src/compressor.js index fdb31c2..f4bac45 100644 --- a/src/compressor.js +++ b/src/compressor.js @@ -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)})
`) - .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 = ``; @@ -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)})
`) + .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"); -}); \ No newline at end of file