FFMpeg path is now set by environment var or file..

This commit is contained in:
vexorian 2025-12-14 23:05:32 -04:00
parent 7daad9e33f
commit caf3b3b72c
11 changed files with 174 additions and 87 deletions

View File

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

View File

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

View File

@ -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}`)

View File

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

View File

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

View File

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

View File

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

View File

@ -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();

View File

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

View File

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

View File

@ -15,40 +15,20 @@
<hr></hr>
<h6>FFMPEG Executable Path</h6>
<div class="row" ng-show="settings.lock !== true">
<div class="row">
<div class="col-sm-9">
<div class="form-group">
<div class="form-group">
<label>Path</label>
<input id="ffmpegPath" ria-describedby="ffmpegHelp" type="text" class="form-control form-control-sm" ng-model="settings.ffmpegPath"></input>
<small class="form-text text-muted">
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.
<div class='loader' ng-show='ffmpegPathLoading'></div>
<input id="ffmpegPath" ria-describedby="ffmpegHelp" type="text" class="form-control form-control-sm" ng-model="ffmpegPath" readonly ng-hide="ffmpegPathLoading"></input>
<small class="form-text text-muted" ng-show="ffmpegPath != null">
The path to the ffmpeg executable. Please check the instructions if you need to change it.
</small>
</div>
</div>
</div>
<div class="col-sm-9">
<div class="form-group">
<input id="lockFfmpeg" type="checkbox" ng-model="settings.addLock"></input>
<label for="lockFfmpeg">Lock ffmpeg path setting</label>
<small class="form-text text-muted">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.</small>
</div>
</div>
</div>
<div class="row" ng-show="settings.lock === true">
<div class="col-sm-9">
<div class="form-group">
<div class="form-group">
<label>Path</label>
<input id="ffmpegPath" ria-describedby="ffmpegHelp" type="text" class="form-control form-control-sm" ng-model="settings.ffmpegPath" readonly></input>
<small class="form-text text-muted">
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 <b>--unlock</b> command line argument to enable editing it again.
<small class="form-text text-muted" ng-show="ffmpegPath == null">
dizqueTV uses FFmpeg to create video streams. Please check the instructions for info about how to set this up.
</small>
</div>
</div>
</div>