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 @@