Audio Detector, Detect Object in Region, and API Methods for Scheduling System

- Audio Detector measures decibels (dB). Section can be found under Motion Detection.
- Regions can now be used with Object detection alone, find the option the Object Detection section.
- Scheduling System will allow automatically activating Monitor States based on time. GUI will be posted in the next commit.
+ Update Installers
+ Update framework.sql
+ `npm test` will run the test
+ Minor Bug Fixes
merge-requests/49/head
Moe 2018-12-29 11:32:12 -08:00
parent 91f530c461
commit afdc0cf568
20 changed files with 573 additions and 169 deletions

View File

@ -143,21 +143,14 @@ if [ "$mysqlDefaultData" = "y" ] || [ "$mysqlDefaultData" = "Y" ]; then
echo "====================================="
echo "====================================="
fi
if [ ! "$sqliteormariadb" = "M" ] && [ ! "$sqliteormariadb" = "m" ]; then
echo "====================================="
echo "||===== Install Completed =====||"
echo "====================================="
echo "|| Login with the Superuser and create a new user!!"
echo "||==================================="
echo "|| Open http://$(ifconfig | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p'):8080/super in your web browser."
echo "||==================================="
echo "|| Default Superuser : admin@shinobi.video"
echo "|| Default Password : admin"
echo "====================================="
echo "====================================="
else
echo "+=================================+"
echo "||===== Install Completed =====||"
echo "|| Access the main Shinobi panel at http://$(ifconfig | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p'):8080 in your web browser."
echo "+=================================+"
fi
echo "====================================="
echo "||===== Install Completed =====||"
echo "====================================="
echo "|| Login with the Superuser and create a new user!!"
echo "||==================================="
echo "|| Open http://$(ifconfig | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p'):8080/super in your web browser."
echo "||==================================="
echo "|| Default Superuser : admin@shinobi.video"
echo "|| Default Password : admin"
echo "====================================="
echo "====================================="

View File

@ -115,15 +115,6 @@ sudo chmod -R 755 .
touch INSTALL/installed.txt
dos2unix /home/Shinobi/INSTALL/shinobi
ln -s /home/Shinobi/INSTALL/shinobi /usr/bin/shinobi
if [ "$mysqlDefaultData" = "y" ] || [ "$mysqlDefaultData" = "Y" ]; then
echo "=====================================" > INSTALL/installed.txt
echo "======= Login Credentials =======" >> INSTALL/installed.txt
echo "|| Username : $userEmail" >> INSTALL/installed.txt
echo "|| Password : $userPasswordPlain" >> INSTALL/installed.txt
echo "|| API Key : $apiKey" >> INSTALL/installed.txt
echo "=====================================" >> INSTALL/installed.txt
echo "=====================================" >> INSTALL/installed.txt
fi
echo "Shinobi - Start Shinobi and set to start on boot?"
echo "(y)es or (N)o"
read startShinobi
@ -134,31 +125,14 @@ if [ "$startShinobi" = "y" ] || [ "$startShinobi" = "Y" ]; then
sudo pm2 save
sudo pm2 list
fi
if [ "$mysqlDefaultData" = "y" ] || [ "$mysqlDefaultData" = "Y" ]; then
echo "details written to INSTALL/installed.txt"
echo "====================================="
echo "======= Login Credentials ======="
echo "|| Username : $userEmail"
echo "|| Password : $userPasswordPlain"
echo "|| API Key : $apiKey"
echo "====================================="
echo "====================================="
fi
if [ ! "$sqliteormariadb" = "M" ] && [ ! "$sqliteormariadb" = "m" ]; then
echo "====================================="
echo "||===== Install Completed =====||"
echo "====================================="
echo "|| Login with the Superuser and create a new user!!"
echo "||==================================="
echo "|| Open http://$(/sbin/ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1):8080/super in your web browser."
echo "||==================================="
echo "|| Default Superuser : admin@shinobi.video"
echo "|| Default Password : admin"
echo "====================================="
echo "====================================="
else
echo "+=================================+"
echo "||===== Install Completed =====||"
echo "|| Access the main Shinobi panel at http://$(/sbin/ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1):8080 in your web browser."
echo "+=================================+"
fi
echo "====================================="
echo "||===== Install Completed =====||"
echo "====================================="
echo "|| Login with the Superuser and create a new user!!"
echo "||==================================="
echo "|| Open http://$(/sbin/ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1):8080/super in your web browser."
echo "||==================================="
echo "|| Default Superuser : admin@shinobi.video"
echo "|| Default Password : admin"
echo "====================================="
echo "====================================="

View File

