UNPKG

shinobi

Version:
992 lines (987 loc) 178 kB
// // Shinobi // Copyright (C) 2016 Moe Alam, moeiscool // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // # Donate // // If you like what I am doing here and want me to continue please consider donating :) // PayPal : paypal@m03.ca // process.on('uncaughtException', function (err) { console.error('uncaughtException',err); }); var fs = require('fs'); var os = require('os'); var URL = require('url'); var path = require('path'); var mysql = require('mysql'); var moment = require('moment'); var request = require("request"); var express = require('express'); var app = express(); var appHTTPS = express(); var http = require('http'); var https = require('https'); var server = http.createServer(app); var bodyParser = require('body-parser'); var CircularJSON = require('circular-json'); var ejs = require('ejs'); var io = new (require('socket.io'))(); var execSync = require('child_process').execSync; var exec = require('child_process').exec; var spawn = require('child_process').spawn; var crypto = require('crypto'); var webdav = require("webdav"); var connectionTester = require('connection-tester'); var events = require('events'); var df = require('node-df'); var Cam = require('onvif').Cam; var config = require('./conf.json'); if(!config.language){ config.language='en_CA' } if(config.language.split('_')[0]==='he'){config.language=='ar'} try{ var lang = require('./languages/'+config.language+'.json'); }catch(er){ console.error(er) console.log('There was an error loading your language file.') var lang = require('./languages/en_CA.json'); } process.send = process.send || function () {}; if(config.mail){ var nodemailer = require('nodemailer').createTransport(config.mail); } //config defaults if(config.cpuUsageMarker===undefined){config.cpuUsageMarker='%Cpu'} 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.deleteOverMax===undefined)config.cron.deleteOverMax=true; if(config.cron.deleteOverMaxOffset===undefined)config.cron.deleteOverMaxOffset=0.9; if(config.pluginKeys===undefined)config.pluginKeys={}; s={factorAuth:{},child_help:false,totalmem:os.totalmem(),platform:os.platform(),s:JSON.stringify,isWin:(process.platform==='win32')}; s.loadedLanguages={} s.loadedLanguages[config.language]=lang; s.getLanguageFile=function(rule,file){ if(rule&&rule!==''){ file=s.loadedLanguages[file] if(!file){ try{ s.loadedLanguages[rule]=require('./languages/'+rule+'.json') file=s.loadedLanguages[rule] }catch(err){ file=lang } } }else{ file=lang } return file } s.systemLog=function(q,w,e){ if(!w){w=''} if(!e){e=''} if(config.systemLog===true){ return console.log(moment().format(),q,w,e) } } s.disc=function(){ sql = mysql.createConnection(config.db); sql.connect(function(err){if(err){s.systemLog(lang['Error Connecting']+' : DB',err);setTimeout(s.disc, 2000);}}); sql.on('error',function(err) {s.systemLog(lang['DB Lost.. Retrying..']);s.systemLog(err);s.disc();return;}); } s.disc(); //kill any ffmpeg running s.ffmpegKill=function(){exec("ps aux | grep -ie ffmpeg | awk '{print $2}' | xargs kill -9",{detached: true})}; process.on('exit',s.ffmpegKill.bind(null,{cleanup:true})); process.on('SIGINT',s.ffmpegKill.bind(null, {exit:true})); //key for child servers s.child_nodes={}; s.child_key='3123asdasdf1dtj1hjk23sdfaasd12asdasddfdbtnkkfgvesra3asdsd3123afdsfqw345'; s.checkRelativePath=function(x){ if(x.charAt(0)!=='/'){ x=__dirname+'/'+x } return x } s.checkCorrectPathEnding=function(x){ var length=x.length if(x.charAt(length-1)!=='/'){ x=x+'/' } return x } s.md5=function(x){return crypto.createHash('md5').update(x).digest("hex");} s.tx=function(z,y,x){if(x){return x.broadcast.to(y).emit('f',z)};io.to(y).emit('f',z);} s.cx=function(z,y,x){if(x){return x.broadcast.to(y).emit('c',z)};io.to(y).emit('c',z);} s.txWithSubPermissions=function(z,y,permissionChoices){ if(typeof permissionChoices==='string'){ permissionChoices=[permissionChoices] } if(s.group[z.ke]){ Object.keys(s.group[z.ke].users).forEach(function(v){ var user = s.group[z.ke].users[v] if(user.details.sub){ if(user.details.allmonitors!=='1'){ var valid=0 var checked=permissionChoices.length permissionChoices.forEach(function(b){ if(user.details[b].indexOf(z.mid)!==-1){ ++valid } }) if(valid===checked){ s.tx(z,user.cnid) } }else{ s.tx(z,user.cnid) } }else{ s.tx(z,user.cnid) } }) } } //load camera controller vars s.nameToTime=function(x){x=x.split('.')[0].split('T'),x[1]=x[1].replace(/-/g,':');x=x.join(' ');return x;} s.ratio=function(width,height,ratio){ratio = width / height;return ( Math.abs( ratio - 4 / 3 ) < Math.abs( ratio - 16 / 9 ) ) ? '4:3' : '16:9';} s.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.moment_withOffset=function(e,x){ if(!e){e=new Date};if(!x){x='YYYY-MM-DDTHH-mm-ss'}; e=moment(e);if(config.utcOffset){e=e.utcOffset(config.utcOffset)} return e.format(x); } s.moment=function(e,x){ if(!e){e=new Date};if(!x){x='YYYY-MM-DDTHH-mm-ss'}; return moment(e).format(x); } 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.kill=function(x,e,p){ if(s.group[e.ke]&&s.group[e.ke].mon[e.id]&&s.group[e.ke].mon[e.id].spawn !== undefined){ if(s.group[e.ke].mon[e.id].spawn){ try{ s.group[e.ke].mon[e.id].spawn.removeListener('end',s.group[e.ke].mon[e.id].spawn_exit); s.group[e.ke].mon[e.id].spawn.removeListener('exit',s.group[e.ke].mon[e.id].spawn_exit); delete(s.group[e.ke].mon[e.id].spawn_exit); }catch(er){} } clearTimeout(s.group[e.ke].mon[e.id].checker); delete(s.group[e.ke].mon[e.id].checker); clearTimeout(s.group[e.ke].mon[e.id].watchdog_stop); delete(s.group[e.ke].mon[e.id].watchdog_stop); if(e&&s.group[e.ke].mon[e.id].record){ clearTimeout(s.group[e.ke].mon[e.id].record.capturing); // if(s.group[e.ke].mon[e.id].record.request){s.group[e.ke].mon[e.id].record.request.abort();delete(s.group[e.ke].mon[e.id].record.request);} }; if(s.group[e.ke].mon[e.id].child_node){ s.cx({f:'kill',d:s.init('noReference',e)},s.group[e.ke].mon[e.id].child_node_id) }else{ if(!x||x===1){return}; p=x.pid; if(s.group[e.ke].mon_conf[e.id].type===('dashcam'||'socket'||'jpeg'||'pipe')){ x.stdin.pause();setTimeout(function(){x.kill('SIGTERM');delete(x);},500) }else{ try{ x.stdin.setEncoding('utf8');x.stdin.write('q'); }catch(er){} } setTimeout(function(){exec('kill -9 '+p,{detached: true})},1000) } } } s.log=function(e,x){ if(!x||!e.mid){return} if(e.details&&e.details.sqllog==1){ sql.query('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:moment()},'GRP_'+e.ke); // s.systemLog('s.log : ',{f:'log',ke:e.ke,mid:e.mid,log:x,time:moment()},'GRP_'+e.ke) } //SSL options if(config.ssl&&config.ssl.key&&config.ssl.cert){ config.ssl.key=fs.readFileSync(s.checkRelativePath(config.ssl.key),'utf8') config.ssl.cert=fs.readFileSync(s.checkRelativePath(config.ssl.cert),'utf8') if(config.ssl.port===undefined){ config.ssl.port=443 } if(config.ssl.bindip===undefined){ config.ssl.bindip=config.bindip } if(config.ssl.ca&&config.ssl.ca instanceof Array){ config.ssl.ca.forEach(function(v,n){ config.ssl.ca[n]=fs.readFileSync(s.checkRelativePath(v),'utf8') }) } var serverHTTPS = https.createServer(config.ssl,app); serverHTTPS.listen(config.ssl.port,config.bindip,function(){ console.log('SSL '+lang.Shinobi+' - SSL PORT : '+config.ssl.port); }); io.attach(serverHTTPS); } //start HTTP server.listen(config.port,config.bindip,function(){ console.log(lang.Shinobi+' - PORT : '+config.port); }); io.attach(server); console.log('NODE.JS : '+execSync("node -v")) //ffmpeg location if(!config.ffmpegDir){ if(s.isWin===true){ config.ffmpegDir=__dirname+'/ffmpeg/ffmpeg.exe' }else{ config.ffmpegDir='ffmpeg' } } //directories s.group={}; if(!config.windowsTempDir&&s.isWin===true){config.windowsTempDir='C:/Windows/Temp'} if(!config.defaultMjpeg){config.defaultMjpeg=__dirname+'/web/libs/img/bg.jpg'} //default stream folder check if(!config.streamDir){ if(s.isWin===false){ config.streamDir='/dev/shm' }else{ config.streamDir=config.windowsTempDir } if(!fs.existsSync(config.streamDir)){ config.streamDir=__dirname+'/streams/' }else{ config.streamDir+='/streams/' } } if(!config.videosDir){config.videosDir=__dirname+'/videos/'} s.dir={videos:config.videosDir,streams:config.streamDir,languages:'./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); } ////Camera Controller s.init=function(x,e,k,fn){ if(!e){e={}} if(!k){k={}} switch(x){ case 0://camera if(!s.group[e.ke]){s.group[e.ke]={}}; if(!s.group[e.ke].mon){s.group[e.ke].mon={}} if(!s.group[e.ke].users){s.group[e.ke].users={}} if(!s.group[e.ke].mon[e.mid]){s.group[e.ke].mon[e.mid]={}} if(!s.group[e.ke].mon[e.mid].watch){s.group[e.ke].mon[e.mid].watch={}}; if(!s.group[e.ke].mon[e.mid].fixingVideos){s.group[e.ke].mon[e.mid].fixingVideos={}}; if(!s.group[e.ke].mon[e.mid].record){s.group[e.ke].mon[e.mid].record={yes:e.record}}; if(!s.group[e.ke].mon[e.mid].started){s.group[e.ke].mon[e.mid].started=0}; if(s.group[e.ke].mon[e.mid].delete){clearTimeout(s.group[e.ke].mon[e.mid].delete)} if(!s.group[e.ke].mon_conf){s.group[e.ke].mon_conf={}} s.init('apps',e) break; case'apps': if(!s.group[e.ke].init){ s.group[e.ke].init={}; } if(!s.group[e.ke].webdav||!s.group[e.ke].init.size){ sql.query('SELECT * FROM Users WHERE ke=? AND details NOT LIKE ?',[e.ke,'%"sub"%'],function(ar,r){ if(r&&r[0]){ r=r[0]; ar=JSON.parse(r.details); //owncloud/webdav if(ar.webdav_user&& ar.webdav_user!==''&& ar.webdav_pass&& ar.webdav_pass!==''&& ar.webdav_url&& ar.webdav_url!=='' ){ if(!ar.webdav_dir||ar.webdav_dir===''){ ar.webdav_dir='/'; if(ar.webdav_dir.slice(-1)!=='/'){ar.webdav_dir+='/';} } s.group[e.ke].webdav = webdav( ar.webdav_url, ar.webdav_user, ar.webdav_pass ); } Object.keys(ar).forEach(function(v){ s.group[e.ke].init[v]=ar[v] }) } }); } break; case'sync': e.cn=Object.keys(s.child_nodes); e.cn.forEach(function(v){ if(s.group[e.ke]){ s.cx({f:'sync',sync:s.init('noReference',s.group[e.ke].mon[e.mid]),ke:e.ke,mid:e.mid},s.child_nodes[v].cnid); } }); break; case'noReference': x={keys:Object.keys(e),ar:{}}; x.keys.forEach(function(v){ if(v!=='last_frame'&&v!=='record'&&v!=='spawn'&&v!=='running'&&(v!=='time'&&typeof e[v]!=='function')){x.ar[v]=e[v];} }); return x.ar; break; case'url': e.authd=''; if(e.details.muser&&e.details.muser!==''&&e.host.indexOf('@')===-1) { e.authd=e.details.muser+':'+e.details.mpass+'@'; } if(e.port==80&&e.details.port_force!=='1'){e.porty=''}else{e.porty=':'+e.port} e.url=e.protocol+'://'+e.authd+e.host+e.porty+e.path;return e.url; break; case'url_no_path': e.authd=''; if(!e.details.muser){e.details.muser=''} if(!e.details.mpass){e.details.mpass=''} if(e.details.muser!==''&&e.host.indexOf('@')===-1) { e.authd=e.details.muser+':'+e.details.mpass+'@'; } if(e.port==80&&e.details.port_force!=='1'){e.porty=''}else{e.porty=':'+e.port} e.url=e.protocol+'://'+e.authd+e.host+e.porty;return e.url; break; case'diskUsed': if(s.group[e.ke]&&s.group[e.ke].init){ s.tx({f:'diskUsed',size:s.group[e.ke].init.used_space,limit:s.group[e.ke].init.size},'GRP_'+e.ke); } break; } if(typeof e.callback==='function'){setTimeout(function(){e.callback()},500);} } s.filter=function(x,d){ switch(x){ case'archive': d.videos.forEach(function(v,n){ s.video('archive',v) }) break; case'email': if(d.videos&&d.videos.length>0){ d.videos.forEach(function(v,n){ }) d.mailOptions = { from: '"ShinobiCCTV" <no-reply@shinobi.video>', // sender address to: d.mail, // list of receivers subject: lang['Filter Matches']+' : '+d.name, // Subject line html: lang.FilterMatchesText1+' '+d.videos.length+' '+lang.FilterMatchesText2, }; if(d.execute&&d.execute!==''){ d.mailOptions.html+='<div><b>'+lang.Executed+' :</b> '+d.execute+'</div>' } if(d.delete==='1'){ d.mailOptions.html+='<div><b>'+lang.Deleted+' :</b> '+lang.Yes+'</div>' } d.mailOptions.html+='<div><b>'+lang.Query+' :</b> '+d.query+'</div>' d.mailOptions.html+='<div><b>'+lang['Filter ID']+' :</b> '+d.id+'</div>' nodemailer.sendMail(d.mailOptions, (error, info) => { if (error) { s.tx({f:'error',ff:'filter_mail',ke:d.ke,error:error},'GRP_'+d.ke); return ; } s.tx({f:'filter_mail',ke:d.ke,info:info},'GRP_'+d.ke); }); } break; case'delete': d.videos.forEach(function(v,n){ s.video('delete',v) }) break; case'execute': exec(d.execute,{detached: true}) break; } } s.video=function(x,e){ if(!e){e={}}; k={} if(e.mid&&!e.id){e.id=e.mid}; switch(x){ case'fix': e.dir=s.dir.videos+e.ke+'/'+e.id+'/'; e.sdir=s.dir.streams+e.ke+'/'+e.id+'/'; if(!e.filename&&e.time){e.filename=s.moment(e.time)} if(e.filename.indexOf('.')===-1){ e.filename=e.filename+'.'+e.ext } s.tx({f:'video_fix_start',mid:e.mid,ke:e.ke,filename:e.filename},'GRP_'+e.ke) s.group[e.ke].mon[e.id].fixingVideos[e.filename]={} switch(e.ext){ case'mp4': e.fixFlags='-vcodec libx264 -acodec aac -strict -2'; break; case'webm': e.fixFlags='-vcodec libvpx -acodec libvorbis'; break; } e.spawn=spawn(config.ffmpegDir,('-i '+e.dir+e.filename+' '+e.fixFlags+' '+e.sdir+e.filename).split(' '),{detached: true}) e.spawn.stdout.on('data',function(data){ s.tx({f:'video_fix_data',mid:e.mid,ke:e.ke,filename:e.filename},'GRP_'+e.ke) }); e.spawn.on('close',function(data){ exec('mv '+e.dir+e.filename+' '+e.sdir+e.filename,{detached: true}).on('exit',function(){ s.tx({f:'video_fix_success',mid:e.mid,ke:e.ke,filename:e.filename},'GRP_'+e.ke) delete(s.group[e.ke].mon[e.id].fixingVideos[e.filename]); }) }); break; case'archive': e.dir=s.dir.videos+e.ke+'/'+e.id+'/'; if(!e.filename&&e.time){e.filename=s.moment(e.time)} if(!e.status){e.status=0} e.save=[e.id,e.ke,s.nameToTime(e.filename)]; sql.query('UPDATE Videos SET status=3 WHERE `mid`=? AND `ke`=? AND `time`=?',e.save,function(err,r){ s.tx({f:'video_edit',status:3,filename:e.filename+'.'+e.ext,mid:e.mid,ke:e.ke,time:s.nameToTime(e.filename)},'GRP_'+e.ke); }); break; case'delete': e.dir=s.dir.videos+e.ke+'/'+e.id+'/'; if(!e.filename&&e.time){e.filename=s.moment(e.time)} if(!e.status){e.status=0} e.save=[e.id,e.ke,s.nameToTime(e.filename)]; sql.query('DELETE FROM Videos WHERE `mid`=? AND `ke`=? AND `time`=?',e.save,function(err,r){ fs.stat(e.dir+e.filename+'.'+e.ext,function(err,file){ if(err){ return s.systemLog(err) } s.group[e.ke].init.used_space=s.group[e.ke].init.used_space-(file.size/1000000) s.init('diskUsed',e) }) s.tx({f:'video_delete',filename:e.filename+'.'+e.ext,mid:e.mid,ke:e.ke,time:s.nameToTime(e.filename),end:s.moment(new Date,'YYYY-MM-DD HH:mm:ss')},'GRP_'+e.ke); s.file('delete',e.dir+e.filename+'.'+e.ext) }) break; case'open': e.save=[e.id,e.ke,s.nameToTime(e.filename),e.ext]; if(!e.status){e.save.push(0)}else{e.save.push(e.status)} sql.query('INSERT INTO Videos (mid,ke,time,ext,status) VALUES (?,?,?,?,?)',e.save) s.tx({f:'video_build_start',filename:e.filename+'.'+e.ext,mid:e.id,ke:e.ke,time:s.nameToTime(e.filename),end:s.moment(new Date,'YYYY-MM-DD HH:mm:ss')},'GRP_'+e.ke); break; case'close': e.dir=s.dir.videos+e.ke+'/'+e.id+'/'; if(s.group[e.ke]&&s.group[e.ke].mon[e.id]){ if(s.group[e.ke].mon[e.id].open&&!e.filename){e.filename=s.group[e.ke].mon[e.id].open;e.ext=s.group[e.ke].mon[e.id].open_ext} if(s.group[e.ke].mon[e.id].child_node){ s.cx({f:'close',d:s.init('noReference',e)},s.group[e.ke].mon[e.id].child_node_id); }else{ if(fs.existsSync(e.dir+e.filename+'.'+e.ext)===true){ k.stat=fs.statSync(e.dir+e.filename+'.'+e.ext); e.filesize=k.stat.size; e.filesizeMB=parseFloat((e.filesize/1000000).toFixed(2)); e.end_time=s.moment(k.stat.mtime,'YYYY-MM-DD HH:mm:ss'); if(config.deleteCorruptFiles===true&&e.filesizeMB<0.05){ s.video('delete',e); s.log(e,{type:'File Corrupt',msg:{ffmpeg:s.group[e.ke].mon[e.mid].ffmpeg,filesize:e.filesizeMB}}) }else{ e.save=[e.filesize,1,e.end_time,e.id,e.ke,s.nameToTime(e.filename)]; if(!e.status){e.save.push(0)}else{e.save.push(e.status)} sql.query('UPDATE Videos SET `size`=?,`status`=?,`end`=? WHERE `mid`=? AND `ke`=? AND `time`=? AND `status`=?',e.save) s.txWithSubPermissions({f:'video_build_success',hrefNoAuth:'/videos/'+e.ke+'/'+e.mid+'/'+e.filename+'.'+e.ext,filename:e.filename+'.'+e.ext,mid:e.id,ke:e.ke,time:moment(s.nameToTime(e.filename)).format(),size:e.filesize,end:moment(e.end_time).format()},'GRP_'+e.ke,'video_view'); //cloud auto savers //webdav if(s.group[e.ke].webdav&&s.group[e.ke].init.use_webdav!=='0'&&s.group[e.ke].init.webdav_save=="1"){ fs.readFile(e.dir+e.filename+'.'+e.ext,function(err,data){ s.group[e.ke].webdav.putFileContents(s.group[e.ke].init.webdav_dir+e.ke+'/'+e.mid+'/'+e.filename+'.'+e.ext,"binary",data) .catch(function(err) { s.log(e,{type:lang['Webdav Error'],msg:{msg:lang.WebdavErrorText+' <b>/'+e.ke+'/'+e.id+'</b>',info:err},ffmpeg:s.group[e.ke].mon[e.id].ffmpeg}) console.error(err); }); }); } if(s.group[e.ke].init){ if(!s.group[e.ke].init.used_space){s.group[e.ke].init.used_space=0}else{s.group[e.ke].init.used_space=parseFloat(s.group[e.ke].init.used_space)} s.group[e.ke].init.used_space=s.group[e.ke].init.used_space+e.filesizeMB; if(config.cron.deleteOverMax===true&&s.group[e.ke].checkSpaceLock!==1){ //check space var check=function(){ if(s.group[e.ke].init.used_space>(s.group[e.ke].init.size*config.cron.deleteOverMaxOffset)){ s.group[e.ke].checkSpaceLock=1; sql.query('SELECT * FROM Videos WHERE status != 0 AND ke=? ORDER BY `time` ASC LIMIT 2',[e.ke],function(err,evs){ k.del=[];k.ar=[e.ke]; evs.forEach(function(ev){ ev.dir=s.dir.videos+e.ke+'/'+ev.mid+'/'+s.moment(ev.time)+'.'+ev.ext; k.del.push('(mid=? AND time=?)'); k.ar.push(ev.mid),k.ar.push(ev.time); s.file('delete',ev.dir); s.group[e.ke].init.used_space=s.group[e.ke].init.used_space-ev.size/1000000; s.tx({f:'video_delete',ff:'over_max',size:s.group[e.ke].init.used_space,limit:s.group[e.ke].init.size,filename:s.moment(ev.time)+'.'+ev.ext,mid:ev.mid,ke:ev.ke,time:ev.time,end:s.moment(new Date,'YYYY-MM-DD HH:mm:ss')},'GRP_'+e.ke); }); if(k.del.length>0){ k.qu=k.del.join(' OR '); sql.query('DELETE FROM Videos WHERE ke =? AND ('+k.qu+')',k.ar,function(){ check() }) } }) }else{ s.group[e.ke].checkSpaceLock=0 s.init('diskUsed',e) } } check() }else{ s.init('diskUsed',e) } } } }else{ s.video('delete',e); s.log(e,{type:lang['File Not Exist'],msg:lang.FileNotExistText,ffmpeg:s.group[e.ke].mon[e.id].ffmpeg}) if(e.mode&&config.restart.onVideoNotExist===true&&e.fn){ delete(s.group[e.ke].mon[e.id].open); s.log(e,{type:lang['Camera is not recording'],msg:{msg:lang.CameraNotRecordingText}}); if(s.group[e.ke].mon[e.id].started===1){ s.camera('restart',e) } } } } } delete(s.group[e.ke].mon[e.id].open); break; } } s.ffmpeg=function(e,x){ //set X for temporary values so we don't break our main monitor object. if(!x){x={tmp:''}} //set some placeholding values to avoid "undefined" in ffmpeg string. x.record_string='' x.cust_input='' x.cust_detect=' ' x.record_video_filters=[] x.stream_video_filters=[] //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 - check protocol //input switch(e.type){ case'h264': switch(e.protocol){ case'rtsp': if(e.details.rtsp_transport&&e.details.rtsp_transport!==''&&e.details.rtsp_transport!=='no'){x.cust_input+=' -rtsp_transport '+e.details.rtsp_transport;} break; } break; } //record - resolution switch(s.ratio(e.width,e.height)){ case'16:9': x.ratio='640x360'; break; default: x.ratio='640x480'; break; } 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.ratio=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)+' '; //record - check for double quotes if(e.details.dqf=='1'){ x.segment+='"'+e.dir+'%Y-%m-%dT%H-%M-%S.'+e.ext+'"'; }else{ x.segment+=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; } //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} //stream - frames per second if(!e.details.sfps||e.details.sfps===''){ e.details.sfps=parseFloat(e.details.sfps); if(isNaN(e.details.sfps)){e.details.sfps=1} } if(e.fps&&e.fps!==''){x.framerate=' -r '+e.fps}else{x.framerate=''} 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"){ x.record_video_filters.push('transpose='+e.details.rotate_record); } //check custom record filters for -vf if(e.details.vf&&e.details.vf!==''){ x.record_video_filters.push(e.details.vf) } //compile filter string for -vf if(x.record_video_filters.length>0){ x.record_video_filters=' -vf '+x.record_video_filters.join(',') }else{ x.record_video_filters='' } //stream - timestamp if(e.details.stream_timestamp&&e.details.stream_timestamp=="1"&&e.details.vcodec!=='copy'){ //font if(e.details.stream_timestamp_font&&e.details.stream_timestamp_font!==''){x.stream_timestamp_font=e.details.stream_timestamp_font}else{x.stream_timestamp_font='/usr/share/fonts/truetype/freefont/FreeSans.ttf'} //position x if(e.details.stream_timestamp_x&&e.details.stream_timestamp_x!==''){x.stream_timestamp_x=e.details.stream_timestamp_x}else{x.stream_timestamp_x='(w-tw)/2'} //position y if(e.details.stream_timestamp_y&&e.details.stream_timestamp_y!==''){x.stream_timestamp_y=e.details.stream_timestamp_y}else{x.stream_timestamp_y='0'} //text color if(e.details.stream_timestamp_color&&e.details.stream_timestamp_color!==''){x.stream_timestamp_color=e.details.stream_timestamp_color}else{x.stream_timestamp_color='white'} //box color if(e.details.stream_timestamp_box_color&&e.details.stream_timestamp_box_color!==''){x.stream_timestamp_box_color=e.details.stream_timestamp_box_color}else{x.stream_timestamp_box_color='0x00000000@1'} //text size if(e.details.stream_timestamp_font_size&&e.details.stream_timestamp_font_size!==''){x.stream_timestamp_font_size=e.details.stream_timestamp_font_size}else{x.stream_timestamp_font_size='10'} x.stream_video_filters.push('drawtext=fontfile='+x.stream_timestamp_font+':text=\'%{localtime}\':x='+x.stream_timestamp_x+':y='+x.stream_timestamp_y+':fontcolor='+x.stream_timestamp_color+':box=1:boxcolor='+x.stream_timestamp_box_color+':fontsize='+x.stream_timestamp_font_size); } //stream - watermark for -vf if(e.details.stream_watermark&&e.details.stream_watermark=="1"&&e.details.stream_watermark_location&&e.details.stream_watermark_location!==''){ switch(e.details.stream_watermark_position){ case'tl'://top left x.stream_watermark_position='10:10' break; case'tr'://top right x.stream_watermark_position='main_w-overlay_w-10:10' break; case'bl'://bottom left x.stream_watermark_position='10:main_h-overlay_h-10' break; default://bottom right x.stream_watermark_position='(main_w-overlay_w-10)/2:(main_h-overlay_h-10)/2' break; } x.stream_video_filters.push('movie='+e.details.stream_watermark_location+'[watermark],[in][watermark]overlay='+x.stream_watermark_position+'[out]'); } //stream - rotation if(e.details.rotate_stream&&e.details.rotate_stream!==""&&e.details.rotate_stream!=="no"){ x.stream_video_filters.push('transpose='+e.details.rotate_stream); } //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 - 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='libx264'} }else{ x.stream_vcodec=''; } //stream - hls acodec if(e.details.stream_acodec!=='no'){ if(e.details.stream_acodec&&e.details.stream_acodec!==''){x.stream_acodec=' -c:a '+e.details.stream_acodec}else{x.stream_acodec=''} }else{ x.stream_acodec=' -an'; } //stream - hls segment time if(e.details.hls_time&&e.details.hls_time!==''){x.hls_time=e.details.hls_time}else{x.hls_time=2} //hls list size if(e.details.hls_list_size&&e.details.hls_list_size!==''){x.hls_list_size=e.details.hls_list_size}else{x.hls_list_size=2} //stream - custom flags if(e.details.cust_stream&&e.details.cust_stream!==''){x.cust_stream=' '+e.details.cust_stream}else{x.cust_stream=''} //stream - preset if(e.details.preset_stream&&e.details.preset_stream!==''){x.preset_stream=' -preset '+e.details.preset_stream;}else{x.preset_stream=''} //stream - quality if(e.details.stream_quality&&e.details.stream_quality!==''){x.stream_quality=e.details.stream_quality}else{x.stream_quality=''} //stream - pipe build switch(e.details.stream_type){ case'hls': 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.stream_quality)x.stream_quality=' -crf '+x.stream_quality; x.pipe=x.preset_stream+x.stream_quality+x.stream_acodec+x.stream_vcodec+x.stream_fps+' -f hls -s '+x.ratio+x.stream_video_filters+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(x.stream_quality)x.stream_quality=' -q:v '+x.stream_quality; x.pipe=' -c:v mjpeg -f mpjpeg -boundary_tag shinobi'+x.cust_stream+x.stream_video_filters+x.stream_quality+x.stream_fps+' -s '+x.ratio+' pipe:1'; break; case'b64':case'':case undefined:case null://base64 if(x.stream_quality)x.stream_quality=' -q:v '+x.stream_quality; x.pipe=' -c:v mjpeg -f image2pipe'+x.cust_stream+x.stream_video_filters+x.stream_quality+x.stream_fps+' -s '+x.ratio+' pipe:1'; break; default: x.pipe='' break; } //detector - plugins, motion if(e.details.detector==='1'&&e.details.detector_send_frames==='1'){ if(!e.details.detector_fps||e.details.detector_fps===''){e.details.detector_fps=2} if(e.details.detector_scale_x&&e.details.detector_scale_x!==''&&e.details.detector_scale_y&&e.details.detector_scale_y!==''){x.dratio=' -s '+e.details.detector_scale_x+'x'+e.details.detector_scale_y}else{x.dratio=' -s 320x240'} if(e.details.cust_detect&&e.details.cust_detect!==''){x.cust_detect+=e.details.cust_detect;} x.pipe+=' -f singlejpeg -vf fps='+e.details.detector_fps+x.cust_detect+x.dratio+' pipe:0'; } //api - snapshot bin/ cgi.bin (JPEG Mode) if(e.details.snap==='1'||e.details.stream_type==='jpeg'){ if(!e.details.snap_fps||e.details.snap_fps===''){e.details.snap_fps=1} if(e.details.snap_vf&&e.details.snap_vf!==''){x.snap_vf=' -vf '+e.details.snap_vf}else{x.snap_vf=''} if(e.details.snap_scale_x&&e.details.snap_scale_x!==''&&e.details.snap_scale_y&&e.details.snap_scale_y!==''){x.sratio=' -s '+e.details.snap_scale_x+'x'+e.details.snap_scale_y}else{x.sratio=''} 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.sratio+x.snap_vf+' '+e.sdir+'s.jpg -y'; } // //Stream to YouTube (Stream out to server) // if(e.details.stream_server==='1'){ // if(!e.details.stream_server_vbr||e.details.stream_server_vbr===''){e.details.stream_server_vbr='256k'} // x.stream_server_vbr=' -b:v '+e.details.stream_server_vbr; // if(e.details.stream_server_fps&&e.details.stream_server_fps!==''){ // x.stream_server_fps=' -r '+e.details.stream_server_fps // e.details.stream_server_fps=parseFloat(e.details.stream_server_fps) // x.stream_server_fps+=' -g '+e.details.stream_server_fps // }else{x.stream_server_fps=''} // if(e.details.stream_server_crf&&e.details.stream_server_crf!==''){x.stream_server_crf=' -crf '+e.details.stream_server_crf}else{x.stream_server_crf=''} // if(e.details.stream_server_vf&&e.details.stream_server_vf!==''){x.stream_server_vf=' -vf '+e.details.stream_server_vf}else{x.stream_server_vf=''} // if(e.details.stream_server_preset&&e.details.stream_server_preset!==''){x.stream_server_preset=' -preset '+e.details.stream_server_preset}else{x.stream_server_preset=''} // if(e.details.stream_server_scale_x&&e.details.stream_server_scale_x!==''&&e.details.stream_server_scale_y&&e.details.stream_server_scale_y!==''){x.stream_server_ratio=' -s '+e.details.stream_server_scale_x+'x'+e.details.stream_server_scale_y}else{x.stream_server_ratio=''} // if(e.details.cust_stream_server&&e.details.cust_stream_server!==''){x.cust_stream_server=' '+e.details.cust_stream_server;}else{x.cust_stream_server=''} // x.pipe+=' -vcodec libx264 -pix_fmt yuv420p'+x.stream_server_preset+x.stream_server_crf+x.stream_server_fps+x.stream_server_vbr+x.stream_server_ratio+x.stream_server_vf+' -acodec aac -strict 2 -ar 44100 -q:a 3 -b:a 712000'+x.cust_stream_server+' -f flv '+e.details.stream_server_url; // } //custom - output if(e.details.custom_output&&e.details.custom_output!==''){x.pipe+=' '+e.details.custom_output;} //custom - input flags if(e.details.cust_input&&e.details.cust_input!==''){x.cust_input+=' '+e.details.cust_input;} //logging - level if(e.details.loglevel&&e.details.loglevel!==''){x.loglevel='-loglevel '+e.details.loglevel;}else{x.loglevel='-loglevel error'} if(e.mode=='record'){ //custom - record flags if(e.details.cust_record&&e.details.cust_record!==''){x.record_string+=' '+e.details.cust_record;} //record - preset if(e.details.preset_record&&e.details.preset_record!==''){x.record_string+=' -preset '+e.details.preset_record;} } //build final string based on the input type. switch(e.type){ case'dashcam': if(e.mode==='record'){x.record_string+=x.vcodec+x.framerate+x.record_video_filters+x.record_dimensions+x.segment;} x.tmp=x.loglevel+' -i -'+x.record_string+x.pipe; break; case'socket':case'jpeg':case'pipe': if(e.mode==='record'){x.record_string+=x.vcodec+x.framerate+x.record_video_filters+x.record_dimensions+x.segment;} x.tmp=x.loglevel+' -pattern_type glob -f image2pipe'+x.framerate+' -vcodec mjpeg'+x.cust_input+' -i -'+x.record_string+x.pipe; break; case'mjpeg': if(e.mode=='record'){ x.record_string+=x.vcodec+x.record_video_filters+x.framerate+x.record_dimensions+x.segment; } x.tmp=x.loglevel+' -reconnect 1 -r '+e.details.sfps+' -f mjpeg'+x.cust_input+' -i '+e.url+''+x.record_string+x.pipe; break; case'h264':case'hls':case'mp4': if(e.mode=='record'){ x.record_string+=x.vcodec+x.framerate+x.acodec+x.record_dimensions+x.record_video_filters+' '+x.segment; } x.tmp=x.loglevel+x.cust_input+' -i '+e.url+x.record_string+x.pipe; break; case'local': if(e.mode=='record'){ x.record_string+=x.vcodec+x.framerate+x.acodec+x.record_dimensions+x.record_video_filters+' '+x.segment; } x.tmp=x.loglevel+x.cust_input+' -i '+e.path+''+x.record_string+x.pipe; break; } s.group[e.ke].mon[e.mid].ffmpeg=x.tmp; return spawn(config.ffmpegDir,x.tmp.replace(/\s+/g,' ').trim().split(' '),{detached: true}); } 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 -rf '+e,{detached: true}); break; case'delete_files': 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 -rf {} +',{detached: true}); break; } } s.camera=function(x,e,cn,tx){ if(x!=='motion'){ var ee=s.init('noReference',e); if(!e){e={}};if(cn&&cn.ke&&!e.ke){e.ke=cn.ke}; if(!e.mode){e.mode=x;} if(!e.id&&e.mid){e.id=e.mid} } if(e.details&&(e.details instanceof Object)===false){ try{e.details=JSON.parse(e.details)}catch(err){} } ['detector_cascades','cords'].forEach(function(v){ if(e.details&&e.details[v]&&(e.details[v] instanceof Object)===false){ try{ e.details[v]=JSON.parse(e.details[v]); if(!e.details[v])e.details[v]={}; }catch(err){ e.details[v]={}; } } }) switch(x){ case'snapshot'://get snapshot from monitor URL if(config.doSnapshot===true){ if(e.mon.mode!=='stop'){ try{e.mon.details=JSON.parse(e.mon.details)}catch(er){} if(e.mon.details.snap==='1'){ fs.readFile(s.dir.streams+e.ke+'/'+e.mid+'/s.jpg',function(err,data){ if(err){s.tx({f:'monitor_snapshot',snapshot:e.mon.name,snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke);return}; s.tx({f:'monitor_snapshot',snapshot:data,snapshot_format:'ab',mid:e.mid,ke:e.ke},'GRP_'+e.ke) }) }else{ e.url=s.init('url',e.mon); switch(e.mon.type){ case'mjpeg':case'h264':case'local': if(e.mon.type==='local'){e.url=e.mon.path;} e.spawn=spawn(config.ffmpegDir,('-loglevel quiet -i '+e.url+' -s 400x400 -r 25 -ss 1.8 -frames:v 1 -f singlejpeg pipe:1').split(' '),{detached: true}) e.spawn.stdout.on('data',function(data){ e.snapshot_sent=true; s.tx({f:'monitor_snapshot',snapshot:data.toString('base64'),snapshot_format:'b64',mid:e.mid,ke:e.ke},'GRP_'+e.ke) e.spawn.kill(); }); e.spawn.on('close',function(data){ if(!e.snapshot_sent){ s.tx({f:'monitor_snapshot',snapshot:e.mon.name,snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke) } delete(e.snapshot_sent); }); break; case'jpeg': request({url:e.url,method:'GET',encoding:null},function(err,data){ if(err){s.tx({f:'monitor_snapshot',snapshot:e.mon.name,snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke);return}; s.tx({f:'monitor_snapshot',snapshot:data.body,snapshot_format:'ab',mid:e.mid,ke:e.ke},'GRP_'+e.ke) }) break; default: s.tx({f:'monitor_snapshot',snapshot:'...',snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke) break; } } }else{ s.tx({f:'monitor_snapshot',snapshot:'Disabled',snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke) } }else{ s.tx({f:'monitor_snapshot',snapshot:e.mon.name,snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke) } break; case'recor