diff --git a/.gitignore b/.gitignore
index f81510a..3407ba8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
node_modules
-dist
ffmpeg2pass-0.log
ffmpeg2pass-0.log.mbtree
+video-compressor-build*
+
diff --git a/README.md b/README.md
index b19ce46..0e03927 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-## Big Slime Video Compressor
+## Slugnasty Video Compressor
A beautiful, simple video compressor.
@@ -12,8 +12,8 @@ A beautiful, simple video compressor.
### Preview
-
+
### Love this app?
-[](https://ko-fi.com/bigslime)
+[](https://ko-fi.com/slugnasty)
diff --git a/dist/ffmpeg.exe b/dist/ffmpeg.exe
new file mode 100644
index 0000000..925848e
Binary files /dev/null and b/dist/ffmpeg.exe differ
diff --git a/dist/ffprobe.exe b/dist/ffprobe.exe
new file mode 100644
index 0000000..8df6ad7
Binary files /dev/null and b/dist/ffprobe.exe differ
diff --git a/dist/main.js b/dist/main.js
new file mode 100644
index 0000000..557362b
--- /dev/null
+++ b/dist/main.js
@@ -0,0 +1,141 @@
+"use strict";
+var __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.compressQueue = exports.mainWindow = void 0;
+const electron_1 = require("electron");
+const utils_1 = require("./utils");
+const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg"));
+let videoData = [];
+let currentIndex = 0;
+let currentProgress = 1;
+let totalProgress = 1;
+let cmd = null;
+electron_1.app.on("ready", () => {
+ exports.mainWindow = new electron_1.BrowserWindow({
+ width: 350,
+ height: 500,
+ webPreferences: {
+ contextIsolation: false,
+ nodeIntegration: true,
+ preload: __dirname + "/preload.js",
+ },
+ show: false,
+ resizable: false,
+ autoHideMenuBar: true,
+ });
+ exports.mainWindow.loadFile("./index.html");
+ // mainWindow.webContents.openDevTools();
+ exports.mainWindow.on("ready-to-show", () => {
+ exports.mainWindow.show();
+ (0, utils_1.getFFmpeg)();
+ });
+ console.log("Window ready.");
+});
+electron_1.app.on("window-all-closed", () => {
+ if (process.platform !== "darwin") {
+ killFFmpeg();
+ electron_1.app.quit();
+ }
+});
+electron_1.ipcMain.on("droppedVideos", (event, vids) => {
+ videoData = (0, utils_1.getVideoData)(vids);
+});
+electron_1.ipcMain.on("requestCompress", (event, removeAudio, h265, minBitrate, targetFileSize) => {
+ currentIndex = 0;
+ currentProgress = 0;
+ compressQueue(exports.mainWindow, videoData, removeAudio, h265, minBitrate, targetFileSize)
+ .then(() => {
+ event.reply("compressionComplete");
+ new electron_1.Notification({ title: "Compression complete!", body: "Your new videos are located in the same folder." }).show();
+ })
+ .catch((err) => {
+ event.reply("compressionError", err);
+ new electron_1.Notification({ title: "Error!", body: "There was an error during compression." }).show();
+ });
+});
+electron_1.ipcMain.on("requestAbort", (event) => {
+ killFFmpeg();
+});
+function compressQueue(window, videoData, removeAudio, h265, minBitrate, targetFileSize) {
+ return new Promise((resolve, reject) => {
+ window.webContents.send("compressionStart");
+ totalProgress = videoData.length * 2;
+ const compress = () => {
+ console.log(`Compressing ${currentIndex + 1}/${videoData.length}...`);
+ (0, utils_1.getVideoDuration)(videoData[currentIndex].base)
+ .then((duration) => {
+ const bitrate = (0, utils_1.getCalculatedVideoBitrate)(duration, minBitrate, targetFileSize);
+ compressVideo(window, videoData[currentIndex], bitrate, removeAudio, h265)
+ .then(() => {
+ if (currentIndex + 1 < videoData.length) {
+ currentIndex += 1;
+ compress();
+ }
+ else {
+ resolve(true);
+ }
+ })
+ .catch((err) => {
+ reject(err);
+ });
+ })
+ .catch((err) => {
+ reject(err);
+ });
+ };
+ compress();
+ });
+}
+exports.compressQueue = compressQueue;
+function compressVideo(window, videoData, bitrate, removeAudio, h265) {
+ return new Promise((resolve, reject) => {
+ cmd = (0, fluent_ffmpeg_1.default)();
+ let audioArg = "-b:a 128k";
+ let codec = "-c:v libx264";
+ if (removeAudio) {
+ audioArg = "-an";
+ }
+ if (h265) {
+ codec = "-c:v libx265";
+ }
+ let pass1 = [`-y`, codec, `-b:v ${bitrate}k`, `-pass 1`, `-an`, `-f mp4`];
+ let pass2 = [codec, `-b:v ${bitrate}k`, `-pass 2`, `-c:a aac`, audioArg];
+ cmd.setFfmpegPath((0, utils_1.getFFmpeg)()[0]);
+ cmd.setFfprobePath((0, utils_1.getFFmpeg)()[1]);
+ cmd.input(videoData.base);
+ cmd.outputOptions(pass1);
+ cmd.output(`${videoData.path}temp`);
+ cmd.on("end", () => {
+ currentProgress += 1;
+ window.webContents.send("progressUpdate", currentProgress, totalProgress);
+ cmd = (0, fluent_ffmpeg_1.default)();
+ cmd.setFfmpegPath((0, utils_1.getFFmpeg)()[0]);
+ cmd.setFfprobePath((0, utils_1.getFFmpeg)()[1]);
+ cmd.input(videoData.base);
+ cmd.outputOptions(pass2);
+ cmd.output(`${videoData.path}${videoData.name}-compressed${videoData.ext}`);
+ cmd.on("end", () => {
+ currentProgress += 1;
+ window.webContents.send("progressUpdate", currentProgress, totalProgress);
+ console.log(`Compressed ${videoData.name}`);
+ resolve(true);
+ });
+ cmd.on("error", (err) => {
+ reject(err);
+ });
+ cmd.run();
+ });
+ cmd.on("error", (err) => {
+ reject(err);
+ });
+ cmd.run();
+ });
+}
+function killFFmpeg() {
+ if (cmd != null) {
+ cmd.kill();
+ console.log("Killed FFmpeg process.");
+ }
+}
diff --git a/dist/preload.js b/dist/preload.js
new file mode 100644
index 0000000..3918c74
--- /dev/null
+++ b/dist/preload.js
@@ -0,0 +1 @@
+"use strict";
diff --git a/dist/renderer/renderer.js b/dist/renderer/renderer.js
new file mode 100644
index 0000000..6addfae
--- /dev/null
+++ b/dist/renderer/renderer.js
@@ -0,0 +1,95 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+const electron_1 = require("electron");
+const btnCompress = document.getElementById("btn-compress");
+const btnAbort = document.getElementById("btn-abort");
+const btnSupport = document.getElementById("btn-support");
+const btnGithub = document.getElementById("btn-github");
+const lblStatus = document.getElementById("lbl-status");
+const lblStatusSmall = document.getElementById("lbl-status-small");
+const dropZone = document.getElementById("drop-zone");
+const progressBar = document.getElementById("progress-bar");
+const checkRemoveAudio = document.getElementById("check-remove-audio");
+const checkH265 = document.getElementById("check-h265");
+const inputMinBitrate = document.getElementById("input-min-bitrate");
+const inputFileSize = document.getElementById("input-file-size");
+let videoPaths = [];
+btnCompress?.addEventListener("click", () => {
+ const removeAudio = checkRemoveAudio.checked;
+ const h265 = checkH265.checked;
+ const minBitrate = parseFloat(inputMinBitrate.value) || 100;
+ const fileSize = parseFloat(inputFileSize.value) || 8.0;
+ btnCompress.disabled = true;
+ electron_1.ipcRenderer.send("requestCompress", removeAudio, h265, minBitrate, fileSize);
+});
+btnAbort?.addEventListener("click", () => {
+ electron_1.ipcRenderer.send("requestAbort");
+});
+btnSupport?.addEventListener("click", () => {
+ electron_1.shell.openExternal("https://ko-fi.com/slugnasty");
+});
+btnGithub?.addEventListener("click", () => {
+ electron_1.shell.openExternal("https://github.com/slugnasty/video-compressor");
+});
+dropZone?.addEventListener("dragover", (event) => {
+ event.stopPropagation();
+ event.preventDefault();
+});
+dropZone?.addEventListener("drop", (event) => {
+ event.stopPropagation();
+ event.preventDefault();
+ videoPaths = [];
+ const files = event.dataTransfer?.files;
+ for (const file of files) {
+ videoPaths.push(file.path);
+ }
+ lblStatus.innerText = `${videoPaths.length} video(s) ready to compress.`;
+ lblStatusSmall.innerText = "Click 'Compress' to begin.";
+ btnCompress.disabled = false;
+ electron_1.ipcRenderer.send("droppedVideos", videoPaths);
+});
+electron_1.ipcRenderer.on("message", (event, bigText, smallText) => {
+ lblStatus.innerText = bigText;
+ if (smallText) {
+ lblStatusSmall.innerText = smallText;
+ }
+});
+electron_1.ipcRenderer.on("progressUpdate", (event, currentValue, totalValue) => {
+ const progress = Math.round((currentValue / totalValue) * 100);
+ console.log(`Progress: ${progress}`);
+ lblStatusSmall.innerText = `Progress: ${progress}%`;
+ progressBar.style.width = `${progress}%`;
+});
+electron_1.ipcRenderer.on("compressionStart", (event) => {
+ lblStatus.innerText = "Compressing, please wait...";
+ lblStatusSmall.innerText = "Large videos can take a long time!";
+ btnCompress.disabled = true;
+ btnAbort.disabled = false;
+ checkRemoveAudio.disabled = true;
+ checkH265.disabled = true;
+ inputMinBitrate.disabled = true;
+ inputFileSize.disabled = true;
+ progressBar.style.width = "0%";
+});
+electron_1.ipcRenderer.on("compressionComplete", (event) => {
+ lblStatus.innerText = "Compression complete, enjoy!";
+ lblStatusSmall.innerText = "Please consider supporting my work :)";
+ btnCompress.disabled = true;
+ btnAbort.disabled = true;
+ checkRemoveAudio.disabled = false;
+ checkH265.disabled = false;
+ inputMinBitrate.disabled = false;
+ inputFileSize.disabled = false;
+ progressBar.style.width = "100%";
+});
+electron_1.ipcRenderer.on("compressionError", (event, err) => {
+ lblStatus.innerText = err;
+ lblStatusSmall.innerText = "";
+ btnCompress.disabled = true;
+ btnAbort.disabled = true;
+ checkRemoveAudio.disabled = false;
+ checkH265.disabled = false;
+ inputMinBitrate.disabled = false;
+ inputFileSize.disabled = false;
+ progressBar.style.width = "100%";
+});
diff --git a/dist/utils.js b/dist/utils.js
new file mode 100644
index 0000000..23fb0c4
--- /dev/null
+++ b/dist/utils.js
@@ -0,0 +1,65 @@
+"use strict";
+var __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.getFFmpeg = exports.getCalculatedVideoBitrate = exports.getVideoDuration = exports.getVideoData = void 0;
+const electron_1 = require("electron");
+const path_1 = require("path");
+const child_process_1 = require("child_process");
+const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg"));
+function getVideoData(videoPaths) {
+ let videoData = [];
+ for (let base of videoPaths) {
+ const name = (0, path_1.parse)(base).name;
+ const ext = (0, path_1.parse)(base).ext;
+ const path = base.split(name)[0];
+ videoData.push({ base: base, path: path, name: name, ext: ext });
+ }
+ return videoData;
+}
+exports.getVideoData = getVideoData;
+function getVideoDuration(videoPath) {
+ return new Promise((resolve, reject) => {
+ const cmd = (0, fluent_ffmpeg_1.default)();
+ cmd.setFfmpegPath(getFFmpeg()[0]);
+ cmd.setFfprobePath(getFFmpeg()[1]);
+ cmd.input(videoPath);
+ cmd.ffprobe((err, metadata) => {
+ if (err) {
+ reject(err);
+ }
+ else {
+ const duration = metadata.format.duration;
+ console.log(`Video duration: ${duration}`);
+ resolve(duration);
+ }
+ });
+ });
+}
+exports.getVideoDuration = getVideoDuration;
+function getCalculatedVideoBitrate(duration, minBitrate, targetFileSize) {
+ const magic = Math.max((targetFileSize * 8192.0) / (1.048576 * duration) - 128, minBitrate);
+ console.log(`Calculated bitrate: ${magic}`);
+ return magic;
+}
+exports.getCalculatedVideoBitrate = getCalculatedVideoBitrate;
+function getFFmpeg() {
+ let ffmpegPath = __dirname + "/ffmpeg.exe";
+ let ffprobePath = __dirname + "/ffprobe.exe";
+ if (process.platform === "linux") {
+ const cmd = (0, child_process_1.spawnSync)("which", ["ffmpeg"], { encoding: "utf8" });
+ if (cmd.stdout != "") {
+ ffmpegPath = "ffmpeg";
+ ffprobePath = "ffprobe";
+ }
+ else {
+ require("./main").mainWindow.webContents.send("message", "FFmpeg missing!", "Please install FFmpeg before continuing.");
+ new electron_1.Notification({ title: "FFmpeg missing!", body: "Please install FFmpeg before continuing." }).show();
+ ffmpegPath = "null";
+ ffprobePath = "null";
+ }
+ }
+ return [ffmpegPath, ffprobePath];
+}
+exports.getFFmpeg = getFFmpeg;
diff --git a/index.html b/index.html
index fb75976..e968cb4 100644
--- a/index.html
+++ b/index.html
@@ -5,7 +5,7 @@
-
Big Slime Video Compressor
+ Slugnasty Video Compressor
@@ -69,7 +69,13 @@ Drop your videos here.
-
+
@@ -79,7 +85,13 @@
Drop your videos here.
-
+
@@ -88,11 +100,21 @@
Drop your videos here.
-
-