@ -30,12 +30,17 @@ if [ ! -e "./super.json" ]; then
echo "* You can edit these settings in \"super.json\" located in the Shinobi directory."
sudo cp super.sample.json super.json
fi
if ! [ -x "$(command -v ifconfig)" ]; then
echo "============="
echo "Shinobi - Installing Net-Tools"
sudo apt install net-tools -y
fi
if ! [ -x "$(command -v node)" ]; then
echo "============="
echo "Shinobi - Installing Node.js"
wget https://deb.nodesource.com/setup_9.x
chmod +x setup_9.x
./setup_9.x
wget https://deb.nodesource.com/setup_8.x
chmod +x setup_8.x
./setup_8.x
sudo apt install nodejs -y
else
echo "Node.js Found..."
@ -132,21 +137,14 @@ if [ "$startShinobi" = "y" ] || [ "$startShinobi" = "y" ]; then
sudo pm2 save
sudo pm2 list
fi
if [ ! "$sqliteormariadb" = "M" ] && [ ! "$sqliteormariadb" = "m" ]; then
echo "====================================="
echo "||===== Install Completed =====||"
echo "====================================="
echo "|| Login with the Superuser and create a new user!!"
echo "||==================================="
echo "|| Open http://$(ifconfig | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p'):8080/super in your web browser."
echo "||==================================="
echo "|| Default Superuser : admin@shinobi.video"
echo "|| Default Password : admin"
echo "====================================="
echo "====================================="
else
echo "+=================================+"
echo "||===== Install Completed =====||"
echo "|| Access the main Shinobi panel at http://$(ifconfig | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p'):8080 in your web browser."
echo "+=================================+"
fi
echo "====================================="
echo "||===== Install Completed =====||"
echo "====================================="
echo "|| Login with the Superuser and create a new user!!"
echo "||==================================="
echo "|| Open http://$(ifconfig | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p'):8080/super in your web browser."
echo "||==================================="
echo "|| Default Superuser : admin@shinobi.video"
echo "|| Default Password : admin"
echo "====================================="
echo "====================================="

View File

@ -67,6 +67,8 @@ loadLib('ffmpeg')(s,config,function(){
loadLib('notification')(s,config,lang)
//custom module loader
loadLib('customAutoLoad')(s,config,lang,app,io)
//scheduling engine
loadLib('scheduler')(s,config,lang,app,io)
//on-start actions, daemon(s) starter
loadLib('startup')(s,config,lang)
})

View File

@ -329,6 +329,10 @@
"Filter for Objects only": "Filter for Objects only",
"Custom": "Custom",
"Detector": "Detector",
"Audio Detector": "Audio Detector",
"Audio Detection": "Audio Detection",
"Minimum dB": "Minimum dB",
"Maximum dB": "Maximum dB",
"Connected": "Connected",
"Not Saved": "Not Saved",
"Not Connected": "Not Connected",
@ -666,6 +670,10 @@
"Inserted State Configuration": "Inserted State Configuration",
"Edited State Configuration": "Edited State Configuration",
"Deleted State Configuration": "Deleted State Configuration",
"Schedule Configuration Not Found": "Schedule Configuration Not Found",
"Inserted Schedule Configuration": "Inserted Schedule Configuration",
"Edited Schedule Configuration": "Edited Schedule Configuration",
"Deleted Schedule Configuration": "Deleted Schedule Configuration",
"Dashboard Language": "Dashboard Language",
"Form Data Not Found": "Form Data Not Found",
"File Not Found": "File Not Found",
@ -792,6 +800,7 @@
"TV Channel ID":"TV Channel ID",
"TV Channel Group":"TV Channel Group",
"Emotion Average":"Emotion Average",
"Require Object to be in Region":"Require Object to be in Region",
"Show Regions of Interest":"Show Regions of Interest",
"Confidence of Detection":"Confidence of Detection",
"Edit Selected":"Edit Selected",

View File

