From c297fc8204e27e3cd10ea32b506047b4b960ee72 Mon Sep 17 00:00:00 2001 From: Mylloon Date: Sun, 30 Jul 2023 13:44:47 +0200 Subject: [PATCH] add size control --- package-lock.json | 217 ++++++++++++++++++++++++++++++++++++++-- package.json | 1 + src/main.ts | 50 +++++++-- src/preload.ts | 2 + src/scripts/renderer.ts | 17 +++- 5 files changed, 264 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1653979..c337856 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "AGPL-3.0-or-later", "dependencies": { "ffmpeg-static": "^5.1.0", + "get-video-duration": "^4.1.0", "typescript": "^5.1.6" }, "devDependencies": { @@ -471,6 +472,123 @@ "node": ">=10" } }, + "node_modules/@ffprobe-installer/darwin-arm64": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@ffprobe-installer/darwin-arm64/-/darwin-arm64-5.0.1.tgz", + "integrity": "sha512-vwNCNjokH8hfkbl6m95zICHwkSzhEvDC3GVBcUp5HX8+4wsX10SP3B+bGur7XUzTIZ4cQpgJmEIAx6TUwRepMg==", + "cpu": [ + "arm64" + ], + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@ffprobe-installer/darwin-x64": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@ffprobe-installer/darwin-x64/-/darwin-x64-5.0.0.tgz", + "integrity": "sha512-Zl0UkZ+wW/eyMKBPLTUCcNQch2VDnZz/cBn1DXv3YtCBVbYd9aYzGj4MImdxgWcoE0+GpbfbO6mKGwMq5HCm6A==", + "cpu": [ + "x64" + ], + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@ffprobe-installer/ffprobe": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@ffprobe-installer/ffprobe/-/ffprobe-1.4.1.tgz", + "integrity": "sha512-3WJvxU0f4d7IOZdzoVCAj9fYtiQNC6E0521FJFe9iP5Ej8auTXU7TsrUzIAG1CydeQI+BnM3vGog92SCcF9KtA==", + "optionalDependencies": { + "@ffprobe-installer/darwin-arm64": "5.0.1", + "@ffprobe-installer/darwin-x64": "5.0.0", + "@ffprobe-installer/linux-arm": "5.0.0", + "@ffprobe-installer/linux-arm64": "5.0.0", + "@ffprobe-installer/linux-ia32": "5.0.0", + "@ffprobe-installer/linux-x64": "5.0.0", + "@ffprobe-installer/win32-ia32": "5.0.0", + "@ffprobe-installer/win32-x64": "5.0.0" + } + }, + "node_modules/@ffprobe-installer/linux-arm": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@ffprobe-installer/linux-arm/-/linux-arm-5.0.0.tgz", + "integrity": "sha512-mM1PPxP2UX5SUvhy0urcj5U8UolwbYgmnXA/eBWbW78k6N2Wk1COvcHYzOPs6c5yXXL6oshS2rZHU1kowigw7g==", + "cpu": [ + "arm" + ], + "hasInstallScript": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ffprobe-installer/linux-arm64": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@ffprobe-installer/linux-arm64/-/linux-arm64-5.0.0.tgz", + "integrity": "sha512-IwFbzhe1UydR849YXLPP0RMpHgHXSuPO1kznaCHcU5FscFBV5gOZLkdD8e/xrcC8g/nhKqy0xMjn5kv6KkFQlQ==", + "cpu": [ + "arm64" + ], + "hasInstallScript": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ffprobe-installer/linux-ia32": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@ffprobe-installer/linux-ia32/-/linux-ia32-5.0.0.tgz", + "integrity": "sha512-c3bWlWEDMST59SAZycVh0oyc2eNS/CxxeRjoNryGRgqcZX3EJWJJQL1rAXbpQOMLMi8to1RqnmMuwPJgLLjjUA==", + "cpu": [ + "ia32" + ], + "hasInstallScript": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ffprobe-installer/linux-x64": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@ffprobe-installer/linux-x64/-/linux-x64-5.0.0.tgz", + "integrity": "sha512-zgLnWJFvMGCaw1txGtz84sMEQt6mQUzdw86ih9S/kZOWnp06Gj/ams/EXxEkAxgAACCVM6/O0mkDe/6biY5tgA==", + "cpu": [ + "x64" + ], + "hasInstallScript": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ffprobe-installer/win32-ia32": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@ffprobe-installer/win32-ia32/-/win32-ia32-5.0.0.tgz", + "integrity": "sha512-NnDdAZD6ShFXzJeCkAFl2ZjAv7GcJWYudLA+0T/vjZwvskBop+sq1PGfdmVltfFDcdQiomoThRhn9Xiy9ZC71g==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@ffprobe-installer/win32-x64": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@ffprobe-installer/win32-x64/-/win32-x64-5.0.0.tgz", + "integrity": "sha512-P4ZMRFxVMnfMsOyTfBM/+nkTodLeOUfXNPo+X1bKEWBiZxRErqX/IHS5sLA0yAH8XmtKZcL7Cu6M26ztGcQYxw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1288,7 +1406,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -2099,6 +2216,71 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-video-duration": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-video-duration/-/get-video-duration-4.1.0.tgz", + "integrity": "sha512-kDmnD/C/WFHmZx7eV2duUNCwAH6w9YwpigiUHmq7cRNmxuTvcJ/otvcxa4A5GDus2iSZ+CZEA5e0va/r5ZWumw==", + "dependencies": { + "@ffprobe-installer/ffprobe": "^1.4.1", + "execa": "^5.0.0", + "is-stream": "^2.0.0" + } + }, + "node_modules/get-video-duration/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/get-video-duration/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-video-duration/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-video-duration/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -2380,6 +2562,14 @@ "node": ">= 6" } }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "engines": { + "node": ">=10.17.0" + } + }, "node_modules/humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", @@ -2620,8 +2810,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/jackspeak": { "version": "2.2.2", @@ -2901,6 +3090,11 @@ "node": ">=6" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2927,7 +3121,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, "engines": { "node": ">=6" } @@ -3314,7 +3507,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, "dependencies": { "mimic-fn": "^2.1.0" }, @@ -3498,7 +3690,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -4117,7 +4308,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -4129,7 +4319,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -4137,8 +4326,7 @@ "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "node_modules/slice-ansi": { "version": "3.0.0", @@ -4342,6 +4530,14 @@ "node": ">=0.10.0" } }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "engines": { + "node": ">=6" + } + }, "node_modules/strip-outer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", @@ -4616,7 +4812,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, diff --git a/package.json b/package.json index 116f062..409e543 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "license": "AGPL-3.0-or-later", "dependencies": { "ffmpeg-static": "^5.1.0", + "get-video-duration": "^4.1.0", "typescript": "^5.1.6" }, "devDependencies": { diff --git a/src/main.ts b/src/main.ts index 47979d2..43e8b92 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,16 @@ import { BrowserWindow, app, dialog, ipcMain } from "electron"; -import { unlink } from "fs"; +import { unlink, statSync } from "fs"; import path = require("path"); import ffmpegPath = require("ffmpeg-static"); import child_process = require("child_process"); +import { getVideoDurationInSeconds } from "get-video-duration"; + +const moviesFilter = { + name: "Videos", + extensions: ["mp4", "mkv"], +}; + +const isWindows = process.platform === "win32"; /** Create a new window */ const createWindow = () => { @@ -20,11 +28,6 @@ const createWindow = () => { return win; }; -const moviesFilter = { - name: "Videos", - extensions: ["mp4", "mkv"], -}; - /* Create a new filename from the OG one */ const getNewFilename = (ogFile: string, part: string) => { const oldFile = path.parse(ogFile); @@ -32,18 +35,18 @@ const getNewFilename = (ogFile: string, part: string) => { }; /** Merge all audios track of a video into one */ -const mergeAudio = (file: string) => { +const mergeAudio = async (file: string) => { const tmp_file = getNewFilename(file, "TMP_"); const outFile = getNewFilename(file, "(merged audio) "); // Merge 2 audio child_process.execSync( - `${ffmpegPath} -i "${file}" -filter_complex "[0:a]amerge=inputs=2[a]" -ac 1 -map 0:v -map "[a]" -c:v copy -y "${tmp_file}"` + `${ffmpegPath} -y -i "${file}" -filter_complex "[0:a]amerge=inputs=2[a]" -ac 1 -map 0:v -map "[a]" -c:v copy "${tmp_file}"` ); // Add merged audio as first position to original video child_process.execSync( - `${ffmpegPath} -i "${tmp_file}" -i "${file}" -map 0 -map 1:a -c:v copy -y "${outFile}"` + `${ffmpegPath} -y -i "${tmp_file}" -i "${file}" -map 0 -map 1:a -c:v copy "${outFile}"` ); // Delete the temporary file @@ -53,7 +56,31 @@ const mergeAudio = (file: string) => { } }); - return outFile; + const duration = await getVideoDurationInSeconds(outFile); + const stats = statSync(outFile); + + return { title: outFile, size: stats.size / 1024 / 1024, duration }; +}; + +/* Reduce size of a file */ +const reduceSize = (file: string, bitrate: number) => { + const audioBitrate = 128; + const videoBitrate = Math.floor(bitrate) - audioBitrate; + + /* Trash the output, depends on the platform */ + const nul = isWindows ? "NUL" : "/dev/null"; + + /* AND operator, depends on the platform */ + const and = isWindows ? "^" : "&&"; + + const finalFile = getNewFilename(file, "Compressed - "); + + child_process.execSync( + `${ffmpegPath} -y -i "${file}" -c:v libx264 -b:v ${videoBitrate}k -pass 1 -an -f null ${nul} ${and} \ + ${ffmpegPath} -y -i "${file}" -c:v libx264 -b:v ${videoBitrate}k -pass 2 -c:a aac -b:a ${audioBitrate}k "${finalFile}"` + ); + + return finalFile; }; /* Ready to create the window */ @@ -78,6 +105,9 @@ app.whenReady().then(() => { ipcMain.handle("allowedExtensions", () => moviesFilter); ipcMain.handle("askFile", () => askFile()); ipcMain.handle("mergeAudio", (_, file: string) => mergeAudio(file)); + ipcMain.handle("reduceSize", (_, file: string, bitrate: number) => + reduceSize(file, bitrate) + ); ipcMain.handle("exit", async () => app.quit()); ipcMain.handle("confirmation", async (_, text: string) => confirmation(text)); }); diff --git a/src/preload.ts b/src/preload.ts index 6648fea..bd013ec 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -6,6 +6,8 @@ contextBridge.exposeInMainWorld("internals", { allowedExtensions: () => ipcRenderer.invoke("allowedExtensions"), askFile: () => ipcRenderer.invoke("askFile"), mergeAudio: (file: string) => ipcRenderer.invoke("mergeAudio", file), + reduceSize: (file: string, bitrate: number) => + ipcRenderer.invoke("reduceSize", file, bitrate), exit: () => ipcRenderer.invoke("exit"), confirmation: (text: string) => ipcRenderer.invoke("confirmation", text), }); diff --git a/src/scripts/renderer.ts b/src/scripts/renderer.ts index b091e05..4590c5d 100644 --- a/src/scripts/renderer.ts +++ b/src/scripts/renderer.ts @@ -6,7 +6,10 @@ let internals: { }>; askFile: () => Promise; exit: () => Promise; - mergeAudio: (filename: string) => Promise; + mergeAudio: ( + filename: string + ) => Promise<{ title: string; duration: number; size: number }>; + reduceSize: (file: string, bitrate: number) => Promise; confirmation: (text: string) => Promise; }; @@ -30,9 +33,19 @@ const getFile = async () => { /** Main function */ const main = async () => { + const maxSizeDiscord = 25; const file = await getFile(); const newFile = await internals.mergeAudio(file); - await internals.confirmation(`File ok @ ${newFile}!`); + let finalTitle = newFile.title; + if (newFile.size > maxSizeDiscord) { + const targetSize = maxSizeDiscord - 2; + finalTitle = await internals.reduceSize( + newFile.title, + // https://trac.ffmpeg.org/wiki/Encode/H.264#twopass + (targetSize * 8388.608) / newFile.duration + ); + } + await internals.confirmation(`File ok @ ${finalTitle}!`); await internals.exit(); }; main();