diff --git a/.gitignore b/.gitignore index ad0dc100..4ff02101 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,12 @@ /remote /.idea +/ffmpeg/ffmpeg +/ffmpeg/ffprobe + +/ffmpeg/ffmpeg.exe +/ffmpeg/ffprobe.exe + *.js *.js.map *.log diff --git a/README.md b/README.md index 295f63db..c653ad55 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,8 @@ See [issues](https://github.com/whyboris/Video-Hub-App/issues) for what is in pr ⚠ The repository is usually ahead of the publicly [released version](https://github.com/whyboris/Video-Hub-App/releases) - it is 🚧 WIP and may have bugs in it. +⚠ For screenshot generation to work, download and place `ffmpeg` and `ffprobe` executable into the `ffmpeg` folder. You can download the files from [FFmpeg](https://ffmpeg.org/download.html). For [windows](https://www.gyan.dev/ffmpeg/builds/) you can download `release-full` which will include hardware acceleration for nvidia (to enable, toggle `nvidiaBuild` variable in `main-extract.ts`). + How to start: - `npm install --legacy-peer-deps` to install diff --git a/electron-builder.json b/electron-builder.json index d213a4e7..6113405f 100644 --- a/electron-builder.json +++ b/electron-builder.json @@ -17,6 +17,10 @@ "!tslint.json" ], "extraResources": [ + { + "from": "ffmpeg", + "to": "ffmpeg" + }, { "from": "src/assets/icons/mac/touch-bar", "to": "assets" diff --git a/node/main-extract.ts b/node/main-extract.ts index ce58b1c0..1aa81bed 100644 --- a/node/main-extract.ts +++ b/node/main-extract.ts @@ -1,3 +1,5 @@ +import { app } from "electron"; + /** * This file contains all the logic for extracting: * first thumbnail, @@ -25,7 +27,7 @@ const fs = require('fs'); import * as path from 'path'; const spawn = require('child_process').spawn; -const ffmpegPath = require('ffmpeg-static').replace('app.asar', 'app.asar.unpacked'); +const ffmpegPath: string = app.isPackaged ? (process.resourcesPath + '/ffmpeg/ffmpeg') : './ffmpeg/ffmpeg'; import { GLOBALS } from './main-globals'; @@ -36,6 +38,20 @@ import type { ImageElement, ScreenshotSettings } from '../interfaces/final-objec // FFMPEG arg generating functions // ======================================================================================== +/** + * nVidia-accelerated GPU decoding for screenshot generation + * added conditionally on `nvidiaBuild` variable + */ +const prependNvidiaAccelerationArgumentsIfEnabled = (args: string[]): string[] => { + + if (GLOBALS.nvidiaBuild) { + args.push('-hwaccel'); + args.push('cuda'); + } + + return args; +} + /** * Generate the ffmpeg args to extract a single frame according to settings * @param pathToVideo @@ -52,7 +68,12 @@ const extractSingleFrameArgs = ( const ssWidth: number = screenshotHeight * (16 / 9); + const nvidia: string[] = []; + + prependNvidiaAccelerationArgumentsIfEnabled(nvidia); + const args: string[] = [ + ...nvidia, '-ss', (duration / 10).toString(), '-i', pathToVideo, '-frames', '1', @@ -88,6 +109,8 @@ const generateScreenshotStripArgs = ( const totalCount = numberOfScreenshots; const step: number = duration / (totalCount + 1); const args: string[] = []; + prependNvidiaAccelerationArgumentsIfEnabled(args); + let allFramesFiltered = ''; let outputFrames = ''; @@ -137,6 +160,8 @@ const generatePreviewClipArgs = ( const totalCount = clipSnippets; const step: number = duration / (totalCount + 1); const args: string[] = []; + prependNvidiaAccelerationArgumentsIfEnabled(args); + let concat = ''; // make the magic filter diff --git a/node/main-globals.ts b/node/main-globals.ts index cb6b0a4d..0053624a 100644 --- a/node/main-globals.ts +++ b/node/main-globals.ts @@ -1,9 +1,10 @@ import type { ScreenshotSettings, InputSources } from '../interfaces/final-object.interface'; -// For release, update 4 values: <---- !!! RELEASE !!! +// For release, update 5 values: <---- !!! RELEASE !!! // // `demo: true,` -- below // `version: 'X.X.X',` -- below +// `nvidiaBuild: true, -- below (separate build, requires nvidia FFmpeg) // `"version": "X.X.X",` -- package.json // `"productName": "... Demo" -- electron-builder.json @@ -11,9 +12,10 @@ export const GLOBALS: VhaGlobals = { angularApp: null, // reference used to send messages back to Angular App currentlyOpenVhaFile: '', // OFFICAL DECREE IN NODE WHICH FILE IS CURRENTLY OPEN !!! debug: false, + nvidiaBuild: false, // make sure to use FFmpeg with nvidia cuda abilities <---- !!! RELEASE !!! demo: false, // change to `true` during release <---- !!! RELEASE !!! hubName: 'untitled', // in case user doesn't name their hub any name - macVersion: false, // auto updated by `main.ts` + macVersion: true, // auto updated by `main.ts` readyToQuit: false, // hack to quit gracefully selectedOutputFolder: '', selectedSourceFolders: {}, @@ -40,6 +42,7 @@ export interface VhaGlobals { demo: boolean; hubName: string; macVersion: boolean; + nvidiaBuild: boolean; readyToQuit: boolean; screenshotSettings: ScreenshotSettings; selectedOutputFolder: string; diff --git a/node/main-support.ts b/node/main-support.ts index e6a80ba5..f6814aaa 100644 --- a/node/main-support.ts +++ b/node/main-support.ts @@ -1,3 +1,5 @@ +import { app } from "electron" + /* * This whole file is meant to contain only PURE functions * @@ -12,7 +14,9 @@ import { GLOBALS } from './main-globals'; // TODO -- eliminate dependence on `GL import * as path from 'path'; const exec = require('child_process').exec; -const ffprobePath = require('@ffprobe-installer/ffprobe').path.replace('app.asar', 'app.asar.unpacked'); + +const ffprobePath: string = app.isPackaged ? (process.resourcesPath + '/ffmpeg/ffprobe') : './ffmpeg/ffprobe'; + const fs = require('fs'); const hash = require('object-hash'); const hasher = require('crypto').createHash; diff --git a/package-lock.json b/package-lock.json index 5ce65edc..2f5d5368 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,6 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { - "@ffprobe-installer/ffprobe": "2.1.2", "@iharbeck/ngx-virtual-scroller": "19.0.1", "@ngx-translate/core": "17.0.0", "async": "3.2.6", @@ -19,7 +18,6 @@ "electron-window-state": "5.0.3", "express": "5.1.0", "fdir": "6.5.0", - "ffmpeg-static": "5.3.0", "fuse.js": "7.1.0", "ip": "2.0.1", "natural-orderby": "5.0.0", @@ -2816,21 +2814,6 @@ "postcss-selector-parser": "^7.0.0" } }, - "node_modules/@derhuerst/http-basic": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/@derhuerst/http-basic/-/http-basic-8.2.4.tgz", - "integrity": "sha512-F9rL9k9Xjf5blCz8HsJRO4diy111cayL2vkY2XE4r4t3n0yPXVYy3KD3nJ1qbrSn9743UWSXH4IwuCa/HWlGFw==", - "license": "MIT", - "dependencies": { - "caseless": "^0.12.0", - "concat-stream": "^2.0.0", - "http-response-object": "^3.0.1", - "parse-cache-control": "^1.0.1" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@discoveryjs/json-ext": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", @@ -3773,135 +3756,6 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "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, - "license": "LGPL-2.1", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@ffprobe-installer/darwin-x64": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/darwin-x64/-/darwin-x64-5.1.0.tgz", - "integrity": "sha512-J+YGscZMpQclFg31O4cfVRGmDpkVsQ2fZujoUdMAAYcP0NtqpC49Hs3SWJpBdsGB4VeqOt5TTm1vSZQzs1NkhA==", - "cpu": [ - "x64" - ], - "hasInstallScript": true, - "license": "GPL-3.0", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@ffprobe-installer/ffprobe": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/ffprobe/-/ffprobe-2.1.2.tgz", - "integrity": "sha512-ZNvwk4f2magF42Zji2Ese16SMj9BS7Fui4kRjg6gTYTxY3gWZNpg85n4MIfQyI9nimHg4x/gT6FVkp/bBDuBwg==", - "license": "LGPL-2.1", - "engines": { - "node": ">=14.21.2" - }, - "optionalDependencies": { - "@ffprobe-installer/darwin-arm64": "5.0.1", - "@ffprobe-installer/darwin-x64": "5.1.0", - "@ffprobe-installer/linux-arm": "5.2.0", - "@ffprobe-installer/linux-arm64": "5.2.0", - "@ffprobe-installer/linux-ia32": "5.2.0", - "@ffprobe-installer/linux-x64": "5.2.0", - "@ffprobe-installer/win32-ia32": "5.1.0", - "@ffprobe-installer/win32-x64": "5.1.0" - } - }, - "node_modules/@ffprobe-installer/linux-arm": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/linux-arm/-/linux-arm-5.2.0.tgz", - "integrity": "sha512-PF5HqEhCY7WTWHtLDYbA/+rLS+rhslWvyBlAG1Fk8VzVlnRdl93o6hy7DE2kJgxWQbFaR3ZktPQGEzfkrmQHvQ==", - "cpu": [ - "arm" - ], - "hasInstallScript": true, - "license": "GPL-3.0", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@ffprobe-installer/linux-arm64": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/linux-arm64/-/linux-arm64-5.2.0.tgz", - "integrity": "sha512-X1VvWtlLs6ScP73biVLuHD5ohKJKsMTa0vafCESOen4mOoNeLAYbxOVxDWAdFz9cpZgRiloFj5QD6nDj8E28yQ==", - "cpu": [ - "arm64" - ], - "hasInstallScript": true, - "license": "GPL-3.0", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@ffprobe-installer/linux-ia32": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/linux-ia32/-/linux-ia32-5.2.0.tgz", - "integrity": "sha512-TFVK5sasXyXhbIG7LtPRDmtkrkOsInwKcL43iEvEw+D9vCS2rc//mn9/0Q+BR0UoJEiMK4+ApYr/3LLVUBPOCQ==", - "cpu": [ - "ia32" - ], - "hasInstallScript": true, - "license": "GPL-3.0", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@ffprobe-installer/linux-x64": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/linux-x64/-/linux-x64-5.2.0.tgz", - "integrity": "sha512-D3UeqTLYPNs7pBWPLUYGehPdRVqU8eACox4OZy3pZUZatxye2YKlvBwEfaLdL1v2Z4FOAlLUhms0kY8m8kqSRA==", - "cpu": [ - "x64" - ], - "hasInstallScript": true, - "license": "GPL-3.0", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@ffprobe-installer/win32-ia32": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/win32-ia32/-/win32-ia32-5.1.0.tgz", - "integrity": "sha512-5O3vOoNRxmut0/Nu9vSazTdSHasrr+zPT2B3Hm7kjmO3QVFcIfVImS6ReQnZeSy8JPJOqXts5kX5x/3KOX54XQ==", - "cpu": [ - "ia32" - ], - "license": "GPL-3.0", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@ffprobe-installer/win32-x64": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/win32-x64/-/win32-x64-5.1.0.tgz", - "integrity": "sha512-jMGYeAgkrdn4e2vvYt/qakgHRE3CPju4bn5TmdPfoAm1BlX1mY9cyMd8gf5vSzI8gH8Zq5WQAyAkmekX/8TSTg==", - "cpu": [ - "x64" - ], - "license": "GPL-3.0", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@hapi/address": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@hapi/address/-/address-5.1.1.tgz", @@ -8566,6 +8420,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, "license": "MIT" }, "node_modules/builder-util": { @@ -8862,12 +8717,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "license": "Apache-2.0" - }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -9277,21 +9126,6 @@ "node": ">= 0.6" } }, - "node_modules/concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "engines": [ - "node >= 6.0" - ], - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } - }, "node_modules/connect-history-api-fallback": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", @@ -10588,6 +10422,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -11153,47 +10988,6 @@ } } }, - "node_modules/ffmpeg-static": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ffmpeg-static/-/ffmpeg-static-5.3.0.tgz", - "integrity": "sha512-H+K6sW6TiIX6VGend0KQwthe+kaceeH/luE8dIZyOP35ik7ahYojDuqlTV1bOrtEwl01sy2HFNGQfi5IDJvotg==", - "hasInstallScript": true, - "license": "GPL-3.0-or-later", - "dependencies": { - "@derhuerst/http-basic": "^8.2.0", - "env-paths": "^2.2.0", - "https-proxy-agent": "^5.0.0", - "progress": "^2.0.3" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/ffmpeg-static/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ffmpeg-static/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/file-entry-cache": { "version": "10.1.4", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.1.4.tgz", @@ -12129,21 +11923,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/http-response-object": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", - "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", - "license": "MIT", - "dependencies": { - "@types/node": "^10.0.3" - } - }, - "node_modules/http-response-object/node_modules/@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", - "license": "MIT" - }, "node_modules/http2-wrapper": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", @@ -15991,11 +15770,6 @@ "node": ">=6" } }, - "node_modules/parse-cache-control": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", - "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==" - }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -16574,6 +16348,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -16982,6 +16757,7 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -18325,6 +18101,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -19685,12 +19462,6 @@ "dev": true, "license": "MIT" }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "license": "MIT" - }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -19917,6 +19688,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, "license": "MIT" }, "node_modules/util/node_modules/inherits": { diff --git a/package.json b/package.json index 21534d70..2f8bf2dd 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,6 @@ "postinstall": "electron-builder install-app-deps" }, "dependencies": { - "@ffprobe-installer/ffprobe": "2.1.2", "@iharbeck/ngx-virtual-scroller": "19.0.1", "@ngx-translate/core": "17.0.0", "async": "3.2.6", @@ -53,7 +52,6 @@ "electron-window-state": "5.0.3", "express": "5.1.0", "fdir": "6.5.0", - "ffmpeg-static": "5.3.0", "fuse.js": "7.1.0", "ip": "2.0.1", "natural-orderby": "5.0.0",