@ -33,7 +33,7 @@ module.exports = function(s,config){
s.parseJSON = function(string){
var parsed
try{
string = JSON.parse(string)
parsed = JSON.parse(string)
}catch(err){
}

View File

@ -1,6 +1,11 @@
var P2P = require('pipe2pam');
// Matrix In Region Libs >
var SAT = require('sat')
var V = SAT.Vector;
var P = SAT.Polygon;
// Matrix In Region Libs />
var P2P = require('pipe2pam')
// pamDiff is based on https://www.npmjs.com/package/pam-diff
var PamDiff = require('./detectorPamDiff.js');
var PamDiff = require('pam-diff')
module.exports = function(s,config){
s.createPamDiffEngine = function(e){
var width,
@ -46,7 +51,7 @@ module.exports = function(s,config){
[width,height],
[width,0]
]
};
}
}
e.triggerTimer = {}
@ -58,7 +63,7 @@ module.exports = function(s,config){
regions : regions.forPam,
drawMatrix : e.details.detector_show_matrix
});
s.group[e.ke].mon[e.id].p2p = new P2P();
s.group[e.ke].mon[e.id].p2p = new P2P()
var regionArray = Object.values(regionJson)
if(config.detectorMergePamRegionTriggers === true){
// merge pam triggers for performance boost
@ -317,4 +322,39 @@ module.exports = function(s,config){
}
return trigger
}
s.isAtleastOneMatrixInRegion = function(regions,matrices,callback){
var regionPolys = []
var matrixPoints = []
regions.forEach(function(region,n){
var polyPoints = []
region.points.forEach(function(point){
polyPoints.push(new V(parseInt(point[0]),parseInt(point[1])))
})
regionPolys[n] = new P(new V(0,0), polyPoints)
})
var collisions = []
var foundInRegion = false
matrices.forEach(function(matrix){
var matrixPoints = [
new V(matrix.x,matrix.y),
new V(matrix.width,matrix.y),
new V(matrix.width,matrix.height),
new V(matrix.x,matrix.height)
]
var matrixPoly = new P(new V(0,0), matrixPoints)
regionPolys.forEach(function(region,n){
var response = new SAT.Response()
var collided = SAT.testPolygonPolygon(matrixPoly, region, response)
if(collided === true){
collisions.push({
matrix: matrix,
region: regions[n]
})
foundInRegion = true
}
})
})
if(callback)callback(foundInRegion,collisions)
return foundInRegion
}
}

View File

