391 lines
15 KiB
JavaScript
391 lines
15 KiB
JavaScript
const fs = require('fs');
|
|
const exec = require('child_process').exec;
|
|
const execSync = require('child_process').execSync;
|
|
const spawn = require('child_process').spawn;
|
|
const treekill = require('tree-kill');
|
|
module.exports = (s,config,lang) => {
|
|
const {
|
|
mergeDeep,
|
|
validateIntValue,
|
|
} = require('../common.js')
|
|
const getPossibleWarnings = require('./possibleWarnings.js')
|
|
const activeProbes = {}
|
|
const runFFprobe = (url,auth,callback,forceOverlap,customInput) => {
|
|
var endData = {ok: false, result: {}}
|
|
if(!url){
|
|
endData.error = 'Missing URL'
|
|
if(callback)callback(endData)
|
|
return {
|
|
result: {
|
|
format: {
|
|
duration: 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(!forceOverlap && activeProbes[auth]){
|
|
endData.error = 'Account is already probing'
|
|
if(callback)callback(endData)
|
|
return {
|
|
result: {
|
|
format: {
|
|
duration: 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return new Promise((resolve) => {
|
|
activeProbes[auth] = 1
|
|
var stderr = ''
|
|
var stdout = ''
|
|
const probeCommand = splitForFFPMEG(`${customInput ? customInput + ' ' : ''}-analyzeduration 10000 -probesize 10000 -v quiet -print_format json -show_format -show_streams -i "${url}"`)
|
|
var processTimeout = null
|
|
var ffprobeLocation = config.ffmpegDir.split('/')
|
|
ffprobeLocation[ffprobeLocation.length - 1] = 'ffprobe'
|
|
ffprobeLocation = ffprobeLocation.join('/')
|
|
const probeProcess = spawn(ffprobeLocation, probeCommand)
|
|
const finishReponse = () => {
|
|
delete(activeProbes[auth])
|
|
if(!stdout){
|
|
endData.error = stderr
|
|
}else{
|
|
endData.ok = true
|
|
endData.result = s.parseJSON(stdout)
|
|
}
|
|
endData.probe = probeCommand
|
|
if(callback)callback(endData)
|
|
resolve(endData)
|
|
}
|
|
probeProcess.stderr.on('data',function(data){
|
|
stderr += data.toString()
|
|
})
|
|
probeProcess.stdout.on('data',function(data){
|
|
stdout += data.toString()
|
|
})
|
|
probeProcess.on('exit',function(){
|
|
clearTimeout(processTimeout)
|
|
finishReponse()
|
|
})
|
|
processTimeout = setTimeout(() => {
|
|
treekill(probeProcess.pid)
|
|
},10000)
|
|
})
|
|
}
|
|
const probeMonitor = (monitor,timeoutAmount,forceOverlap) => {
|
|
return new Promise((resolve,reject) => {
|
|
const inputTypeIsH264 = monitor.type === 'h264'
|
|
const protocolIsRtsp = monitor.protocol === 'rtsp'
|
|
const rtspTransportIsManual = monitor.details.rtsp_transport && monitor.details.rtsp_transport !== 'no'
|
|
const url = s.buildMonitorUrl(monitor);
|
|
runFFprobe(url,`${monitor.ke}${monitor.mid}`,(response) => {
|
|
setTimeout(() => {
|
|
resolve(response)
|
|
},timeoutAmount || 1000)
|
|
},forceOverlap,
|
|
inputTypeIsH264 && protocolIsRtsp && rtspTransportIsManual ? `-rtsp_transport ${monitor.details.rtsp_transport}` : null)
|
|
})
|
|
}
|
|
const getStreamInfoFromProbe = (probeResult) => {
|
|
const streamIndex = {
|
|
video: [],
|
|
audio: [],
|
|
all: []
|
|
}
|
|
const streams = probeResult.streams || []
|
|
streams.forEach((stream) => {
|
|
try{
|
|
const codecType = stream.codec_type || 'video'
|
|
if(!streamIndex[codecType])streamIndex[codecType] = []
|
|
const simpleInfo = {
|
|
fps: eval(stream.r_frame_rate) || '',
|
|
width: stream.coded_width,
|
|
height: stream.coded_height,
|
|
streamType: stream.codec_type,
|
|
codec: (stream.codec_name || '').toLowerCase(),
|
|
}
|
|
streamIndex.all.push(simpleInfo)
|
|
streamIndex[codecType].push(simpleInfo)
|
|
}catch(err){
|
|
s.debugLog(err)
|
|
}
|
|
})
|
|
if(streamIndex.video.length === 0){
|
|
streamIndex.video.push({
|
|
streamType: 'video',
|
|
codec: 'unknown',
|
|
})
|
|
}
|
|
return streamIndex
|
|
}
|
|
const createWarningsForConfiguration = (monitor,probeResult) => {
|
|
const warnings = []
|
|
const possibleWarnings = getPossibleWarnings(monitor,probeResult,config,lang)
|
|
possibleWarnings.forEach((warning) => {
|
|
if(warning.isTrue)warnings.push(warning)
|
|
})
|
|
return warnings
|
|
}
|
|
const buildMonitorConfigPartialFromWarnings = (warnings) => {
|
|
var configPartial = {}
|
|
warnings.forEach((warning) => {
|
|
if(warning.automaticChange)configPartial = mergeDeep(configPartial,warning.automaticChange)
|
|
})
|
|
return configPartial
|
|
}
|
|
const repairConfiguration = (monitor,probeResult) => {
|
|
const warnings = createWarningsForConfiguration(monitor,probeResult)
|
|
const configPartial = buildMonitorConfigPartialFromWarnings(warnings)
|
|
return mergeDeep(monitor,configPartial)
|
|
}
|
|
const applyPartialToConfiguration = (activeMonitor,configPartial) => {
|
|
Object.keys(configPartial).forEach((key) => {
|
|
if(key !== 'details'){
|
|
activeMonitor[key] = configPartial[key]
|
|
}else{
|
|
const details = s.parseJSON(configPartial.details)
|
|
Object.keys(details).forEach((key) => {
|
|
try{
|
|
activeMonitor.details[key] = details[key]
|
|
}catch(err){
|
|
console.log(err)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
const validateDimensions = (oldWidth,oldHeight) => {
|
|
const width = validateIntValue(oldWidth)
|
|
const height = validateIntValue(oldHeight)
|
|
return {
|
|
videoWidth: width,
|
|
videoHeight: height,
|
|
}
|
|
}
|
|
const sanitizedFfmpegCommand = (e,ffmpegCommandString) => {
|
|
var sanitizedCmd = `${ffmpegCommandString}`
|
|
if(e.details.muser && e.details.mpass){
|
|
sanitizedCmd = ffmpegCommandString
|
|
.replace(`//${e.details.muser}:${e.details.mpass}@`,'//')
|
|
.replace(`=${e.details.muser}`,'=USERNAME')
|
|
.replace(`=${e.details.mpass}`,'=PASSWORD')
|
|
}else if(e.details.muser){
|
|
sanitizedCmd = ffmpegCommandString.replace(`//${e.details.muser}:@`,'//').replace(`=${e.details.muser}`,'=USERNAME')
|
|
}
|
|
return sanitizedCmd
|
|
}
|
|
const createPipeArray = function(e, amountToAdd){
|
|
const stdioPipes = [];
|
|
var times = amountToAdd ? amountToAdd + config.pipeAddition : config.pipeAddition;
|
|
if(e.details && e.details.stream_channels){
|
|
times += e.details.stream_channels.length
|
|
}
|
|
for(var i=0; i < times; i++){
|
|
stdioPipes.push('pipe')
|
|
}
|
|
return stdioPipes
|
|
}
|
|
const splitForFFPMEG = function(ffmpegCommandAsString) {
|
|
return ffmpegCommandAsString.replace(/\s+/g,' ').trim().match(/\\?.|^$/g).reduce((p, c) => {
|
|
if(c === '"'){
|
|
p.quote ^= 1;
|
|
}else if(!p.quote && c === ' '){
|
|
p.a.push('');
|
|
}else{
|
|
p.a[p.a.length-1] += c.replace(/\\(.)/,"$1");
|
|
}
|
|
return p;
|
|
}, {a: ['']}).a
|
|
}
|
|
//check local ffmpeg
|
|
const checkForWindows = function(){
|
|
s.debugLog('ffmpeg.js : checkForWindows')
|
|
const ffmpegPath = s.mainDirectory + '/ffmpeg/ffmpeg.exe'
|
|
const hasFfmpeg = s.isWin && fs.existsSync(ffmpegPath)
|
|
const response = {
|
|
ok: hasFfmpeg
|
|
}
|
|
if (hasFfmpeg) {
|
|
config.ffmpegDir = ffmpegPath
|
|
}
|
|
return response
|
|
}
|
|
//check local ffmpeg
|
|
const checkForUnix = function(){
|
|
s.debugLog('ffmpeg.js : checkForUnix')
|
|
const response = {
|
|
ok: false
|
|
}
|
|
if(s.isWin === false){
|
|
if (fs.existsSync('/usr/bin/ffmpeg')) {
|
|
response.ok = true
|
|
config.ffmpegDir = '/usr/bin/ffmpeg'
|
|
}else{
|
|
if (fs.existsSync('/usr/local/bin/ffmpeg')) {
|
|
response.ok = true
|
|
config.ffmpegDir = '/usr/local/bin/ffmpeg'
|
|
}
|
|
}
|
|
}
|
|
return response
|
|
}
|
|
//check node module : ffmpeg-static
|
|
const checkForNpmStatic = function(){
|
|
s.debugLog('ffmpeg.js : checkForNpmStatic')
|
|
const response = {
|
|
ok: false
|
|
}
|
|
try{
|
|
var staticFFmpeg = require('ffmpeg-static');
|
|
staticFFmpeg = staticFFmpeg.path ? staticFFmpeg.path : staticFFmpeg
|
|
if (fs.statSync(staticFFmpeg)) {
|
|
response.ok = true
|
|
config.ffmpegDir = staticFFmpeg
|
|
}else{
|
|
response.msg = `"ffmpeg-static" from NPM has failed to provide a compatible library or has been corrupted.
|
|
Run "npm uninstall ffmpeg-static" to remove it.
|
|
Run "npm install ffbinaries" to get a different static FFmpeg downloader.`
|
|
}
|
|
}catch(err){
|
|
response.error = err
|
|
response.msg = 'No "ffmpeg-static".'
|
|
}
|
|
return response
|
|
}
|
|
//check node module : ffbinaries
|
|
const checkForFfbinary = function(){
|
|
s.debugLog('ffmpeg.js : checkForFfbinary')
|
|
const response = {
|
|
ok: false
|
|
}
|
|
return new Promise((resolve,reject) => {
|
|
try{
|
|
ffbinaries = require('ffbinaries')
|
|
var ffbinaryDir = s.mainDirectory + '/ffmpeg/'
|
|
if (!fs.existsSync(ffbinaryDir + 'ffmpeg')) {
|
|
console.log('ffbinaries : Downloading FFmpeg. Please Wait...');
|
|
ffbinaries.downloadBinaries(['ffmpeg', 'ffprobe'], {
|
|
destination: ffbinaryDir,
|
|
version : '4.2'
|
|
},function () {
|
|
config.ffmpegDir = ffbinaryDir + 'ffmpeg'
|
|
response.msg = 'ffbinaries : FFmpeg Downloaded.'
|
|
response.ok = true
|
|
resolve(response)
|
|
})
|
|
}else{
|
|
response.msg = 'ffbinaries : Found.'
|
|
response.ok = true
|
|
config.ffmpegDir = ffbinaryDir + 'ffmpeg'
|
|
resolve(response)
|
|
}
|
|
}catch(err){
|
|
response.msg = `No "ffbinaries". Continuing.
|
|
Run "npm install ffbinaries" to get this static FFmpeg downloader.`
|
|
resolve(response)
|
|
}
|
|
})
|
|
}
|
|
const checkStaticBuilds = async () => {
|
|
s.debugLog('ffmpeg.js : checkStaticBuilds')
|
|
const response = {
|
|
ok: false,
|
|
msg: []
|
|
}
|
|
const ffbinaryCheck = await checkForFfbinary()
|
|
if(!ffbinaryCheck.ok){
|
|
// needs ffprobe solution
|
|
// const npmStaticCheck = checkForNpmStatic()
|
|
// if(npmStaticCheck.ok){
|
|
// response.ok = true
|
|
// }
|
|
// if(npmStaticCheck.msg)response.msg.push(npmStaticCheck.msg)
|
|
}else{
|
|
response.ok = true
|
|
}
|
|
if(ffbinaryCheck.msg)response.msg.push(ffbinaryCheck.msg)
|
|
return response
|
|
}
|
|
//ffmpeg version
|
|
const checkVersion = function(callback){
|
|
s.debugLog('ffmpeg.js : checkVersion')
|
|
const response = {
|
|
ok: false
|
|
}
|
|
try{
|
|
s.ffmpegVersion = execSync(config.ffmpegDir+" -version").toString().split('Copyright')[0].replace('ffmpeg version','').trim()
|
|
if(s.ffmpegVersion.indexOf(': 2.')>-1){
|
|
s.systemLog('FFMPEG is too old : '+s.ffmpegVersion+', Needed : 3.2+',err)
|
|
throw (new Error())
|
|
}else{
|
|
response.ok = true
|
|
}
|
|
}catch(err){
|
|
console.log('No FFmpeg found.')
|
|
// process.exit()
|
|
}
|
|
return response
|
|
}
|
|
//check available hardware acceleration methods
|
|
const checkHwAccelMethods = function(){
|
|
s.debugLog('ffmpeg.js : checkHwAccelMethods')
|
|
const response = {
|
|
ok: true
|
|
}
|
|
if(config.availableHWAccels === undefined){
|
|
const hwAccels = execSync(config.ffmpegDir+" -loglevel quiet -hwaccels").toString().split('\n')
|
|
hwAccels.shift()
|
|
availableHWAccels = []
|
|
hwAccels.forEach(function(method){
|
|
if(method && method !== '')availableHWAccels.push(method.trim())
|
|
})
|
|
config.availableHWAccels = availableHWAccels
|
|
config.availableHWAccels = ['auto'].concat(config.availableHWAccels)
|
|
console.log('Available Hardware Acceleration Methods : ',availableHWAccels.join(', '))
|
|
var methods = {
|
|
auto: {label:lang['Auto'],value:'auto'},
|
|
drm: {label:lang['drm'],value:'drm'},
|
|
cuvid: {label:lang['cuvid'],value:'cuvid'},
|
|
cuda: {label:lang['cuda'],value:'cuda'},
|
|
opencl: {label:lang['opencl'],value:'opencl'},
|
|
vaapi: {label:lang['vaapi'],value:'vaapi'},
|
|
qsv: {label:lang['qsv'],value:'qsv'},
|
|
vdpau: {label:lang['vdpau'],value:'vdpau'},
|
|
dxva2: {label:lang['dxva2'],value:'dxva2'},
|
|
vdpau: {label:lang['vdpau'],value:'vdpau'},
|
|
videotoolbox: {label:lang['videotoolbox'],value:'videotoolbox'}
|
|
}
|
|
s.listOfHwAccels = []
|
|
config.availableHWAccels.forEach(function(availibleMethod){
|
|
if(methods[availibleMethod]){
|
|
var method = methods[availibleMethod]
|
|
s.listOfHwAccels.push({
|
|
name: method.label,
|
|
value: method.value,
|
|
})
|
|
}
|
|
})
|
|
}
|
|
return response
|
|
}
|
|
return {
|
|
ffprobe: runFFprobe,
|
|
probeMonitor: probeMonitor,
|
|
getStreamInfoFromProbe: getStreamInfoFromProbe,
|
|
createWarningsForConfiguration: createWarningsForConfiguration,
|
|
buildMonitorConfigPartialFromWarnings: buildMonitorConfigPartialFromWarnings,
|
|
applyPartialToConfiguration: applyPartialToConfiguration,
|
|
repairConfiguration: repairConfiguration,
|
|
validateDimensions: validateDimensions,
|
|
sanitizedFfmpegCommand: sanitizedFfmpegCommand,
|
|
createPipeArray: createPipeArray,
|
|
splitForFFPMEG: splitForFFPMEG,
|
|
checkForWindows: checkForWindows,
|
|
checkForUnix: checkForUnix,
|
|
checkForNpmStatic: checkForNpmStatic,
|
|
checkForFfbinary: checkForFfbinary,
|
|
checkStaticBuilds: checkStaticBuilds,
|
|
checkVersion: checkVersion,
|
|
checkHwAccelMethods: checkHwAccelMethods,
|
|
}
|
|
}
|