mirror of
https://gitlab.com/Shinobi-Systems/ShinobiCE.git
synced 2025-03-09 15:40:15 +00:00
Blue Turtle - The Refactoring
This commit is contained in:
parent
04011678fb
commit
b7d08eb500
67 changed files with 11651 additions and 8452 deletions
191
libs/auth.js
Normal file
191
libs/auth.js
Normal file
|
@ -0,0 +1,191 @@
|
|||
var fs = require('fs');
|
||||
module.exports = function(s,config,lang){
|
||||
//Authenticator functions
|
||||
s.api = {}
|
||||
s.superUsersApi = {}
|
||||
s.factorAuth = {}
|
||||
s.failedLoginAttempts = {}
|
||||
//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));
|
||||
}
|
||||
}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&¶ms.username!==''&¶ms.password&¶ms.password!==''){
|
||||
s.sqlQuery('SELECT * FROM Users WHERE mail=? AND (pass=? OR pass=?)',[params.username,params.password,s.createHash(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 mail,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(params,callback,res,req){
|
||||
var userFound = false
|
||||
var userSelected = false
|
||||
var adminUsersSelected = null
|
||||
try{
|
||||
var success = function(){
|
||||
if(req && res){
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
var ip = req.headers['cf-connecting-ip']||req.headers["CF-Connecting-IP"]||req.headers["'x-forwarded-for"]||req.connection.remoteAddress;
|
||||
var resp = {
|
||||
ok: userFound,
|
||||
ip: ip
|
||||
}
|
||||
if(userFound === false){
|
||||
resp.msg = lang['Not Authorized']
|
||||
res.end(s.prettyPrint(resp))
|
||||
}
|
||||
if(userSelected){
|
||||
resp.$user = userSelected
|
||||
}
|
||||
if(adminUsersSelected){
|
||||
resp.users = adminUsersSelected
|
||||
}
|
||||
}
|
||||
callback({
|
||||
ip : ip,
|
||||
$user:userSelected,
|
||||
users:adminUsersSelected,
|
||||
config:config,
|
||||
lang:lang
|
||||
})
|
||||
}
|
||||
var foundUser = function(){
|
||||
if(params.users === true){
|
||||
s.sqlQuery('SELECT * FROM Users WHERE details NOT LIKE ?',['%"sub"%'],function(err,r) {
|
||||
adminUsersSelected = r
|
||||
success()
|
||||
})
|
||||
}else{
|
||||
success()
|
||||
}
|
||||
}
|
||||
if(params.auth && s.superUsersApi[params.auth]){
|
||||
userFound = true
|
||||
userSelected = s.superUsersApi[params.auth].$user
|
||||
foundUser()
|
||||
}else{
|
||||
var superUserList = JSON.parse(fs.readFileSync(s.location.super))
|
||||
superUserList.forEach(function(superUser,n){
|
||||
if(
|
||||
userFound === false &&
|
||||
(
|
||||
params.auth && superUser.tokens && superUser.tokens[params.auth] || //using API key (object)
|
||||
params.auth && superUser.tokens && superUser.tokens.indexOf && superUser.tokens.indexOf(params.auth) > -1 || //using API key (array)
|
||||
(
|
||||
params.mail && params.mail.toLowerCase() === superUser.mail.toLowerCase() && //email matches
|
||||
(
|
||||
params.pass === superUser.pass || //user give it already hashed
|
||||
superUser.pass === s.createHash(params.pass) || //hash and check it
|
||||
superUser.pass.toLowerCase() === s.md5(params.pass).toLowerCase() //check if still using md5
|
||||
)
|
||||
)
|
||||
)
|
||||
){
|
||||
userFound = true
|
||||
userSelected = superUser
|
||||
foundUser()
|
||||
}
|
||||
})
|
||||
}
|
||||
}catch(err){
|
||||
console.log('The following error may mean your super.json is not formatted correctly.')
|
||||
console.log(err)
|
||||
}
|
||||
if(userFound === true){
|
||||
return true
|
||||
}else{
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
227
libs/basic.js
Normal file
227
libs/basic.js
Normal file
|
@ -0,0 +1,227 @@
|
|||
var moment = require('moment');
|
||||
var crypto = require('crypto');
|
||||
var exec = require('child_process').exec;
|
||||
var spawn = require('child_process').spawn;
|
||||
var events = require('events');
|
||||
var http = require('http');
|
||||
var https = require('https');
|
||||
module.exports = function(s,config){
|
||||
//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=s.mainDirectory+'/'+x
|
||||
}
|
||||
return x
|
||||
}
|
||||
s.checkDetails = function(e){
|
||||
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){}
|
||||
}
|
||||
}
|
||||
s.parseJSON = function(string){
|
||||
try{
|
||||
string = JSON.parse(string)
|
||||
}catch(err){
|
||||
|
||||
}
|
||||
return string
|
||||
}
|
||||
s.stringJSON = function(json){
|
||||
try{
|
||||
if(json instanceof Object){
|
||||
json = JSON.stringify(json)
|
||||
}
|
||||
}catch(err){
|
||||
|
||||
}
|
||||
return json
|
||||
}
|
||||
s.addUserPassToUrl = function(url,user,pass){
|
||||
var splitted = url.split('://')
|
||||
splitted[1] = user + ':' + pass + '@' + splitted[1]
|
||||
return splitted.join('://')
|
||||
}
|
||||
s.checkCorrectPathEnding = function(x){
|
||||
var length=x.length
|
||||
if(x.charAt(length-1)!=='/'){
|
||||
x=x+'/'
|
||||
}
|
||||
return x.replace('__DIR__',s.mainDirectory)
|
||||
}
|
||||
s.md5 = function(x){return crypto.createHash('md5').update(x).digest("hex")}
|
||||
s.createHash = s.md5
|
||||
switch(config.passwordType){
|
||||
case'sha512':
|
||||
if(config.passwordSalt){
|
||||
s.createHash = function(x){return crypto.pbkdf2Sync(x, config.passwordSalt, 100000, 64, 'sha512').toString('hex')}
|
||||
}
|
||||
break;
|
||||
case'sha256':
|
||||
s.createHash = function(x){return crypto.createHash('sha256').update(x).digest("hex")}
|
||||
break;
|
||||
}
|
||||
//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
|
||||
}
|
||||
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.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);
|
||||
});
|
||||
}
|
||||
//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(config.debugLog === true){
|
||||
if(!w){w = ''}
|
||||
if(!e){e = ''}
|
||||
console.log(s.timeObject().format(),q,w,e)
|
||||
if(config.debugLogVerbose === true){
|
||||
console.log(new Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
s.getOriginalUrl = function(req){
|
||||
var url
|
||||
if(config.baseURL || config.baseURL === ''){
|
||||
url = config.baseURL
|
||||
}else{
|
||||
url = req.protocol + '://' + req.get('host') + '/'
|
||||
}
|
||||
return url
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
217
libs/childNode.js
Normal file
217
libs/childNode.js
Normal file
|
@ -0,0 +1,217 @@
|
|||
var fs = require('fs');
|
||||
var http = require('http');
|
||||
var https = require('https');
|
||||
var express = require('express');
|
||||
module.exports = function(s,config,lang,app,io){
|
||||
//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);
|
||||
});
|
||||
s.debugLog('childNodeWebsocket.attach(childNodeServer)')
|
||||
childNodeWebsocket.attach(childNodeServer);
|
||||
//send data to child node function (experimental)
|
||||
s.cx = function(z,y,x){
|
||||
if(!z.mid && !z.d){
|
||||
console.error('Missing ID')
|
||||
}else if(x){
|
||||
x.broadcast.to(y).emit('c',z)
|
||||
}else{
|
||||
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 = {}
|
||||
d.availableHWAccels.forEach(function(accel){
|
||||
if(config.availableHWAccels.indexOf(accel) === -1)config.availableHWAccels.push(accel)
|
||||
})
|
||||
tx({
|
||||
f : 'init_success',
|
||||
childNodes : s.childNodes
|
||||
})
|
||||
s.childNodes[cn.ip].coreCount = d.coreCount
|
||||
}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.userLog':
|
||||
if(!d.mon || !d.data)return console.log('LOG DROPPED',d.mon,d.data);
|
||||
s.userLog(d.mon,d.data)
|
||||
break;
|
||||
case'created_file_chunk':
|
||||
if(!s.group[d.ke].mon[d.mid].childNodeStreamWriters[d.filename]){
|
||||
d.dir = s.getVideoDirectory(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.time,
|
||||
size:d.filesize,
|
||||
end:d.end
|
||||
},'GRP_'+d.ke,'video_view')
|
||||
//save database row
|
||||
var insert = {
|
||||
startTime : d.time,
|
||||
filesize : d.filesize,
|
||||
endTime : d.end,
|
||||
dir : s.getVideoDirectory(d.d),
|
||||
file : d.filename,
|
||||
filename : d.filename,
|
||||
filesizeMB : parseFloat((d.filesize/1000000).toFixed(2))
|
||||
}
|
||||
s.insertDatabaseRow(d.d,insert)
|
||||
s.insertCompletedVideoExtensions.forEach(function(extender){
|
||||
extender(d.d,insert)
|
||||
})
|
||||
//purge over max
|
||||
s.purgeDiskForGroup(d)
|
||||
//send new diskUsage values
|
||||
s.setDiskUsedForGroup(d,insert.filesizeMB)
|
||||
clearTimeout(s.group[d.ke].mon[d.mid].recordingChecker)
|
||||
clearTimeout(s.group[d.ke].mon[d.mid].streamChecker)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
cn.on('disconnect',function(){
|
||||
console.log('childNodeWebsocket.disconnect')
|
||||
|
||||
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.cleanMonitorObject(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.cleanMonitorObject(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.userLog = function(x,y){s.cx({f:'s.userLog',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,
|
||||
coreCount : s.coreCount,
|
||||
availableHWAccels : config.availableHWAccels
|
||||
})
|
||||
})
|
||||
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.initiateMonitorObject(d.d);
|
||||
s.cameraDestroy(s.group[d.d.ke].mon[d.d.id].spawn,d.d)
|
||||
break;
|
||||
case'sync':
|
||||
s.initiateMonitorObject(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.insertCompletedVideo(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;
|
||||
})
|
||||
}
|
||||
}
|
555
libs/cloudUploaders.js
Normal file
555
libs/cloudUploaders.js
Normal file
|
@ -0,0 +1,555 @@
|
|||
var fs = require('fs');
|
||||
var exec = require('child_process').exec;
|
||||
var spawn = require('child_process').spawn;
|
||||
var webdav = require("webdav-fs");
|
||||
module.exports = function(s,config,lang){
|
||||
// WebDAV
|
||||
var beforeAccountSaveForWebDav = function(d){
|
||||
//d = save event
|
||||
d.form.details.use_webdav=d.d.use_webdav
|
||||
}
|
||||
var cloudDiskUseStartupForWebDav = function(group,userDetails){
|
||||
group.cloudDiskUse['webdav'].name = 'WebDAV'
|
||||
group.cloudDiskUse['webdav'].sizeLimitCheck = (userDetails.use_webdav_size_limit === '1')
|
||||
if(!userDetails.webdav_size_limit || userDetails.webdav_size_limit === ''){
|
||||
group.cloudDiskUse['webdav'].sizeLimit = 10000
|
||||
}else{
|
||||
group.cloudDiskUse['webdav'].sizeLimit = parseFloat(userDetails.webdav_size_limit)
|
||||
}
|
||||
}
|
||||
var loadWebDavForUser = function(e){
|
||||
// e = user
|
||||
var ar = JSON.parse(e.details);
|
||||
//owncloud/webdav
|
||||
if(!s.group[e.ke].webdav &&
|
||||
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='/'
|
||||
}
|
||||
ar.webdav_dir = s.checkCorrectPathEnding(ar.webdav_dir)
|
||||
s.group[e.ke].webdav = webdav(
|
||||
ar.webdav_url,
|
||||
ar.webdav_user,
|
||||
ar.webdav_pass
|
||||
)
|
||||
}
|
||||
}
|
||||
var unloadWebDavForUser = function(user){
|
||||
s.group[user.ke].webdav = null
|
||||
}
|
||||
var deleteVideoFromWebDav = function(e,video,callback){
|
||||
// e = user
|
||||
try{
|
||||
var videoDetails = JSON.parse(video.details)
|
||||
}catch(err){
|
||||
var videoDetails = video.details
|
||||
}
|
||||
if(!videoDetails.location){
|
||||
var prefix = s.addUserPassToUrl(s.checkCorrectPathEnding(s.group[e.ke].init.webdav_url),s.group[e.ke].init.webdav_user,s.group[e.ke].init.webdav_pass)
|
||||
videoDetails.location = video.href.replace(prefix,'')
|
||||
}
|
||||
s.group[e.ke].webdav.unlink(videoDetails.location, function(err) {
|
||||
if (err) console.log(videoDetails.location,err)
|
||||
callback()
|
||||
})
|
||||
}
|
||||
var uploadVideoToWebDav = function(e,k){
|
||||
//e = video object
|
||||
//k = temporary values
|
||||
if(!k)k={};
|
||||
//cloud saver - webdav
|
||||
var wfs = s.group[e.ke].webdav
|
||||
if(wfs && s.group[e.ke].init.use_webdav !== '0' && s.group[e.ke].init.webdav_save === "1"){
|
||||
var webdavUploadDir = s.group[e.ke].init.webdav_dir+e.ke+'/'+e.mid+'/'
|
||||
var startWebDavUpload = function(){
|
||||
s.group[e.ke].mon[e.id].webdavDirExist = true
|
||||
var wfsWriteStream =
|
||||
fs.createReadStream(k.dir + k.filename).pipe(wfs.createWriteStream(webdavUploadDir + k.filename))
|
||||
if(s.group[e.ke].init.webdav_log === '1'){
|
||||
var webdavRemoteUrl = s.addUserPassToUrl(s.checkCorrectPathEnding(s.group[e.ke].init.webdav_url),s.group[e.ke].init.webdav_user,s.group[e.ke].init.webdav_pass) + s.group[e.ke].init.webdav_dir + e.ke + '/'+e.mid+'/'+k.filename
|
||||
var save = [
|
||||
e.mid,
|
||||
e.ke,
|
||||
k.startTime,
|
||||
1,
|
||||
s.s({
|
||||
type : 'webdav',
|
||||
location : webdavUploadDir + k.filename
|
||||
}),
|
||||
k.filesize,
|
||||
k.endTime,
|
||||
webdavRemoteUrl
|
||||
]
|
||||
s.sqlQuery('INSERT INTO `Cloud Videos` (mid,ke,time,status,details,size,end,href) VALUES (?,?,?,?,?,?,?,?)',save)
|
||||
s.setCloudDiskUsedForGroup(e,{
|
||||
amount : k.filesizeMB,
|
||||
storageType : 'webdav'
|
||||
})
|
||||
s.purgeCloudDiskForGroup(e,'webdav')
|
||||
}
|
||||
}
|
||||
if(s.group[e.ke].mon[e.id].webdavDirExist !== true){
|
||||
//check if webdav dir exist
|
||||
var parentPoint = 0
|
||||
var webDavParentz = webdavUploadDir.split('/')
|
||||
var webDavParents = []
|
||||
webDavParentz.forEach(function(v){
|
||||
if(v && v !== '')webDavParents.push(v)
|
||||
})
|
||||
var stitchPieces = './'
|
||||
var lastParentCheck = function(){
|
||||
++parentPoint
|
||||
if(parentPoint === webDavParents.length){
|
||||
startWebDavUpload()
|
||||
}
|
||||
checkPathPiece(webDavParents[parentPoint])
|
||||
}
|
||||
var checkPathPiece = function(pathPiece){
|
||||
if(pathPiece && pathPiece !== ''){
|
||||
stitchPieces += pathPiece + '/'
|
||||
wfs.stat(stitchPieces, function(error, stats) {
|
||||
if(error){
|
||||
reply = {
|
||||
status : error.status,
|
||||
msg : lang.WebdavErrorTextTryCreatingDir,
|
||||
dir : stitchPieces,
|
||||
}
|
||||
s.userLog(e,{type:lang['Webdav Error'],msg:reply})
|
||||
wfs.mkdir(stitchPieces, function(error) {
|
||||
if(error){
|
||||
reply = {
|
||||
status : error.status,
|
||||
msg : lang.WebdavErrorTextCreatingDir,
|
||||
dir : stitchPieces,
|
||||
}
|
||||
s.userLog(e,{type:lang['Webdav Error'],msg:reply})
|
||||
}else{
|
||||
lastParentCheck()
|
||||
}
|
||||
})
|
||||
}else{
|
||||
lastParentCheck()
|
||||
}
|
||||
})
|
||||
}else{
|
||||
++parentPoint
|
||||
}
|
||||
}
|
||||
checkPathPiece(webDavParents[0])
|
||||
}else{
|
||||
startWebDavUpload()
|
||||
}
|
||||
}
|
||||
}
|
||||
//Amazon S3
|
||||
var beforeAccountSaveForAmazonS3 = function(d){
|
||||
//d = save event
|
||||
d.form.details.use_aws_s3=d.d.use_aws_s3
|
||||
}
|
||||
var cloudDiskUseStartupForAmazonS3 = function(group,userDetails){
|
||||
group.cloudDiskUse['s3'].name = 'Amazon S3'
|
||||
group.cloudDiskUse['s3'].sizeLimitCheck = (userDetails.use_aws_s3_size_limit === '1')
|
||||
if(!userDetails.aws_s3_size_limit || userDetails.aws_s3_size_limit === ''){
|
||||
group.cloudDiskUse['s3'].sizeLimit = 10000
|
||||
}else{
|
||||
group.cloudDiskUse['s3'].sizeLimit = parseFloat(userDetails.aws_s3_size_limit)
|
||||
}
|
||||
}
|
||||
var loadAmazonS3ForUser = function(e){
|
||||
// e = user
|
||||
var ar = JSON.parse(e.details);
|
||||
//Amazon S3
|
||||
if(!s.group[e.ke].aws &&
|
||||
!s.group[e.ke].aws_s3 &&
|
||||
ar.aws_s3 !== '0' &&
|
||||
ar.aws_accessKeyId !== ''&&
|
||||
ar.aws_secretAccessKey &&
|
||||
ar.aws_secretAccessKey !== ''&&
|
||||
ar.aws_region &&
|
||||
ar.aws_region !== ''&&
|
||||
ar.aws_s3_bucket !== ''
|
||||
){
|
||||
if(!ar.aws_s3_dir || ar.aws_s3_dir === '/'){
|
||||
ar.aws_s3_dir = ''
|
||||
}
|
||||
if(ar.aws_s3_dir !== ''){
|
||||
ar.aws_s3_dir = s.checkCorrectPathEnding(ar.aws_s3_dir)
|
||||
}
|
||||
s.group[e.ke].aws = new require("aws-sdk")
|
||||
s.group[e.ke].aws.config = new s.group[e.ke].aws.Config({
|
||||
accessKeyId: ar.aws_accessKeyId,
|
||||
secretAccessKey: ar.aws_secretAccessKey,
|
||||
region: ar.aws_region
|
||||
})
|
||||
s.group[e.ke].aws_s3 = new s.group[e.ke].aws.S3();
|
||||
}
|
||||
}
|
||||
var unloadAmazonS3ForUser = function(user){
|
||||
s.group[user.ke].aws = null
|
||||
s.group[user.ke].aws_s3 = null
|
||||
}
|
||||
var deleteVideoFromAmazonS3 = function(e,video,callback){
|
||||
// e = user
|
||||
try{
|
||||
var videoDetails = JSON.parse(video.details)
|
||||
}catch(err){
|
||||
var videoDetails = video.details
|
||||
}
|
||||
if(!videoDetails.location){
|
||||
videoDetails.location = video.href.split('.amazonaws.com')[1]
|
||||
}
|
||||
s.group[e.ke].aws_s3.deleteObject({
|
||||
Bucket: s.group[e.ke].init.aws_s3_bucket,
|
||||
Key: videoDetails.location,
|
||||
}, function(err, data) {
|
||||
if (err) console.log(err);
|
||||
callback()
|
||||
});
|
||||
}
|
||||
var uploadVideoToAmazonS3 = function(e,k){
|
||||
//e = video object
|
||||
//k = temporary values
|
||||
if(!k)k={};
|
||||
//cloud saver - amazon s3
|
||||
if(s.group[e.ke].aws_s3 && s.group[e.ke].init.use_aws_s3 !== '0' && s.group[e.ke].init.aws_s3_save === '1'){
|
||||
var ext = k.filename.split('.')
|
||||
ext = ext[ext.length - 1]
|
||||
var fileStream = fs.createReadStream(k.dir+k.filename);
|
||||
fileStream.on('error', function (err) {
|
||||
console.error(err)
|
||||
})
|
||||
var saveLocation = s.group[e.ke].init.aws_s3_dir+e.ke+'/'+e.mid+'/'+k.filename
|
||||
s.group[e.ke].aws_s3.upload({
|
||||
Bucket: s.group[e.ke].init.aws_s3_bucket,
|
||||
Key: saveLocation,
|
||||
Body:fileStream,
|
||||
ACL:'public-read',
|
||||
ContentType:'video/'+ext
|
||||
},function(err,data){
|
||||
if(err){
|
||||
s.userLog(e,{type:lang['Amazon S3 Upload Error'],msg:err})
|
||||
}
|
||||
if(s.group[e.ke].init.aws_s3_log === '1' && data && data.Location){
|
||||
var save = [
|
||||
e.mid,
|
||||
e.ke,
|
||||
k.startTime,
|
||||
1,
|
||||
s.s({
|
||||
type : 's3',
|
||||
location : saveLocation
|
||||
}),
|
||||
k.filesize,
|
||||
k.endTime,
|
||||
data.Location
|
||||
]
|
||||
s.sqlQuery('INSERT INTO `Cloud Videos` (mid,ke,time,status,details,size,end,href) VALUES (?,?,?,?,?,?,?,?)',save)
|
||||
s.setCloudDiskUsedForGroup(e,{
|
||||
amount : k.filesizeMB,
|
||||
storageType : 's3'
|
||||
})
|
||||
s.purgeCloudDiskForGroup(e,'s3')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
//Backblaze B2
|
||||
var beforeAccountSaveForBackblazeB2 = function(d){
|
||||
//d = save event
|
||||
d.form.details.use_aws_s3=d.d.use_bb_b2
|
||||
}
|
||||
var cloudDiskUseStartupForBackblazeB2 = function(group,userDetails){
|
||||
group.cloudDiskUse['b2'].name = 'Backblaze B2'
|
||||
group.cloudDiskUse['b2'].sizeLimitCheck = (userDetails.use_bb_b2_size_limit === '1')
|
||||
if(!userDetails.bb_b2_size_limit || userDetails.bb_b2_size_limit === ''){
|
||||
group.cloudDiskUse['b2'].sizeLimit = 10000
|
||||
}else{
|
||||
group.cloudDiskUse['b2'].sizeLimit = parseFloat(userDetails.bb_b2_size_limit)
|
||||
}
|
||||
}
|
||||
var loadBackblazeB2ForUser = function(e){
|
||||
var ar = JSON.parse(e.details);
|
||||
try{
|
||||
if(!s.group[e.ke].bb_b2 &&
|
||||
ar.bb_b2_accountId &&
|
||||
ar.bb_b2_accountId !=='' &&
|
||||
ar.bb_b2_applicationKey &&
|
||||
ar.bb_b2_applicationKey !=='' &&
|
||||
ar.bb_b2_bucket &&
|
||||
ar.bb_b2_bucket !== ''
|
||||
){
|
||||
var B2 = require('backblaze-b2')
|
||||
if(!ar.bb_b2_dir || ar.bb_b2_dir === '/'){
|
||||
ar.bb_b2_dir = ''
|
||||
}
|
||||
if(ar.bb_b2_dir !== ''){
|
||||
ar.bb_b2_dir = s.checkCorrectPathEnding(ar.bb_b2_dir)
|
||||
}
|
||||
var b2 = new B2({
|
||||
accountId: ar.bb_b2_accountId,
|
||||
applicationKey: ar.bb_b2_applicationKey
|
||||
})
|
||||
s.group[e.ke].bb_b2 = b2
|
||||
var backblazeErr = function(err){
|
||||
// console.log(err)
|
||||
s.userLog({mid:'$USER',ke:e.ke},{type:lang['Backblaze Error'],msg:err.data})
|
||||
}
|
||||
b2.authorize().then(function(resp){
|
||||
s.group[e.ke].bb_b2_downloadUrl = resp.data.downloadUrl
|
||||
b2.listBuckets().then(function(resp){
|
||||
var buckets = resp.data.buckets
|
||||
var bucketN = -2
|
||||
buckets.forEach(function(item,n){
|
||||
if(item.bucketName === ar.bb_b2_bucket){
|
||||
bucketN = n
|
||||
}
|
||||
})
|
||||
if(bucketN > -1){
|
||||
s.group[e.ke].bb_b2_bucketId = buckets[bucketN].bucketId
|
||||
}else{
|
||||
b2.createBucket(
|
||||
ar.bb_b2_bucket,
|
||||
'allPublic'
|
||||
).then(function(resp){
|
||||
s.group[e.ke].bb_b2_bucketId = resp.data.bucketId
|
||||
}).catch(backblazeErr)
|
||||
}
|
||||
}).catch(backblazeErr)
|
||||
}).catch(backblazeErr)
|
||||
}
|
||||
}catch(err){
|
||||
s.debugLog(err)
|
||||
}
|
||||
}
|
||||
var unloadBackblazeB2ForUser = function(user){
|
||||
s.group[user.ke].bb_b2 = null
|
||||
}
|
||||
var deleteVideoFromBackblazeB2 = function(e,video,callback){
|
||||
// e = user
|
||||
try{
|
||||
var videoDetails = JSON.parse(video.details)
|
||||
}catch(err){
|
||||
var videoDetails = video.details
|
||||
}
|
||||
s.group[e.ke].bb_b2.deleteFileVersion({
|
||||
fileId: videoDetails.fileId,
|
||||
fileName: videoDetails.fileName
|
||||
}).then(function(resp){
|
||||
// console.log('deleteFileVersion',resp.data)
|
||||
}).catch(function(err){
|
||||
console.log('deleteFileVersion',err)
|
||||
})
|
||||
}
|
||||
var uploadVideoToBackblazeB2 = function(e,k){
|
||||
//e = video object
|
||||
//k = temporary values
|
||||
if(!k)k={};
|
||||
//cloud saver - Backblaze B2
|
||||
if(s.group[e.ke].bb_b2 && s.group[e.ke].init.use_bb_b2 !== '0' && s.group[e.ke].init.bb_b2_save === '1'){
|
||||
var backblazeErr = function(err){
|
||||
// console.log(err)
|
||||
s.userLog({mid:'$USER',ke:e.ke},{type:lang['Backblaze Error'],msg:err.data})
|
||||
}
|
||||
fs.readFile(k.dir+k.filename,function(err,data){
|
||||
var backblazeSavePath = s.group[e.ke].init.bb_b2_dir+e.ke+'/'+e.mid+'/'+k.filename
|
||||
var getUploadUrl = function(bucketId,callback){
|
||||
s.group[e.ke].bb_b2.getUploadUrl(bucketId).then(function(resp){
|
||||
callback(resp.data)
|
||||
}).catch(backblazeErr)
|
||||
}
|
||||
getUploadUrl(s.group[e.ke].bb_b2_bucketId,function(req){
|
||||
s.group[e.ke].bb_b2.uploadFile({
|
||||
uploadUrl: req.uploadUrl,
|
||||
uploadAuthToken: req.authorizationToken,
|
||||
filename: backblazeSavePath,
|
||||
data: data,
|
||||
onUploadProgress: null
|
||||
}).then(function(resp){
|
||||
if(s.group[e.ke].init.bb_b2_log === '1' && resp.data.fileId){
|
||||
var backblazeDownloadUrl = s.group[e.ke].bb_b2_downloadUrl + '/file/' + s.group[e.ke].init.bb_b2_bucket + '/' + backblazeSavePath
|
||||
var save = [
|
||||
e.mid,
|
||||
e.ke,
|
||||
k.startTime,
|
||||
1,
|
||||
s.s({
|
||||
type : 'b2',
|
||||
bucketId : resp.data.bucketId,
|
||||
fileId : resp.data.fileId,
|
||||
fileName : resp.data.fileName
|
||||
}),
|
||||
k.filesize,
|
||||
k.endTime,
|
||||
backblazeDownloadUrl
|
||||
]
|
||||
s.sqlQuery('INSERT INTO `Cloud Videos` (mid,ke,time,status,details,size,end,href) VALUES (?,?,?,?,?,?,?,?)',save)
|
||||
s.setCloudDiskUsedForGroup(e,{
|
||||
amount : k.filesizeMB,
|
||||
storageType : 'b2'
|
||||
})
|
||||
s.purgeCloudDiskForGroup(e,'b2')
|
||||
}
|
||||
}).catch(backblazeErr)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
//SFTP
|
||||
// var beforeAccountSaveForSftp = function(d){
|
||||
// //d = save event
|
||||
// d.form.details.use_sftp = d.d.use_sftp
|
||||
// }
|
||||
// var cloudDiskUseStartupForSftp = function(group,userDetails){
|
||||
// group.cloudDiskUse['sftp'].name = 'SFTP'
|
||||
// group.cloudDiskUse['sftp'].sizeLimitCheck = (userDetails.use_aws_s3_size_limit === '1')
|
||||
// if(!userDetails.aws_s3_size_limit || userDetails.aws_s3_size_limit === ''){
|
||||
// group.cloudDiskUse['sftp'].sizeLimit = 10000
|
||||
// }else{
|
||||
// group.cloudDiskUse['sftp'].sizeLimit = parseFloat(userDetails.aws_s3_size_limit)
|
||||
// }
|
||||
// }
|
||||
// var loadSftpForUser = function(e){
|
||||
// // e = user
|
||||
// var ar = JSON.parse(e.details);
|
||||
// //SFTP
|
||||
// if(!s.group[e.ke].sftp &&
|
||||
// !s.group[e.ke].sftp &&
|
||||
// ar.sftp !== '0' &&
|
||||
// ar.sftp_accessKeyId !== ''&&
|
||||
// ar.sftp_secretAccessKey &&
|
||||
// ar.sftp_secretAccessKey !== ''&&
|
||||
// ar.sftp_region &&
|
||||
// ar.sftp_region !== ''&&
|
||||
// ar.sftp_bucket !== ''
|
||||
// ){
|
||||
// if(!ar.sftp_dir || ar.sftp_dir === '/'){
|
||||
// ar.sftp_dir = ''
|
||||
// }
|
||||
// if(ar.sftp_dir !== ''){
|
||||
// ar.sftp_dir = s.checkCorrectPathEnding(ar.sftp_dir)
|
||||
// }
|
||||
// s.group[e.ke].sftp = new s.group[e.ke].sftp.S3();
|
||||
// s.group[e.ke].sftp = new require('ssh2-sftp-client')();
|
||||
// var connectionDetails = {
|
||||
// host: ar.sftp_host,
|
||||
// port: ar.sftp_port
|
||||
// }
|
||||
// if(!ar.sftp_port)ar.sftp_port = 22
|
||||
// if(ar.sftp_username)connectionDetails.username = ar.sftp_username
|
||||
// if(ar.sftp_password)connectionDetails.password = ar.sftp_password
|
||||
// if(ar.sftp_privateKey)connectionDetails.privateKey = ar.sftp_privateKey
|
||||
// sftp.connect(connectionDetails).then(() => {
|
||||
// return sftp.list('/pathname');
|
||||
// }).then((data) => {
|
||||
// console.log(data, 'the data info');
|
||||
// }).catch((err) => {
|
||||
// console.log(err, 'catch error');
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// var unloadSftpForUser = function(user){
|
||||
// s.group[user.ke].sftp = null
|
||||
// }
|
||||
// var deleteVideoFromSftp = function(e,video,callback){
|
||||
// // e = user
|
||||
// try{
|
||||
// var videoDetails = JSON.parse(video.details)
|
||||
// }catch(err){
|
||||
// var videoDetails = video.details
|
||||
// }
|
||||
// s.group[e.ke].sftp.deleteObject({
|
||||
// Bucket: s.group[e.ke].init.sftp_bucket,
|
||||
// Key: videoDetails.location,
|
||||
// }, function(err, data) {
|
||||
// if (err) console.log(err);
|
||||
// callback()
|
||||
// });
|
||||
// }
|
||||
// var uploadVideoToSftp = function(e,k){
|
||||
// //e = video object
|
||||
// //k = temporary values
|
||||
// if(!k)k={};
|
||||
// //cloud saver - SFTP
|
||||
// if(s.group[e.ke].sftp && s.group[e.ke].init.use_sftp !== '0' && s.group[e.ke].init.sftp_save === '1'){
|
||||
// var fileStream = fs.createReadStream(k.dir+k.filename);
|
||||
// fileStream.on('error', function (err) {
|
||||
// console.error(err)
|
||||
// })
|
||||
// var saveLocation = s.group[e.ke].init.sftp_dir+e.ke+'/'+e.mid+'/'+k.filename
|
||||
// s.group[e.ke].sftp.upload({
|
||||
// Bucket: s.group[e.ke].init.sftp_bucket,
|
||||
// Key: saveLocation,
|
||||
// Body:fileStream,
|
||||
// ACL:'public-read'
|
||||
// },function(err,data){
|
||||
// if(err){
|
||||
// s.userLog(e,{type:lang['SFTP Upload Error'],msg:err})
|
||||
// }
|
||||
// if(s.group[e.ke].init.sftp_log === '1' && data && data.Location){
|
||||
// var save = [
|
||||
// e.mid,
|
||||
// e.ke,
|
||||
// k.startTime,
|
||||
// 1,
|
||||
// s.s({
|
||||
// type : 'sftp',
|
||||
// location : saveLocation
|
||||
// }),
|
||||
// k.filesize,
|
||||
// k.endTime,
|
||||
// data.Location
|
||||
// ]
|
||||
// s.sqlQuery('INSERT INTO `Cloud Videos` (mid,ke,time,status,details,size,end,href) VALUES (?,?,?,?,?,?,?,?)',save)
|
||||
// s.setCloudDiskUsedForGroup(e,{
|
||||
// amount : k.filesizeMB,
|
||||
// storageType : 'sftp'
|
||||
// })
|
||||
// s.purgeCloudDiskForGroup(e,'sftp')
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
//add the extenders
|
||||
//webdav
|
||||
s.loadGroupAppExtender(loadWebDavForUser)
|
||||
s.unloadGroupAppExtender(unloadWebDavForUser)
|
||||
s.insertCompletedVideoExtender(uploadVideoToWebDav)
|
||||
s.deleteVideoFromCloudExtensions['webdav'] = deleteVideoFromWebDav
|
||||
s.cloudDiskUseStartupExtensions['webdav'] = cloudDiskUseStartupForWebDav
|
||||
s.beforeAccountSave(beforeAccountSaveForWebDav)
|
||||
s.onAccountSave(cloudDiskUseStartupForWebDav)
|
||||
s.cloudDisksLoader('webdav')
|
||||
//amazon s3
|
||||
s.loadGroupAppExtender(loadAmazonS3ForUser)
|
||||
s.unloadGroupAppExtender(unloadAmazonS3ForUser)
|
||||
s.insertCompletedVideoExtender(uploadVideoToAmazonS3)
|
||||
s.deleteVideoFromCloudExtensions['s3'] = deleteVideoFromAmazonS3
|
||||
s.cloudDiskUseStartupExtensions['s3'] = cloudDiskUseStartupForAmazonS3
|
||||
s.beforeAccountSave(beforeAccountSaveForAmazonS3)
|
||||
s.onAccountSave(cloudDiskUseStartupForAmazonS3)
|
||||
s.cloudDisksLoader('s3')
|
||||
//backblaze b2
|
||||
s.loadGroupAppExtender(loadBackblazeB2ForUser)
|
||||
s.unloadGroupAppExtender(unloadBackblazeB2ForUser)
|
||||
s.insertCompletedVideoExtender(uploadVideoToBackblazeB2)
|
||||
s.deleteVideoFromCloudExtensions['b2'] = deleteVideoFromBackblazeB2
|
||||
s.cloudDiskUseStartupExtensions['b2'] = cloudDiskUseStartupForBackblazeB2
|
||||
s.beforeAccountSave(beforeAccountSaveForBackblazeB2)
|
||||
s.onAccountSave(cloudDiskUseStartupForBackblazeB2)
|
||||
s.cloudDisksLoader('b2')
|
||||
//SFTP
|
||||
// s.loadGroupAppExtender(loadSftpForUser)
|
||||
// s.unloadGroupAppExtender(unloadSftpForUser)
|
||||
// s.insertCompletedVideoExtender(uploadVideoToSftp)
|
||||
// s.deleteVideoFromCloudExtensions['sftp'] = deleteVideoFromSftp
|
||||
// s.cloudDiskUseStartupExtensions['sftp'] = cloudDiskUseStartupForSftp
|
||||
// s.beforeAccountSave(beforeAccountSaveForSftp)
|
||||
// s.onAccountSave(cloudDiskUseStartupForSftp)
|
||||
// s.cloudDisksLoader('sftp')
|
||||
}
|
55
libs/config.js
Normal file
55
libs/config.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
module.exports = function(s){
|
||||
s.location = {
|
||||
super : s.mainDirectory+'/super.json',
|
||||
config : s.mainDirectory+'/conf.json',
|
||||
languages : s.mainDirectory+'/languages'
|
||||
}
|
||||
var config = require(s.location.config);
|
||||
if(!config.productType){
|
||||
config.productType='CE'
|
||||
}
|
||||
//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.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.iconURL === undefined){config.iconURL = "https://shinobi.video/libs/assets/icon/apple-touch-icon-152x152.png"}
|
||||
if(config.pipeAddition === undefined){config.pipeAddition=7}else{config.pipeAddition=parseInt(config.pipeAddition)}
|
||||
if(config.hideCloudSaveUrls === undefined){config.hideCloudSaveUrls = true}
|
||||
if(config.insertOrphans === undefined){config.insertOrphans = true}
|
||||
if(config.orphanedVideoCheckMax === undefined){config.orphanedVideoCheckMax = 20}
|
||||
//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'
|
||||
];
|
||||
|
||||
return config
|
||||
}
|
204
libs/detector.js
Normal file
204
libs/detector.js
Normal file
|
@ -0,0 +1,204 @@
|
|||
var P2P = require('pipe2pam');
|
||||
var PamDiff = require('pam-diff');
|
||||
module.exports = function(s,config){
|
||||
s.createPamDiffEngine = function(e){
|
||||
var width,
|
||||
height,
|
||||
globalSensitivity,
|
||||
globalColorThreshold,
|
||||
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_color_threshold===''){
|
||||
globalColorThreshold = 9
|
||||
}else{
|
||||
globalColorThreshold = parseInt(e.details.detector_color_threshold)
|
||||
}
|
||||
|
||||
globalThreshold = parseInt(e.details.detector_threshold) || 0
|
||||
|
||||
var regionJson
|
||||
try{
|
||||
regionJson = JSON.parse(s.group[e.ke].mon_conf[e.id].details.cords)
|
||||
}catch(err){
|
||||
regionJson = s.group[e.ke].mon_conf[e.id].details.cords
|
||||
}
|
||||
|
||||
if(Object.keys(regionJson).length === 0 || e.details.detector_frame === '1'){
|
||||
fullFrame = {
|
||||
name:'FULL_FRAME',
|
||||
sensitivity:globalSensitivity,
|
||||
color_threshold:globalColorThreshold,
|
||||
points:[
|
||||
[0,0],
|
||||
[0,height],
|
||||
[width,height],
|
||||
[width,0]
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
e.triggerTimer = {}
|
||||
|
||||
var regions = s.createPamDiffRegionArray(regionJson,globalColorThreshold,globalSensitivity,fullFrame)
|
||||
|
||||
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
|
||||
}
|
||||
var region = Object.values(regionJson).find(x => x.name == detectorObject.name)
|
||||
s.checkMaximumSensitivity(e, region, detectorObject, function() {
|
||||
s.checkTriggerThreshold(e, region, detectorObject, function() {
|
||||
detectorObject.doObjectDetection = (s.ocv && e.details.detector_use_detect_object === '1')
|
||||
s.triggerEvent(detectorObject)
|
||||
})
|
||||
})
|
||||
}
|
||||
if(e.details.detector_noise_filter==='1'){
|
||||
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.on('diff', (data) => {
|
||||
data.trigger.forEach(function(trigger){
|
||||
s.filterTheNoise(e,noiseFilterArray,regions,trigger,function(){
|
||||
sendTrigger(trigger)
|
||||
})
|
||||
})
|
||||
})
|
||||
}else{
|
||||
s.group[e.ke].mon[e.id].pamDiff.on('diff', (data) => {
|
||||
data.trigger.forEach(sendTrigger)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
s.createPamDiffRegionArray = function(regions,globalColorThreshold,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){
|
||||
if(!region)return false;
|
||||
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)
|
||||
}
|
||||
if(region.color_threshold===''){
|
||||
region.color_threshold = globalColorThreshold
|
||||
}else{
|
||||
region.color_threshold = parseInt(region.color_threshold)
|
||||
}
|
||||
pamDiffCompliantArray.push({name: region.name, difference: region.color_threshold, percent: region.sensitivity, polygon:region.polygon})
|
||||
arrayForOtherStuff[region.name] = region;
|
||||
})
|
||||
if(pamDiffCompliantArray.length===0){pamDiffCompliantArray = null}
|
||||
return {forPam:pamDiffCompliantArray,notForPam:arrayForOtherStuff};
|
||||
}
|
||||
|
||||
s.filterTheNoise = function(e,noiseFilterArray,regions,trigger,callback){
|
||||
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;
|
||||
var triggerPercentWithoutNoise = trigger.percent - theNoise;
|
||||
if(triggerPercentWithoutNoise > regions.notForPam[trigger.name].sensitivity){
|
||||
callback(trigger)
|
||||
}
|
||||
}
|
||||
|
||||
s.checkMaximumSensitivity = function(monitor, region, detectorObject, success) {
|
||||
var logName = detectorObject.id + ':' + detectorObject.name
|
||||
var globalMaxSensitivity = parseInt(monitor.details.detector_max_sensitivity) || undefined
|
||||
var maxSensitivity = parseInt(region.max_sensitivity) || globalMaxSensitivity
|
||||
if (maxSensitivity === undefined || detectorObject.details.confidence <= maxSensitivity) {
|
||||
success()
|
||||
} else {
|
||||
if (monitor.triggerTimer[detectorObject.name] !== undefined) {
|
||||
clearTimeout(monitor.triggerTimer[detectorObject.name].timeout)
|
||||
monitor.triggerTimer[detectorObject.name] = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.checkTriggerThreshold = function(monitor, region, detectorObject, success){
|
||||
var threshold = parseInt(region.threshold) || globalThreshold
|
||||
if (threshold <= 1) {
|
||||
success()
|
||||
} else {
|
||||
if (monitor.triggerTimer[detectorObject.name] === undefined) {
|
||||
monitor.triggerTimer[detectorObject.name] = {
|
||||
count : threshold,
|
||||
timeout : null
|
||||
}
|
||||
}
|
||||
if (--monitor.triggerTimer[detectorObject.name].count == 0) {
|
||||
success()
|
||||
clearTimeout(monitor.triggerTimer[detectorObject.name].timeout)
|
||||
monitor.triggerTimer[detectorObject.name] = undefined
|
||||
} else {
|
||||
var fps = parseFloat(monitor.details.detector_fps) || 2
|
||||
if (monitor.triggerTimer[detectorObject.name].timeout !== null)
|
||||
clearTimeout(monitor.triggerTimer[detectorObject.name].timeout)
|
||||
monitor.triggerTimer[detectorObject.name].timeout = setTimeout(function() {
|
||||
monitor.triggerTimer[detectorObject.name] = undefined
|
||||
}, ((threshold+0.5) * 1000) / fps)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
384
libs/events.js
Normal file
384
libs/events.js
Normal file
|
@ -0,0 +1,384 @@
|
|||
var moment = require('moment');
|
||||
var execSync = require('child_process').execSync;
|
||||
var exec = require('child_process').exec;
|
||||
var spawn = require('child_process').spawn;
|
||||
var request = require('request');
|
||||
module.exports = function(s,config,lang){
|
||||
s.filterEvents = function(x,d){
|
||||
switch(x){
|
||||
case'archive':
|
||||
d.videos.forEach(function(v,n){
|
||||
s.video('archive',v)
|
||||
})
|
||||
break;
|
||||
case'delete':
|
||||
d.videos.forEach(function(v,n){
|
||||
s.deleteVideo(v)
|
||||
})
|
||||
break;
|
||||
case'execute':
|
||||
exec(d.execute,{detached: true})
|
||||
break;
|
||||
}
|
||||
s.onEventTriggerBeforeFilterExtensions.forEach(function(extender){
|
||||
extender(x,d)
|
||||
})
|
||||
}
|
||||
s.triggerEvent = function(d){
|
||||
var filter = {
|
||||
halt : false,
|
||||
addToMotionCounter : true,
|
||||
useLock : true,
|
||||
save : true,
|
||||
webhook : true,
|
||||
command : true,
|
||||
record : true,
|
||||
indifference : false
|
||||
}
|
||||
s.onEventTriggerBeforeFilterExtensions.forEach(function(extender){
|
||||
extender(d,filter)
|
||||
})
|
||||
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];
|
||||
var currentConfig = s.group[d.ke].mon[d.id].details
|
||||
//read filters
|
||||
if(
|
||||
currentConfig.use_detector_filters === '1' &&
|
||||
((currentConfig.use_detector_filters_object === '1' && d.details.matrices) ||
|
||||
currentConfig.use_detector_filters_object !== '1')
|
||||
){
|
||||
var parseValue = function(key,val){
|
||||
var newVal
|
||||
switch(val){
|
||||
case'':
|
||||
newVal = filter[key]
|
||||
break;
|
||||
case'0':
|
||||
newVal = false
|
||||
break;
|
||||
case'1':
|
||||
newVal = true
|
||||
break;
|
||||
default:
|
||||
newVal = val
|
||||
break;
|
||||
}
|
||||
return newVal
|
||||
}
|
||||
var filters = currentConfig.detector_filters
|
||||
Object.keys(filters).forEach(function(key){
|
||||
var conditionChain = {}
|
||||
var dFilter = filters[key]
|
||||
dFilter.where.forEach(function(condition,place){
|
||||
conditionChain[place] = {ok:false,next:condition.p4,matrixCount:0}
|
||||
if(d.details.matrices)conditionChain[place].matrixCount = d.details.matrices.length
|
||||
var modifyFilters = function(toCheck,matrixPosition){
|
||||
var param = toCheck[condition.p1]
|
||||
var pass = function(){
|
||||
if(matrixPosition && dFilter.actions.halt === '1'){
|
||||
delete(d.details.matrices[matrixPosition])
|
||||
}else{
|
||||
conditionChain[place].ok = true
|
||||
}
|
||||
}
|
||||
switch(condition.p2){
|
||||
case'indexOf':
|
||||
if(param.indexOf(condition.p3) > -1){
|
||||
pass()
|
||||
}
|
||||
break;
|
||||
case'!indexOf':
|
||||
if(param.indexOf(condition.p3) === -1){
|
||||
pass()
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if(eval('param '+condition.p2+' "'+condition.p3.replace(/"/g,'\\"')+'"')){
|
||||
pass()
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch(condition.p1){
|
||||
case'tag':
|
||||
case'x':
|
||||
case'y':
|
||||
case'height':
|
||||
case'width':
|
||||
if(d.details.matrices){
|
||||
d.details.matrices.forEach(function(matrix,position){
|
||||
modifyFilters(matrix,position)
|
||||
})
|
||||
}
|
||||
break;
|
||||
case'time':
|
||||
var timeNow = new Date()
|
||||
var timeCondition = new Date()
|
||||
var doAtTime = condition.p3.split(':')
|
||||
var atHour = parseInt(doAtTime[0]) - 1
|
||||
var atHourNow = timeNow.getHours()
|
||||
var atMinuteNow = timeNow.getMinutes()
|
||||
var atSecondNow = timeNow.getSeconds()
|
||||
if(atHour){
|
||||
var atMinute = parseInt(doAtTime[1]) - 1 || timeNow.getMinutes()
|
||||
var atSecond = parseInt(doAtTime[2]) - 1 || timeNow.getSeconds()
|
||||
var nowAddedInSeconds = atHourNow * 60 * 60 + atMinuteNow * 60 + atSecondNow
|
||||
var conditionAddedInSeconds = atHour * 60 * 60 + atMinute * 60 + atSecond
|
||||
if(eval('nowAddedInSeconds '+condition.p2+' conditionAddedInSeconds')){
|
||||
conditionChain[place].ok = true
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
modifyFilters(d.details)
|
||||
break;
|
||||
}
|
||||
})
|
||||
var conditionArray = Object.values(conditionChain)
|
||||
var validationString = ''
|
||||
conditionArray.forEach(function(condition,number){
|
||||
validationString += condition.ok+' '
|
||||
if(conditionArray.length-1 !== number){
|
||||
validationString += condition.next+' '
|
||||
}
|
||||
})
|
||||
if(eval(validationString)){
|
||||
if(dFilter.actions.halt !== '1'){
|
||||
delete(dFilter.actions.halt)
|
||||
Object.keys(dFilter.actions).forEach(function(key){
|
||||
var value = dFilter.actions[key]
|
||||
filter[key] = parseValue(key,value)
|
||||
})
|
||||
}else{
|
||||
filter.halt = true
|
||||
}
|
||||
}
|
||||
})
|
||||
if(d.details.matrices && d.details.matrices.length === 0 || filter.halt === true){
|
||||
return
|
||||
}else if(d.details.matrices && d.details.matrices.length > 0){
|
||||
var reviewedMatrix = []
|
||||
d.details.matrices.forEach(function(matrix){
|
||||
if(matrix)reviewedMatrix.push(matrix)
|
||||
})
|
||||
d.details.matrices = reviewedMatrix
|
||||
}
|
||||
}
|
||||
//motion counter
|
||||
if(filter.addToMotionCounter && filter.record){
|
||||
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(filter.useLock){
|
||||
if(s.group[d.ke].mon[d.id].motion_lock){
|
||||
return
|
||||
}
|
||||
var detector_lock_timeout
|
||||
if(!currentConfig.detector_lock_timeout||currentConfig.detector_lock_timeout===''){
|
||||
detector_lock_timeout = 2000
|
||||
}
|
||||
detector_lock_timeout = parseFloat(currentConfig.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
|
||||
}
|
||||
}
|
||||
// check modified indifference
|
||||
if(filter.indifference !== false && d.details.confidence < parseFloat(filter.indifference)){
|
||||
// fails indifference check for modified indifference
|
||||
return
|
||||
}
|
||||
//
|
||||
if(d.doObjectDetection === true){
|
||||
s.ocvTx({
|
||||
f : 'frame',
|
||||
mon : s.group[d.ke].mon_conf[d.id].details,
|
||||
ke : d.ke,
|
||||
id : d.id,
|
||||
time : s.formattedTime(),
|
||||
frame : s.group[d.ke].mon[d.id].lastJpegDetectorFrame
|
||||
})
|
||||
}else{
|
||||
//save this detection result in SQL, only coords. not image.
|
||||
if(filter.save && currentConfig.detector_save==='1'){
|
||||
s.sqlQuery('INSERT INTO Events (ke,mid,details) VALUES (?,?,?)',[d.ke,d.id,detailString])
|
||||
}
|
||||
if(currentConfig.detector_notrigger === '1'){
|
||||
var detector_notrigger_timeout
|
||||
if(!currentConfig.detector_notrigger_timeout||currentConfig.detector_notrigger_timeout===''){
|
||||
detector_notrigger_timeout = 10
|
||||
}
|
||||
detector_notrigger_timeout = parseFloat(currentConfig.detector_notrigger_timeout)*1000*60;
|
||||
s.group[d.ke].mon[d.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)
|
||||
}
|
||||
var detector_timeout
|
||||
if(!currentConfig.detector_timeout||currentConfig.detector_timeout===''){
|
||||
detector_timeout = 10
|
||||
}else{
|
||||
detector_timeout = parseFloat(currentConfig.detector_timeout)
|
||||
}
|
||||
if(filter.record && d.mon.mode=='start'&¤tConfig.detector_trigger==='1'&¤tConfig.detector_record_method==='sip'){
|
||||
s.createEventBasedRecording(d)
|
||||
}else if(filter.record && d.mon.mode!=='stop'&¤tConfig.detector_trigger=='1'&¤tConfig.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(currentConfig.watchdog_reset!=='0'){
|
||||
d.urlQuery.push('reset=1')
|
||||
}
|
||||
if(currentConfig.detector_trigger_record_fps&¤tConfig.detector_trigger_record_fps!==''&¤tConfig.detector_trigger_record_fps!=='0'){
|
||||
d.urlQuery.push('fps='+currentConfig.detector_trigger_record_fps)
|
||||
}
|
||||
if(d.urlQuery.length>0){
|
||||
d.url+='?'+d.urlQuery.join('&')
|
||||
}
|
||||
request({url:d.url,method:'GET'},function(err,data){
|
||||
if(err){
|
||||
//could not start hotswap
|
||||
}else{
|
||||
delete(s.group[d.ke].users[d.auth])
|
||||
d.cx.f='detector_record_engaged';
|
||||
d.cx.msg = JSON.parse(data.body)
|
||||
s.tx(d.cx,'GRP_'+d.ke);
|
||||
}
|
||||
})
|
||||
}
|
||||
d.currentTime = new Date()
|
||||
d.currentTimestamp = s.timeObject(d.currentTime).format()
|
||||
d.screenshotName = 'Motion_'+(d.mon.name.replace(/[^\w\s]/gi,''))+'_'+d.id+'_'+d.ke+'_'+s.formattedTime()
|
||||
d.screenshotBuffer = null
|
||||
|
||||
s.onEventTriggerExtensions.forEach(function(extender){
|
||||
extender(d,filter)
|
||||
})
|
||||
|
||||
if(filter.webhook && currentConfig.detector_webhook === '1'){
|
||||
var detector_webhook_url = currentConfig.detector_webhook_url
|
||||
.replace(/{{TIME}}/g,d.currentTimestamp)
|
||||
.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_webhook_url = detector_webhook_url
|
||||
.replace(/{{CONFIDENCE}}/g,d.details.confidence)
|
||||
}
|
||||
request({url:detector_webhook_url,method:'GET',encoding:null},function(err,data){
|
||||
if(err){
|
||||
s.userLog(d,{type:lang["Event Webhook Error"],msg:{error:err,data:data}})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if(filter.command && currentConfig.detector_command_enable === '1' && !s.group[d.ke].mon[d.id].detector_command){
|
||||
var detector_command_timeout
|
||||
if(!currentConfig.detector_command_timeout||currentConfig.detector_command_timeout===''){
|
||||
detector_command_timeout = 1000*60*10;
|
||||
}else{
|
||||
detector_command_timeout = parseFloat(currentConfig.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 = currentConfig.detector_command
|
||||
.replace(/{{TIME}}/g,d.currentTimestamp)
|
||||
.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);
|
||||
}
|
||||
s.createEventBasedRecording = function(d){
|
||||
var currentConfig = s.group[d.ke].mon[d.id].details
|
||||
var detector_timeout
|
||||
if(!currentConfig.detector_timeout||currentConfig.detector_timeout===''){
|
||||
detector_timeout = 10
|
||||
}else{
|
||||
detector_timeout = parseFloat(currentConfig.detector_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(60)
|
||||
}
|
||||
if(!s.api[d.auth]){
|
||||
s.api[d.auth] = {
|
||||
system: 1,
|
||||
ip: '0.0.0.0',
|
||||
details: {},
|
||||
lang: lang
|
||||
}
|
||||
}
|
||||
s.group[d.ke].mon[d.id].eventBasedRecording.allowEnd = false;
|
||||
var runRecord = function(){
|
||||
var filename = s.formattedTime()+'.mp4'
|
||||
s.userLog(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.getVideoDirectory(d.mon) + filename + '"')))
|
||||
var ffmpegError='';
|
||||
var error
|
||||
s.group[d.ke].mon[d.id].eventBasedRecording.process.stderr.on('data',function(data){
|
||||
s.userLog(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.userLog(d,{type:"Traditional Recording",msg:"Detector Recording Process Exited Prematurely. Restarting."})
|
||||
runRecord()
|
||||
return
|
||||
}
|
||||
s.insertCompletedVideo(d.mon,{
|
||||
file : filename
|
||||
})
|
||||
s.userLog(d,{type:"Traditional Recording",msg:"Detector Recording Complete"})
|
||||
delete(s.api[d.auth])
|
||||
s.userLog(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].recordingChecker)
|
||||
})
|
||||
}
|
||||
runRecord()
|
||||
}
|
||||
}
|
||||
s.closeEventBasedRecording = function(e){
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
58
libs/extenders.js
Normal file
58
libs/extenders.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
module.exports = function(s,config){
|
||||
////// USER //////
|
||||
s.loadGroupAppExtensions = []
|
||||
s.loadGroupAppExtender = function(callback){
|
||||
s.loadGroupAppExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.unloadGroupAppExtensions = []
|
||||
s.unloadGroupAppExtender = function(callback){
|
||||
s.unloadGroupAppExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.cloudDisksLoaded = []
|
||||
s.cloudDisksLoader = function(storageType){
|
||||
s.cloudDisksLoaded.push(storageType)
|
||||
}
|
||||
//
|
||||
s.onAccountSaveExtensions = []
|
||||
s.onAccountSave = function(callback){
|
||||
s.onAccountSaveExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.beforeAccountSaveExtensions = []
|
||||
s.beforeAccountSave = function(callback){
|
||||
s.beforeAccountSaveExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onTwoFactorAuthCodeNotificationExtensions = []
|
||||
s.onTwoFactorAuthCodeNotification = function(callback){
|
||||
s.onTwoFactorAuthCodeNotificationExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.cloudDiskUseStartupExtensions = {}
|
||||
|
||||
////// EVENTS //////
|
||||
s.onEventTriggerExtensions = []
|
||||
s.onEventTrigger = function(callback){
|
||||
s.onEventTriggerExtensions.push(callback)
|
||||
}
|
||||
s.onEventTriggerBeforeFilterExtensions = []
|
||||
s.onEventTriggerBeforeFilter = function(callback){
|
||||
s.onEventTriggerBeforeFilterExtensions.push(callback)
|
||||
}
|
||||
s.onFilterEventExtensions = []
|
||||
s.onFilterEvent = function(callback){
|
||||
s.onFilterEventExtensions.push(callback)
|
||||
}
|
||||
|
||||
////// MONITOR //////
|
||||
s.onMonitorInitExtensions = []
|
||||
s.onMonitorInit = function(callback){
|
||||
s.onMonitorInitExtensions.push(callback)
|
||||
}
|
||||
s.onDetectorNoTriggerTimeoutExtensions = []
|
||||
s.onDetectorNoTriggerTimeout = function(callback){
|
||||
s.onDetectorNoTriggerTimeoutExtensions.push(callback)
|
||||
}
|
||||
}
|
910
libs/ffmpeg.js
Normal file
910
libs/ffmpeg.js
Normal file
|
@ -0,0 +1,910 @@
|
|||
var fs = require('fs');
|
||||
var spawn = require('child_process').spawn;
|
||||
var execSync = require('child_process').execSync;
|
||||
module.exports = function(s,config,onFinish){
|
||||
var ffmpeg = {}
|
||||
var downloadingFfmpeg = false;
|
||||
//check local ffmpeg
|
||||
ffmpeg.checkForWindows = function(failback){
|
||||
if (s.isWin && fs.existsSync(s.mainDirectory+'/ffmpeg/ffmpeg.exe')) {
|
||||
config.ffmpegDir = s.mainDirectory+'/ffmpeg/ffmpeg.exe'
|
||||
}else{
|
||||
failback()
|
||||
}
|
||||
}
|
||||
//check local ffmpeg
|
||||
ffmpeg.checkForUnix = function(failback){
|
||||
if(s.isWin === false){
|
||||
if (fs.existsSync('/usr/bin/ffmpeg')) {
|
||||
config.ffmpegDir = '/usr/bin/ffmpeg'
|
||||
}else{
|
||||
if (fs.existsSync('/usr/local/bin/ffmpeg')) {
|
||||
config.ffmpegDir = '/usr/local/bin/ffmpeg'
|
||||
}else{
|
||||
failback()
|
||||
}
|
||||
}
|
||||
}else{
|
||||
failback()
|
||||
}
|
||||
}
|
||||
//check node module : ffmpeg-static
|
||||
ffmpeg.checkForNpmStatic = function(failback){
|
||||
try{
|
||||
var staticFFmpeg = require('ffmpeg-static').path;
|
||||
if (fs.statSync(staticFFmpeg)) {
|
||||
config.ffmpegDir = staticFFmpeg
|
||||
}else{
|
||||
console.log('"ffmpeg-static" from NPM has failed to provide a compatible library or has been corrupted.')
|
||||
console.log('Run "npm uninstall ffmpeg-static" to remove it.')
|
||||
console.log('Run "npm install ffbinaries" to get a different static FFmpeg downloader.')
|
||||
}
|
||||
}catch(err){
|
||||
console.log('No "ffmpeg-static".')
|
||||
failback()
|
||||
}
|
||||
}
|
||||
//check node module : ffbinaries
|
||||
ffmpeg.checkForFfbinary = function(failback){
|
||||
try{
|
||||
ffbinaries = require('ffbinaries')
|
||||
var ffbinaryDir = s.mainDirectory + '/ffmpeg/'
|
||||
var downloadFFmpeg = function(){
|
||||
downloadingFfmpeg = true
|
||||
console.log('ffbinaries : Downloading FFmpeg. Please Wait...');
|
||||
ffbinaries.downloadBinaries(['ffmpeg', 'ffprobe'], {
|
||||
destination: ffbinaryDir,
|
||||
version : '3.4'
|
||||
},function () {
|
||||
config.ffmpegDir = ffbinaryDir + 'ffmpeg'
|
||||
console.log('ffbinaries : FFmpeg Downloaded.');
|
||||
ffmpeg.completeCheck()
|
||||
})
|
||||
}
|
||||
if (!fs.existsSync(ffbinaryDir + 'ffmpeg')) {
|
||||
downloadFFmpeg()
|
||||
}else{
|
||||
config.ffmpegDir = ffbinaryDir + 'ffmpeg'
|
||||
}
|
||||
}catch(err){
|
||||
console.log('No "ffbinaries". Continuing.')
|
||||
console.log('Run "npm install ffbinaries" to get this static FFmpeg downloader.')
|
||||
failback()
|
||||
}
|
||||
}
|
||||
//ffmpeg version
|
||||
ffmpeg.checkVersion = function(callback){
|
||||
try{
|
||||
s.ffmpegVersion = execSync(config.ffmpegDir+" -version").toString().split('Copyright')[0].replace('ffmpeg version','').trim()
|
||||
if(s.ffmpegVersion.indexOf(': 2.')>-1){
|
||||
s.systemLog('FFMPEG is too old : '+s.ffmpegVersion+', Needed : 3.2+',err)
|
||||
throw (new Error())
|
||||
}
|
||||
}catch(err){
|
||||
console.log('No FFmpeg found.')
|
||||
// process.exit()
|
||||
}
|
||||
callback()
|
||||
}
|
||||
//check available hardware acceleration methods
|
||||
ffmpeg.checkHwAccelMethods = function(callback){
|
||||
if(config.availableHWAccels === undefined){
|
||||
hwAccels = execSync(config.ffmpegDir+" -loglevel quiet -hwaccels").toString().split('\n')
|
||||
hwAccels.shift()
|
||||
availableHWAccels = []
|
||||
hwAccels.forEach(function(method){
|
||||
if(method && method !== '')availableHWAccels.push(method.trim())
|
||||
})
|
||||
config.availableHWAccels = availableHWAccels
|
||||
config.availableHWAccels = ['auto'].concat(config.availableHWAccels)
|
||||
console.log('Available Hardware Acceleration Methods : ',availableHWAccels.join(', '))
|
||||
}
|
||||
callback()
|
||||
}
|
||||
ffmpeg.completeCheck = function(){
|
||||
ffmpeg.checkVersion(function(){
|
||||
ffmpeg.checkHwAccelMethods(function(){
|
||||
onFinish(ffmpeg)
|
||||
})
|
||||
})
|
||||
}
|
||||
//ffmpeg string cleaner, splits for use with spawn()
|
||||
s.splitForFFPMEG = function (ffmpegCommandAsString) {
|
||||
//this function ignores spaces inside quotes.
|
||||
return ffmpegCommandAsString.replace(/\s+/g,' ').trim().match(/\\?.|^$/g).reduce((p, c) => {
|
||||
if(c === '"'){
|
||||
p.quote ^= 1;
|
||||
}else if(!p.quote && c === ' '){
|
||||
p.a.push('');
|
||||
}else{
|
||||
p.a[p.a.length-1] += c.replace(/\\(.)/,"$1");
|
||||
}
|
||||
return p;
|
||||
}, {a: ['']}).a
|
||||
};
|
||||
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.stream_type !== 'h265' && 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 === 'auto')e.details.hwaccel = ''
|
||||
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' || channel.stream_type === 'mjpeg' || channel.stream_type === 'b64'){
|
||||
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
|
||||
}
|
||||
ffmpeg.buildMainInput = function(e,x){
|
||||
//e = monitor object
|
||||
//x = temporary values
|
||||
//check if CUDA is enabled
|
||||
e.isStreamer = (e.type === 'dashcam'|| e.type === 'socket')
|
||||
if(e.details.accelerator === '1' && e.details.hwaccel === 'cuvid' && e.details.hwaccel_vcodec === ('h264_cuvid' || 'hevc_cuvid' || 'mjpeg_cuvid' || 'mpeg4_cuvid')){
|
||||
e.cudaEnabled = true
|
||||
}
|
||||
//
|
||||
x.hwaccel = ''
|
||||
x.cust_input = ''
|
||||
//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' && (e.type === 'mp4' || e.type === 'local')){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;
|
||||
}
|
||||
//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';
|
||||
// }
|
||||
// }
|
||||
}
|
||||
//logging - level
|
||||
if(e.details.loglevel&&e.details.loglevel!==''){x.loglevel='-loglevel '+e.details.loglevel;}else{x.loglevel='-loglevel error'}
|
||||
//custom - input flags
|
||||
if(e.details.cust_input&&e.details.cust_input!==''){x.cust_input+=' '+e.details.cust_input;}
|
||||
//add main input
|
||||
if((e.type === 'mp4' || e.type === 'mjpeg') && x.cust_input.indexOf('-re') === -1){
|
||||
x.cust_input += ' -re'
|
||||
}
|
||||
}
|
||||
ffmpeg.buildMainStream = function(e,x){
|
||||
//e = monitor object
|
||||
//x = temporary values
|
||||
x.stream_video_filters = []
|
||||
x.pipe = ''
|
||||
//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.stream_type !== 'h265' && e.details.preset_stream && e.details.preset_stream !== ''){x.preset_stream=' -preset '+e.details.preset_stream;}else{x.preset_stream=''}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
if(e.cudaEnabled && (e.details.stream_type === 'mjpeg' || e.details.stream_type === 'b64')){
|
||||
x.stream_video_filters.push('hwdownload,format=nv12')
|
||||
}
|
||||
//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' || e.details.stream_type === 'mjpeg' || e.details.stream_type === 'b64'){
|
||||
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.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'h265':
|
||||
x.cust_stream+=' -movflags +frag_keyframe+empty_moov+default_base_moof -metadata title="Shinobi H.265 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 hevc'+x.stream_acodec+x.stream_vcodec+x.cust_stream+' 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;
|
||||
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)
|
||||
})
|
||||
}
|
||||
//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.details.snap_fps || e.details.snap_fps === ''){e.details.snap_fps = 1}
|
||||
if(e.details.snap_vf && e.details.snap_vf !== '' || e.cudaEnabled){
|
||||
var snapVf = e.details.snap_vf.split(',')
|
||||
if(e.details.snap_vf === '')snapVf.shift()
|
||||
if(e.cudaEnabled){
|
||||
snapVf.push('hwdownload,format=nv12')
|
||||
}
|
||||
//-vf "thumbnail_cuda=2,hwdownload,format=nv12"
|
||||
x.snap_vf=' -vf "'+snapVf.join(',')+'"'
|
||||
}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';
|
||||
}
|
||||
//custom - output
|
||||
if(e.details.custom_output&&e.details.custom_output!==''){x.pipe+=' '+e.details.custom_output;}
|
||||
}
|
||||
ffmpeg.buildMainRecording = function(e,x){
|
||||
//e = monitor object
|
||||
//x = temporary values
|
||||
x.record_video_filters = []
|
||||
x.record_string = ''
|
||||
//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=''
|
||||
}
|
||||
//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;
|
||||
}
|
||||
}
|
||||
ffmpeg.buildMainDetector = function(e,x){
|
||||
//e = monitor object
|
||||
//x = temporary values
|
||||
x.cust_detect = ' '
|
||||
//detector - plugins, motion
|
||||
if(e.details.detector === '1' && e.details.detector_send_frames === '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;}
|
||||
x.detector_vf = ['fps='+e.details.detector_fps]
|
||||
if(e.cudaEnabled){
|
||||
x.detector_vf.push('hwdownload,format=nv12')
|
||||
}
|
||||
x.detector_vf = '-vf "'+x.detector_vf.join(',')+'"'
|
||||
if(e.details.detector_pam==='1'){
|
||||
if(e.cudaEnabled){
|
||||
x.pipe += ' -vf "hwdownload,format=nv12"'
|
||||
}
|
||||
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 '+x.detector_vf+x.cust_detect+x.dratio+' pipe:4';
|
||||
}
|
||||
}else{
|
||||
x.pipe+=' -f image2pipe '+x.detector_vf+x.cust_detect+x.dratio+' pipe:3';
|
||||
}
|
||||
}
|
||||
//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'){
|
||||
switch(e.type){
|
||||
case'h264':case'hls':case'mp4':
|
||||
e.details.detector_buffer_vcodec = 'copy'
|
||||
break;
|
||||
default:
|
||||
if(e.details.accelerator === '1' && e.cudaEnabled){
|
||||
e.details.detector_buffer_vcodec = 'h264_nvenc'
|
||||
}else{
|
||||
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'mjpeg':case'jpeg':case'socket':
|
||||
e.details.detector_buffer_acodec = 'no'
|
||||
break;
|
||||
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"'
|
||||
}
|
||||
}
|
||||
ffmpeg.assembleMainPieces = function(e,x){
|
||||
//create executeable FFMPEG command
|
||||
x.ffmpegCommandString = x.loglevel+x.input_fps;
|
||||
//progress pipe
|
||||
x.ffmpegCommandString += ' -progress pipe:5';
|
||||
|
||||
switch(e.type){
|
||||
case'dashcam':
|
||||
x.ffmpegCommandString += x.cust_input+x.hwaccel+' -i -';
|
||||
break;
|
||||
case'socket':case'jpeg':case'pipe'://case'webpage':
|
||||
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
|
||||
}
|
||||
ffmpeg.createPipeArray = function(e,x){
|
||||
//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')
|
||||
}
|
||||
}
|
||||
s.ffmpeg = function(e){
|
||||
//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.
|
||||
ffmpeg.buildMainInput(e,x)
|
||||
ffmpeg.buildMainStream(e,x)
|
||||
ffmpeg.buildMainRecording(e,x)
|
||||
ffmpeg.buildMainDetector(e,x)
|
||||
ffmpeg.assembleMainPieces(e,x)
|
||||
ffmpeg.createPipeArray(e,x)
|
||||
//hold ffmpeg command for log stream
|
||||
s.group[e.ke].mon[e.mid].ffmpeg = x.ffmpegCommandString
|
||||
//clean the string of spatial impurities and split for spawn()
|
||||
x.ffmpegCommandString = s.splitForFFPMEG(x.ffmpegCommandString)
|
||||
//launch that bad boy
|
||||
return spawn(config.ffmpegDir,x.ffmpegCommandString,{detached: true,stdio:x.stdioPipes})
|
||||
}
|
||||
if(!config.ffmpegDir){
|
||||
ffmpeg.checkForWindows(function(){
|
||||
ffmpeg.checkForUnix(function(){
|
||||
ffmpeg.checkForFfbinary(function(){
|
||||
ffmpeg.checkForNpmStatic(function(){
|
||||
console.log('No FFmpeg found.')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
if(downloadingFfmpeg === false){
|
||||
//not downloading ffmpeg
|
||||
ffmpeg.completeCheck()
|
||||
}
|
||||
return ffmpeg
|
||||
}
|
49
libs/folders.js
Normal file
49
libs/folders.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
var fs = require('fs');
|
||||
module.exports = function(s,config){
|
||||
//directories
|
||||
s.group={};
|
||||
if(!config.windowsTempDir&&s.isWin===true){config.windowsTempDir='C:/Windows/Temp'}
|
||||
if(!config.defaultMjpeg){config.defaultMjpeg=s.mainDirectory+'/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=s.mainDirectory+'/streams/'
|
||||
}else{
|
||||
config.streamDir+='/streams/'
|
||||
}
|
||||
}
|
||||
if(!config.videosDir){config.videosDir=s.mainDirectory+'/videos/'}
|
||||
if(!config.binDir){config.binDir=s.mainDirectory+'/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:s.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);
|
||||
}
|
||||
})
|
||||
}
|
76
libs/health.js
Normal file
76
libs/health.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
var exec = require('child_process').exec;
|
||||
var spawn = require('child_process').spawn;
|
||||
module.exports = function(s,config,lang,io){
|
||||
s.sendDiskUsedAmountToClients = function(e){
|
||||
//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);
|
||||
}
|
||||
}
|
||||
s.heartBeat = function(){
|
||||
setTimeout(s.heartBeat, 8000);
|
||||
io.sockets.emit('ping',{beat:1});
|
||||
}
|
||||
s.heartBeat()
|
||||
s.cpuUsage = function(callback){
|
||||
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;
|
||||
case'freebsd':
|
||||
k.cmd='vmstat 1 2 | tail -1 | awk \'{print $17}\''
|
||||
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, "")
|
||||
}
|
||||
callback(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,"")
|
||||
}
|
||||
callback(d)
|
||||
});
|
||||
} else {
|
||||
callback(0)
|
||||
}
|
||||
}
|
||||
s.ramUsage = function(callback){
|
||||
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;
|
||||
case'freebsd':
|
||||
k.cmd = "echo \"scale=4; $(vmstat -H | tail -1 | awk '{print $5}')*1024*100/$(sysctl hw.physmem | awk '{print $2}')\" | bc"
|
||||
break;
|
||||
default:
|
||||
k.cmd = "LANG=C free | grep Mem | awk '{print $7/$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
|
||||
}
|
||||
callback(d)
|
||||
});
|
||||
}else{
|
||||
callback(0)
|
||||
}
|
||||
}
|
||||
}
|
61
libs/language.js
Normal file
61
libs/language.js
Normal file
|
@ -0,0 +1,61 @@
|
|||
module.exports = function(s,config){
|
||||
if(!config.language){
|
||||
config.language='en_CA'
|
||||
}
|
||||
try{
|
||||
var lang = require(s.location.languages+'/'+config.language+'.json');
|
||||
}catch(er){
|
||||
console.error(er)
|
||||
console.log('There was an error loading your language file.')
|
||||
var lang = require(s.location.languages+'/en_CA.json');
|
||||
}
|
||||
s.location.definitions = s.mainDirectory+'/definitions'
|
||||
try{
|
||||
var definitions = require(s.location.definitions+'/'+config.language+'.json');
|
||||
}catch(er){
|
||||
console.error(er)
|
||||
console.log('There was an error loading your language file.')
|
||||
var definitions = require(s.location.definitions+'/en_CA.json');
|
||||
}
|
||||
//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(s.location.languages+'/'+rule+'.json')
|
||||
s.loadedLanguages[rule] = Object.assign(lang,s.loadedLanguages[rule])
|
||||
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(s.location.definitions+'/'+rule+'.json')
|
||||
s.loadedDefinitons[rule] = Object.assign(definitions,s.loadedDefinitons[rule])
|
||||
file = s.loadedDefinitons[rule]
|
||||
}catch(err){
|
||||
file = definitions
|
||||
}
|
||||
}
|
||||
}else{
|
||||
file = definitions
|
||||
}
|
||||
return file
|
||||
}
|
||||
return lang
|
||||
}
|
1321
libs/monitor.js
Normal file
1321
libs/monitor.js
Normal file
File diff suppressed because it is too large
Load diff
318
libs/notification.js
Normal file
318
libs/notification.js
Normal file
|
@ -0,0 +1,318 @@
|
|||
var Discord = require("discord.js")
|
||||
module.exports = function(s,config,lang){
|
||||
//discord bot
|
||||
if(config.discordBot === true){
|
||||
try{
|
||||
s.discordMsg = function(data,files,groupKey){
|
||||
if(!data)data = {};
|
||||
var bot = s.group[groupKey].discordBot
|
||||
if(!bot){
|
||||
s.userLog({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: config.iconURL,
|
||||
text: "Shinobi Systems"
|
||||
}
|
||||
},data)
|
||||
bot.channels.get(s.group[groupKey].init.discordbot_channel).send({
|
||||
embed: sendBody,
|
||||
files: files
|
||||
}).catch(err => {
|
||||
if(err){
|
||||
s.userLog({ke:groupKey,mid:'$USER'},{type:lang.DiscordErrorText,msg:err})
|
||||
s.group[groupKey].discordBot = null
|
||||
s.loadGroupApps({ke:groupKey})
|
||||
}
|
||||
})
|
||||
}
|
||||
var onEventTriggerBeforeFilterForDiscord = function(d,filter){
|
||||
filter.discord = true
|
||||
}
|
||||
var onEventTriggerForDiscord = function(d,filter){
|
||||
// d = event object
|
||||
//discord bot
|
||||
if(filter.discord && 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: config.iconURL
|
||||
},
|
||||
title: lang.Event+' - '+d.screenshotName,
|
||||
description: lang.EventText1+' '+d.currentTimestamp,
|
||||
fields: [],
|
||||
timestamp: d.currentTime,
|
||||
footer: {
|
||||
icon_url: config.iconURL,
|
||||
text: "Shinobi Systems"
|
||||
}
|
||||
},files,d.ke)
|
||||
}
|
||||
if(d.mon.details.detector_discordbot_send_video === '1'){
|
||||
s.mergeDetectorBufferChunks(d,function(mergedFilepath,filename){
|
||||
s.discordMsg({
|
||||
author: {
|
||||
name: s.group[d.ke].mon_conf[d.id].name,
|
||||
icon_url: config.iconURL
|
||||
},
|
||||
title: filename,
|
||||
fields: [],
|
||||
timestamp: d.currentTime,
|
||||
footer: {
|
||||
icon_url: config.iconURL,
|
||||
text: "Shinobi Systems"
|
||||
}
|
||||
},[
|
||||
{
|
||||
attachment: mergedFilepath,
|
||||
name: filename
|
||||
}
|
||||
],d.ke)
|
||||
})
|
||||
}
|
||||
s.getRawSnapshotFromMonitor(d.mon,function(data){
|
||||
if((data[data.length-2] === 0xFF && data[data.length-1] === 0xD9)){
|
||||
d.screenshotBuffer = data
|
||||
files.push({
|
||||
attachment: d.screenshotBuffer,
|
||||
name: d.screenshotName+'.jpg'
|
||||
})
|
||||
}
|
||||
sendAlert()
|
||||
})
|
||||
}
|
||||
}
|
||||
var onTwoFactorAuthCodeNotificationForDiscord = function(r){
|
||||
// r = user
|
||||
if(r.details.factor_discord === '1'){
|
||||
s.discordMsg({
|
||||
author: {
|
||||
name: r.lang['2-Factor Authentication'],
|
||||
icon_url: config.iconURL
|
||||
},
|
||||
title: r.lang['Enter this code to proceed'],
|
||||
description: '**'+s.factorAuth[r.ke][r.uid].key+'** '+r.lang.FactorAuthText1,
|
||||
fields: [],
|
||||
timestamp: new Date(),
|
||||
footer: {
|
||||
icon_url: config.iconURL,
|
||||
text: "Shinobi Systems"
|
||||
}
|
||||
},[],r.ke)
|
||||
}
|
||||
}
|
||||
var loadDiscordBotForUser = function(user){
|
||||
ar=JSON.parse(user.details);
|
||||
//discordbot
|
||||
if(!s.group[user.ke].discordBot &&
|
||||
config.discordBot === true &&
|
||||
ar.discordbot === '1' &&
|
||||
ar.discordbot_token !== ''
|
||||
){
|
||||
s.group[user.ke].discordBot = new Discord.Client()
|
||||
s.group[user.ke].discordBot.on('ready', () => {
|
||||
console.log(`${user.mail} : Discord Bot Logged in as ${s.group[user.ke].discordBot.user.tag}!`)
|
||||
})
|
||||
s.group[user.ke].discordBot.login(ar.discordbot_token)
|
||||
}
|
||||
}
|
||||
var unloadDiscordBotForUser = function(user){
|
||||
if(s.group[user.ke].discordBot && s.group[user.ke].discordBot.destroy){
|
||||
s.group[user.ke].discordBot.destroy()
|
||||
delete(s.group[user.ke].discordBot)
|
||||
}
|
||||
}
|
||||
s.loadGroupAppExtender(loadDiscordBotForUser)
|
||||
s.unloadGroupAppExtender(unloadDiscordBotForUser)
|
||||
s.onTwoFactorAuthCodeNotification(onTwoFactorAuthCodeNotificationForDiscord)
|
||||
s.onEventTrigger(onEventTriggerForDiscord)
|
||||
s.onEventTriggerBeforeFilter(onEventTriggerBeforeFilterForDiscord)
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
console.log('Could not start Discord bot, please run "npm install discord.js" inside the Shinobi folder.')
|
||||
s.discordMsg = function(){}
|
||||
}
|
||||
}
|
||||
// mailing with nodemailer
|
||||
try{
|
||||
if(config.mail){
|
||||
if(config.mail.from === undefined){config.mail.from = '"ShinobiCCTV" <no-reply@shinobi.video>'}
|
||||
s.nodemailer = require('nodemailer').createTransport(config.mail);
|
||||
}
|
||||
var onDetectorNoTriggerTimeoutForEmail = function(e){
|
||||
//e = monitor object
|
||||
if(config.mail && e.details.detector_notrigger_mail === '1'){
|
||||
s.sqlQuery('SELECT mail FROM Users WHERE ke=? AND details NOT LIKE ?',[e.ke,'%"sub"%'],function(err,r){
|
||||
r = r[0]
|
||||
var 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>',
|
||||
}
|
||||
mailOptions.html+='<div><b>'+lang['Monitor Name']+' </b> : '+e.name+'</div>'
|
||||
mailOptions.html+='<div><b>'+lang['Monitor ID']+' </b> : '+e.id+'</div>'
|
||||
s.nodemailer.sendMail(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);
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
var onTwoFactorAuthCodeNotificationForEmail = function(r){
|
||||
// r = user object
|
||||
if(r.details.factor_mail !== '0'){
|
||||
var 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,
|
||||
};
|
||||
s.nodemailer.sendMail(mailOptions, (error, info) => {
|
||||
if (error) {
|
||||
s.systemLog(r.lang.MailError,error)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
var onFilterEventForEmail = function(x,d){
|
||||
// x = filter function
|
||||
// d = filter event object
|
||||
if(x === 'email'){
|
||||
if(d.videos && d.videos.length > 0){
|
||||
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>'
|
||||
s.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);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
var onEventTriggerBeforeFilterForEmail = function(d,filter){
|
||||
filter.mail = true
|
||||
}
|
||||
var onEventTriggerForEmail = function(d,filter){
|
||||
if(filter.mail && 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+' - '+d.screenshotName, // Subject line
|
||||
html: '<i>'+lang.EventText1+' '+d.currentTimestamp+'.</i>',
|
||||
attachments: files
|
||||
}
|
||||
var sendMail = function(){
|
||||
Object.keys(d.details).forEach(function(v,n){
|
||||
mailOptions.html+='<div><b>'+v+'</b> : '+d.details[v]+'</div>'
|
||||
})
|
||||
s.nodemailer.sendMail(mailOptions, (error, info) => {
|
||||
if (error) {
|
||||
s.systemLog(lang.MailError,error)
|
||||
return false;
|
||||
}
|
||||
})
|
||||
}
|
||||
if(d.mon.details.detector_mail_send_video === '1'){
|
||||
s.mergeDetectorBufferChunks(d,function(mergedFilepath,filename){
|
||||
s.nodemailer.sendMail({
|
||||
from: config.mail.from,
|
||||
to: r.mail,
|
||||
subject: filename,
|
||||
html: '',
|
||||
attachments: [
|
||||
{
|
||||
filename: filename,
|
||||
content: fs.readFileSync(mergedFilepath)
|
||||
}
|
||||
]
|
||||
}, (error, info) => {
|
||||
if (error) {
|
||||
s.systemLog(lang.MailError,error)
|
||||
return false;
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
if(d.screenshotBuffer){
|
||||
files.push({
|
||||
filename: d.screenshotName+'.jpg',
|
||||
content: d.screenshotBuffer
|
||||
})
|
||||
sendMail()
|
||||
}else{
|
||||
s.getRawSnapshotFromMonitor(d.mon,function(data){
|
||||
d.screenshotBuffer = data
|
||||
files.push({
|
||||
filename: d.screenshotName+'.jpg',
|
||||
content: data
|
||||
})
|
||||
sendMail()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
s.onTwoFactorAuthCodeNotification(onTwoFactorAuthCodeNotificationForEmail)
|
||||
s.onEventTriggerBeforeFilter(onEventTriggerBeforeFilterForEmail)
|
||||
s.onEventTrigger(onEventTriggerForEmail)
|
||||
s.onFilterEvent(onFilterEventForEmail)
|
||||
s.onDetectorNoTriggerTimeout(onDetectorNoTriggerTimeoutForEmail)
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
}
|
||||
}
|
105
libs/plugins.js
Normal file
105
libs/plugins.js
Normal file
|
@ -0,0 +1,105 @@
|
|||
var socketIOclient = require('socket.io-client');
|
||||
module.exports = function(s,config,lang){
|
||||
//function for receiving detector data
|
||||
s.pluginEventController=function(d){
|
||||
switch(d.f){
|
||||
case'trigger':
|
||||
s.triggerEvent(d)
|
||||
break;
|
||||
case's.tx':
|
||||
s.tx(d.data,d.to)
|
||||
break;
|
||||
case's.sqlQuery':
|
||||
s.sqlQuery(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){
|
||||
if(s.connectedPlugins[d.plug])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;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
33
libs/process.js
Normal file
33
libs/process.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
var os = require('os')
|
||||
module.exports = function(process,__dirname){
|
||||
process.send = process.send || function () {};
|
||||
process.on('uncaughtException', function (err) {
|
||||
console.error('Uncaught Exception occured!');
|
||||
console.error(err.stack);
|
||||
});
|
||||
// [CTRL] + [C] = exit
|
||||
process.on('SIGINT', function() {
|
||||
console.log('Shinobi is Exiting...')
|
||||
process.exit();
|
||||
});
|
||||
// s = Shinobi
|
||||
s = {
|
||||
//Total Memory
|
||||
coreCount : os.cpus().length,
|
||||
//Total Memory
|
||||
totalmem : os.totalmem(),
|
||||
//Check Platform
|
||||
platform : os.platform(),
|
||||
//JSON stringify short-hand
|
||||
s : JSON.stringify,
|
||||
//Pretty Print JSON
|
||||
prettyPrint : function(obj){return JSON.stringify(obj,null,3)},
|
||||
//Check if Windows
|
||||
isWin : (process.platform === 'win32' || process.platform === 'win64'),
|
||||
//UTC Offset
|
||||
utcOffset : require('moment')().utcOffset(),
|
||||
//directory path for this file
|
||||
mainDirectory : __dirname
|
||||
}
|
||||
return s
|
||||
}
|
1433
libs/socketio.js
Normal file
1433
libs/socketio.js
Normal file
File diff suppressed because it is too large
Load diff
88
libs/sql.js
Normal file
88
libs/sql.js
Normal file
|
@ -0,0 +1,88 @@
|
|||
module.exports = function(s,config){
|
||||
//sql/database connection with knex
|
||||
s.databaseOptions = {
|
||||
client: config.databaseType,
|
||||
connection: config.db,
|
||||
}
|
||||
if(s.databaseOptions.client.indexOf('sqlite')>-1){
|
||||
s.databaseOptions.client = 'sqlite3';
|
||||
s.databaseOptions.useNullAsDefault = true;
|
||||
}
|
||||
if(s.databaseOptions.client === 'sqlite3' && s.databaseOptions.connection.filename === undefined){
|
||||
s.databaseOptions.connection.filename = s.mainDirectory+"/shinobi.sqlite"
|
||||
}
|
||||
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,hideLog){
|
||||
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 && !hideLog){
|
||||
console.log('s.sqlQuery QUERY ERRORED',query)
|
||||
console.log('s.sqlQuery ERROR',err)
|
||||
}
|
||||
if(onMoveOn && typeof onMoveOn === 'function'){
|
||||
switch(s.databaseOptions.client){
|
||||
case'sqlite3':
|
||||
if(!r)r=[]
|
||||
break;
|
||||
default:
|
||||
if(r)r=r[0]
|
||||
break;
|
||||
}
|
||||
onMoveOn(err,r)
|
||||
}
|
||||
})
|
||||
}
|
||||
s.preQueries = function(){
|
||||
//add Cloud Videos table, will remove in future
|
||||
s.sqlQuery('CREATE TABLE IF NOT EXISTS `Cloud Videos` (`mid` varchar(50) NOT NULL,`ke` varchar(50) DEFAULT NULL,`href` text NOT NULL,`size` float DEFAULT NULL,`time` timestamp NULL DEFAULT NULL,`end` timestamp NULL DEFAULT NULL,`status` int(1) DEFAULT \'0\' COMMENT \'0:Complete,1:Read,2:Archive\',`details` text) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;',[],function(err){
|
||||
// if(err)console.log(err)
|
||||
},true)
|
||||
//create Files table
|
||||
s.sqlQuery('CREATE TABLE IF NOT EXISTS `Files` (`ke` varchar(50) NOT NULL,`mid` varchar(50) NOT NULL,`name` tinytext NOT NULL,`size` float NOT NULL DEFAULT \'0\',`details` text NOT NULL,`status` int(1) NOT NULL DEFAULT \'0\') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;',[],function(err){
|
||||
// if(err)console.log(err)
|
||||
//add time column to Files table
|
||||
s.sqlQuery('ALTER TABLE `Files` ADD COLUMN `time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `status`;',[],function(err){
|
||||
// if(err)console.log(err)
|
||||
},true)
|
||||
},true)
|
||||
delete(s.preQueries)
|
||||
}
|
||||
}
|
165
libs/startup.js
Normal file
165
libs/startup.js
Normal file
|
@ -0,0 +1,165 @@
|
|||
|
||||
var fs = require('fs');
|
||||
var moment = require('moment');
|
||||
var crypto = require('crypto');
|
||||
var exec = require('child_process').exec;
|
||||
var execSync = require('child_process').execSync;
|
||||
module.exports = function(s,config,lang,io){
|
||||
console.log('FFmpeg version : '+s.ffmpegVersion)
|
||||
console.log('Node.js version : '+execSync("node -v"))
|
||||
s.processReady = function(){
|
||||
s.systemLog(lang.startUpText5)
|
||||
process.send('ready')
|
||||
}
|
||||
var loadedAccounts = []
|
||||
var loadMonitors = function(callback){
|
||||
s.systemLog(lang.startUpText4)
|
||||
//preliminary monitor start
|
||||
s.sqlQuery('SELECT * FROM Monitors', function(err,monitors) {
|
||||
if(err){s.systemLog(err)}
|
||||
if(monitors && monitors[0]){
|
||||
var loadCompleted = 0
|
||||
var orphanedVideosForMonitors = {}
|
||||
var loadMonitor = function(monitor){
|
||||
if(!orphanedVideosForMonitors[monitor.ke])orphanedVideosForMonitors[monitor.ke] = {}
|
||||
if(!orphanedVideosForMonitors[monitor.ke][monitor.mid])orphanedVideosForMonitors[monitor.ke][monitor.mid] = 0
|
||||
s.initiateMonitorObject(monitor)
|
||||
s.orphanedVideoCheck(monitor,2,function(orphanedFilesCount){
|
||||
if(orphanedFilesCount){
|
||||
orphanedVideosForMonitors[monitor.ke][monitor.mid] += orphanedFilesCount
|
||||
}
|
||||
s.group[monitor.ke].mon_conf[monitor.mid] = monitor
|
||||
var monObj = Object.assign(monitor,{id : monitor.mid})
|
||||
s.camera(monitor.mode,monObj)
|
||||
++loadCompleted
|
||||
if(monitors[loadCompleted]){
|
||||
loadMonitor(monitors[loadCompleted])
|
||||
}else{
|
||||
s.systemLog(lang.startUpText6+' : '+s.s(orphanedVideosForMonitors))
|
||||
callback()
|
||||
}
|
||||
})
|
||||
}
|
||||
loadMonitor(monitors[loadCompleted])
|
||||
}else{
|
||||
callback()
|
||||
}
|
||||
})
|
||||
}
|
||||
var loadDiskUseForUser = function(user,callback){
|
||||
s.systemLog(user.mail+' : '+lang.startUpText0)
|
||||
var userDetails = JSON.parse(user.details)
|
||||
user.size = 0
|
||||
user.limit = userDetails.size
|
||||
s.sqlQuery('SELECT * FROM Videos WHERE ke=? AND status!=?',[user.ke,0],function(err,videos){
|
||||
if(videos && videos[0]){
|
||||
videos.forEach(function(video){
|
||||
user.size += video.size
|
||||
})
|
||||
}
|
||||
s.systemLog(user.mail+' : '+lang.startUpText1+' : '+videos.length,user.size)
|
||||
callback()
|
||||
})
|
||||
}
|
||||
var loadCloudDiskUseForUser = function(user,callback){
|
||||
var userDetails = JSON.parse(user.details)
|
||||
user.cloudDiskUse = {}
|
||||
user.size = 0
|
||||
user.limit = userDetails.size
|
||||
s.cloudDisksLoaded.forEach(function(storageType){
|
||||
user.cloudDiskUse[storageType] = {
|
||||
usedSpace : 0,
|
||||
firstCount : 0
|
||||
}
|
||||
if(s.cloudDiskUseStartupExtensions[storageType])s.cloudDiskUseStartupExtensions[storageType](user,userDetails)
|
||||
})
|
||||
s.sqlQuery('SELECT * FROM `Cloud Videos` WHERE ke=? AND status!=?',[user.ke,0],function(err,videos){
|
||||
if(videos && videos[0]){
|
||||
videos.forEach(function(video){
|
||||
var storageType = JSON.parse(video.details).type
|
||||
if(!storageType)storageType = 's3'
|
||||
user.cloudDiskUse[storageType].usedSpace += (video.size /1000000)
|
||||
++user.cloudDiskUse[storageType].firstCount
|
||||
})
|
||||
s.cloudDisksLoaded.forEach(function(storageType){
|
||||
var firstCount = user.cloudDiskUse[storageType].firstCount
|
||||
s.systemLog(user.mail+' : '+lang.startUpText1+' : '+firstCount,storageType,user.cloudDiskUse[storageType].usedSpace)
|
||||
delete(user.cloudDiskUse[storageType].firstCount)
|
||||
})
|
||||
}
|
||||
s.group[user.ke].cloudDiskUse = user.cloudDiskUse
|
||||
callback()
|
||||
})
|
||||
}
|
||||
var loadAdminUsers = function(callback){
|
||||
//get current disk used for each isolated account (admin user) on startup
|
||||
s.sqlQuery('SELECT * FROM Users WHERE details NOT LIKE ?',['%"sub"%'],function(err,users){
|
||||
if(users && users[0]){
|
||||
var loadLocalDiskUse = function(callback){
|
||||
var count = users.length
|
||||
var countFinished = 0
|
||||
users.forEach(function(user){
|
||||
loadedAccounts.push(user.ke)
|
||||
loadDiskUseForUser(user,function(){
|
||||
s.loadGroup(user)
|
||||
s.loadGroupApps(user)
|
||||
++countFinished
|
||||
if(countFinished === count){
|
||||
callback()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
var loadCloudDiskUse = function(callback){
|
||||
var count = users.length
|
||||
var countFinished = 0
|
||||
users.forEach(function(user){
|
||||
loadCloudDiskUseForUser(user,function(){
|
||||
++countFinished
|
||||
if(countFinished === count){
|
||||
callback()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
loadLocalDiskUse(function(){
|
||||
loadCloudDiskUse(function(){
|
||||
callback()
|
||||
})
|
||||
})
|
||||
}else{
|
||||
s.processReady()
|
||||
}
|
||||
})
|
||||
}
|
||||
//check disk space every 20 minutes
|
||||
if(config.autoDropCache===true){
|
||||
setInterval(function(){
|
||||
exec('echo 3 > /proc/sys/vm/drop_caches',{detached: true})
|
||||
},60000*20)
|
||||
}
|
||||
//master node - startup functions
|
||||
setInterval(function(){
|
||||
s.cpuUsage(function(cpu){
|
||||
s.ramUsage(function(ram){
|
||||
s.tx({f:'os',cpu:cpu,ram:ram},'CPU');
|
||||
})
|
||||
})
|
||||
},10000)
|
||||
//run prerequsite queries, load users and monitors
|
||||
if(config.childNodes.mode !== 'child'){
|
||||
//sql/database connection with knex
|
||||
s.databaseEngine = require('knex')(s.databaseOptions)
|
||||
//run prerequsite queries
|
||||
s.preQueries()
|
||||
setTimeout(function(){
|
||||
//load administrators (groups)
|
||||
loadAdminUsers(function(){
|
||||
//load monitors (for groups)
|
||||
loadMonitors(function(){
|
||||
s.processReady()
|
||||
})
|
||||
})
|
||||
},1500)
|
||||
}
|
||||
}
|
235
libs/user.js
Normal file
235
libs/user.js
Normal file
|
@ -0,0 +1,235 @@
|
|||
var fs = require('fs');
|
||||
var events = require('events');
|
||||
var spawn = require('child_process').spawn;
|
||||
var exec = require('child_process').exec;
|
||||
module.exports = function(s,config){
|
||||
s.purgeDiskForGroup = function(e){
|
||||
if(config.cron.deleteOverMax === true){
|
||||
s.group[e.ke].sizePurgeQueue.push(1)
|
||||
if(s.group[e.ke].sizePurging !== true){
|
||||
s.group[e.ke].sizePurging = true
|
||||
var finish = function(){
|
||||
//remove value just used from queue
|
||||
s.group[e.ke].sizePurgeQueue.shift()
|
||||
//do next one
|
||||
if(s.group[e.ke].sizePurgeQueue.length > 0){
|
||||
checkQueue()
|
||||
}else{
|
||||
s.group[e.ke].sizePurging=false
|
||||
s.sendDiskUsedAmountToClients(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 3',[e.ke],function(err,videos){
|
||||
var videosToDelete = []
|
||||
var queryValues = [e.ke]
|
||||
var completedCheck = 0
|
||||
if(videos){
|
||||
videos.forEach(function(video){
|
||||
video.dir = s.getVideoDirectory(video) + s.formattedTime(video.time) + '.' + video.ext
|
||||
videosToDelete.push('(mid=? AND `time`=?)')
|
||||
queryValues.push(video.mid)
|
||||
queryValues.push(video.time)
|
||||
fs.chmod(video.dir,0o777,function(err){
|
||||
fs.unlink(video.dir,function(err){
|
||||
++completedCheck
|
||||
if(err){
|
||||
fs.stat(video.dir,function(err){
|
||||
if(!err){
|
||||
s.file('delete',video.dir)
|
||||
}
|
||||
})
|
||||
}
|
||||
if(videosToDelete.length === completedCheck){
|
||||
videosToDelete = videosToDelete.join(' OR ')
|
||||
s.sqlQuery('DELETE FROM Videos WHERE ke =? AND ('+videosToDelete+')',queryValues,function(){
|
||||
deleteVideos()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
s.setDiskUsedForGroup(e,-(video.size/1000000))
|
||||
s.tx({
|
||||
f: 'video_delete',
|
||||
ff: 'over_max',
|
||||
filename: s.formattedTime(video.time)+'.'+video.ext,
|
||||
mid: video.mid,
|
||||
ke: video.ke,
|
||||
time: video.time,
|
||||
end: s.formattedTime(new Date,'YYYY-MM-DD HH:mm:ss')
|
||||
},'GRP_'+e.ke)
|
||||
})
|
||||
}else{
|
||||
console.log(err)
|
||||
}
|
||||
if(videosToDelete.length === 0){
|
||||
finish()
|
||||
}
|
||||
})
|
||||
}else{
|
||||
finish()
|
||||
}
|
||||
}
|
||||
deleteVideos()
|
||||
}
|
||||
checkQueue()
|
||||
}
|
||||
}else{
|
||||
s.sendDiskUsedAmountToClients(e)
|
||||
}
|
||||
}
|
||||
s.setDiskUsedForGroup = function(e,bytes){
|
||||
//`bytes` will be used as the value to add or substract
|
||||
if(s.group[e.ke] && s.group[e.ke].diskUsedEmitter){
|
||||
s.group[e.ke].diskUsedEmitter.emit('set',bytes)
|
||||
}
|
||||
}
|
||||
s.purgeCloudDiskForGroup = function(e,storageType){
|
||||
if(s.group[e.ke].diskUsedEmitter){
|
||||
s.group[e.ke].diskUsedEmitter.emit('purgeCloud',storageType)
|
||||
}
|
||||
}
|
||||
s.setCloudDiskUsedForGroup = function(e,usage){
|
||||
//`bytes` will be used as the value to add or substract
|
||||
if(s.group[e.ke].diskUsedEmitter){
|
||||
s.group[e.ke].diskUsedEmitter.emit('setCloud',usage)
|
||||
}
|
||||
}
|
||||
s.sendDiskUsedAmountToClients = function(e){
|
||||
//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);
|
||||
}
|
||||
}
|
||||
//user log
|
||||
s.userLog = function(e,x){
|
||||
if(e.id && !e.mid)e.mid = e.id
|
||||
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.loadGroup = function(e){
|
||||
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].users){s.group[e.ke].users={}}
|
||||
if(!s.group[e.ke].dashcamUsers){s.group[e.ke].dashcamUsers={}}
|
||||
if(!s.group[e.ke].sizePurgeQueue){s.group[e.ke].sizePurgeQueue=[]}
|
||||
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.sendDiskUsedAmountToClients(e)
|
||||
}
|
||||
s.loadGroupApps = function(e){
|
||||
// e = user
|
||||
if(!s.group[e.ke].init){
|
||||
s.group[e.ke].init={};
|
||||
}
|
||||
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);
|
||||
//load extenders
|
||||
s.loadGroupAppExtensions.forEach(function(extender){
|
||||
extender(r)
|
||||
})
|
||||
//disk Used Emitter
|
||||
if(!s.group[e.ke].diskUsedEmitter){
|
||||
s.group[e.ke].diskUsedEmitter = new events.EventEmitter()
|
||||
s.group[e.ke].diskUsedEmitter.on('setCloud',function(currentChange){
|
||||
var amount = currentChange.amount
|
||||
var storageType = currentChange.storageType
|
||||
var cloudDisk = s.group[e.ke].cloudDiskUse[storageType]
|
||||
//validate current values
|
||||
if(!cloudDisk.usedSpace){
|
||||
cloudDisk.usedSpace = 0
|
||||
}else{
|
||||
cloudDisk.usedSpace = parseFloat(cloudDisk.usedSpace)
|
||||
}
|
||||
if(cloudDisk.usedSpace < 0 || isNaN(cloudDisk.usedSpace)){
|
||||
cloudDisk.usedSpace = 0
|
||||
}
|
||||
//change global size value
|
||||
cloudDisk.usedSpace = cloudDisk.usedSpace + amount
|
||||
})
|
||||
s.group[e.ke].diskUsedEmitter.on('purgeCloud',function(storageType){
|
||||
if(config.cron.deleteOverMax === true){
|
||||
//set queue processor
|
||||
var finish=function(){
|
||||
// s.sendDiskUsedAmountToClients(e)
|
||||
}
|
||||
var deleteVideos = function(){
|
||||
//run purge command
|
||||
var cloudDisk = s.group[e.ke].cloudDiskUse[storageType]
|
||||
if(cloudDisk.sizeLimitCheck && cloudDisk.usedSpace > (cloudDisk.sizeLimit*config.cron.deleteOverMaxOffset)){
|
||||
s.sqlQuery('SELECT * FROM `Cloud Videos` WHERE status != 0 AND ke=? AND details LIKE \'%"type":"'+storageType+'"%\' ORDER BY `time` ASC LIMIT 2',[e.ke],function(err,videos){
|
||||
var videosToDelete = []
|
||||
var queryValues = [e.ke]
|
||||
if(!videos)return console.log(err)
|
||||
videos.forEach(function(video){
|
||||
video.dir = s.getVideoDirectory(video) + s.formattedTime(video.time) + '.' + video.ext
|
||||
videosToDelete.push('(mid=? AND `time`=?)')
|
||||
queryValues.push(video.mid)
|
||||
queryValues.push(video.time)
|
||||
s.setCloudDiskUsedForGroup(e,{
|
||||
amount : -(video.size/1000000),
|
||||
storageType : storageType
|
||||
})
|
||||
s.deleteVideoFromCloudExtensionsRunner(e,storageType,video)
|
||||
})
|
||||
if(videosToDelete.length > 0){
|
||||
videosToDelete = videosToDelete.join(' OR ')
|
||||
s.sqlQuery('DELETE FROM `Cloud Videos` WHERE ke =? AND ('+videosToDelete+')',queryValues,function(){
|
||||
deleteVideos()
|
||||
})
|
||||
}else{
|
||||
finish()
|
||||
}
|
||||
})
|
||||
}else{
|
||||
finish()
|
||||
}
|
||||
}
|
||||
deleteVideos()
|
||||
}else{
|
||||
// s.sendDiskUsedAmountToClients(e)
|
||||
}
|
||||
})
|
||||
//s.setDiskUsedForGroup
|
||||
s.group[e.ke].diskUsedEmitter.on('set',function(currentChange){
|
||||
//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
|
||||
}
|
||||
//change global size value
|
||||
s.group[e.ke].usedSpace += currentChange
|
||||
//remove value just used from queue
|
||||
s.sendDiskUsedAmountToClients(e)
|
||||
})
|
||||
}
|
||||
Object.keys(ar).forEach(function(v){
|
||||
s.group[e.ke].init[v]=ar[v]
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
329
libs/videos.js
Normal file
329
libs/videos.js
Normal file
|
@ -0,0 +1,329 @@
|
|||
var fs = require('fs');
|
||||
var exec = require('child_process').exec;
|
||||
var spawn = require('child_process').spawn;
|
||||
module.exports = function(s,config,lang){
|
||||
/**
|
||||
* Gets the video directory of the supplied video or monitor database row.
|
||||
* @constructor
|
||||
* @param {object} e - Monitor object or Video object. Object is a database row.
|
||||
*/
|
||||
s.getVideoDirectory = function(e){
|
||||
if(e.mid&&!e.id){e.id=e.mid};
|
||||
s.checkDetails(e)
|
||||
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+'/';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Creates available API based URLs for streaming
|
||||
* @constructor
|
||||
* @param {object} videos - Array of video objects
|
||||
* @param {object} options - Contains middle parameter of URL and auth key
|
||||
* @param [options.auth] {string} - API Key
|
||||
* @param [options.videoParam] {string} - currently only `videos` and `cloudVideos` available.
|
||||
*/
|
||||
s.buildVideoLinks = function(videos,options){
|
||||
videos.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 = ''
|
||||
}
|
||||
if(!v.ext && v.href){
|
||||
v.ext = v.href.split('.')
|
||||
v.ext = v.ext[v.ext.length - 1]
|
||||
}
|
||||
v.filename = s.formattedTime(v.time)+'.'+v.ext;
|
||||
if(!options.videoParam)options.videoParam = 'videos'
|
||||
var href = '/'+options.auth+'/'+options.videoParam+'/'+v.ke+'/'+v.mid+'/'+v.filename;
|
||||
v.actionUrl = href
|
||||
v.links = {
|
||||
deleteVideo : href+'/delete' + queryString,
|
||||
changeToUnread : href+'/status/1' + queryString,
|
||||
changeToRead : href+'/status/2' + queryString
|
||||
}
|
||||
if(!v.href || options.hideRemote === true)v.href = href + queryString
|
||||
v.details = details
|
||||
})
|
||||
}
|
||||
//extender for "s.insertCompletedVideo"
|
||||
s.insertCompletedVideoExtensions = []
|
||||
s.insertCompletedVideoExtender = function(callback){
|
||||
s.insertCompletedVideoExtensions.push(callback)
|
||||
}
|
||||
s.insertDatabaseRow = function(e,k,callback){
|
||||
s.checkDetails(e)
|
||||
//save database row
|
||||
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,
|
||||
k.startTime,
|
||||
e.ext,
|
||||
1,
|
||||
s.s(k.details),
|
||||
k.filesize,
|
||||
k.endTime,
|
||||
]
|
||||
s.sqlQuery('INSERT INTO Videos (mid,ke,time,ext,status,details,size,end) VALUES (?,?,?,?,?,?,?,?)',save,function(err){
|
||||
if(callback)callback(err)
|
||||
fs.chmod(k.dir+k.file,0o777,function(err){
|
||||
|
||||
})
|
||||
})
|
||||
}
|
||||
//on video completion
|
||||
s.insertCompletedVideo = function(e,k,callback){
|
||||
//e = monitor object
|
||||
//k = video insertion object
|
||||
s.checkDetails(e)
|
||||
if(!k)k={};
|
||||
e.dir = s.getVideoDirectory(e)
|
||||
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)
|
||||
k.filesize = k.stat.size
|
||||
k.filesizeMB = parseFloat((k.filesize/1000000).toFixed(2))
|
||||
|
||||
k.startTime = new Date(s.nameToTime(k.file))
|
||||
k.endTime = new Date(k.stat.mtime)
|
||||
if(config.useUTC === true){
|
||||
fs.rename(k.dir+k.file, k.dir+s.formattedTime(k.startTime)+'.'+e.ext, (err) => {
|
||||
if (err) return console.error(err);
|
||||
});
|
||||
k.filename = s.formattedTime(k.startTime)+'.'+e.ext
|
||||
}else{
|
||||
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,{ highWaterMark: 500 })
|
||||
.on('data',function(data){
|
||||
s.cx({
|
||||
f:'created_file_chunk',
|
||||
mid:e.mid,
|
||||
ke:e.ke,
|
||||
chunk:data,
|
||||
filename:k.filename,
|
||||
d:s.cleanMonitorObject(e),
|
||||
filesize:e.filesize,
|
||||
time:s.timeObject(k.startTime).format(),
|
||||
end:s.timeObject(k.endTime).format()
|
||||
})
|
||||
})
|
||||
.on('close',function(){
|
||||
clearTimeout(s.group[e.ke].mon[e.id].recordingChecker)
|
||||
clearTimeout(s.group[e.ke].mon[e.id].streamChecker)
|
||||
s.cx({
|
||||
f:'created_file',
|
||||
mid:e.id,
|
||||
ke:e.ke,
|
||||
filename:k.filename,
|
||||
d:s.cleanMonitorObject(e),
|
||||
filesize:k.filesize,
|
||||
time:s.timeObject(k.startTime).format(),
|
||||
end:s.timeObject(k.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:k.startTime,
|
||||
size:k.filesize,
|
||||
end:k.endTime
|
||||
},'GRP_'+e.ke,'video_view')
|
||||
//purge over max
|
||||
s.purgeDiskForGroup(e)
|
||||
//send new diskUsage values
|
||||
s.setDiskUsedForGroup(e,k.filesizeMB)
|
||||
s.insertDatabaseRow(e,k,callback)
|
||||
s.insertCompletedVideoExtensions.forEach(function(extender){
|
||||
extender(e,k)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
s.deleteVideo = function(e){
|
||||
//e = video object
|
||||
s.checkDetails(e)
|
||||
e.dir = s.getVideoDirectory(e)
|
||||
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)
|
||||
var queryValues = [e.id,e.ke,time];
|
||||
s.sqlQuery('SELECT * FROM Videos WHERE `mid`=? AND `ke`=? AND `time`=?',queryValues,function(err,r){
|
||||
if(r && r[0]){
|
||||
r = r[0]
|
||||
fs.chmod(e.dir+filename,0o777,function(err){
|
||||
s.tx({
|
||||
f: 'video_delete',
|
||||
filename: filename,
|
||||
mid: e.id,
|
||||
ke: e.ke,
|
||||
time: s.nameToTime(filename),
|
||||
end: s.formattedTime(new Date,'YYYY-MM-DD HH:mm:ss')
|
||||
},'GRP_'+e.ke);
|
||||
s.setDiskUsedForGroup(e,-(r.size / 1000000))
|
||||
s.sqlQuery('DELETE FROM Videos WHERE `mid`=? AND `ke`=? AND `time`=?',queryValues,function(err){
|
||||
if(err){
|
||||
s.systemLog(lang['File Delete Error'] + ' : '+e.ke+' : '+' : '+e.id,err)
|
||||
}
|
||||
})
|
||||
fs.unlink(e.dir+filename,function(err){
|
||||
fs.stat(e.dir+filename,function(err){
|
||||
if(!err){
|
||||
s.file('delete',e.dir+filename)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
var videoSnap = s.dir.videoSnaps + e.ke + '/' + e.mid + '/' + filename.split('.')[0] + '.jpg'
|
||||
fs.chmod(videoSnap,0o777,function(err){
|
||||
if(!err){
|
||||
fs.unlink(videoSnap,function(err){})
|
||||
}
|
||||
})
|
||||
}else{
|
||||
console.log(lang['Database row does not exist'],queryValues)
|
||||
}
|
||||
})
|
||||
}
|
||||
s.deleteVideoFromCloudExtensions = {}
|
||||
s.deleteVideoFromCloudExtensionsRunner = function(e,storageType,video){
|
||||
// e = user
|
||||
if(!storageType){
|
||||
var videoDetails = JSON.parse(r.details)
|
||||
videoDetails.type = videoDetails.type || 's3'
|
||||
}
|
||||
if(s.deleteVideoFromCloudExtensions[storageType]){
|
||||
s.deleteVideoFromCloudExtensions[storageType](e,video,function(){
|
||||
s.tx({
|
||||
f: 'video_delete_cloud',
|
||||
mid: e.mid,
|
||||
ke: e.ke,
|
||||
time: e.time,
|
||||
end: e.end
|
||||
},'GRP_'+e.ke);
|
||||
})
|
||||
}
|
||||
}
|
||||
s.deleteVideoFromCloud = function(e){
|
||||
// e = video object
|
||||
s.checkDetails(e)
|
||||
var videoSelector = [e.id,e.ke,new Date(e.time)]
|
||||
s.sqlQuery('SELECT * FROM `Cloud Videos` WHERE `mid`=? AND `ke`=? AND `time`=?',videoSelector,function(err,r){
|
||||
if(r&&r[0]){
|
||||
r = r[0]
|
||||
s.sqlQuery('DELETE FROM `Cloud Videos` WHERE `mid`=? AND `ke`=? AND `time`=?',videoSelector,function(){
|
||||
s.deleteVideoFromCloudExtensionsRunner(e,r)
|
||||
})
|
||||
}else{
|
||||
// console.log('Delete Failed',e)
|
||||
// console.error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
s.orphanedVideoCheck = function(monitor,checkMax,callback,forceCheck){
|
||||
var finish = function(orphanedFilesCount){
|
||||
if(callback)callback(orphanedFilesCount)
|
||||
}
|
||||
if(forceCheck === true || config.insertOrphans === true){
|
||||
if(!checkMax){
|
||||
checkMax = config.orphanedVideoCheckMax
|
||||
}
|
||||
var videosDirectory = s.getVideoDirectory(monitor)// + s.formattedTime(video.time) + '.' + video.ext
|
||||
fs.readdir(videosDirectory,function(err,files){
|
||||
if(files && files.length > 0){
|
||||
var fiveRecentFiles = files.slice(files.length - checkMax,files.length)
|
||||
var completedFile = 0
|
||||
var orphanedFilesCount = 0
|
||||
var fileComplete = function(){
|
||||
++completedFile
|
||||
if(fiveRecentFiles.length === completedFile){
|
||||
finish(orphanedFilesCount)
|
||||
}
|
||||
}
|
||||
fiveRecentFiles.forEach(function(filename){
|
||||
if(/T[0-9][0-9]-[0-9][0-9]-[0-9][0-9]./.test(filename)){
|
||||
var queryValues = [
|
||||
monitor.ke,
|
||||
monitor.mid,
|
||||
s.nameToTime(filename)
|
||||
]
|
||||
s.sqlQuery('SELECT * FROM Videos WHERE ke=? AND mid=? AND time=? LIMIT 1',queryValues,function(err,rows){
|
||||
if(!err && (!rows || !rows[0])){
|
||||
++orphanedFilesCount
|
||||
var video = rows[0]
|
||||
s.insertCompletedVideo(monitor,{
|
||||
file : filename
|
||||
},function(){
|
||||
fileComplete()
|
||||
})
|
||||
}else{
|
||||
fileComplete()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}else{
|
||||
finish()
|
||||
}
|
||||
})
|
||||
}else{
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
35
libs/webServer.js
Normal file
35
libs/webServer.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
var fs = require('fs');
|
||||
var http = require('http');
|
||||
var https = require('https');
|
||||
var express = require('express');
|
||||
var app = express()
|
||||
module.exports = function(s,config,lang,io){
|
||||
var server = http.createServer(app);
|
||||
//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 Web Server Listening on '+config.ssl.port);
|
||||
});
|
||||
io.attach(serverHTTPS);
|
||||
}
|
||||
//start HTTP
|
||||
server.listen(config.port,config.bindip,function(){
|
||||
console.log(lang.Shinobi+' : Web Server Listening on '+config.port);
|
||||
});
|
||||
io.attach(server);
|
||||
return app
|
||||
}
|
428
libs/webServerAdminPaths.js
Normal file
428
libs/webServerAdminPaths.js
Normal file
|
@ -0,0 +1,428 @@
|
|||
var fs = require('fs');
|
||||
var os = require('os');
|
||||
var moment = require('moment')
|
||||
var request = require('request')
|
||||
var jsonfile = require("jsonfile")
|
||||
var exec = require('child_process').exec;
|
||||
var spawn = require('child_process').spawn;
|
||||
var execSync = require('child_process').execSync;
|
||||
module.exports = function(s,config,lang,app){
|
||||
var closeResponse = function(res,endData){
|
||||
res.setHeader('Content-Type', 'application/json')
|
||||
res.end(s.prettyPrint(endData))
|
||||
}
|
||||
/**
|
||||
* API : Administrator : Edit Sub-Account (Account to share cameras with)
|
||||
*/
|
||||
app.all(config.webPaths.adminApiPrefix+':auth/accounts/:ke/edit', function (req,res){
|
||||
s.auth(req.params,function(user){
|
||||
var endData = {
|
||||
ok : false
|
||||
}
|
||||
if(user.details.sub){
|
||||
endData.msg = user.lang['Not Permitted']
|
||||
closeResponse(res,endData)
|
||||
return
|
||||
}
|
||||
var form = s.getPostData(req)
|
||||
var uid = s.getPostData(req,'uid',false)
|
||||
var mail = s.getPostData(req,'mail',false)
|
||||
if(form){
|
||||
var keys = Object.keys(form)
|
||||
var condition = []
|
||||
var value = []
|
||||
keys.forEach(function(v){
|
||||
condition.push(v+'=?')
|
||||
if(form[v] instanceof Object)form[v] = JSON.stringify(form[v])
|
||||
value.push(form[v])
|
||||
})
|
||||
value = value.concat([req.params.ke,uid])
|
||||
s.sqlQuery("UPDATE Users SET "+condition.join(',')+" WHERE ke=? AND uid=?",value)
|
||||
s.tx({
|
||||
f: 'edit_sub_account',
|
||||
ke: req.params.ke,
|
||||
uid: uid,
|
||||
mail: mail,
|
||||
form: form
|
||||
},'ADM_'+req.params.ke)
|
||||
endData.ok = true
|
||||
s.sqlQuery("SELECT * FROM API WHERE ke=? AND uid=?",[req.params.ke,uid],function(err,rows){
|
||||
if(rows && rows[0]){
|
||||
rows.forEach(function(row){
|
||||
delete(s.api[row.code])
|
||||
})
|
||||
}
|
||||
})
|
||||
}else{
|
||||
endData.msg = lang.postDataBroken
|
||||
}
|
||||
closeResponse(res,endData)
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Administrator : Delete Sub-Account (Account to share cameras with)
|
||||
*/
|
||||
app.all(config.webPaths.adminApiPrefix+':auth/accounts/:ke/delete', function (req,res){
|
||||
s.auth(req.params,function(user){
|
||||
var endData = {
|
||||
ok : false
|
||||
}
|
||||
if(user.details.sub){
|
||||
endData.msg = user.lang['Not Permitted']
|
||||
closeResponse(res,endData)
|
||||
return
|
||||
}
|
||||
var uid = s.getPostData(req,'uid',false)
|
||||
var mail = s.getPostData(req,'mail',false)
|
||||
s.sqlQuery('DELETE FROM Users WHERE uid=? AND ke=? AND mail=?',[uid,req.params.ke,mail])
|
||||
s.sqlQuery("SELECT * FROM API WHERE ke=? AND uid=?",[req.params.ke,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=?',[uid,req.params.ke])
|
||||
}
|
||||
})
|
||||
s.tx({
|
||||
f: 'delete_sub_account',
|
||||
ke: req.params.ke,
|
||||
uid: uid,
|
||||
mail: mail
|
||||
},'ADM_'+req.params.ke)
|
||||
endData.ok = true
|
||||
closeResponse(res,endData)
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Administrator : Add Sub-Account (Account to share cameras with)
|
||||
*/
|
||||
app.post([
|
||||
config.webPaths.adminApiPrefix+':auth/accounts/:ke/register',
|
||||
//these two routes are for backwards compatibility
|
||||
config.webPaths.adminApiPrefix+':auth/register/:ke/:uid',
|
||||
config.webPaths.apiPrefix+':auth/register/:ke/:uid'
|
||||
],function (req,res){
|
||||
endData = {
|
||||
ok : false
|
||||
}
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
s.auth(req.params,function(user){
|
||||
if(user.details.sub){
|
||||
endData.msg = user.lang['Not an Administrator Account']
|
||||
closeResponse(res,endData)
|
||||
return
|
||||
}
|
||||
var form = s.getPostData(req)
|
||||
if(form.mail !== '' && form.pass !== ''){
|
||||
if(form.pass === form.password_again || form.pass === form.pass_again){
|
||||
s.sqlQuery('SELECT * FROM Users WHERE mail=?',[form.mail],function(err,r) {
|
||||
if(r&&r[0]){
|
||||
//found one exist
|
||||
endData.msg = 'Email address is in use.'
|
||||
}else{
|
||||
//create new
|
||||
endData.msg = 'New Account Created'
|
||||
endData.ok = true
|
||||
var newId = s.gid()
|
||||
var details = s.s({
|
||||
sub: "1",
|
||||
allmonitors: "1"
|
||||
})
|
||||
s.sqlQuery('INSERT INTO Users (ke,uid,mail,pass,details) VALUES (?,?,?,?,?)',[req.params.ke,newId,form.mail,s.createHash(form.pass),details])
|
||||
s.tx({
|
||||
f: 'add_sub_account',
|
||||
details: details,
|
||||
ke: req.params.ke,
|
||||
uid: newId,
|
||||
mail: form.mail
|
||||
},'ADM_'+req.params.ke)
|
||||
}
|
||||
res.end(s.prettyPrint(endData))
|
||||
})
|
||||
}else{
|
||||
endData.msg = user.lang["Passwords Don't Match"]
|
||||
}
|
||||
}else{
|
||||
endData.msg = user.lang['Fields cannot be empty']
|
||||
}
|
||||
if(endData.msg){
|
||||
res.end(s.prettyPrint(endData))
|
||||
}
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Administrator : Monitor : Add, Edit, and Delete
|
||||
*/
|
||||
app.all([
|
||||
config.webPaths.apiPrefix+':auth/configureMonitor/:ke/:id',
|
||||
config.webPaths.apiPrefix+':auth/configureMonitor/:ke/:id/:f',
|
||||
config.webPaths.adminApiPrefix+':auth/configureMonitor/:ke/:id',
|
||||
config.webPaths.adminApiPrefix+':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'){
|
||||
var form = s.getPostData(req)
|
||||
if(!form){
|
||||
req.ret.msg = user.lang.monitorEditText1;
|
||||
res.end(s.prettyPrint(req.ret))
|
||||
return
|
||||
}
|
||||
if(!user.details.sub ||
|
||||
user.details.allmonitors === '1' ||
|
||||
hasRestrictions && user.details.monitor_edit.indexOf(form.mid) >- 1 ||
|
||||
hasRestrictions && user.details.monitor_create === '1'){
|
||||
if(form&&form.mid&&form.name){
|
||||
req.set=[],req.ar=[];
|
||||
form.mid=req.params.id.replace(/[^\w\s]/gi,'').replace(/ /g,'');
|
||||
try{
|
||||
JSON.parse(form.details)
|
||||
}catch(er){
|
||||
if(!form.details||!form.details.stream_type){
|
||||
req.ret.msg=user.lang.monitorEditText2;
|
||||
res.end(s.prettyPrint(req.ret))
|
||||
return
|
||||
}else{
|
||||
form.details=JSON.stringify(form.details)
|
||||
}
|
||||
}
|
||||
form.ke=req.params.ke
|
||||
req.logObject={details:JSON.parse(form.details),ke:req.params.ke,mid:req.params.id}
|
||||
s.sqlQuery('SELECT * FROM Monitors WHERE ke=? AND mid=?',[form.ke,form.mid],function(er,r){
|
||||
req.tx={f:'monitor_edit',mid:form.mid,ke:form.ke,mon:form};
|
||||
if(r&&r[0]){
|
||||
req.tx.new=false;
|
||||
Object.keys(form).forEach(function(v){
|
||||
if(form[v]&&form[v]!==''){
|
||||
req.set.push(v+'=?'),req.ar.push(form[v]);
|
||||
}
|
||||
})
|
||||
req.set=req.set.join(',');
|
||||
req.ar.push(form.ke),req.ar.push(form.mid);
|
||||
s.userLog(form,{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[form.ke].init.max_camera||s.group[form.ke].init.max_camera==''||Object.keys(s.group[form.ke].mon).length <= parseInt(s.group[form.ke].init.max_camera)){
|
||||
req.tx.new=true;
|
||||
req.st=[];
|
||||
Object.keys(form).forEach(function(v){
|
||||
if(form[v]&&form[v]!==''){
|
||||
req.set.push(v),req.st.push('?'),req.ar.push(form[v]);
|
||||
}
|
||||
})
|
||||
// req.set.push('ke'),req.st.push('?'),req.ar.push(form.ke);
|
||||
req.set=req.set.join(','),req.st=req.st.join(',');
|
||||
s.userLog(form,{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){
|
||||
form.details=JSON.parse(form.details)
|
||||
req.ret.ok=true;
|
||||
s.initiateMonitorObject({mid:form.mid,ke:form.ke});
|
||||
s.group[form.ke].mon_conf[form.mid]=s.cleanMonitorObject(form);
|
||||
if(form.mode==='stop'){
|
||||
s.camera('stop',form);
|
||||
}else{
|
||||
s.camera('stop',form);setTimeout(function(){s.camera(form.mode,form);},5000)
|
||||
};
|
||||
s.tx(req.tx,'STR_'+form.ke);
|
||||
};
|
||||
s.tx(req.tx,'GRP_'+form.ke);
|
||||
res.end(s.prettyPrint(req.ret))
|
||||
})
|
||||
}else{
|
||||
req.ret.msg=user.lang.monitorEditText1;
|
||||
res.end(s.prettyPrint(req.ret))
|
||||
}
|
||||
}else{
|
||||
req.ret.msg=user.lang['Not Permitted'];
|
||||
res.end(s.prettyPrint(req.ret))
|
||||
}
|
||||
}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.userLog(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.prettyPrint(req.ret))
|
||||
}else{
|
||||
req.ret.msg=user.lang['Not Permitted'];
|
||||
res.end(s.prettyPrint(req.ret))
|
||||
}
|
||||
}
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Add API Key, binded to the user who created it
|
||||
*/
|
||||
app.all([
|
||||
config.webPaths.adminApiPrefix+':auth/api/:ke/add',
|
||||
config.webPaths.apiPrefix+':auth/api/:ke/add',
|
||||
],function (req,res){
|
||||
var endData = {ok:false}
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
s.auth(req.params,function(user){
|
||||
var endData = {
|
||||
ok : false
|
||||
}
|
||||
var form = s.getPostData(req)
|
||||
if(form){
|
||||
var insert = {
|
||||
ke : req.params.ke,
|
||||
uid : user.uid,
|
||||
code : s.gid(30),
|
||||
ip : form.ip,
|
||||
details : s.stringJSON(form.details)
|
||||
}
|
||||
var escapes = []
|
||||
Object.keys(insert).forEach(function(column){
|
||||
escapes.push('?')
|
||||
});
|
||||
s.sqlQuery('INSERT INTO API ('+Object.keys(insert).join(',')+') VALUES ('+escapes.join(',')+')',Object.values(insert),function(err,r){
|
||||
insert.time = s.formattedTime(new Date,'YYYY-DD-MM HH:mm:ss');
|
||||
if(!err){
|
||||
s.tx({
|
||||
f: 'api_key_added',
|
||||
uid: user.uid,
|
||||
form: insert
|
||||
},'GRP_' + req.params.ke)
|
||||
endData.ok = true
|
||||
}
|
||||
closeResponse(res,endData)
|
||||
})
|
||||
}else{
|
||||
endData.msg = lang.postDataBroken
|
||||
closeResponse(res,endData)
|
||||
}
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Delete API Key
|
||||
*/
|
||||
app.all([
|
||||
config.webPaths.adminApiPrefix+':auth/api/:ke/delete',
|
||||
config.webPaths.apiPrefix+':auth/api/:ke/delete',
|
||||
],function (req,res){
|
||||
var endData = {ok:false}
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
s.auth(req.params,function(user){
|
||||
var endData = {
|
||||
ok : false
|
||||
}
|
||||
var form = s.getPostData(req)
|
||||
if(form){
|
||||
if(!form.code){
|
||||
s.tx({
|
||||
f:'form_incomplete',
|
||||
uid: user.uid,
|
||||
form:'APIs'
|
||||
},'GRP_' + req.params.ke)
|
||||
endData.msg = lang.postDataBroken
|
||||
closeResponse(res,endData)
|
||||
return
|
||||
}
|
||||
var row = {
|
||||
ke : req.params.ke,
|
||||
uid : user.uid,
|
||||
code : form.code
|
||||
}
|
||||
var where = []
|
||||
Object.keys(row).forEach(function(column){
|
||||
where.push(column+'=?')
|
||||
})
|
||||
s.sqlQuery('DELETE FROM API WHERE '+where.join(' AND '),Object.values(row),function(err,r){
|
||||
if(!err){
|
||||
s.tx({
|
||||
f: 'api_key_deleted',
|
||||
uid: user.uid,
|
||||
form: row
|
||||
},'GRP_' + req.params.ke)
|
||||
endData.ok = true
|
||||
delete(s.api[row.code])
|
||||
}
|
||||
closeResponse(res,endData)
|
||||
})
|
||||
}else{
|
||||
endData.msg = lang.postDataBroken
|
||||
closeResponse(res,endData)
|
||||
}
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : List API Keys for Authenticated user
|
||||
*/
|
||||
app.get([
|
||||
config.webPaths.adminApiPrefix+':auth/api/:ke/list',
|
||||
config.webPaths.apiPrefix+':auth/api/:ke/list',
|
||||
],function (req,res){
|
||||
var endData = {ok:false}
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
s.auth(req.params,function(user){
|
||||
var endData = {
|
||||
ok : false
|
||||
}
|
||||
var row = {
|
||||
ke : req.params.ke,
|
||||
uid : user.uid
|
||||
}
|
||||
var where = []
|
||||
Object.keys(row).forEach(function(column){
|
||||
where.push(column+'=?')
|
||||
})
|
||||
s.sqlQuery('SELECT * FROM API WHERE '+where.join(' AND '),Object.values(row),function(err,rows){
|
||||
if(rows && rows[0]){
|
||||
rows.forEach(function(row){
|
||||
row.details = JSON.parse(row.details)
|
||||
})
|
||||
endData.ok = true
|
||||
endData.uid = user.uid
|
||||
endData.ke = user.ke
|
||||
endData.keys = rows
|
||||
}
|
||||
closeResponse(res,endData)
|
||||
})
|
||||
},res,req)
|
||||
})
|
||||
}
|
1843
libs/webServerPaths.js
Normal file
1843
libs/webServerPaths.js
Normal file
File diff suppressed because it is too large
Load diff
358
libs/webServerStreamPaths.js
Normal file
358
libs/webServerStreamPaths.js
Normal file
|
@ -0,0 +1,358 @@
|
|||
var express = require('express');
|
||||
var fs = require('fs');
|
||||
var bodyParser = require('body-parser');
|
||||
var os = require('os');
|
||||
var moment = require('moment');
|
||||
var request = require('request');
|
||||
var execSync = require('child_process').execSync;
|
||||
var exec = require('child_process').exec;
|
||||
var spawn = require('child_process').spawn;
|
||||
var httpProxy = require('http-proxy');
|
||||
var proxy = httpProxy.createProxyServer({})
|
||||
var ejs = require('ejs');
|
||||
var CircularJSON = require('circular-json');
|
||||
module.exports = function(s,config,lang,app){
|
||||
/**
|
||||
* Page : Get Embed Stream
|
||||
*/
|
||||
app.get([config.webPaths.apiPrefix+':auth/embed/:ke/:id',config.webPaths.apiPrefix+':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].isStarted === true){
|
||||
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])),originalURL:s.getOriginalUrl(req)});
|
||||
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);
|
||||
});
|
||||
/**
|
||||
* API : Get Poseidon MP4 Stream
|
||||
*/
|
||||
app.get([config.webPaths.apiPrefix+':auth/mp4/:ke/:id/:channel/s.mp4',config.webPaths.apiPrefix+':auth/mp4/:ke/:id/s.mp4',config.webPaths.apiPrefix+':auth/mp4/:ke/:id/:channel/s.ts',config.webPaths.apiPrefix+':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);
|
||||
var ip = s.getClientIp(req)
|
||||
s.camera('watch_on',{
|
||||
id : req.params.id,
|
||||
ke : req.params.ke
|
||||
},{
|
||||
id : req.params.auth + ip + req.headers['user-agent']
|
||||
})
|
||||
res.on('close', () => {
|
||||
try{
|
||||
mp4frag.unpipe(res)
|
||||
}catch(err){}
|
||||
s.camera('watch_off',{
|
||||
id : req.params.id,
|
||||
ke : req.params.ke
|
||||
},{
|
||||
id : req.params.auth + ip + req.headers['user-agent']
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
},res,req);
|
||||
},res,req);
|
||||
});
|
||||
/**
|
||||
* API and Page : Get MJPEG Stream or Page
|
||||
* @param {string} full - if `true` page will load the MJPEG iframe page
|
||||
*/
|
||||
app.get([config.webPaths.apiPrefix+':auth/mjpeg/:ke/:id',config.webPaths.apiPrefix+':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,originalURL:s.getOriginalUrl(req)});
|
||||
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");
|
||||
var ip = s.getClientIp(req)
|
||||
s.camera('watch_on',{
|
||||
id : req.params.id,
|
||||
ke : req.params.ke
|
||||
},{
|
||||
id : req.params.auth + ip + req.headers['user-agent']
|
||||
})
|
||||
Emitter.on('data',contentWriter=function(d){
|
||||
content = d;
|
||||
res.write(content,'binary');
|
||||
})
|
||||
res.on('close', function () {
|
||||
Emitter.removeListener('data',contentWriter)
|
||||
s.camera('watch_off',{
|
||||
id : req.params.id,
|
||||
ke : req.params.ke
|
||||
},{
|
||||
id : req.params.auth + ip + req.headers['user-agent']
|
||||
})
|
||||
});
|
||||
}else{
|
||||
res.end();
|
||||
}
|
||||
},res,req);
|
||||
},res,req);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* API : Get HLS Stream
|
||||
*/
|
||||
app.get([config.webPaths.apiPrefix+':auth/hls/:ke/:id/:file',config.webPaths.apiPrefix+':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);
|
||||
})
|
||||
/**
|
||||
* API : Get JPEG Snapshot
|
||||
*/
|
||||
app.get(config.webPaths.apiPrefix+':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);
|
||||
});
|
||||
/**
|
||||
* API : Get FLV Stream
|
||||
*/
|
||||
app.get([config.webPaths.apiPrefix+':auth/flv/:ke/:id/s.flv',config.webPaths.apiPrefix+':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])
|
||||
var ip = s.getClientIp(req)
|
||||
s.camera('watch_on',{
|
||||
id : req.params.id,
|
||||
ke : req.params.ke
|
||||
},{
|
||||
id : req.params.auth + ip + req.headers['user-agent']
|
||||
})
|
||||
//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)
|
||||
s.camera('watch_off',{
|
||||
id : req.params.id,
|
||||
ke : req.params.ke
|
||||
},{
|
||||
id : req.params.auth + ip + req.headers['user-agent']
|
||||
})
|
||||
})
|
||||
}else{
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(s.prettyPrint({ok:false,msg:'FLV not started or not ready'}))
|
||||
}
|
||||
},res,req)
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Get H.265/h265 HEVC stream
|
||||
*/
|
||||
app.get([config.webPaths.apiPrefix+':auth/h265/:ke/:id/s.hevc',config.webPaths.apiPrefix+':auth/h265/:ke/:id/:channel/s.hevc'], 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
|
||||
}
|
||||
//variable name of contentWriter
|
||||
var contentWriter
|
||||
//set headers
|
||||
res.setHeader('Content-Type', 'video/mp4');
|
||||
res.setHeader('Access-Control-Allow-Origin','*');
|
||||
var ip = s.getClientIp(req)
|
||||
s.camera('watch_on',{
|
||||
id : req.params.id,
|
||||
ke : req.params.ke
|
||||
},{
|
||||
id : req.params.auth + ip + req.headers['user-agent']
|
||||
})
|
||||
//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)
|
||||
s.camera('watch_off',{
|
||||
id : req.params.id,
|
||||
ke : req.params.ke
|
||||
},{
|
||||
id : req.params.auth + ip + req.headers['user-agent']
|
||||
})
|
||||
})
|
||||
},res,req)
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Get H.264 over HTTP
|
||||
*/
|
||||
app.get([
|
||||
config.webPaths.apiPrefix+':auth/mpegts/:ke/:id/:feed/:file',
|
||||
config.webPaths.apiPrefix+':auth/mpegts/:ke/:id/:feed/',
|
||||
config.webPaths.apiPrefix+':auth/h264/:ke/:id/:feed/:file',
|
||||
config.webPaths.apiPrefix+':auth/h264/:ke/:id/:feed',
|
||||
config.webPaths.apiPrefix+':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]
|
||||
}
|
||||
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',
|
||||
})
|
||||
var ip = s.getClientIp(req)
|
||||
s.camera('watch_on',{
|
||||
id : req.params.id,
|
||||
ke : req.params.ke
|
||||
},{
|
||||
id : req.params.auth + ip + req.headers['user-agent']
|
||||
})
|
||||
Emitter.on('data',contentWriter=function(buffer){
|
||||
res.write(buffer)
|
||||
})
|
||||
res.on('close', function () {
|
||||
Emitter.removeListener('data',contentWriter)
|
||||
s.camera('watch_off',{
|
||||
id : req.params.id,
|
||||
ke : req.params.ke
|
||||
},{
|
||||
id : req.params.auth + ip + req.headers['user-agent']
|
||||
})
|
||||
})
|
||||
},res,req);
|
||||
},res,req);
|
||||
});
|
||||
}
|
411
libs/webServerSuperPaths.js
Normal file
411
libs/webServerSuperPaths.js
Normal file
|
@ -0,0 +1,411 @@
|
|||
var fs = require('fs');
|
||||
var os = require('os');
|
||||
var moment = require('moment')
|
||||
var request = require('request')
|
||||
var jsonfile = require("jsonfile")
|
||||
var exec = require('child_process').exec;
|
||||
var spawn = require('child_process').spawn;
|
||||
var execSync = require('child_process').execSync;
|
||||
module.exports = function(s,config,lang,app){
|
||||
/**
|
||||
* API : Superuser : Get Logs
|
||||
*/
|
||||
app.all([config.webPaths.supersuperApiPrefix+':auth/logs'], function (req,res){
|
||||
req.ret={ok:false};
|
||||
s.superAuth(req.params,function(resp){
|
||||
req.sql='SELECT * FROM Logs WHERE ke=?';req.ar=['$'];
|
||||
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.prettyPrint(err));
|
||||
return
|
||||
}
|
||||
if(!r){r=[]}
|
||||
r.forEach(function(v,n){
|
||||
r[n].info=JSON.parse(v.info)
|
||||
})
|
||||
res.end(s.prettyPrint(r))
|
||||
})
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Superuser : Log delete.
|
||||
*/
|
||||
app.all(config.webPaths.superApiPrefix+':auth/logs/delete', function (req,res){
|
||||
s.superAuth(req.params,function(resp){
|
||||
s.sqlQuery('DELETE FROM Logs WHERE ke=?',['$'],function(){
|
||||
var endData = {
|
||||
ok : true
|
||||
}
|
||||
res.end(s.prettyPrint(endData))
|
||||
})
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Superuser : Update Shinobi
|
||||
*/
|
||||
app.all(config.webPaths.superApiPrefix+':auth/system/update', function (req,res){
|
||||
s.superAuth(req.params,function(resp){
|
||||
s.ffmpegKill()
|
||||
s.systemLog('Shinobi ordered to update',{
|
||||
by: resp.$user.mail,
|
||||
ip: resp.ip
|
||||
})
|
||||
var updateProcess = spawn('sh',(s.mainDirectory+'/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())
|
||||
})
|
||||
var endData = {
|
||||
ok : true
|
||||
}
|
||||
res.end(s.prettyPrint(endData))
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Superuser : Restart Shinobi
|
||||
*/
|
||||
app.all(config.webPaths.superApiPrefix+':auth/system/restart/:script', function (req,res){
|
||||
s.superAuth(req.params,function(resp){
|
||||
var check = function(x){return req.params.script.indexOf(x)>-1}
|
||||
var endData = {
|
||||
ok : true
|
||||
}
|
||||
if(check('system')){
|
||||
s.systemLog('Shinobi ordered to restart',{by:resp.$user.mail,ip:resp.ip})
|
||||
s.ffmpegKill()
|
||||
endData.systemOuput = execSync('pm2 restart '+s.mainDirectory+'/camera.js')
|
||||
}
|
||||
if(check('cron')){
|
||||
s.systemLog('Shinobi CRON ordered to restart',{by:resp.$user.mail,ip:resp.ip})
|
||||
endData.cronOuput = execSync('pm2 restart '+s.mainDirectory+'/cron.js')
|
||||
}
|
||||
if(check('logs')){
|
||||
s.systemLog('Flush PM2 Logs',{by:resp.$user.mail,ip:resp.ip})
|
||||
endData.logsOuput = execSync('pm2 flush')
|
||||
}
|
||||
res.end(s.prettyPrint(endData))
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Superuser : Modify Configuration (conf.json)
|
||||
*/
|
||||
app.all(config.webPaths.superApiPrefix+':auth/system/configure', function (req,res){
|
||||
s.superAuth(req.params,function(resp){
|
||||
var endData = {
|
||||
ok : true
|
||||
}
|
||||
var postBody = s.getPostData(req)
|
||||
if(!postBody){
|
||||
endData.ok = false
|
||||
endData.msg = lang.postDataBroken
|
||||
}else{
|
||||
s.systemLog('conf.json Modified',{
|
||||
by: resp.$user.mail,
|
||||
ip: resp.ip,
|
||||
old:jsonfile.readFileSync(s.location.config)
|
||||
})
|
||||
jsonfile.writeFile(s.location.config,postBody,{spaces: 2},function(){
|
||||
s.tx({f:'save_configuration'},'$')
|
||||
})
|
||||
}
|
||||
res.end(s.prettyPrint(endData))
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Superuser : Get users in system
|
||||
*/
|
||||
app.all([
|
||||
config.webPaths.superApiPrefix+':auth/accounts/list',
|
||||
config.webPaths.superApiPrefix+':auth/accounts/list/:type',
|
||||
], function (req,res){
|
||||
s.superAuth(req.params,function(resp){
|
||||
var endData = {
|
||||
ok : true
|
||||
}
|
||||
searchQuery = 'SELECT ke,uid,auth,mail,details FROM Users'
|
||||
queryVals = []
|
||||
switch(req.params.type){
|
||||
case'admin':case'administrator':
|
||||
searchQuery += ' WHERE details NOT LIKE ?'
|
||||
queryVals.push('%"sub"%')
|
||||
break;
|
||||
case'sub':case'subaccount':
|
||||
searchQuery += ' WHERE details LIKE ?'
|
||||
queryVals.push('%"sub"%')
|
||||
break;
|
||||
}
|
||||
// ' WHERE details NOT LIKE ?'
|
||||
s.sqlQuery(searchQuery,queryVals,function(err,users) {
|
||||
endData.users = users
|
||||
res.end(s.prettyPrint(endData))
|
||||
})
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Superuser : Save Superuser Preferences
|
||||
*/
|
||||
app.all(config.webPaths.superApiPrefix+':auth/accounts/saveSettings', function (req,res){
|
||||
s.superAuth(req.params,function(resp){
|
||||
var endData = {
|
||||
ok : true
|
||||
}
|
||||
var form = s.getPostData(req)
|
||||
if(form){
|
||||
var currentSuperUserList = jsonfile.readFileSync(s.location.super)
|
||||
var currentSuperUser = {}
|
||||
var currentSuperUserPosition = -1
|
||||
//find this user in current list
|
||||
currentSuperUserList.forEach(function(user,pos){
|
||||
if(user.mail === resp.$user.mail){
|
||||
currentSuperUser = user
|
||||
currentSuperUserPosition = pos
|
||||
}
|
||||
})
|
||||
var logDetails = {
|
||||
by : resp.$user.mail,
|
||||
ip : resp.ip
|
||||
}
|
||||
//check if pass and pass_again match, if not remove password
|
||||
if(form.pass !== '' && form.pass === form.pass_again){
|
||||
form.pass = s.createHash(form.pass)
|
||||
}else{
|
||||
delete(form.pass)
|
||||
}
|
||||
//delete pass_again from object
|
||||
delete(form.pass_again)
|
||||
//set new values
|
||||
currentSuperUser = Object.assign(currentSuperUser,form)
|
||||
//reset email and log change of email
|
||||
if(form.mail !== resp.$user.mail){
|
||||
logDetails.newEmail = form.mail
|
||||
logDetails.oldEmail = resp.$user.mail
|
||||
}
|
||||
//log this change
|
||||
s.systemLog('super.json Modified',logDetails)
|
||||
//modify or add account in temporary master list
|
||||
if(currentSuperUserList[currentSuperUserPosition]){
|
||||
currentSuperUserList[currentSuperUserPosition] = currentSuperUser
|
||||
}else{
|
||||
currentSuperUserList.push(currentSuperUser)
|
||||
}
|
||||
//update master list in system
|
||||
jsonfile.writeFile(s.location.super,currentSuperUserList,{spaces: 2},function(){
|
||||
s.tx({f:'save_preferences'},'$')
|
||||
})
|
||||
}else{
|
||||
endData.ok = false
|
||||
endData.msg = lang.postDataBroken
|
||||
}
|
||||
res.end(s.prettyPrint(endData))
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Superuser : Create Admin account (Account to manage cameras)
|
||||
*/
|
||||
app.all(config.webPaths.superApiPrefix+':auth/accounts/registerAdmin', function (req,res){
|
||||
s.superAuth(req.params,function(resp){
|
||||
var endData = {
|
||||
ok : false
|
||||
}
|
||||
var close = function(){
|
||||
res.end(s.prettyPrint(endData))
|
||||
}
|
||||
var isCallbacking = false
|
||||
var form = s.getPostData(req)
|
||||
if(form){
|
||||
if(form.mail !== '' && form.pass !== ''){
|
||||
if(form.pass === form.password_again || form.pass === form.pass_again){
|
||||
isCallbacking = true
|
||||
s.sqlQuery('SELECT * FROM Users WHERE mail=?',[form.mail],function(err,r) {
|
||||
if(r&&r[0]){
|
||||
//found address already exists
|
||||
endData.msg = lang['Email address is in use.'];
|
||||
}else{
|
||||
endData.ok = true
|
||||
//create new
|
||||
//user id
|
||||
form.uid = s.gid()
|
||||
//check to see if custom key set
|
||||
if(!form.ke||form.ke===''){
|
||||
form.ke=s.gid()
|
||||
}else{
|
||||
form.ke = form.ke.replace(/[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, '')
|
||||
}
|
||||
//check if "details" is object
|
||||
if(form.details instanceof Object){
|
||||
form.details = JSON.stringify(form.details)
|
||||
}
|
||||
//write user to db
|
||||
s.sqlQuery(
|
||||
'INSERT INTO Users (ke,uid,mail,pass,details) VALUES (?,?,?,?,?)',
|
||||
[
|
||||
form.ke,
|
||||
form.uid,
|
||||
form.mail,
|
||||
s.createHash(form.pass),
|
||||
form.details
|
||||
]
|
||||
)
|
||||
s.tx({f:'add_account',details:form.details,ke:form.ke,uid:form.uid,mail:form.mail},'$')
|
||||
//init user
|
||||
s.loadGroup(form)
|
||||
}
|
||||
close()
|
||||
})
|
||||
}else{
|
||||
endData.msg = lang["Passwords Don't Match"]
|
||||
}
|
||||
}else{
|
||||
endData.msg = lang['Email and Password fields cannot be empty']
|
||||
}
|
||||
}else{
|
||||
endData.msg = lang.postDataBroken
|
||||
}
|
||||
if(isCallbacking === false)close()
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Superuser : Edit Admin account (Account to manage cameras)
|
||||
*/
|
||||
app.all(config.webPaths.superApiPrefix+':auth/accounts/editAdmin', function (req,res){
|
||||
s.superAuth(req.params,function(resp){
|
||||
var endData = {
|
||||
ok : false
|
||||
}
|
||||
var close = function(){
|
||||
res.end(s.prettyPrint(endData))
|
||||
}
|
||||
var form = s.getPostData(req)
|
||||
if(form){
|
||||
var account = s.getPostData(req,'account')
|
||||
s.sqlQuery('SELECT * FROM Users WHERE mail=?',[account.mail],function(err,r) {
|
||||
if(r && r[0]){
|
||||
r = r[0]
|
||||
var details = JSON.parse(r.details)
|
||||
if(form.pass && form.pass !== ''){
|
||||
if(form.pass === form.password_again){
|
||||
form.pass = s.createHash(form.pass);
|
||||
}else{
|
||||
endData.msg = lang["Passwords Don't Match"]
|
||||
close()
|
||||
return
|
||||
}
|
||||
}else{
|
||||
delete(form.pass);
|
||||
}
|
||||
delete(form.password_again);
|
||||
var keys = Object.keys(form)
|
||||
var set = []
|
||||
var values = []
|
||||
keys.forEach(function(v,n){
|
||||
if(set==='ke'||set==='password_again'||!form[v]){return}
|
||||
set.push(v+'=?')
|
||||
if(v === 'details'){
|
||||
form[v] = s.stringJSON(Object.assign(details,s.parseJSON(form[v])))
|
||||
}
|
||||
values.push(form[v])
|
||||
})
|
||||
values.push(account.mail)
|
||||
s.sqlQuery('UPDATE Users SET '+set.join(',')+' WHERE mail=?',values,function(err,r) {
|
||||
if(err){
|
||||
console.log(err)
|
||||
endData.error = err
|
||||
endData.msg = lang.AccountEditText1
|
||||
}else{
|
||||
endData.ok = true
|
||||
s.tx({f:'edit_account',form:form,ke:account.ke,uid:account.uid},'$')
|
||||
delete(s.group[account.ke].init);
|
||||
s.loadGroupApps(account)
|
||||
}
|
||||
close()
|
||||
})
|
||||
}
|
||||
})
|
||||
}else{
|
||||
endData.msg = lang.postDataBroken
|
||||
close()
|
||||
}
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Superuser : Delete Admin account (Account to manage cameras)
|
||||
*/
|
||||
app.all(config.webPaths.superApiPrefix+':auth/accounts/deleteAdmin', function (req,res){
|
||||
s.superAuth(req.params,function(resp){
|
||||
var endData = {
|
||||
ok : true
|
||||
}
|
||||
var close = function(){
|
||||
res.end(s.prettyPrint(endData))
|
||||
}
|
||||
var account = s.getPostData(req,'account')
|
||||
s.sqlQuery('DELETE FROM Users WHERE uid=? AND ke=? AND mail=?',[account.uid,account.ke,account.mail])
|
||||
s.sqlQuery('DELETE FROM API WHERE uid=? AND ke=?',[account.uid,account.ke])
|
||||
if(s.getPostData(req,'deleteSubAccounts',false) === '1'){
|
||||
s.sqlQuery('DELETE FROM Users WHERE ke=?',[account.ke])
|
||||
}
|
||||
if(s.getPostData(req,'deleteMonitors',false) == '1'){
|
||||
s.sqlQuery('SELECT * FROM Monitors WHERE ke=?',[account.ke],function(err,monitors){
|
||||
if(monitors && monitors[0]){
|
||||
monitors.forEach(function(monitor){
|
||||
s.camera('stop',monitor)
|
||||
})
|
||||
s.sqlQuery('DELETE FROM Monitors WHERE ke=?',[account.ke])
|
||||
}
|
||||
})
|
||||
}
|
||||
if(s.getPostData(req,'deleteVideos',false) == '1'){
|
||||
s.sqlQuery('DELETE FROM Videos WHERE ke=?',[account.ke])
|
||||
fs.chmod(s.dir.videos+account.ke,0o777,function(err){
|
||||
fs.unlink(s.dir.videos+account.ke,function(err){})
|
||||
})
|
||||
}
|
||||
if(s.getPostData(req,'deleteEvents',false) == '1'){
|
||||
s.sqlQuery('DELETE FROM Events WHERE ke=?',[account.ke])
|
||||
}
|
||||
s.tx({f:'delete_account',ke:account.ke,uid:account.uid,mail:account.mail},'$')
|
||||
close()
|
||||
},res,req)
|
||||
})
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue