diff --git a/Dockerfile b/Dockerfile index 991ecd6..d24b18c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,5 +10,6 @@ FROM akashisn/ffmpeg:4.4.5 EXPOSE 8000 WORKDIR /home/node/app ENTRYPOINT [ "./dizquetv" ] +ENV DIZQUETV_FFMPEG_PATH=/usr/bin/ffmpeg COPY --from=0 /home/node/app/dist/dizquetv /home/node/app/ RUN ln -s /usr/local/bin/ffmpeg /usr/bin/ffmpeg diff --git a/Dockerfile-nvidia b/Dockerfile-nvidia index 52fc831..ad468b3 100644 --- a/Dockerfile-nvidia +++ b/Dockerfile-nvidia @@ -10,5 +10,6 @@ FROM jrottenberg/ffmpeg:4.4.5-nvidia2204 EXPOSE 8000 WORKDIR /home/node/app ENTRYPOINT [ "./dizquetv" ] +ENV DIZQUETV_FFMPEG_PATH=/usr/bin/ffmpeg COPY --from=0 /home/node/app/dist/dizquetv /home/node/app/ RUN ln -s /usr/local/bin/ffmpeg /usr/bin/ffmpeg diff --git a/index.js b/index.js index 9f614e6..390c6e9 100644 --- a/index.js +++ b/index.js @@ -34,6 +34,7 @@ const ProgramPlayTimeDB = require('./src/dao/program-play-time-db') const FfmpegSettingsService = require('./src/services/ffmpeg-settings-service') const PlexProxyService = require('./src/services/plex-proxy-service') const PlexServerDB = require('./src/dao/plex-server-db'); +const FFMPEGInfo = require('./src/ffmpeg-info'); const onShutdown = require("node-graceful-shutdown").onShutdown; @@ -53,16 +54,12 @@ if (NODE < 12) { console.error(`WARNING: Your nodejs version ${process.version} is lower than supported. dizqueTV has been tested best on nodejs 12.16.`); } -unlockPath = false; for (let i = 0, l = process.argv.length; i < l; i++) { if ((process.argv[i] === "-p" || process.argv[i] === "--port") && i + 1 !== l) process.env.PORT = process.argv[i + 1] if ((process.argv[i] === "-d" || process.argv[i] === "--database") && i + 1 !== l) process.env.DATABASE = process.argv[i + 1] - if (process.argv[i] === "--unlock") { - unlockPath = true; - } } process.env.DATABASE = process.env.DATABASE || path.join(".", ".dizquetv") @@ -94,6 +91,8 @@ if(!fs.existsSync(path.join(process.env.DATABASE, 'cache','images'))) { fs.mkdirSync(path.join(process.env.DATABASE, 'cache','images')) } +const ffmpegInfo = new FFMPEGInfo(process.env, process.env.DATABASE); +ffmpegInfo.initialize(); channelDB = new ChannelDB( path.join(process.env.DATABASE, 'channels') ); @@ -108,7 +107,7 @@ channelService = new ChannelService(channelDB); fillerDB = new FillerDB( path.join(process.env.DATABASE, 'filler') , channelService ); customShowDB = new CustomShowDB( path.join(process.env.DATABASE, 'custom-shows') ); let programPlayTimeDB = new ProgramPlayTimeDB( path.join(process.env.DATABASE, 'play-cache') ); -let ffmpegSettingsService = new FfmpegSettingsService(db, unlockPath); +let ffmpegSettingsService = new FfmpegSettingsService(db); let plexServerDB = new PlexServerDB(channelService, fillerDB, customShowDB, db); let plexProxyService = new PlexProxyService(plexServerDB); @@ -324,12 +323,12 @@ app.use('/favicon.svg', express.static( app.use('/custom.css', express.static(path.join(process.env.DATABASE, 'custom.css'))) // API Routers -app.use(api.router(db, channelService, fillerDB, customShowDB, xmltvInterval, guideService, m3uService, eventService, ffmpegSettingsService, plexServerDB, plexProxyService)) +app.use(api.router(db, channelService, fillerDB, customShowDB, xmltvInterval, guideService, m3uService, eventService, ffmpegSettingsService, plexServerDB, plexProxyService, ffmpegInfo)) app.use('/api/cache/images', cacheImageService.apiRouters()) app.use('/' + fontAwesome, express.static(path.join(process.env.DATABASE, fontAwesome))) app.use('/' + bootstrap, express.static(path.join(process.env.DATABASE, bootstrap))) -app.use(video.router( channelService, fillerDB, db, programmingService, activeChannelService, programPlayTimeDB )) +app.use(video.router( channelService, fillerDB, db, programmingService, activeChannelService, programPlayTimeDB, ffmpegInfo )) app.use(hdhr.router) app.listen(process.env.PORT, () => { console.log(`HTTP server running on port: http://*:${process.env.PORT}`) diff --git a/src/api.js b/src/api.js index 904574a..3e5cba9 100644 --- a/src/api.js +++ b/src/api.js @@ -4,8 +4,6 @@ const path = require('path') const fs = require('fs') const constants = require('./constants'); const JSONStream = require('JSONStream'); -const FFMPEGInfo = require('./ffmpeg-info'); -const PlexServerDB = require('./dao/plex-server-db'); const Plex = require("./plex.js"); const timeSlotsService = require('./services/time-slots-service'); @@ -24,14 +22,13 @@ function safeString(object) { } module.exports = { router: api } -function api(db, channelService, fillerDB, customShowDB, xmltvInterval, guideService, _m3uService, eventService, ffmpegSettingsService, plexServerDB, plexProxyService ) { +function api(db, channelService, fillerDB, customShowDB, xmltvInterval, guideService, _m3uService, eventService, ffmpegSettingsService, plexServerDB, plexProxyService, ffmpegInfo ) { let m3uService = _m3uService; const router = express.Router() router.get('/api/version', async (req, res) => { try { - let ffmpegSettings = db['ffmpeg-settings'].find()[0]; - let v = await (new FFMPEGInfo(ffmpegSettings)).getVersion(); + let v = await ffmpegInfo.getVersion(); res.send( { "dizquetv" : constants.VERSION_NAME, "ffmpeg" : v, @@ -616,6 +613,18 @@ function api(db, channelService, fillerDB, customShowDB, xmltvInterval, guideSe }) + router.get('/api/ffmpeg-info', async (req, res) => { + try { + let ffmpeg = await ffmpegInfo.getPath(); + let obj = { ffmpegPath: ffmpeg } + res.send(obj) + } catch(err) { + console.error(err); + res.status(500).send("error"); + } + }) + + // PLEX SETTINGS router.get('/api/plex-settings', (req, res) => { try { diff --git a/src/database-migration.js b/src/database-migration.js index cef34a7..a5e5771 100644 --- a/src/database-migration.js +++ b/src/database-migration.js @@ -20,8 +20,7 @@ const path = require('path'); var fs = require('fs'); -const TARGET_VERSION = 805; -const DAY_MS = 1000 * 60 * 60 * 24; +const TARGET_VERSION = 900; const STEPS = [ // [v, v2, x] : if the current version is v, call x(db), and version becomes v2 @@ -44,8 +43,9 @@ const STEPS = [ [ 800, 801, (db) => addImageCache(db) ], [ 801, 802, () => addGroupTitle() ], [ 802, 803, () => fixNonIntegerDurations() ], - [ 803, 805, (db) => addFFMpegLock(db) ], - [ 804, 805, (db) => addFFMpegLock(db) ], + [ 803, 900, (db) => fixFFMpegPathSetting(db) ], + [ 804, 900, (db) => fixFFMpegPathSetting(db) ], + [ 805, 900, (db) => fixFFMpegPathSetting(db) ], ] const { v4: uuidv4 } = require('uuid'); @@ -75,7 +75,7 @@ function appNameChange(db) { function basicDB(db) { //this one should either try recovering the db from a very old version - //or buildl a completely empty db at version 0 + //or build a completely empty db at version 0 let ffmpegSettings = db['ffmpeg-settings'].find() let plexSettings = db['plex-settings'].find() @@ -386,8 +386,6 @@ function ffmpeg() { return { //How default ffmpeg settings should look configVersion: 5, - ffmpegPath: "/usr/bin/ffmpeg", - ffmpegPathLockDate: new Date().getTime() + DAY_MS, threads: 4, concatMuxDelay: "0", logFfmpeg: false, @@ -769,19 +767,23 @@ function addScalingAlgorithm(db) { fs.writeFileSync( f, JSON.stringify( [ffmpegSettings] ) ); } -function addFFMpegLock(db) { +function fixFFMpegPathSetting(db) { let ffmpegSettings = db['ffmpeg-settings'].find()[0]; let f = path.join(process.env.DATABASE, 'ffmpeg-settings.json'); - if ( typeof(ffmpegSettings.ffmpegPathLockDate) === 'undefined' || ffmpegSettings.ffmpegPathLockDate == null ) { + let f2 = path.join(process.env.DATABASE, 'ffmpeg-path.json'); + delete ffmpegSettings.ffmpegPathLockDate; + let fpath = ffmpegSettings.ffmpegPath; + delete ffmpegSettings.ffmpegPath; - console.log("Adding ffmpeg lock. For your security it will not be possible to modify the ffmpeg path using the UI anymore unless you launch dizquetv by following special instructions.."); - // We are migrating an existing db that had a ffmpeg path. Make sure - // it's already locked. - ffmpegSettings.ffmpegPathLockDate = new Date().getTime() - 2 * DAY_MS; + if (typeof(fpath) === "string" ) { + console.log(`Found existing setting ffmpegPath=${fpath}, creating setting file (This file will get ignored if you are already setting an environment variable (the docker images do that)).`); + let pathJson = { ffmpegPath : fpath }; + fs.writeFileSync( f2, JSON.stringify( pathJson ) ); } fs.writeFileSync( f, JSON.stringify( [ffmpegSettings] ) ); } + function moveBackup(path) { if (fs.existsSync(`${process.env.DATABASE}${path}`) ) { let i = 0; diff --git a/src/ffmpeg-info.js b/src/ffmpeg-info.js index e7dd7f0..cceddcf 100644 --- a/src/ffmpeg-info.js +++ b/src/ffmpeg-info.js @@ -1,13 +1,98 @@ const exec = require('child_process').exec; +const fs = require('fs'); +const path = require('path'); class FFMPEGInfo { - constructor(opts) { - this.ffmpegPath = opts.ffmpegPath + + constructor(env, dbPath) { + this.initialized = false; + this.env = env; + this.dbPath = dbPath; + this.ffmpegPath = null; + this.origin = "Not found"; } - async getVersion() { + + async initialize() { + let selectedPath = null; + if (typeof(this.env.DIZQUETV_FFMPEG_PATH) === "string") { + selectedPath = this.env.DIZQUETV_FFMPEG_PATH; + this.origin = "env.DIZQUETV_FFMPEG_PATH"; + } else { + selectedPath = await this.getPathFromFile(this.dbPath, 'ffmpeg-path.json'); + this.origin = "ffmpeg-path.json"; + } + if (selectedPath == null) { + //windows Path environment var + let paths = this.env.Path; + if (typeof(paths) === "string") { + let maybe = paths.split(";").filter( + (str) => str.contains("ffmpeg" ) + )[0]; + if (typeof(maybe) === "string") { + selectedPath = path.join(maybe, "ffmpeg.exe"); + this.origin = "Widnows Env. Path"; + } + } + } + if (selectedPath == null) { + //Default install path for ffmpeg in n*x OSes. + // if someone has built ffmpeg manually or wants an alternate + // path, they are most likely capable of configuring it manually. + selectedPath = "/usr/bin/ffmpeg"; + this.origin = "Default"; + } + + if (selectedPath != null) { + let version = await this.checkVersion(selectedPath); + if (version == null) { + selectedPath = null; + } else { + console.log(`FFmpeg found: ${selectedPath} from: ${this.origin}. version: ${version}`); + this.ffmpegPath = selectedPath; + } + } + this.initialized = true; + + } + + async getPath() { + if (! this.initialized) { + await this.initialize(); + } + return this.ffmpegPath; + } + + async getPathFromFile(folder, fileName) { + let f = path.join(folder, fileName); + try { + let json = await new Promise( (resolve, reject) => { + fs.readFile(f, (err, data) => { + if (err) { + return reject(err); + } + try { + resolve( JSON.parse(data) ) + } catch (err) { + reject(err); + } + }) + }); + let ffmpeg = json["ffmpegPath"]; + if (typeof(ffmpeg) === "string") { + return ffmpeg; + } else { + return null; + } + } catch (err) { + console.error(err); + return null; + } + } + + async checkVersion(ffmpegPath) { try { let s = await new Promise( (resolve, reject) => { - exec( `"${this.ffmpegPath}" -version`, function(error, stdout, stderr){ + exec( `"${ffmpegPath}" -version`, function(error, stdout, stderr){ if (error !== null) { reject(error); } else { @@ -23,7 +108,20 @@ class FFMPEGInfo { return m[1]; } catch (err) { console.error("Error getting ffmpeg version", err); + return null; + } + } + + + async getVersion() { + if (! this.initialized) { + await this.initialize(); + } + let version = await this.checkVersion(this.ffmpegPath); + if (version == null) { return "Error"; + } else { + return version; } } } diff --git a/src/ffmpeg.js b/src/ffmpeg.js index c503279..08b43b0 100644 --- a/src/ffmpeg.js +++ b/src/ffmpeg.js @@ -7,6 +7,7 @@ const REALLY_RIDICULOUSLY_HIGH_FPS_FOR_DIZQUETVS_USECASE = 120; class FFMPEG extends events.EventEmitter { constructor(opts, channel) { super() + this.ffmpegPath = opts.ffmpegPath; this.opts = opts; this.errorPicturePath = `http://localhost:${process.env.PORT}/images/generic-error-screen.png`; this.ffmpegName = "unnamed ffmpeg"; @@ -22,7 +23,6 @@ class FFMPEG extends events.EventEmitter { this.opts.maxFPS = REALLY_RIDICULOUSLY_HIGH_FPS_FOR_DIZQUETVS_USECASE; } this.channel = channel - this.ffmpegPath = opts.ffmpegPath let resString = opts.targetResolution; if ( @@ -601,7 +601,7 @@ class FFMPEG extends events.EventEmitter { return; } if (! this.sentData) { - this.emit('error', { code: code, cmd: `${this.opts.ffmpegPath} ${ffmpegArgs.join(' ')}` }) + this.emit('error', { code: code, cmd: `${this.ffmpegPath} ${ffmpegArgs.join(' ')}` }) } console.log( `${this.ffmpegName} exited with code 255.` ); this.emit('close', code) diff --git a/src/services/ffmpeg-settings-service.js b/src/services/ffmpeg-settings-service.js index 6e4b149..0308488 100644 --- a/src/services/ffmpeg-settings-service.js +++ b/src/services/ffmpeg-settings-service.js @@ -4,11 +4,8 @@ const path = require('path'); const fs = require('fs'); class FfmpegSettingsService { - constructor(db, unlock) { + constructor(db) { this.db = db; - if (unlock) { - this.unlock(); - } } get() { @@ -21,13 +18,6 @@ class FfmpegSettingsService { return ffmpeg; } - unlock() { - let ffmpeg = this.getCurrentState(); - console.log("ffmpeg path UI unlocked for another day..."); - ffmpeg.ffmpegPathLockDate = new Date().getTime() + DAY_MS; - this.db['ffmpeg-settings'].update({ _id: ffmpeg._id }, ffmpeg) - } - update(attempt) { let ffmpeg = this.getCurrentState(); @@ -62,7 +52,6 @@ class FfmpegSettingsService { } reset() { - // Even if reseting, it's impossible to unlock the ffmpeg path let ffmpeg = databaseMigration.defaultFFMPEG() ; this.update(ffmpeg); return this.get(); diff --git a/src/video.js b/src/video.js index f113ab2..acea791 100644 --- a/src/video.js +++ b/src/video.js @@ -18,17 +18,18 @@ async function shutdown() { stopPlayback = true; } -function video( channelService, fillerDB, db, programmingService, activeChannelService, programPlayTimeDB ) { +function video( channelService, fillerDB, db, programmingService, activeChannelService, programPlayTimeDB, ffmpegInfo ) { var router = express.Router() - router.get('/setup', (req, res) => { + router.get('/setup', async (req, res) => { let ffmpegSettings = db['ffmpeg-settings'].find()[0] // Check if ffmpeg path is valid - if (!fs.existsSync(ffmpegSettings.ffmpegPath)) { - res.status(500).send("FFMPEG path is invalid. The file (executable) doesn't exist.") - console.error("The FFMPEG Path is invalid. Please check your configuration.") + let ffmpegPath = await ffmpegInfo.getPath(); + if (ffmpegPath == null) { + res.status(500).send("Missing FFmpeg.") return } + ffmpegSettings.ffmpegPath = ffmpegPath; console.log(`\r\nStream starting. Channel: 1 (dizqueTV)`) @@ -72,14 +73,14 @@ function video( channelService, fillerDB, db, programmingService, activeChannelS return } - let ffmpegSettings = db['ffmpeg-settings'].find()[0] - // Check if ffmpeg path is valid - if (!fs.existsSync(ffmpegSettings.ffmpegPath)) { - res.status(500).send("FFMPEG path is invalid. The file (executable) doesn't exist.") - console.error("The FFMPEG Path is invalid. Please check your configuration.") + let ffmpegSettings = db['ffmpeg-settings'].find()[0] + let ffmpegPath = await ffmpegInfo.getPath(); + if (ffmpegPath == null) { + res.status(500).send("Missing FFmpeg.") return } + ffmpegSettings.ffmpegPath = ffmpegPath; if (step == 0) { res.writeHead(200, { @@ -174,14 +175,14 @@ function video( channelService, fillerDB, db, programmingService, activeChannelS let isBetween = ( (typeof req.query.between !== 'undefined') && (req.query.between=='1') ); - let ffmpegSettings = db['ffmpeg-settings'].find()[0] - // Check if ffmpeg path is valid - if (!fs.existsSync(ffmpegSettings.ffmpegPath)) { - res.status(500).send("FFMPEG path is invalid. The file (executable) doesn't exist.") - console.error("The FFMPEG Path is invalid. Please check your configuration.") + let ffmpegSettings = db['ffmpeg-settings'].find()[0] + let ffmpegPath = await ffmpegInfo.getPath(); + if (ffmpegPath == null) { + res.status(500).send("Missing FFmpeg.") return } + ffmpegSettings.ffmpegPath = ffmpegPath; if (ffmpegSettings.disablePreludes === true) { //disable the preludes diff --git a/web/directives/ffmpeg-settings.js b/web/directives/ffmpeg-settings.js index e474097..df409b9 100644 --- a/web/directives/ffmpeg-settings.js +++ b/web/directives/ffmpeg-settings.js @@ -6,7 +6,14 @@ module.exports = function (dizquetv, resolutionOptions) { scope: { }, link: function (scope, element, attrs) { - //add validations to ffmpeg settings, speciall commas in codec name + + scope.ffmpegPathLoading = true; + scope.ffmpegPath = "" + dizquetv.getFFMpegPath().then( (fpath) => { + scope.ffmpegPath = fpath.ffmpegPath; + scope.ffmpegPathLoading = false; + }); + //add validations to ffmpeg settings, special commas in codec name dizquetv.getFfmpegSettings().then((settings) => { scope.settings = settings }) diff --git a/web/public/templates/ffmpeg-settings.html b/web/public/templates/ffmpeg-settings.html index bcaa3f7..4f0f9bf 100644 --- a/web/public/templates/ffmpeg-settings.html +++ b/web/public/templates/ffmpeg-settings.html @@ -15,40 +15,20 @@
FFMPEG Executable Path
-
+ +
- - - The path to the ffmpeg executable. (e.g: /usr/bin/ffmpeg or C:\ffmpeg\bin\ffmpeg.exe) FFMPEG version 4.2+ required. Check by opening the version tab. - +
+ + + The path to the ffmpeg executable. Please check the instructions if you need to change it. - -
-
-
-
-
- - - This will lock the ffmpeg path setting so that it is no longer editable from UI. Even if you don't toggle this option, the setting will get locked in 24 hours. -
-
-
- - -
-
-
-
- - - - The ffmpeg path setting is currently locked and can't be edited from the UI. It's not usually necessary to update this path once it's known to be working. Run dizquetv with the --unlock command line argument to enable editing it again. + + dizqueTV uses FFmpeg to create video streams. Please check the instructions for info about how to set this up. -