Video compressor initial support
This commit is contained in:
parent
b1dc6dff0b
commit
575e964958
@ -304,4 +304,4 @@ body::-webkit-scrollbar-thumb {
|
|||||||
|
|
||||||
#proxyTxt:invalid {
|
#proxyTxt:invalid {
|
||||||
border: 2px solid var(--redBtn);
|
border: 2px solid var(--redBtn);
|
||||||
}
|
}
|
@ -587,7 +587,7 @@ button {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#extractBtn:active {
|
#extractBtn:active, .blueBtn:active {
|
||||||
top: 4px;
|
top: 4px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
border: none;
|
border: none;
|
||||||
@ -723,3 +723,113 @@ body::-webkit-scrollbar-thumb {
|
|||||||
width: 80%;
|
width: 80%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Compressor styles */
|
||||||
|
#compressor_body {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: var(--box-main);
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-zone {
|
||||||
|
border: 2px dashed #ccc;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
transition: border-color 0.3s ease;
|
||||||
|
background: var(--box-toggle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-zone:hover {
|
||||||
|
border-color: #2196F3;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-zone.dragover {
|
||||||
|
border-color: #2196F3;
|
||||||
|
background-color: #f5fbff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-group {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compress-label {
|
||||||
|
font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#compression-status {
|
||||||
|
margin-top: 2rem;
|
||||||
|
border-top: 1px solid var(--item-bg);
|
||||||
|
padding-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-item {
|
||||||
|
padding: 15px;
|
||||||
|
margin: 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-direction: row;
|
||||||
|
background-color: var(--item-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-item.success {
|
||||||
|
color: var(--greenBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-item.error {
|
||||||
|
color: var(--redBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filename {
|
||||||
|
flex: 1;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
min-width: 80px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
font-size: 0.9em;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fileinput-btn {
|
||||||
|
background: var(--blueBtn);
|
||||||
|
color: white;
|
||||||
|
padding: 10px;
|
||||||
|
display: block;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin: 5px 0 10px 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progressBarCompress {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nvidia_opt, .amf_opt, .qsv_opt, .vaapi_opt, .videotoolbox_opt {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* End */
|
153
html/compressor.html
Normal file
153
html/compressor.html
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" theme="dark">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Video Compressor</title>
|
||||||
|
<link rel="stylesheet" href="../assets/css/extra.css">
|
||||||
|
<link rel="stylesheet" href="../assets/css/index.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="compressor_body">
|
||||||
|
<!-- Menu icon -->
|
||||||
|
<img src="../assets/images/menu.png" alt="menu" id="menuIcon">
|
||||||
|
|
||||||
|
<!-- Menu -->
|
||||||
|
<div id="menu">
|
||||||
|
<a id="homeWin" class="menuItem">Homepage</a>
|
||||||
|
<a id="preferenceWin" class="menuItem">Preferences</a>
|
||||||
|
<a id="aboutWin" class="menuItem">About</a>
|
||||||
|
<span id="themeTxt" class="menuItem">Theme:</span>
|
||||||
|
<select name="themeToggle" id="themeToggle">
|
||||||
|
<option id="lightTxt" value="light">Light</option>
|
||||||
|
<option id="darkTxt" value="dark">Dark</option>
|
||||||
|
<option id="frappeTxt" value="frappe">Frappé</option>
|
||||||
|
<option id="onedarkTxt" value="onedark">One dark</option>
|
||||||
|
<option id="matrixTxt" value="matrix">Matrix</option>
|
||||||
|
<option id="githubTxt" value="github">Github</option>
|
||||||
|
<option id="latteTxt" value="latte">Latte</option>
|
||||||
|
<option id="solarizedDarkTxt" value="solarized-dark">Solarized Dark</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h1>Video Compressor</h1>
|
||||||
|
|
||||||
|
<div class="drop-zone">
|
||||||
|
<p>Drag and drop files here or</p>
|
||||||
|
<input type="file" id="fileInput" multiple hidden>
|
||||||
|
<label for="fileInput" class="fileinput-btn">Choose Files</label>
|
||||||
|
<div id="selected-files">No files selected</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-group">
|
||||||
|
<label class="compress-label" for="extension">Video format</label>
|
||||||
|
<select id="file_extension">
|
||||||
|
<option value="unchanged">Unchanged</option>
|
||||||
|
<option value="mp4">mp4</option>
|
||||||
|
<option value="mkv">mkv</option>
|
||||||
|
<option value="webm">webm</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-group">
|
||||||
|
<label class="compress-label" for="encoder">Video Encoder</label>
|
||||||
|
<select id="encoder">
|
||||||
|
<option value="x264">x264</option>
|
||||||
|
<option value="x265">x265</option>
|
||||||
|
|
||||||
|
<option class="qsv_opt" value="qsv">x264 (Intel QSV Hardware Acceleration)</option>
|
||||||
|
<option class="qsv_opt" value="hevc_qsv">x264 (Intel QSV Hardware Acceleration)</option>
|
||||||
|
|
||||||
|
<option class="amf_opt" value="amf">x264 (AMD Hardware Acceleration)</option>
|
||||||
|
<option class="amf_opt" value="hevc_amf">x265 (AMD Hardware Acceleration)</option>
|
||||||
|
|
||||||
|
<option class="nvidia_opt" value="nvenc">x264 (NVIDIA Hardware Acceleration)</option>
|
||||||
|
<option class="nvidia_opt" value="hevc_nvenc">x265 (NVIDIA Hardware Acceleration)</option>
|
||||||
|
|
||||||
|
<option class="vaapi_opt" value="vaapi">x264 (VA-API Hardware Acceleration)</option>
|
||||||
|
<option class="vaapi_opt" value="hevc_vaapi">x265 (VA-API Hardware Acceleration)</option>
|
||||||
|
|
||||||
|
<option class="videotoolbox_opt" value="videotoolbox">x264 (VideoToolbox Hardware Acceleration)</option>
|
||||||
|
<option class="videotoolbox_opt" value="hevc_videotoolbox">x265 (VideoToolbox Hardware Acceleration)</option>
|
||||||
|
|
||||||
|
|
||||||
|
<option value="copy">Copy video</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-group">
|
||||||
|
<label class="compress-label" for="compression-speed">Compression Speed</label>
|
||||||
|
<select id="compression-speed">
|
||||||
|
<option value="fast">Fast</option>
|
||||||
|
<option selected value="medium">Medium</option>
|
||||||
|
<option value="slow">Slow</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-group">
|
||||||
|
<label class="compress-label" for="video-quality">Video Quality
|
||||||
|
</label>
|
||||||
|
<select id="video-quality">
|
||||||
|
<option value="18">18 (Best quality)</option>
|
||||||
|
<option value="19">19</option>
|
||||||
|
<option value="20">20</option>
|
||||||
|
<option value="21">21</option>
|
||||||
|
<option value="22">22</option>
|
||||||
|
<option selected value="23">23 (Medium size, good quality)</option>
|
||||||
|
<option value="24">24</option>
|
||||||
|
<option value="25">25</option>
|
||||||
|
<option value="26">26</option>
|
||||||
|
<option value="27">27</option>
|
||||||
|
<option value="28">28 (Smaller file size, decent quality)</option>
|
||||||
|
<option value="29">29</option>
|
||||||
|
<option value="30">30</option>
|
||||||
|
<option value="31">31</option>
|
||||||
|
<option value="32">32</option>
|
||||||
|
<option value="33">33</option>
|
||||||
|
<option value="34">34</option>
|
||||||
|
<option value="35">35</option>
|
||||||
|
<option value="36">36</option>
|
||||||
|
<option value="37">37</option>
|
||||||
|
<option value="38">38</option>
|
||||||
|
<option value="39">39</option>
|
||||||
|
<option value="40">40</option>
|
||||||
|
<option value="41">41</option>
|
||||||
|
<option value="42">42</option>
|
||||||
|
<option value="43">43</option>
|
||||||
|
<option value="44">44</option>
|
||||||
|
<option value="45">45</option>
|
||||||
|
<option value="46">46</option>
|
||||||
|
<option value="47">47</option>
|
||||||
|
<option value="48">48</option>
|
||||||
|
<option value="49">49</option>
|
||||||
|
<option value="50">50</option>
|
||||||
|
<option value="51">51 (Worst quality)</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-group">
|
||||||
|
<label class="compress-label" for="audio-format">Audio Format</label>
|
||||||
|
<select id="audio-format">
|
||||||
|
<option value="copy">Unchanged</option>
|
||||||
|
<option value="aac">aac</option>
|
||||||
|
<option value="mp3">mp3</option>
|
||||||
|
<option value="opus">opus</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<button class="blueBtn" id="compress-btn">Start Compression</button>
|
||||||
|
<button class="advancedToggle" id="cancel-btn">Cancel (not finished)</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="compression-status"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="../src/compressor.js">
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
@ -47,6 +47,7 @@
|
|||||||
<a id="playlistWin" class="menuItem">Download Playlist</a>
|
<a id="playlistWin" class="menuItem">Download Playlist</a>
|
||||||
<!-- <a id="newPlaylistWin" class="menuItem">New Playlist</a> -->
|
<!-- <a id="newPlaylistWin" class="menuItem">New Playlist</a> -->
|
||||||
<a id="preferenceWin" class="menuItem">Preferences</a>
|
<a id="preferenceWin" class="menuItem">Preferences</a>
|
||||||
|
<a id="compressorWin" class="menuItem">Compressor</a>
|
||||||
<a id="aboutWin" class="menuItem">About</a>
|
<a id="aboutWin" class="menuItem">About</a>
|
||||||
<span id="themeTxt" class="menuItem">Theme:</span>
|
<span id="themeTxt" class="menuItem">Theme:</span>
|
||||||
<select name="themeToggle" id="themeToggle">
|
<select name="themeToggle" id="themeToggle">
|
||||||
|
35
package-lock.json
generated
35
package-lock.json
generated
@ -10,10 +10,11 @@
|
|||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"electron-updater": "^6.3.9",
|
"electron-updater": "^6.3.9",
|
||||||
|
"systeminformation": "^5.25.11",
|
||||||
"yt-dlp-wrap-plus": "^2.3.20"
|
"yt-dlp-wrap-plus": "^2.3.20"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "^34.0.2",
|
"electron": "^30.5.1",
|
||||||
"electron-builder": "^25.1.8",
|
"electron-builder": "^25.1.8",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
}
|
}
|
||||||
@ -2114,9 +2115,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron": {
|
"node_modules/electron": {
|
||||||
"version": "34.0.2",
|
"version": "30.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/electron/-/electron-34.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/electron/-/electron-30.5.1.tgz",
|
||||||
"integrity": "sha512-u3F+DSUlg9NaGS+9qnYmSRN8VjAnc3LJDDk1ye1uISJnh4gjG76y3681qLowsPMx4obvCP2eBINnmbLo0yT5WA==",
|
"integrity": "sha512-AhL7+mZ8Lg14iaNfoYTkXQ2qee8mmsQyllKdqxlpv/zrKgfxz6jNVtcRRbQtLxtF8yzcImWdfTQROpYiPumdbw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -4663,6 +4664,32 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/systeminformation": {
|
||||||
|
"version": "5.25.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.25.11.tgz",
|
||||||
|
"integrity": "sha512-jI01fn/t47rrLTQB0FTlMCC+5dYx8o0RRF+R4BPiUNsvg5OdY0s9DKMFmJGrx5SwMZQ4cag0Gl6v8oycso9b/g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"os": [
|
||||||
|
"darwin",
|
||||||
|
"linux",
|
||||||
|
"win32",
|
||||||
|
"freebsd",
|
||||||
|
"openbsd",
|
||||||
|
"netbsd",
|
||||||
|
"sunos",
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"bin": {
|
||||||
|
"systeminformation": "lib/cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "Buy me a coffee",
|
||||||
|
"url": "https://www.buymeacoffee.com/systeminfo"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tar": {
|
"node_modules/tar": {
|
||||||
"version": "6.2.1",
|
"version": "6.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"electron-updater": "^6.3.9",
|
"electron-updater": "^6.3.9",
|
||||||
|
"systeminformation": "^5.25.11",
|
||||||
"yt-dlp-wrap-plus": "^2.3.20"
|
"yt-dlp-wrap-plus": "^2.3.20"
|
||||||
},
|
},
|
||||||
"name": "ytdownloader",
|
"name": "ytdownloader",
|
||||||
@ -33,7 +34,7 @@
|
|||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"description": "Download videos and audios from YouTube and many other sites",
|
"description": "Download videos and audios from YouTube and many other sites",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "^34.0.2",
|
"electron": "^30.5.1",
|
||||||
"electron-builder": "^25.1.8",
|
"electron-builder": "^25.1.8",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
},
|
},
|
||||||
|
473
src/compressor.js
Normal file
473
src/compressor.js
Normal file
@ -0,0 +1,473 @@
|
|||||||
|
const {exec} = require("child_process");
|
||||||
|
const path = require("path");
|
||||||
|
const {ipcRenderer} = require("electron");
|
||||||
|
const os = require("os");
|
||||||
|
const si = require("systeminformation")
|
||||||
|
|
||||||
|
let ffmpeg;
|
||||||
|
if (os.platform() === "win32") {
|
||||||
|
ffmpeg = `"${__dirname}\\..\\ffmpeg.exe"`;
|
||||||
|
} else {
|
||||||
|
ffmpeg = `"${__dirname}/../ffmpeg"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const vaapi_device = "/dev/dri/renderD128"
|
||||||
|
|
||||||
|
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 = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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();
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateSelectedFiles() {
|
||||||
|
const fileList = files
|
||||||
|
.map((f) => `${f.name} (${formatBytes(f.size)})`)
|
||||||
|
.join("\n");
|
||||||
|
selectedFilesDiv.textContent = 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;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await compressVideo(file, settings, itemId);
|
||||||
|
updateProgress("success", "Successfully compressed", itemId);
|
||||||
|
currentItemId = ""
|
||||||
|
} catch (error) {
|
||||||
|
updateProgress("error", error.message, itemId);
|
||||||
|
currentItemId = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelCompression() {
|
||||||
|
activeProcesses.forEach((child) => child.kill("SIGTERM"));
|
||||||
|
activeProcesses.clear();
|
||||||
|
updateProgress("", "cancelled", currentItemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {File} file
|
||||||
|
*/
|
||||||
|
function generateOutputPath(file, settings) {
|
||||||
|
const output_extension = settings.extension
|
||||||
|
const parsed_file = path.parse(file.path)
|
||||||
|
|
||||||
|
|
||||||
|
if (output_extension == "unchanged") {
|
||||||
|
return path.join(parsed_file.dir, `${parsed_file.name}_compressed${parsed_file.ext}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.join(parsed_file.dir, `${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
|
||||||
|
*/
|
||||||
|
async function compressVideo(file, settings, itemId) {
|
||||||
|
const command = buildFFmpegCommand(file, settings);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const child = exec(command, (error) => {
|
||||||
|
if (error) reject(error);
|
||||||
|
else resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
activeProcesses.add(child);
|
||||||
|
child.on("exit", () => activeProcesses.delete(child));
|
||||||
|
|
||||||
|
let video_info = {
|
||||||
|
duration: "",
|
||||||
|
bitrate: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
createProgressItem(
|
||||||
|
path.basename(file.path),
|
||||||
|
"progress",
|
||||||
|
`Starting...`,
|
||||||
|
itemId
|
||||||
|
);
|
||||||
|
|
||||||
|
child.stderr.on("data", (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) {
|
||||||
|
const progress = Math.round(
|
||||||
|
(currentSeconds / totalSeconds) * 100
|
||||||
|
);
|
||||||
|
|
||||||
|
getId(
|
||||||
|
itemId + "_prog"
|
||||||
|
).innerHTML = `<progress class="progressBarCompress" min=0 max=100 value=${progress}>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {File} file
|
||||||
|
* @param {{ encoder: string; speed: string; videoQuality: string; audioQuality: string; audioFormat: string }} settings
|
||||||
|
*/
|
||||||
|
function buildFFmpegCommand(file, settings) {
|
||||||
|
const inputPath = file.path;
|
||||||
|
|
||||||
|
const outputPath = generateOutputPath(file, settings);
|
||||||
|
|
||||||
|
console.log("Output path: " + outputPath)
|
||||||
|
|
||||||
|
const args = ["-y", "-stats", "-i", `"${inputPath}"`];
|
||||||
|
|
||||||
|
switch (settings.encoder) {
|
||||||
|
case "copy":
|
||||||
|
args.push("-c:v", "copy");
|
||||||
|
break;
|
||||||
|
case "x264":
|
||||||
|
args.push(
|
||||||
|
"-c:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset",
|
||||||
|
settings.speed,
|
||||||
|
"-crf",
|
||||||
|
parseInt(settings.videoQuality).toString()
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "x265":
|
||||||
|
args.push(
|
||||||
|
"-c:v",
|
||||||
|
"libx265",
|
||||||
|
"-preset",
|
||||||
|
settings.speed,
|
||||||
|
"-crf",
|
||||||
|
parseInt(settings.videoQuality).toString()
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
// Intel windows
|
||||||
|
case "qsv":
|
||||||
|
args.push(
|
||||||
|
"-c:v",
|
||||||
|
"h264_qsv",
|
||||||
|
"-preset",
|
||||||
|
settings.speed,
|
||||||
|
"-global_quality",
|
||||||
|
parseInt(settings.videoQuality).toString()
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
// Linux amd and intel
|
||||||
|
case "vaapi":
|
||||||
|
args.push(
|
||||||
|
"-vaapi_device",
|
||||||
|
vaapi_device,
|
||||||
|
"-vf",
|
||||||
|
"format=nv12,hwupload",
|
||||||
|
"-c:v",
|
||||||
|
"h264_vaapi",
|
||||||
|
"-qp",
|
||||||
|
parseInt(settings.videoQuality).toString()
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "hevc_vaapi":
|
||||||
|
args.push(
|
||||||
|
"-vaapi_device",
|
||||||
|
vaapi_device,
|
||||||
|
"-vf",
|
||||||
|
"format=nv12,hwupload",
|
||||||
|
"-c:v",
|
||||||
|
"hevc_vaapi",
|
||||||
|
"-qp",
|
||||||
|
parseInt(settings.videoQuality).toString()
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
// Nvidia windows and linux
|
||||||
|
case "nvenc":
|
||||||
|
args.push(
|
||||||
|
"-c:v",
|
||||||
|
"h264_nvenc",
|
||||||
|
"-preset",
|
||||||
|
getNvencPreset(settings.speed),
|
||||||
|
"-rc",
|
||||||
|
"vbr",
|
||||||
|
"-cq",
|
||||||
|
parseInt(settings.videoQuality).toString()
|
||||||
|
);
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push(
|
||||||
|
"-c:v",
|
||||||
|
"hevc_amf",
|
||||||
|
"-quality",
|
||||||
|
amf_hevc_quality,
|
||||||
|
"-rc",
|
||||||
|
"cqp",
|
||||||
|
"-qp_i",
|
||||||
|
parseInt(settings.videoQuality).toString(),
|
||||||
|
"-qp_i",
|
||||||
|
parseInt(settings.videoQuality).toString()
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "amf":
|
||||||
|
let amf_quality = "balanced";
|
||||||
|
|
||||||
|
if (settings.speed == "slow") {
|
||||||
|
amf_quality = "quality";
|
||||||
|
} else if (settings.speed == "fast") {
|
||||||
|
amf_quality = "speed";
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push(
|
||||||
|
"-c:v",
|
||||||
|
"h264_amf",
|
||||||
|
"-quality",
|
||||||
|
amf_quality,
|
||||||
|
"-rc",
|
||||||
|
"cqp",
|
||||||
|
"-qp_i",
|
||||||
|
parseInt(settings.videoQuality).toString(),
|
||||||
|
"-qp_i",
|
||||||
|
parseInt(settings.videoQuality).toString()
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "videotoolbox":
|
||||||
|
args.push(
|
||||||
|
"-c:v",
|
||||||
|
"h264_videotoolbox",
|
||||||
|
"-q:v",
|
||||||
|
parseInt(settings.videoQuality).toString()
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push("-c:a", settings.audioFormat, `"${outputPath}"`);
|
||||||
|
|
||||||
|
return `${ffmpeg} ${args.join(" ")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {{ encoder: string; speed: string; videoQuality: string; audioQuality?: string; audioFormat: string, extension: 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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") {
|
||||||
|
getId(itemId).classList.remove("progress");
|
||||||
|
getId(itemId).classList.add(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
getId(itemId + "_prog").textContent = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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");
|
||||||
|
newStatus.id = itemId;
|
||||||
|
newStatus.className = `status-item ${status}`;
|
||||||
|
newStatus.innerHTML = `
|
||||||
|
<div class="filename">${filename}</div>
|
||||||
|
<div id="${itemId + "_prog"}" class="itemProgress">${data}</div>
|
||||||
|
`;
|
||||||
|
statusElement.prepend(newStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} bytes
|
||||||
|
*/
|
||||||
|
function formatBytes(bytes) {
|
||||||
|
const units = ["B", "KB", "MB", "GB"];
|
||||||
|
let size = bytes;
|
||||||
|
let unitIndex = 0;
|
||||||
|
while (size >= 1024 && unitIndex < units.length - 1) {
|
||||||
|
size /= 1024;
|
||||||
|
unitIndex++;
|
||||||
|
}
|
||||||
|
return `${size.toFixed(2)} ${units[unitIndex]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} timeStr
|
||||||
|
*/
|
||||||
|
function timeToSeconds(timeStr) {
|
||||||
|
if (!timeStr) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [hh, mm, ss] = timeStr.split(":").map(parseFloat);
|
||||||
|
return hh * 3600 + mm * 60 + ss;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Menu
|
||||||
|
getId("preferenceWin").addEventListener("click", () => {
|
||||||
|
closeMenu();
|
||||||
|
ipcRenderer.send("load-page", __dirname + "/preferences.html");
|
||||||
|
});
|
||||||
|
|
||||||
|
getId("aboutWin").addEventListener("click", () => {
|
||||||
|
closeMenu();
|
||||||
|
ipcRenderer.send("load-page", __dirname + "/about.html");
|
||||||
|
});
|
||||||
|
getId("homeWin").addEventListener("click", () => {
|
||||||
|
closeMenu();
|
||||||
|
ipcRenderer.send("load-win", __dirname + "/index.html");
|
||||||
|
});
|
||||||
|
|
||||||
|
getId("themeToggle").addEventListener("change", () => {
|
||||||
|
document.documentElement.setAttribute("theme", getId("themeToggle").value);
|
||||||
|
localStorage.setItem("theme", getId("themeToggle").value);
|
||||||
|
});
|
||||||
|
|
||||||
|
const storageTheme = localStorage.getItem("theme");
|
||||||
|
if (storageTheme) {
|
||||||
|
document.documentElement.setAttribute("theme", storageTheme);
|
||||||
|
getId("themeToggle").value = storageTheme;
|
||||||
|
}
|
@ -1229,7 +1229,8 @@ function download(
|
|||||||
} else {
|
} else {
|
||||||
getId(randomId + "speed").textContent = `${i18n.__("Speed")}: ${
|
getId(randomId + "speed").textContent = `${i18n.__("Speed")}: ${
|
||||||
progress.currentSpeed || 0
|
progress.currentSpeed || 0
|
||||||
}`; ipcRenderer.send("progress", progress.percent)
|
}`;
|
||||||
|
ipcRenderer.send("progress", progress.percent)
|
||||||
|
|
||||||
getId(
|
getId(
|
||||||
randomId + "prog"
|
randomId + "prog"
|
||||||
@ -1428,6 +1429,11 @@ getId("playlistWin").addEventListener("click", () => {
|
|||||||
closeMenu();
|
closeMenu();
|
||||||
ipcRenderer.send("load-win", __dirname + "/playlist.html");
|
ipcRenderer.send("load-win", __dirname + "/playlist.html");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
getId("compressorWin").addEventListener("click", () => {
|
||||||
|
closeMenu();
|
||||||
|
ipcRenderer.send("load-win", __dirname + "/compressor.html");
|
||||||
|
});
|
||||||
// getId("newPlaylistWin").addEventListener("click", () => {
|
// getId("newPlaylistWin").addEventListener("click", () => {
|
||||||
// closeMenu();
|
// closeMenu();
|
||||||
// ipcRenderer.send("load-win", __dirname + "/playlist_new.html");
|
// ipcRenderer.send("load-win", __dirname + "/playlist_new.html");
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"checkJs": true,
|
"checkJs": true,
|
||||||
"lib": [
|
"lib": [
|
||||||
"ES2021.String"
|
"ES2021.String",
|
||||||
|
"DOM",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user