Shinobi/camera.js

7460 lines
357 KiB
JavaScript

//
// Shinobi
// Copyright (C) 2016 Moe Alam, moeiscool
//
//
// # Donate
//
// If you like what I am doing here and want me to continue please consider donating :)
// PayPal : paypal@m03.ca
//
var fs = require('fs');
process.on('uncaughtException', function (err) {
console.error('Uncaught Exception occured!');
console.error(err.stack);
});
var staticFFmpeg = false;
try{
staticFFmpeg = require('ffmpeg-static').path;
if (!fs.existsSync(staticFFmpeg)) {
staticFFmpeg = false
console.log('"ffmpeg-static" from NPM has failed to provide a compatible library or has been corrupted.')
console.log('You may need to install FFmpeg manually or you can try running "npm uninstall ffmpeg-static && npm install ffmpeg-static".')
}
}catch(err){
staticFFmpeg = false;
console.log('No Static FFmpeg. Continuing.')
//no static ffmpeg
}
var os = require('os');
var URL = require('url');
var path = require('path');
var mysql = require('mysql');
var moment = require('moment');
var request = require("request");
var express = require('express');
var app = express();
var http = require('http');
var https = require('https');
var server = http.createServer(app);
var bodyParser = require('body-parser');
var CircularJSON = require('circular-json');
var ejs = require('ejs');
var io = new (require('socket.io'))();
var execSync = require('child_process').execSync;
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
var socketIOclient = require('socket.io-client');
var crypto = require('crypto');
var webdav = require("webdav");
var jsonfile = require("jsonfile");
var connectionTester = require('connection-tester');
var events = require('events');
var onvif = require('node-onvif');
var knex = require('knex');
var Mp4Frag = require('mp4frag');
var P2P = require('pipe2pam');
var PamDiff = require('pam-diff');
var httpProxy = require('http-proxy');
var proxy = httpProxy.createProxyServer({})
var location = {}
location.super = __dirname+'/super.json'
location.config = __dirname+'/conf.json'
location.languages = __dirname+'/languages'
location.definitions = __dirname+'/definitions'
var config = require(location.config);
if(!config.productType){
config.productType='CE'
}
if(config.productType==='Pro'){
var LdapAuth = require('ldapauth-fork');
}
if(!config.language){
config.language='en_CA'
}
try{
var lang = require(location.languages+'/'+config.language+'.json');
}catch(er){
console.error(er)
console.log('There was an error loading your language file.')
var lang = require(location.languages+'/en_CA.json');
}
try{
var definitions = require(location.definitions+'/'+config.language+'.json');
}catch(er){
console.error(er)
console.log('There was an error loading your language file.')
var definitions = require(location.definitions+'/en_CA.json');
}
process.send = process.send || function () {};
if(config.mail){
if(config.mail.from === undefined){config.mail.from = '"ShinobiCCTV" <no-reply@shinobi.video>'}
var nodemailer = require('nodemailer').createTransport(config.mail);
}
//config defaults
if(config.cpuUsageMarker===undefined){config.cpuUsageMarker='%Cpu'}
if(config.customCpuCommand===undefined){config.customCpuCommand=null}
if(config.autoDropCache===undefined){config.autoDropCache=true}
if(config.doSnapshot===undefined){config.doSnapshot=true}
if(config.restart===undefined){config.restart={}}
if(config.systemLog===undefined){config.systemLog=true}
if(config.deleteCorruptFiles===undefined){config.deleteCorruptFiles=true}
if(config.restart.onVideoNotExist===undefined){config.restart.onVideoNotExist=true}
if(config.ip===undefined||config.ip===''||config.ip.indexOf('0.0.0.0')>-1){config.ip='localhost'}else{config.bindip=config.ip};
if(config.cron===undefined)config.cron={};
if(config.cron.enabled===undefined)config.cron.enabled=true;
if(config.cron.deleteOld===undefined)config.cron.deleteOld=true;
if(config.cron.deleteOrphans===undefined)config.cron.deleteOrphans=false;
if(config.cron.deleteNoVideo===undefined)config.cron.deleteNoVideo=true;
if(config.cron.deleteNoVideoRecursion===undefined)config.cron.deleteNoVideoRecursion=false;
if(config.cron.deleteOverMax===undefined)config.cron.deleteOverMax=true;
if(config.cron.deleteOverMaxOffset===undefined)config.cron.deleteOverMaxOffset=0.9;
if(config.cron.deleteLogs===undefined)config.cron.deleteLogs=true;
if(config.cron.deleteEvents===undefined)config.cron.deleteEvents=true;
if(config.cron.deleteFileBins===undefined)config.cron.deleteFileBins=true;
if(config.cron.interval===undefined)config.cron.interval=1;
if(config.databaseType===undefined){config.databaseType='mysql'}
if(config.pluginKeys===undefined)config.pluginKeys={};
if(config.databaseLogs===undefined){config.databaseLogs=false}
if(config.useUTC===undefined){config.useUTC=false}
if(config.pipeAddition===undefined){config.pipeAddition=7}else{config.pipeAddition=parseInt(config.pipeAddition)}
//Web Paths
if(config.webPaths===undefined){config.webPaths={}}
//main access URI
if(config.webPaths.home===undefined){config.webPaths.index='/'}
//Super User URI
if(config.webPaths.super===undefined){config.webPaths.super='/super'}
//Admin URI
if(config.webPaths.admin===undefined){config.webPaths.admin='/admin'}
//Page Rander Paths
if(config.renderPaths===undefined){config.renderPaths={}}
//login page
if(config.renderPaths.index===undefined){config.renderPaths.index='pages/index'}
//dashboard page
if(config.renderPaths.home===undefined){config.renderPaths.home='pages/home'}
//sub-account administration page
if(config.renderPaths.admin===undefined){config.renderPaths.admin='pages/admin'}
//superuser page
if(config.renderPaths.super===undefined){config.renderPaths.super='pages/super'}
//2-Factor Auth page
if(config.renderPaths.factorAuth===undefined){config.renderPaths.factorAuth='pages/factor'}
//Streamer (Dashbcam Prototype) page
if(config.renderPaths.streamer===undefined){config.renderPaths.streamer='pages/streamer'}
//Streamer v2 (Dashbcam) page
if(config.renderPaths.dashcam===undefined){config.renderPaths.dashcam='pages/dashcam'}
//embeddable widget page
if(config.renderPaths.embed===undefined){config.renderPaths.embed='pages/embed'}
//mjpeg full screen page
if(config.renderPaths.mjpeg===undefined){config.renderPaths.mjpeg='pages/mjpeg'}
//gridstack only page
if(config.renderPaths.grid===undefined){config.renderPaths.grid='pages/grid'}
//Child Nodes
if(config.childNodes===undefined)config.childNodes = {};
//enabled
if(config.childNodes.enabled===undefined)config.childNodes.enabled = false;
//mode, set value as `child` for all other machines in the cluster
if(config.childNodes.mode===undefined)config.childNodes.mode = 'master';
//child node connection port
if(config.childNodes.port===undefined)config.childNodes.port = 8288;
//child node connection key
if(config.childNodes.key===undefined)config.childNodes.key = [
'3123asdasdf1dtj1hjk23sdfaasd12asdasddfdbtnkkfgvesra3asdsd3123afdsfqw345'
];
s={
factorAuth : {},
totalmem : os.totalmem(),
platform : os.platform(),
s : JSON.stringify,
isWin : (process.platform==='win32'),
utcOffset : moment().utcOffset()
};
//load languages dynamically
s.loadedLanguages={}
s.loadedLanguages[config.language]=lang;
s.getLanguageFile=function(rule){
if(rule&&rule!==''){
var file=s.loadedLanguages[file]
if(!file){
try{
s.loadedLanguages[rule]=require(location.languages+'/'+rule+'.json')
file=s.loadedLanguages[rule]
}catch(err){
file=lang
}
}
}else{
file=lang
}
return file
}
//load defintions dynamically
s.loadedDefinitons={}
s.loadedDefinitons[config.language]=definitions;
s.getDefinitonFile=function(rule){
if(rule&&rule!==''){
var file=s.loadedDefinitons[file]
if(!file){
try{
s.loadedDefinitons[rule]=require(location.definitions+'/'+rule+'.json')
file=s.loadedDefinitons[rule]
}catch(err){
file=definitions
}
}
}else{
file=definitions
}
return file
}
var databaseOptions = {
client: config.databaseType,
connection: config.db,
}
if(databaseOptions.client.indexOf('sqlite')>-1){
databaseOptions.client = 'sqlite3';
databaseOptions.useNullAsDefault = true;
}
if(databaseOptions.client === 'sqlite3' && databaseOptions.connection.filename === undefined){
databaseOptions.connection.filename = __dirname+"/shinobi.sqlite"
}
s.databaseEngine = knex(databaseOptions)
s.mergeQueryValues = function(query,values){
if(!values){values=[]}
var valuesNotFunction = true;
if(typeof values === 'function'){
var values = [];
valuesNotFunction = false;
}
if(values&&valuesNotFunction){
var splitQuery = query.split('?')
var newQuery = ''
splitQuery.forEach(function(v,n){
newQuery += v
var value = values[n]
if(value){
if(isNaN(value) || value instanceof Date){
newQuery += "'"+value+"'"
}else{
newQuery += value
}
}
})
}else{
newQuery = query
}
return newQuery
}
s.stringToSqlTime = function(value){
newValue = new Date(value.replace('T',' '))
return newValue
}
s.sqlQuery = function(query,values,onMoveOn){
if(!values){values=[]}
if(typeof values === 'function'){
var onMoveOn = values;
var values = [];
}
if(!onMoveOn){onMoveOn=function(){}}
var mergedQuery = s.mergeQueryValues(query,values)
s.debugLog('s.sqlQuery QUERY',mergedQuery)
return s.databaseEngine
.raw(query,values)
.asCallback(function(err,r){
if(err){
console.log('s.sqlQuery QUERY ERRORED',query)
console.log('s.sqlQuery ERROR',err)
}
if(onMoveOn && typeof onMoveOn === 'function'){
switch(databaseOptions.client){
case'sqlite3':
if(!r)r=[]
break;
default:
if(r)r=r[0]
break;
}
onMoveOn(err,r)
}
})
}
//discord bot
if(config.discordBot === true){
try{
var Discord = require("discord.js")
s.discordMsg = function(data,files,groupKey){
if(!data)data = {};
var bot = s.group[groupKey].discordBot
if(!bot){
s.log({ke:groupKey,mid:'$USER'},{type:lang.DiscordFailedText,msg:lang.DiscordNotEnabledText})
return
}
var sendBody = Object.assign({
color: 3447003,
title: 'Alert from Shinobi',
description: "",
fields: [],
timestamp: new Date(),
footer: {
icon_url: "https://shinobi.video/libs/assets/icon/apple-touch-icon-152x152.png",
text: "Shinobi Systems"
}
},data)
bot.channels.get(s.group[groupKey].init.discordbot_channel).send({
embed: sendBody,
files: files
})
}
}catch(err){
console.log('Could not start Discord bot, please run "npm install discord.js" inside the Shinobi folder.')
s.discordMsg = function(){}
}
}
//kill any ffmpeg running
s.ffmpegKill=function(){
var cmd=''
if(s.isWin===true){
cmd="Taskkill /IM ffmpeg.exe /F"
}else{
cmd="ps aux | grep -ie ffmpeg | awk '{print $2}' | xargs kill -9"
}
exec(cmd,{detached: true})
};
process.on('exit',s.ffmpegKill.bind(null,{cleanup:true}));
process.on('SIGINT',s.ffmpegKill.bind(null, {exit:true}));
s.checkRelativePath=function(x){
if(x.charAt(0)!=='/'){
x=__dirname+'/'+x
}
return x
}
s.checkCorrectPathEnding=function(x){
var length=x.length
if(x.charAt(length-1)!=='/'){
x=x+'/'
}
return x.replace('__DIR__',__dirname)
}
s.md5=function(x){return crypto.createHash('md5').update(x).digest("hex");}
//send data to detector plugin
s.ocvTx=function(data){
if(!s.ocv){return}
if(s.ocv.isClientPlugin===true){
s.tx(data,s.ocv.id)
}else{
s.connectedPlugins[s.ocv.plug].tx(data)
}
}
//send data to socket client function
s.tx = function(z,y,x){if(x){return x.broadcast.to(y).emit('f',z)};io.to(y).emit('f',z);}
s.txToDashcamUsers = function(data,groupKey){
Object.keys(s.group[groupKey].dashcamUsers).forEach(function(auth){
s.tx(data,s.group[groupKey].dashcamUsers[auth].cnid)
})
}
s.txWithSubPermissions = function(z,y,permissionChoices){
if(typeof permissionChoices==='string'){
permissionChoices=[permissionChoices]
}
if(s.group[z.ke]){
Object.keys(s.group[z.ke].users).forEach(function(v){
var user = s.group[z.ke].users[v]
if(user.details.sub){
if(user.details.allmonitors!=='1'){
var valid=0
var checked=permissionChoices.length
permissionChoices.forEach(function(b){
if(user.details[b] && user.details[b].indexOf(z.mid)!==-1){
++valid
}
})
if(valid===checked){
s.tx(z,user.cnid)
}
}else{
s.tx(z,user.cnid)
}
}else{
s.tx(z,user.cnid)
}
})
}
}
//load camera controller vars
s.nameToTime=function(x){x=x.split('.')[0].split('T'),x[1]=x[1].replace(/-/g,':');x=x.join(' ');return x;}
s.ratio=function(width,height,ratio){ratio = width / height;return ( Math.abs( ratio - 4 / 3 ) < Math.abs( ratio - 16 / 9 ) ) ? '4:3' : '16:9';}
s.randomNumber=function(x){
if(!x){x=10};
return Math.floor((Math.random() * x) + 1);
};
s.gid=function(x){
if(!x){x=10};var t = "";var p = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < x; i++ )
t += p.charAt(Math.floor(Math.random() * p.length));
return t;
};
s.nid=function(x){
if(!x){x=6};var t = "";var p = "0123456789";
for( var i=0; i < x; i++ )
t += p.charAt(Math.floor(Math.random() * p.length));
return t;
};
s.formattedTime_withOffset=function(e,x){
if(!e){e=new Date};if(!x){x='YYYY-MM-DDTHH-mm-ss'};
e=s.timeObject(e);if(config.utcOffset){e=e.utcOffset(config.utcOffset)}
return e.format(x);
}
s.formattedTime=function(e,x){
if(!e){e=new Date};if(!x){x='YYYY-MM-DDTHH-mm-ss'};
return s.timeObject(e).format(x);
}
s.utcToLocal = function(time){
return moment.utc(time).utcOffset(s.utcOffset).format()
}
s.localTimeObject = function(e,x){
return moment(e)
}
if(config.useUTC === true){
s.timeObject = function(time){
return moment(time).utc()
}
}else{
s.timeObject = moment
}
console.log('config.useUTC',config.useUTC)
s.ipRange=function(start_ip, end_ip) {
var start_long = s.toLong(start_ip);
var end_long = s.toLong(end_ip);
if (start_long > end_long) {
var tmp=start_long;
start_long=end_long
end_long=tmp;
}
var range_array = [];
var i;
for (i=start_long; i<=end_long;i++) {
range_array.push(s.fromLong(i));
}
return range_array;
}
s.portRange=function(lowEnd,highEnd){
var list = [];
for (var i = lowEnd; i <= highEnd; i++) {
list.push(i);
}
return list;
}
//toLong taken from NPM package 'ip'
s.toLong=function(ip) {
var ipl = 0;
ip.split('.').forEach(function(octet) {
ipl <<= 8;
ipl += parseInt(octet);
});
return(ipl >>> 0);
};
//fromLong taken from NPM package 'ip'
s.fromLong=function(ipl) {
return ((ipl >>> 24) + '.' +
(ipl >> 16 & 255) + '.' +
(ipl >> 8 & 255) + '.' +
(ipl & 255) );
};
s.getFunctionParamNames = function(func) {
var fnStr = func.toString().replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg, '');
var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(/([^\s,]+)/g);
if(result === null)
result = [];
return result;
}
s.getDetectorStreams = function(monitor){
var pathDir = s.dir.streams+monitor.ke+'/'+monitor.id+'/'
var streamDirItems = fs.readdirSync(pathDir)
var items = []
streamDirItems.forEach(function(filename){
if(filename.indexOf('detectorStream') > -1 && filename.indexOf('.m3u8') === -1){
try{
items.push(pathDir+filename)
}catch(err){
console.log(err)
}
}
})
return items
}
s.createPamDiffEngine = function(e){
var width,
height,
globalSensitivity,
fullFrame = false
if(s.group[e.ke].mon_conf[e.id].details.detector_scale_x===''||s.group[e.ke].mon_conf[e.id].details.detector_scale_y===''){
width = s.group[e.ke].mon_conf[e.id].details.detector_scale_x;
height = s.group[e.ke].mon_conf[e.id].details.detector_scale_y;
}else{
width = e.width
height = e.height
}
if(e.details.detector_sensitivity===''){
globalSensitivity = 10
}else{
globalSensitivity = parseInt(e.details.detector_sensitivity)
}
if(e.details.detector_frame==='1'){
fullFrame={
name:'FULL_FRAME',
sensitivity:globalSensitivity,
points:[
[0,0],
[0,height],
[width,height],
[width,0]
]
};
}
var regions = s.createPamDiffRegionArray(s.group[e.ke].mon_conf[e.id].details.cords,globalSensitivity,fullFrame);
if(!s.group[e.ke].mon[e.id].noiseFilterArray)s.group[e.ke].mon[e.id].noiseFilterArray = {}
var noiseFilterArray = s.group[e.ke].mon[e.id].noiseFilterArray
Object.keys(regions.notForPam).forEach(function(name){
if(!noiseFilterArray[name])noiseFilterArray[name]=[];
})
s.group[e.ke].mon[e.id].pamDiff = new PamDiff({grayscale: 'luminosity', regions : regions.forPam});
s.group[e.ke].mon[e.id].p2p = new P2P();
var sendTrigger = function(trigger){
var detectorObject = {
f:'trigger',
id:e.id,
ke:e.ke,
name:trigger.name,
details:{
plug:'built-in',
name:trigger.name,
reason:'motion',
confidence:trigger.percent,
},
plates:[],
imgHeight:height,
imgWidth:width
}
detectorObject.doObjectDetection = (s.ocv && e.details.detector_use_detect_object === '1')
s.camera('motion',detectorObject)
if(detectorObject.doObjectDetection === true){
s.ocvTx({f:'frame',mon:s.group[e.ke].mon_conf[e.id].details,ke:e.ke,id:e.id,time:s.formattedTime(),frame:s.group[e.ke].mon[e.id].lastJpegDetectorFrame});
}
}
var filterTheNoise = function(trigger){
if(noiseFilterArray[trigger.name].length > 2){
var thePreviousTriggerPercent = noiseFilterArray[trigger.name][noiseFilterArray[trigger.name].length - 1];
var triggerDifference = trigger.percent - thePreviousTriggerPercent;
var noiseRange = e.details.detector_noise_filter_range
if(!noiseRange || noiseRange === ''){
noiseRange = 6
}
noiseRange = parseFloat(noiseRange)
if(((trigger.percent - thePreviousTriggerPercent) < noiseRange)||(thePreviousTriggerPercent - trigger.percent) > -noiseRange){
noiseFilterArray[trigger.name].push(trigger.percent);
}
}else{
noiseFilterArray[trigger.name].push(trigger.percent);
}
if(noiseFilterArray[trigger.name].length > 10){
noiseFilterArray[trigger.name] = noiseFilterArray[trigger.name].splice(1,10)
}
var theNoise = 0;
noiseFilterArray[trigger.name].forEach(function(v,n){
theNoise += v;
})
theNoise = theNoise / noiseFilterArray[trigger.name].length;
// console.log(noiseFilterArray[trigger.name])
// console.log(theNoise)
var triggerPercentWithoutNoise = trigger.percent - theNoise;
if(triggerPercentWithoutNoise > regions.notForPam[trigger.name].sensitivity){
sendTrigger(trigger);
}
}
if(e.details.detector_noise_filter==='1'){
s.group[e.ke].mon[e.id].pamDiff.on('diff', (data) => {
data.trigger.forEach(filterTheNoise)
})
}else{
s.group[e.ke].mon[e.id].pamDiff.on('diff', (data) => {
data.trigger.forEach(sendTrigger)
})
}
}
s.createPamDiffRegionArray = function(regions,globalSensitivity,fullFrame){
var pamDiffCompliantArray = [],
arrayForOtherStuff = [],
json
try{
json = JSON.parse(regions)
}catch(err){
json = regions
}
if(fullFrame){
json[fullFrame.name]=fullFrame;
}
Object.values(json).forEach(function(region){
region.polygon = [];
region.points.forEach(function(points){
region.polygon.push({x:parseFloat(points[0]),y:parseFloat(points[1])})
})
if(region.sensitivity===''){
region.sensitivity = globalSensitivity
}else{
region.sensitivity = parseInt(region.sensitivity)
}
pamDiffCompliantArray.push({name: region.name, difference: 9, percent: region.sensitivity, polygon:region.polygon})
arrayForOtherStuff[region.name] = region;
})
if(pamDiffCompliantArray.length===0){pamDiffCompliantArray = null}
return {forPam:pamDiffCompliantArray,notForPam:arrayForOtherStuff};
}
s.getRequest = function(url,callback){
return http.get(url, function(res){
var body = '';
res.on('data', function(chunk){
body += chunk;
});
res.on('end',function(){
try{body = JSON.parse(body)}catch(err){}
callback(body)
});
}).on('error', function(e){
// s.systemLog("Get Snapshot Error", e);
});
}
s.kill = function(x,e,p){
if(s.group[e.ke]&&s.group[e.ke].mon[e.id]&&s.group[e.ke].mon[e.id].spawn !== undefined){
if(s.group[e.ke].mon[e.id].spawn){
s.group[e.ke].mon[e.id].allowStdinWrite = false
s.txToDashcamUsers({
f : 'disable_stream',
ke : e.ke,
mid : e.id
},e.ke)
s.group[e.ke].mon[e.id].spawn.stdio[3].unpipe();
// if(s.group[e.ke].mon[e.id].p2pStream){s.group[e.ke].mon[e.id].p2pStream.unpipe();}
if(s.group[e.ke].mon[e.id].p2p){s.group[e.ke].mon[e.id].p2p.unpipe();}
delete(s.group[e.ke].mon[e.id].p2pStream)
delete(s.group[e.ke].mon[e.id].p2p)
delete(s.group[e.ke].mon[e.id].pamDiff)
try{
s.group[e.ke].mon[e.id].spawn.removeListener('end',s.group[e.ke].mon[e.id].spawn_exit);
s.group[e.ke].mon[e.id].spawn.removeListener('exit',s.group[e.ke].mon[e.id].spawn_exit);
delete(s.group[e.ke].mon[e.id].spawn_exit);
}catch(er){}
}
clearTimeout(s.group[e.ke].mon[e.id].checker);
delete(s.group[e.ke].mon[e.id].checker);
clearTimeout(s.group[e.ke].mon[e.id].checkStream);
delete(s.group[e.ke].mon[e.id].checkStream);
clearTimeout(s.group[e.ke].mon[e.id].checkSnap);
delete(s.group[e.ke].mon[e.id].checkSnap);
clearTimeout(s.group[e.ke].mon[e.id].watchdog_stop);
delete(s.group[e.ke].mon[e.id].watchdog_stop);
delete(s.group[e.ke].mon[e.id].lastJpegDetectorFrame);
if(e&&s.group[e.ke].mon[e.id].record){
clearTimeout(s.group[e.ke].mon[e.id].record.capturing);
// if(s.group[e.ke].mon[e.id].record.request){s.group[e.ke].mon[e.id].record.request.abort();delete(s.group[e.ke].mon[e.id].record.request);}
};
if(s.group[e.ke].mon[e.id].childNode){
s.cx({f:'kill',d:s.init('noReference',e)},s.group[e.ke].mon[e.id].childNodeId)
}else{
s.coSpawnClose(e)
if(!x||x===1){return};
p=x.pid;
if(s.group[e.ke].mon_conf[e.id].type===('dashcam'||'socket'||'jpeg'||'pipe')){
x.stdin.pause();setTimeout(function(){x.kill('SIGTERM');},500)
}else{
try{
x.stdin.setEncoding('utf8');x.stdin.write('q');
}catch(er){}
}
setTimeout(function(){exec('kill -9 '+p,{detached: true})},1000)
}
}
}
//user log
s.log=function(e,x){
if(!x||!e.mid){return}
if((e.details&&e.details.sqllog==='1')||e.mid.indexOf('$')>-1){
s.sqlQuery('INSERT INTO Logs (ke,mid,info) VALUES (?,?,?)',[e.ke,e.mid,s.s(x)]);
}
s.tx({f:'log',ke:e.ke,mid:e.mid,log:x,time:s.timeObject()},'GRPLOG_'+e.ke);
// s.systemLog('s.log : ',{f:'log',ke:e.ke,mid:e.mid,log:x,time:s.timeObject()},'GRP_'+e.ke)
}
//system log
s.systemLog = function(q,w,e){
if(!w){w=''}
if(!e){e=''}
if(config.systemLog===true){
if(typeof q==='string'&&s.databaseEngine){
s.sqlQuery('INSERT INTO Logs (ke,mid,info) VALUES (?,?,?)',['$','$SYSTEM',s.s({type:q,msg:w})]);
s.tx({f:'log',log:{time:s.timeObject(),ke:'$',mid:'$SYSTEM',time:s.timeObject(),info:s.s({type:q,msg:w})}},'$');
}
return console.log(s.timeObject().format(),q,w,e)
}
}
//system log
s.debugLog = function(q,w,e){
if(!w){w = ''}
if(!e){e = ''}
if(config.debugLog === true){
console.log(s.timeObject().format(),q,w,e)
if(config.debugLogVerbose === true){
console.log(new Error())
}
}
}
//SSL options
if(config.ssl&&config.ssl.key&&config.ssl.cert){
config.ssl.key=fs.readFileSync(s.checkRelativePath(config.ssl.key),'utf8')
config.ssl.cert=fs.readFileSync(s.checkRelativePath(config.ssl.cert),'utf8')
if(config.ssl.port===undefined){
config.ssl.port=443
}
if(config.ssl.bindip===undefined){
config.ssl.bindip=config.bindip
}
if(config.ssl.ca&&config.ssl.ca instanceof Array){
config.ssl.ca.forEach(function(v,n){
config.ssl.ca[n]=fs.readFileSync(s.checkRelativePath(v),'utf8')
})
}
var serverHTTPS = https.createServer(config.ssl,app);
serverHTTPS.listen(config.ssl.port,config.bindip,function(){
console.log('SSL '+lang.Shinobi+' - SSL PORT : '+config.ssl.port);
});
io.attach(serverHTTPS);
}
//start HTTP
server.listen(config.port,config.bindip,function(){
console.log(lang.Shinobi+' - PORT : '+config.port);
});
io.attach(server);
console.log('NODE.JS version : '+execSync("node -v"))
//ffmpeg location
if(!config.ffmpegDir){
if(staticFFmpeg !== false){
config.ffmpegDir = staticFFmpeg
}else{
if(s.isWin===true){
config.ffmpegDir = __dirname+'/ffmpeg/ffmpeg.exe'
}else{
config.ffmpegDir = 'ffmpeg'
}
}
}
//ffmpeg version
s.ffmpegVersion=execSync(config.ffmpegDir+" -version").toString().split('Copyright')[0].replace('ffmpeg version','').trim()
console.log('FFMPEG version : '+s.ffmpegVersion)
if(s.ffmpegVersion.indexOf(': 2.')>-1){
s.systemLog('FFMPEG is too old : '+s.ffmpegVersion+', Needed : 3.2+',err)
throw (new Error())
}
//directories
s.group={};
if(!config.windowsTempDir&&s.isWin===true){config.windowsTempDir='C:/Windows/Temp'}
if(!config.defaultMjpeg){config.defaultMjpeg=__dirname+'/web/libs/img/bg.jpg'}
//default stream folder check
if(!config.streamDir){
if(s.isWin===false){
config.streamDir='/dev/shm'
}else{
config.streamDir=config.windowsTempDir
}
if(!fs.existsSync(config.streamDir)){
config.streamDir=__dirname+'/streams/'
}else{
config.streamDir+='/streams/'
}
}
if(!config.videosDir){config.videosDir=__dirname+'/videos/'}
if(!config.binDir){config.binDir=__dirname+'/fileBin/'}
if(!config.addStorage){config.addStorage=[]}
s.dir={
videos:s.checkCorrectPathEnding(config.videosDir),
streams:s.checkCorrectPathEnding(config.streamDir),
fileBin:s.checkCorrectPathEnding(config.binDir),
addStorage:config.addStorage,
languages:location.languages+'/'
};
//streams dir
if(!fs.existsSync(s.dir.streams)){
fs.mkdirSync(s.dir.streams);
}
//videos dir
if(!fs.existsSync(s.dir.videos)){
fs.mkdirSync(s.dir.videos);
}
//fileBin dir
if(!fs.existsSync(s.dir.fileBin)){
fs.mkdirSync(s.dir.fileBin);
}
//additional storage areas
s.dir.addStorage.forEach(function(v,n){
v.path=s.checkCorrectPathEnding(v.path)
if(!fs.existsSync(v.path)){
fs.mkdirSync(v.path);
}
})
////Camera Controller
s.init=function(x,e,k,fn){
if(!e){e={}}
if(!k){k={}}
switch(x){
case 0://init camera
if(!s.group[e.ke]){s.group[e.ke]={}};
if(!s.group[e.ke].mon){s.group[e.ke].mon={}}
if(!s.group[e.ke].mon[e.mid]){s.group[e.ke].mon[e.mid]={}}
if(!s.group[e.ke].mon[e.mid].streamIn){s.group[e.ke].mon[e.mid].streamIn={}};
if(!s.group[e.ke].mon[e.mid].emitterChannel){s.group[e.ke].mon[e.mid].emitterChannel={}};
if(!s.group[e.ke].mon[e.mid].mp4frag){s.group[e.ke].mon[e.mid].mp4frag={}};
if(!s.group[e.ke].mon[e.mid].firstStreamChunk){s.group[e.ke].mon[e.mid].firstStreamChunk={}};
if(!s.group[e.ke].mon[e.mid].contentWriter){s.group[e.ke].mon[e.mid].contentWriter={}};
if(!s.group[e.ke].mon[e.mid].childNodeStreamWriters){s.group[e.ke].mon[e.mid].childNodeStreamWriters={}};
if(!s.group[e.ke].mon[e.mid].eventBasedRecording){s.group[e.ke].mon[e.mid].eventBasedRecording={}};
if(!s.group[e.ke].mon[e.mid].watch){s.group[e.ke].mon[e.mid].watch={}};
if(!s.group[e.ke].mon[e.mid].fixingVideos){s.group[e.ke].mon[e.mid].fixingVideos={}};
if(!s.group[e.ke].mon[e.mid].record){s.group[e.ke].mon[e.mid].record={yes:e.record}};
if(!s.group[e.ke].mon[e.mid].started){s.group[e.ke].mon[e.mid].started=0};
if(s.group[e.ke].mon[e.mid].delete){clearTimeout(s.group[e.ke].mon[e.mid].delete)}
if(!s.group[e.ke].mon_conf){s.group[e.ke].mon_conf={}}
break;
case'group':
if(!s.group[e.ke]){
s.group[e.ke]={}
}
if(!s.group[e.ke].init){
s.group[e.ke].init={}
}
if(!s.group[e.ke].fileBin){s.group[e.ke].fileBin={}};
if(!s.group[e.ke].sizeChangeQueue){s.group[e.ke].sizeChangeQueue=[]}
if(!s.group[e.ke].sizePurgeQueue){s.group[e.ke].sizePurgeQueue=[]}
if(!s.group[e.ke].users){s.group[e.ke].users={}}
if(!s.group[e.ke].dashcamUsers){s.group[e.ke].dashcamUsers={}}
if(!e.limit||e.limit===''){e.limit=10000}else{e.limit=parseFloat(e.limit)}
//save global space limit for group key (mb)
s.group[e.ke].sizeLimit=e.limit;
//save global used space as megabyte value
s.group[e.ke].usedSpace=e.size/1000000;
//emit the changes to connected users
s.init('diskUsedEmit',e)
break;
case'apps':
if(!s.group[e.ke].init){
s.group[e.ke].init={};
}
if(!s.group[e.ke].webdav||!s.group[e.ke].sizeLimit){
s.sqlQuery('SELECT * FROM Users WHERE ke=? AND details NOT LIKE ?',[e.ke,'%"sub"%'],function(ar,r){
if(r&&r[0]){
r=r[0];
ar=JSON.parse(r.details);
//owncloud/webdav
if(ar.webdav_user&&
ar.webdav_user!==''&&
ar.webdav_pass&&
ar.webdav_pass!==''&&
ar.webdav_url&&
ar.webdav_url!==''
){
if(!ar.webdav_dir||ar.webdav_dir===''){
ar.webdav_dir='/';
if(ar.webdav_dir.slice(-1)!=='/'){ar.webdav_dir+='/';}
}
s.group[e.ke].webdav = webdav(
ar.webdav_url,
ar.webdav_user,
ar.webdav_pass
);
}
//discordbot
if(!s.group[e.ke].discordBot &&
config.discordBot === true &&
ar.discordbot === '1' &&
ar.discordbot_token !== ''
){
s.group[e.ke].discordBot = new Discord.Client()
s.group[e.ke].discordBot.on('ready', () => {
console.log(`${r.mail} : Discord Bot Logged in as ${s.group[e.ke].discordBot.user.tag}!`)
})
s.group[e.ke].discordBot.login(ar.discordbot_token)
}
Object.keys(ar).forEach(function(v){
s.group[e.ke].init[v]=ar[v]
})
}
});
}
break;
case'sync':
e.cn=Object.keys(s.childNodes);
e.cn.forEach(function(v){
if(s.group[e.ke]){
s.cx({f:'sync',sync:s.init('noReference',s.group[e.ke].mon_conf[e.mid]),ke:e.ke,mid:e.mid},s.childNodes[v].cnid);
}
});
break;
case'noReference':
x={keys:Object.keys(e),ar:{}};
x.keys.forEach(function(v){
if(v!=='last_frame'&&v!=='record'&&v!=='spawn'&&v!=='running'&&(v!=='time'&&typeof e[v]!=='function')){x.ar[v]=e[v];}
});
return x.ar;
break;
case'url':
//build a complete url from pieces
e.authd='';
if(e.details.muser&&e.details.muser!==''&&e.host.indexOf('@')===-1) {
e.authd=e.details.muser+':'+e.details.mpass+'@';
}
if(e.port==80&&e.details.port_force!=='1'){e.porty=''}else{e.porty=':'+e.port}
e.url=e.protocol+'://'+e.authd+e.host+e.porty+e.path;return e.url;
break;
case'url_no_path':
e.authd='';
if(!e.details.muser){e.details.muser=''}
if(!e.details.mpass){e.details.mpass=''}
if(e.details.muser!==''&&e.host.indexOf('@')===-1) {
e.authd=e.details.muser+':'+e.details.mpass+'@';
}
if(e.port==80&&e.details.port_force!=='1'){e.porty=''}else{e.porty=':'+e.port}
e.url=e.protocol+'://'+e.authd+e.host+e.porty;return e.url;
break;
case'diskUsedEmit':
//send the amount used disk space to connected users
if(s.group[e.ke]&&s.group[e.ke].init){
s.tx({f:'diskUsed',size:s.group[e.ke].usedSpace,limit:s.group[e.ke].sizeLimit},'GRP_'+e.ke);
}
break;
case'diskUsedSet':
//`k` will be used as the value to add or substract
s.group[e.ke].sizeChangeQueue.push(k)
if(s.group[e.ke].sizeChanging!==true){
//lock this function
s.group[e.ke].sizeChanging=true
//validate current values
if(!s.group[e.ke].usedSpace){
s.group[e.ke].usedSpace=0
}else{
s.group[e.ke].usedSpace=parseFloat(s.group[e.ke].usedSpace)
}
if(s.group[e.ke].usedSpace<0||isNaN(s.group[e.ke].usedSpace)){
s.group[e.ke].usedSpace=0
}
//set queue processor
var checkQueue=function(){
//get first in queue
var currentChange = s.group[e.ke].sizeChangeQueue[0]
//change global size value
s.group[e.ke].usedSpace=s.group[e.ke].usedSpace+currentChange
//remove value just used from queue
s.group[e.ke].sizeChangeQueue = s.group[e.ke].sizeChangeQueue.splice(1,s.group[e.ke].sizeChangeQueue.length+10)
//do next one
if(s.group[e.ke].sizeChangeQueue.length>0){
checkQueue()
}else{
s.group[e.ke].sizeChanging=false
s.init('diskUsedEmit',e)
}
}
checkQueue()
}
break;
case'monitorStatus':
// s.discordMsg({
// author: {
// name: s.group[e.ke].mon_conf[e.id].name,
// icon_url: "https://shinobi.video/libs/assets/icon/apple-touch-icon-152x152.png"
// },
// title: lang['Status Changed'],
// description: lang['Monitor is now '+e.status],
// fields: [],
// timestamp: new Date(),
// footer: {
// icon_url: "https://shinobi.video/libs/assets/icon/apple-touch-icon-152x152.png",
// text: "Shinobi Systems"
// }
// },[],e.ke)
s.group[e.ke].mon[e.id].monitorStatus = e.status
s.tx(Object.assign(e,{f:'monitor_status'}),'GRP_'+e.ke)
break;
}
if(typeof e.callback==='function'){setTimeout(function(){e.callback()},500);}
}
s.filterEvents=function(x,d){
switch(x){
case'archive':
d.videos.forEach(function(v,n){
s.video('archive',v)
})
break;
case'email':
if(d.videos&&d.videos.length>0){
d.videos.forEach(function(v,n){
})
d.mailOptions = {
from: config.mail.from, // sender address
to: d.mail, // list of receivers
subject: lang['Filter Matches']+' : '+d.name, // Subject line
html: lang.FilterMatchesText1+' '+d.videos.length+' '+lang.FilterMatchesText2,
};
if(d.execute&&d.execute!==''){
d.mailOptions.html+='<div><b>'+lang.Executed+' :</b> '+d.execute+'</div>'
}
if(d.delete==='1'){
d.mailOptions.html+='<div><b>'+lang.Deleted+' :</b> '+lang.Yes+'</div>'
}
d.mailOptions.html+='<div><b>'+lang.Query+' :</b> '+d.query+'</div>'
d.mailOptions.html+='<div><b>'+lang['Filter ID']+' :</b> '+d.id+'</div>'
nodemailer.sendMail(d.mailOptions, (error, info) => {
if (error) {
s.tx({f:'error',ff:'filter_mail',ke:d.ke,error:error},'GRP_'+d.ke);
return ;
}
s.tx({f:'filter_mail',ke:d.ke,info:info},'GRP_'+d.ke);
});
}
break;
case'delete':
d.videos.forEach(function(v,n){
s.video('delete',v)
})
break;
case'execute':
exec(d.execute,{detached: true})
break;
}
}
s.video=function(x,e,k){
if(!e){e={}};
switch(x){
case'getDir':
if(e.mid&&!e.id){e.id=e.mid};
if(e.details&&(e.details instanceof Object)===false){
try{e.details=JSON.parse(e.details)}catch(err){}
}
if(e.details&&e.details.dir&&e.details.dir!==''){
return s.checkCorrectPathEnding(e.details.dir)+e.ke+'/'+e.id+'/'
}else{
return s.dir.videos+e.ke+'/'+e.id+'/';
}
break;
}
if(!k)k={};
if(x!=='getDir'){e.dir=s.video('getDir',e)}
switch(x){
case'fix':
e.sdir=s.dir.streams+e.ke+'/'+e.id+'/';
if(!e.filename&&e.time){e.filename=s.formattedTime(e.time)}
if(e.filename.indexOf('.')===-1){
e.filename=e.filename+'.'+e.ext
}
s.tx({f:'video_fix_start',mid:e.mid,ke:e.ke,filename:e.filename},'GRP_'+e.ke)
s.group[e.ke].mon[e.id].fixingVideos[e.filename]={}
switch(e.ext){
case'mp4':
e.fixFlags='-vcodec libx264 -acodec aac -strict -2';
break;
case'webm':
e.fixFlags='-vcodec libvpx -acodec libvorbis';
break;
}
e.spawn=spawn(config.ffmpegDir,('-i '+e.dir+e.filename+' '+e.fixFlags+' '+e.sdir+e.filename).split(' '),{detached: true})
e.spawn.stdout.on('data',function(data){
s.tx({f:'video_fix_data',mid:e.mid,ke:e.ke,filename:e.filename},'GRP_'+e.ke)
});
e.spawn.on('close',function(data){
exec('mv '+e.dir+e.filename+' '+e.sdir+e.filename,{detached: true}).on('exit',function(){
s.tx({f:'video_fix_success',mid:e.mid,ke:e.ke,filename:e.filename},'GRP_'+e.ke)
delete(s.group[e.ke].mon[e.id].fixingVideos[e.filename]);
})
});
break;
case'delete':
if(!e.filename && e.time){
e.filename = s.formattedTime(e.time)
}
var filename,
time
if(e.filename.indexOf('.')>-1){
filename = e.filename
}else{
filename = e.filename+'.'+e.ext
}
if(e.filename && !e.time){
time = s.nameToTime(filename)
}else{
time = e.time
}
time = new Date(time)
e.save=[e.id,e.ke,time];
s.sqlQuery('SELECT * FROM Videos WHERE `mid`=? AND `ke`=? AND `time`=?',e.save,function(err,r){
if(r&&r[0]){
r=r[0]
var dir=s.video('getDir',r)
s.sqlQuery('DELETE FROM Videos WHERE `mid`=? AND `ke`=? AND `time`=?',e.save,function(){
fs.stat(dir+filename,function(err,file){
if(err){
s.systemLog('File Delete Error : '+e.ke+' : '+' : '+e.mid,err)
}
s.init('diskUsedSet',e,-(r.size/1000000))
})
s.tx({f:'video_delete',filename:filename,mid:e.mid,ke:e.ke,time:s.nameToTime(filename),end:s.formattedTime(new Date,'YYYY-MM-DD HH:mm:ss')},'GRP_'+e.ke);
s.file('delete',dir+filename)
})
}else{
// console.log('Delete Failed',e)
// console.error(err)
}
})
break;
// case'open':
// //on video open
// e.save=[e.id,e.ke,s.nameToTime(e.filename),e.ext];
// if(!e.status){e.save.push(0)}else{e.save.push(e.status)}
// k.details={}
// if(e.details&&e.details.dir&&e.details.dir!==''){
// k.details.dir=e.details.dir
// }
// e.save.push(s.s(k.details))
// s.sqlQuery('INSERT INTO Videos (mid,ke,time,ext,status,details) VALUES (?,?,?,?,?,?)',e.save)
// s.tx({f:'video_build_start',filename:e.filename+'.'+e.ext,mid:e.id,ke:e.ke,time:s.nameToTime(e.filename),end:s.formattedTime(new Date,'YYYY-MM-DD HH:mm:ss')},'GRP_'+e.ke);
// break;
// case'close':
// //video function : close
// if(s.group[e.ke]&&s.group[e.ke].mon[e.id]){
// if(s.group[e.ke].mon[e.id].open&&!e.filename){
// e.filename=s.group[e.ke].mon[e.id].open;
// e.ext=s.group[e.ke].mon[e.id].open_ext
// }
// if(s.group[e.ke].mon[e.id].childNode){
// s.cx({f:'close',d:s.init('noReference',e)},s.group[e.ke].mon[e.id].childNodeId);
// }else{
// k.file = e.filename+'.'+e.ext
// k.dir = e.dir.toString()
// //get file directory
// k.fileExists = fs.existsSync(k.dir+k.file)
// if(k.fileExists!==true){
// k.dir=s.dir.videos+'/'+e.ke+'/'+e.id+'/'
// k.fileExists=fs.existsSync(k.dir+k.file)
// if(k.fileExists!==true){
// s.dir.addStorage.forEach(function(v){
// if(k.fileExists!==true){
// k.dir=s.checkCorrectPathEnding(v.path)+e.ke+'/'+e.id+'/'
// k.fileExists=fs.existsSync(k.dir+k.file)
// }
// })
// }
// }
// if(k.fileExists===true){
// //close video row
// k.stat = fs.statSync(k.dir+k.file)
// e.filesize = k.stat.size
// e.filesizeMB = parseFloat((e.filesize/1000000).toFixed(2))
// e.end_time = s.formattedTime(k.stat.mtime,'YYYY-MM-DD HH:mm:ss')
// var save = [
// e.filesize,
// 1,
// e.end_time,
// e.id,
// e.ke,
// s.nameToTime(e.filename)
// ]
// if(!e.status){
// save.push(0)
// }else{
// save.push(e.status)
// }
// s.sqlQuery('UPDATE Videos SET `size`=?,`status`=?,`end`=? WHERE `mid`=? AND `ke`=? AND `time`=? AND `status`=?',save)
// //send event for completed recording
// s.txWithSubPermissions({
// f:'video_build_success',
// hrefNoAuth:'/videos/'+e.ke+'/'+e.mid+'/'+k.file,
// filename:k.file,
// mid:e.id,
// ke:e.ke,
// time:s.timeObject(s.nameToTime(e.filename)).format(),
// size:e.filesize,
// end:s.timeObject(e.end_time).format()
// },'GRP_'+e.ke,'video_view');
// //send new diskUsage values
// s.video('diskUseUpdate',e,k)
// }else{
// s.video('delete',e);
// s.log(e,{type:lang['File Not Exist'],msg:lang.FileNotExistText,ffmpeg:s.group[e.ke].mon[e.id].ffmpeg})
// if(e.mode && config.restart.onVideoNotExist === true){
// delete(s.group[e.ke].mon[e.id].open);
// s.log(e,{
// type : lang['Camera is not recording'],
// msg : {
// msg : lang.CameraNotRecordingText
// }
// });
// if(s.group[e.ke].mon[e.id].started===1){
// s.camera('restart',e)
// }
// }
// }
// }
// }
// delete(s.group[e.ke].mon[e.id].open);
// break;
case'linkBuild':
//e = video rows
//k = auth key
e.forEach(function(v){
var details = JSON.parse(v.details)
var queryString = []
if(details.isUTC === true){
queryString.push('isUTC=true')
}else{
v.time = s.utcToLocal(v.time)
v.end = s.utcToLocal(v.end)
}
if(queryString.length > 0){
queryString = '?'+queryString.join('&')
}else{
queryString = ''
}
v.filename = s.formattedTime(v.time)+'.'+v.ext;
v.href = '/'+k+'/videos/'+v.ke+'/'+v.mid+'/'+v.filename;
v.links = {
deleteVideo : v.href+'/delete' + queryString,
changeToUnread : v.href+'/status/1' + queryString,
changeToRead : v.href+'/status/2' + queryString
}
v.href = v.href + queryString
v.details = details
})
break;
case'diskUseUpdate':
if(s.group[e.ke].init){
s.init('diskUsedSet',e,e.filesizeMB)
if(config.cron.deleteOverMax===true){
//check space
s.group[e.ke].sizePurgeQueue.push(1)
if(s.group[e.ke].sizePurging!==true){
//lock this function
s.group[e.ke].sizePurging=true
//set queue processor
var finish=function(){
//remove value just used from queue
s.group[e.ke].sizePurgeQueue = s.group[e.ke].sizePurgeQueue.splice(1,s.group[e.ke].sizePurgeQueue.length+10)
//do next one
if(s.group[e.ke].sizePurgeQueue.length>0){
checkQueue()
}else{
s.group[e.ke].sizePurging=false
s.init('diskUsedEmit',e)
}
}
var checkQueue=function(){
//get first in queue
var currentPurge = s.group[e.ke].sizePurgeQueue[0]
var deleteVideos = function(){
//run purge command
if(s.group[e.ke].usedSpace>(s.group[e.ke].sizeLimit*config.cron.deleteOverMaxOffset)){
s.sqlQuery('SELECT * FROM Videos WHERE status != 0 AND details NOT LIKE \'%"archived":"1"%\' AND ke=? ORDER BY `time` ASC LIMIT 2',[e.ke],function(err,evs){
k.del=[];k.ar=[e.ke];
if(!evs)return console.log(err)
evs.forEach(function(ev){
ev.dir=s.video('getDir',ev)+s.formattedTime(ev.time)+'.'+ev.ext;
k.del.push('(mid=? AND `time`=?)');
k.ar.push(ev.mid),k.ar.push(ev.time);
s.file('delete',ev.dir);
s.init('diskUsedSet',e,-(ev.size/1000000))
s.tx({f:'video_delete',ff:'over_max',filename:s.formattedTime(ev.time)+'.'+ev.ext,mid:ev.mid,ke:ev.ke,time:ev.time,end:s.formattedTime(new Date,'YYYY-MM-DD HH:mm:ss')},'GRP_'+e.ke);
});
if(k.del.length>0){
k.qu=k.del.join(' OR ');
s.sqlQuery('DELETE FROM Videos WHERE ke =? AND ('+k.qu+')',k.ar,function(){
deleteVideos()
})
}else{
finish()
}
})
}else{
finish()
}
}
deleteVideos()
}
checkQueue()
}
}else{
s.init('diskUsedEmit',e)
}
}
break;
case'insertCompleted':
k.dir = e.dir.toString()
if(s.group[e.ke].mon[e.id].childNode){
s.cx({f:'insertCompleted',d:s.group[e.ke].mon_conf[e.id],k:k},s.group[e.ke].mon[e.id].childNodeId);
}else{
//get file directory
k.fileExists = fs.existsSync(k.dir+k.file)
if(k.fileExists!==true){
k.dir = s.dir.videos+'/'+e.ke+'/'+e.id+'/'
k.fileExists = fs.existsSync(k.dir+k.file)
if(k.fileExists !== true){
s.dir.addStorage.forEach(function(v){
if(k.fileExists !== true){
k.dir = s.checkCorrectPathEnding(v.path)+e.ke+'/'+e.id+'/'
k.fileExists = fs.existsSync(k.dir+k.file)
}
})
}
}
if(k.fileExists===true){
//close video row
k.stat = fs.statSync(k.dir+k.file)
e.filesize = k.stat.size
e.filesizeMB = parseFloat((e.filesize/1000000).toFixed(2))
e.startTime = new Date(s.nameToTime(k.file))
e.endTime = new Date(k.stat.mtime)
if(config.useUTC === true){
fs.rename(k.dir+k.file, k.dir+s.formattedTime(e.startTime)+'.'+e.ext, (err) => {
if (err) return console.error(err);
});
k.filename = s.formattedTime(e.startTime)+'.'+e.ext
}else{
// e.startTime = s.utcToLocal(e.startTime)
// e.endTime = s.utcToLocal(e.endTime)
k.filename = k.file
}
if(!e.ext){e.ext = k.filename.split('.')[1]}
//send event for completed recording
if(config.childNodes.enabled === true && config.childNodes.mode === 'child' && config.childNodes.host){
fs.createReadStream(k.dir+k.filename)
.on('data',function(data){
s.cx({
f:'created_file_chunk',
mid:e.id,
ke:e.ke,
chunk:data,
filename:k.filename,
d:s.init('noReference',e),
filesize:e.filesize,
time:s.timeObject(e.startTime).format(),
end:s.timeObject(e.endTime).format()
})
})
.on('close',function(){
clearTimeout(s.group[e.ke].mon[e.id].checker)
clearTimeout(s.group[e.ke].mon[e.id].checkStream)
s.cx({
f:'created_file',
mid:e.id,
ke:e.ke,
filename:k.filename,
d:s.init('noReference',e),
filesize:e.filesize,
time:s.timeObject(e.startTime).format(),
end:s.timeObject(e.endTime).format()
})
});
}else{
var href = '/videos/'+e.ke+'/'+e.mid+'/'+k.filename
if(config.useUTC === true)href += '?isUTC=true';
s.txWithSubPermissions({
f:'video_build_success',
hrefNoAuth:href,
filename:k.filename,
mid:e.mid,
ke:e.ke,
time:e.startTime,
size:e.filesize,
end:e.endTime
},'GRP_'+e.ke,'video_view');
}
//cloud auto savers
//webdav
// var webDAV = s.group[e.ke].webdav
// if(webDAV&&s.group[e.ke].init.use_webdav!=='0'&&s.group[e.ke].init.webdav_save=="1"){
// fs.readFile(k.dir+k.filename,function(err,data){
// var webdavUploadDir = s.group[e.ke].init.webdav_dir+e.ke+'/'+e.mid+'/'
// fs.readFile(k.dir+k.filename,function(err,data){
// webDAV.putFileContents(webdavUploadDir+k.filename,"binary",data).catch(function(err) {
// if(err){
// webDAV.createDirectory(webdavUploadDir).catch(function(err) {
// s.log(e,{type:lang['Webdav Error'],msg:{msg:lang.WebdavErrorText+' <b>/'+webdavUploadDir+'</b>',info:err}})
// })
// webDAV.putFileContents(webdavUploadDir+k.filename,"binary",data).catch(function(err) {
// s.log(e,{type:lang['Webdav Error'],msg:{msg:lang.WebdavErrorText+' <b>/'+webdavUploadDir+'</b>',info:err}})
// })
// s.log(e,{type:lang['Webdav Error'],msg:{msg:lang.WebdavErrorText+' <b>/'+webdavUploadDir+'</b>',info:err}})
// }
// });
// });
// });
// }
if(s.group[e.ke].webdav&&s.group[e.ke].init.use_webdav!=='0'&&s.group[e.ke].init.webdav_save=="1"){
fs.readFile(k.dir+k.filename,function(err,data){
s.group[e.ke].webdav.putFileContents(s.group[e.ke].init.webdav_dir+e.ke+'/'+e.mid+'/'+k.filename,"binary",data)
.catch(function(err) {
s.log(e,{type:lang['Webdav Error'],msg:{msg:lang.WebdavErrorText+' <b>/'+e.ke+'/'+e.id+'</b>',info:err},ffmpeg:s.group[e.ke].mon[e.id].ffmpeg})
console.error(err);
});
});
}
k.details = {}
if(e.details&&e.details.dir&&e.details.dir!==''){
k.details.dir = e.details.dir
}
if(config.useUTC === true)k.details.isUTC = config.useUTC;
var save = [
e.mid,
e.ke,
e.startTime,
e.ext,
1,
s.s(k.details),
e.filesize,
e.endTime,
]
s.sqlQuery('INSERT INTO Videos (mid,ke,time,ext,status,details,size,end) VALUES (?,?,?,?,?,?,?,?)',save)
//send new diskUsage values
s.video('diskUseUpdate',e,k)
}
}
break;
}
}
s.splitForFFPMEG = function (ffmpegCommandAsString) {
//this function ignores spaces inside quotes.
return ffmpegCommandAsString.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
};
s.createFFmpegMap = function(e,arrayOfMaps){
//`e` is the monitor object
var string = '';
if(e.details.input_maps && e.details.input_maps.length > 0){
if(arrayOfMaps && arrayOfMaps instanceof Array && arrayOfMaps.length>0){
arrayOfMaps.forEach(function(v){
if(v.map==='')v.map='0'
string += ' -map '+v.map
})
}else{
string += ' -map 0:0'
}
}
return string;
}
s.createInputMap = function(e,number,input){
//`e` is the monitor object
//`x` is an object used to contain temporary values.
var x = {}
x.cust_input = ''
x.hwaccel = ''
if(input.cust_input&&input.cust_input!==''){x.cust_input+=' '+input.cust_input}
//input - analyze duration
if(input.aduration&&input.aduration!==''){x.cust_input+=' -analyzeduration '+input.aduration}
//input - probe size
if(input.probesize&&input.probesize!==''){x.cust_input+=' -probesize '+input.probesize}
//input - stream loop (good for static files/lists)
if(input.stream_loop==='1'){x.cust_input+=' -stream_loop -1'}
//input - fps
if(x.cust_input.indexOf('-r ')===-1&&input.sfps&&input.sfps!==''){
input.sfps=parseFloat(input.sfps);
if(isNaN(input.sfps)){input.sfps=1}
x.cust_input+=' -r '+input.sfps
}
//input - is mjpeg
if(input.type==='mjpeg'){
if(x.cust_input.indexOf('-f ')===-1){
x.cust_input+=' -f mjpeg'
}
//input - frames per second
x.cust_input+=' -reconnect 1'
}else
//input - is h264 has rtsp in address and transport method is chosen
if((input.type==='h264'||input.type==='mp4')&&input.fulladdress.indexOf('rtsp://')>-1&&input.rtsp_transport!==''&&input.rtsp_transport!=='no'){
x.cust_input += ' -rtsp_transport '+input.rtsp_transport
}else
if((input.type==='mp4'||input.type==='mjpeg')&&x.cust_input.indexOf('-re')===-1){
x.cust_input += ' -re'
}
//hardware acceleration
if(input.accelerator&&input.accelerator==='1'){
if(input.hwaccel&&input.hwaccel!==''){
x.hwaccel+=' -hwaccel '+input.hwaccel;
}
if(input.hwaccel_vcodec&&input.hwaccel_vcodec!==''&&input.hwaccel_vcodec!=='auto'&&input.hwaccel_vcodec!=='no'){
x.hwaccel+=' -c:v '+input.hwaccel_vcodec;
}
if(input.hwaccel_device&&input.hwaccel_device!==''){
switch(input.hwaccel){
case'vaapi':
x.hwaccel+=' -vaapi_device '+input.hwaccel_device+' -hwaccel_output_format vaapi';
break;
default:
x.hwaccel+=' -hwaccel_device '+input.hwaccel_device;
break;
}
}
}
//custom - input flags
return x.hwaccel+x.cust_input+' -i "'+input.fulladdress+'"';
}
//create sub stream channel
s.createStreamChannel = function(e,number,channel){
//`e` is the monitor object
//`x` is an object used to contain temporary values.
var x = {
pipe:''
}
if(!number||number==''){
x.channel_sdir = e.sdir;
}else{
x.channel_sdir = e.sdir+'channel'+number+'/';
if (!fs.existsSync(x.channel_sdir)){
fs.mkdirSync(x.channel_sdir);
}
}
x.stream_video_filters=[]
//stream - frames per second
if(channel.stream_vcodec!=='copy'){
if(!channel.stream_fps||channel.stream_fps===''){
switch(channel.stream_type){
case'rtmp':
channel.stream_fps=30
break;
default:
// channel.stream_fps=5
break;
}
}
}
if(channel.stream_fps&&channel.stream_fps!==''){x.stream_fps=' -r '+channel.stream_fps}else{x.stream_fps=''}
//stream - hls vcodec
if(channel.stream_vcodec&&channel.stream_vcodec!=='no'){
if(channel.stream_vcodec!==''){x.stream_vcodec=' -c:v '+channel.stream_vcodec}else{x.stream_vcodec=' -c:v libx264'}
}else{
x.stream_vcodec='';
}
//stream - hls acodec
if(channel.stream_acodec!=='no'){
if(channel.stream_acodec&&channel.stream_acodec!==''){x.stream_acodec=' -c:a '+channel.stream_acodec}else{x.stream_acodec=''}
}else{
x.stream_acodec=' -an';
}
//stream - resolution
if(channel.stream_scale_x&&channel.stream_scale_x!==''&&channel.stream_scale_y&&channel.stream_scale_y!==''){
x.dimensions = channel.stream_scale_x+'x'+channel.stream_scale_y;
}
//stream - hls segment time
if(channel.hls_time&&channel.hls_time!==''){x.hls_time=channel.hls_time}else{x.hls_time="2"}
//hls list size
if(channel.hls_list_size&&channel.hls_list_size!==''){x.hls_list_size=channel.hls_list_size}else{x.hls_list_size=2}
//stream - custom flags
if(channel.cust_stream&&channel.cust_stream!==''){x.cust_stream=' '+channel.cust_stream}else{x.cust_stream=''}
//stream - preset
if(channel.preset_stream&&channel.preset_stream!==''){x.preset_stream=' -preset '+channel.preset_stream;}else{x.preset_stream=''}
//hardware acceleration
if(e.details.accelerator&&e.details.accelerator==='1'){
if(e.details.hwaccel&&e.details.hwaccel!==''){
x.hwaccel+=' -hwaccel '+e.details.hwaccel;
}
if(e.details.hwaccel_vcodec&&e.details.hwaccel_vcodec!==''){
x.hwaccel+=' -c:v '+e.details.hwaccel_vcodec;
}
if(e.details.hwaccel_device&&e.details.hwaccel_device!==''){
switch(e.details.hwaccel){
case'vaapi':
x.hwaccel+=' -vaapi_device '+e.details.hwaccel_device+' -hwaccel_output_format vaapi';
break;
default:
x.hwaccel+=' -hwaccel_device '+e.details.hwaccel_device;
break;
}
}
// else{
// if(e.details.hwaccel==='vaapi'){
// x.hwaccel+=' -hwaccel_device 0';
// }
// }
}
if(channel.rotate_stream&&channel.rotate_stream!==""&&channel.rotate_stream!=="no"){
x.stream_video_filters.push('transpose='+channel.rotate_stream);
}
//stream - video filter
if(channel.svf&&channel.svf!==''){
x.stream_video_filters.push(channel.svf)
}
if(x.stream_video_filters.length>0){
var string = x.stream_video_filters.join(',').trim()
if(string===''){
x.stream_video_filters=''
}else{
x.stream_video_filters=' -vf '+string
}
}else{
x.stream_video_filters=''
}
if(e.details.input_map_choices&&e.details.input_map_choices.record){
//add input feed map
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices['stream_channel-'+(number-config.pipeAddition)])
}
if(channel.stream_vcodec!=='copy'){
x.cust_stream+=x.stream_fps
}
switch(channel.stream_type){
case'mp4':
x.cust_stream+=' -movflags +frag_keyframe+empty_moov+default_base_moof -metadata title="Poseidon Stream" -reset_timestamps 1'
if(channel.stream_vcodec!=='copy'){
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
if(channel.stream_quality && channel.stream_quality !== '')x.cust_stream+=' -crf '+channel.stream_quality;
x.cust_stream+=x.preset_stream
x.cust_stream+=x.stream_video_filters
}
x.pipe+=' -f mp4'+x.stream_acodec+x.stream_vcodec+x.cust_stream+' pipe:'+number;
break;
case'rtmp':
x.rtmp_server_url=s.checkCorrectPathEnding(channel.rtmp_server_url);
if(channel.stream_vcodec!=='copy'){
if(channel.stream_vcodec==='libx264'){
channel.stream_vcodec = 'h264'
}
if(channel.stream_quality && channel.stream_quality !== '')x.cust_stream+=' -crf '+channel.stream_quality;
x.cust_stream+=x.preset_stream
if(channel.stream_v_br&&channel.stream_v_br!==''){x.cust_stream+=' -b:v '+channel.stream_v_br}
}
if(channel.stream_vcodec!=='no'&&channel.stream_vcodec!==''){
x.cust_stream+=' -vcodec '+channel.stream_vcodec
}
if(channel.stream_acodec!=='copy'){
if(!channel.stream_acodec||channel.stream_acodec===''||channel.stream_acodec==='no'){
channel.stream_acodec = 'aac'
}
if(!channel.stream_a_br||channel.stream_a_br===''){channel.stream_a_br='128k'}
x.cust_stream+=' -ab '+channel.stream_a_br
}
if(channel.stream_acodec!==''){
x.cust_stream+=' -acodec '+channel.stream_acodec
}
x.pipe+=' -f flv'+x.stream_video_filters+x.cust_stream+' "'+x.rtmp_server_url+channel.rtmp_stream_key+'"';
break;
case'h264':
if(channel.stream_vcodec!=='copy'){
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
if(channel.stream_quality && channel.stream_quality !== '')x.cust_stream+=' -crf '+channel.stream_quality;
x.cust_stream+=x.preset_stream
x.cust_stream+=x.stream_video_filters
}
x.pipe+=' -f mpegts'+x.stream_acodec+x.stream_vcodec+x.cust_stream+' pipe:'+number;
break;
case'flv':
if(channel.stream_vcodec!=='copy'){
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
if(channel.stream_quality && channel.stream_quality !== '')x.cust_stream+=' -crf '+channel.stream_quality;
x.cust_stream+=x.preset_stream
x.cust_stream+=x.stream_video_filters
}
x.pipe+=' -f flv'+x.stream_acodec+x.stream_vcodec+x.cust_stream+' pipe:'+number;
break;
case'hls':
if(channel.stream_vcodec!=='h264_vaapi'&&channel.stream_vcodec!=='copy'){
if(channel.stream_quality && channel.stream_quality !== '')x.cust_stream+=' -crf '+channel.stream_quality;
if(x.cust_stream.indexOf('-tune')===-1){x.cust_stream+=' -tune zerolatency'}
if(x.cust_stream.indexOf('-g ')===-1){x.cust_stream+=' -g 1'}
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
x.cust_stream+=x.stream_video_filters
}
x.pipe+=x.preset_stream+x.stream_acodec+x.stream_vcodec+' -f hls'+x.cust_stream+' -hls_time '+x.hls_time+' -hls_list_size '+x.hls_list_size+' -start_number 0 -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist "'+x.channel_sdir+'s.m3u8"';
break;
case'mjpeg':
if(channel.stream_quality && channel.stream_quality !== '')x.cust_stream+=' -q:v '+channel.stream_quality;
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
x.pipe+=' -c:v mjpeg -f mpjpeg -boundary_tag shinobi'+x.cust_stream+x.stream_video_filters+' pipe:'+number;
break;
default:
x.pipe=''
break;
}
return x.pipe
}
s.ffmpegCoProcessor = function(e){
if(e.coProcessor === false)return;
var x = {}
//x.input is the input and connection
if(e.details.loglevel&&e.details.loglevel!==''){x.loglevel='-loglevel '+e.details.loglevel;}else{x.loglevel='-loglevel error'}
x.input = x.loglevel+' -re -i '+e.sdir+'cpuOnly.m3u8'
//x.pipe is the stream out methods
x.cust_input=''
x.cust_detect=' '
x.stream_video_filters=[]
x.hwaccel=''
x.pipe=''
//main stream frames
//stream - timestamp
if(e.details.stream_timestamp&&e.details.stream_timestamp=="1"&&e.details.vcodec!=='copy'){
//font
if(e.details.stream_timestamp_font&&e.details.stream_timestamp_font!==''){x.stream_timestamp_font=e.details.stream_timestamp_font}else{x.stream_timestamp_font='/usr/share/fonts/truetype/freefont/FreeSans.ttf'}
//position x
if(e.details.stream_timestamp_x&&e.details.stream_timestamp_x!==''){x.stream_timestamp_x=e.details.stream_timestamp_x}else{x.stream_timestamp_x='(w-tw)/2'}
//position y
if(e.details.stream_timestamp_y&&e.details.stream_timestamp_y!==''){x.stream_timestamp_y=e.details.stream_timestamp_y}else{x.stream_timestamp_y='0'}
//text color
if(e.details.stream_timestamp_color&&e.details.stream_timestamp_color!==''){x.stream_timestamp_color=e.details.stream_timestamp_color}else{x.stream_timestamp_color='white'}
//box color
if(e.details.stream_timestamp_box_color&&e.details.stream_timestamp_box_color!==''){x.stream_timestamp_box_color=e.details.stream_timestamp_box_color}else{x.stream_timestamp_box_color='0x00000000@1'}
//text size
if(e.details.stream_timestamp_font_size&&e.details.stream_timestamp_font_size!==''){x.stream_timestamp_font_size=e.details.stream_timestamp_font_size}else{x.stream_timestamp_font_size='10'}
x.stream_video_filters.push('drawtext=fontfile='+x.stream_timestamp_font+':text=\'%{localtime}\':x='+x.stream_timestamp_x+':y='+x.stream_timestamp_y+':fontcolor='+x.stream_timestamp_color+':box=1:boxcolor='+x.stream_timestamp_box_color+':fontsize='+x.stream_timestamp_font_size);
}
//stream - watermark for -vf
if(e.details.stream_watermark&&e.details.stream_watermark=="1"&&e.details.stream_watermark_location&&e.details.stream_watermark_location!==''){
switch(e.details.stream_watermark_position){
case'tl'://top left
x.stream_watermark_position='10:10'
break;
case'tr'://top right
x.stream_watermark_position='main_w-overlay_w-10:10'
break;
case'bl'://bottom left
x.stream_watermark_position='10:main_h-overlay_h-10'
break;
default://bottom right
x.stream_watermark_position='(main_w-overlay_w-10)/2:(main_h-overlay_h-10)/2'
break;
}
x.stream_video_filters.push('movie='+e.details.stream_watermark_location+'[watermark],[in][watermark]overlay='+x.stream_watermark_position+'[out]');
}
//stream - rotation
if(e.details.rotate_stream&&e.details.rotate_stream!==""&&e.details.rotate_stream!=="no"&&e.details.stream_vcodec!=='copy'){
x.stream_video_filters.push('transpose='+e.details.rotate_stream);
}
if(e.details.svf&&e.details.svf!==''){
x.stream_video_filters.push(e.details.svf)
}
if(x.stream_video_filters.length>0){
x.stream_video_filters=' -vf '+x.stream_video_filters.join(',')
}else{
x.stream_video_filters=''
}
if(e.details.cust_stream&&e.details.cust_stream!==''){x.cust_stream=' '+e.details.cust_stream}else{x.cust_stream=''}
if(e.details.stream_fps&&e.details.stream_fps!==''){x.stream_fps=' -r '+e.details.stream_fps}else{x.stream_fps=''}
if(e.details.stream_vcodec!=='copy'){
x.cust_stream+=x.stream_fps
}
switch(e.details.stream_type){
case'mjpeg':
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -q:v '+e.details.stream_quality;
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
x.pipe += ' -an -c:v mjpeg -f mpjpeg -boundary_tag shinobi'+x.cust_stream+x.stream_video_filters+' pipe:1';
break;
case'b64':case'':case undefined:case null://base64
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -q:v '+e.details.stream_quality;
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
x.pipe += ' -an -c:v mjpeg -f image2pipe'+x.cust_stream+x.stream_video_filters+' pipe:1';
break;
}
//detector frames
if(e.details.detector === '1'){
if(!e.details.detector_fps||e.details.detector_fps===''){e.details.detector_fps=2}
if(e.details.detector_scale_x&&e.details.detector_scale_x!==''&&e.details.detector_scale_y&&e.details.detector_scale_y!==''){x.dratio=' -s '+e.details.detector_scale_x+'x'+e.details.detector_scale_y}else{x.dratio=' -s 320x240'}
if(e.details.cust_detect&&e.details.cust_detect!==''){x.cust_detect+=e.details.cust_detect;}
if(e.details.detector_pam==='1'){
x.pipe += ' -an -c:v pam -pix_fmt gray -f image2pipe -r '+e.details.detector_fps+x.cust_detect+x.dratio+' pipe:3'
if(e.details.detector_use_detect_object === '1'){
//for object detection
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.detector)
x.pipe += ' -f singlejpeg -vf fps='+e.details.detector_fps+x.cust_detect+x.dratio+' pipe:4';
}
}else{
x.pipe+=' -f singlejpeg -vf fps='+e.details.detector_fps+x.cust_detect+x.dratio+' pipe:3';
}
}
//snapshot frames
if(e.details.snap === '1'){
if(!e.details.snap_fps || e.details.snap_fps === ''){e.details.snap_fps = 1}
if(e.details.snap_vf && e.details.snap_vf !== ''){x.snap_vf=' -vf '+e.details.snap_vf}else{x.snap_vf=''}
if(e.details.snap_scale_x && e.details.snap_scale_x !== '' && e.details.snap_scale_y && e.details.snap_scale_y !== ''){x.snap_ratio = ' -s '+e.details.snap_scale_x+'x'+e.details.snap_scale_y}else{x.snap_ratio=''}
if(e.details.cust_snap && e.details.cust_snap !== ''){x.cust_snap = ' '+e.details.cust_snap}else{x.cust_snap=''}
x.pipe += ' -update 1 -r '+e.details.snap_fps+x.cust_snap+x.snap_ratio+x.snap_vf+' "'+e.sdir+'s.jpg" -y';
}
x.stdioPipes = [];
var times = config.pipeAddition;
if(e.details.stream_channels){
times+=e.details.stream_channels.length
}
for(var i=0; i < times; i++){
x.stdioPipes.push('pipe')
}
var commandString = x.input+x.pipe
if(commandString === x.input){
return false
}
s.group[e.ke].mon[e.mid].coProcessorCmd = commandString
return spawn(config.ffmpegDir,s.splitForFFPMEG((commandString).replace(/\s+/g,' ').trim()),{detached: true,stdio:x.stdioPipes})
}
s.coSpawnLauncher = function(e){
if(s.group[e.ke].mon[e.id].started === 1 && e.coProcessor === true){
s.coSpawnClose(e)
s.group[e.ke].mon[e.id].coSpawnProcessor = s.ffmpegCoProcessor(e)
if(s.group[e.ke].mon[e.id].coSpawnProcessor === false){
return
}
s.log(e,{type:lang['coProcessor Started'],msg:{msg:lang.coProcessorTextStarted,cmd:s.group[e.ke].mon[e.id].coProcessorCmd}});
s.group[e.ke].mon[e.id].coSpawnProcessorExit = function(){
s.log(e,{type:lang['coProcess Unexpected Exit'],msg:{msg:lang['coProcess Crashed for Monitor']+' : '+e.id,cmd:s.group[e.ke].mon[e.id].coProcessorCmd}});
setTimeout(function(){
s.coSpawnLauncher(e)
},2000)
}
s.group[e.ke].mon[e.id].coSpawnProcessor.on('end',s.group[e.ke].mon[e.id].coSpawnProcessorExit)
s.group[e.ke].mon[e.id].coSpawnProcessor.on('exit',s.group[e.ke].mon[e.id].coSpawnProcessorExit)
var checkLog = function(d,x){return d.indexOf(x)>-1;}
s.group[e.ke].mon[e.id].coSpawnProcessor.stderr.on('data',function(d){
d=d.toString();
switch(true){
case checkLog(d,'deprecated pixel format used'):
case checkLog(d,'[hls @'):
case checkLog(d,'Past duration'):
case checkLog(d,'Last message repeated'):
case checkLog(d,'pkt->duration = 0'):
case checkLog(d,'Non-monotonous DTS'):
case checkLog(d,'NULL @'):
return
break;
}
s.log(e,{type:lang.coProcessor,msg:d});
})
if(e.frame_to_stream){
s.group[e.ke].mon[e.id].coSpawnProcessor.stdout.on('data',e.frame_to_stream)
}
if(e.details.detector === '1'){
if(e.details.detector_pam === '1'){
s.createPamDiffEngine(e)
s.group[e.ke].mon[e.id].coSpawnProcessor.stdio[3].pipe(s.group[e.ke].mon[e.id].p2p).pipe(s.group[e.ke].mon[e.id].pamDiff)
}else{
s.group[e.ke].mon[e.id].coSpawnProcessor.stdio[3].on('data',function(d){
s.ocvTx({f:'frame',mon:s.group[e.ke].mon_conf[e.id].details,ke:e.ke,id:e.id,time:s.formattedTime(),frame:d});
})
}
}
}
}
s.coSpawnClose = function(e){
if(s.group[e.ke].mon[e.id].coSpawnProcessor){
s.group[e.ke].mon[e.id].coSpawnProcessor.removeListener('end',s.group[e.ke].mon[e.id].coSpawnProcessorExit);
s.group[e.ke].mon[e.id].coSpawnProcessor.removeListener('exit',s.group[e.ke].mon[e.id].coSpawnProcessorExit);
s.group[e.ke].mon[e.id].coSpawnProcessor.stdin.pause()
s.group[e.ke].mon[e.id].coSpawnProcessor.kill()
delete(s.group[e.ke].mon[e.id].coSpawnProcessor)
s.log(e,{type:lang['coProcessor Stopped'],msg:{msg:lang.coProcessorTextStopped+' : '+e.id}});
}
}
s.ffmpeg = function(e){
e.coProcessor = false
e.isStreamer = (e.type === 'dashcam'|| e.type === 'socket')
if(e.details.accelerator === '1' && e.details.hwaccel_vcodec !== 'auto' && e.isStreamer === false && (!e.details.input_maps || e.details.input_maps.length === 0)){
e.coProcessor = true
}
//set X for temporary values so we don't break our main monitor object.
var x={tmp:''};
//set some placeholding values to avoid "undefined" in ffmpeg string.
x.record_string=''
x.cust_input=''
x.cust_detect=' '
x.record_video_filters=[]
x.stream_video_filters=[]
x.hwaccel=''
x.pipe=''
//input - frame rate (capture rate)
if(e.details.sfps && e.details.sfps!==''){x.input_fps=' -r '+e.details.sfps}else{x.input_fps=''}
//input - analyze duration
if(e.details.aduration&&e.details.aduration!==''){x.cust_input+=' -analyzeduration '+e.details.aduration};
//input - probe size
if(e.details.probesize&&e.details.probesize!==''){x.cust_input+=' -probesize '+e.details.probesize};
//input - stream loop (good for static files/lists)
if(e.details.stream_loop==='1'){x.cust_input+=' -stream_loop -1'};
//input
if(e.details.cust_input.indexOf('-fflags') === -1){x.cust_input+=' -fflags +igndts'}
switch(e.type){
case'h264':
switch(e.protocol){
case'rtsp':
if(e.details.rtsp_transport&&e.details.rtsp_transport!==''&&e.details.rtsp_transport!=='no'){x.cust_input+=' -rtsp_transport '+e.details.rtsp_transport;}
break;
}
break;
}
//record - resolution
if(e.width!==''&&e.height!==''&&!isNaN(e.width)&&!isNaN(e.height)){
x.record_dimensions=' -s '+e.width+'x'+e.height
}else{
x.record_dimensions=''
}
if(e.details.stream_scale_x&&e.details.stream_scale_x!==''&&e.details.stream_scale_y&&e.details.stream_scale_y!==''){
x.dimensions = e.details.stream_scale_x+'x'+e.details.stream_scale_y;
}
//record - segmenting
x.segment=' -f segment -segment_atclocktime 1 -reset_timestamps 1 -strftime 1 -segment_list pipe:2 -segment_time '+(60*e.cutoff)+' "'+e.dir+'%Y-%m-%dT%H-%M-%S.'+e.ext+'"';
//record - set defaults for extension, video quality
switch(e.ext){
case'mp4':
x.vcodec='libx264';x.acodec='aac';
if(e.details.crf&&e.details.crf!==''){x.vcodec+=' -crf '+e.details.crf}
break;
case'webm':
x.acodec='libvorbis',x.vcodec='libvpx';
if(e.details.crf&&e.details.crf!==''){x.vcodec+=' -q:v '+e.details.crf}else{x.vcodec+=' -q:v 1';}
break;
}
if(e.details.vcodec==='h264_vaapi'){
x.record_video_filters.push('format=nv12,hwupload');
}
//record - use custom video codec
if(e.details.vcodec&&e.details.vcodec!==''&&e.details.vcodec!=='default'){x.vcodec=e.details.vcodec}
//record - use custom audio codec
if(e.details.acodec&&e.details.acodec!==''&&e.details.acodec!=='default'){x.acodec=e.details.acodec}
if(e.details.cust_record){
if(x.acodec=='aac'&&e.details.cust_record.indexOf('-strict -2')===-1){e.details.cust_record+=' -strict -2';}
if(e.details.cust_record.indexOf('-threads')===-1){e.details.cust_record+=' -threads 1';}
}
// if(e.details.cust_input&&(e.details.cust_input.indexOf('-use_wallclock_as_timestamps 1')>-1)===false){e.details.cust_input+=' -use_wallclock_as_timestamps 1';}
//record - ready or reset codecs
if(x.acodec!=='no'){
if(x.acodec.indexOf('none')>-1){x.acodec=''}else{x.acodec=' -acodec '+x.acodec}
}else{
x.acodec=' -an'
}
if(x.vcodec.indexOf('none')>-1){x.vcodec=''}else{x.vcodec=' -vcodec '+x.vcodec}
//record - frames per second (fps)
if(e.fps&&e.fps!==''&&e.details.vcodec!=='copy'){x.record_fps=' -r '+e.fps}else{x.record_fps=''}
//stream - frames per second (fps)
if(e.details.stream_fps&&e.details.stream_fps!==''){x.stream_fps=' -r '+e.details.stream_fps}else{x.stream_fps=''}
//record - timestamp options for -vf
if(e.details.timestamp&&e.details.timestamp=="1"&&e.details.vcodec!=='copy'){
//font
if(e.details.timestamp_font&&e.details.timestamp_font!==''){x.time_font=e.details.timestamp_font}else{x.time_font='/usr/share/fonts/truetype/freefont/FreeSans.ttf'}
//position x
if(e.details.timestamp_x&&e.details.timestamp_x!==''){x.timex=e.details.timestamp_x}else{x.timex='(w-tw)/2'}
//position y
if(e.details.timestamp_y&&e.details.timestamp_y!==''){x.timey=e.details.timestamp_y}else{x.timey='0'}
//text color
if(e.details.timestamp_color&&e.details.timestamp_color!==''){x.time_color=e.details.timestamp_color}else{x.time_color='white'}
//box color
if(e.details.timestamp_box_color&&e.details.timestamp_box_color!==''){x.time_box_color=e.details.timestamp_box_color}else{x.time_box_color='0x00000000@1'}
//text size
if(e.details.timestamp_font_size&&e.details.timestamp_font_size!==''){x.time_font_size=e.details.timestamp_font_size}else{x.time_font_size='10'}
x.record_video_filters.push('drawtext=fontfile='+x.time_font+':text=\'%{localtime}\':x='+x.timex+':y='+x.timey+':fontcolor='+x.time_color+':box=1:boxcolor='+x.time_box_color+':fontsize='+x.time_font_size);
}
//record - watermark for -vf
if(e.details.watermark&&e.details.watermark=="1"&&e.details.watermark_location&&e.details.watermark_location!==''){
switch(e.details.watermark_position){
case'tl'://top left
x.watermark_position='10:10'
break;
case'tr'://top right
x.watermark_position='main_w-overlay_w-10:10'
break;
case'bl'://bottom left
x.watermark_position='10:main_h-overlay_h-10'
break;
default://bottom right
x.watermark_position='(main_w-overlay_w-10)/2:(main_h-overlay_h-10)/2'
break;
}
x.record_video_filters.push('movie='+e.details.watermark_location+'[watermark],[in][watermark]overlay='+x.watermark_position+'[out]');
}
//record - rotation
if(e.details.rotate_record&&e.details.rotate_record!==""&&e.details.rotate_record!=="no"&&e.details.stream_vcodec!=="copy"){
x.record_video_filters.push('transpose='+e.details.rotate_record);
}
//check custom record filters for -vf
if(e.details.vf&&e.details.vf!==''){
x.record_video_filters.push(e.details.vf)
}
//compile filter string for -vf
if(x.record_video_filters.length>0){
x.record_video_filters=' -vf '+x.record_video_filters.join(',')
}else{
x.record_video_filters=''
}
//stream - timestamp
if(e.details.stream_timestamp&&e.details.stream_timestamp=="1"&&e.details.vcodec!=='copy'){
//font
if(e.details.stream_timestamp_font&&e.details.stream_timestamp_font!==''){x.stream_timestamp_font=e.details.stream_timestamp_font}else{x.stream_timestamp_font='/usr/share/fonts/truetype/freefont/FreeSans.ttf'}
//position x
if(e.details.stream_timestamp_x&&e.details.stream_timestamp_x!==''){x.stream_timestamp_x=e.details.stream_timestamp_x}else{x.stream_timestamp_x='(w-tw)/2'}
//position y
if(e.details.stream_timestamp_y&&e.details.stream_timestamp_y!==''){x.stream_timestamp_y=e.details.stream_timestamp_y}else{x.stream_timestamp_y='0'}
//text color
if(e.details.stream_timestamp_color&&e.details.stream_timestamp_color!==''){x.stream_timestamp_color=e.details.stream_timestamp_color}else{x.stream_timestamp_color='white'}
//box color
if(e.details.stream_timestamp_box_color&&e.details.stream_timestamp_box_color!==''){x.stream_timestamp_box_color=e.details.stream_timestamp_box_color}else{x.stream_timestamp_box_color='0x00000000@1'}
//text size
if(e.details.stream_timestamp_font_size&&e.details.stream_timestamp_font_size!==''){x.stream_timestamp_font_size=e.details.stream_timestamp_font_size}else{x.stream_timestamp_font_size='10'}
x.stream_video_filters.push('drawtext=fontfile='+x.stream_timestamp_font+':text=\'%{localtime}\':x='+x.stream_timestamp_x+':y='+x.stream_timestamp_y+':fontcolor='+x.stream_timestamp_color+':box=1:boxcolor='+x.stream_timestamp_box_color+':fontsize='+x.stream_timestamp_font_size);
}
//stream - watermark for -vf
if(e.details.stream_watermark&&e.details.stream_watermark=="1"&&e.details.stream_watermark_location&&e.details.stream_watermark_location!==''){
switch(e.details.stream_watermark_position){
case'tl'://top left
x.stream_watermark_position='10:10'
break;
case'tr'://top right
x.stream_watermark_position='main_w-overlay_w-10:10'
break;
case'bl'://bottom left
x.stream_watermark_position='10:main_h-overlay_h-10'
break;
default://bottom right
x.stream_watermark_position='(main_w-overlay_w-10)/2:(main_h-overlay_h-10)/2'
break;
}
x.stream_video_filters.push('movie='+e.details.stream_watermark_location+'[watermark],[in][watermark]overlay='+x.stream_watermark_position+'[out]');
}
//stream - rotation
if(e.details.rotate_stream&&e.details.rotate_stream!==""&&e.details.rotate_stream!=="no"&&e.details.stream_vcodec!=='copy'){
x.stream_video_filters.push('transpose='+e.details.rotate_stream);
}
//stream - hls vcodec
if(e.details.stream_vcodec&&e.details.stream_vcodec!=='no'){
if(e.details.stream_vcodec!==''){x.stream_vcodec=' -c:v '+e.details.stream_vcodec}else{x.stream_vcodec=' -c:v libx264'}
}else{
x.stream_vcodec='';
}
//stream - hls acodec
if(e.details.stream_acodec!=='no'){
if(e.details.stream_acodec&&e.details.stream_acodec!==''){x.stream_acodec=' -c:a '+e.details.stream_acodec}else{x.stream_acodec=''}
}else{
x.stream_acodec=' -an';
}
//stream - hls segment time
if(e.details.hls_time&&e.details.hls_time!==''){x.hls_time=e.details.hls_time}else{x.hls_time="2"} //hls list size
if(e.details.hls_list_size&&e.details.hls_list_size!==''){x.hls_list_size=e.details.hls_list_size}else{x.hls_list_size=2}
//stream - custom flags
if(e.details.cust_stream&&e.details.cust_stream!==''){x.cust_stream=' '+e.details.cust_stream}else{x.cust_stream=''}
//stream - preset
if(e.details.preset_stream&&e.details.preset_stream!==''){x.preset_stream=' -preset '+e.details.preset_stream;}else{x.preset_stream=''}
//stream - quality
//hardware acceleration
if(e.details.accelerator && e.details.accelerator==='1' && e.isStreamer === false){
if(e.details.hwaccel&&e.details.hwaccel!==''){
x.hwaccel+=' -hwaccel '+e.details.hwaccel;
}
if(e.details.hwaccel_vcodec&&e.details.hwaccel_vcodec!==''){
x.hwaccel+=' -c:v '+e.details.hwaccel_vcodec;
}
if(e.details.hwaccel_device&&e.details.hwaccel_device!==''){
switch(e.details.hwaccel){
case'vaapi':
x.hwaccel+=' -vaapi_device '+e.details.hwaccel_device;
break;
default:
x.hwaccel+=' -hwaccel_device '+e.details.hwaccel_device;
break;
}
}
// else{
// if(e.details.hwaccel==='vaapi'){
// x.hwaccel+=' -hwaccel_device 0';
// }
// }
}
if(e.details.stream_vcodec==='h264_vaapi'){
x.stream_video_filters=[]
x.stream_video_filters.push('format=nv12,hwupload');
if(e.details.stream_scale_x&&e.details.stream_scale_x!==''&&e.details.stream_scale_y&&e.details.stream_scale_y!==''){
x.stream_video_filters.push('scale_vaapi=w='+e.details.stream_scale_x+':h='+e.details.stream_scale_y)
}
}
//stream - video filter
if(e.details.svf&&e.details.svf!==''){
x.stream_video_filters.push(e.details.svf)
}
if(x.stream_video_filters.length>0){
x.stream_video_filters=' -vf '+x.stream_video_filters.join(',')
}else{
x.stream_video_filters=''
}
//stream - pipe build
if(e.details.input_map_choices&&e.details.input_map_choices.stream){
//add input feed map
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.stream)
}
if(e.details.stream_vcodec!=='copy'){
x.cust_stream+=x.stream_fps
}
switch(e.details.stream_type){
case'mp4':
x.cust_stream+=' -movflags +frag_keyframe+empty_moov+default_base_moof -metadata title="Poseidon Stream" -reset_timestamps 1'
if(e.details.stream_vcodec!=='copy'){
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -crf '+e.details.stream_quality;
x.cust_stream+=x.preset_stream
x.cust_stream+=x.stream_video_filters
}
x.pipe+=' -f mp4'+x.stream_acodec+x.stream_vcodec+x.cust_stream+' pipe:1';
break;
case'flv':
if(e.details.stream_vcodec!=='copy'){
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -crf '+e.details.stream_quality;
x.cust_stream+=x.preset_stream
x.cust_stream+=x.stream_video_filters
}
x.pipe+=' -f flv'+x.stream_acodec+x.stream_vcodec+x.cust_stream+' pipe:1';
break;
case'hls':
if(e.details.stream_vcodec!=='h264_vaapi'&&e.details.stream_vcodec!=='copy'){
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -crf '+e.details.stream_quality;
if(x.cust_stream.indexOf('-tune')===-1){x.cust_stream+=' -tune zerolatency'}
if(x.cust_stream.indexOf('-g ')===-1){x.cust_stream+=' -g 1'}
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
x.cust_stream+=x.stream_video_filters
}
x.pipe+=x.preset_stream+x.stream_acodec+x.stream_vcodec+' -f hls'+x.cust_stream+' -hls_time '+x.hls_time+' -hls_list_size '+x.hls_list_size+' -start_number 0 -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist "'+e.sdir+'s.m3u8"';
break;
case'mjpeg':
if(e.coProcessor === false){
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -q:v '+e.details.stream_quality;
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
x.pipe+=' -an -c:v mjpeg -f mpjpeg -boundary_tag shinobi'+x.cust_stream+x.stream_video_filters+' pipe:1';
}
break;
case'pam':
if(e.coProcessor === false){
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -q:v '+e.details.stream_quality;
x.pipe+=' -an -c:v pam -pix_fmt rgba -f image2pipe'+x.cust_stream+x.stream_video_filters+' pipe:1';
}
break;
case'b64':case'':case undefined:case null://base64
if(e.coProcessor === false){
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -q:v '+e.details.stream_quality;
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
x.pipe+=' -an -c:v mjpeg -f image2pipe'+x.cust_stream+x.stream_video_filters+' pipe:1';
}
break;
default:
x.pipe=''
break;
}
if(e.details.stream_channels){
e.details.stream_channels.forEach(function(v,n){
x.pipe += s.createStreamChannel(e,n+config.pipeAddition,v)
})
}
//detector - plugins, motion
if(e.details.detector==='1'&&e.details.detector_send_frames==='1'){
if(e.details.accelerator !== '1'){
if(e.details.input_map_choices&&e.details.input_map_choices.detector){
//add input feed map
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.detector)
}
if(!e.details.detector_fps||e.details.detector_fps===''){e.details.detector_fps=2}
if(e.details.detector_scale_x&&e.details.detector_scale_x!==''&&e.details.detector_scale_y&&e.details.detector_scale_y!==''){x.dratio=' -s '+e.details.detector_scale_x+'x'+e.details.detector_scale_y}else{x.dratio=' -s 320x240'}
if(e.details.cust_detect&&e.details.cust_detect!==''){x.cust_detect+=e.details.cust_detect;}
if(e.details.detector_pam==='1'){
x.pipe+=' -an -c:v pam -pix_fmt gray -f image2pipe -r '+e.details.detector_fps+x.cust_detect+x.dratio+' pipe:3'
if(e.details.detector_use_detect_object === '1'){
//for object detection
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.detector)
x.pipe += ' -f singlejpeg -vf fps='+e.details.detector_fps+x.cust_detect+x.dratio+' pipe:4';
}
}else{
x.pipe+=' -f singlejpeg -vf fps='+e.details.detector_fps+x.cust_detect+x.dratio+' pipe:3';
}
}
}
//api - snapshot bin/ cgi.bin (JPEG Mode)
if(e.details.snap === '1'){
if(e.details.input_map_choices&&e.details.input_map_choices.snap){
//add input feed map
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.snap)
}
if(e.coProcessor === false){
if(!e.details.snap_fps || e.details.snap_fps === ''){e.details.snap_fps = 1}
if(e.details.snap_vf && e.details.snap_vf !== ''){x.snap_vf=' -vf '+e.details.snap_vf}else{x.snap_vf=''}
if(e.details.snap_scale_x && e.details.snap_scale_x !== '' && e.details.snap_scale_y && e.details.snap_scale_y !== ''){x.snap_ratio = ' -s '+e.details.snap_scale_x+'x'+e.details.snap_scale_y}else{x.snap_ratio=''}
if(e.details.cust_snap && e.details.cust_snap !== ''){x.cust_snap = ' '+e.details.cust_snap}else{x.cust_snap=''}
x.pipe+=' -update 1 -r '+e.details.snap_fps+x.cust_snap+x.snap_ratio+x.snap_vf+' "'+e.sdir+'s.jpg" -y';
}
}
//Traditional Recording Buffer
if(e.details.detector=='1'&&e.details.detector_trigger=='1'&&e.details.detector_record_method==='sip'){
if(e.details.input_map_choices&&e.details.input_map_choices.detector_sip_buffer){
//add input feed map
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.detector_sip_buffer)
}
x.detector_buffer_filters=[]
if(!e.details.detector_buffer_vcodec||e.details.detector_buffer_vcodec===''||e.details.detector_buffer_vcodec==='auto'){
if(e.details.accelerator === '1' && e.details.hwaccel_vcodec === 'cuvid' && e.details.hwaccel_vcodec === ('h264_cuvid' || 'hevc_cuvid' || 'mjpeg_cuvid' || 'mpeg4_cuvid')){
e.details.detector_buffer_vcodec = 'h264_nvenc'
}else{
switch(e.type){
case'h264':case'hls':case'mp4':
e.details.detector_buffer_vcodec = 'copy'
break;
default:
e.details.detector_buffer_vcodec = 'libx264'
break;
}
}
}
if(!e.details.detector_buffer_acodec||e.details.detector_buffer_acodec===''||e.details.detector_buffer_acodec==='auto'){
switch(e.type){
case'h264':case'hls':case'mp4':
e.details.detector_buffer_acodec = 'copy'
break;
default:
e.details.detector_buffer_acodec = 'aac'
break;
}
}
if(e.details.detector_buffer_acodec === 'no'){
x.detector_buffer_acodec = ' -an'
}else{
x.detector_buffer_acodec = ' -c:a '+e.details.detector_buffer_acodec
}
if(!e.details.detector_buffer_tune||e.details.detector_buffer_tune===''){e.details.detector_buffer_tune='zerolatency'}
if(!e.details.detector_buffer_g||e.details.detector_buffer_g===''){e.details.detector_buffer_g='1'}
if(!e.details.detector_buffer_hls_time||e.details.detector_buffer_hls_time===''){e.details.detector_buffer_hls_time='2'}
if(!e.details.detector_buffer_hls_list_size||e.details.detector_buffer_hls_list_size===''){e.details.detector_buffer_hls_list_size='4'}
if(!e.details.detector_buffer_start_number||e.details.detector_buffer_start_number===''){e.details.detector_buffer_start_number='0'}
if(!e.details.detector_buffer_live_start_index||e.details.detector_buffer_live_start_index===''){e.details.detector_buffer_live_start_index='-3'}
if(e.details.detector_buffer_vcodec.indexOf('_vaapi')>-1){
if(x.hwaccel.indexOf('-vaapi_device')>-1){
x.detector_buffer_filters.push('format=nv12')
x.detector_buffer_filters.push('hwupload')
}else{
e.details.detector_buffer_vcodec='libx264'
}
}
if(e.details.detector_buffer_vcodec!=='copy'){
if(e.details.detector_buffer_fps&&e.details.detector_buffer_fps!==''){
x.detector_buffer_fps=' -r '+e.details.detector_buffer_fps
}else{
x.detector_buffer_fps=' -r 30'
}
}else{
x.detector_buffer_fps=''
}
if(x.detector_buffer_filters.length>0){
x.pipe+=' -vf '+x.detector_buffer_filters.join(',')
}
x.pipe+=x.detector_buffer_fps+x.detector_buffer_acodec+' -c:v '+e.details.detector_buffer_vcodec+' -f hls -tune '+e.details.detector_buffer_tune+' -g '+e.details.detector_buffer_g+' -hls_time '+e.details.detector_buffer_hls_time+' -hls_list_size '+e.details.detector_buffer_hls_list_size+' -start_number '+e.details.detector_buffer_start_number+' -live_start_index '+e.details.detector_buffer_live_start_index+' -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist "'+e.sdir+'detectorStream.m3u8"'
}
if(e.coProcessor === true){
// the coProcessor ffmpeg consumes this HLS stream (no audio, frames only)
x.pipe += ' -q:v 1 -an -c:v copy -f hls -tune zerolatency -g 1 -hls_time 2 -hls_list_size 3 -start_number 0 -live_start_index 3 -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist "'+e.sdir+'cpuOnly.m3u8"'
}
//custom - output
if(e.details.custom_output&&e.details.custom_output!==''){x.pipe+=' '+e.details.custom_output;}
//custom - input flags
if(e.details.cust_input&&e.details.cust_input!==''){x.cust_input+=' '+e.details.cust_input;}
//logging - level
if(e.details.loglevel&&e.details.loglevel!==''){x.loglevel='-loglevel '+e.details.loglevel;}else{x.loglevel='-loglevel error'}
//build record string.
if(e.mode==='record'){
if(e.details.input_map_choices&&e.details.input_map_choices.record){
//add input feed map
x.record_string += s.createFFmpegMap(e,e.details.input_map_choices.record)
}
//if h264, hls, mp4, or local add the audio codec flag
switch(e.type){
case'h264':case'hls':case'mp4':case'local':
x.record_string+=x.acodec;
break;
}
//custom flags
if(e.details.cust_record&&e.details.cust_record!==''){x.record_string+=' '+e.details.cust_record;}
//preset flag
if(e.details.preset_record&&e.details.preset_record!==''){x.record_string+=' -preset '+e.details.preset_record;}
//main string write
x.record_string+=x.vcodec+x.record_fps+x.record_video_filters+x.record_dimensions+x.segment;
}
//create executeable FFMPEG command
x.ffmpegCommandString = x.loglevel+x.input_fps;
//progress pipe
// x.ffmpegCommandString += ' -progress pipe:5';
//add main input
if((e.type === 'mp4' || e.type === 'mjpeg') && x.cust_input.indexOf('-re') === -1){
x.cust_input += ' -re'
}
switch(e.type){
case'dashcam':
x.ffmpegCommandString += x.cust_input+x.hwaccel+' -i -';
break;
case'socket':case'jpeg':case'pipe':
x.ffmpegCommandString += ' -pattern_type glob -f image2pipe'+x.record_fps+' -vcodec mjpeg'+x.cust_input+x.hwaccel+' -i -';
break;
case'mjpeg':
x.ffmpegCommandString += ' -reconnect 1 -f mjpeg'+x.cust_input+x.hwaccel+' -i "'+e.url+'"';
break;
case'h264':case'hls':case'mp4':
x.ffmpegCommandString += x.cust_input+x.hwaccel+' -i "'+e.url+'"';
break;
case'local':
x.ffmpegCommandString += x.cust_input+x.hwaccel+' -i "'+e.path+'"';
break;
}
//add extra input maps
if(e.details.input_maps){
e.details.input_maps.forEach(function(v,n){
x.ffmpegCommandString += s.createInputMap(e,n+1,v)
})
}
//add recording and stream outputs
x.ffmpegCommandString += x.record_string+x.pipe
//hold ffmpeg command for log stream
s.group[e.ke].mon[e.mid].ffmpeg = x.ffmpegCommandString;
//create additional pipes from ffmpeg
x.stdioPipes = [];
var times = config.pipeAddition;
if(e.details.stream_channels){
times+=e.details.stream_channels.length
}
for(var i=0; i < times; i++){
x.stdioPipes.push('pipe')
}
x.ffmpegCommandString = s.splitForFFPMEG(x.ffmpegCommandString.replace(/\s+/g,' ').trim())
return spawn(config.ffmpegDir,x.ffmpegCommandString,{detached: true,stdio:x.stdioPipes});
}
s.file=function(x,e){
if(!e){e={}};
switch(x){
case'size':
return fs.statSync(e.filename)["size"];
break;
case'delete':
if(!e){return false;}
return exec('rm -f '+e,{detached: true});
break;
case'deleteFolder':
if(!e){return false;}
return exec('rm -rf '+e,{detached: true});
break;
case'deleteFiles':
if(!e.age_type){e.age_type='min'};if(!e.age){e.age='1'};
exec('find '+e.path+' -type f -c'+e.age_type+' +'+e.age+' -exec rm -f {} +',{detached: true});
break;
}
}
s.camera=function(x,e,cn,tx){
if(x!=='motion'){
var ee=s.init('noReference',e);
if(!e){e={}};if(cn&&cn.ke&&!e.ke){e.ke=cn.ke};
if(!e.mode){e.mode=x;}
if(!e.id&&e.mid){e.id=e.mid}
}
if(e.details&&(e.details instanceof Object)===false){
try{e.details=JSON.parse(e.details)}catch(err){}
}
//parse Objects
(['detector_cascades','cords','input_map_choices']).forEach(function(v){
if(e.details&&e.details[v]&&(e.details[v] instanceof Object)===false){
try{
if(e.details[v] === '') e.details[v] = '{}'
e.details[v]=JSON.parse(e.details[v]);
if(!e.details[v])e.details[v]={};
}catch(err){
}
}
});
//parse Arrays
(['stream_channels','input_maps']).forEach(function(v){
if(e.details&&e.details[v]&&(e.details[v] instanceof Array)===false){
try{
e.details[v]=JSON.parse(e.details[v]);
if(!e.details[v])e.details[v]=[];
}catch(err){
e.details[v]=[];
}
}
});
s.init(0,{ke:e.ke,mid:e.id})
switch(x){
case'buildOptionsFromUrl':
var monitorConfig = cn
URLobject=URL.parse(e)
if(monitorConfig.details.control_url_method === 'ONVIF' && monitorConfig.details.control_base_url === ''){
URLobject.port = 8000
}else if(!URLobject.port){
URLobject.port = 80
}
options = {
host: URLobject.hostname,
port: URLobject.port,
method: monitorConfig.details.control_url_method,
path: URLobject.pathname,
};
if(URLobject.query){
options.path=options.path+'?'+URLobject.query
}
if(URLobject.username&&URLobject.password){
options.username = URLobject.username
options.password = URLobject.password
options.auth=URLobject.username+':'+URLobject.password
}else if(URLobject.auth){
var auth = URLobject.auth.split(':')
options.auth=URLobject.auth
options.username = auth[0]
options.password = auth[1]
}
return options
break;
case'control':
if(!s.group[e.ke]||!s.group[e.ke].mon[e.id]){return}
var monitorConfig = s.group[e.ke].mon_conf[e.id];
if(monitorConfig.details.control!=="1"){s.log(e,{type:lang['Control Error'],msg:lang.ControlErrorText1});return}
if(!monitorConfig.details.control_base_url||monitorConfig.details.control_base_url===''){
e.base=s.init('url_no_path',monitorConfig);
}else{
e.base=monitorConfig.details.control_base_url;
}
if(!monitorConfig.details.control_url_stop_timeout || monitorConfig.details.control_url_stop_timeout === ''){
monitorConfig.details.control_url_stop_timeout = 1000
}
if(!monitorConfig.details.control_url_method||monitorConfig.details.control_url_method===''){monitorConfig.details.control_url_method="GET"}
var controlURL = e.base+monitorConfig.details['control_url_'+e.direction]
var controlURLOptions = s.camera('buildOptionsFromUrl',controlURL,monitorConfig)
if(monitorConfig.details.control_url_stop_timeout === '0' && monitorConfig.details.control_stop === '1' && s.group[e.ke].mon[e.id].ptzMoving === true){
e.direction = 'stopMove'
s.group[e.ke].mon[e.id].ptzMoving = false
}else{
s.group[e.ke].mon[e.id].ptzMoving = true
}
if(monitorConfig.details.control_url_method === 'ONVIF'){
try{
var move = function(device){
var stopOptions = {ProfileToken : device.current_profile.token,'PanTilt': true,'Zoom': true}
switch(e.direction){
case'center':
// device.services.ptz.gotoHomePosition()
msg = {type:'Center button inactive'}
s.log(e,msg)
cn(msg)
break;
case'stopMove':
msg = {type:'Control Trigger Ended'}
s.log(e,msg)
cn(msg)
device.services.ptz.stop(stopOptions).then((result) => {
// console.log(JSON.stringify(result['data'], null, ' '));
}).catch((error) => {
// console.error(error);
});
break;
default:
var controlOptions = {
ProfileToken : device.current_profile.token,
Velocity : {}
}
var onvifDirections = {
"left" : [-1.0,'x'],
"right" : [1.0,'x'],
"down" : [-1.0,'y'],
"up" : [1.0,'y'],
"zoom_in" : [1.0,'zoom'],
"zoom_out" : [-1.0,'zoom']
}
var direction = onvifDirections[e.direction]
controlOptions.Velocity[direction[1]] = direction[0];
(['x','y','z']).forEach(function(axis){
if(!controlOptions.Velocity[axis])
controlOptions.Velocity[axis] = 0
})
if(monitorConfig.details.control_stop=='1'){
device.services.ptz.continuousMove(controlOptions).then(function(err){
s.log(e,{type:'Control Trigger Started'});
if(monitorConfig.details.control_url_stop_timeout !== '0'){
setTimeout(function(){
msg = {type:'Control Trigger Ended'}
s.log(e,msg)
cn(msg)
device.services.ptz.stop(stopOptions).then((result) => {
// console.log(JSON.stringify(result['data'], null, ' '));
}).catch((error) => {
console.log(error);
});
},monitorConfig.details.control_url_stop_timeout)
}
}).catch(function(err){
console.log(err)
});
}else{
device.services.ptz.absoluteMove(controlOptions).then(function(err){
msg = {type:'Control Triggered'}
s.log(e,msg);
cn(msg)
}).catch(function(err){
console.log(err)
});
}
break;
}
}
//create onvif connection
if(!s.group[e.ke].mon[e.id].onvifConnection){
s.group[e.ke].mon[e.id].onvifConnection = new onvif.OnvifDevice({
xaddr : 'http://' + controlURLOptions.host + ':' + controlURLOptions.port + '/onvif/device_service',
user : controlURLOptions.username,
pass : controlURLOptions.password
})
s.group[e.ke].mon[e.id].onvifConnection.init().then((info) => {
move(s.group[e.ke].mon[e.id].onvifConnection)
}).catch(function(error){
console.log(error)
s.log(e,{type:lang['Control Error'],msg:error})
})
}else{
move(s.group[e.ke].mon[e.id].onvifConnection)
}
}catch(err){
console.log(err)
msg = {type:lang['Control Error'],msg:{msg:lang.ControlErrorText2,error:err,options:controlURLOptions,direction:e.direction}}
s.log(e,msg)
cn(msg)
}
}else{
var stopCamera = function(){
var stopURL = e.base+monitorConfig.details['control_url_'+e.direction+'_stop']
var options = s.camera('buildOptionsFromUrl',stopURL,monitorConfig)
var requestOptions = {
url : stopURL,
method : options.method,
auth : {
user : options.username,
pass : options.password
}
}
if(monitorConfig.details.control_digest_auth === '1'){
requestOptions.sendImmediately = true
}
request(requestOptions,function(err,data){
if(err){
msg = {ok:false,type:'Control Error',msg:err}
}else{
msg = {ok:true,type:'Control Trigger Ended'}
}
cn(msg)
s.log(e,msg);
})
}
if(e.direction === 'stopMove'){
stopCamera()
}else{
var requestOptions = {
url : controlURL,
method : controlURLOptions.method,
auth : {
user : controlURLOptions.username,
pass : controlURLOptions.password
}
}
if(monitorConfig.details.control_digest_auth === '1'){
requestOptions.sendImmediately = true
}
request(requestOptions,function(err,data){
if(err){
msg = {ok:false,type:'Control Error',msg:err};
cn(msg)
s.log(e,msg);
return
}
if(monitorConfig.details.control_stop=='1'&&e.direction!=='center'){
s.log(e,{type:'Control Triggered Started'});
if(monitorConfig.details.control_url_stop_timeout > 0){
setTimeout(function(){
stopCamera()
},monitorConfig.details.control_url_stop_timeout)
}
}else{
msg = {ok:true,type:'Control Triggered'};
cn(msg)
s.log(e,msg);
}
})
}
}
break;
case'snapshot'://get snapshot from monitor URL
if(config.doSnapshot===true){
if(e.mon.mode!=='stop'){
if(e.mon.details.snap==='1'){
fs.readFile(s.dir.streams+e.ke+'/'+e.mid+'/s.jpg',function(err,data){
if(err){s.tx({f:'monitor_snapshot',snapshot:e.mon.name,snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke);return};
s.tx({f:'monitor_snapshot',snapshot:data,snapshot_format:'ab',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
})
}else{
e.url=s.init('url',e.mon);
switch(e.mon.type){
case'mjpeg':case'h264':case'local':
if(e.mon.type==='local'){e.url=e.mon.path;}
e.spawn=spawn(config.ffmpegDir,('-loglevel quiet -i '+e.url+' -s 400x400 -r 25 -ss 1.8 -frames:v 1 -f singlejpeg pipe:1').split(' '),{detached: true})
e.spawn.stdout.on('data',function(data){
e.snapshot_sent=true; s.tx({f:'monitor_snapshot',snapshot:data.toString('base64'),snapshot_format:'b64',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
e.spawn.kill();
});
e.spawn.on('close',function(data){
if(!e.snapshot_sent){
s.tx({f:'monitor_snapshot',snapshot:e.mon.name,snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
}
delete(e.snapshot_sent);
});
break;
case'jpeg':
request({url:e.url,method:'GET',encoding:null},function(err,data){
if(err){s.tx({f:'monitor_snapshot',snapshot:e.mon.name,snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke);return};
s.tx({f:'monitor_snapshot',snapshot:data.body,snapshot_format:'ab',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
})
break;
default:
s.tx({f:'monitor_snapshot',snapshot:'...',snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
break;
}
}
}else{
s.tx({f:'monitor_snapshot',snapshot:'Disabled',snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
}
}else{
s.tx({f:'monitor_snapshot',snapshot:e.mon.name,snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
}
break;
case'record_off'://stop recording and start
if(!s.group[e.ke].mon[e.id].record){s.group[e.ke].mon[e.id].record={}}
s.group[e.ke].mon[e.id].record.yes=0;
s.camera('start',e);
break;
case'watch_on'://live streamers - join
// if(s.group[e.ke].mon[e.id].watch[cn.id]){s.camera('watch_off',e,cn,tx);return}
if(!cn.monitor_watching){cn.monitor_watching={}}
if(!cn.monitor_watching[e.id]){cn.monitor_watching[e.id]={ke:e.ke}}
s.group[e.ke].mon[e.id].watch[cn.id]={};
// if(Object.keys(s.group[e.ke].mon[e.id].watch).length>0){
// s.sqlQuery('SELECT * FROM Monitors WHERE ke=? AND mid=?',[e.ke,e.id],function(err,r) {
// if(r&&r[0]){
// r=r[0];
// r.url=s.init('url',r);
// s.group[e.ke].mon.type=r.type;
// }
// })
// }
break;
case'watch_off'://live streamers - leave
if(cn.monitor_watching){delete(cn.monitor_watching[e.id])}
if(s.group[e.ke].mon[e.id]&&s.group[e.ke].mon[e.id].watch){
delete(s.group[e.ke].mon[e.id].watch[cn.id]),e.ob=Object.keys(s.group[e.ke].mon[e.id].watch).length
if(e.ob===0){
delete(s.group[e.ke].mon[e.id].watch)
}
}else{
e.ob=0;
}
if(tx){tx({f:'monitor_watch_off',ke:e.ke,id:e.id,cnid:cn.id})};
s.tx({viewers:e.ob,ke:e.ke,id:e.id},'MON_'+e.id);
break;
case'restart'://restart monitor
s.init('monitorStatus',{id:e.id,ke:e.ke,status:'Restarting'});
s.camera('stop',e)
setTimeout(function(){
s.camera(e.mode,e)
},1300)
break;
case'idle':case'stop'://stop monitor
if(!s.group[e.ke]||!s.group[e.ke].mon[e.id]){return}
if(config.childNodes.enabled === true && config.childNodes.mode === 'master' && s.group[e.ke].mon[e.id].childNode && s.childNodes[s.group[e.ke].mon[e.id].childNode].activeCameras[e.ke+e.id]){
s.group[e.ke].mon[e.id].started = 0
s.cx({
//function
f : 'cameraStop',
//data, options
d : s.group[e.ke].mon_conf[e.id]
},s.group[e.ke].mon[e.id].childNodeId)
s.cx({f:'sync',sync:s.group[e.ke].mon_conf[e.id],ke:e.ke,mid:e.id},s.group[e.ke].mon[e.id].childNodeId);
}else{
if(s.group[e.ke].mon[e.id].eventBasedRecording.process){
clearTimeout(s.group[e.ke].mon[e.id].eventBasedRecording.timeout)
s.group[e.ke].mon[e.id].eventBasedRecording.allowEnd=true;
s.group[e.ke].mon[e.id].eventBasedRecording.process.kill('SIGTERM');
}
if(s.group[e.ke].mon[e.id].fswatch){s.group[e.ke].mon[e.id].fswatch.close();delete(s.group[e.ke].mon[e.id].fswatch)}
if(s.group[e.ke].mon[e.id].fswatchStream){s.group[e.ke].mon[e.id].fswatchStream.close();delete(s.group[e.ke].mon[e.id].fswatchStream)}
if(s.group[e.ke].mon[e.id].last_frame){delete(s.group[e.ke].mon[e.id].last_frame)}
if(s.group[e.ke].mon[e.id].started!==1){return}
s.kill(s.group[e.ke].mon[e.id].spawn,e);
if(e.neglectTriggerTimer===1){
delete(e.neglectTriggerTimer);
}else{
clearTimeout(s.group[e.ke].mon[e.id].trigger_timer)
delete(s.group[e.ke].mon[e.id].trigger_timer)
}
clearInterval(s.group[e.ke].mon[e.id].running);
clearInterval(s.group[e.ke].mon[e.id].detector_notrigger_timeout)
clearTimeout(s.group[e.ke].mon[e.id].err_fatal_timeout);
s.group[e.ke].mon[e.id].started=0;
if(s.group[e.ke].mon[e.id].record){s.group[e.ke].mon[e.id].record.yes=0}
s.tx({f:'monitor_stopping',mid:e.id,ke:e.ke,time:s.formattedTime()},'GRP_'+e.ke);
s.camera('snapshot',{mid:e.id,ke:e.ke,mon:e})
if(x==='stop'){
s.log(e,{type:lang['Monitor Stopped'],msg:lang.MonitorStoppedText});
clearTimeout(s.group[e.ke].mon[e.id].delete)
if(e.delete===1){
s.group[e.ke].mon[e.id].delete=setTimeout(function(){
delete(s.group[e.ke].mon[e.id]);
delete(s.group[e.ke].mon_conf[e.id]);
},1000*60);
}
}else{
s.tx({f:'monitor_idle',mid:e.id,ke:e.ke,time:s.formattedTime()},'GRP_'+e.ke);
s.log(e,{type:lang['Monitor Idling'],msg:lang.MonitorIdlingText});
}
}
var wantedStatus = lang.Stopped
if(x==='idle'){
var wantedStatus = lang.Idle
}
s.init('monitorStatus',{id:e.id,ke:e.ke,status:wantedStatus});
break;
case'start':case'record'://watch or record monitor url
s.init(0,{ke:e.ke,mid:e.id})
if(!s.group[e.ke].mon_conf[e.id]){s.group[e.ke].mon_conf[e.id]=s.init('noReference',e);}
e.url = s.init('url',e);
if(s.group[e.ke].mon[e.id].started===1){
//stop action, monitor already started or recording
return
}
//lock this function
s.init('monitorStatus',{id:e.id,ke:e.ke,status:lang.Starting});
s.group[e.ke].mon[e.id].started = 1;
//create host string without username and password
e.hosty = e.host.split('@');
if(e.hosty[1]){
//username and password found
e.hosty = e.hosty[1]
}else{
//no username or password in `host` string
e.hosty = e.hosty[0]
}
//set recording status
var wantedStatus = lang.Watching
if(x==='record'){
var wantedStatus = lang.Recording
s.group[e.ke].mon[e.id].record.yes=1;
}else{
s.group[e.ke].mon[e.mid].record.yes=0;
}
//set the recording directory
if(e.details && e.details.dir && e.details.dir !== '' && config.childNodes.mode !== 'child'){
//addStorage choice
e.dir=s.checkCorrectPathEnding(e.details.dir)+e.ke+'/';
if (!fs.existsSync(e.dir)){
fs.mkdirSync(e.dir);
}
e.dir=e.dir+e.id+'/';
if (!fs.existsSync(e.dir)){
fs.mkdirSync(e.dir);
}
}else{
//MAIN videos dir
e.dir=s.dir.videos+e.ke+'/';
if (!fs.existsSync(e.dir)){
fs.mkdirSync(e.dir);
}
e.dir=s.dir.videos+e.ke+'/'+e.id+'/';
if (!fs.existsSync(e.dir)){
fs.mkdirSync(e.dir);
}
}
//set the temporary files directory
var setStreamDir = function(){
//stream dir
e.sdir=s.dir.streams+e.ke+'/';
if (!fs.existsSync(e.sdir)){
fs.mkdirSync(e.sdir);
}
e.sdir=s.dir.streams+e.ke+'/'+e.id+'/';
if (!fs.existsSync(e.sdir)){
fs.mkdirSync(e.sdir);
}else{
s.file('deleteFolder',e.sdir+'*')
}
}
setStreamDir()
//set up fatal error handler
if(e.details.fatal_max===''){
e.details.fatal_max = 10
}else{
e.details.fatal_max = parseFloat(e.details.fatal_max)
}
var errorFatal = function(errorMessage){
s.debugLog(errorMessage)
clearTimeout(s.group[e.ke].mon[e.id].err_fatal_timeout);
++errorFatalCount;
if(s.group[e.ke].mon[e.id].started===1){
s.group[e.ke].mon[e.id].err_fatal_timeout=setTimeout(function(){
if(e.details.fatal_max!==0&&errorFatalCount>e.details.fatal_max){
s.camera('stop',{id:e.id,ke:e.ke})
}else{
launchMonitorProcesses()
};
},5000);
}else{
s.kill(s.group[e.ke].mon[e.id].spawn,e);
}
s.init('monitorStatus',{id:e.id,ke:e.ke,status:lang.Died});
}
var errorFatalCount = 0;
//cutoff time and recording check interval
if(!e.details.cutoff||e.details.cutoff===''){e.cutoff=15}else{e.cutoff=parseFloat(e.details.cutoff)};
if(isNaN(e.cutoff)===true){e.cutoff=15}
//set master based process launcher
var launchMonitorProcesses = function(){
s.group[e.ke].mon[e.id].allowStdinWrite = false
s.txToDashcamUsers({
f : 'disable_stream',
ke : e.ke,
mid : e.id
},e.ke)
if(e.details.detector_trigger=='1'){
s.group[e.ke].mon[e.id].motion_lock=setTimeout(function(){
clearTimeout(s.group[e.ke].mon[e.id].motion_lock);
delete(s.group[e.ke].mon[e.id].motion_lock);
},15000)
}
//start "no motion" checker
if(e.details.detector=='1'&&e.details.detector_notrigger=='1'){
if(!e.details.detector_notrigger_timeout||e.details.detector_notrigger_timeout===''){
e.details.detector_notrigger_timeout=10
}
e.detector_notrigger_timeout=parseFloat(e.details.detector_notrigger_timeout)*1000*60;
s.sqlQuery('SELECT mail FROM Users WHERE ke=? AND details NOT LIKE ?',[e.ke,'%"sub"%'],function(err,r){
r=r[0];
s.group[e.ke].mon[e.id].detector_notrigger_timeout_function=function(){
if(config.mail&&e.details.detector_notrigger_mail=='1'){
e.mailOptions = {
from: config.mail.from, // sender address
to: r.mail, // list of receivers
subject: lang.NoMotionEmailText1+' '+e.name+' ('+e.id+')', // Subject line
html: '<i>'+lang.NoMotionEmailText2+' '+e.details.detector_notrigger_timeout+' '+lang.minutes+'.</i>',
};
e.mailOptions.html+='<div><b>'+lang['Monitor Name']+' </b> : '+e.name+'</div>'
e.mailOptions.html+='<div><b>'+lang['Monitor ID']+' </b> : '+e.id+'</div>'
nodemailer.sendMail(e.mailOptions, (error, info) => {
if (error) {
s.systemLog('detector:notrigger:sendMail',error)
s.tx({f:'error',ff:'detector_notrigger_mail',id:e.id,ke:e.ke,error:error},'GRP_'+e.ke);
return ;
}
s.tx({f:'detector_notrigger_mail',id:e.id,ke:e.ke,info:info},'GRP_'+e.ke);
});
}
}
clearInterval(s.group[e.ke].mon[e.id].detector_notrigger_timeout)
s.group[e.ke].mon[e.id].detector_notrigger_timeout=setInterval(s.group[e.ke].mon[e.id].detector_notrigger_timeout_function,s.group[e.ke].mon[e.id].detector_notrigger_timeout)
})
}
var resetRecordingCheck = function(){
clearTimeout(s.group[e.ke].mon[e.id].checker)
var cutoff = e.cutoff + 0
if(e.type === 'dashcam'){
cutoff *= 100
}
s.group[e.ke].mon[e.id].checker=setTimeout(function(){
if(s.group[e.ke].mon[e.id].started === 1 && s.group[e.ke].mon_conf[e.id].mode === 'record'){
launchMonitorProcesses();
s.init('monitorStatus',{id:e.id,ke:e.ke,status:lang.Restarting});
s.log(e,{type:lang['Camera is not recording'],msg:{msg:lang['Restarting Process']}});
}
},60000 * cutoff * 1.1);
}
var resetStreamCheck=function(){
clearTimeout(s.group[e.ke].mon[e.id].checkStream)
s.group[e.ke].mon[e.id].checkStream = setTimeout(function(){
if(s.group[e.ke].mon[e.id].started===1){
launchMonitorProcesses();
s.log(e,{type:lang['Camera is not streaming'],msg:{msg:lang['Restarting Process']}});
}
},60000*1);
}
if(e.details.snap === '1'){
var resetSnapCheck = function(){
clearTimeout(s.group[e.ke].mon[e.id].checkSnap)
s.group[e.ke].mon[e.id].checkSnap = setTimeout(function(){
if(s.group[e.ke].mon[e.id].started === 1){
fs.stat(e.sdir+'s.jpg',function(err,snap){
var notStreaming = function(){
if(e.coProcessor === true){
s.coSpawnLauncher(e)
}else{
launchMonitorProcesses()
}
s.log(e,{type:lang['Camera is not streaming'],msg:{msg:lang['Restarting Process']}});
}
if(err){
notStreaming()
}else{
if(!e.checkSnapTime)e.checkSnapTime = snap.mtime
if(err || e.checkSnapTime === snap.mtime){
e.checkSnapTime = snap.mtime
notStreaming()
}else{
resetSnapCheck()
}
}
})
}
},60000*1);
}
resetSnapCheck()
}
if(config.childNodes.mode !== 'child' && s.platform!=='darwin' && (x==='record' || (x==='start'&&e.details.detector_record_method==='sip'))){
//check if ffmpeg is recording
s.group[e.ke].mon[e.id].fswatch = fs.watch(e.dir, {encoding : 'utf8'}, (event, filename) => {
switch(event){
case'rename':
s.group[e.ke].mon[e.id].open = filename.split('.')[0]
break;
case'change':
resetRecordingCheck()
break;
}
});
}
if(
//is MacOS
s.platform !== 'darwin' &&
//is Watch-Only or Record
(x === 'start' || x === 'record') &&
//if JPEG API enabled or Stream Type is HLS
(e.details.stream_type === 'jpeg' || e.details.stream_type === 'hls' || e.details.snap === '1')
){
s.group[e.ke].mon[e.id].fswatchStream = fs.watch(e.sdir, {encoding : 'utf8'}, () => {
resetStreamCheck()
})
}
s.camera('snapshot',{mid:e.id,ke:e.ke,mon:e})
//check host to see if has password and user in it
setStreamDir()
clearTimeout(s.group[e.ke].mon[e.id].checker)
if(s.group[e.ke].mon[e.id].started===1){
e.error_count=0;
s.group[e.ke].mon[e.id].error_socket_timeout_count=0;
s.kill(s.group[e.ke].mon[e.id].spawn,e);
startVideoProcessor=function(err,o){
if(o.success===true){
e.frames=0;
if(!s.group[e.ke].mon[e.id].record){s.group[e.ke].mon[e.id].record={yes:1}};
//launch ffmpeg (main)
s.group[e.ke].mon[e.id].spawn = s.ffmpeg(e)
if(e.type === 'dashcam'){
setTimeout(function(){
s.group[e.ke].mon[e.id].allowStdinWrite = true
s.txToDashcamUsers({
f : 'enable_stream',
ke : e.ke,
mid : e.id
},e.ke)
},30000)
}
s.init('monitorStatus',{id:e.id,ke:e.ke,status:wantedStatus});
//on unexpected exit restart
s.group[e.ke].mon[e.id].spawn_exit=function(){
if(s.group[e.ke].mon[e.id].started===1){
if(e.details.loglevel!=='quiet'){
s.log(e,{type:lang['Process Unexpected Exit'],msg:{msg:lang['Process Crashed for Monitor']+' : '+e.id,cmd:s.group[e.ke].mon[e.id].ffmpeg}});
}
errorFatal('Process Unexpected Exit');
}
}
s.group[e.ke].mon[e.id].spawn.on('end',s.group[e.ke].mon[e.id].spawn_exit)
s.group[e.ke].mon[e.id].spawn.on('exit',s.group[e.ke].mon[e.id].spawn_exit)
//emitter for mjpeg
if(!e.details.stream_mjpeg_clients||e.details.stream_mjpeg_clients===''||isNaN(e.details.stream_mjpeg_clients)===false){e.details.stream_mjpeg_clients=20;}else{e.details.stream_mjpeg_clients=parseInt(e.details.stream_mjpeg_clients)}
s.group[e.ke].mon[e.id].emitter = new events.EventEmitter().setMaxListeners(e.details.stream_mjpeg_clients);
s.log(e,{type:'FFMPEG Process Started',msg:{cmd:s.group[e.ke].mon[e.id].ffmpeg}});
s.tx({f:'monitor_starting',mode:x,mid:e.id,time:s.formattedTime()},'GRP_'+e.ke);
//start workers
if(e.type==='jpeg'){
if(!e.details.sfps||e.details.sfps===''){
var capture_fps=parseFloat(e.details.sfps);
if(isNaN(capture_fps)){capture_fps=1}
}
if(s.group[e.ke].mon[e.id].spawn){
s.group[e.ke].mon[e.id].spawn.stdin.on('error',function(err){
if(err&&e.details.loglevel!=='quiet'){
s.log(e,{type:'STDIN ERROR',msg:err});
}
})
}else{
if(x==='record'){
s.log(e,{type:lang.FFmpegCantStart,msg:lang.FFmpegCantStartText});
return
}
}
e.captureOne=function(f){
s.group[e.ke].mon[e.id].record.request=request({url:e.url,method:'GET',encoding: null,timeout:15000},function(err,data){
if(err){
return;
}
}).on('data',function(d){
if(!e.buffer0){
e.buffer0=[d]
}else{
e.buffer0.push(d);
}
if((d[d.length-2] === 0xFF && d[d.length-1] === 0xD9)){
e.buffer0=Buffer.concat(e.buffer0);
++e.frames;
if(s.group[e.ke].mon[e.id].spawn&&s.group[e.ke].mon[e.id].spawn.stdin){
s.group[e.ke].mon[e.id].spawn.stdin.write(e.buffer0);
}
if(s.group[e.ke].mon[e.id].started===1){
s.group[e.ke].mon[e.id].record.capturing=setTimeout(function(){
e.captureOne()
},1000/capture_fps);
}
e.buffer0=null;
}
if(!e.timeOut){
e.timeOut=setTimeout(function(){e.error_count=0;delete(e.timeOut);},3000);
}
}).on('error', function(err){
++e.error_count;
clearTimeout(e.timeOut);delete(e.timeOut);
if(e.details.loglevel!=='quiet'){
s.log(e,{type:lang['JPEG Error'],msg:{msg:lang.JPEGErrorText,info:err}});
switch(err.code){
case'ESOCKETTIMEDOUT':
case'ETIMEDOUT':
++s.group[e.ke].mon[e.id].error_socket_timeout_count
if(e.details.fatal_max!==0&&s.group[e.ke].mon[e.id].error_socket_timeout_count>e.details.fatal_max){
s.log(e,{type:lang['Fatal Maximum Reached'],msg:{code:'ESOCKETTIMEDOUT',msg:lang.FatalMaximumReachedText}});
s.camera('stop',e)
}else{
s.log(e,{type:lang['Restarting Process'],msg:{code:'ESOCKETTIMEDOUT',msg:lang.FatalMaximumReachedText}});
s.camera('restart',e)
}
return;
break;
}
}
if(e.details.fatal_max!==0&&e.error_count>e.details.fatal_max){
clearTimeout(s.group[e.ke].mon[e.id].record.capturing);
launchMonitorProcesses();
}
});
}
e.captureOne()
}
if(!s.group[e.ke]||!s.group[e.ke].mon[e.id]){s.init(0,e)}
s.group[e.ke].mon[e.id].spawn.on('error',function(er){
s.log(e,{type:'Spawn Error',msg:er});errorFatal('Spawn Error')
});
if(e.details.detector==='1'){
s.ocvTx({f:'init_monitor',id:e.id,ke:e.ke})
//frames from motion detect
if(e.details.detector_pam==='1'){
if(e.coProcessor === false){
s.createPamDiffEngine(e)
s.group[e.ke].mon[e.id].spawn.stdio[3].pipe(s.group[e.ke].mon[e.id].p2p).pipe(s.group[e.ke].mon[e.id].pamDiff)
if(e.details.detector_use_detect_object === '1'){
s.group[e.ke].mon[e.id].spawn.stdio[4].on('data',function(d){
s.group[e.ke].mon[e.id].lastJpegDetectorFrame = d
})
}
}
}else{
if(e.coProcessor === false){
s.group[e.ke].mon[e.id].spawn.stdio[3].on('data',function(d){
s.ocvTx({f:'frame',mon:s.group[e.ke].mon_conf[e.id].details,ke:e.ke,id:e.id,time:s.formattedTime(),frame:d});
})
}
}
}
//frames to stream
switch(e.details.stream_type){
case'mp4':
s.group[e.ke].mon[e.id].mp4frag['MAIN'] = new Mp4Frag();
s.group[e.ke].mon[e.id].mp4frag['MAIN'].on('error',function(error){
s.log(e,{type:lang['Mp4Frag'],msg:{error:error}})
})
s.group[e.ke].mon[e.id].spawn.stdio[1].pipe(s.group[e.ke].mon[e.id].mp4frag['MAIN'])
break;
case'flv':
e.frame_to_stream=function(d){
if(!s.group[e.ke].mon[e.id].firstStreamChunk['MAIN'])s.group[e.ke].mon[e.id].firstStreamChunk['MAIN'] = d;
e.frame_to_stream=function(d){
resetStreamCheck()
s.group[e.ke].mon[e.id].emitter.emit('data',d);
}
e.frame_to_stream(d)
}
break;
case'mjpeg':
e.frame_to_stream=function(d){
resetStreamCheck()
s.group[e.ke].mon[e.id].emitter.emit('data',d);
}
break;
// case'pam':
// s.group[e.ke].mon[e.id].p2pStream = new P2P();
// s.group[e.ke].mon[e.id].spawn.stdout.pipe(s.group[e.ke].mon[e.id].p2pStream)
// s.group[e.ke].mon[e.id].p2pStream.on('pam',function(d){
// resetStreamCheck()
// s.tx({f:'pam_frame',ke:e.ke,id:e.id,imageData:{
// data : d.pixels,
// height : d.height,
// width : d.width
// }},'MON_STREAM_'+e.id);
// })
// break;
case'b64':case undefined:case null:case'':
var buffer
e.frame_to_stream=function(d){
resetStreamCheck()
if(!buffer){
buffer=[d]
}else{
buffer.push(d);
}
if((d[d.length-2] === 0xFF && d[d.length-1] === 0xD9)){
s.group[e.ke].mon[e.id].emitter.emit('data',Buffer.concat(buffer));
buffer=null;
}
}
break;
}
if(e.frame_to_stream){
if(e.coProcessor === true && e.details.stream_type === ('b64'||'mjpeg')){
}else{
s.group[e.ke].mon[e.id].spawn.stdout.on('data',e.frame_to_stream)
}
}
if(e.details.stream_channels&&e.details.stream_channels!==''){
var createStreamEmitter = function(channel,number){
var pipeNumber = number+config.pipeAddition;
if(!s.group[e.ke].mon[e.id].emitterChannel[pipeNumber]){
s.group[e.ke].mon[e.id].emitterChannel[pipeNumber] = new events.EventEmitter().setMaxListeners(0);
}
var frame_to_stream
switch(channel.stream_type){
case'mp4':
s.group[e.ke].mon[e.id].mp4frag[pipeNumber] = new Mp4Frag();
s.group[e.ke].mon[e.id].spawn.stdio[pipeNumber].pipe(s.group[e.ke].mon[e.id].mp4frag[pipeNumber])
break;
case'mjpeg':
frame_to_stream=function(d){
s.group[e.ke].mon[e.id].emitterChannel[pipeNumber].emit('data',d);
}
break;
case'flv':
frame_to_stream=function(d){
if(!s.group[e.ke].mon[e.id].firstStreamChunk[pipeNumber])s.group[e.ke].mon[e.id].firstStreamChunk[pipeNumber] = d;
frame_to_stream=function(d){
s.group[e.ke].mon[e.id].emitterChannel[pipeNumber].emit('data',d);
}
frame_to_stream(d)
}
break;
case'h264':
frame_to_stream=function(d){
s.group[e.ke].mon[e.id].emitterChannel[pipeNumber].emit('data',d);
}
break;
}
if(frame_to_stream){
s.group[e.ke].mon[e.id].spawn.stdio[pipeNumber].on('data',frame_to_stream);
}
}
e.details.stream_channels.forEach(createStreamEmitter)
}
if(x==='record'||e.type==='mjpeg'||e.type==='h264'||e.type==='local'){
var checkLog = function(d,x){return d.indexOf(x)>-1;}
s.group[e.ke].mon[e.id].spawn.stderr.on('data',function(d){
d=d.toString();
switch(true){
case checkLog(d,'[hls @'):
case checkLog(d,'Past duration'):
case checkLog(d,'Last message repeated'):
case checkLog(d,'pkt->duration = 0'):
case checkLog(d,'Non-monotonous DTS'):
case checkLog(d,'NULL @'):
case checkLog(d,'RTP: missed'):
case checkLog(d,'deprecated pixel format used'):
return
break;
//mp4 output with webm encoder chosen
case checkLog(d,'Could not find tag for vp8'):
case checkLog(d,'Only VP8 or VP9 Video'):
case checkLog(d,'Could not write header'):
// switch(e.ext){
// case'mp4':
// e.details.vcodec='libx264'
// e.details.acodec='none'
// break;
// case'webm':
// e.details.vcodec='libvpx'
// e.details.acodec='none'
// break;
// }
// if(e.details.stream_type==='hls'){
// e.details.stream_vcodec='libx264'
// e.details.stream_acodec='no'
// }
// s.camera('restart',e)
return s.log(e,{type:lang['Incorrect Settings Chosen'],msg:{msg:d}})
break;
// case checkLog(d,'av_interleaved_write_frame'):
case checkLog(d,'Connection refused'):
case checkLog(d,'Connection timed out'):
//restart
setTimeout(function(){
s.log(e,{type:lang['Connection timed out'],msg:lang['Retrying...']});
errorFatal('Connection timed out');
},1000)
break;
// case checkLog(d,'No such file or directory'):
// case checkLog(d,'Unable to open RTSP for listening'):
// case checkLog(d,'timed out'):
// case checkLog(d,'Invalid data found when processing input'):
// case checkLog(d,'reset by peer'):
// if(e.frames===0&&x==='record'){s.video('delete',e)};
// setTimeout(function(){
// if(!s.group[e.ke].mon[e.id].spawn){launchMonitorProcesses()}
// },2000)
// break;
case checkLog(d,'Immediate exit requested'):
case checkLog(d,'mjpeg_decode_dc'):
case checkLog(d,'bad vlc'):
case checkLog(d,'error dc'):
launchMonitorProcesses()
break;
case /T[0-9][0-9]-[0-9][0-9]-[0-9][0-9]./.test(d):
var filename = d.split('.')[0]+'.'+e.ext
s.video('insertCompleted',e,{
file : filename
})
s.log(e,{type:lang['Video Finished'],msg:{filename:d}})
if(
e.details.detector==='1'&&
s.group[e.ke].mon[e.id].started===1&&
e.details&&
e.details.detector_record_method==='del'&&
e.details.detector_delete_motionless_videos==='1'&&
s.group[e.ke].mon[e.id].detector_motion_count===0
){
if(e.details.loglevel!=='quiet'){
s.log(e,{type:lang['Delete Motionless Video'],msg:filename});
}
s.video('delete',{
filename : filename,
ke : e.ke,
id : e.id
})
}
s.group[e.ke].mon[e.id].detector_motion_count = 0
resetRecordingCheck()
return;
break;
}
s.log(e,{type:"FFMPEG STDERR",msg:d})
});
}
if(e.coProcessor === true){
setTimeout(function(){
s.coSpawnLauncher(e)
},6000)
}
}else{
s.log(e,{type:lang["Ping Failed"],msg:lang.skipPingText1});
errorFatal("Ping Failed");return;
}
}
if(e.type!=='socket'&&e.type!=='dashcam'&&e.protocol!=='udp'&&e.type!=='local'||e.details.skip_ping === '1'){
connectionTester.test(e.hosty,e.port,2000,startVideoProcessor);
}else{
startVideoProcessor(null,{success:true})
}
}else{
s.kill(s.group[e.ke].mon[e.id].spawn,e);
}
}
//start drawing files
delete(s.group[e.ke].mon[e.id].childNode)
if(config.childNodes.enabled === true && config.childNodes.mode === 'master'){
var childNodeList = Object.keys(s.childNodes)
if(childNodeList.length>0){
e.ch_stop = 0;
launchMonitorProcesses = function(){
startVideoProcessor = function(){
s.cx({
//function
f : 'cameraStart',
//mode
mode : x,
//data, options
d : s.group[e.ke].mon_conf[e.id]
},s.group[e.ke].mon[e.id].childNodeId)
}
if(e.type!=='socket'&&e.type!=='dashcam'&&e.protocol!=='udp'&&e.type!=='local' && e.details.skip_ping !== '1'){
console.log(e.hosty,e.port)
connectionTester.test(e.hosty,e.port,2000,function(err,o){
if(o.success===true){
startVideoProcessor()
}else{
s.log(e,{type:lang["Ping Failed"],msg:lang.skipPingText1});
errorFatal("Ping Failed");return;
}
})
}else{
startVideoProcessor()
}
}
childNodeList.forEach(function(ip){
if(e.ch_stop===0&&s.childNodes[ip].cpu<80){
e.ch_stop=1;
s.childNodes[ip].activeCameras[e.ke+e.id] = s.init('noReference',s.group[e.ke].mon_conf[e.id]);
s.group[e.ke].mon[e.id].childNode = ip;
s.group[e.ke].mon[e.id].childNodeId = s.childNodes[ip].cnid;
s.cx({f:'sync',sync:s.group[e.ke].mon_conf[e.id],ke:e.ke,mid:e.id},s.group[e.ke].mon[e.id].childNodeId);
launchMonitorProcesses();
}
})
}else{
launchMonitorProcesses();
}
}else{
launchMonitorProcesses();
}
break;
case'motion':
var d=e;
if(s.group[d.ke].mon[d.id].open){
d.details.videoTime = s.group[d.ke].mon[d.id].open;
}
var detailString = JSON.stringify(d.details);
if(!s.group[d.ke]||!s.group[d.ke].mon[d.id]){
return s.systemLog(lang['No Monitor Found, Ignoring Request'])
}
d.mon=s.group[d.ke].mon_conf[d.id];
if(!s.group[d.ke].mon[d.id].detector_motion_count){
s.group[d.ke].mon[d.id].detector_motion_count=0
}
s.group[d.ke].mon[d.id].detector_motion_count+=1
if(s.group[d.ke].mon[d.id].motion_lock){
return
}
var detector_lock_timeout
if(!d.mon.details.detector_lock_timeout||d.mon.details.detector_lock_timeout===''){
detector_lock_timeout = 2000
}
detector_lock_timeout = parseFloat(d.mon.details.detector_lock_timeout);
if(!s.group[d.ke].mon[d.id].detector_lock_timeout){
s.group[d.ke].mon[d.id].detector_lock_timeout=setTimeout(function(){
clearTimeout(s.group[d.ke].mon[d.id].detector_lock_timeout)
delete(s.group[d.ke].mon[d.id].detector_lock_timeout)
},detector_lock_timeout)
}else{
return
}
if(d.doObjectDetection !== true){
//save this detection result in SQL, only coords. not image.
if(d.mon.details.detector_save==='1'){
s.sqlQuery('INSERT INTO Events (ke,mid,details) VALUES (?,?,?)',[d.ke,d.id,detailString])
}
if(d.mon.details.detector_notrigger=='1'){
var detector_notrigger_timeout
if(!d.mon.details.detector_notrigger_timeout||d.mon.details.detector_notrigger_timeout===''){
detector_notrigger_timeout = 10
}
detector_notrigger_timeout = parseFloat(d.mon.details.detector_notrigger_timeout)*1000*60;
s.group[e.ke].mon[e.id].detector_notrigger_timeout = detector_notrigger_timeout;
clearInterval(s.group[d.ke].mon[d.id].detector_notrigger_timeout)
s.group[d.ke].mon[d.id].detector_notrigger_timeout = setInterval(s.group[d.ke].mon[d.id].detector_notrigger_timeout_function,detector_notrigger_timeout)
}
if(d.mon.details.detector_webhook=='1'){
var detector_webhook_url = d.mon.details.detector_webhook_url
.replace(/{{TIME}}/g,s.timeObject(new Date).format())
.replace(/{{REGION_NAME}}/g,d.details.name)
.replace(/{{SNAP_PATH}}/g,s.dir.streams+'/'+d.ke+'/'+d.id+'/s.jpg')
.replace(/{{MONITOR_ID}}/g,d.id)
.replace(/{{GROUP_KEY}}/g,d.ke)
.replace(/{{DETAILS}}/g,detailString)
http.get(detector_webhook_url, function(data) {
data.setEncoding('utf8');
var chunks='';
data.on('data', (chunk) => {
chunks+=chunk;
});
data.on('end', () => {
});
}).on('error', function(e) {
}).end();
}
var detector_timeout
if(!d.mon.details.detector_timeout||d.mon.details.detector_timeout===''){
detector_timeout = 10
}else{
detector_timeout = parseFloat(d.mon.details.detector_timeout)
}
if(d.mon.mode=='start'&&d.mon.details.detector_trigger==='1'&&d.mon.details.detector_record_method==='sip'){
//s.group[d.ke].mon[d.id].eventBasedRecording.timeout
// clearTimeout(s.group[d.ke].mon[d.id].eventBasedRecording.timeout)
s.group[d.ke].mon[d.id].eventBasedRecording.timeout = setTimeout(function(){
s.group[d.ke].mon[d.id].eventBasedRecording.allowEnd=true;
},detector_timeout * 950 * 60)
if(!s.group[d.ke].mon[d.id].eventBasedRecording.process){
if(!d.auth){
d.auth=s.gid();
}
if(!s.group[d.ke].users[d.auth]){
s.group[d.ke].users[d.auth]={system:1,details:{},lang:lang}
}
s.group[d.ke].mon[d.id].eventBasedRecording.allowEnd = false;
var runRecord = function(){
var filename = s.formattedTime()+'.mp4'
s.log(d,{type:"Traditional Recording",msg:"Started"})
//-t 00:'+s.timeObject(new Date(detector_timeout * 1000 * 60)).format('mm:ss')+'
s.group[d.ke].mon[d.id].eventBasedRecording.process = spawn(config.ffmpegDir,s.splitForFFPMEG(('-loglevel warning -analyzeduration 1000000 -probesize 1000000 -re -i http://'+config.ip+':'+config.port+'/'+d.auth+'/hls/'+d.ke+'/'+d.id+'/detectorStream.m3u8 -t 00:'+s.timeObject(new Date(detector_timeout * 1000 * 60)).format('mm:ss')+' -c:v copy -strftime 1 "'+s.video('getDir',d.mon) + filename + '"').replace(/\s+/g,' ').trim()))
var ffmpegError='';
var error
s.group[d.ke].mon[d.id].eventBasedRecording.process.stderr.on('data',function(data){
s.log(d,{type:"Traditional Recording",msg:data.toString()})
})
s.group[d.ke].mon[d.id].eventBasedRecording.process.on('close',function(){
if(!s.group[d.ke].mon[d.id].eventBasedRecording.allowEnd){
s.log(d,{type:"Traditional Recording",msg:"Detector Recording Process Exited Prematurely. Restarting."})
runRecord()
return
}
s.video('insertCompleted',d.mon,{
file : filename
})
s.log(d,{type:"Traditional Recording",msg:"Detector Recording Complete"})
delete(s.group[d.ke].users[d.auth])
s.log(d,{type:"Traditional Recording",msg:'Clear Recorder Process'})
delete(s.group[d.ke].mon[d.id].eventBasedRecording.process)
delete(s.group[d.ke].mon[d.id].eventBasedRecording.timeout)
clearTimeout(s.group[d.ke].mon[d.id].checker)
})
}
runRecord()
}
}else if(d.mon.mode!=='stop'&&d.mon.details.detector_trigger=='1'&&d.mon.details.detector_record_method==='hot'){
if(!d.auth){
d.auth=s.gid();
}
if(!s.group[d.ke].users[d.auth]){
s.group[d.ke].users[d.auth]={system:1,details:{},lang:lang}
}
d.urlQuery=[]
d.url='http://'+config.ip+':'+config.port+'/'+d.auth+'/monitor/'+d.ke+'/'+d.id+'/record/'+detector_timeout+'/min';
if(d.mon.details.watchdog_reset!=='0'){
d.urlQuery.push('reset=1')
}
if(d.mon.details.detector_trigger_record_fps&&d.mon.details.detector_trigger_record_fps!==''&&d.mon.details.detector_trigger_record_fps!=='0'){
d.urlQuery.push('fps='+d.mon.details.detector_trigger_record_fps)
}
if(d.urlQuery.length>0){
d.url+='?'+d.urlQuery.join('&')
}
http.get(d.url, function(data) {
data.setEncoding('utf8');
var chunks='';
data.on('data', (chunk) => {
chunks+=chunk;
});
data.on('end', () => {
delete(s.group[d.ke].users[d.auth])
d.cx.f='detector_record_engaged';
d.cx.msg=JSON.parse(chunks);
s.tx(d.cx,'GRP_'+d.ke);
});
}).on('error', function(e) {
}).end();
}
var screenshotName = 'Motion_'+(d.mon.name.replace(/[^\w\s]/gi,''))+'_'+d.id+'_'+d.ke+'_'+s.formattedTime()
var screenshotBuffer = null
var detectorStreamBuffers = null
//discord bot
if(d.mon.details.detector_discordbot === '1' && !s.group[d.ke].mon[d.id].detector_discordbot){
var detector_discordbot_timeout
if(!d.mon.details.detector_discordbot_timeout||d.mon.details.detector_discordbot_timeout===''){
detector_discordbot_timeout = 1000*60*10;
}else{
detector_discordbot_timeout = parseFloat(d.mon.details.detector_discordbot_timeout)*1000*60;
}
//lock mailer so you don't get emailed on EVERY trigger event.
s.group[d.ke].mon[d.id].detector_discordbot=setTimeout(function(){
//unlock so you can mail again.
clearTimeout(s.group[d.ke].mon[d.id].detector_discordbot);
delete(s.group[d.ke].mon[d.id].detector_discordbot);
},detector_discordbot_timeout);
var files = []
var sendAlert = function(){
s.discordMsg({
author: {
name: s.group[d.ke].mon_conf[d.id].name,
icon_url: "https://shinobi.video/libs/assets/icon/apple-touch-icon-152x152.png"
},
title: lang.Event+' - '+screenshotName,
description: lang.EventText1+' '+s.timeObject(new Date).format(),
fields: [],
timestamp: new Date(),
footer: {
icon_url: "https://shinobi.video/libs/assets/icon/apple-touch-icon-152x152.png",
text: "Shinobi Systems"
}
},files,d.ke)
}
if(d.mon.details.detector_discordbot_send_video === '1'){
if(!detectorStreamBuffers){
detectorStreamBuffers = s.getDetectorStreams(d)
}
detectorStreamBuffers.slice(detectorStreamBuffers.length - 2,detectorStreamBuffers.length).forEach(function(filepath,n){
files.push({
attachment: filepath,
name: 'Video Clip '+n+'.ts'
})
})
}
if(screenshotBuffer){
sendAlert()
}else if(d.mon.details.snap === '1'){
fs.readFile(s.dir.streams+'/'+d.ke+'/'+d.id+'/s.jpg',function(err, frame){
if(err){
s.systemLog(lang.EventText2+' '+d.ke+' '+d.id,err)
}else{
screenshotBuffer = frame
files.push({
attachment: screenshotBuffer,
name: screenshotName+'.jpg'
})
}
sendAlert()
})
}else{
sendAlert()
}
}
//mailer
if(config.mail && !s.group[d.ke].mon[d.id].detector_mail && d.mon.details.detector_mail === '1'){
s.sqlQuery('SELECT mail FROM Users WHERE ke=? AND details NOT LIKE ?',[d.ke,'%"sub"%'],function(err,r){
r=r[0];
var detector_mail_timeout
if(!d.mon.details.detector_mail_timeout||d.mon.details.detector_mail_timeout===''){
detector_mail_timeout = 1000*60*10;
}else{
detector_mail_timeout = parseFloat(d.mon.details.detector_mail_timeout)*1000*60;
}
//lock mailer so you don't get emailed on EVERY trigger event.
s.group[d.ke].mon[d.id].detector_mail=setTimeout(function(){
//unlock so you can mail again.
clearTimeout(s.group[d.ke].mon[d.id].detector_mail);
delete(s.group[d.ke].mon[d.id].detector_mail);
},detector_mail_timeout);
var files = []
var mailOptions = {
from: config.mail.from, // sender address
to: r.mail, // list of receivers
subject: lang.Event+' - '+screenshotName, // Subject line
html: '<i>'+lang.EventText1+' '+s.timeObject(new Date).format()+'.</i>',
attachments: files
}
var sendMail = function(){
Object.keys(d.details).forEach(function(v,n){
mailOptions.html+='<div><b>'+v+'</b> : '+d.details[v]+'</div>'
})
nodemailer.sendMail(mailOptions, (error, info) => {
if (error) {
s.systemLog(lang.MailError,error)
return false;
}
})
}
if(d.mon.details.detector_mail_send_video === '1'){
if(!detectorStreamBuffers){
detectorStreamBuffers = s.getDetectorStreams(d)
}
detectorStreamBuffers.slice(detectorStreamBuffers.length - 2,detectorStreamBuffers.length).forEach(function(filepath,n){
files.push({
filename: 'Video Clip '+n+'.ts',
content: fs.readFileSync(filepath)
})
})
}
if(screenshotBuffer){
sendMail()
}else if(d.mon.details.snap === '1'){
fs.readFile(s.dir.streams+'/'+d.ke+'/'+d.id+'/s.jpg',function(err, frame){
if(err){
s.systemLog(lang.EventText2+' '+d.ke+' '+d.id,err)
}else{
screenshotBuffer = frame
files.push({
filename: screenshotName+'.jpg',
content: frame
})
}
sendMail()
})
}else{
sendMail()
}
});
}
if(d.mon.details.detector_command_enable==='1'&&!s.group[d.ke].mon[d.id].detector_command){
var detector_command_timeout
if(!d.mon.details.detector_command_timeout||d.mon.details.detector_command_timeout===''){
detector_command_timeout = 1000*60*10;
}else{
detector_command_timeout = parseFloat(d.mon.details.detector_command_timeout)*1000*60;
}
s.group[d.ke].mon[d.id].detector_command=setTimeout(function(){
clearTimeout(s.group[d.ke].mon[d.id].detector_command);
delete(s.group[d.ke].mon[d.id].detector_command);
},detector_command_timeout);
var detector_command = d.mon.details.detector_command
.replace(/{{TIME}}/g,s.timeObject(new Date).format())
.replace(/{{REGION_NAME}}/g,d.details.name)
.replace(/{{SNAP_PATH}}/g,s.dir.streams+'/'+d.ke+'/'+d.id+'/s.jpg')
.replace(/{{MONITOR_ID}}/g,d.id)
.replace(/{{GROUP_KEY}}/g,d.ke)
.replace(/{{DETAILS}}/g,detailString)
if(d.details.confidence){
detector_command = detector_command
.replace(/{{CONFIDENCE}}/g,d.details.confidence)
}
exec(detector_command,{detached: true})
}
}
//show client machines the event
d.cx={f:'detector_trigger',id:d.id,ke:d.ke,details:d.details,doObjectDetection:d.doObjectDetection};
s.tx(d.cx,'DETECTOR_'+d.ke+d.id);
break;
}
if(typeof cn==='function'){setTimeout(function(){cn()},1000);}
}
//function for receiving detector data
s.pluginEventController=function(d){
switch(d.f){
case'trigger':
s.camera('motion',d)
break;
case's.tx':
s.tx(d.data,d.to)
break;
case'sql':
sql.query(d.query,d.values);
break;
case'log':
s.systemLog('PLUGIN : '+d.plug+' : ',d)
break;
}
}
//multi plugin connections
s.connectedPlugins={}
s.pluginInitiatorSuccess=function(mode,d,cn){
s.systemLog('pluginInitiatorSuccess',d)
if(mode==='client'){
//is in client mode (camera.js is client)
cn.pluginEngine=d.plug
if(!s.connectedPlugins[d.plug]){
s.connectedPlugins[d.plug]={plug:d.plug}
}
s.systemLog('Connected to plugin : Detector - '+d.plug+' - '+d.type)
switch(d.type){
default:case'detector':
s.ocv={started:s.timeObject(),id:cn.id,plug:d.plug,notice:d.notice,isClientPlugin:true};
cn.ocv=1;
s.tx({f:'detector_plugged',plug:d.plug,notice:d.notice},'CPU')
break;
}
}else{
//is in host mode (camera.js is client)
switch(d.type){
default:case'detector':
s.ocv={started:s.timeObject(),id:"host",plug:d.plug,notice:d.notice,isHostPlugin:true};
break;
}
}
s.connectedPlugins[d.plug].plugged=true
s.tx({f:'readPlugins',ke:d.ke},'CPU')
s.ocvTx({f:'api_key',key:d.plug})
s.api[d.plug]={pluginEngine:d.plug,permissions:{},details:{},ip:'0.0.0.0'};
}
s.pluginInitiatorFail=function(mode,d,cn){
s.connectedPlugins[d.plug].plugged=false
if(mode==='client'){
//is in client mode (camera.js is client)
cn.disconnect()
}else{
//is in host mode (camera.js is client)
}
}
if(config.plugins&&config.plugins.length>0){
config.plugins.forEach(function(v){
s.connectedPlugins[v.id]={plug:v.id}
if(v.enabled===false){return}
if(v.mode==='host'){
//is in host mode (camera.js is client)
if(v.https===true){
v.https='https://'
}else{
v.https='http://'
}
if(!v.port){
v.port=80
}
var socket = socketIOclient(v.https+v.host+':'+v.port)
s.connectedPlugins[v.id].tx = function(x){return socket.emit('f',x)}
socket.on('connect', function(cn){
s.systemLog('Connected to plugin (host) : '+v.id)
s.connectedPlugins[v.id].tx({f:'init_plugin_as_host',key:v.key})
});
socket.on('init',function(d){
s.systemLog('Initialize Plugin : Host',d)
if(d.ok===true){
s.pluginInitiatorSuccess("host",d)
}else{
s.pluginInitiatorFail("host",d)
}
});
socket.on('ocv',s.pluginEventController);
socket.on('disconnect', function(){
s.connectedPlugins[v.id].plugged=false
delete(s.api[v.id])
s.systemLog('Plugin Disconnected : '+v.id)
s.connectedPlugins[v.id].reconnector = setInterval(function(){
if(socket.connected===true){
clearInterval(s.connectedPlugins[v.id].reconnector)
}else{
socket.connect()
}
},1000*2)
});
s.connectedPlugins[v.id].ws = socket;
}
})
}
////socket controller
s.cn=function(cn){return{id:cn.id,ke:cn.ke,uid:cn.uid}}
io.on('connection', function (cn) {
var tx;
//set "client" detector plugin event function
cn.on('ocv',function(d){
if(!cn.pluginEngine&&d.f==='init'){
if(config.pluginKeys[d.plug]===d.pluginKey){
s.pluginInitiatorSuccess("client",d,cn)
}else{
s.pluginInitiatorFail("client",d,cn)
}
}else{
if(config.pluginKeys[d.plug]===d.pluginKey){
s.pluginEventController(d)
}else{
cn.disconnect()
}
}
})
//unique Base64 socket stream
cn.on('Base64',function(d){
if(!s.group[d.ke]||!s.group[d.ke].mon||!s.group[d.ke].mon[d.id]){
cn.disconnect();return;
}
cn.ip=cn.request.connection.remoteAddress;
var toUTC = function(){
return new Date().toISOString();
}
var tx=function(z){cn.emit('data',z);}
d.failed=function(msg){
tx({f:'stop_reconnect',msg:msg,token_used:d.auth,ke:d.ke});
cn.disconnect();
}
d.success=function(r){
r=r[0];
var Emitter,chunkChannel
if(!d.channel){
Emitter = s.group[d.ke].mon[d.id].emitter
chunkChannel = 'MAIN'
}else{
Emitter = s.group[d.ke].mon[d.id].emitterChannel[parseInt(d.channel)+config.pipeAddition]
chunkChannel = parseInt(d.channel)+config.pipeAddition
}
if(!Emitter){
cn.disconnect();return;
}
if(!d.channel)d.channel = 'MAIN';
cn.ke=d.ke,
cn.uid=d.uid,
cn.auth=d.auth;
cn.channel=d.channel;
cn.removeListenerOnDisconnect=true;
cn.socketVideoStream=d.id;
var contentWriter
cn.closeSocketVideoStream = function(){
Emitter.removeListener('data', contentWriter);
}
Emitter.on('data',contentWriter = function(base64){
tx(base64)
})
}
//check if auth key is user's temporary session key
if(s.group[d.ke]&&s.group[d.ke].users&&s.group[d.ke].users[d.auth]){
d.success(s.group[d.ke].users[d.auth]);
}else{
s.sqlQuery('SELECT ke,uid,auth,mail,details FROM Users WHERE ke=? AND auth=? AND uid=?',[d.ke,d.auth,d.uid],function(err,r) {
if(r&&r[0]){
d.success(r)
}else{
s.sqlQuery('SELECT * FROM API WHERE ke=? AND code=? AND uid=?',[d.ke,d.auth,d.uid],function(err,r) {
if(r&&r[0]){
r=r[0]
r.details=JSON.parse(r.details)
if(r.details.auth_socket==='1'){
s.sqlQuery('SELECT ke,uid,auth,mail,details FROM Users WHERE ke=? AND uid=?',[r.ke,r.uid],function(err,r) {
if(r&&r[0]){
d.success(r)
}else{
d.failed('User not found')
}
})
}else{
d.failed('Permissions for this key do not allow authentication with Websocket')
}
}else{
d.failed('Not an API key')
}
})
}
})
}
})
//unique FLV socket stream
cn.on('FLV',function(d){
if(!s.group[d.ke]||!s.group[d.ke].mon||!s.group[d.ke].mon[d.id]){
cn.disconnect();return;
}
cn.ip=cn.request.connection.remoteAddress;
var toUTC = function(){
return new Date().toISOString();
}
var tx=function(z){cn.emit('data',z);}
d.failed=function(msg){
tx({f:'stop_reconnect',msg:msg,token_used:d.auth,ke:d.ke});
cn.disconnect();
}
d.success=function(r){
r=r[0];
var Emitter,chunkChannel
if(!d.channel){
Emitter = s.group[d.ke].mon[d.id].emitter
chunkChannel = 'MAIN'
}else{
Emitter = s.group[d.ke].mon[d.id].emitterChannel[parseInt(d.channel)+config.pipeAddition]
chunkChannel = parseInt(d.channel)+config.pipeAddition
}
if(!Emitter){
cn.disconnect();return;
}
if(!d.channel)d.channel = 'MAIN';
cn.ke=d.ke,
cn.uid=d.uid,
cn.auth=d.auth;
cn.channel=d.channel;
cn.removeListenerOnDisconnect=true;
cn.socketVideoStream=d.id;
var contentWriter
cn.closeSocketVideoStream = function(){
Emitter.removeListener('data', contentWriter);
}
tx({time:toUTC(),buffer:s.group[d.ke].mon[d.id].firstStreamChunk[chunkChannel]})
Emitter.on('data',contentWriter = function(buffer){
tx({time:toUTC(),buffer:buffer})
})
}
if(s.group[d.ke]&&s.group[d.ke].users&&s.group[d.ke].users[d.auth]){
d.success(s.group[d.ke].users[d.auth]);
}else{
s.sqlQuery('SELECT ke,uid,auth,mail,details FROM Users WHERE ke=? AND auth=? AND uid=?',[d.ke,d.auth,d.uid],function(err,r) {
if(r&&r[0]){
d.success(r)
}else{
s.sqlQuery('SELECT * FROM API WHERE ke=? AND code=? AND uid=?',[d.ke,d.auth,d.uid],function(err,r) {
if(r&&r[0]){
r=r[0]
r.details=JSON.parse(r.details)
if(r.details.auth_socket==='1'){
s.sqlQuery('SELECT ke,uid,auth,mail,details FROM Users WHERE ke=? AND uid=?',[r.ke,r.uid],function(err,r) {
if(r&&r[0]){
d.success(r)
}else{
d.failed('User not found')
}
})
}else{
d.failed('Permissions for this key do not allow authentication with Websocket')
}
}else{
d.failed('Not an API key')
}
})
}
})
}
})
//unique MP4 socket stream
cn.on('MP4',function(d){
if(!s.group[d.ke]||!s.group[d.ke].mon||!s.group[d.ke].mon[d.id]){
cn.disconnect();return;
}
cn.ip=cn.request.connection.remoteAddress;
var toUTC = function(){
return new Date().toISOString();
}
var tx=function(z){cn.emit('data',z);}
d.failed=function(msg){
tx({f:'stop_reconnect',msg:msg,token_used:d.auth,ke:d.ke});
cn.disconnect();
}
d.success=function(r){
r=r[0];
var Emitter,chunkChannel
if(!d.channel){
Emitter = s.group[d.ke].mon[d.id].emitter
chunkChannel = 'MAIN'
}else{
Emitter = s.group[d.ke].mon[d.id].emitterChannel[parseInt(d.channel)+config.pipeAddition]
chunkChannel = parseInt(d.channel)+config.pipeAddition
}
if(!Emitter){
cn.disconnect();return;
}
if(!d.channel)d.channel = 'MAIN';
cn.ke=d.ke,
cn.uid=d.uid,
cn.auth=d.auth;
cn.channel=d.channel;
cn.socketVideoStream=d.id;
var mp4frag = s.group[d.ke].mon[d.id].mp4frag[d.channel];
var onInitialized = () => {
cn.emit('mime', mp4frag.mime);
mp4frag.removeListener('initialized', onInitialized);
};
//event listener
var onSegment = function(data){
cn.emit('segment', data);
};
cn.closeSocketVideoStream = function(){
mp4frag.removeListener('segment', onSegment)
mp4frag.removeListener('initialized', onInitialized)
}
cn.on('MP4Command',function(msg){
switch (msg) {
case 'mime' ://client is requesting mime
var mime = mp4frag.mime;
if (mime) {
cn.emit('mime', mime);
} else {
mp4frag.on('initialized', onInitialized);
}
break;
case 'initialization' ://client is requesting initialization segment
cn.emit('initialization', mp4frag.initialization);
break;
case 'segment' ://client is requesting a SINGLE segment
var segment = mp4frag.segment;
if (segment) {
cn.emit('segment', segment);
} else {
mp4frag.once('segment', onSegment);
}
break;
case 'segments' ://client is requesting ALL segments
//send current segment first to start video asap
var segment = mp4frag.segment;
if (segment) {
cn.emit('segment', segment);
}
//add listener for segments being dispatched by mp4frag
mp4frag.on('segment', onSegment);
break;
case 'pause' :
mp4frag.removeListener('segment', onSegment);
break;
case 'resume' :
mp4frag.on('segment', onSegment);
break;
case 'stop' ://client requesting to stop receiving segments
cn.closeSocketVideoStream()
break;
}
})
}
if(s.group[d.ke]&&s.group[d.ke].users&&s.group[d.ke].users[d.auth]){
d.success(s.group[d.ke].users[d.auth]);
}else{
s.sqlQuery('SELECT ke,uid,auth,mail,details FROM Users WHERE ke=? AND auth=? AND uid=?',[d.ke,d.auth,d.uid],function(err,r) {
if(r&&r[0]){
d.success(r)
}else{
s.sqlQuery('SELECT * FROM API WHERE ke=? AND code=? AND uid=?',[d.ke,d.auth,d.uid],function(err,r) {
if(r&&r[0]){
r=r[0]
r.details=JSON.parse(r.details)
if(r.details.auth_socket==='1'){
s.sqlQuery('SELECT ke,uid,auth,mail,details FROM Users WHERE ke=? AND uid=?',[r.ke,r.uid],function(err,r) {
if(r&&r[0]){
d.success(r)
}else{
d.failed('User not found')
}
})
}else{
d.failed('Permissions for this key do not allow authentication with Websocket')
}
}else{
d.failed('Not an API key')
}
})
}
})
}
})
//main socket control functions
cn.on('f',function(d){
if(!cn.ke&&d.f==='init'){//socket login
cn.ip=cn.request.connection.remoteAddress;
tx=function(z){if(!z.ke){z.ke=cn.ke;};cn.emit('f',z);}
d.failed=function(){tx({ok:false,msg:'Not Authorized',token_used:d.auth,ke:d.ke});cn.disconnect();}
d.success=function(r){
r=r[0];cn.join('GRP_'+d.ke);cn.join('CPU');
cn.ke=d.ke,
cn.uid=d.uid,
cn.auth=d.auth;
if(!s.group[d.ke])s.group[d.ke]={};
// if(!s.group[d.ke].vid)s.group[d.ke].vid={};
if(!s.group[d.ke].users)s.group[d.ke].users={};
// s.group[d.ke].vid[cn.id]={uid:d.uid};
s.group[d.ke].users[d.auth]={cnid:cn.id,uid:r.uid,mail:r.mail,details:JSON.parse(r.details),logged_in_at:s.timeObject(new Date).format(),login_type:'Dashboard'}
try{s.group[d.ke].users[d.auth].details=JSON.parse(r.details)}catch(er){}
if(s.group[d.ke].users[d.auth].details.get_server_log!=='0'){
cn.join('GRPLOG_'+d.ke)
}
s.group[d.ke].users[d.auth].lang=s.getLanguageFile(s.group[d.ke].users[d.auth].details.lang)
s.log({ke:d.ke,mid:'$USER'},{type:s.group[d.ke].users[d.auth].lang['Websocket Connected'],msg:{mail:r.mail,id:d.uid,ip:cn.ip}})
if(!s.group[d.ke].mon){
s.group[d.ke].mon={}
if(!s.group[d.ke].mon){s.group[d.ke].mon={}}
}
if(s.ocv){
tx({f:'detector_plugged',plug:s.ocv.plug,notice:s.ocv.notice})
s.ocvTx({f:'readPlugins',ke:d.ke})
}
tx({f:'users_online',users:s.group[d.ke].users})
s.tx({f:'user_status_change',ke:d.ke,uid:cn.uid,status:1,user:s.group[d.ke].users[d.auth]},'GRP_'+d.ke)
s.init('diskUsedEmit',d)
s.init('apps',d)
s.sqlQuery('SELECT * FROM API WHERE ke=? AND uid=?',[d.ke,d.uid],function(err,rrr) {
tx({
f:'init_success',
users:s.group[d.ke].vid,
apis:rrr,
os:{
platform:s.platform,
cpuCount:os.cpus().length,
totalmem:s.totalmem
}
})
try{
s.sqlQuery('SELECT * FROM Monitors WHERE ke=?', [d.ke], function(err,r) {
if(r && r[0]){
r.forEach(function(monitor){
s.camera('snapshot',{mid:monitor.mid,ke:monitor.ke,mon:monitor})
})
}
})
}catch(err){
console.log(err)
}
})
}
s.sqlQuery('SELECT ke,uid,auth,mail,details FROM Users WHERE ke=? AND auth=? AND uid=?',[d.ke,d.auth,d.uid],function(err,r) {
if(r&&r[0]){
d.success(r)
}else{
s.sqlQuery('SELECT * FROM API WHERE ke=? AND code=? AND uid=?',[d.ke,d.auth,d.uid],function(err,r) {
if(r&&r[0]){
r=r[0]
r.details=JSON.parse(r.details)
if(r.details.auth_socket==='1'){
s.sqlQuery('SELECT ke,uid,auth,mail,details FROM Users WHERE ke=? AND uid=?',[r.ke,r.uid],function(err,r) {
if(r&&r[0]){
d.success(r)
}else{
d.failed()
}
})
}else{
d.failed()
}
}else{
d.failed()
}
})
}
})
return;
}
if((d.id||d.uid||d.mid)&&cn.ke){
try{
switch(d.f){
case'ocv_in':
s.ocvTx(d.data)
break;
case'monitorOrder':
if(d.monitorOrder&&d.monitorOrder instanceof Object){
s.sqlQuery('SELECT details FROM Users WHERE uid=? AND ke=?',[cn.uid,cn.ke],function(err,r){
if(r&&r[0]){
r=JSON.parse(r[0].details);
r.monitorOrder=d.monitorOrder;
s.sqlQuery('UPDATE Users SET details=? WHERE uid=? AND ke=?',[JSON.stringify(r),cn.uid,cn.ke])
}
})
}
break;
case'update':
if(!config.updateKey){
tx({error:lang.updateKeyText1});
return;
}
if(d.key===config.updateKey){
exec('chmod +x '+__dirname+'/UPDATE.sh&&'+__dirname+'/UPDATE.sh',{detached: true})
}else{
tx({error:lang.updateKeyText2});
}
break;
case'cron':
if(s.group[cn.ke]&&s.group[cn.ke].users[cn.auth].details&&!s.group[cn.ke].users[cn.auth].details.sub){
s.tx({f:d.ff},s.cron.id)
}
break;
case'api':
switch(d.ff){
case'delete':
d.set=[],d.ar=[];
d.form.ke=cn.ke;d.form.uid=cn.uid;delete(d.form.ip);
if(!d.form.code){tx({f:'form_incomplete',form:'APIs'});return}
d.for=Object.keys(d.form);
d.for.forEach(function(v){
d.set.push(v+'=?'),d.ar.push(d.form[v]);
});
s.sqlQuery('DELETE FROM API WHERE '+d.set.join(' AND '),d.ar,function(err,r){
if(!err){
tx({f:'api_key_deleted',form:d.form});
delete(s.api[d.form.code]);
}else{
s.systemLog('API Delete Error : '+e.ke+' : '+' : '+e.mid,err)
}
})
break;
case'add':
d.set=[],d.qu=[],d.ar=[];
d.form.ke=cn.ke,d.form.uid=cn.uid,d.form.code=s.gid(30);
d.for=Object.keys(d.form);
d.for.forEach(function(v){
d.set.push(v),d.qu.push('?'),d.ar.push(d.form[v]);
});
s.sqlQuery('INSERT INTO API ('+d.set.join(',')+') VALUES ('+d.qu.join(',')+')',d.ar,function(err,r){
d.form.time=s.formattedTime(new Date,'YYYY-DD-MM HH:mm:ss');
if(!err){tx({f:'api_key_added',form:d.form});}else{s.systemLog(err)}
});
break;
}
break;
case'settings':
switch(d.ff){
case'filters':
switch(d.fff){
case'save':case'delete':
s.sqlQuery('SELECT details FROM Users WHERE ke=? AND uid=?',[d.ke,d.uid],function(err,r){
if(r&&r[0]){
r=r[0];
d.d=JSON.parse(r.details);
if(d.form.id===''){d.form.id=s.gid(5)}
if(!d.d.filters)d.d.filters={};
//save/modify or delete
if(d.fff==='save'){
d.d.filters[d.form.id]=d.form;
}else{
delete(d.d.filters[d.form.id]);
}
s.sqlQuery('UPDATE Users SET details=? WHERE ke=? AND uid=?',[JSON.stringify(d.d),d.ke,d.uid],function(err,r){
tx({f:'filters_change',uid:d.uid,ke:d.ke,filters:d.d.filters});
});
}
})
break;
}
break;
case'edit':
s.sqlQuery('SELECT details FROM Users WHERE ke=? AND uid=?',[d.ke,d.uid],function(err,r){
if(r&&r[0]){
r=r[0];
d.d=JSON.parse(r.details);
if(!d.d.sub || d.d.user_change === "1"){
if(d.d.get_server_log==='1'){
cn.join('GRPLOG_'+d.ke)
}else{
cn.leave('GRPLOG_'+d.ke)
}
///unchangeable from client side, so reset them in case they did.
d.form.details=JSON.parse(d.form.details)
//admin permissions
d.form.details.permissions=d.d.permissions
d.form.details.edit_size=d.d.edit_size
d.form.details.edit_days=d.d.edit_days
d.form.details.use_admin=d.d.use_admin
d.form.details.use_webdav=d.d.use_webdav
d.form.details.use_ldap=d.d.use_ldap
//check
if(d.d.edit_days=="0"){
d.form.details.days=d.d.days;
}
if(d.d.edit_size=="0"){
d.form.details.size=d.d.size;
}
if(d.d.sub){
d.form.details.sub=d.d.sub;
if(d.d.monitors){d.form.details.monitors=d.d.monitors;}
if(d.d.allmonitors){d.form.details.allmonitors=d.d.allmonitors;}
if(d.d.monitor_create){d.form.details.monitor_create=d.d.monitor_create;}
if(d.d.video_delete){d.form.details.video_delete=d.d.video_delete;}
if(d.d.video_view){d.form.details.video_view=d.d.video_view;}
if(d.d.monitor_edit){d.form.details.monitor_edit=d.d.monitor_edit;}
if(d.d.size){d.form.details.size=d.d.size;}
if(d.d.days){d.form.details.days=d.d.days;}
delete(d.form.details.mon_groups)
}
var newSize = d.form.details.size
d.form.details=JSON.stringify(d.form.details)
///
d.set=[],d.ar=[];
if(d.form.pass&&d.form.pass!==''){d.form.pass=s.md5(d.form.pass);}else{delete(d.form.pass)};
delete(d.form.password_again);
d.for=Object.keys(d.form);
d.for.forEach(function(v){
d.set.push(v+'=?'),d.ar.push(d.form[v]);
});
d.ar.push(d.ke),d.ar.push(d.uid);
s.sqlQuery('UPDATE Users SET '+d.set.join(',')+' WHERE ke=? AND uid=?',d.ar,function(err,r){
if(!d.d.sub){
s.group[d.ke].sizeLimit = parseFloat(newSize)
delete(s.group[d.ke].webdav)
if(s.group[d.ke].discordBot && s.group[d.ke].discordBot.destroy){
s.group[d.ke].discordBot.destroy()
delete(s.group[d.ke].discordBot)
}
s.init('apps',d)
}
tx({f:'user_settings_change',uid:d.uid,ke:d.ke,form:d.form});
});
}
}
})
break;
}
break;
case'monitor':
switch(d.ff){
case'get':
switch(d.fff){
case'videos&events':
if(!d.eventLimit){
d.eventLimit = 500
}else{
d.eventLimit = parseInt(d.eventLimit);
}
if(!d.eventStartDate&&d.startDate){
d.eventStartDate = s.stringToSqlTime(d.startDate)
}
if(!d.eventEndDate&&d.endDate){
d.eventEndDate = s.stringToSqlTime(d.endDate)
}
var monitorQuery = ''
var monitorValues = []
var permissions = s.group[d.ke].users[cn.auth].details;
if(!d.mid){
if(permissions.sub&&permissions.monitors&&permissions.allmonitors!=='1'){
try{permissions.monitors=JSON.parse(permissions.monitors);}catch(er){}
var or = [];
permissions.monitors.forEach(function(v,n){
or.push('mid=?');
monitorValues.push(v)
})
monitorQuery += ' AND ('+or.join(' OR ')+')'
}
}else if(!permissions.sub||permissions.allmonitors!=='0'||permissions.monitors.indexOf(d.mid)>-1){
monitorQuery += ' and mid=?';
monitorValues.push(d.mid)
}
var getEvents = function(callback){
var eventQuery = 'SELECT * FROM Events WHERE ke=?';
var eventQueryValues = [cn.ke];
if(d.eventStartDate&&d.eventStartDate!==''){
if(d.eventEndDate&&d.eventEndDate!==''){
eventQuery+=' AND `time` >= ? AND `time` <= ?';
eventQueryValues.push(d.eventStartDate)
eventQueryValues.push(d.eventEndDate)
}else{
eventQuery+=' AND `time` >= ?';
eventQueryValues.push(d.eventStartDate)
}
}
if(monitorValues.length>0){
eventQuery += monitorQuery;
eventQueryValues = eventQueryValues.concat(monitorValues);
}
eventQuery+=' ORDER BY `time` DESC LIMIT '+d.eventLimit+'';
s.sqlQuery(eventQuery,eventQueryValues,function(err,r){
if(err){
console.log(eventQuery)
console.error('LINE 2428',err)
setTimeout(function(){
getEvents(callback)
},2000)
}else{
if(!r){r=[]}
r.forEach(function(v,n){
r[n].details=JSON.parse(v.details);
})
callback(r)
}
})
}
if(!d.videoLimit&&d.limit){
d.videoLimit=d.limit
eventQuery.push()
}
if(!d.videoStartDate&&d.startDate){
d.videoStartDate = s.stringToSqlTime(d.startDate)
}
if(!d.videoEndDate&&d.endDate){
d.videoEndDate = s.stringToSqlTime(d.endDate)
}
var getVideos = function(callback){
var videoQuery='SELECT * FROM Videos WHERE ke=?';
var videoQueryValues=[cn.ke];
if(d.videoStartDate||d.videoEndDate){
if(!d.videoStartDateOperator||d.videoStartDateOperator==''){
d.videoStartDateOperator='>='
}
if(!d.videoEndDateOperator||d.videoEndDateOperator==''){
d.videoEndDateOperator='<='
}
switch(true){
case(d.videoStartDate&&d.videoStartDate!==''&&d.videoEndDate&&d.videoEndDate!==''):
videoQuery+=' AND `time` '+d.videoStartDateOperator+' ? AND `end` '+d.videoEndDateOperator+' ?';
videoQueryValues.push(d.videoStartDate)
videoQueryValues.push(d.videoEndDate)
break;
case(d.videoStartDate&&d.videoStartDate!==''):
videoQuery+=' AND `time` '+d.videoStartDateOperator+' ?';
videoQueryValues.push(d.videoStartDate)
break;
case(d.videoEndDate&&d.videoEndDate!==''):
videoQuery+=' AND `end` '+d.videoEndDateOperator+' ?';
videoQueryValues.push(d.videoEndDate)
break;
}
}
if(monitorValues.length>0){
videoQuery += monitorQuery;
videoQueryValues = videoQueryValues.concat(monitorValues);
}
videoQuery+=' ORDER BY `time` DESC';
if(!d.videoLimit||d.videoLimit==''){
d.videoLimit='100'
}
if(d.videoLimit!=='0'){
videoQuery+=' LIMIT '+d.videoLimit
}
s.sqlQuery(videoQuery,videoQueryValues,function(err,r){
if(err){
console.log(videoQuery)
console.error('LINE 2416',err)
setTimeout(function(){
getVideos(callback)
},2000)
}else{
s.video('linkBuild',r,cn.auth)
callback({total:r.length,limit:d.videoLimit,videos:r})
}
})
}
getVideos(function(videos){
getEvents(function(events){
tx({
f:'drawPowerVideoMainTimeLine',
videos:videos,
events:events
})
})
})
break;
}
break;
case'control':
s.camera('control',d,function(resp){
tx({f:'control',response:resp})
})
break;
case'jpeg_off':
delete(cn.jpeg_on);
if(cn.monitor_watching){
Object.keys(cn.monitor_watching).forEach(function(n,v){
v=cn.monitor_watching[n];
cn.join('MON_STREAM_'+n);
});
}
tx({f:'mode_jpeg_off'})
break;
case'jpeg_on':
cn.jpeg_on=true;
if(cn.monitor_watching){
Object.keys(cn.monitor_watching).forEach(function(n,v){
v=cn.monitor_watching[n];
cn.leave('MON_STREAM_'+n);
});
}
tx({f:'mode_jpeg_on'})
break;
case'watch_on':
if(!d.ke){d.ke=cn.ke}
s.init(0,{mid:d.id,ke:d.ke});
if(!s.group[d.ke]||!s.group[d.ke].mon[d.id]||s.group[d.ke].mon[d.id].started===0){return false}
s.camera(d.ff,d,cn,tx)
cn.join('MON_'+d.id);
cn.join('DETECTOR_'+d.ke+d.id);
if(cn.jpeg_on!==true){
cn.join('MON_STREAM_'+d.id);
} if(s.group[d.ke]&&s.group[d.ke].mon&&s.group[d.ke].mon[d.id]&&s.group[d.ke].mon[d.id].watch){
tx({f:'monitor_watch_on',id:d.id,ke:d.ke})
s.tx({viewers:Object.keys(s.group[d.ke].mon[d.id].watch).length,ke:d.ke,id:d.id},'MON_'+d.id)
}
break;
case'watch_off':
if(!d.ke){d.ke=cn.ke;};cn.leave('MON_'+d.id);s.camera(d.ff,d,cn,tx);
s.tx({viewers:d.ob,ke:d.ke,id:d.id},'MON_'+d.id)
break;
case'start':case'stop':
s.sqlQuery('SELECT * FROM Monitors WHERE ke=? AND mid=?',[cn.ke,d.id],function(err,r) {
if(r&&r[0]){r=r[0]
s.camera(d.ff,{type:r.type,url:s.init('url',r),id:d.id,mode:d.ff,ke:cn.ke});
}
})
break;
}
break;
// case'video':
// switch(d.ff){
// case'fix':
// s.video('fix',d)
// break;
// }
// break;
case'ffprobe':
if(s.group[cn.ke].users[cn.auth]){
switch(d.ff){
case'stop':
exec('kill -9 '+s.group[cn.ke].users[cn.auth].ffprobe.pid,{detatched: true})
break;
default:
if(s.group[cn.ke].users[cn.auth].ffprobe){
return
}
s.group[cn.ke].users[cn.auth].ffprobe=1;
tx({f:'ffprobe_start'})
exec('ffprobe '+('-v quiet -print_format json -show_format -show_streams '+d.query),function(err,data){
tx({f:'ffprobe_data',data:data.toString('utf8')})
delete(s.group[cn.ke].users[cn.auth].ffprobe)
tx({f:'ffprobe_stop'})
})
//auto kill in 30 seconds
setTimeout(function(){
exec('kill -9 '+d.pid,{detached: true})
},30000)
break;
}
}
break;
case'onvif':
d.ip=d.ip.replace(/ /g,'');
d.port=d.port.replace(/ /g,'');
if(d.ip===''){
var interfaces = os.networkInterfaces();
var addresses = [];
for (var k in interfaces) {
for (var k2 in interfaces[k]) {
var address = interfaces[k][k2];
if (address.family === 'IPv4' && !address.internal) {
addresses.push(address.address);
}
}
}
d.arr=[]
addresses.forEach(function(v){
if(v.indexOf('0.0.0')>-1){return false}
v=v.split('.');
delete(v[3]);
v=v.join('.');
d.arr.push(v+'1-'+v+'254')
})
d.ip=d.arr.join(',')
}
if(d.port===''){
d.port='80,8080,8000,7575,8081,554'
}
d.ip.split(',').forEach(function(v){
if(v.indexOf('-')>-1){
v=v.split('-');
d.IP_RANGE_START = v[0],
d.IP_RANGE_END = v[1];
}else{
d.IP_RANGE_START = v;
d.IP_RANGE_END = v;
}
if(!d.IP_LIST){
d.IP_LIST = s.ipRange(d.IP_RANGE_START,d.IP_RANGE_END);
}else{
d.IP_LIST=d.IP_LIST.concat(s.ipRange(d.IP_RANGE_START,d.IP_RANGE_END))
}
//check port
if(d.port.indexOf('-')>-1){
d.port=d.port.split('-');
d.PORT_RANGE_START = d.port[0];
d.PORT_RANGE_END = d.port[1];
d.PORT_LIST = s.portRange(d.PORT_RANGE_START,d.PORT_RANGE_END);
}else{
d.PORT_LIST=d.port.split(',')
}
//check user name and pass
d.USERNAME='';
if(d.user){
d.USERNAME = d.user
}
d.PASSWORD='';
if(d.pass){
d.PASSWORD = d.pass
}
})
d.cams=[]
d.IP_LIST.forEach(function(ip_entry,n) {
d.PORT_LIST.forEach(function(port_entry,nn) {
var device = new onvif.OnvifDevice({
xaddr : 'http://' + ip_entry + ':' + port_entry + '/onvif/device_service',
user : d.USERNAME,
pass : d.PASSWORD
})
device.init().then((info) => {
var data = {
f : 'onvif',
ip : ip_entry,
port : port_entry,
info : info
}
device.services.device.getSystemDateAndTime().then((date) => {
data.date = date
device.services.media.getStreamUri({
ProfileToken : device.current_profile.token,
Protocol : 'RTSP'
}).then((stream) => {
data.uri = stream.data.GetStreamUriResponse.MediaUri.Uri
tx(data)
}).catch((error) => {
// console.log(error)
});
}).catch((error) => {
// console.log(error)
});
}).catch(function(error){
// console.log(error)
})
});
});
// tx({f:'onvif_end'})
break;
}
}catch(er){
s.systemLog('ERROR CATCH 1',er)
}
}else{
tx({ok:false,msg:lang.NotAuthorizedText1});
}
});
// admin page socket functions
cn.on('super',function(d){
if(!cn.init&&d.f=='init'){
d.ok=s.superAuth({mail:d.mail,pass:d.pass},function(data){
cn.uid=d.mail
cn.join('$');
cn.ip=cn.request.connection.remoteAddress
s.log({ke:'$',mid:'$USER'},{type:lang['Websocket Connected'],msg:{for:lang['Superuser'],id:cn.uid,ip:cn.ip}})
cn.init='super';
cn.mail=d.mail;
s.tx({f:'init_success',mail:d.mail},cn.id);
})
if(d.ok===false){
cn.disconnect();
}
}else{
if(cn.mail&&cn.init=='super'){
switch(d.f){
case'logs':
switch(d.ff){
case'delete':
s.sqlQuery('DELETE FROM Logs WHERE ke=?',[d.ke])
break;
}
break;
case'system':
switch(d.ff){
case'update':
s.ffmpegKill()
s.systemLog('Shinobi ordered to update',{
by:cn.mail,
ip:cn.ip
})
var updateProcess = spawn('sh',(__dirname+'/UPDATE.sh').split(' '),{detached: true})
updateProcess.stderr.on('data',function(data){
s.systemLog('Update Info',data.toString())
})
updateProcess.stdout.on('data',function(data){
s.systemLog('Update Info',data.toString())
})
break;
case'restart':
d.check=function(x){return d.target.indexOf(x)>-1}
if(d.check('system')){
s.systemLog('Shinobi ordered to restart',{by:cn.mail,ip:cn.ip})
s.ffmpegKill()
exec('pm2 restart '+__dirname+'/camera.js')
}
if(d.check('cron')){
s.systemLog('Shinobi CRON ordered to restart',{by:cn.mail,ip:cn.ip})
exec('pm2 restart '+__dirname+'/cron.js')
}
if(d.check('logs')){
s.systemLog('Flush PM2 Logs',{by:cn.mail,ip:cn.ip})
exec('pm2 flush')
}
break;
case'configure':
s.systemLog('conf.json Modified',{by:cn.mail,ip:cn.ip,old:jsonfile.readFileSync(location.config)})
jsonfile.writeFile(location.config,d.data,{spaces: 2},function(){
s.tx({f:'save_configuration'},cn.id)
})
break;
}
break;
case'accounts':
switch(d.ff){
case'register':
if(d.form.mail!==''&&d.form.pass!==''){
if(d.form.pass===d.form.password_again){
s.sqlQuery('SELECT * FROM Users WHERE mail=?',[d.form.mail],function(err,r) {
if(r&&r[0]){
//found address already exists
d.msg='Email address is in use.';
s.tx({f:'error',ff:'account_register',msg:d.msg},cn.id)
}else{
//create new
//user id
d.form.uid=s.gid();
//check to see if custom key set
if(!d.form.ke||d.form.ke===''){
d.form.ke=s.gid()
}
//write user to db
s.sqlQuery('INSERT INTO Users (ke,uid,mail,pass,details) VALUES (?,?,?,?,?)',[d.form.ke,d.form.uid,d.form.mail,s.md5(d.form.pass),d.form.details])
s.tx({f:'add_account',details:d.form.details,ke:d.form.ke,uid:d.form.uid,mail:d.form.mail},'$');
//init user
s.init('group',d.form)
}
})
}else{
d.msg=lang["Passwords Don't Match"];
}
}else{
d.msg=lang['Fields cannot be empty'];
}
if(d.msg){
s.tx({f:'error',ff:'account_register',msg:d.msg},cn.id)
}
break;
case'edit':
if(d.form.pass&&d.form.pass!==''){
if(d.form.pass===d.form.password_again){
d.form.pass=s.md5(d.form.pass);
}else{
s.tx({f:'error',ff:'account_edit',msg:lang["Passwords Don't Match"]},cn.id)
return
}
}else{
delete(d.form.pass);
}
delete(d.form.password_again);
d.keys=Object.keys(d.form);
d.set=[];
d.values=[];
d.keys.forEach(function(v,n){
if(d.set==='ke'||d.set==='password_again'||!d.form[v]){return}
d.set.push(v+'=?')
d.values.push(d.form[v])
})
d.values.push(d.account.mail)
s.sqlQuery('UPDATE Users SET '+d.set.join(',')+' WHERE mail=?',d.values,function(err,r) {
if(err){
s.tx({f:'error',ff:'account_edit',msg:lang.AccountEditText1},cn.id)
return
}
s.tx({f:'edit_account',form:d.form,ke:d.account.ke,uid:d.account.uid},'$');
delete(s.group[d.account.ke].init);
s.init('apps',d.account)
})
break;
case'delete':
s.sqlQuery('DELETE FROM Users WHERE uid=? AND ke=? AND mail=?',[d.account.uid,d.account.ke,d.account.mail])
s.sqlQuery('DELETE FROM API WHERE uid=? AND ke=?',[d.account.uid,d.account.ke])
s.tx({f:'delete_account',ke:d.account.ke,uid:d.account.uid,mail:d.account.mail},'$');
break;
}
break;
}
}
}
})
// admin page socket functions
cn.on('a',function(d){
if(!cn.init&&d.f=='init'){
s.sqlQuery('SELECT * FROM Users WHERE auth=? AND uid=?',[d.auth,d.uid],function(err,r){
if(r&&r[0]){
r=r[0];
if(!s.group[d.ke]){s.group[d.ke]={users:{}}}
if(!s.group[d.ke].users[d.auth]){s.group[d.ke].users[d.auth]={cnid:cn.id}}
try{s.group[d.ke].users[d.auth].details=JSON.parse(r.details)}catch(er){}
cn.join('ADM_'+d.ke);
cn.ke=d.ke;
cn.uid=d.uid;
cn.auth=d.auth;
cn.init='admin';
}else{
cn.disconnect();
}
})
}else{
s.auth({auth:d.auth,ke:d.ke,id:d.id,ip:cn.request.connection.remoteAddress},function(user){
if(!user.details.sub){
switch(d.f){
case'accounts':
switch(d.ff){
case'edit':
d.keys=Object.keys(d.form);
d.condition=[];
d.value=[];
d.keys.forEach(function(v){
d.condition.push(v+'=?')
d.value.push(d.form[v])
})
d.value=d.value.concat([d.ke,d.$uid])
s.sqlQuery("UPDATE Users SET "+d.condition.join(',')+" WHERE ke=? AND uid=?",d.value)
s.tx({f:'edit_sub_account',ke:d.ke,uid:d.$uid,mail:d.mail,form:d.form},'ADM_'+d.ke);
s.sqlQuery("SELECT * FROM API WHERE ke=? AND uid=?",[d.ke,d.$uid],function(err,rows){
if(rows && rows[0]){
rows.forEach(function(row){
delete(s.api[row.code])
})
}
})
break;
case'delete':
s.sqlQuery('DELETE FROM Users WHERE uid=? AND ke=? AND mail=?',[d.$uid,d.ke,d.mail])
s.sqlQuery("SELECT * FROM API WHERE ke=? AND uid=?",[d.ke,d.$uid],function(err,rows){
if(rows && rows[0]){
rows.forEach(function(row){
delete(s.api[row.code])
})
s.sqlQuery('DELETE FROM API WHERE uid=? AND ke=?',[d.$uid,d.ke])
}
})
s.tx({f:'delete_sub_account',ke:d.ke,uid:d.$uid,mail:d.mail},'ADM_'+d.ke);
break;
}
break;
}
}
})
}
})
//functions for webcam recorder
cn.on('r',function(d){
if(!cn.ke&&d.f==='init'){
s.sqlQuery('SELECT ke,uid,auth,mail,details FROM Users WHERE ke=? AND auth=? AND uid=?',[d.ke,d.auth,d.uid],function(err,r) {
if(r&&r[0]){
r=r[0]
cn.ke=d.ke,cn.uid=d.uid,cn.auth=d.auth;
if(!s.group[d.ke])s.group[d.ke]={};
if(!s.group[d.ke].users)s.group[d.ke].users={};
if(!s.group[d.ke].dashcamUsers)s.group[d.ke].dashcamUsers={};
s.group[d.ke].users[d.auth]={
cnid:cn.id,
ke : d.ke,
uid:r.uid,
mail:r.mail,
details:JSON.parse(r.details),
logged_in_at:s.timeObject(new Date).format(),
login_type:'Streamer'
}
s.group[d.ke].dashcamUsers[d.auth] = s.group[d.ke].users[d.auth]
if(s.group[d.ke].mon){
Object.keys(s.group[d.ke].mon).forEach(function(monitorId){
var dataToClient = {
f : 'disable_stream',
mid : monitorId,
ke : d.ke
}
var mon = s.group[d.ke].mon[monitorId]
if(s.group[d.ke].mon_conf[monitorId].type === 'dashcam'){
if(mon.allowStdinWrite === true){
dataToClient.f = 'enable_stream'
}
s.tx(dataToClient,cn.id)
}
})
}
}
})
}else{
if(s.group[d.ke] && s.group[d.ke].mon[d.mid]){
if(s.group[d.ke].mon[d.mid].allowStdinWrite === true){
switch(d.f){
case'monitor_chunk':
if(s.group[d.ke].mon[d.mid].started!==1 || !s.group[d.ke].mon[d.mid].spawn || !s.group[d.ke].mon[d.mid].spawn.stdin){
s.tx({error:'Not Started'},cn.id);
return false
};
s.group[d.ke].mon[d.mid].spawn.stdin.write(new Buffer(d.chunk, "binary"));
break;
case'monitor_frame':
if(s.group[d.ke].mon[d.mid].started!==1){
s.tx({error:'Not Started'},cn.id);
return false
};
s.group[d.ke].mon[d.mid].spawn.stdin.write(d.frame);
break;
}
}else{
s.tx({error:'Cannot Write Yet'},cn.id)
}
}else{
s.tx({error:'Non Existant Monitor'},cn.id)
}
}
})
//embed functions
cn.on('e', function (d) {
tx=function(z){if(!z.ke){z.ke=cn.ke;};cn.emit('f',z);}
switch(d.f){
case'init':
if(!s.group[d.ke]||!s.group[d.ke].mon[d.id]||s.group[d.ke].mon[d.id].started===0){return false}
s.auth({auth:d.auth,ke:d.ke,id:d.id,ip:cn.request.connection.remoteAddress},function(user){
cn.embedded=1;
cn.ke=d.ke;
if(!cn.mid){cn.mid={}}
cn.mid[d.id]={};
// if(!s.group[d.ke].embed){s.group[d.ke].embed={}}
// if(!s.group[d.ke].embed[d.mid]){s.group[d.ke].embed[d.mid]={}}
// s.group[d.ke].embed[d.mid][cn.id]={}
s.camera('watch_on',d,cn,tx)
cn.join('MON_'+d.id);
cn.join('MON_STREAM_'+d.id);
cn.join('DETECTOR_'+d.ke+d.id);
cn.join('STR_'+d.ke);
if(s.group[d.ke]&&s.group[d.ke].mon[d.id]&&s.group[d.ke].mon[d.id].watch){
tx({f:'monitor_watch_on',id:d.id,ke:d.ke},'MON_'+d.id)
s.tx({viewers:Object.keys(s.group[d.ke].mon[d.id].watch).length,ke:d.ke,id:d.id},'MON_'+d.id)
}
});
break;
}
})
//functions for retrieving cron announcements
cn.on('cron',function(d){
if(d.f==='init'){
if(config.cron.key){
if(config.cron.key===d.cronKey){
s.cron={started:moment(),last_run:moment(),id:cn.id};
}else{
cn.disconnect()
}
}else{
s.cron={started:moment(),last_run:moment(),id:cn.id};
}
}else{
if(s.cron&&cn.id===s.cron.id){
delete(d.cronKey)
switch(d.f){
case'filters':
s.filterEvents(d.ff,d);
break;
case's.tx':
s.tx(d.data,d.to)
break;
case's.video':
s.video(d.data,d.file)
break;
case'start':case'end':
d.mid='_cron';s.log(d,{type:'cron',msg:d.msg})
break;
default:
s.systemLog('CRON : ',d)
break;
}
}else{
cn.disconnect()
}
}
})
cn.on('disconnect', function () {
if(cn.socketVideoStream){
cn.closeSocketVideoStream()
return
}
if(cn.ke){
if(cn.monitor_watching){
cn.monitor_count=Object.keys(cn.monitor_watching)
if(cn.monitor_count.length>0){
cn.monitor_count.forEach(function(v){
s.camera('watch_off',{id:v,ke:cn.monitor_watching[v].ke},s.cn(cn))
})
}
}else if(!cn.embedded){
if(s.group[cn.ke].users[cn.auth].login_type==='Dashboard'){
s.tx({f:'user_status_change',ke:cn.ke,uid:cn.uid,status:0})
}
s.log({ke:cn.ke,mid:'$USER'},{type:lang['Websocket Disconnected'],msg:{mail:s.group[cn.ke].users[cn.auth].mail,id:cn.uid,ip:cn.ip}})
delete(s.group[cn.ke].users[cn.auth]);
if(s.group[cn.ke].dashcamUsers && s.group[cn.ke].dashcamUsers[cn.auth])delete(s.group[cn.ke].dashcamUsers[cn.auth]);
}
}
if(cn.pluginEngine){
s.connectedPlugins[cn.pluginEngine].plugged=false
s.tx({f:'plugin_engine_unplugged',plug:cn.pluginEngine},'CPU')
delete(s.api[cn.pluginEngine])
}
if(cn.cron){
delete(s.cron);
}
if(cn.ocv){
s.tx({f:'detector_unplugged',plug:s.ocv.plug},'CPU')
delete(s.ocv);
delete(s.api[cn.id])
}
})
});
//Authenticator functions
s.api={};
//auth handler
//params = parameters
//cb = callback
//res = response, only needed for express (http server)
//request = request, only needed for express (http server)
s.checkChildProxy = function(params,cb,res,req){
if(s.group[params.ke] && s.group[params.ke].mon[params.id] && s.group[params.ke].mon[params.id].childNode){
var url = 'http://' + s.group[params.ke].mon[params.id].childNode// + req.originalUrl
proxy.web(req, res, { target: url })
}else{
cb()
}
}
//auth handler
//params = parameters
//cb = callback
//res = response, only needed for express (http server)
//request = request, only needed for express (http server)
s.auth = function(params,cb,res,req){
if(req){
//express (http server) use of auth function
params.ip=req.headers['cf-connecting-ip'] || req.headers['x-forwarded-for'] || req.connection.remoteAddress;
var failed=function(){
if(!req.ret){req.ret={ok:false}}
req.ret.msg=lang['Not Authorized'];
res.end(s.s(req.ret, null, 3));
}
}else{
//socket.io use of auth function
var failed=function(){
//maybe log
}
}
var clearAfterTime=function(){
//remove temp key from memory
clearTimeout(s.api[params.auth].timeout)
s.api[params.auth].timeout=setTimeout(function(){
delete(s.api[params.auth])
},1000*60*5)
}
//check IP address of connecting user
var finish=function(user){
if(s.api[params.auth].ip.indexOf('0.0.0.0')>-1||s.api[params.auth].ip.indexOf(params.ip)>-1){
cb(user);
}else{
failed();
}
}
//check if auth key is user's temporary session key
if(s.group[params.ke]&&s.group[params.ke].users&&s.group[params.ke].users[params.auth]){
s.group[params.ke].users[params.auth].permissions={};
cb(s.group[params.ke].users[params.auth]);
}else{
//check if key is already in memory to save query time
if(s.api[params.auth]&&s.api[params.auth].details){
finish(s.api[params.auth]);
if(s.api[params.auth].timeout){
clearAfterTime()
}
}else{
//no key in memory, query db to see if key exists
//check if using username and password in plain text or md5
if(params.username&&params.username!==''&&params.password&&params.password!==''){
s.sqlQuery('SELECT * FROM Users WHERE mail=? AND (pass=? OR pass=?)',[params.username,params.password,s.md5(params.password)],function(err,r){
if(r&&r[0]){
r=r[0];
r.ip='0.0.0.0';
r.auth = s.gid(20);
params.auth = r.auth;
r.details=JSON.parse(r.details);
r.permissions = {};
s.api[r.auth]=r;
clearAfterTime();
finish(r);
}else{
failed();
}
})
}else{
//not using plain login
s.sqlQuery('SELECT * FROM API WHERE code=? AND ke=?',[params.auth,params.ke],function(err,r){
if(r&&r[0]){
r=r[0];
s.api[params.auth]={ip:r.ip,uid:r.uid,ke:r.ke,permissions:JSON.parse(r.details),details:{}};
s.sqlQuery('SELECT details FROM Users WHERE uid=? AND ke=?',[r.uid,r.ke],function(err,rr){
if(rr&&rr[0]){
rr=rr[0];
try{
s.api[params.auth].mail=rr.mail
s.api[params.auth].details=JSON.parse(rr.details)
s.api[params.auth].lang=s.getLanguageFile(s.api[params.auth].details.lang)
}catch(er){}
}
finish(s.api[params.auth]);
})
}else{
s.sqlQuery('SELECT * FROM Users WHERE auth=? AND ke=?',[params.auth,params.ke],function(err,r){
if(r&&r[0]){
r=r[0];
r.ip='0.0.0.0'
s.api[params.auth]=r
s.api[params.auth].details=JSON.parse(r.details)
s.api[params.auth].permissions={}
clearAfterTime()
finish(r)
}else{
failed();
}
})
}
})
}
}
}
}
//super user authentication handler
s.superAuth=function(x,callback){
req={};
req.super=require(location.super);
req.super.forEach(function(v,n){
if(x.md5===true){
x.pass=s.md5(x.pass);
}
if(x.mail.toLowerCase()===v.mail.toLowerCase()&&x.pass===v.pass){
req.found=1;
if(x.users===true){
s.sqlQuery('SELECT * FROM Users WHERE details NOT LIKE ?',['%"sub"%'],function(err,r) {
callback({$user:v,users:r,config:config,lang:lang})
})
}else{
callback({$user:v,config:config,lang:lang})
}
}
})
if(req.found!==1){
return false;
}else{
return true;
}
}
////Pages
app.enable('trust proxy');
app.use('/libs',express.static(__dirname + '/web/libs'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.set('views', __dirname + '/web');
app.set('view engine','ejs');
//add template handler
if(config.renderPaths.handler!==undefined){require(__dirname+'/web/'+config.renderPaths.handler+'.js').addHandlers(s,app,io)}
//readme
app.get('/:auth/logout/:ke/:id', function (req,res){
if(s.group[req.params.ke]&&s.group[req.params.ke].users[req.params.auth]){
delete(s.api[req.params.auth]);
delete(s.group[req.params.ke].users[req.params.auth]);
s.sqlQuery("UPDATE Users SET auth=? WHERE auth=? AND ke=? AND uid=?",['',req.params.auth,req.params.ke,req.params.id])
res.end(s.s({ok:true,msg:'You have been logged out, session key is now inactive.'}, null, 3))
}else{
res.end(s.s({ok:false,msg:'This group key does not exist or this user is not logged in.'}, null, 3))
}
});
//main page
app.get(config.webPaths.index, function (req,res){
res.render(config.renderPaths.index,{lang:lang,config:config,screen:'dashboard'},function(err,html){
if(err){
s.systemLog(err)
}
res.end(html)
})
});
//admin page
app.get(config.webPaths.admin, function (req,res){
res.render(config.renderPaths.index,{lang:lang,config:config,screen:'admin'},function(err,html){
if(err){
s.systemLog(err)
}
res.end(html)
})
});
//super page
app.get(config.webPaths.super, function (req,res){
res.render(config.renderPaths.index,{lang:lang,config:config,screen:'super'},function(err,html){
if(err){
s.systemLog(err)
}
res.end(html)
})
});
//update server
app.get('/:auth/update/:key', function (req,res){
req.ret={ok:false};
res.setHeader('Content-Type', 'application/json');
req.fn=function(user){
if(!config.updateKey){
req.ret.msg=user.lang.updateKeyText1;
return;
}
if(req.params.key===config.updateKey){
req.ret.ok=true;
exec('chmod +x '+__dirname+'/UPDATE.sh&&'+__dirname+'/UPDATE.sh',{detached: true})
}else{
req.ret.msg=user.lang.updateKeyText2;
}
res.end(s.s(req.ret, null, 3));
}
s.auth(req.params,req.fn,res,req);
});
//get user details by API key
app.get('/:auth/userInfo/:ke',function (req,res){
req.ret={ok:false};
res.setHeader('Content-Type', 'application/json');
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
req.ret.ok=true
req.ret.user=user
res.end(s.s(req.ret, null, 3));
},res,req);
})
//register function
app.post('/:auth/register/:ke/:uid',function (req,res){
req.resp={ok:false};
res.setHeader('Content-Type', 'application/json');
s.auth(req.params,function(user){
s.sqlQuery('SELECT * FROM Users WHERE uid=? AND ke=? AND details NOT LIKE ? LIMIT 1',[req.params.uid,req.params.ke,'%"sub"%'],function(err,u) {
if(u&&u[0]){
if(req.body.mail!==''&&req.body.pass!==''){
if(req.body.pass===req.body.password_again){
s.sqlQuery('SELECT * FROM Users WHERE mail=?',[req.body.mail],function(err,r) {
if(r&&r[0]){//found one exist
req.resp.msg='Email address is in use.';
}else{//create new
req.resp.msg='New Account Created';req.resp.ok=true;
req.gid=s.gid();
req.body.details='{"sub":"1","allmonitors":"1"}';
s.sqlQuery('INSERT INTO Users (ke,uid,mail,pass,details) VALUES (?,?,?,?,?)',[req.params.ke,req.gid,req.body.mail,s.md5(req.body.pass),req.body.details])
s.tx({f:'add_sub_account',details:req.body.details,ke:req.params.ke,uid:req.gid,mail:req.body.mail},'ADM_'+req.params.ke);
}
res.end(s.s(req.resp,null,3));
})
}else{
req.resp.msg=user.lang['Passwords Don\'t Match'];
}
}else{
req.resp.msg=user.lang['Fields cannot be empty'];
}
}else{
req.resp.msg=user.lang['Not an Administrator Account'];
}
if(req.resp.msg){
res.end(s.s(req.resp,null,3));
}
})
},res,req);
})
//login function
s.deleteFactorAuth=function(r){
delete(s.factorAuth[r.ke][r.uid])
if(Object.keys(s.factorAuth[r.ke]).length===0){
delete(s.factorAuth[r.ke])
}
}
app.post(['/','/:screen'],function (req,res){
req.ip=req.headers['cf-connecting-ip']||req.headers["CF-Connecting-IP"]||req.headers["'x-forwarded-for"]||req.connection.remoteAddress;
if(req.query.json=='true'){
res.header("Access-Control-Allow-Origin",req.headers.origin);
}
req.renderFunction=function(focus,data){
if(req.query.json=='true'){
delete(data.config)
data.ok=true;
res.setHeader('Content-Type', 'application/json');
res.end(s.s(data, null, 3))
}else{
data.screen=req.params.screen
res.render(focus,data,function(err,html){
if(err){
s.systemLog(err)
}
res.end(html)
});
}
}
req.failed=function(board){
if(req.query.json=='true'){
res.setHeader('Content-Type', 'application/json');
res.end(s.s({ok:false}, null, 3))
}else{
res.render(config.renderPaths.index,{failedLogin:true,lang:lang,config:config,screen:req.params.screen},function(err,html){
if(err){
s.systemLog(err)
}
res.end(html);
});
}
req.logTo={ke:'$',mid:'$USER'}
req.logData={type:lang['Authentication Failed'],msg:{for:board,mail:req.body.mail,ip:req.ip}}
if(board==='super'){
s.log(req.logTo,req.logData)
}else{
s.sqlQuery('SELECT ke,uid,details FROM Users WHERE mail=?',[req.body.mail],function(err,r) {
if(r&&r[0]){
r=r[0]
r.details=JSON.parse(r.details);
r.lang=s.getLanguageFile(r.details.lang)
req.logData.id=r.uid
req.logData.type=r.lang['Authentication Failed']
req.logTo.ke=r.ke
}
s.log(req.logTo,req.logData)
})
}
}
req.fn=function(r){
switch(req.body.function){
case'cam':
s.sqlQuery('SELECT * FROM Monitors WHERE ke=? AND type=?',[r.ke,"dashcam"],function(err,rr){
req.resp.mons=rr;
req.renderFunction(config.renderPaths.dashcam,{$user:req.resp,lang:r.lang,define:s.getDefinitonFile(r.details.lang)});
})
break;
case'streamer':
s.sqlQuery('SELECT * FROM Monitors WHERE ke=? AND type=?',[r.ke,"socket"],function(err,rr){
req.resp.mons=rr;
req.renderFunction(config.renderPaths.streamer,{$user:req.resp,lang:r.lang,define:s.getDefinitonFile(r.details.lang)});
})
break;
case'admin':
if(!r.details.sub){
s.sqlQuery('SELECT uid,mail,details FROM Users WHERE ke=? AND details LIKE \'%"sub"%\'',[r.ke],function(err,rr) {
s.sqlQuery('SELECT * FROM Monitors WHERE ke=?',[r.ke],function(err,rrr) {
req.renderFunction(config.renderPaths.admin,{$user:req.resp,$subs:rr,$mons:rrr,lang:r.lang,define:s.getDefinitonFile(r.details.lang)});
})
})
}else{
//not admin user
req.renderFunction(config.renderPaths.home,{$user:req.resp,config:config,lang:r.lang,define:s.getDefinitonFile(r.details.lang),addStorage:s.dir.addStorage,fs:fs,__dirname:__dirname});
}
break;
default:
req.renderFunction(config.renderPaths.home,{$user:req.resp,config:config,lang:r.lang,define:s.getDefinitonFile(r.details.lang),addStorage:s.dir.addStorage,fs:fs,__dirname:__dirname});
break;
}
s.log({ke:r.ke,mid:'$USER'},{type:r.lang['New Authentication Token'],msg:{for:req.body.function,mail:r.mail,id:r.uid,ip:req.ip}})
// res.end();
}
if(req.body.mail&&req.body.pass){
req.default=function(){
s.sqlQuery('SELECT * FROM Users WHERE mail=? AND pass=?',[req.body.mail,s.md5(req.body.pass)],function(err,r) {
req.resp={ok:false};
if(!err&&r&&r[0]){
r=r[0];r.auth=s.md5(s.gid());
s.sqlQuery("UPDATE Users SET auth=? WHERE ke=? AND uid=?",[r.auth,r.ke,r.uid])
req.resp={ok:true,auth_token:r.auth,ke:r.ke,uid:r.uid,mail:r.mail,details:r.details};
r.details=JSON.parse(r.details);
r.lang=s.getLanguageFile(r.details.lang)
req.factorAuth=function(cb){
if(r.details.factorAuth==="1"){
if(!r.details.acceptedMachines||!(r.details.acceptedMachines instanceof Object)){
r.details.acceptedMachines={}
}
if(!r.details.acceptedMachines[req.body.machineID]){
req.complete=function(){
s.factorAuth[r.ke][r.uid].info=req.resp;
clearTimeout(s.factorAuth[r.ke][r.uid].expireAuth)
s.factorAuth[r.ke][r.uid].expireAuth=setTimeout(function(){
s.deleteFactorAuth(r)
},1000*60*15)
req.renderFunction(config.renderPaths.factorAuth,{$user:req.resp,lang:r.lang})
}
if(!s.factorAuth[r.ke]){s.factorAuth[r.ke]={}}
if(!s.factorAuth[r.ke][r.uid]){
s.factorAuth[r.ke][r.uid]={key:s.nid(),user:r}
r.mailOptions = {
from: config.mail.from,
to: r.mail,
subject: r.lang['2-Factor Authentication'],
html: r.lang['Enter this code to proceed']+' <b>'+s.factorAuth[r.ke][r.uid].key+'</b>. '+r.lang.FactorAuthText1,
};
nodemailer.sendMail(r.mailOptions, (error, info) => {
if (error) {
s.systemLog(r.lang.MailError,error)
req.fn(r)
return
}
req.complete()
});
}else{
req.complete()
}
}else{
req.fn(r)
}
}else{
req.fn(r)
}
}
if(r.details.sub){
s.sqlQuery('SELECT details FROM Users WHERE ke=? AND details NOT LIKE ?',[r.ke,'%"sub"%'],function(err,rr) {
rr=rr[0];
rr.details=JSON.parse(rr.details);
r.details.mon_groups=rr.details.mon_groups;
req.resp.details=JSON.stringify(r.details);
req.factorAuth()
})
}else{
req.factorAuth()
}
}else{
req.failed(req.body.function)
}
})
}
if(LdapAuth&&req.body.function==='ldap'&&req.body.key!==''){
s.sqlQuery('SELECT * FROM Users WHERE ke=? AND details NOT LIKE ?',[req.body.key,'%"sub"%'],function(err,r) {
if(r&&r[0]){
r=r[0]
r.details=JSON.parse(r.details)
r.lang=s.getLanguageFile(r.details.lang)
if(r.details.use_ldap!=='0'&&r.details.ldap_enable==='1'&&r.details.ldap_url&&r.details.ldap_url!==''){
req.mailArray={}
req.body.mail.split(',').forEach(function(v){
v=v.split('=')
req.mailArray[v[0]]=v[1]
})
if(!r.details.ldap_bindDN||r.details.ldap_bindDN===''){
r.details.ldap_bindDN=req.body.mail
}
if(!r.details.ldap_bindCredentials||r.details.ldap_bindCredentials===''){
r.details.ldap_bindCredentials=req.body.pass
}
if(!r.details.ldap_searchFilter||r.details.ldap_searchFilter===''){
r.details.ldap_searchFilter=req.body.mail
if(req.mailArray.cn){
r.details.ldap_searchFilter='cn='+req.mailArray.cn
}
if(req.mailArray.uid){
r.details.ldap_searchFilter='uid='+req.mailArray.uid
}
}else{
r.details.ldap_searchFilter=r.details.ldap_searchFilter.replace('{{username}}',req.body.mail)
}
if(!r.details.ldap_searchBase||r.details.ldap_searchBase===''){
r.details.ldap_searchBase='dc=test,dc=com'
}
req.auth = new LdapAuth({
url:r.details.ldap_url,
bindDN:r.details.ldap_bindDN,
bindCredentials:r.details.ldap_bindCredentials,
searchBase:r.details.ldap_searchBase,
searchFilter:'('+r.details.ldap_searchFilter+')',
reconnect:true
});
req.auth.on('error', function (err) {
console.error('LdapAuth: ', err);
});
req.auth.authenticate(req.body.mail, req.body.pass, function(err, user) {
if(user){
//found user
if(!user.uid){
user.uid=s.gid()
}
req.resp={
ke:req.body.key,
uid:user.uid,
auth:s.md5(s.gid()),
mail:user.mail,
pass:s.md5(req.body.pass),
details:JSON.stringify({
sub:'1',
ldap:'1',
allmonitors:'1',
filter: {}
})
}
user.post=[]
Object.keys(req.resp).forEach(function(v){
user.post.push(req.resp[v])
})
s.log({ke:req.body.key,mid:'$USER'},{type:r.lang['LDAP Success'],msg:{user:user}})
s.sqlQuery('SELECT * FROM Users WHERE ke=? AND mail=?',[req.body.key,user.cn],function(err,rr){
if(rr&&rr[0]){
//already registered
rr=rr[0]
req.resp=rr;
rr.details=JSON.parse(rr.details)
req.resp.lang=s.getLanguageFile(rr.details.lang)
s.log({ke:req.body.key,mid:'$USER'},{type:r.lang['LDAP User Authenticated'],msg:{user:user,shinobiUID:rr.uid}})
s.sqlQuery("UPDATE Users SET auth=? WHERE ke=? AND uid=?",[req.resp.auth,req.resp.ke,rr.uid])
}else{
//new ldap login
s.log({ke:req.body.key,mid:'$USER'},{type:r.lang['LDAP User is New'],msg:{info:r.lang['Creating New Account'],user:user}})
req.resp.lang=r.lang
s.sqlQuery('INSERT INTO Users (ke,uid,auth,mail,pass,details) VALUES (?,?,?,?,?,?)',user.post)
}
req.resp.details=JSON.stringify(req.resp.details)
req.resp.auth_token=req.resp.auth
req.resp.ok=true
req.fn(req.resp)
})
return
}
s.log({ke:req.body.key,mid:'$USER'},{type:r.lang['LDAP Failed'],msg:{err:err}})
//no user
req.default()
});
req.auth.close(function(err) {
})
}else{
req.default()
}
}else{
req.default()
}
})
}else{
if(req.body.function==='super'){
if(!fs.existsSync(location.super)){
res.end(lang.superAdminText)
return
}
req.ok=s.superAuth({mail:req.body.mail,pass:req.body.pass,users:true,md5:true},function(data){
s.sqlQuery('SELECT * FROM Logs WHERE ke=? ORDER BY `time` DESC LIMIT 30',['$'],function(err,r) {
if(!r){
r=[]
}
data.Logs=r;
fs.readFile(location.config,'utf8',function(err,file){
data.plainConfig=JSON.parse(file)
req.renderFunction(config.renderPaths.super,data);
})
})
})
if(req.ok===false){
req.failed(req.body.function)
}
}else{
req.default()
}
}
}else{
if(req.body.machineID&&req.body.factorAuthKey){
if(s.factorAuth[req.body.ke]&&s.factorAuth[req.body.ke][req.body.id]&&s.factorAuth[req.body.ke][req.body.id].key===req.body.factorAuthKey){
if(s.factorAuth[req.body.ke][req.body.id].key===req.body.factorAuthKey){
if(req.body.remember==="1"){
req.details=JSON.parse(s.factorAuth[req.body.ke][req.body.id].info.details)
req.lang=s.getLanguageFile(req.details.lang)
if(!req.details.acceptedMachines||!(req.details.acceptedMachines instanceof Object)){
req.details.acceptedMachines={}
}
if(!req.details.acceptedMachines[req.body.machineID]){
req.details.acceptedMachines[req.body.machineID]={}
s.sqlQuery("UPDATE Users SET details=? WHERE ke=? AND uid=?",[s.s(req.details),req.body.ke,req.body.id])
}
}
req.resp=s.factorAuth[req.body.ke][req.body.id].info
req.fn(s.factorAuth[req.body.ke][req.body.id].user)
}else{
req.renderFunction(config.renderPaths.factorAuth,{$user:s.factorAuth[req.body.ke][req.body.id].info,lang:req.lang});
res.end();
}
}else{
req.failed(lang['2-Factor Authentication'])
}
}else{
req.failed(lang['2-Factor Authentication'])
}
}
});
// Get HLS stream (m3u8)
app.get(['/:auth/hls/:ke/:id/:file','/:auth/hls/:ke/:id/:channel/:file'], function (req,res){
res.header("Access-Control-Allow-Origin",req.headers.origin);
req.fn=function(user){
s.checkChildProxy(req.params,function(){
req.dir=s.dir.streams+req.params.ke+'/'+req.params.id+'/'
if(req.params.channel){
req.dir+='channel'+(parseInt(req.params.channel)+config.pipeAddition)+'/'+req.params.file;
}else{
req.dir+=req.params.file;
}
res.on('finish',function(){res.end();});
if (fs.existsSync(req.dir)){
fs.createReadStream(req.dir).pipe(res);
}else{
res.end(lang['File Not Found'])
}
},res,req)
}
s.auth(req.params,req.fn,res,req);
});
//Get JPEG snap
app.get('/:auth/jpeg/:ke/:id/s.jpg', function(req,res){
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
s.checkChildProxy(req.params,function(){
if(user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitors&&user.details.monitors.indexOf(req.params.id)===-1){
res.end(user.lang['Not Permitted'])
return
}
req.dir=s.dir.streams+req.params.ke+'/'+req.params.id+'/s.jpg';
res.writeHead(200, {
'Content-Type': 'image/jpeg',
'Cache-Control': 'no-cache',
'Pragma': 'no-cache'
});
res.on('finish',function(){res.end();});
if (fs.existsSync(req.dir)){
fs.createReadStream(req.dir).pipe(res);
}else{
fs.createReadStream(config.defaultMjpeg).pipe(res);
}
},res,req);
},res,req);
});
//Get FLV stream
app.get(['/:auth/flv/:ke/:id/s.flv','/:auth/flv/:ke/:id/:channel/s.flv'], function(req,res) {
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
s.checkChildProxy(req.params,function(){
var Emitter,chunkChannel
if(!req.params.channel){
Emitter = s.group[req.params.ke].mon[req.params.id].emitter
chunkChannel = 'MAIN'
}else{
Emitter = s.group[req.params.ke].mon[req.params.id].emitterChannel[parseInt(req.params.channel)+config.pipeAddition]
chunkChannel = parseInt(req.params.channel)+config.pipeAddition
}
if(s.group[req.params.ke].mon[req.params.id].firstStreamChunk[chunkChannel]){
//variable name of contentWriter
var contentWriter
//set headers
res.setHeader('Content-Type', 'video/x-flv');
res.setHeader('Access-Control-Allow-Origin','*');
//write first frame on stream
res.write(s.group[req.params.ke].mon[req.params.id].firstStreamChunk[chunkChannel])
//write new frames as they happen
Emitter.on('data',contentWriter=function(buffer){
res.write(buffer)
})
//remove contentWriter when client leaves
res.on('close', function () {
Emitter.removeListener('data',contentWriter)
})
}else{
res.setHeader('Content-Type', 'application/json');
res.end(s.s({ok:false,msg:'FLV not started or not ready'},null,3))
}
},res,req)
},res,req)
})
//montage - stand alone squished view with gridstackjs
app.get(['/:auth/grid/:ke','/:auth/grid/:ke/:group'], function(req,res) {
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
if(user.permissions.get_monitors==="0"){
res.end(user.lang['Not Permitted'])
return
}
req.params.protocol=req.protocol;
req.sql='SELECT * FROM Monitors WHERE mode!=? AND mode!=? AND ke=?';req.ar=['stop','idle',req.params.ke];
if(!req.params.id){
if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){}
req.or=[];
user.details.monitors.forEach(function(v,n){
req.or.push('mid=?');req.ar.push(v)
})
req.sql+=' AND ('+req.or.join(' OR ')+')'
}
}else{
if(!user.details.sub||user.details.allmonitors!=='0'||user.details.monitors.indexOf(req.params.id)>-1){
req.sql+=' and mid=?';req.ar.push(req.params.id)
}else{
res.end(user.lang['There are no monitors that you can view with this account.']);
return;
}
}
s.sqlQuery(req.sql,req.ar,function(err,r){
if(req.params.group){
var filteredByGroupCheck = {};
var filteredByGroup = [];
r.forEach(function(v,n){
var details = JSON.parse(r[n].details);
try{
req.params.group.split('|').forEach(function(group){
var groups = JSON.parse(details.groups);
if(groups.indexOf(group) > -1 && !filteredByGroupCheck[v.mid]){
filteredByGroupCheck[v.mid] = true;
filteredByGroup.push(v)
}
})
}catch(err){
}
})
r = filteredByGroup;
}
r.forEach(function(v,n){
if(s.group[v.ke]&&s.group[v.ke].mon[v.mid]&&s.group[v.ke].mon[v.mid].watch){
r[n].currentlyWatching=Object.keys(s.group[v.ke].mon[v.mid].watch).length
}
r[n].subStream={}
var details = JSON.parse(r[n].details)
if(details.snap==='1'){
r[n].subStream.jpeg = '/'+req.params.auth+'/jpeg/'+v.ke+'/'+v.mid+'/s.jpg'
}
if(details.stream_channels&&details.stream_channels!==''){
try{
details.stream_channels=JSON.parse(details.stream_channels)
r[n].channels=[]
details.stream_channels.forEach(function(b,m){
var streamURL
switch(b.stream_type){
case'mjpeg':
streamURL='/'+req.params.auth+'/mjpeg/'+v.ke+'/'+v.mid+'/'+m
break;
case'hls':
streamURL='/'+req.params.auth+'/hls/'+v.ke+'/'+v.mid+'/'+m+'/s.m3u8'
break;
case'h264':
streamURL='/'+req.params.auth+'/h264/'+v.ke+'/'+v.mid+'/'+m
break;
case'flv':
streamURL='/'+req.params.auth+'/flv/'+v.ke+'/'+v.mid+'/'+m+'/s.flv'
break;
case'mp4':
streamURL='/'+req.params.auth+'/mp4/'+v.ke+'/'+v.mid+'/'+m+'/s.mp4'
break;
}
r[n].channels.push(streamURL)
})
}catch(err){
s.log(req.params,{type:'Broken Monitor Object',msg:'Stream Channels Field is damaged. Skipping.'})
}
}
})
res.render(config.renderPaths.grid,{
data:Object.assign(req.params,req.query),
baseUrl:req.protocol+'://'+req.hostname,
config:config,
lang:user.lang,
$user:user,
monitors:r
});
})
},res,req)
});
//MJPEG feed
// if query string `full=true` is not present then it will load the MJPEG data directly and not the iframe ready page.
app.get(['/:auth/mjpeg/:ke/:id','/:auth/mjpeg/:ke/:id/:channel'], function(req,res) {
res.header("Access-Control-Allow-Origin",req.headers.origin);
if(req.query.full=='true'){
res.render(config.renderPaths.mjpeg,{url:'/'+req.params.auth+'/mjpeg/'+req.params.ke+'/'+req.params.id});
res.end()
}else{
s.auth(req.params,function(user){
s.checkChildProxy(req.params,function(){
if(s.group[req.params.ke]&&s.group[req.params.ke].mon[req.params.id]){
if(user.permissions.watch_stream==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitors.indexOf(req.params.id)===-1){
res.end(user.lang['Not Permitted'])
return
}
var Emitter
if(!req.params.channel){
Emitter = s.group[req.params.ke].mon[req.params.id].emitter
}else{
Emitter = s.group[req.params.ke].mon[req.params.id].emitterChannel[parseInt(req.params.channel)+config.pipeAddition]
}
res.writeHead(200, {
'Content-Type': 'multipart/x-mixed-replace; boundary=shinobi',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Pragma': 'no-cache'
});
var contentWriter,content = fs.readFileSync(config.defaultMjpeg,'binary');
res.write("--shinobi\r\n");
res.write("Content-Type: image/jpeg\r\n");
res.write("Content-Length: " + content.length + "\r\n");
res.write("\r\n");
res.write(content,'binary');
res.write("\r\n");
Emitter.on('data',contentWriter=function(d){
content = d;
res.write(content,'binary');
})
res.on('close', function () {
Emitter.removeListener('data',contentWriter)
});
}else{
res.end();
}
},res,req);
},res,req);
}
});
//embed monitor
app.get(['/:auth/embed/:ke/:id','/:auth/embed/:ke/:id/:addon'], function (req,res){
res.header("Access-Control-Allow-Origin",req.headers.origin);
req.params.protocol=req.protocol;
s.auth(req.params,function(user){
if(user.permissions.watch_stream==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitors.indexOf(req.params.id)===-1){
res.end(user.lang['Not Permitted'])
return
}
if(s.group[req.params.ke]&&s.group[req.params.ke].mon[req.params.id]){
if(s.group[req.params.ke].mon[req.params.id].started===1){
req.params.uid=user.uid;
res.render(config.renderPaths.embed,{data:req.params,baseUrl:req.protocol+'://'+req.hostname,config:config,lang:user.lang,mon:CircularJSON.parse(CircularJSON.stringify(s.group[req.params.ke].mon_conf[req.params.id]))});
res.end()
}else{
res.end(user.lang['Cannot watch a monitor that isn\'t running.'])
}
}else{
res.end(user.lang['No Monitor Exists with this ID.'])
}
},res,req);
});
// Get TV Channels (Monitor Streams) json
app.get(['/:auth/tvChannels/:ke','/:auth/tvChannels/:ke/:id','/get.php'], function (req,res){
req.ret={ok:false};
if(req.query.username&&req.query.password){
req.params.username = req.query.username
req.params.password = req.query.password
}
var output = ['h264','hls','mp4']
if(req.query.output&&req.query.output!==''){
output = req.query.output.split(',')
output.forEach(function(type,n){
if(type==='ts'){
output[n]='h264'
if(output.indexOf('hls')===-1){
output.push('hls')
}
}
})
}
var isM3u8 = false;
if(req.query.type==='m3u8'||req.query.type==='m3u_plus'){
//is m3u8
isM3u8 = true;
}else{
res.setHeader('Content-Type', 'application/json');
}
res.header("Access-Control-Allow-Origin",req.headers.origin);
req.fn=function(user){
if(user.permissions.get_monitors==="0"){
res.end(s.s([]))
return
}
if(!req.params.ke){
req.params.ke = user.ke;
}
if(req.query.id&&!req.params.id){
req.params.id = req.query.id;
}
req.sql='SELECT * FROM Monitors WHERE mode!=? AND ke=?';req.ar=['stop',req.params.ke];
if(!req.params.id){
if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){}
req.or=[];
user.details.monitors.forEach(function(v,n){
req.or.push('mid=?');req.ar.push(v)
})
req.sql+=' AND ('+req.or.join(' OR ')+')'
}
}else{
if(!user.details.sub||user.details.allmonitors!=='0'||user.details.monitors.indexOf(req.params.id)>-1){
req.sql+=' and mid=?';req.ar.push(req.params.id)
}else{
res.end('[]');
return;
}
}
s.sqlQuery(req.sql,req.ar,function(err,r){
var tvChannelMonitors = [];
r.forEach(function(v,n){
var buildStreamURL = function(channelRow,type,channelNumber){
var streamURL
if(channelNumber){channelNumber = '/'+channelNumber}else{channelNumber=''}
switch(type){
case'mjpeg':
streamURL='/'+req.params.auth+'/mjpeg/'+v.ke+'/'+v.mid+channelNumber
break;
case'hls':
streamURL='/'+req.params.auth+'/hls/'+v.ke+'/'+v.mid+channelNumber+'/s.m3u8'
break;
case'h264':
streamURL='/'+req.params.auth+'/h264/'+v.ke+'/'+v.mid+channelNumber
break;
case'flv':
streamURL='/'+req.params.auth+'/flv/'+v.ke+'/'+v.mid+channelNumber+'/s.flv'
break;
case'mp4':
streamURL='/'+req.params.auth+'/mp4/'+v.ke+'/'+v.mid+channelNumber+'/s.ts'
break;
}
if(streamURL){
if(!channelRow.streamsSortedByType[type]){
channelRow.streamsSortedByType[type]=[]
}
channelRow.streamsSortedByType[type].push(streamURL)
channelRow.streams.push(streamURL)
}
return streamURL
}
var details = JSON.parse(r[n].details);
if(!details.tv_channel_id||details.tv_channel_id==='')details.tv_channel_id = 'temp_'+s.gid(5)
var channelRow = {
ke:v.ke,
mid:v.mid,
type:v.type,
groupTitle:details.tv_channel_group_title,
channel:details.tv_channel_id,
};
if(details.snap==='1'){
channelRow.snapshot = '/'+req.params.auth+'/jpeg/'+v.ke+'/'+v.mid+'/s.jpg'
}
channelRow.streams=[]
channelRow.streamsSortedByType={}
buildStreamURL(channelRow,details.stream_type)
if(details.stream_channels&&details.stream_channels!==''){
details.stream_channels=JSON.parse(details.stream_channels)
details.stream_channels.forEach(function(b,m){
buildStreamURL(channelRow,b.stream_type,m.toString())
})
}
if(details.tv_channel==='1'){
tvChannelMonitors.push(channelRow)
}
})
if(isM3u8){
var m3u8 = '#EXTM3U'+'\n'
tvChannelMonitors.forEach(function(channelRow,n){
output.forEach(function(type){
if(channelRow.streamsSortedByType[type]){
if(req.query.type==='m3u_plus'){
m3u8 +='#EXTINF-1 tvg-id="'+channelRow.mid+'" tvg-name="'+channelRow.channel+'" tvg-logo="'+req.protocol+'://'+req.headers.host+channelRow.snapshot+'" group-title="'+channelRow.groupTitle+'",'+channelRow.channel+'\n'
}else{
m3u8 +='#EXTINF:-1,'+channelRow.channel+' ('+type.toUpperCase()+') \n'
}
m3u8 += req.protocol+'://'+req.headers.host+channelRow.streamsSortedByType[type][0]+'\n'
}
})
})
res.end(m3u8)
}else{
if(tvChannelMonitors.length===1){tvChannelMonitors=tvChannelMonitors[0];}
res.end(s.s(tvChannelMonitors, null, 3));
}
})
}
s.auth(req.params,req.fn,res,req);
});
// Get monitors json
app.get(['/:auth/monitor/:ke','/:auth/monitor/:ke/:id'], function (req,res){
req.ret={ok:false};
res.setHeader('Content-Type', 'application/json');
res.header("Access-Control-Allow-Origin",req.headers.origin);
req.fn=function(user){
if(user.permissions.get_monitors==="0"){
res.end(s.s([]))
return
}
req.sql='SELECT * FROM Monitors WHERE ke=?';req.ar=[req.params.ke];
if(!req.params.id){
if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){}
req.or=[];
user.details.monitors.forEach(function(v,n){
req.or.push('mid=?');req.ar.push(v)
})
req.sql+=' AND ('+req.or.join(' OR ')+')'
}
}else{
if(!user.details.sub||user.details.allmonitors!=='0'||user.details.monitors.indexOf(req.params.id)>-1){
req.sql+=' and mid=?';req.ar.push(req.params.id)
}else{
res.end('[]');
return;
}
}
s.sqlQuery(req.sql,req.ar,function(err,r){
r.forEach(function(v,n){
if(s.group[v.ke]&&s.group[v.ke].mon[v.mid]&&s.group[v.ke].mon[v.mid].watch){
r[n].currentlyWatching=Object.keys(s.group[v.ke].mon[v.mid].watch).length
}
if(s.group[v.ke]&&s.group[v.ke].mon[v.mid]&&s.group[v.ke].mon[v.mid].watch){
r[n].status = s.group[v.ke].mon[v.mid].monitorStatus
}
var buildStreamURL = function(type,channelNumber){
var streamURL
if(channelNumber){channelNumber = '/'+channelNumber}else{channelNumber=''}
switch(type){
case'mjpeg':
streamURL='/'+req.params.auth+'/mjpeg/'+v.ke+'/'+v.mid+channelNumber
break;
case'hls':
streamURL='/'+req.params.auth+'/hls/'+v.ke+'/'+v.mid+channelNumber+'/s.m3u8'
break;
case'h264':
streamURL='/'+req.params.auth+'/h264/'+v.ke+'/'+v.mid+channelNumber
break;
case'flv':
streamURL='/'+req.params.auth+'/flv/'+v.ke+'/'+v.mid+channelNumber+'/s.flv'
break;
case'mp4':
streamURL='/'+req.params.auth+'/mp4/'+v.ke+'/'+v.mid+channelNumber+'/s.mp4'
break;
}
if(streamURL){
if(!r[n].streamsSortedByType[type]){
r[n].streamsSortedByType[type]=[]
}
r[n].streamsSortedByType[type].push(streamURL)
r[n].streams.push(streamURL)
}
return streamURL
}
var details = JSON.parse(r[n].details);
if(!details.tv_channel_id||details.tv_channel_id==='')details.tv_channel_id = 'temp_'+s.gid(5)
if(details.snap==='1'){
r[n].snapshot = '/'+req.params.auth+'/jpeg/'+v.ke+'/'+v.mid+'/s.jpg'
}
r[n].streams=[]
r[n].streamsSortedByType={}
buildStreamURL(details.stream_type)
if(details.stream_channels&&details.stream_channels!==''){
details.stream_channels=JSON.parse(details.stream_channels)
details.stream_channels.forEach(function(b,m){
buildStreamURL(b.stream_type,m.toString())
})
}
})
if(r.length===1){r=r[0];}
res.end(s.s(r, null, 3));
})
}
s.auth(req.params,req.fn,res,req);
});
// Get videos json
app.get(['/:auth/videos/:ke','/:auth/videos/:ke/:id'], function (req,res){
res.setHeader('Content-Type', 'application/json');
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
var hasRestrictions = user.details.sub && user.details.allmonitors !== '1'
if(
user.permissions.watch_videos==="0" ||
hasRestrictions && (!user.details.video_view || user.details.video_view.indexOf(req.params.id)===-1)
){
res.end(s.s([]))
return
}
req.sql='SELECT * FROM Videos WHERE ke=?';req.ar=[req.params.ke];
req.count_sql='SELECT COUNT(*) FROM Videos WHERE ke=?';req.count_ar=[req.params.ke];
if(req.query.archived=='1'){
req.sql+=' AND details LIKE \'%"archived":"1"\''
req.count_sql+=' AND details LIKE \'%"archived":"1"\''
}
if(!req.params.id){
if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){}
req.or=[];
user.details.monitors.forEach(function(v,n){
req.or.push('mid=?');req.ar.push(v)
})
req.sql+=' AND ('+req.or.join(' OR ')+')'
req.count_sql+=' AND ('+req.or.join(' OR ')+')'
}
}else{
if(!user.details.sub||user.details.allmonitors!=='0'||user.details.monitors.indexOf(req.params.id)>-1){
req.sql+=' and mid=?';req.ar.push(req.params.id)
req.count_sql+=' and mid=?';req.count_ar.push(req.params.id)
}else{
res.end('[]');
return;
}
}
if(req.query.start||req.query.end){
if(req.query.start && req.query.start !== ''){
req.query.start = s.stringToSqlTime(req.query.start)
}
if(req.query.end && req.query.end !== ''){
req.query.end = s.stringToSqlTime(req.query.end)
}
if(!req.query.startOperator||req.query.startOperator==''){
req.query.startOperator='>='
}
if(!req.query.endOperator||req.query.endOperator==''){
req.query.endOperator='<='
}
switch(true){
case(req.query.start&&req.query.start!==''&&req.query.end&&req.query.end!==''):
req.sql+=' AND `time` '+req.query.startOperator+' ? AND `end` '+req.query.endOperator+' ?';
req.count_sql+=' AND `time` '+req.query.startOperator+' ? AND `end` '+req.query.endOperator+' ?';
req.ar.push(req.query.start)
req.ar.push(req.query.end)
req.count_ar.push(req.query.start)
req.count_ar.push(req.query.end)
break;
case(req.query.start&&req.query.start!==''):
req.sql+=' AND `time` '+req.query.startOperator+' ?';
req.count_sql+=' AND `time` '+req.query.startOperator+' ?';
req.ar.push(req.query.start)
req.count_ar.push(req.query.start)
break;
case(req.query.end&&req.query.end!==''):
req.sql+=' AND `end` '+req.query.endOperator+' ?';
req.count_sql+=' AND `end` '+req.query.endOperator+' ?';
req.ar.push(req.query.end)
req.count_ar.push(req.query.end)
break;
}
}
req.sql+=' ORDER BY `time` DESC';
if(!req.query.limit||req.query.limit==''){
req.query.limit='100'
}
if(req.query.limit!=='0'){
req.sql+=' LIMIT '+req.query.limit
}
s.sqlQuery(req.sql,req.ar,function(err,r){
if(!r){
res.end(s.s({total:0,limit:req.query.limit,skip:0,videos:[]}, null, 3));
return
}
s.sqlQuery(req.count_sql,req.count_ar,function(err,count){
s.video('linkBuild',r,req.params.auth)
if(req.query.limit.indexOf(',')>-1){
req.skip=parseInt(req.query.limit.split(',')[0])
req.query.limit=parseInt(req.query.limit.split(',')[0])
}else{
req.skip=0
req.query.limit=parseInt(req.query.limit)
}
res.end(s.s({isUTC:config.useUTC,total:count[0]['COUNT(*)'],limit:req.query.limit,skip:req.skip,videos:r}, null, 3));
})
})
},res,req);
});
// Get events json (motion logs)
app.get(['/:auth/events/:ke','/:auth/events/:ke/:id','/:auth/events/:ke/:id/:limit','/:auth/events/:ke/:id/:limit/:start','/:auth/events/:ke/:id/:limit/:start/:end'], function (req,res){
req.ret={ok:false};
res.setHeader('Content-Type', 'application/json');
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
if(user.permissions.watch_videos==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.video_view.indexOf(req.params.id)===-1){
res.end(s.s([]))
return
}
req.sql='SELECT * FROM Events WHERE ke=?';req.ar=[req.params.ke];
if(!req.params.id){
if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){}
req.or=[];
user.details.monitors.forEach(function(v,n){
req.or.push('mid=?');req.ar.push(v)
})
req.sql+=' AND ('+req.or.join(' OR ')+')'
}
}else{
if(!user.details.sub||user.details.allmonitors!=='0'||user.details.monitors.indexOf(req.params.id)>-1){
req.sql+=' and mid=?';req.ar.push(req.params.id)
}else{
res.end('[]');
return;
}
}
if(req.params.start&&req.params.start!==''){
req.params.start = s.stringToSqlTime(req.params.start)
if(req.params.end&&req.params.end!==''){
req.params.end = s.stringToSqlTime(req.params.end)
req.sql+=' AND `time` >= ? AND `time` <= ?';
req.ar.push(decodeURIComponent(req.params.start))
req.ar.push(decodeURIComponent(req.params.end))
}else{
req.sql+=' AND `time` >= ?';
req.ar.push(decodeURIComponent(req.params.start))
}
}
if(!req.params.limit||req.params.limit==''){req.params.limit=100}
req.sql+=' ORDER BY `time` DESC LIMIT '+req.params.limit+'';
s.sqlQuery(req.sql,req.ar,function(err,r){
if(err){
err.sql=req.sql;
res.end(s.s(err, null, 3));
return
}
if(!r){r=[]}
r.forEach(function(v,n){
r[n].details=JSON.parse(v.details);
})
res.end(s.s(r, null, 3));
})
},res,req);
});
// Get logs json
app.get(['/:auth/logs/:ke','/:auth/logs/:ke/:id'], function (req,res){
req.ret={ok:false};
res.setHeader('Content-Type', 'application/json');
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
if(user.permissions.get_logs==="0" || user.details.sub && user.details.view_logs !== '1'){
res.end(s.s([]))
return
}
req.sql='SELECT * FROM Logs WHERE ke=?';req.ar=[req.params.ke];
if(!req.params.id){
if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){}
req.or=[];
user.details.monitors.forEach(function(v,n){
req.or.push('mid=?');req.ar.push(v)
})
req.sql+=' AND ('+req.or.join(' OR ')+')'
}
}else{
if(!user.details.sub||user.details.allmonitors!=='0'||user.details.monitors.indexOf(req.params.id)>-1||req.params.id.indexOf('$')>-1){
req.sql+=' and mid=?';req.ar.push(req.params.id)
}else{
res.end('[]');
return;
}
}
if(req.query.start||req.query.end){
if(!req.query.startOperator||req.query.startOperator==''){
req.query.startOperator='>='
}
if(!req.query.endOperator||req.query.endOperator==''){
req.query.endOperator='<='
}
if(req.query.start && req.query.start !== '' && req.query.end && req.query.end !== ''){
req.query.start = s.stringToSqlTime(req.query.start)
req.query.end = s.stringToSqlTime(req.query.end)
req.sql+=' AND `time` '+req.query.startOperator+' ? AND `time` '+req.query.endOperator+' ?';
req.ar.push(req.query.start)
req.ar.push(req.query.end)
}else if(req.query.start && req.query.start !== ''){
req.query.start = s.stringToSqlTime(req.query.start)
req.sql+=' AND `time` '+req.query.startOperator+' ?';
req.ar.push(req.query.start)
}
}
if(!req.query.limit||req.query.limit==''){req.query.limit=50}
req.sql+=' ORDER BY `time` DESC LIMIT '+req.query.limit+'';
s.sqlQuery(req.sql,req.ar,function(err,r){
if(err){
err.sql=req.sql;
res.end(s.s(err, null, 3));
return
}
if(!r){r=[]}
r.forEach(function(v,n){
r[n].info=JSON.parse(v.info)
})
res.end(s.s(r, null, 3));
})
},res,req);
});
// Get monitors online json
app.get('/:auth/smonitor/:ke', function (req,res){
req.ret={ok:false};
res.setHeader('Content-Type', 'application/json');
res.header("Access-Control-Allow-Origin",req.headers.origin);
req.fn=function(user){
if(user.permissions.get_monitors==="0"){
res.end(s.s([]))
return
}
req.sql='SELECT * FROM Monitors WHERE ke=?';req.ar=[req.params.ke];
if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){}
req.or=[];
user.details.monitors.forEach(function(v,n){
req.or.push('mid=?');req.ar.push(v)
})
req.sql+=' AND ('+req.or.join(' OR ')+')'
}
s.sqlQuery(req.sql,req.ar,function(err,r){
if(r&&r[0]){
req.ar=[];
r.forEach(function(v){
if(s.group[req.params.ke]&&s.group[req.params.ke].mon[v.mid]&&s.group[req.params.ke].mon[v.mid].started===1){
req.ar.push(v)
}
})
}else{
req.ar=[];
}
res.end(s.s(req.ar, null, 3));
})
}
s.auth(req.params,req.fn,res,req);
});
// Monitor Add,Edit,Delete
app.all(['/:auth/configureMonitor/:ke/:id','/:auth/configureMonitor/:ke/:id/:f'], function (req,res){
req.ret={ok:false};
res.setHeader('Content-Type', 'application/json');
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
var hasRestrictions = user.details.sub && user.details.allmonitors !== '1'
if(req.params.f !== 'delete'){
if(!req.body.data&&!req.query.data){
req.ret.msg='No Monitor Data found.'
res.end(s.s(req.ret, null, 3))
return
}
try{
if(req.query.data){
req.monitor=JSON.parse(req.query.data)
}else{
req.monitor=JSON.parse(req.body.data)
}
}catch(er){
if(!req.monitor){
req.ret.msg=user.lang.monitorEditText1;
res.end(s.s(req.ret, null, 3))
}
return
}
if(!user.details.sub ||
user.details.allmonitors === '1' ||
hasRestrictions && user.details.monitor_edit.indexOf(req.monitor.mid) >- 1 ||
hasRestrictions && user.details.monitor_create === '1'){
if(req.monitor&&req.monitor.mid&&req.monitor.name){
req.set=[],req.ar=[];
req.monitor.mid=req.params.id.replace(/[^\w\s]/gi,'').replace(/ /g,'');
try{
JSON.parse(req.monitor.details)
}catch(er){
if(!req.monitor.details||!req.monitor.details.stream_type){
req.ret.msg=user.lang.monitorEditText2;
res.end(s.s(req.ret, null, 3))
return
}else{
req.monitor.details=JSON.stringify(req.monitor.details)
}
}
req.monitor.ke=req.params.ke
req.logObject={details:JSON.parse(req.monitor.details),ke:req.params.ke,mid:req.params.id}
s.sqlQuery('SELECT * FROM Monitors WHERE ke=? AND mid=?',[req.monitor.ke,req.monitor.mid],function(er,r){
req.tx={f:'monitor_edit',mid:req.monitor.mid,ke:req.monitor.ke,mon:req.monitor};
if(r&&r[0]){
req.tx.new=false;
Object.keys(req.monitor).forEach(function(v){
if(req.monitor[v]&&req.monitor[v]!==''){
req.set.push(v+'=?'),req.ar.push(req.monitor[v]);
}
})
req.set=req.set.join(',');
req.ar.push(req.monitor.ke),req.ar.push(req.monitor.mid);
s.log(req.monitor,{type:'Monitor Updated',msg:'by user : '+user.uid});
req.ret.msg=user.lang['Monitor Updated by user']+' : '+user.uid;
s.sqlQuery('UPDATE Monitors SET '+req.set+' WHERE ke=? AND mid=?',req.ar)
req.finish=1;
}else{
if(!s.group[req.monitor.ke].init.max_camera||s.group[req.monitor.ke].init.max_camera==''||Object.keys(s.group[req.monitor.ke].mon).length <= parseInt(s.group[req.monitor.ke].init.max_camera)){
req.tx.new=true;
req.st=[];
Object.keys(req.monitor).forEach(function(v){
if(req.monitor[v]&&req.monitor[v]!==''){
req.set.push(v),req.st.push('?'),req.ar.push(req.monitor[v]);
}
})
// req.set.push('ke'),req.st.push('?'),req.ar.push(req.monitor.ke);
req.set=req.set.join(','),req.st=req.st.join(',');
s.log(req.monitor,{type:'Monitor Added',msg:'by user : '+user.uid});
req.ret.msg=user.lang['Monitor Added by user']+' : '+user.uid;
s.sqlQuery('INSERT INTO Monitors ('+req.set+') VALUES ('+req.st+')',req.ar)
req.finish=1;
}else{
req.tx.f='monitor_edit_failed';
req.tx.ff='max_reached';
req.ret.msg=user.lang.monitorEditFailedMaxReached;
}
}
if(req.finish===1){
req.monitor.details=JSON.parse(req.monitor.details)
req.ret.ok=true;
s.init(0,{mid:req.monitor.mid,ke:req.monitor.ke});
s.group[req.monitor.ke].mon_conf[req.monitor.mid]=s.init('noReference',req.monitor);
if(req.monitor.mode==='stop'){
s.camera('stop',req.monitor);
}else{
s.camera('stop',req.monitor);setTimeout(function(){s.camera(req.monitor.mode,req.monitor);},5000)
};
s.tx(req.tx,'STR_'+req.monitor.ke);
};
s.tx(req.tx,'GRP_'+req.monitor.ke);
res.end(s.s(req.ret, null, 3))
})
}else{
req.ret.msg=user.lang.monitorEditText1;
res.end(s.s(req.ret, null, 3))
}
}else{
req.ret.msg=user.lang['Not Permitted'];
res.end(s.s(req.ret, null, 3))
}
}else{
if(!user.details.sub || user.details.allmonitors === '1' || user.details.monitor_edit.indexOf(req.params.id) > -1 || hasRestrictions && user.details.monitor_create === '1'){
s.log(s.group[req.params.ke].mon_conf[req.params.id],{type:'Monitor Deleted',msg:'by user : '+user.uid});
req.params.delete=1;s.camera('stop',req.params);
s.tx({f:'monitor_delete',uid:user.uid,mid:req.params.id,ke:req.params.ke},'GRP_'+req.params.ke);
s.sqlQuery('DELETE FROM Monitors WHERE ke=? AND mid=?',[req.params.ke,req.params.id])
// s.sqlQuery('DELETE FROM Files WHERE ke=? AND mid=?',[req.params.ke,req.params.id])
if(req.query.deleteFiles === 'true'){
//videos
s.dir.addStorage.forEach(function(v,n){
var videosDir = v.path+req.params.ke+'/'+req.params.id+'/'
fs.stat(videosDir,function(err,stat){
if(!err){
s.file('deleteFolder',videosDir)
}
})
})
var videosDir = s.dir.videos+req.params.ke+'/'+req.params.id+'/'
fs.stat(videosDir,function(err,stat){
if(!err){
s.file('deleteFolder',videosDir)
}
})
//fileBin
var binDir = s.dir.fileBin+req.params.ke+'/'+req.params.id+'/'
fs.stat(binDir,function(err,stat){
if(!err){
s.file('deleteFolder',binDir)
}
})
}
req.ret.ok=true;
req.ret.msg='Monitor Deleted by user : '+user.uid
res.end(s.s(req.ret, null, 3))
}else{
req.ret.msg=user.lang['Not Permitted'];
res.end(s.s(req.ret, null, 3))
}
}
})
})
app.get(['/:auth/monitor/:ke/:id/:f','/:auth/monitor/:ke/:id/:f/:ff','/:auth/monitor/:ke/:id/:f/:ff/:fff'], function (req,res){
req.ret={ok:false};
res.setHeader('Content-Type', 'application/json');
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
if(user.permissions.control_monitors==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitor_edit.indexOf(req.params.id)===-1){
res.end(user.lang['Not Permitted'])
return
}
if(req.params.f===''){req.ret.msg=user.lang.monitorGetText1;res.end(s.s(req.ret, null, 3));return}
if(req.params.f!=='stop'&&req.params.f!=='start'&&req.params.f!=='record'){
req.ret.msg='Mode not recognized.';
res.end(s.s(req.ret, null, 3));
return;
}
s.sqlQuery('SELECT * FROM Monitors WHERE ke=? AND mid=?',[req.params.ke,req.params.id],function(err,r){
if(r&&r[0]){
r=r[0];
if(req.query.reset==='1'||(s.group[r.ke]&&s.group[r.ke].mon_conf[r.mid].mode!==req.params.f)||req.query.fps&&(!s.group[r.ke].mon[r.mid].currentState||!s.group[r.ke].mon[r.mid].currentState.trigger_on)){
if(req.query.reset!=='1'||!s.group[r.ke].mon[r.mid].trigger_timer){
if(!s.group[r.ke].mon[r.mid].currentState)s.group[r.ke].mon[r.mid].currentState={}
s.group[r.ke].mon[r.mid].currentState.mode=r.mode.toString()
s.group[r.ke].mon[r.mid].currentState.fps=r.fps.toString()
if(!s.group[r.ke].mon[r.mid].currentState.trigger_on){
s.group[r.ke].mon[r.mid].currentState.trigger_on=true
}else{
s.group[r.ke].mon[r.mid].currentState.trigger_on=false
}
r.mode=req.params.f;
try{r.details=JSON.parse(r.details);}catch(er){}
if(req.query.fps){
r.fps=parseFloat(r.details.detector_trigger_record_fps)
s.group[r.ke].mon[r.mid].currentState.detector_trigger_record_fps=r.fps
}
r.id=r.mid;
s.sqlQuery('UPDATE Monitors SET mode=? WHERE ke=? AND mid=?',[r.mode,r.ke,r.mid]);
s.group[r.ke].mon_conf[r.mid]=r;
s.tx({f:'monitor_edit',mid:r.mid,ke:r.ke,mon:r},'GRP_'+r.ke);
s.tx({f:'monitor_edit',mid:r.mid,ke:r.ke,mon:r},'STR_'+r.ke);
s.camera('stop',s.init('noReference',r));
if(req.params.f!=='stop'){
s.camera(req.params.f,s.init('noReference',r));
}
req.ret.msg=user.lang['Monitor mode changed']+' : '+req.params.f;
}else{
req.ret.msg=user.lang['Reset Timer'];
}
req.ret.cmd_at=s.formattedTime(new Date,'YYYY-MM-DD HH:mm:ss');
req.ret.ok=true;
if(req.params.ff&&req.params.f!=='stop'){
req.params.ff=parseFloat(req.params.ff);
clearTimeout(s.group[r.ke].mon[r.mid].trigger_timer)
switch(req.params.fff){
case'day':case'days':
req.timeout=req.params.ff*1000*60*60*24
break;
case'hr':case'hour':case'hours':
req.timeout=req.params.ff*1000*60*60
break;
case'min':case'minute':case'minutes':
req.timeout=req.params.ff*1000*60
break;
default://seconds
req.timeout=req.params.ff*1000
break;
}
s.group[r.ke].mon[r.mid].trigger_timer=setTimeout(function(){
delete(s.group[r.ke].mon[r.mid].trigger_timer)
s.sqlQuery('UPDATE Monitors SET mode=? WHERE ke=? AND mid=?',[s.group[r.ke].mon[r.mid].currentState.mode,r.ke,r.mid]);
r.neglectTriggerTimer=1;
r.mode=s.group[r.ke].mon[r.mid].currentState.mode;
r.fps=s.group[r.ke].mon[r.mid].currentState.fps;
s.camera('stop',s.init('noReference',r),function(){
if(s.group[r.ke].mon[r.mid].currentState.mode!=='stop'){
s.camera(s.group[r.ke].mon[r.mid].currentState.mode,s.init('noReference',r));
}
s.group[r.ke].mon_conf[r.mid]=r;
});
s.tx({f:'monitor_edit',mid:r.mid,ke:r.ke,mon:r},'GRP_'+r.ke);
s.tx({f:'monitor_edit',mid:r.mid,ke:r.ke,mon:r},'STR_'+r.ke);
},req.timeout);
// req.ret.end_at=s.formattedTime(new Date,'YYYY-MM-DD HH:mm:ss').add(req.timeout,'milliseconds');
}
}else{
req.ret.msg=user.lang['Monitor mode is already']+' : '+req.params.f;
}
}else{
req.ret.msg=user.lang['Monitor or Key does not exist.'];
}
res.end(s.s(req.ret, null, 3));
})
},res,req);
})
//get file from fileBin bin
app.get(['/:auth/fileBin/:ke','/:auth/fileBin/:ke/:id'],function (req,res){
res.setHeader('Content-Type', 'application/json');
res.header("Access-Control-Allow-Origin",req.headers.origin);
req.fn=function(user){
req.sql='SELECT * FROM Files WHERE ke=?';req.ar=[req.params.ke];
if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){}
req.or=[];
user.details.monitors.forEach(function(v,n){
req.or.push('mid=?');req.ar.push(v)
})
req.sql+=' AND ('+req.or.join(' OR ')+')'
}else{
if(req.params.id&&(!user.details.sub||user.details.allmonitors!=='0'||user.details.monitors.indexOf(req.params.id)>-1)){
req.sql+=' and mid=?';req.ar.push(req.params.id)
}
}
s.sqlQuery(req.sql,req.ar,function(err,r){
if(!r){
r=[]
}else{
r.forEach(function(v){
v.details=JSON.parse(v.details)
v.href='/'+req.params.auth+'/fileBin/'+req.params.ke+'/'+req.params.id+'/'+v.details.year+'/'+v.details.month+'/'+v.details.day+'/'+v.name;
})
}
res.end(s.s(r, null, 3));
})
}
s.auth(req.params,req.fn,res,req);
});
//get file from fileBin bin
app.get('/:auth/fileBin/:ke/:id/:year/:month/:day/:file', function (req,res){
res.header("Access-Control-Allow-Origin",req.headers.origin);
req.fn=function(user){
req.failed=function(){
res.end(user.lang['File Not Found'])
}
if (!s.group[req.params.ke].fileBin[req.params.id+'/'+req.params.file]){
s.sqlQuery('SELECT * FROM Files WHERE ke=? AND mid=? AND name=?',[req.params.ke,req.params.id,req.params.file],function(err,r){
if(r&&r[0]){
r=r[0]
r.details=JSON.parse(r.details)
req.dir=s.dir.fileBin+req.params.ke+'/'+req.params.id+'/'+r.details.year+'/'+r.details.month+'/'+r.details.day+'/'+req.params.file;
if(fs.existsSync(req.dir)){
res.on('finish',function(){res.end();});
fs.createReadStream(req.dir).pipe(res);
}else{
req.failed()
}
}else{
req.failed()
}
})
}else{
res.end(user.lang['Please Wait for Completion'])
}
}
s.auth(req.params,req.fn,res,req);
});
//zip videos and get link from fileBin
app.get('/:auth/zipVideos/:ke', function (req,res){
res.header("Access-Control-Allow-Origin",req.headers.origin);
var failed = function(resp){
res.setHeader('Content-Type', 'application/json');
res.end(s.s(resp))
}
if(req.query.videos && req.query.videos !== ''){
s.auth(req.params,function(user){
var videosSelected = JSON.parse(req.query.videos)
var where = []
var values = []
videosSelected.forEach(function(video){
where.push("(ke=? AND mid=? AND `time`=?)")
if(!video.ke)video.ke = req.params.ke
values.push(video.ke)
values.push(video.mid)
var time = s.nameToTime(video.filename)
if(req.query.isUTC === 'true'){
time = s.utcToLocal(time)
}
time = new Date(time)
values.push(time)
})
s.sqlQuery('SELECT * FROM Videos WHERE '+where.join(' OR '),values,function(err,r){
var resp = {ok:false}
if(r && r[0]){
resp.ok = true
var zipDownload = null
var tempFiles = []
var fileId = s.gid()
var fileBinDir = s.dir.fileBin+req.params.ke+'/'
var tempScript = s.dir.streams+req.params.ke+'/'+fileId+'.sh'
var zippedFilename = s.formattedTime()+'-'+fileId+'-Shinobi_Recordings.zip'
var zippedFile = fileBinDir+zippedFilename
var script = 'cd '+fileBinDir+' && zip -9 -r '+zippedFile
res.on('close', () => {
if(zipDownload && zipDownload.destroy){
zipDownload.destroy()
}
fs.unlink(zippedFile);
})
if(!fs.existsSync(fileBinDir)){
fs.mkdirSync(fileBinDir);
}
r.forEach(function(video){
timeFormatted = s.formattedTime(video.time)
video.filename = timeFormatted+'.'+video.ext
var dir = s.video('getDir',video)+video.filename
var tempVideoFile = timeFormatted+' - '+video.mid+'.'+video.ext
fs.writeFileSync(fileBinDir+tempVideoFile, fs.readFileSync(dir))
tempFiles.push(fileBinDir+tempVideoFile)
script += ' "'+tempVideoFile+'"'
})
fs.writeFileSync(tempScript,script,'utf8')
var zipCreate = spawn('sh',(tempScript).split(' '),{detached: true})
zipCreate.stderr.on('data',function(data){
s.log({ke:req.params.ke,mid:'$USER'},{title:'Zip Create Error',msg:data.toString()})
})
zipCreate.on('exit',function(data){
fs.unlinkSync(tempScript)
tempFiles.forEach(function(file){
fs.unlink(file,function(){})
})
res.setHeader('Content-Disposition', 'attachment; filename="'+zippedFilename+'"')
var zipDownload = fs.createReadStream(zippedFile)
zipDownload.pipe(res)
zipDownload.on('error', function (error) {
s.log({ke:req.params.ke,mid:'$USER'},{title:'Zip Download Error',msg:error.toString()})
if(zipDownload && zipDownload.destroy){
zipDownload.destroy()
}
});
zipDownload.on('close', function () {
res.end()
zipDownload.destroy();
fs.unlinkSync(zippedFile);
});
})
}else{
failed({ok:false,msg:'No Videos Found'})
}
})
},res,req);
}else{
failed({ok:false,msg:'"videos" query variable is missing from request.'})
}
});
// Get video file
app.get('/:auth/videos/:ke/:id/:file', function (req,res){
s.auth(req.params,function(user){
if(user.permissions.watch_videos==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitors.indexOf(req.params.id)===-1){
res.end(user.lang['Not Permitted'])
return
}
var time = s.nameToTime(req.params.file)
if(req.query.isUTC === 'true'){
time = s.utcToLocal(time)
}
time = new Date(time)
s.sqlQuery('SELECT * FROM Videos WHERE ke=? AND mid=? AND `time`=?',[req.params.ke,req.params.id,time],function(err,r){
if(r&&r[0]){
req.dir=s.video('getDir',r[0])+req.params.file
if (fs.existsSync(req.dir)){
req.ext=req.params.file.split('.')[1];
var total = fs.statSync(req.dir).size;
if (req.headers['range']) {
var range = req.headers.range;
var parts = range.replace(/bytes=/, "").split("-");
var partialstart = parts[0];
var partialend = parts[1];
var start = parseInt(partialstart, 10);
var end = partialend ? parseInt(partialend, 10) : total-1;
var chunksize = (end-start)+1;
var file = fs.createReadStream(req.dir, {start: start, end: end});
req.headerWrite={ 'Content-Range': 'bytes ' + start + '-' + end + '/' + total, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': 'video/'+req.ext }
req.writeCode=206
} else {
req.headerWrite={ 'Content-Length': total, 'Content-Type': 'video/'+req.ext};
var file=fs.createReadStream(req.dir)
req.writeCode=200
}
if(req.query.downloadName){
req.headerWrite['content-disposition']='attachment; filename="'+req.query.downloadName+'"';
}
res.writeHead(req.writeCode,req.headerWrite);
file.on('close',function(){
res.end();
})
file.pipe(res);
}else{
res.end(user.lang['File Not Found in Filesystem'])
}
}else{
res.end(user.lang['File Not Found in Database'])
}
})
},res,req);
});
//motion trigger
app.get('/:auth/motion/:ke/:id', function (req,res){
s.auth(req.params,function(user){
if(req.query.data){
try{
var d={id:req.params.id,ke:req.params.ke,details:JSON.parse(req.query.data)};
}catch(err){
res.end('Data Broken',err);
return;
}
}else{
res.end('No Data');
return;
}
if(!d.ke||!d.id||!s.group[d.ke]){
res.end(user.lang['No Group with this key exists']);
return;
}
s.camera('motion',d,function(){
res.end(user.lang['Trigger Successful'])
});
},res,req);
})
//hookTester trigger
app.get('/:auth/hookTester/:ke/:id', function (req,res){
res.setHeader('Content-Type', 'application/json');
s.auth(req.params,function(user){
s.log(req.params,{type:'Test',msg:'Hook Test'})
res.end(s.s({ok:true},null,3))
},res,req);
})
//control trigger
app.get('/:auth/control/:ke/:id/:direction', function (req,res){
res.setHeader('Content-Type', 'application/json');
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
s.camera('control',req.params,function(resp){
res.end(s.s(resp,null,3))
});
},res,req);
})
//modify video file
app.get(['/:auth/videos/:ke/:id/:file/:mode','/:auth/videos/:ke/:id/:file/:mode/:f'], function (req,res){
req.ret={ok:false};
res.setHeader('Content-Type', 'application/json');
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
if(user.permissions.watch_videos==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.video_delete.indexOf(req.params.id)===-1){
res.end(user.lang['Not Permitted'])
return
}
var time = s.nameToTime(req.params.file)
if(req.query.isUTC === 'true'){
time = s.utcToLocal(time)
}
time = new Date(time)
req.sql='SELECT * FROM Videos WHERE ke=? AND mid=? AND `time`=?';
req.ar=[req.params.ke,req.params.id,time];
s.sqlQuery(req.sql,req.ar,function(err,r){
if(r&&r[0]){
r=r[0];r.filename=s.formattedTime(r.time)+'.'+r.ext;
switch(req.params.mode){
case'fix':
req.ret.ok=true;
s.video('fix',r)
break;
case'status':
r.f = 'video_edit'
r.status = parseInt(req.params.f)
if(isNaN(req.params.f)||req.params.f===0){
req.ret.msg='Not a valid value.';
}else{
req.ret.ok=true;
s.sqlQuery('UPDATE Videos SET status=? WHERE ke=? AND mid=? AND `time`=?',[req.params.f,req.params.ke,req.params.id,time])
s.tx(r,'GRP_'+r.ke);
}
break;
case'delete':
req.ret.ok=true;
s.video('delete',r)
break;
default:
req.ret.msg=user.lang.modifyVideoText1;
break;
}
}else{
req.ret.msg=user.lang['No such file'];
}
res.end(s.s(req.ret, null, 3));
})
},res,req);
})
//ffmpeg pushed stream in here to make a pipe
app.all(['/streamIn/:ke/:id','/streamIn/:ke/:id/:feed'], function (req, res) {
var checkOrigin = function(search){return req.headers.host.indexOf(search)>-1}
if(checkOrigin('127.0.0.1')){
if(!req.params.feed){req.params.feed='1'}
if(!s.group[req.params.ke].mon[req.params.id].streamIn[req.params.feed]){
s.group[req.params.ke].mon[req.params.id].streamIn[req.params.feed] = new events.EventEmitter().setMaxListeners(0)
}
//req.params.feed = Feed Number
res.connection.setTimeout(0);
req.on('data', function(buffer){
s.group[req.params.ke].mon[req.params.id].streamIn[req.params.feed].emit('data',buffer)
});
req.on('end',function(){
// console.log('streamIn closed',req.params);
});
}else{
res.end('Local connection is only allowed.')
}
})
//MP4 Stream
app.get(['/:auth/mp4/:ke/:id/:channel/s.mp4','/:auth/mp4/:ke/:id/s.mp4','/:auth/mp4/:ke/:id/:channel/s.ts','/:auth/mp4/:ke/:id/s.ts'], function (req, res) {
s.auth(req.params,function(user){
if(!s.group[req.params.ke] || !s.group[req.params.ke].mon[req.params.id]){
res.status(404);
res.end('404 : Monitor not found');
return
}
s.checkChildProxy(req.params,function(){
var Channel = 'MAIN'
if(req.params.channel){
Channel = parseInt(req.params.channel)+config.pipeAddition
}
var mp4frag = s.group[req.params.ke].mon[req.params.id].mp4frag[Channel];
var errorMessage = 'MP4 Stream is not enabled'
if(!mp4frag){
res.status(503);
res.end('503 : initialization : '+errorMessage);
}else{
var init = mp4frag.initialization;
if (!init) {
res.status(503);
res.end('404 : Not Found : '+errorMessage);
} else {
res.locals.mp4frag = mp4frag
res.set('Access-Control-Allow-Origin', '*')
res.set('Connection', 'close')
res.set('Cache-Control', 'private, no-cache, no-store, must-revalidate')
res.set('Expires', '-1')
res.set('Pragma', 'no-cache')
res.set('Content-Type', 'video/mp4')
res.status(200);
res.write(init);
mp4frag.pipe(res);
res.on('close', () => {
mp4frag.unpipe(res);
});
}
}
},res,req);
},res,req);
});
//simulate RTSP over HTTP
app.get([
'/:auth/mpegts/:ke/:id/:feed/:file',
'/:auth/mpegts/:ke/:id/:feed/',
'/:auth/h264/:ke/:id/:feed/:file',
'/:auth/h264/:ke/:id/:feed',
'/:auth/h264/:ke/:id'
], function (req, res) {
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
s.checkChildProxy(req.params,function(){
if(!req.query.feed){req.query.feed='1'}
var Emitter
if(!req.params.feed){
Emitter = s.group[req.params.ke].mon[req.params.id].streamIn[req.query.feed]
}else{
Emitter = s.group[req.params.ke].mon[req.params.id].emitterChannel[parseInt(req.params.feed)+config.pipeAddition]
}
s.init('streamIn',req.params)
var contentWriter
var date = new Date();
res.writeHead(200, {
'Date': date.toUTCString(),
'Connection': 'keep-alive',
'Cache-Control': 'no-cache',
'Pragma': 'no-cache',
'Content-Type': 'video/mp4',
'Server': 'Shinobi H.264 Test Stream',
});
Emitter.on('data',contentWriter=function(buffer){
res.write(buffer)
})
res.on('close', function () {
Emitter.removeListener('data',contentWriter)
})
},res,req);
},res,req);
});
//FFprobe by API
app.get('/:auth/probe/:ke',function (req,res){
req.ret={ok:false};
res.setHeader('Content-Type', 'application/json');
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
switch(req.query.action){
// case'stop':
// exec('kill -9 '+user.ffprobe.pid,{detatched: true})
// break;
default:
if(!req.query.url){
req.ret.error = 'Missing URL'
res.end(s.s(req.ret, null, 3));
return
}
if(user.ffprobe){
req.ret.error = 'Account is already probing'
res.end(s.s(req.ret, null, 3));
return
}
user.ffprobe=1;
if(req.query.flags==='default'){
req.query.flags = '-v quiet -print_format json -show_format -show_streams'
}else{
if(!req.query.flags){
req.query.flags = ''
}
}
req.probeCommand = s.splitForFFPMEG(req.query.flags+' -i '+req.query.url).join(' ')
exec('ffprobe '+req.probeCommand+' | echo ',function(err,stdout,stderr){
delete(user.ffprobe)
if(err){
req.ret.error=(err)
}else{
req.ret.ok=true
req.ret.result = stdout+stderr
}
req.ret.probe = req.probeCommand
res.end(s.s(req.ret, null, 3));
})
break;
}
},res,req);
})
//ONVIF requesting with Shinobi API structure
app.all(['/:auth/onvif/:ke/:id/:action','/:auth/onvif/:ke/:id/:service/:action'],function (req,res){
var response = {ok:false};
res.setHeader('Content-Type', 'application/json');
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
var errorMessage = function(msg,error){
response.ok = false
response.msg = msg
response.error = error
res.end(s.s(response,null,3))
}
var actionCallback = function(onvifActionResponse){
response.ok = true
if(onvifActionResponse.data){
response.responseFromDevice = onvifActionResponse.data
}else{
response.responseFromDevice = onvifActionResponse
}
if(onvifActionResponse.soap)response.soap = onvifActionResponse.soap
res.end(s.s(response,null,3))
}
var isEmpty = function(obj) {
for(var key in obj) {
if(obj.hasOwnProperty(key))
return false;
}
return true;
}
var doAction = function(Camera){
var completeAction = function(command){
if(command.then){
command.then(actionCallback).catch(function(error){
errorMessage('Device responded with an error',error)
})
}else if(command){
response.ok = true
response.repsonseFromDevice = command
res.end(s.s(response,null,3))
}else{
response.error = 'Big Errors, Please report it to Shinobi Development'
res.end(s.s(response,null,3))
}
}
var action
if(req.params.service){
if(Camera.services[req.params.service] === undefined){
return errorMessage('This is not an available service. Please use one of the following : '+Object.keys(Camera.services).join(', '))
}
if(Camera.services[req.params.service] === null){
return errorMessage('This service is not activated. Maybe you are not connected through ONVIF. You can test by attempting to use the "Control" feature with ONVIF in Shinobi.')
}
action = Camera.services[req.params.service][req.params.action]
}else{
action = Camera[req.params.action]
}
if(!action || typeof action !== 'function'){
errorMessage(req.params.action+' is not an available ONVIF function. See https://github.com/futomi/node-onvif for functions.')
}else{
var argNames = s.getFunctionParamNames(action)
var options
var command
if(argNames[0] === 'options' || argNames[0] === 'params'){
options = {}
if(req.query.options){
var jsonRevokedText = 'JSON not formated correctly'
try{
options = JSON.parse(req.query.options)
}catch(err){
return errorMessage(jsonRevokedText,err)
}
}else if(req.body.options){
try{
options = JSON.parse(req.body.options)
}catch(err){
return errorMessage(jsonRevokedText,err)
}
}else if(req.query.params){
try{
options = JSON.parse(req.query.params)
}catch(err){
return errorMessage(jsonRevokedText,err)
}
}else if(req.body.params){
try{
options = JSON.parse(req.body.params)
}catch(err){
return errorMessage(jsonRevokedText,err)
}
}
}
if(req.params.service){
command = Camera.services[req.params.service][req.params.action](options)
}else{
command = Camera[req.params.action](options)
}
completeAction(command)
}
}
if(!s.group[req.params.ke].mon[req.params.id].onvifConnection){
//prepeare onvif connection
var controlURL
var monitorConfig = s.group[req.params.ke].mon_conf[req.params.id]
if(!monitorConfig.details.control_base_url||monitorConfig.details.control_base_url===''){
controlURL = s.init('url_no_path',monitorConfig)
}else{
controlURL = monitorConfig.details.control_base_url
}
var controlURLOptions = s.camera('buildOptionsFromUrl',controlURL,monitorConfig)
//create onvif connection
s.group[req.params.ke].mon[req.params.id].onvifConnection = new onvif.OnvifDevice({
xaddr : 'http://' + controlURLOptions.host + ':' + controlURLOptions.port + '/onvif/device_service',
user : controlURLOptions.username,
pass : controlURLOptions.password
})
var device = s.group[req.params.ke].mon[req.params.id].onvifConnection
device.init().then((info) => {
if(info)doAction(device)
}).catch(function(error){
return errorMessage('Device responded with an error',error)
})
}else{
doAction(s.group[req.params.ke].mon[req.params.id].onvifConnection)
}
},res,req);
})
s.cpuUsage=function(e){
k={}
switch(s.platform){
case'win32':
k.cmd="@for /f \"skip=1\" %p in ('wmic cpu get loadpercentage') do @echo %p%"
break;
case'darwin':
k.cmd="ps -A -o %cpu | awk '{s+=$1} END {print s}'";
break;
case'linux':
k.cmd='LANG=C top -b -n 2 | grep "^'+config.cpuUsageMarker+'" | awk \'{print $2}\' | tail -n1';
break;
}
if(config.customCpuCommand){
exec(config.customCpuCommand,{encoding:'utf8',detached: true},function(err,d){
if(s.isWin===true) {
d = d.replace(/(\r\n|\n|\r)/gm, "").replace(/%/g, "")
}
e(d)
});
} else if(k.cmd){
exec(k.cmd,{encoding:'utf8',detached: true},function(err,d){
if(s.isWin===true){
d=d.replace(/(\r\n|\n|\r)/gm,"").replace(/%/g,"")
}
e(d)
});
} else{
e(0)
}
}
s.ramUsage=function(e){
k={}
switch(s.platform){
case'win32':
k.cmd = "wmic OS get FreePhysicalMemory /Value"
break;
case'darwin':
k.cmd = "vm_stat | awk '/^Pages free: /{f=substr($3,1,length($3)-1)} /^Pages active: /{a=substr($3,1,length($3-1))} /^Pages inactive: /{i=substr($3,1,length($3-1))} /^Pages speculative: /{s=substr($3,1,length($3-1))} /^Pages wired down: /{w=substr($4,1,length($4-1))} /^Pages occupied by compressor: /{c=substr($5,1,length($5-1)); print ((a+w)/(f+a+i+w+s+c))*100;}'"
break;
default:
k.cmd = "LANG=C free | grep Mem | awk '{print $4/$2 * 100.0}'";
break;
}
if(k.cmd){
exec(k.cmd,{encoding:'utf8',detached: true},function(err,d){
if(s.isWin===true){
d=(parseInt(d.split('=')[1])/(s.totalmem/1000))*100
}
e(d)
});
}else{
e(0)
}
}
//check disk space every 20 minutes
if(config.autoDropCache===true){
setInterval(function(){
exec('echo 3 > /proc/sys/vm/drop_caches',{detached: true})
},60000*20);
}
s.beat=function(){
setTimeout(s.beat, 8000);
io.sockets.emit('ping',{beat:1});
}
s.beat();
s.processReady = function(){
s.systemLog(lang.startUpText5)
process.send('ready')
}
//setup Master for childNodes
if(config.childNodes.enabled === true && config.childNodes.mode === 'master'){
s.childNodes = {};
var childNodeHTTP = express();
var childNodeServer = http.createServer(app);
var childNodeWebsocket = new (require('socket.io'))()
childNodeServer.listen(config.childNodes.port,config.bindip,function(){
console.log(lang.Shinobi+' - CHILD NODE PORT : '+config.childNodes.port);
});
childNodeWebsocket.attach(childNodeServer);
//send data to child node function (experimental)
s.cx = function(z,y,x){if(!z.mid && !z.d){
var err = new Error();
console.log(err.stack);
};if(x){return x.broadcast.to(y).emit('c',z)};childNodeWebsocket.to(y).emit('c',z);}
//child Node Websocket
childNodeWebsocket.on('connection', function (cn) {
//functions for dispersing work to child servers;
cn.on('c',function(d){
if(config.childNodes.key.indexOf(d.socketKey) > -1){
if(!cn.shinobi_child&&d.f=='init'){
cn.ip = cn.request.connection.remoteAddress.replace('::ffff:','')+':'+d.port
cn.shinobi_child = 1
tx = function(z){
cn.emit('c',z)
}
if(!s.childNodes[cn.ip]){
s.childNodes[cn.ip] = {}
};
s.childNodes[cn.ip].cnid = cn.id
s.childNodes[cn.ip].cpu = 0
s.childNodes[cn.ip].activeCameras = {}
tx({
f : 'init_success',
childNodes : s.childNodes
});
}else{
switch(d.f){
case'cpu':
s.childNodes[cn.ip].cpu = d.cpu;
break;
case'sql':
s.sqlQuery(d.query,d.values,function(err,rows){
cn.emit('c',{f:'sqlCallback',rows:rows,err:err,callbackId:d.callbackId});
});
break;
case'camera':
s.camera(d.mode,d.data)
break;
case's.tx':
s.tx(d.data,d.to)
break;
case's.log':
if(!d.mon || !d.data)return console.log('LOG DROPPED',d.mon,d.data);
s.log(d.mon,d.data)
break;
case'created_file_chunk':
if(!s.group[d.ke].mon[d.mid].childNodeStreamWriters[d.filename]){
d.dir = s.video('getDir',s.group[d.ke].mon_conf[d.mid])
s.group[d.ke].mon[d.mid].childNodeStreamWriters[d.filename] = fs.createWriteStream(d.dir+d.filename)
}
s.group[d.ke].mon[d.mid].childNodeStreamWriters[d.filename].write(d.chunk)
break;
case'created_file':
if(!s.group[d.ke].mon[d.mid].childNodeStreamWriters[d.filename]){
return console.log('FILE NOT EXIST')
}
s.group[d.ke].mon[d.mid].childNodeStreamWriters[d.filename].end();
tx({
f:'delete',
file:d.filename,
ke:d.ke,
mid:d.mid
});
s.txWithSubPermissions({
f:'video_build_success',
hrefNoAuth:'/videos/'+d.ke+'/'+d.mid+'/'+d.filename,
filename:d.filename,
mid:d.mid,
ke:d.ke,
time:d.startTime,
size:d.filesize,
end:d.endTime
},'GRP_'+d.ke,'video_view');
clearTimeout(s.group[d.ke].mon[d.mid].checker)
clearTimeout(s.group[d.ke].mon[d.mid].checkStream)
break;
}
}
}
})
cn.on('disconnect',function(){
if(s.childNodes[cn.ip]){
var activeCameraKeys = Object.keys(s.childNodes[cn.ip].activeCameras)
activeCameraKeys.forEach(function(key){
var monitor = s.childNodes[cn.ip].activeCameras[key]
s.camera('stop',s.init('noReference',monitor))
delete(s.group[monitor.ke].mon[monitor.mid].childNode)
delete(s.group[monitor.ke].mon[monitor.mid].childNodeId)
setTimeout(function(){
s.camera(monitor.mode,s.init('noReference',monitor))
},1300)
})
delete(s.childNodes[cn.ip]);
}
})
})
}else
//setup Child for childNodes
if(config.childNodes.enabled === true && config.childNodes.mode === 'child' && config.childNodes.host){
s.connected = false;
childIO = require('socket.io-client')('ws://'+config.childNodes.host);
s.cx = function(x){x.socketKey = config.childNodes.key;childIO.emit('c',x)}
s.tx = function(x,y){s.cx({f:'s.tx',data:x,to:y})}
s.log = function(x,y){s.cx({f:'s.log',mon:x,data:y})}
s.queuedSqlCallbacks = {}
s.sqlQuery = function(query,values,onMoveOn){
var callbackId = s.gid()
if(!values){values=[]}
if(typeof values === 'function'){
var onMoveOn = values;
var values = [];
}
if(typeof onMoveOn !== 'function'){onMoveOn=function(){}}
s.queuedSqlCallbacks[callbackId] = onMoveOn
s.cx({f:'sql',query:query,values:values,callbackId:callbackId});
}
setInterval(function(){
s.cpuUsage(function(cpu){
io.emit('c',{f:'cpu',cpu:parseFloat(cpu)});
})
},2000);
childIO.on('connect', function(d){
console.log('CHILD CONNECTION SUCCESS')
s.cx({
f : 'init',
port : config.port
})
})
childIO.on('c', function (d) {
switch(d.f){
case'sqlCallback':
if(s.queuedSqlCallbacks[d.callbackId]){
s.queuedSqlCallbacks[d.callbackId](d.err,d.rows)
delete(s.queuedSqlCallbacks[d.callbackId])
}
break;
case'init_success':
s.connected=true;
s.other_helpers=d.child_helpers;
break;
case'kill':
s.init(0,d.d);
s.kill(s.group[d.d.ke].mon[d.d.id].spawn,d.d)
break;
case'sync':
s.init(0,d.sync);
Object.keys(d.sync).forEach(function(v){
s.group[d.sync.ke].mon[d.sync.mid][v]=d.sync[v];
});
break;
case'delete'://delete video
s.file('delete',s.dir.videos+d.ke+'/'+d.mid+'/'+d.file)
break;
case'insertCompleted'://close video
s.video('insertCompleted',d.d,d.k)
break;
case'cameraStop'://start camera
s.camera('stop',d.d)
break;
case'cameraStart'://start or record camera
s.camera(d.mode,d.d)
break;
}
})
childIO.on('disconnect',function(d){
s.connected = false;
})
}
if(config.childNodes.mode === 'child'){
//child node - startup functions
// fs.readdir(s.dir.videos, function(err,groupKeys) {
// groupKeys.forEach(function(groupKey){
// fs.readdir(s.dir.videos+groupKey, function(err,monitorIds) {
// monitorIds.forEach(function(monitorId){
// fs.readdir(s.dir.videos+groupKey+'/'+monitorId, function(err,files) {
// files.forEach(function(file){
// if(/T[0-9][0-9]-[0-9][0-9]-[0-9][0-9]./.test(file)){
// var filePath = s.dir.videos+groupKey+'/'+monitorId+'/'+file
// var stat = fs.statSync(filePath)
// var filesize = stat.size
// var filesizeMB = parseFloat((filesize/1000000).toFixed(2))
// var startTime = s.nameToTime(file)
// var endTime = s.formattedTime(stat.mtime,'YYYY-MM-DD HH:mm:ss')
// fs.createReadStream(filePath)
// .on('data',function(data){
// s.cx({
// f:'created_file_chunk',
// mid:monitorId,
// ke:groupKey,
// chunk:data,
// filename:file,
// filesize:filesize,
// time:s.timeObject(startTime).format(),
// end:s.timeObject(endTime).format()
// })
// })
// .on('close',function(){
// s.cx({
// f:'created_file',
// mid:monitorId,
// ke:groupKey,
// filename:file,
// filesize:filesize,
// time:s.timeObject(startTime).format(),
// end:s.timeObject(endTime).format()
// })
// })
// .on('error',function(){
// console.log('File Read Error',file)
// });
// }else{
// console.log('Not Video',file)
// }
// })
// })
// })
// })
// })
// })
}else{
//master node - startup functions
setInterval(function(){
s.cpuUsage(function(cpu){
s.ramUsage(function(ram){
s.tx({f:'os',cpu:cpu,ram:ram},'CPU');
})
})
},10000);
setTimeout(function(){
//get current disk used for each isolated account (admin user) on startup
s.sqlQuery('SELECT * FROM Users WHERE details NOT LIKE ?',['%"sub"%'],function(err,r){
if(r&&r[0]){
var count = r.length
var countFinished = 0
r.forEach(function(v,n){
v.size=0;
v.limit=JSON.parse(v.details).size
s.sqlQuery('SELECT * FROM Videos WHERE ke=? AND status!=?',[v.ke,0],function(err,rr){
++countFinished
if(r&&r[0]){
rr.forEach(function(b){
v.size+=b.size
})
}
s.systemLog(v.mail+' : '+lang.startUpText0+' : '+rr.length,v.size)
s.init('group',v)
s.init('apps',v)
s.systemLog(v.mail+' : '+lang.startUpText1,countFinished+'/'+count)
if(countFinished===count){
s.systemLog(lang.startUpText4)
//preliminary monitor start
s.sqlQuery('SELECT * FROM Monitors', function(err,r) {
if(err){s.systemLog(err)}
if(r&&r[0]){
r.forEach(function(v){
s.init(0,v);
r.ar={};
r.ar.id=v.mid;
Object.keys(v).forEach(function(b){
r.ar[b]=v[b];
})
if(!s.group[v.ke]){
s.group[v.ke]={}
s.group[v.ke].mon_conf={}
}
v.details=JSON.parse(v.details);
s.group[v.ke].mon_conf[v.mid]=v;
s.camera(v.mode,r.ar);
});
}
s.processReady()
});
}
})
})
}else{
s.processReady()
}
})
},1500)
}