@ -45,6 +45,7 @@ module.exports = function(s,config,lang){
}
d.mon=s.group[d.ke].mon_conf[d.id];
var currentConfig = s.group[d.ke].mon[d.id].details
var hasMatrices = (d.details.matrices && d.details.matrices.length > 0)
//read filters
if(
currentConfig.use_detector_filters === '1' &&
@ -160,7 +161,7 @@ module.exports = function(s,config,lang){
})
if(d.details.matrices && d.details.matrices.length === 0 || filter.halt === true){
return
}else if(d.details.matrices && d.details.matrices.length > 0){
}else if(hasMatrices){
var reviewedMatrix = []
d.details.matrices.forEach(function(matrix){
if(matrix)reviewedMatrix.push(matrix)
@ -193,6 +194,16 @@ module.exports = function(s,config,lang){
return
}
}
// check if object should be in region
if(hasMatrices && currentConfig.detector_obj_region === '1'){
var regions = s.group[d.ke].mon[d.id].parsedObjects.cords
var isMatrixInRegions = s.isAtleastOneMatrixInRegion(regions,d.details.matrices)
if(isMatrixInRegions){
s.debugLog('Matrix in region!')
}else{
return
}
}
// check modified indifference
if(filter.indifference !== false && d.details.confidence < parseFloat(filter.indifference)){
// fails indifference check for modified indifference

View File

@ -735,6 +735,17 @@ module.exports = function(s,config,onFinish){
x.record_string+=x.vcodec+x.record_fps+x.record_video_filters+x.record_dimensions+x.segment;
}
}
ffmpeg.buildAudioDetector = function(e,x){
if(e.details.detector_audio === '1'){
if(e.details.input_map_choices&&e.details.input_map_choices.detector_audio){
//add input feed map
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.detector_audio)
}else{
x.pipe += ' -map 0:a'
}
x.pipe += ' -acodec pcm_s16le -f s16le -ac 1 -ar 16000 pipe:6'
}
}
ffmpeg.buildMainDetector = function(e,x){
//e = monitor object
//x = temporary values
@ -761,10 +772,10 @@ module.exports = function(s,config,onFinish){
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';
x.pipe += ' -an -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';
x.pipe+=' -an -f image2pipe '+x.detector_vf+x.cust_detect+x.dratio+' pipe:3';
}
}
//Traditional Recording Buffer
@ -886,6 +897,7 @@ module.exports = function(s,config,onFinish){
ffmpeg.buildMainInput(e,x)
ffmpeg.buildMainStream(e,x)
ffmpeg.buildMainRecording(e,x)
ffmpeg.buildAudioDetector(e,x)
ffmpeg.buildMainDetector(e,x)
s.onFfmpegCameraStringCreationExtensions.forEach(function(extender){
extender(e,x)

View File

@ -6,6 +6,7 @@ var Mp4Frag = require('mp4frag');
var onvif = require('node-onvif');
var request = require('request');
var connectionTester = require('connection-tester')
var SoundDetection = require('shinobi-sound-detection')
var URL = require('url')
module.exports = function(s,config,lang){
s.initiateMonitorObject = function(e){
@ -21,6 +22,7 @@ module.exports = function(s,config,lang){
if(!s.group[e.ke].mon[e.mid].eventBasedRecording){s.group[e.ke].mon[e.mid].eventBasedRecording={}};
if(!s.group[e.ke].mon[e.mid].watch){s.group[e.ke].mon[e.mid].watch={}};
if(!s.group[e.ke].mon[e.mid].fixingVideos){s.group[e.ke].mon[e.mid].fixingVideos={}};
if(!s.group[e.ke].mon[e.mid].parsedObjects){s.group[e.ke].mon[e.mid].parsedObjects={}};
if(!s.group[e.ke].mon[e.mid].isStarted){s.group[e.ke].mon[e.mid].isStarted = false};
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={}}
@ -282,12 +284,20 @@ module.exports = function(s,config,lang){
s.cameraCheckObjectsInDetails = function(e){
//parse Objects
(['detector_cascades','cords','detector_filters','input_map_choices']).forEach(function(v){
if(e.details&&e.details[v]&&(e.details[v] instanceof Object)===false){
if(e.details && e.details[v]){
try{
if(e.details[v] === '') e.details[v] = '{}'
e.details[v]=JSON.parse(e.details[v]);
if(!e.details[v])e.details[v]={};
s.group[e.ke].mon[e.id].details = e.details;
if(!e.details[v] || e.details[v] === '')e.details[v] = '{}'
e.details[v] = s.parseJSON(e.details[v])
if(!e.details[v])e.details[v] = {}
s.group[e.ke].mon[e.id].details = e.details
switch(v){
case'cords':
s.group[e.ke].mon[e.id].parsedObjects[v] = Object.values(s.parseJSON(e.details[v]))
break;
default:
s.group[e.ke].mon[e.id].parsedObjects[v] = s.parseJSON(e.details[v])
break;
}
}catch(err){
}
@ -776,6 +786,46 @@ module.exports = function(s,config,lang){
if(e.type==='jpeg'){
s.cameraPullJpegStream(e)
}
if(e.details.detector_audio === '1'){
var triggerLevel
var triggerLevelMax
if(e.details.detector_audio_min_db && e.details.detector_audio_min_db !== ''){
triggerLevel = parseInt(e.details.detector_audio_min_db)
}else{
triggerLevel = 5
}
if(e.details.detector_audio_max_db && e.details.detector_audio_max_db !== ''){
triggerLevelMax = parseInt(e.details.detector_audio_max_db)
}
var audioDetector = new SoundDetection({
format: {
bitDepth: 16,
numberOfChannels: 1,
signed: true
},
triggerLevel: triggerLevel,
triggerLevelMax: triggerLevelMax
},function(dB) {
s.triggerEvent({
f:'trigger',
id:e.id,
ke:e.ke,
name: 'db',
details:{
plug:'audio',
name:'db',
reason:'soundChange',
confidence:dB
},
plates:[],
imgHeight:e.details.detector_scale_y,
imgWidth:e.details.detector_scale_x
})
})
s.group[e.ke].mon[e.id].audioDetector = audioDetector
audioDetector.start()
s.group[e.ke].mon[e.id].spawn.stdio[6].pipe(audioDetector.streamDecoder)
}
if(e.details.detector === '1'){
s.ocvTx({f:'init_monitor',id:e.id,ke:e.ke})
//frames from motion detect
@ -1435,7 +1485,7 @@ module.exports = function(s,config,lang){
s.group[e.ke].mon[e.mid].isRecording = false
}
//set up fatal error handler
if(e.details.fatal_max===''){
if(e.details.fatal_max === ''){
e.details.fatal_max = 10
}else{
e.details.fatal_max = parseFloat(e.details.fatal_max)
@ -1454,4 +1504,48 @@ module.exports = function(s,config,lang){
}
if(typeof cn === 'function'){setTimeout(function(){cn()},1000)}
}
//
s.activateMonitorStates = function(groupKey,stateName,user,callback){
var endData = {
ok: false
}
s.findPreset([groupKey,'monitorStates',stateName],function(notFound,preset){
if(notFound === false){
var sqlQuery = 'SELECT * FROM Monitors WHERE ke=? AND '
var monitorQuery = []
var sqlQueryValues = [groupKey]
var monitorPresets = {}
preset.details.monitors.forEach(function(monitor){
monitorQuery.push('mid=?')
sqlQueryValues.push(monitor.mid)
monitorPresets[monitor.mid] = monitor
})
sqlQuery += '('+monitorQuery.join(' OR ')+')'
s.sqlQuery(sqlQuery,sqlQueryValues,function(err,monitors){
if(monitors && monitors[0]){
monitors.forEach(function(monitor){
s.checkDetails(monitor)
s.checkDetails(monitorPresets[monitor.mid])
var monitorPreset = monitorPresets[monitor.mid]
monitorPreset.details = Object.assign(monitor.details,monitorPreset.details)
monitor = s.cleanMonitorObjectForDatabase(Object.assign(monitor,monitorPreset))
monitor.details = JSON.stringify(monitor.details)
s.addOrEditMonitor(Object.assign(monitor,{}),function(err,endData){
},user)
})
endData.ok = true
s.tx({f:'change_group_state',ke:groupKey,name:stateName},'GRP_'+groupKey)
callback(endData)
}else{
endData.msg = user.lang['State Configuration has no monitors associated']
callback(endData)
}
})
}else{
endData.msg = user.lang['State Configuration Not Found']
callback(endData)
}
})
}
}

257
libs/scheduler.js Normal file
View File

@ -0,0 +1,257 @@
module.exports = function(s,config,lang,app,io){
s.schedules = {}
//Get all Schedules
s.getAllSchedules = function(callback){
s.schedules = {}
s.sqlQuery('SELECT * FROM Schedules',function(err,rows){
rows.forEach(function(schedule){
s.updateSchedule(schedule)
})
if(callback)callback()
})
}
//update schedule
s.updateSchedule = function(row){
var schedule = Object.assign(row,{})
if(!s.schedules[schedule.ke])s.schedules[schedule.ke] = {}
s.checkDetails(schedule)
if(!s.schedules[schedule.ke][schedule.name]){
s.schedules[schedule.ke][schedule.name] = schedule
}else{
s.schedules[schedule.ke][schedule.name] = Object.assign(s.schedules[schedule.ke][schedule.name],schedule)
}
}
//check time in schedule
s.checkTimeAgainstSchedule = function(start,end,callback){
try{
if(
start
){
var checkStartTime = new Date()
var startSplit = start.split(':')
var startHour = parseInt(startSplit[0])
var startMin = parseInt(startSplit[1])
checkStartTime.setHours(startHour)
checkStartTime.setMinutes(startMin)
if(end){
var checkEndTime = new Date()
var endSplit = end.split(':')
var endHour = parseInt(endSplit[0])
var endMin = parseInt(endSplit[1])
checkEndTime.setHours(endHour)
checkEndTime.setMinutes(endMin)
}
var currentDate = new Date()
if(
(
currentDate >= checkStartTime &&
currentDate <= checkEndTime
) ||
currentDate >= checkStartTime && !end
){
callback()
}else{
callback({
currentDate : currentDate,
startTime : checkStartTime,
endTime : checkEndTime
})
}
}else{
callback()
}
}catch(err){
console.log(err)
callback()
}
}
//check all Schedules
s.checkSchedules = function(v,callback){
var groupKeys = Object.keys(s.schedules)
groupKeys.forEach(function(key){
var scheduleNames = Object.keys(s.schedules[key])
scheduleNames.forEach(function(name){
var schedule = s.schedules[key][name]
if(!schedule.active && schedule.enabled === 1 && schedule.start && schedule.details.monitorStates){
s.checkTimeAgainstSchedule(schedule.start,schedule.end,function(err){
if(!err){
schedule.active = true
var monitorStates = schedule.details.monitorStates
monitorStates.forEach(function(stateName){
s.activateMonitorStates(key,stateName,{
ke: key,
uid: 'System',
details: {},
permissions: {},
lang: lang
},function(endData){
// console.log(endData)
})
})
}else{
schedule.active = false
}
})
}
})
})
}
//
s.findSchedule = function(groupKey,name,callback){
//presetQueryVals = [ke, type, name]
s.sqlQuery("SELECT * FROM Schedules WHERE ke=? AND name=? LIMIT 1",[groupKey,name],function(err,schedules){
var schedule
var notFound = false
if(schedules && schedules[0]){
schedule = schedules[0]
s.checkDetails(schedule)
}else{
notFound = true
}
callback(notFound,schedule)
})
}
//
var onProcessReady = function(){
s.getAllSchedules(function(){
s.checkSchedules()
})
setInterval(function(){
s.checkSchedules()
},1000 * 60 * 5)
}
/**
* WebServerPath : API : Update Schedule
*/
app.all([
config.webPaths.apiPrefix+':auth/schedule/:ke',
config.webPaths.adminApiPrefix+':auth/schedule/:ke',
config.webPaths.apiPrefix+':auth/schedule/:ke/:name',
config.webPaths.adminApiPrefix+':auth/schedule/:ke/:name',
],function (req,res){
s.auth(req.params,function(user){
var endData = {
ok : false
}
if(user.details.sub){
endData.msg = user.lang['Not Permitted']
s.closeJsonResponse(res,endData)
return
}
var theQuery = "SELECT * FROM Schedules WHERE ke=?"
var theQueryValues = [req.params.ke]
if(req.params.name){
theQuery += ' AND name=?'
theQueryValues.push(req.params.name)
}
s.sqlQuery(theQuery,theQueryValues,function(err,schedules){
if(schedules && schedules[0]){
endData.ok = true
schedules.forEach(function(schedule){
s.checkDetails(schedule)
})
endData.schedules = schedules
}else{
endData.msg = user.lang['Not Found']
}
s.closeJsonResponse(res,endData)
})
})
})
/**
* WebServerPath : API : Update Schedule
*/
app.all([
config.webPaths.apiPrefix+':auth/schedule/:ke/:name/:action',
config.webPaths.adminApiPrefix+':auth/schedule/:ke/:name/:action'
],function (req,res){
s.auth(req.params,function(user){
var endData = {
ok : false
}
if(user.details.sub){
endData.msg = user.lang['Not Permitted']
s.closeJsonResponse(res,endData)
return
}
switch(req.params.action){
case'insert':case'edit':
var form = s.getPostData(req)
s.checkDetails(form)
if(!form || !form.details){
endData.msg = user.lang['Form Data Not Found']
s.closeJsonResponse(res,endData)
return
}
form.enabled = parseInt(form.enabled) || 1;
s.findSchedule(req.params.ke,req.params.name,function(notFound,preset){
if(notFound === true){
endData.msg = lang["Inserted Schedule Configuration"]
var insertData = {
ke: req.params.ke,
name: req.params.name,
details: s.stringJSON(form.details),
start: form.start,
end: form.end,
enabled: form.enabled
}
s.sqlQuery('INSERT INTO Schedules ('+Object.keys(insertData).join(',')+') VALUES (?,?,?,?,?,?)',Object.values(insertData))
s.tx({
f: 'add_schedule',
insertData: insertData,
ke: req.params.ke,
name: req.params.name
},'GRP_'+req.params.ke)
}else{
endData.msg = lang["Edited Schedule Configuration"]
var insertData = {
details: s.stringJSON(form.details),
start: form.start,
end: form.end,
enabled: form.enabled,
ke: req.params.ke,
name: req.params.name
}
s.sqlQuery('UPDATE Schedules SET details=?,start=?,end=?,enabled=? WHERE ke=? AND name=?',Object.values(insertData))
s.tx({
f: 'edit_schedule',
insertData: insertData,
ke: req.params.ke,
name: req.params.name
},'GRP_'+req.params.ke)
}
s.updateSchedule({
ke: req.params.ke,
name: req.params.name,
details: s.stringJSON(form.details),
start: form.start,
end: form.end,
enabled: form.enabled
})
endData.ok = true
s.closeJsonResponse(res,endData)
})
break;
case'delete':
s.findSchedule(req.params.ke,req.params.name,function(notFound,schedule){
if(notFound === true){
endData.msg = user.lang['Schedule Configuration Not Found']
s.closeJsonResponse(res,endData)
}else{
s.sqlQuery('DELETE FROM Schedules WHERE ke=? AND name=?',[req.params.ke,req.params.name],function(err){
if(!err){
endData.msg = lang["Deleted Schedule Configuration"]
endData.ok = true
if(s.schedules[schedule.ke])delete(s.schedules[schedule.ke][schedule.name])
}
s.closeJsonResponse(res,endData)
})
}
})
break;
}
})
})
//bind events
s.onProcessReady(onProcessReady)
}

View File

@ -1365,7 +1365,7 @@ module.exports = function(s,config,lang,io){
if(cn.cron){
delete(s.cron);
}
if(cn.ocv){
if(cn.ocv && s.ocv){
s.tx({f:'detector_unplugged',plug:s.ocv.plug},'CPU')
delete(s.ocv);
delete(s.api[cn.id])

View File

@ -10,6 +10,12 @@ module.exports = function(s,config){
if(s.databaseOptions.client.indexOf('sqlite')>-1){
s.databaseOptions.client = 'sqlite3';
s.databaseOptions.useNullAsDefault = true;
try{
require('sqlite3')
}catch(err){
console.log('Installing SQlite3 Module...')
require('child_process').execSync('npm install sqlite3 --unsafe-perm')
}
}
if(s.databaseOptions.client === 'sqlite3' && s.databaseOptions.connection.filename === undefined){
s.databaseOptions.connection.filename = s.mainDirectory+"/shinobi.sqlite"
@ -99,6 +105,10 @@ module.exports = function(s,config){
},true)
}
},true)
//add Schedules table, will remove in future
s.sqlQuery("CREATE TABLE IF NOT EXISTS `Schedules` (`ke` varchar(50) DEFAULT NULL,`name` text,`details` text,`start` varchar(10) DEFAULT NULL,`end` varchar(10) DEFAULT NULL,`enabled` int(1) NOT NULL DEFAULT '1')" + mySQLtail + ';',[],function(err){
if(err)console.error(err)
},true)
//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\',`details` text)' + mySQLtail + ';',[],function(err){
if(err)console.error(err)

View File

@ -4,16 +4,15 @@ 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,processReady){
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')
s.onProcessReadyExtensions.forEach(function(extender){
extender(true)
})
if(processReady)processReady()
process.send('ready')
}
var loadedAccounts = []
var loadMonitors = function(callback){

View File

@ -308,4 +308,18 @@ module.exports = function(s,config){
}
})
}
s.findPreset = function(presetQueryVals,callback){
//presetQueryVals = [ke, type, name]
s.sqlQuery("SELECT * FROM Presets WHERE ke=? AND type=? AND name=? LIMIT 1",presetQueryVals,function(err,presets){
var preset
var notFound = false
if(presets && presets[0]){
preset = presets[0]
s.checkDetails(preset)
}else{
notFound = true
}
callback(notFound,preset)
})
}
}

View File

@ -419,19 +419,7 @@ module.exports = function(s,config,lang,app){
s.closeJsonResponse(res,endData)
return
}
var findPreset = function(callback){
s.sqlQuery("SELECT * FROM Presets WHERE ke=? AND type=? AND name=? LIMIT 1",[req.params.ke,'monitorStates',req.params.stateName],function(err,presets){
var preset
var notFound = false
if(presets && presets[0]){
preset = presets[0]
s.checkDetails(preset)
}else{
notFound = true
}
callback(notFound,preset)
})
}
var presetQueryVals = [req.params.ke,'monitorStates',req.params.stateName]
switch(req.params.action){
case'insert':case'edit':
var form = s.getPostData(req)
@ -441,7 +429,7 @@ module.exports = function(s,config,lang,app){
s.closeJsonResponse(res,endData)
return
}
findPreset(function(notFound,preset){
s.findPreset(presetQueryVals,function(notFound,preset){
if(notFound === true){
endData.msg = lang["Inserted State Configuration"]
var details = {
@ -478,7 +466,7 @@ module.exports = function(s,config,lang,app){
})
break;
case'delete':
findPreset(function(notFound,preset){
s.findPreset(presetQueryVals,function(notFound,preset){
if(notFound === true){
endData.msg = user.lang['State Configuration Not Found']
s.closeJsonResponse(res,endData)
@ -494,43 +482,8 @@ module.exports = function(s,config,lang,app){
})
break;
default://change monitors according to state
findPreset(function(notFound,preset){
if(notFound === false){
var sqlQuery = 'SELECT * FROM Monitors WHERE ke=? AND '
var monitorQuery = []
var sqlQueryValues = [req.params.ke]
var monitorPresets = {}
preset.details.monitors.forEach(function(monitor){
monitorQuery.push('mid=?')
sqlQueryValues.push(monitor.mid)
monitorPresets[monitor.mid] = monitor
})
sqlQuery += '('+monitorQuery.join(' OR ')+')'
s.sqlQuery(sqlQuery,sqlQueryValues,function(err,monitors){
if(monitors && monitors[0]){
monitors.forEach(function(monitor){
s.checkDetails(monitor)
s.checkDetails(monitorPresets[monitor.mid])
var monitorPreset = monitorPresets[monitor.mid]
monitorPreset.details = Object.assign(monitor.details,monitorPreset.details)
monitor = s.cleanMonitorObjectForDatabase(Object.assign(monitor,monitorPreset))
monitor.details = JSON.stringify(monitor.details)
s.addOrEditMonitor(Object.assign(monitor,{}),function(err,endData){
},user)
})
endData.ok = true
s.tx({f:'change_group_state',ke:req.params.ke,name:req.params.stateName},'GRP_'+req.params.ke)
s.closeJsonResponse(res,endData)
}else{
endData.msg = user.lang['State Configuration has no monitors associated']
s.closeJsonResponse(res,endData)
}
})
}else{
endData.msg = user.lang['State Configuration Not Found']
s.closeJsonResponse(res,endData)
}
s.activateMonitorStates(req.params.ke,req.params.stateName,user,function(endData){
s.closeJsonResponse(res,endData)
})
break;
}

View File

@ -1,12 +1,12 @@
{
"name": "shinobi",
"productName": "Shinobi",
"version": "1.0.38",
"version": "2.0.0",
"description": "CCTV and NVR in Node.js",
"main": "camera.js",
"bin": "camera.js",
"scripts": {
"test": "node test.js",
"test": "node camera.js test",
"start": "chmod +x INSTALL/start.sh && INSTALL/start.sh"
},
"repository": {
@ -19,30 +19,33 @@
},
"homepage": "https://gitlab.com/Shinobi-Systems/Shinobi#readme",
"dependencies": {
"aws-sdk": "^2.279.1",
"backblaze-b2": "^1.0.4",
"body-parser": "^1.15.2",
"circular-json": "0.3.1",
"connection-tester": "^0.1.1",
"mp4frag": "^0.0.22",
"discord.js": "^11.3.2",
"ejs": "^2.5.5",
"express": "^4.14.0",
"http-proxy": "^1.17.0",
"jsonfile": "^3.0.1",
"moment": "^2.17.0",
"mysql": "^2.12.0",
"knex": "^0.14.2",
"aws-sdk": "^2.279.1",
"pam-diff": "^0.10.2",
"pipe2pam": "^0.6.2",
"nodemailer": "^4.0.1",
"ldapauth-fork": "^4.0.2",
"moment": "^2.17.0",
"mp4frag": "^0.0.22",
"mysql": "^2.12.0",
"node-onvif": "^0.1.4",
"nodemailer": "^4.0.1",
"pam-diff": "^0.10.2",
"path": "^0.12.7",
"pipe2pam": "^0.6.2",
"request": "^2.79.0",
"socket.io": "^1.7.1",
"socket.io-client": "^1.7.2",
"http-proxy": "^1.17.0",
"sqlite3": "^4.0.4",
"webdav-fs": "^1.11.0",
"discord.js": "^11.3.2",
"backblaze-b2": "^1.0.4",
"ldapauth-fork": "^4.0.2"
"sat": "^0.7.1",
"shinobi-sound-detection": "^0.1.7"
},
"devDependencies": {}
}

View File

@ -125,6 +125,18 @@ CREATE TABLE IF NOT EXISTS `Files` (
`status` int(1) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `Files` ADD COLUMN `time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `status`;
-- Data exporting was unselected.
-- Dumping structure for table ccio.Schedules
CREATE TABLE IF NOT EXISTS `Schedules` (
`ke` varchar(50) DEFAULT NULL,
`name` text,
`details` text,
`start` varchar(10) DEFAULT NULL,
`end` varchar(10) DEFAULT NULL,
`enabled` int(1) NOT NULL DEFAULT '1'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Data exporting was unselected.
/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */;

View File

@ -12,7 +12,7 @@ processArgv.forEach(function(val) {
var theSplit = val.split('=');
var index = theSplit[0];
var value = theSplit[1];
if(index === 'addToConfig'){
if(index.indexOf('addToConfig') > -1){
try{
value = JSON.parse(value)
config = Object.assign(config,value)
@ -36,4 +36,4 @@ processArgv.forEach(function(val) {
jsonfile.writeFile(configLocation,config,{spaces: 2},function(){
console.log('Changes Complete. Here is what it is now.')
console.log(JSON.stringify(config,null,2))
})
})

View File

@ -1065,14 +1065,6 @@
</select></div>
</label>
</div>
<div class="form-group h_det_pam_input h_det_pam_1">
<label><div><span><%-lang['Show Matrices']%></span></div>
<div><select class="form-control" detail="detector_show_matrix">
<option value="0" selected><%-lang.No%></option>
<option value="1"><%-lang.Yes%></option>
</select></div>
</label>
</div>
<div class="form-group">
<label><div><span><%-lang['Indifference']%></span></div>
<div><input class="form-control" detail="detector_sensitivity" placeholder="0.5"></div>
@ -1159,6 +1151,14 @@
</select></div>
</label>
</div>
<div class="form-group">
<label><div><span><%-lang['Require Object to be in Region']%></span></div>
<div><select class="form-control" detail="detector_obj_region">
<option value="0" selected><%-lang.No%></option>
<option value="1"><%-lang.Yes%></option>
</select></div>
</label>
</div>
<div class="form-group">
<label><div><span><%-lang['Check for Motion First']%></span></div>
<div><select class="form-control" detail="detector_use_motion" selector="h_det_mot_fir">
@ -1294,6 +1294,29 @@
</div>
</div>
</div>
<!-- Start of Audio Detection -->
<div class="form-group-group orange" section id="monSectionAudioDetector">
<h4><%-lang['Audio Detector']%></h4>
<div class="form-group">
<label><div><span><%-lang['Enabled']%></span></div>
<div><select class="form-control" detail="detector_audio">
<option value="0" selected><%-lang.No%></option>
<option value="1"><%-lang.Yes%></option>
</select></div>
</label>
</div>
<div class="form-group">
<label><div><span><%-lang['Minimum dB']%></span></div>
<div><input class="form-control" detail="detector_audio_min_db" placeholder="5"></div>
</label>
</div>
<div class="form-group">
<label><div><span><%-lang['Maximum dB']%></span></div>
<div><input class="form-control" detail="detector_audio_max_db" placeholder=""></div>
</label>
</div>
</div>
<!-- END of Audio Detection --/>
<!-- END of Motion -->
<div class="form-group-group blue" section id="monSectionControl">
<h4><%-lang['Control']%></h4>