Merge branch 'dev' into 'master'
Another Day Another Delivery Closes #339 See merge request Shinobi-Systems/Shinobi!332cron-addstorage-fix
commit
cd10e63427
|
@ -94,6 +94,10 @@ docker run -d --name='Shinobi' -p '8080:8080/tcp' -v "/dev/shm/Shinobi/streams":
|
||||||
|
|
||||||
> For those using `DB_DISABLE_INCLUDED=true` please remember to create a user in your databse first. The Docker image will create the `DB_DATABASE` under the specified connection information.
|
> For those using `DB_DISABLE_INCLUDED=true` please remember to create a user in your databse first. The Docker image will create the `DB_DATABASE` under the specified connection information.
|
||||||
|
|
||||||
|
### Power Video Viewer Blank or Not working
|
||||||
|
|
||||||
|
This seems to be an issue with using Docker on some linux OS' like Arch Linux. It is uncertain what the specific issue is but for now please use Docker on a consumer or enterprise supported distribution of linux, like Ubuntu 20.04.
|
||||||
|
|
||||||
### Tips
|
### Tips
|
||||||
|
|
||||||
Modifying `conf.json` or Superuser credentials.
|
Modifying `conf.json` or Superuser credentials.
|
||||||
|
|
|
@ -66,6 +66,8 @@ RUN apt install -y software-properties-common \
|
||||||
tar \
|
tar \
|
||||||
x264
|
x264
|
||||||
|
|
||||||
|
RUN apt install -y zip
|
||||||
|
|
||||||
RUN apt install -y \
|
RUN apt install -y \
|
||||||
ffmpeg \
|
ffmpeg \
|
||||||
git \
|
git \
|
||||||
|
|
|
@ -392,8 +392,6 @@ module.exports = function(s,config,lang){
|
||||||
"default": "10",
|
"default": "10",
|
||||||
"example": "",
|
"example": "",
|
||||||
"possible": "",
|
"possible": "",
|
||||||
"form-group-class": "h_t_input h_t_h264 h_t_hls h_t_mp4 h_t_jpeg h_t_mjpeg h_t_local",
|
|
||||||
"form-group-class-pre-layer": "h_auto_host_input h_auto_host_0 auto_host_fill",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "detail=skip_ping",
|
"name": "detail=skip_ping",
|
||||||
|
|
1257
languages/fr.json
1257
languages/fr.json
File diff suppressed because it is too large
Load Diff
|
@ -1,420 +1,30 @@
|
||||||
var fs = require('fs')
|
|
||||||
var execSync = require('child_process').execSync
|
|
||||||
var P2P = require('pipe2pam')
|
|
||||||
var PamDiff = require('pam-diff')
|
|
||||||
module.exports = function(jsonData,pamDiffResponder){
|
module.exports = function(jsonData,pamDiffResponder){
|
||||||
var noiseFilterArray = {};
|
const {
|
||||||
const groupKey = jsonData.rawMonitorConfig.ke
|
// see libs/detectorUtils.js for more parameters and functions
|
||||||
const monitorId = jsonData.rawMonitorConfig.mid
|
//
|
||||||
const triggerTimer = {}
|
config,
|
||||||
var pamDiff
|
groupKey,
|
||||||
var p2p
|
monitorId,
|
||||||
var regionJson
|
monitorDetails,
|
||||||
try{
|
completeMonitorConfig,
|
||||||
regionJson = JSON.parse(jsonData.rawMonitorConfig.details.cords)
|
pamDetectorIsEnabled,
|
||||||
}catch(err){
|
//
|
||||||
regionJson = jsonData.rawMonitorConfig.details.cords
|
attachPamPipeDrivers,
|
||||||
}
|
//
|
||||||
var width,
|
getAcceptedTriggers,
|
||||||
height,
|
getRegionsWithMinimumChange,
|
||||||
globalSensitivity,
|
getRegionsBelowMaximumChange,
|
||||||
globalColorThreshold,
|
getRegionsWithThresholdMet,
|
||||||
fullFrame = false
|
filterTheNoise,
|
||||||
if(jsonData.rawMonitorConfig.details.detector_scale_x===''||jsonData.rawMonitorConfig.details.detector_scale_y===''){
|
filterTheNoiseFromMultipleRegions,
|
||||||
width = jsonData.rawMonitorConfig.details.detector_scale_x;
|
//
|
||||||
height = jsonData.rawMonitorConfig.details.detector_scale_y;
|
buildDetectorObject,
|
||||||
}
|
buildTriggerEvent,
|
||||||
else{
|
sendDetectedData,
|
||||||
width = jsonData.rawMonitorConfig.width
|
} = require('./libs/detectorUtils.js')(jsonData,pamDiffResponder)
|
||||||
height = jsonData.rawMonitorConfig.height
|
return function(cameraProcess){
|
||||||
}
|
if(pamDetectorIsEnabled){
|
||||||
if(jsonData.rawMonitorConfig.details.detector_sensitivity===''){
|
attachPamPipeDrivers(cameraProcess)
|
||||||
globalSensitivity = 10
|
|
||||||
}else{
|
|
||||||
globalSensitivity = parseInt(jsonData.rawMonitorConfig.details.detector_sensitivity)
|
|
||||||
}
|
|
||||||
if(jsonData.rawMonitorConfig.details.detector_color_threshold===''){
|
|
||||||
globalColorThreshold = 9
|
|
||||||
}else{
|
|
||||||
globalColorThreshold = parseInt(jsonData.rawMonitorConfig.details.detector_color_threshold)
|
|
||||||
}
|
|
||||||
|
|
||||||
globalThreshold = parseInt(jsonData.rawMonitorConfig.details.detector_threshold) || 0
|
|
||||||
var regionConfidenceMinimums = {}
|
|
||||||
Object.values(regionJson).forEach(function(region){
|
|
||||||
// writeToStderr(JSON.stringify(region,null,3))
|
|
||||||
regionConfidenceMinimums[region.name] = region.sensitivity;
|
|
||||||
})
|
|
||||||
var writeToStderr = function(text){
|
|
||||||
fs.appendFileSync('/home/Shinobi/test.log',text + '\n','utf8')
|
|
||||||
|
|
||||||
}
|
|
||||||
// writeToStderr(JSON.stringify({
|
|
||||||
// regionConfidenceMinimums: regionConfidenceMinimums
|
|
||||||
// },null,3))
|
|
||||||
if(typeof pamDiffResponder === 'function'){
|
|
||||||
var sendDetectedData = function(detectorObject){
|
|
||||||
pamDiffResponder(detectorObject)
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
var sendDetectedData = function(detectorObject){
|
|
||||||
pamDiffResponder.write(Buffer.from(JSON.stringify(detectorObject)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function checkMinimumChange(confidence,minimumChangeRequired){
|
|
||||||
const amountChanged = confidence
|
|
||||||
const minimumChange = !isNaN(minimumChangeRequired) ? parseInt(minimumChangeRequired) : 10
|
|
||||||
if(!isNaN(amountChanged)){
|
|
||||||
if(amountChanged < minimumChange){
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
function getRegionsWithMinimumChange(data){
|
|
||||||
try{
|
|
||||||
var acceptedTriggers = []
|
|
||||||
data.trigger.forEach((trigger) => {
|
|
||||||
if(checkMinimumChange(trigger.percent,regionConfidenceMinimums[trigger.name] || globalSensitivity)){
|
|
||||||
acceptedTriggers.push(trigger)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return acceptedTriggers
|
|
||||||
}catch(err){
|
|
||||||
// writeToStderr(err.stack)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function createPamDiffEngine(){
|
|
||||||
|
|
||||||
const regionsAreMasks = jsonData.rawMonitorConfig.details.detector_frame !== '1' && jsonData.rawMonitorConfig.details.inverse_trigger === '1';
|
|
||||||
if(Object.keys(regionJson).length === 0 || jsonData.rawMonitorConfig.details.detector_frame === '1'){
|
|
||||||
fullFrame = {
|
|
||||||
name:'FULL_FRAME',
|
|
||||||
sensitivity: globalSensitivity,
|
|
||||||
color_threshold: globalColorThreshold,
|
|
||||||
points:[
|
|
||||||
[0,0],
|
|
||||||
[0,height],
|
|
||||||
[width,height],
|
|
||||||
[width,0]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mask = {
|
|
||||||
max_sensitivity : globalSensitivity,
|
|
||||||
threshold : globalThreshold,
|
|
||||||
}
|
|
||||||
var regions = createPamDiffRegionArray(regionJson,globalColorThreshold,globalSensitivity,fullFrame)
|
|
||||||
var pamDiffOptions = {
|
|
||||||
mask: regionsAreMasks,
|
|
||||||
grayscale: 'luminosity',
|
|
||||||
regions : regions.forPam,
|
|
||||||
percent : globalSensitivity,
|
|
||||||
difference : globalColorThreshold,
|
|
||||||
response: "bounds"
|
|
||||||
|
|
||||||
}
|
|
||||||
pamDiff = new PamDiff(pamDiffOptions)
|
|
||||||
p2p = new P2P()
|
|
||||||
var regionArray = Object.values(regionJson)
|
|
||||||
if(jsonData.globalInfo.config.detectorMergePamRegionTriggers === true){
|
|
||||||
// merge pam triggers for performance boost
|
|
||||||
var buildTriggerEvent = function(trigger){
|
|
||||||
var detectorObject = {
|
|
||||||
f:'trigger',
|
|
||||||
id:monitorId,
|
|
||||||
ke:groupKey,
|
|
||||||
name:trigger.name,
|
|
||||||
details:{
|
|
||||||
plug:'built-in',
|
|
||||||
name:trigger.name,
|
|
||||||
reason:'motion',
|
|
||||||
confidence:trigger.percent,
|
|
||||||
matrices: trigger.matrices,
|
|
||||||
imgHeight:jsonData.rawMonitorConfig.details.detector_scale_y,
|
|
||||||
imgWidth:jsonData.rawMonitorConfig.details.detector_scale_x
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if(trigger.merged){
|
|
||||||
if(trigger.matrices)detectorObject.details.matrices = trigger.matrices
|
|
||||||
var filteredCount = 0
|
|
||||||
var filteredCountSuccess = 0
|
|
||||||
trigger.merged.forEach(function(triggerPiece){
|
|
||||||
var region = regionsAreMasks ? mask : regionArray.find(x => x.name == triggerPiece.name)
|
|
||||||
checkMaximumSensitivity(region, detectorObject, function(err1) {
|
|
||||||
checkTriggerThreshold(region, detectorObject, function(err2) {
|
|
||||||
++filteredCount
|
|
||||||
if(!err1 && !err2)++filteredCountSuccess
|
|
||||||
if(filteredCount === trigger.merged.length && filteredCountSuccess > 0){
|
|
||||||
detectorObject.doObjectDetection = (jsonData.globalInfo.isAtleatOneDetectorPluginConnected && jsonData.rawMonitorConfig.details.detector_use_detect_object === '1')
|
|
||||||
sendDetectedData(detectorObject)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}else{
|
|
||||||
if(trigger.matrix)detectorObject.details.matrices = [trigger.matrix]
|
|
||||||
var region = regionsAreMasks ? mask : regionArray.find(x => x.name == detectorObject.name)
|
|
||||||
checkMaximumSensitivity(region, detectorObject, function(err1) {
|
|
||||||
checkTriggerThreshold(region, detectorObject, function(err2) {
|
|
||||||
if(!err1 && !err2){
|
|
||||||
detectorObject.doObjectDetection = (jsonData.globalInfo.isAtleatOneDetectorPluginConnected && jsonData.rawMonitorConfig.details.detector_use_detect_object === '1')
|
|
||||||
sendDetectedData(detectorObject)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(jsonData.rawMonitorConfig.details.detector_noise_filter==='1'){
|
|
||||||
Object.keys(regions.notForPam).forEach(function(name){
|
|
||||||
if(!noiseFilterArray[name])noiseFilterArray[name]=[];
|
|
||||||
})
|
|
||||||
pamDiff.on('diff', (data) => {
|
|
||||||
var filteredCount = 0
|
|
||||||
var filteredCountSuccess = 0
|
|
||||||
var acceptedTriggers = getRegionsWithMinimumChange(data)
|
|
||||||
acceptedTriggers.forEach(function(trigger){
|
|
||||||
filterTheNoise(noiseFilterArray,regions,trigger,function(err){
|
|
||||||
++filteredCount
|
|
||||||
if(!err)++filteredCountSuccess
|
|
||||||
if(filteredCount === data.trigger.length && filteredCountSuccess > 0){
|
|
||||||
buildTriggerEvent(mergePamTriggers({trigger: acceptedTriggers}))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}else{
|
|
||||||
pamDiff.on('diff', (data) => {
|
|
||||||
buildTriggerEvent(mergePamTriggers({trigger: getRegionsWithMinimumChange(data)}))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
//config.detectorMergePamRegionTriggers NOT true
|
|
||||||
//original behaviour, all regions have their own event.
|
|
||||||
var buildTriggerEvent = function(trigger){
|
|
||||||
var detectorObject = {
|
|
||||||
f:'trigger',
|
|
||||||
id: monitorId,
|
|
||||||
ke: groupKey,
|
|
||||||
name:trigger.name,
|
|
||||||
details:{
|
|
||||||
plug:'built-in',
|
|
||||||
name:trigger.name,
|
|
||||||
reason:'motion',
|
|
||||||
confidence:trigger.percent,
|
|
||||||
matrices: trigger.matrices,
|
|
||||||
imgHeight:jsonData.rawMonitorConfig.details.detector_scale_y,
|
|
||||||
imgWidth:jsonData.rawMonitorConfig.details.detector_scale_x
|
|
||||||
},
|
|
||||||
plates:[],
|
|
||||||
}
|
|
||||||
if(trigger.matrix)detectorObject.details.matrices = [trigger.matrix]
|
|
||||||
var region = regionsAreMasks ? mask : Object.values(regionJson).find(x => x.name == detectorObject.name)
|
|
||||||
checkMaximumSensitivity(region, detectorObject, function(err1) {
|
|
||||||
checkTriggerThreshold(region, detectorObject, function(err2) {
|
|
||||||
if(!err1 && ! err2){
|
|
||||||
detectorObject.doObjectDetection = (jsonData.globalInfo.isAtleatOneDetectorPluginConnected && jsonData.rawMonitorConfig.details.detector_use_detect_object === '1')
|
|
||||||
sendDetectedData(detectorObject)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if(jsonData.rawMonitorConfig.details.detector_noise_filter==='1'){
|
|
||||||
Object.keys(regions.notForPam).forEach(function(name){
|
|
||||||
if(!noiseFilterArray[name])noiseFilterArray[name]=[];
|
|
||||||
})
|
|
||||||
pamDiff.on('diff', (data) => {
|
|
||||||
getRegionsWithMinimumChange(data).forEach(function(trigger){
|
|
||||||
filterTheNoise(noiseFilterArray,regions,trigger,function(){
|
|
||||||
createMatrixFromPamTrigger(trigger)
|
|
||||||
buildTriggerEvent(trigger)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}else{
|
|
||||||
pamDiff.on('diff', (data) => {
|
|
||||||
getRegionsWithMinimumChange(data).forEach(function(trigger){
|
|
||||||
createMatrixFromPamTrigger(trigger)
|
|
||||||
buildTriggerEvent(trigger)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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){
|
|
||||||
var x = parseFloat(points[0]);
|
|
||||||
var y = parseFloat(points[1]);
|
|
||||||
if(x < 0)x = 0;
|
|
||||||
if(y < 0)y = 0;
|
|
||||||
region.polygon.push({
|
|
||||||
x: x,
|
|
||||||
y: y
|
|
||||||
})
|
|
||||||
})
|
|
||||||
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};
|
|
||||||
}
|
|
||||||
|
|
||||||
filterTheNoise = function(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 = jsonData.rawMonitorConfig.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(null,trigger)
|
|
||||||
}else{
|
|
||||||
callback(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkMaximumSensitivity = function(region, detectorObject, callback) {
|
|
||||||
var logName = detectorObject.id + ':' + detectorObject.name
|
|
||||||
var globalMaxSensitivity = parseInt(jsonData.rawMonitorConfig.details.detector_max_sensitivity) || undefined
|
|
||||||
var maxSensitivity = parseInt(region.max_sensitivity) || globalMaxSensitivity
|
|
||||||
if (maxSensitivity === undefined || detectorObject.details.confidence <= maxSensitivity) {
|
|
||||||
callback(null)
|
|
||||||
} else {
|
|
||||||
callback(true)
|
|
||||||
if (triggerTimer[detectorObject.name] !== undefined) {
|
|
||||||
clearTimeout(triggerTimer[detectorObject.name].timeout)
|
|
||||||
triggerTimer[detectorObject.name] = undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkTriggerThreshold = function(region, detectorObject, callback){
|
|
||||||
var threshold = parseInt(region.threshold) || globalThreshold
|
|
||||||
if (threshold <= 1) {
|
|
||||||
callback(null)
|
|
||||||
} else {
|
|
||||||
if (triggerTimer[detectorObject.name] === undefined) {
|
|
||||||
triggerTimer[detectorObject.name] = {
|
|
||||||
count : threshold,
|
|
||||||
timeout : null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (--triggerTimer[detectorObject.name].count == 0) {
|
|
||||||
callback(null)
|
|
||||||
clearTimeout(triggerTimer[detectorObject.name].timeout)
|
|
||||||
triggerTimer[detectorObject.name] = undefined
|
|
||||||
} else {
|
|
||||||
callback(true)
|
|
||||||
var fps = parseFloat(jsonData.rawMonitorConfig.details.detector_fps) || 2
|
|
||||||
if (triggerTimer[detectorObject.name].timeout !== null)
|
|
||||||
clearTimeout(triggerTimer[detectorObject.name].timeout)
|
|
||||||
triggerTimer[detectorObject.name].timeout = setTimeout(function() {
|
|
||||||
triggerTimer[detectorObject.name] = undefined
|
|
||||||
}, ((threshold+0.5) * 1000) / fps)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mergePamTriggers = function(data){
|
|
||||||
if(data.trigger.length > 1){
|
|
||||||
var n = 0
|
|
||||||
var sum = 0
|
|
||||||
var name = []
|
|
||||||
var matrices = []
|
|
||||||
data.trigger.forEach(function(trigger){
|
|
||||||
name.push(trigger.name + ' ('+trigger.percent+'%)')
|
|
||||||
++n
|
|
||||||
sum += trigger.percent
|
|
||||||
createMatrixFromPamTrigger(trigger)
|
|
||||||
if(trigger.matrix)matrices.push(trigger.matrix)
|
|
||||||
})
|
|
||||||
var average = sum / n
|
|
||||||
name = name.join(', ')
|
|
||||||
if(matrices.length === 0)matrices = null
|
|
||||||
var trigger = {
|
|
||||||
name: name,
|
|
||||||
percent: parseInt(average),
|
|
||||||
matrices: matrices,
|
|
||||||
merged: data.trigger
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
var trigger = data.trigger[0]
|
|
||||||
createMatrixFromPamTrigger(trigger)
|
|
||||||
trigger.matrices = [trigger.matrix]
|
|
||||||
}
|
|
||||||
return trigger
|
|
||||||
}
|
|
||||||
createMatrixFromPamTrigger = function(trigger){
|
|
||||||
if(
|
|
||||||
trigger.minX ||
|
|
||||||
trigger.maxX ||
|
|
||||||
trigger.minY ||
|
|
||||||
trigger.maxY
|
|
||||||
){
|
|
||||||
var coordinates = [
|
|
||||||
{"x" : trigger.minX, "y" : trigger.minY},
|
|
||||||
{"x" : trigger.maxX, "y" : trigger.minY},
|
|
||||||
{"x" : trigger.maxX, "y" : trigger.maxY}
|
|
||||||
]
|
|
||||||
var width = Math.sqrt( Math.pow(coordinates[1].x - coordinates[0].x, 2) + Math.pow(coordinates[1].y - coordinates[0].y, 2));
|
|
||||||
var height = Math.sqrt( Math.pow(coordinates[2].x - coordinates[1].x, 2) + Math.pow(coordinates[2].y - coordinates[1].y, 2))
|
|
||||||
trigger.matrix = {
|
|
||||||
x: coordinates[0].x,
|
|
||||||
y: coordinates[0].y,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
tag: trigger.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return trigger
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(cameraProcess,fallback){
|
|
||||||
if(jsonData.rawMonitorConfig.details.detector_pam === '1'){
|
|
||||||
createPamDiffEngine()
|
|
||||||
cameraProcess.stdio[3].pipe(p2p).pipe(pamDiff)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,394 @@
|
||||||
|
const P2P = require('pipe2pam')
|
||||||
|
const PamDiff = require('pam-diff')
|
||||||
|
module.exports = function(jsonData,pamDiffResponder){
|
||||||
|
const noiseFilterArray = {};
|
||||||
|
const config = jsonData.globalInfo.config
|
||||||
|
const completeMonitorConfig = jsonData.rawMonitorConfig
|
||||||
|
const groupKey = completeMonitorConfig.ke
|
||||||
|
const monitorId = completeMonitorConfig.mid
|
||||||
|
const monitorDetails = completeMonitorConfig.details
|
||||||
|
const triggerTimer = {}
|
||||||
|
let regionJson
|
||||||
|
try{
|
||||||
|
regionJson = JSON.parse(monitorDetails.cords)
|
||||||
|
}catch(err){
|
||||||
|
regionJson = monitorDetails.cords
|
||||||
|
}
|
||||||
|
let fullFrame = null
|
||||||
|
const pamDetectorIsEnabled = monitorDetails.detector_pam === '1'
|
||||||
|
const width = parseInt(monitorDetails.detector_scale_x) || 640
|
||||||
|
const height = parseInt(monitorDetails.detector_scale_y) || 480
|
||||||
|
const globalSensitivity = parseInt(monitorDetails.detector_sensitivity) || 10
|
||||||
|
const globalMaxSensitivity = parseInt(monitorDetails.detector_max_sensitivity) || 100
|
||||||
|
const globalColorThreshold = parseInt(monitorDetails.detector_color_threshold) || 9
|
||||||
|
const globalThreshold = parseInt(monitorDetails.detector_threshold) || 1
|
||||||
|
const detectorFrameRate = parseInt(monitorDetails.detector_fps) || 2
|
||||||
|
const regionsAreMasks = monitorDetails.detector_frame !== '1' && monitorDetails.inverse_trigger === '1';
|
||||||
|
const regionConfidenceMinimums = {}
|
||||||
|
const regionConfidenceMaximums = {}
|
||||||
|
const regionTriggerThresholds = {}
|
||||||
|
const mergeTriggers = config.detectorMergePamRegionTriggers
|
||||||
|
if(Object.keys(regionJson).length === 0 || monitorDetails.detector_frame === '1'){
|
||||||
|
fullFrame = {
|
||||||
|
name:'FULL_FRAME',
|
||||||
|
sensitivity: globalSensitivity,
|
||||||
|
color_threshold: globalColorThreshold,
|
||||||
|
points:[
|
||||||
|
[0,0],
|
||||||
|
[0,height],
|
||||||
|
[width,height],
|
||||||
|
[width,0]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const mask = {
|
||||||
|
name: 'theMask',
|
||||||
|
max_sensitivity : globalSensitivity,
|
||||||
|
threshold : globalThreshold,
|
||||||
|
}
|
||||||
|
const regions = createPamDiffRegionArray(regionJson,globalColorThreshold,globalSensitivity,fullFrame)
|
||||||
|
const pamDiffOptions = {
|
||||||
|
mask: regionsAreMasks,
|
||||||
|
grayscale: 'luminosity',
|
||||||
|
regions : regions.forPam,
|
||||||
|
percent : config.sendDetectionDataWithoutTrigger ? 1 : globalSensitivity,
|
||||||
|
difference : globalColorThreshold,
|
||||||
|
response: "blobs",
|
||||||
|
}
|
||||||
|
const pamDiff = new PamDiff(pamDiffOptions)
|
||||||
|
const p2p = new P2P()
|
||||||
|
Object.values(regionJson).forEach(function(region){
|
||||||
|
regionConfidenceMinimums[region.name] = parseInt(region.sensitivity) || globalSensitivity;
|
||||||
|
regionConfidenceMaximums[region.name] = parseInt(region.max_sensitivity) || globalMaxSensitivity;
|
||||||
|
regionTriggerThresholds[region.name] = parseInt(region.threshold) || globalThreshold;
|
||||||
|
})
|
||||||
|
Object.keys(regions.notForPam).forEach(function(name){
|
||||||
|
if(!noiseFilterArray[name])noiseFilterArray[name] = []
|
||||||
|
})
|
||||||
|
if(typeof pamDiffResponder === 'function'){
|
||||||
|
var sendDetectedData = function(detectorObject){
|
||||||
|
pamDiffResponder(detectorObject)
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
var sendDetectedData = function(detectorObject){
|
||||||
|
pamDiffResponder.write(Buffer.from(JSON.stringify(detectorObject)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function logData(...args){
|
||||||
|
process.logData(JSON.stringify(args))
|
||||||
|
}
|
||||||
|
function getRegionsWithMinimumChange(data){
|
||||||
|
try{
|
||||||
|
var acceptedTriggers = []
|
||||||
|
data.forEach((trigger) => {
|
||||||
|
if(trigger.percent > regionConfidenceMinimums[trigger.name]){
|
||||||
|
acceptedTriggers.push(trigger)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return acceptedTriggers
|
||||||
|
}catch(err){
|
||||||
|
return []
|
||||||
|
// process.logData(err.stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getRegionsBelowMaximumChange(data){
|
||||||
|
try{
|
||||||
|
var acceptedTriggers = []
|
||||||
|
data.forEach((trigger) => {
|
||||||
|
if(trigger.percent < regionConfidenceMaximums[trigger.name]){
|
||||||
|
acceptedTriggers.push(trigger)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return acceptedTriggers
|
||||||
|
}catch(err){
|
||||||
|
return []
|
||||||
|
// process.logData(err.stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getRegionsWithThresholdMet(data){
|
||||||
|
try{
|
||||||
|
var acceptedTriggers = []
|
||||||
|
data.forEach((trigger) => {
|
||||||
|
if(checkTriggerThreshold(trigger.name)){
|
||||||
|
acceptedTriggers.push(trigger)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return acceptedTriggers
|
||||||
|
}catch(err){
|
||||||
|
return []
|
||||||
|
// process.logData(err.stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function buildDetectorObject(trigger){
|
||||||
|
return {
|
||||||
|
f: 'trigger',
|
||||||
|
id: monitorId,
|
||||||
|
ke: groupKey,
|
||||||
|
name: trigger.name,
|
||||||
|
details: {
|
||||||
|
plug:'built-in',
|
||||||
|
name: trigger.name,
|
||||||
|
reason: 'motion',
|
||||||
|
confidence:trigger.percent,
|
||||||
|
matrices: trigger.matrices.filter(matrix => !!matrix),
|
||||||
|
imgHeight: monitorDetails.detector_scale_y,
|
||||||
|
imgWidth: monitorDetails.detector_scale_x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function filterTheNoise(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 = monitorDetails.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(null,trigger)
|
||||||
|
}else{
|
||||||
|
callback(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function filterTheNoiseFromMultipleRegions(acceptedTriggers){
|
||||||
|
return new Promise((resolve,reject) => {
|
||||||
|
let filteredCount = 0
|
||||||
|
let filteredCountSuccess = 0
|
||||||
|
acceptedTriggers.forEach(function(trigger,n){
|
||||||
|
filterTheNoise(trigger,function(err){
|
||||||
|
++filteredCount
|
||||||
|
if(!err)++filteredCountSuccess
|
||||||
|
if(filteredCount === acceptedTriggers.length && filteredCountSuccess > 0){
|
||||||
|
resolve(true)
|
||||||
|
}else if(acceptedTriggers.length === n + 1){
|
||||||
|
resolve(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function getAcceptedTriggers(data){
|
||||||
|
return getRegionsWithThresholdMet(
|
||||||
|
getRegionsBelowMaximumChange(
|
||||||
|
getRegionsWithMinimumChange(data)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
function buildTriggerEvent(data){
|
||||||
|
const detectorObject = buildDetectorObject(data)
|
||||||
|
sendDetectedData(detectorObject)
|
||||||
|
}
|
||||||
|
function attachPamPipeDrivers(cameraProcess,onEvent){
|
||||||
|
let pamAnalyzer = function(){}
|
||||||
|
if(typeof onEvent === 'function'){
|
||||||
|
pamAnalyzer = onEvent
|
||||||
|
}else{
|
||||||
|
if(mergeTriggers === true){
|
||||||
|
// merge pam triggers for performance boost
|
||||||
|
if(monitorDetails.detector_noise_filter === '1'){
|
||||||
|
pamAnalyzer = async (data) => {
|
||||||
|
const acceptedTriggers = getAcceptedTriggers(data.trigger)
|
||||||
|
const passedFilter = await filterTheNoiseFromMultipleRegions(acceptedTriggers)
|
||||||
|
if(passedFilter)buildTriggerEvent(mergePamTriggers(acceptedTriggers))
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
pamAnalyzer = (data) => {
|
||||||
|
const acceptedTriggers = getAcceptedTriggers(data.trigger)
|
||||||
|
// logData(acceptedTriggers)
|
||||||
|
buildTriggerEvent(mergePamTriggers(acceptedTriggers))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
//config.detectorMergePamRegionTriggers NOT true
|
||||||
|
//original behaviour, all regions have their own event.
|
||||||
|
if(monitorDetails.detector_noise_filter === '1'){
|
||||||
|
pamAnalyzer = (data) => {
|
||||||
|
getAcceptedTriggers(data.trigger).forEach(function(trigger){
|
||||||
|
filterTheNoise(trigger,function(){
|
||||||
|
createMatricesFromBlobs(trigger)
|
||||||
|
buildTriggerEvent(trigger)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
pamAnalyzer = (data) => {
|
||||||
|
getAcceptedTriggers(data.trigger).forEach(function(trigger){
|
||||||
|
createMatricesFromBlobs(trigger)
|
||||||
|
buildTriggerEvent(trigger)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pamDiff.on('diff',pamAnalyzer)
|
||||||
|
cameraProcess.stdio[3].pipe(p2p).pipe(pamDiff)
|
||||||
|
}
|
||||||
|
function createPamDiffRegionArray(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){
|
||||||
|
var x = parseFloat(points[0]);
|
||||||
|
var y = parseFloat(points[1]);
|
||||||
|
if(x < 0)x = 0;
|
||||||
|
if(y < 0)y = 0;
|
||||||
|
region.polygon.push({
|
||||||
|
x: x,
|
||||||
|
y: y
|
||||||
|
})
|
||||||
|
})
|
||||||
|
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};
|
||||||
|
}
|
||||||
|
function checkTriggerThreshold(triggerLabel){
|
||||||
|
const threshold = regionTriggerThresholds[triggerLabel] || globalThreshold
|
||||||
|
if (threshold <= 1) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
if (triggerTimer[triggerLabel] === undefined) {
|
||||||
|
triggerTimer[triggerLabel] = {
|
||||||
|
count : threshold,
|
||||||
|
timeout : null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const theTriggerTimerInfo = triggerTimer[triggerLabel]
|
||||||
|
if (--theTriggerTimerInfo.count == 0) {
|
||||||
|
clearTimeout(theTriggerTimerInfo.timeout)
|
||||||
|
theTriggerTimerInfo = undefined
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
if (theTriggerTimerInfo.timeout !== null){
|
||||||
|
clearTimeout(theTriggerTimerInfo.timeout)
|
||||||
|
}
|
||||||
|
theTriggerTimerInfo.timeout = setTimeout(function() {
|
||||||
|
triggerTimer[triggerLabel] = undefined
|
||||||
|
}, ((threshold+0.5) * 1000) / detectorFrameRate)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function mergePamTriggers(acceptedTriggers){
|
||||||
|
var n = 0
|
||||||
|
var sum = 0
|
||||||
|
var matrices = []
|
||||||
|
acceptedTriggers.forEach(function(trigger){
|
||||||
|
++n
|
||||||
|
sum += trigger.percent
|
||||||
|
createMatricesFromBlobs(trigger)
|
||||||
|
if(trigger.matrices)matrices.push(...trigger.matrices)
|
||||||
|
})
|
||||||
|
var average = sum / n
|
||||||
|
if(matrices === null)matrices = []
|
||||||
|
var trigger = {
|
||||||
|
name: `multipleRegions`,
|
||||||
|
percent: parseInt(average),
|
||||||
|
matrices: matrices,
|
||||||
|
acceptedTriggers: acceptedTriggers
|
||||||
|
}
|
||||||
|
return trigger
|
||||||
|
}
|
||||||
|
function getPropertiesFromBlob(data){
|
||||||
|
const coordinates = [
|
||||||
|
{"x" : data.minX, "y" : data.minY},
|
||||||
|
{"x" : data.maxX, "y" : data.minY},
|
||||||
|
{"x" : data.maxX, "y" : data.maxY}
|
||||||
|
]
|
||||||
|
return {
|
||||||
|
confidence: data.percent,
|
||||||
|
width: Math.sqrt( Math.pow(coordinates[1].x - coordinates[0].x, 2) + Math.pow(coordinates[1].y - coordinates[0].y, 2)),
|
||||||
|
height: Math.sqrt( Math.pow(coordinates[2].x - coordinates[1].x, 2) + Math.pow(coordinates[2].y - coordinates[1].y, 2)),
|
||||||
|
x: coordinates[0].x,
|
||||||
|
y: coordinates[0].y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function createMatricesFromBlobs(trigger){
|
||||||
|
trigger.matrices = []
|
||||||
|
trigger.blobs.forEach(function(blob){
|
||||||
|
const blobProperties = getPropertiesFromBlob(blob)
|
||||||
|
blobProperties.tag = trigger.name
|
||||||
|
trigger.matrices.push(blobProperties)
|
||||||
|
})
|
||||||
|
return trigger
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
//functions
|
||||||
|
getRegionsWithMinimumChange,
|
||||||
|
getRegionsBelowMaximumChange,
|
||||||
|
getRegionsWithThresholdMet,
|
||||||
|
buildDetectorObject,
|
||||||
|
filterTheNoise,
|
||||||
|
filterTheNoiseFromMultipleRegions,
|
||||||
|
getAcceptedTriggers,
|
||||||
|
sendDetectedData,
|
||||||
|
buildTriggerEvent,
|
||||||
|
attachPamPipeDrivers,
|
||||||
|
createPamDiffRegionArray,
|
||||||
|
checkTriggerThreshold,
|
||||||
|
mergePamTriggers,
|
||||||
|
getPropertiesFromBlob,
|
||||||
|
createMatricesFromBlobs,
|
||||||
|
logData,
|
||||||
|
// parameters
|
||||||
|
pamDetectorIsEnabled,
|
||||||
|
noiseFilterArray,
|
||||||
|
config,
|
||||||
|
completeMonitorConfig,
|
||||||
|
groupKey,
|
||||||
|
monitorId,
|
||||||
|
monitorDetails,
|
||||||
|
triggerTimer,
|
||||||
|
regionJson,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
globalSensitivity,
|
||||||
|
globalMaxSensitivity,
|
||||||
|
globalColorThreshold,
|
||||||
|
globalThreshold,
|
||||||
|
detectorFrameRate,
|
||||||
|
regionsAreMasks,
|
||||||
|
regionConfidenceMinimums,
|
||||||
|
regionConfidenceMaximums,
|
||||||
|
regionTriggerThresholds,
|
||||||
|
mergeTriggers,
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ const isWindows = (process.platform === 'win32' || process.platform === 'win64')
|
||||||
process.send = process.send || function () {};
|
process.send = process.send || function () {};
|
||||||
|
|
||||||
var jsonData = JSON.parse(fs.readFileSync(process.argv[3],'utf8'))
|
var jsonData = JSON.parse(fs.readFileSync(process.argv[3],'utf8'))
|
||||||
|
const config = jsonData.globalInfo.config
|
||||||
const ffmpegAbsolutePath = process.argv[2].trim()
|
const ffmpegAbsolutePath = process.argv[2].trim()
|
||||||
const ffmpegCommandString = jsonData.cmd
|
const ffmpegCommandString = jsonData.cmd
|
||||||
const rawMonitorConfig = jsonData.rawMonitorConfig
|
const rawMonitorConfig = jsonData.rawMonitorConfig
|
||||||
|
@ -21,6 +22,7 @@ var writeToStderr = function(text){
|
||||||
}
|
}
|
||||||
// fs.appendFileSync('/home/ubuntu/cdn-site/tools/compilers/diycam/Shinobi/test.log',text + '\n','utf8')
|
// fs.appendFileSync('/home/ubuntu/cdn-site/tools/compilers/diycam/Shinobi/test.log',text + '\n','utf8')
|
||||||
}
|
}
|
||||||
|
process.logData = writeToStderr
|
||||||
if(!process.argv[2] || !process.argv[3]){
|
if(!process.argv[2] || !process.argv[3]){
|
||||||
return writeToStderr('Missing FFMPEG Command String or no command operator')
|
return writeToStderr('Missing FFMPEG Command String or no command operator')
|
||||||
}
|
}
|
||||||
|
@ -109,10 +111,8 @@ writeToStderr('Thread Opening')
|
||||||
|
|
||||||
if(rawMonitorConfig.details.detector === '1' && rawMonitorConfig.details.detector_pam === '1'){
|
if(rawMonitorConfig.details.detector === '1' && rawMonitorConfig.details.detector_pam === '1'){
|
||||||
try{
|
try{
|
||||||
const attachPamDetector = require(__dirname + '/detector.js')(jsonData,stdioWriters[3])
|
const attachPamDetector = require(config.monitorDetectorDaemonPath ? config.monitorDetectorDaemonPath : __dirname + '/detector.js')(jsonData,stdioWriters[3])
|
||||||
attachPamDetector(cameraProcess,(err)=>{
|
attachPamDetector(cameraProcess)
|
||||||
writeToStderr(err)
|
|
||||||
})
|
|
||||||
}catch(err){
|
}catch(err){
|
||||||
writeToStderr(err.stack)
|
writeToStderr(err.stack)
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ module.exports = function(s,config,lang,app,io){
|
||||||
Object.keys(options).forEach((key) => {
|
Object.keys(options).forEach((key) => {
|
||||||
const value = options[key]
|
const value = options[key]
|
||||||
if(typeof value === 'string'){
|
if(typeof value === 'string'){
|
||||||
newOptions[key] = value.replace(/__CURRENT_TOKEN/g,Camera.current_profile.token)
|
newOptions[key] = value.replace(/__CURRENT_TOKEN/g,Camera.current_profile ? Camera.current_profile.token : 'NOTOKEN')
|
||||||
}else if(value !== undefined && value !== null){
|
}else if(value !== undefined && value !== null){
|
||||||
newOptions[key] = value
|
newOptions[key] = value
|
||||||
}
|
}
|
||||||
|
|
|
@ -371,6 +371,7 @@ module.exports = function(s,config,lang){
|
||||||
}
|
}
|
||||||
const moveCameraPtzToMatrix = function(event,trackingTarget){
|
const moveCameraPtzToMatrix = function(event,trackingTarget){
|
||||||
if(moveLock[event.ke + event.id])return;
|
if(moveLock[event.ke + event.id])return;
|
||||||
|
trackingTarget = trackingTarget || 'person'
|
||||||
const imgHeight = event.details.imgHeight
|
const imgHeight = event.details.imgHeight
|
||||||
const imgWidth = event.details.imgWidth
|
const imgWidth = event.details.imgWidth
|
||||||
const thresholdX = imgWidth * 0.125
|
const thresholdX = imgWidth * 0.125
|
||||||
|
@ -378,7 +379,7 @@ module.exports = function(s,config,lang){
|
||||||
const imageCenterX = imgWidth / 2
|
const imageCenterX = imgWidth / 2
|
||||||
const imageCenterY = imgHeight / 2
|
const imageCenterY = imgHeight / 2
|
||||||
const matrices = event.details.matrices || []
|
const matrices = event.details.matrices || []
|
||||||
const largestMatrix = getLargestMatrix(matrices.filter(matrix => matrix.tag === (trackingTarget || 'person')))
|
const largestMatrix = getLargestMatrix(matrices.filter(matrix => trackingTarget.indexOf(matrix.tag) > -1))
|
||||||
// console.log(matrices.find(matrix => matrix.tag === 'person'))
|
// console.log(matrices.find(matrix => matrix.tag === 'person'))
|
||||||
if(!largestMatrix)return;
|
if(!largestMatrix)return;
|
||||||
const monitorConfig = s.group[event.ke].rawMonitorConfigurations[event.id]
|
const monitorConfig = s.group[event.ke].rawMonitorConfigurations[event.id]
|
||||||
|
|
|
@ -221,6 +221,7 @@ module.exports = async (s,config,lang,app,io) => {
|
||||||
case'blocks':
|
case'blocks':
|
||||||
fs.readdir(thirdLevelName,function(err,webFolderContents){
|
fs.readdir(thirdLevelName,function(err,webFolderContents){
|
||||||
webFolderContents.forEach(function(filename){
|
webFolderContents.forEach(function(filename){
|
||||||
|
if(!filename)return;
|
||||||
var fullPath = thirdLevelName + '/' + filename
|
var fullPath = thirdLevelName + '/' + filename
|
||||||
var blockPrefix = ''
|
var blockPrefix = ''
|
||||||
switch(true){
|
switch(true){
|
||||||
|
|
|
@ -84,7 +84,6 @@ module.exports = function(s,config,lang,app,io){
|
||||||
plug: "dropInEvent",
|
plug: "dropInEvent",
|
||||||
reason: reason
|
reason: reason
|
||||||
},
|
},
|
||||||
doObjectDetection: (s.isAtleatOneDetectorPluginConnected && s.group[ke].rawMonitorConfigurations[mid].details.detector_use_detect_object === '1')
|
|
||||||
},config.dropInEventForceSaveEvent)
|
},config.dropInEventForceSaveEvent)
|
||||||
}
|
}
|
||||||
if(search(filename,'.txt')){
|
if(search(filename,'.txt')){
|
||||||
|
@ -310,7 +309,6 @@ module.exports = function(s,config,lang,app,io){
|
||||||
plug: "dropInEvent",
|
plug: "dropInEvent",
|
||||||
reason: reasonTag
|
reason: reasonTag
|
||||||
},
|
},
|
||||||
doObjectDetection: (s.isAtleatOneDetectorPluginConnected && details.detector_use_detect_object === '1')
|
|
||||||
},config.dropInEventForceSaveEvent)
|
},config.dropInEventForceSaveEvent)
|
||||||
callback()
|
callback()
|
||||||
})
|
})
|
||||||
|
|
|
@ -347,6 +347,9 @@ module.exports = (s,config,lang,app,io) => {
|
||||||
const runEventExecutions = async (eventTime,monitorConfig,eventDetails,forceSave,filter,d, triggerEvent) => {
|
const runEventExecutions = async (eventTime,monitorConfig,eventDetails,forceSave,filter,d, triggerEvent) => {
|
||||||
const monitorDetails = monitorConfig.details
|
const monitorDetails = monitorConfig.details
|
||||||
const detailString = JSON.stringify(eventDetails)
|
const detailString = JSON.stringify(eventDetails)
|
||||||
|
if(monitorDetails.detector_ptz_follow === '1'){
|
||||||
|
moveCameraPtzToMatrix(d,monitorDetails.detector_ptz_follow_target)
|
||||||
|
}
|
||||||
if(monitorDetails.det_multi_trig === '1'){
|
if(monitorDetails.det_multi_trig === '1'){
|
||||||
runMultiTrigger(monitorConfig,eventDetails, d, triggerEvent)
|
runMultiTrigger(monitorConfig,eventDetails, d, triggerEvent)
|
||||||
}
|
}
|
||||||
|
@ -642,9 +645,6 @@ module.exports = (s,config,lang,app,io) => {
|
||||||
didCountingAlready = true
|
didCountingAlready = true
|
||||||
countObjects(d)
|
countObjects(d)
|
||||||
}
|
}
|
||||||
if(monitorDetails.detector_ptz_follow === '1'){
|
|
||||||
moveCameraPtzToMatrix(d,monitorDetails.detector_ptz_follow_target)
|
|
||||||
}
|
|
||||||
if(filter.useLock){
|
if(filter.useLock){
|
||||||
const passedMotionLock = checkMotionLock(d,monitorDetails)
|
const passedMotionLock = checkMotionLock(d,monitorDetails)
|
||||||
if(!passedMotionLock)return
|
if(!passedMotionLock)return
|
||||||
|
@ -653,6 +653,12 @@ module.exports = (s,config,lang,app,io) => {
|
||||||
if(!passedObjectInRegionCheck)return
|
if(!passedObjectInRegionCheck)return
|
||||||
|
|
||||||
//
|
//
|
||||||
|
d.doObjectDetection = (
|
||||||
|
eventDetails.reason !== 'object' &&
|
||||||
|
s.isAtleatOneDetectorPluginConnected &&
|
||||||
|
monitorDetails.detector_use_detect_object === '1' &&
|
||||||
|
monitorDetails.detector_use_motion === '1'
|
||||||
|
);
|
||||||
if(d.doObjectDetection === true){
|
if(d.doObjectDetection === true){
|
||||||
sendFramesFromSecondaryOutput(d.ke,d.id)
|
sendFramesFromSecondaryOutput(d.ke,d.id)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,11 @@ module.exports = function(s,config){
|
||||||
s.onSocketAuthenticationExtensions.push(callback)
|
s.onSocketAuthenticationExtensions.push(callback)
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
|
s.onUserLogExtensions = []
|
||||||
|
s.onUserLog = function(callback){
|
||||||
|
s.onUserLogExtensions.push(callback)
|
||||||
|
}
|
||||||
|
//
|
||||||
s.loadGroupExtensions = []
|
s.loadGroupExtensions = []
|
||||||
s.loadGroupExtender = function(callback){
|
s.loadGroupExtender = function(callback){
|
||||||
s.loadGroupExtensions.push(callback)
|
s.loadGroupExtensions.push(callback)
|
||||||
|
|
|
@ -63,11 +63,18 @@ module.exports = async (s,config,lang,onFinish) => {
|
||||||
}
|
}
|
||||||
},null,3),'utf8')
|
},null,3),'utf8')
|
||||||
var cameraCommandParams = [
|
var cameraCommandParams = [
|
||||||
__dirname + '/cameraThread/singleCamera.js',
|
config.monitorDaemonPath ? config.monitorDaemonPath : __dirname + '/cameraThread/singleCamera.js',
|
||||||
config.ffmpegDir,
|
config.ffmpegDir,
|
||||||
e.sdir + 'cmd.txt'
|
e.sdir + 'cmd.txt'
|
||||||
]
|
]
|
||||||
return spawn('node',cameraCommandParams,{detached: true,stdio: stdioPipes})
|
const cameraProcess = spawn('node',cameraCommandParams,{detached: true,stdio: stdioPipes})
|
||||||
|
if(config.debugLog === true){
|
||||||
|
cameraProcess.stderr.on('data',(data) => {
|
||||||
|
console.log(`${e.ke} ${e.mid}`)
|
||||||
|
console.log(data.toString())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return cameraProcess
|
||||||
}catch(err){
|
}catch(err){
|
||||||
s.systemLog(err)
|
s.systemLog(err)
|
||||||
return null
|
return null
|
||||||
|
|
|
@ -682,7 +682,7 @@ module.exports = (s,config,lang) => {
|
||||||
const videoFps = !isNaN(parseFloat(e.details.stream_fps)) && e.details.stream_fps !== '0' ? parseFloat(e.details.stream_fps) : null
|
const videoFps = !isNaN(parseFloat(e.details.stream_fps)) && e.details.stream_fps !== '0' ? parseFloat(e.details.stream_fps) : null
|
||||||
const inputMap = buildInputMap(e,e.details.input_map_choices.detector_sip_buffer)
|
const inputMap = buildInputMap(e,e.details.input_map_choices.detector_sip_buffer)
|
||||||
const { videoWidth, videoHeight } = validateDimensions(e.details.event_record_scale_x,e.details.event_record_scale_y)
|
const { videoWidth, videoHeight } = validateDimensions(e.details.event_record_scale_x,e.details.event_record_scale_y)
|
||||||
const hlsTime = !isNaN(parseInt(e.details.detector_buffer_hls_list_time)) ? `${parseInt(e.details.detector_buffer_hls_list_time)}` : '2'
|
const hlsTime = !isNaN(parseInt(e.details.detector_buffer_hls_time)) ? `${parseInt(e.details.detector_buffer_hls_time)}` : '2'
|
||||||
const hlsListSize = !isNaN(parseInt(e.details.detector_buffer_hls_list_size)) ? `${parseInt(e.details.detector_buffer_hls_list_size)}` : '4'
|
const hlsListSize = !isNaN(parseInt(e.details.detector_buffer_hls_list_size)) ? `${parseInt(e.details.detector_buffer_hls_list_size)}` : '4'
|
||||||
if(inputMap)outputFlags.push(inputMap)
|
if(inputMap)outputFlags.push(inputMap)
|
||||||
if(e.details.cust_sip_record)outputFlags.push(e.details.cust_sip_record)
|
if(e.details.cust_sip_record)outputFlags.push(e.details.cust_sip_record)
|
||||||
|
|
|
@ -904,15 +904,28 @@ module.exports = function(s,config,lang){
|
||||||
if(e.details.detector_pam === '1'){
|
if(e.details.detector_pam === '1'){
|
||||||
// s.group[e.ke].activeMonitors[e.id].spawn.stdio[3].pipe(s.group[e.ke].activeMonitors[e.id].p2p).pipe(s.group[e.ke].activeMonitors[e.id].pamDiff)
|
// s.group[e.ke].activeMonitors[e.id].spawn.stdio[3].pipe(s.group[e.ke].activeMonitors[e.id].p2p).pipe(s.group[e.ke].activeMonitors[e.id].pamDiff)
|
||||||
s.group[e.ke].activeMonitors[e.id].spawn.stdio[3].on('data',function(buf){
|
s.group[e.ke].activeMonitors[e.id].spawn.stdio[3].on('data',function(buf){
|
||||||
|
let theJson
|
||||||
try{
|
try{
|
||||||
buf.toString().split('}{').forEach((object,n)=>{
|
buf.toString().split('}{').forEach((object,n)=>{
|
||||||
var theJson = object
|
theJson = object
|
||||||
if(object.substr(object.length - 1) !== '}')theJson += '}'
|
if(object.substr(object.length - 1) !== '}')theJson += '}'
|
||||||
if(object.substr(0,1) !== '{')theJson = '{' + theJson
|
if(object.substr(0,1) !== '{')theJson = '{' + theJson
|
||||||
var data = JSON.parse(theJson)
|
try{
|
||||||
triggerEvent(data)
|
var data = JSON.parse(theJson)
|
||||||
|
}catch(err){
|
||||||
|
var data = JSON.parse(theJson + '}')
|
||||||
|
}
|
||||||
|
switch(data.f){
|
||||||
|
case'trigger':
|
||||||
|
triggerEvent(data)
|
||||||
|
break;
|
||||||
|
case's.tx':
|
||||||
|
s.tx(data.data,data.to)
|
||||||
|
break;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}catch(err){
|
}catch(err){
|
||||||
|
console.log(theJson)
|
||||||
console.log('There was an error parsing a detector event')
|
console.log('There was an error parsing a detector event')
|
||||||
console.log(err)
|
console.log(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,4 +13,5 @@ module.exports = function(s,config,lang){
|
||||||
require('./notifications/email.js')(s,config,lang,getSnapshot)
|
require('./notifications/email.js')(s,config,lang,getSnapshot)
|
||||||
require('./notifications/discordBot.js')(s,config,lang,getSnapshot)
|
require('./notifications/discordBot.js')(s,config,lang,getSnapshot)
|
||||||
require('./notifications/telegram.js')(s,config,lang,getSnapshot)
|
require('./notifications/telegram.js')(s,config,lang,getSnapshot)
|
||||||
|
require('./notifications/pushover.js')(s,config,lang,getSnapshot)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,349 @@
|
||||||
|
var fs = require('fs');
|
||||||
|
module.exports = function (s, config, lang, getSnapshot) {
|
||||||
|
const { getEventBasedRecordingUponCompletion } =
|
||||||
|
require('../events/utils.js')(s, config, lang);
|
||||||
|
|
||||||
|
if (config.pushover === true) {
|
||||||
|
const Pushover = require('pushover-notifications');
|
||||||
|
try {
|
||||||
|
const sendMessage = async function (sendBody, files, groupKey) {
|
||||||
|
var pushover = s.group[groupKey].pushover;
|
||||||
|
if (!pushover) {
|
||||||
|
s.userLog(
|
||||||
|
{ ke: groupKey, mid: '$USER' },
|
||||||
|
{
|
||||||
|
type: lang.NotifyErrorText,
|
||||||
|
msg: lang.DiscordNotEnabledText,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pushover && pushover.send) {
|
||||||
|
try {
|
||||||
|
var msg = {
|
||||||
|
// These values correspond to the parameters detailed on https://pushover.net/api
|
||||||
|
// 'message' is required. All other values are optional.
|
||||||
|
message: sendBody.description, // required
|
||||||
|
title: 'Shinobi: ' + sendBody.title,
|
||||||
|
sound: 'siren',
|
||||||
|
// we do not support devices here. use group identifiers instead.
|
||||||
|
// device: 'devicename',
|
||||||
|
priority: 1,
|
||||||
|
};
|
||||||
|
if (files.length > 0) {
|
||||||
|
// sadly pushover allows only ONE single attachment
|
||||||
|
msg.file = {
|
||||||
|
name: files[0].name,
|
||||||
|
data: files[0].attachment,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pushover.send(msg, function (err, result) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
console.log(result);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
s.userLog(
|
||||||
|
{ ke: groupKey, mid: '$USER' },
|
||||||
|
{ type: lang.NotifyErrorText, msg: err }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.userLog(
|
||||||
|
{
|
||||||
|
ke: groupKey,
|
||||||
|
mid: '$USER',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: lang.NotifyErrorText,
|
||||||
|
msg: lang['Check the Recipient ID'],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadPushoverForUser = function (user) {
|
||||||
|
const userDetails = s.parseJSON(user.details);
|
||||||
|
if (
|
||||||
|
!s.group[user.ke].pushover &&
|
||||||
|
config.pushover === true &&
|
||||||
|
userDetails.pushover === '1' &&
|
||||||
|
userDetails.pushover_token !== '' &&
|
||||||
|
userDetails.pushover_recipient_identifier !== ''
|
||||||
|
) {
|
||||||
|
s.group[user.ke].pushover = new Pushover({
|
||||||
|
user: userDetails.pushover_recipient_identifier,
|
||||||
|
token: userDetails.pushover_token,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const unloadPushoverForUser = function (user) {
|
||||||
|
if (
|
||||||
|
s.group[user.ke].pushover &&
|
||||||
|
s.group[user.ke].pushover.destroy
|
||||||
|
) {
|
||||||
|
s.group[user.ke].pushover.destroy();
|
||||||
|
}
|
||||||
|
delete s.group[user.ke].pushover;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTwoFactorAuthCodeNotificationForPushover = function (r) {
|
||||||
|
// r = user
|
||||||
|
if (r.details.factor_pushover === '1') {
|
||||||
|
sendMessage(
|
||||||
|
{
|
||||||
|
title: r.lang['Enter this code to proceed'],
|
||||||
|
description:
|
||||||
|
'**' +
|
||||||
|
s.factorAuth[r.ke][r.uid].key +
|
||||||
|
'** ' +
|
||||||
|
r.lang.FactorAuthText1,
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
r.ke
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEventTriggerForPushover = async (d, filter) => {
|
||||||
|
const monitorConfig =
|
||||||
|
s.group[d.ke].rawMonitorConfigurations[d.id];
|
||||||
|
// d = event object
|
||||||
|
if (
|
||||||
|
filter.pushover &&
|
||||||
|
s.group[d.ke].pushover &&
|
||||||
|
monitorConfig.details.notify_pushover === '1' &&
|
||||||
|
!s.group[d.ke].activeMonitors[d.id].detector_pushover
|
||||||
|
) {
|
||||||
|
var detector_pushover_timeout;
|
||||||
|
if (
|
||||||
|
!monitorConfig.details.detector_pushover_timeout ||
|
||||||
|
monitorConfig.details.detector_pushover_timeout === ''
|
||||||
|
) {
|
||||||
|
detector_pushover_timeout = 1000 * 60 * 10;
|
||||||
|
} else {
|
||||||
|
detector_pushover_timeout =
|
||||||
|
parseFloat(
|
||||||
|
monitorConfig.details.detector_pushover_timeout
|
||||||
|
) *
|
||||||
|
1000 *
|
||||||
|
60;
|
||||||
|
}
|
||||||
|
s.group[d.ke].activeMonitors[d.id].detector_pushover =
|
||||||
|
setTimeout(function () {
|
||||||
|
clearTimeout(
|
||||||
|
s.group[d.ke].activeMonitors[d.id]
|
||||||
|
.detector_pushover
|
||||||
|
);
|
||||||
|
s.group[d.ke].activeMonitors[
|
||||||
|
d.id
|
||||||
|
].detector_pushover = null;
|
||||||
|
}, detector_pushover_timeout);
|
||||||
|
await getSnapshot(d, monitorConfig);
|
||||||
|
if (d.screenshotBuffer) {
|
||||||
|
sendMessage(
|
||||||
|
{
|
||||||
|
title: lang.Event + ' - ' + d.screenshotName,
|
||||||
|
description:
|
||||||
|
lang.EventText1 + ' ' + d.currentTimestamp,
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: 'photo',
|
||||||
|
attachment: d.screenshotBuffer,
|
||||||
|
name: d.screenshotName + '.jpg',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
d.ke
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEventTriggerBeforeFilterForPushover = function (d, filter) {
|
||||||
|
filter.pushover = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDetectorNoTriggerTimeoutForPushover = function (e) {
|
||||||
|
//e = monitor object
|
||||||
|
var currentTime = new Date();
|
||||||
|
if (e.details.detector_notrigger_pushover === '1') {
|
||||||
|
var html =
|
||||||
|
'*' +
|
||||||
|
lang.NoMotionEmailText2 +
|
||||||
|
' ' +
|
||||||
|
(e.details.detector_notrigger_timeout || 10) +
|
||||||
|
' ' +
|
||||||
|
lang.minutes +
|
||||||
|
'.*\n';
|
||||||
|
html +=
|
||||||
|
'**' + lang['Monitor Name'] + '** : ' + e.name + '\n';
|
||||||
|
html += '**' + lang['Monitor ID'] + '** : ' + e.id + '\n';
|
||||||
|
html += currentTime;
|
||||||
|
sendMessage(
|
||||||
|
{
|
||||||
|
title: lang['"No Motion" Detector'],
|
||||||
|
description: html,
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
e.ke
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMonitorUnexpectedExitForPushover = (monitorConfig) => {
|
||||||
|
if (
|
||||||
|
monitorConfig.details.notify_pushover === '1' &&
|
||||||
|
monitorConfig.details.notify_onUnexpectedExit === '1'
|
||||||
|
) {
|
||||||
|
const ffmpegCommand =
|
||||||
|
s.group[monitorConfig.ke].activeMonitors[
|
||||||
|
monitorConfig.mid
|
||||||
|
].ffmpeg;
|
||||||
|
const description =
|
||||||
|
lang['Process Crashed for Monitor'] +
|
||||||
|
'\n' +
|
||||||
|
ffmpegCommand;
|
||||||
|
const currentTime = new Date();
|
||||||
|
sendMessage(
|
||||||
|
{
|
||||||
|
title:
|
||||||
|
lang['Process Unexpected Exit'] +
|
||||||
|
' : ' +
|
||||||
|
monitorConfig.name,
|
||||||
|
description: description,
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
monitorConfig.ke
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
s.loadGroupAppExtender(loadPushoverForUser);
|
||||||
|
s.unloadGroupAppExtender(unloadPushoverForUser);
|
||||||
|
s.onTwoFactorAuthCodeNotification(
|
||||||
|
onTwoFactorAuthCodeNotificationForPushover
|
||||||
|
);
|
||||||
|
s.onEventTrigger(onEventTriggerForPushover);
|
||||||
|
s.onEventTriggerBeforeFilter(onEventTriggerBeforeFilterForPushover);
|
||||||
|
s.onDetectorNoTriggerTimeout(onDetectorNoTriggerTimeoutForPushover);
|
||||||
|
s.onMonitorUnexpectedExit(onMonitorUnexpectedExitForPushover);
|
||||||
|
s.definitions['Monitor Settings'].blocks[
|
||||||
|
'Notifications'
|
||||||
|
].info[0].info.push({
|
||||||
|
name: 'detail=notify_pushover',
|
||||||
|
field: 'Pushover',
|
||||||
|
description: '',
|
||||||
|
default: '0',
|
||||||
|
example: '',
|
||||||
|
selector: 'h_det_pushover',
|
||||||
|
fieldType: 'select',
|
||||||
|
possible: [
|
||||||
|
{
|
||||||
|
name: lang.No,
|
||||||
|
value: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: lang.Yes,
|
||||||
|
value: '1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
s.definitions['Monitor Settings'].blocks['Notifications'].info.push(
|
||||||
|
{
|
||||||
|
evaluation: "$user.details.use_pushover !== '0'",
|
||||||
|
isFormGroupGroup: true,
|
||||||
|
name: 'Pushover',
|
||||||
|
color: 'blue',
|
||||||
|
'section-class': 'h_det_pushover_input h_det_pushover_1',
|
||||||
|
info: [
|
||||||
|
{
|
||||||
|
name: 'detail=detector_pushover_timeout',
|
||||||
|
field:
|
||||||
|
lang['Allow Next Alert'] +
|
||||||
|
` (${lang['on Event']})`,
|
||||||
|
description: '',
|
||||||
|
default: '10',
|
||||||
|
example: '',
|
||||||
|
possible: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
s.definitions['Account Settings'].blocks[
|
||||||
|
'2-Factor Authentication'
|
||||||
|
].info.push({
|
||||||
|
name: 'detail=factor_pushover',
|
||||||
|
field: 'Pushover',
|
||||||
|
default: '1',
|
||||||
|
example: '',
|
||||||
|
fieldType: 'select',
|
||||||
|
possible: [
|
||||||
|
{
|
||||||
|
name: lang.No,
|
||||||
|
value: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: lang.Yes,
|
||||||
|
value: '1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
s.definitions['Account Settings'].blocks['Pushover'] = {
|
||||||
|
evaluation: "$user.details.use_pushover !== '0'",
|
||||||
|
name: 'Pushover',
|
||||||
|
color: 'blue',
|
||||||
|
info: [
|
||||||
|
{
|
||||||
|
name: 'detail=pushover',
|
||||||
|
selector: 'u_pushover',
|
||||||
|
field: lang.Enabled,
|
||||||
|
default: '0',
|
||||||
|
example: '',
|
||||||
|
fieldType: 'select',
|
||||||
|
possible: [
|
||||||
|
{
|
||||||
|
name: lang.No,
|
||||||
|
value: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: lang.Yes,
|
||||||
|
value: '1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
hidden: true,
|
||||||
|
name: 'detail=pushover_token',
|
||||||
|
fieldType: 'password',
|
||||||
|
placeholder: 'azGDORePK8gMaC0QOYAMyEEuzJnyUi',
|
||||||
|
field: lang.Token,
|
||||||
|
'form-group-class': 'u_pushover_input u_pushover_1',
|
||||||
|
description: '',
|
||||||
|
default: '',
|
||||||
|
example: '',
|
||||||
|
possible: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
hidden: true,
|
||||||
|
name: 'detail=pushover_recipient_identifier',
|
||||||
|
placeholder: 'uQiRzpo4DXghDmr9QzzfQu27cmVRsG',
|
||||||
|
field: lang['Recipient ID'],
|
||||||
|
'form-group-class': 'u_pushover_input u_pushover_1',
|
||||||
|
description: '',
|
||||||
|
default: '',
|
||||||
|
example: '',
|
||||||
|
possible: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
console.log(
|
||||||
|
'Could not start pushover notifications, please run "npm install pushover-notifications" inside the Shinobi folder.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
12
libs/user.js
12
libs/user.js
|
@ -105,7 +105,17 @@ module.exports = function(s,config,lang){
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
s.tx({f:'log',ke:e.ke,mid:e.mid,log:x,time:s.timeObject()},'GRPLOG_'+e.ke);
|
const logEvent = {
|
||||||
|
f: 'log',
|
||||||
|
ke: e.ke,
|
||||||
|
mid: e.mid,
|
||||||
|
log: x,
|
||||||
|
time: s.timeObject()
|
||||||
|
}
|
||||||
|
s.tx(logEvent,'GRPLOG_'+e.ke);
|
||||||
|
s.onUserLogExtensions.forEach(function(extender){
|
||||||
|
extender(logEvent)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
s.loadGroup = function(e){
|
s.loadGroup = function(e){
|
||||||
s.loadGroupExtensions.forEach(function(extender){
|
s.loadGroupExtensions.forEach(function(extender){
|
||||||
|
|
|
@ -1354,7 +1354,6 @@ module.exports = function(s,config,lang,app,io){
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
d.doObjectDetection = (!d.details.matrices || d.details.matrices.length === 0) && (s.isAtleatOneDetectorPluginConnected && details.detector_use_detect_object === '1')
|
|
||||||
triggerEvent(d)
|
triggerEvent(d)
|
||||||
s.closeJsonResponse(res,{
|
s.closeJsonResponse(res,{
|
||||||
ok: true,
|
ok: true,
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
"node-ssh": "^11.1.1",
|
"node-ssh": "^11.1.1",
|
||||||
"node-telegram-bot-api": "^0.52.0",
|
"node-telegram-bot-api": "^0.52.0",
|
||||||
"nodemailer": "^6.4.11",
|
"nodemailer": "^6.4.11",
|
||||||
|
"node-pushover": "^1.0.0",
|
||||||
"pam-diff": "^1.0.0",
|
"pam-diff": "^1.0.0",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"pipe2pam": "^0.6.2",
|
"pipe2pam": "^0.6.2",
|
||||||
|
|
|
@ -18,154 +18,28 @@ wget -O $DIR/package.json https://cdn.shinobi.video/binaries/tensorflow/2.3.0/pa
|
||||||
echo "Removing existing Tensorflow Node.js modules..."
|
echo "Removing existing Tensorflow Node.js modules..."
|
||||||
rm -rf $DIR/node_modules
|
rm -rf $DIR/node_modules
|
||||||
npm install yarn -g --unsafe-perm --force
|
npm install yarn -g --unsafe-perm --force
|
||||||
|
npm install dotenv
|
||||||
installJetsonFlag=false
|
|
||||||
installArmFlag=false
|
|
||||||
installGpuFlag=false
|
|
||||||
dontCreateKeyFlag=false
|
|
||||||
|
|
||||||
while [ ! $# -eq 0 ]
|
|
||||||
do
|
|
||||||
case "$1" in
|
|
||||||
--jetson)
|
|
||||||
installJetsonFlag=true
|
|
||||||
exit
|
|
||||||
;;
|
|
||||||
--arm)
|
|
||||||
installArmFlag=true
|
|
||||||
exit
|
|
||||||
;;
|
|
||||||
--gpu)
|
|
||||||
installGpuFlag=true
|
|
||||||
exit
|
|
||||||
;;
|
|
||||||
--dont-create-key)
|
|
||||||
dontCreateKeyFlag=true
|
|
||||||
exit
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
shift
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$installJetsonFlag" = true ] && [ "$installArmFlag" = true ]; then
|
|
||||||
echo "--jetson and --arm cannot both be set. Exiting..."
|
|
||||||
exit -1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ([ "$installJetsonFlag" = true ] || [ "$installArmFlag" = true ]) && [ "$installGpuFlag" = true ]; then
|
|
||||||
echo "--gpu flag cannot be set with --jetson or --arm. Exiting..."
|
|
||||||
exit -2
|
|
||||||
fi
|
|
||||||
|
|
||||||
nonInteractiveFlag=false
|
|
||||||
if [ "$installJetsonFlag" = true ] || [ "$installArmFlag" = true ] || [ "$installGpuFlag" = true ]; then
|
|
||||||
nonInteractiveFlag=true
|
|
||||||
fi
|
|
||||||
manualInstallRequirements() {
|
|
||||||
npm install --unsafe-perm
|
|
||||||
npm install @tensorflow/tfjs-backend-cpu@2.3.0 @tensorflow/tfjs-backend-webgl@2.3.0 @tensorflow/tfjs-converter@2.3.0 @tensorflow/tfjs-core@2.3.0 @tensorflow/tfjs-layers@2.3.0 @tensorflow/tfjs-node@2.3.0 --unsafe-perm --force
|
|
||||||
}
|
|
||||||
runRebuildCpu() {
|
|
||||||
npm rebuild @tensorflow/tfjs-node --build-addon-from-source --unsafe-perm
|
|
||||||
}
|
|
||||||
|
|
||||||
runRebuildGpu() {
|
|
||||||
npm rebuild @tensorflow/tfjs-node-gpu --build-addon-from-source --unsafe-perm
|
|
||||||
}
|
|
||||||
|
|
||||||
installJetson() {
|
|
||||||
installGpuFlag=true
|
|
||||||
npm install @tensorflow/tfjs-node-gpu@2.3.0 --unsafe-perm
|
|
||||||
customBinaryLocation="node_modules/@tensorflow/tfjs-node-gpu/scripts/custom-binary.json"
|
|
||||||
case cudaCompute in
|
|
||||||
"33" ) # Nano and TX1
|
|
||||||
echo '{"tf-lib": "https://cdn.shinobi.video/binaries/tensorflow/2.3.0/libtensorflow.tar.gz"}' > "$customBinaryLocation"
|
|
||||||
;;
|
|
||||||
"25" ) # Xavier NX and AGX Xavier
|
|
||||||
echo '{"tf-lib": "https://cdn.shinobi.video/binaries/tensorflow/2.3.0-xavier/libtensorflow.tar.gz"}' > "$customBinaryLocation"
|
|
||||||
;;
|
|
||||||
* ) # default
|
|
||||||
echo '{"tf-lib": "https://cdn.shinobi.video/binaries/tensorflow/2.3.0/libtensorflow.tar.gz"}' > "$customBinaryLocation"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
manualInstallRequirements
|
|
||||||
chmod -R 777 .
|
|
||||||
runRebuildGpu
|
|
||||||
}
|
|
||||||
|
|
||||||
installGpuRoute() {
|
|
||||||
installGpuFlag=true
|
|
||||||
manualInstallRequirements
|
|
||||||
npm install @tensorflow/tfjs-node-gpu@2.3.0 --unsafe-perm --force
|
|
||||||
}
|
|
||||||
|
|
||||||
installNonGpuRoute() {
|
|
||||||
manualInstallRequirements
|
|
||||||
npm install @tensorflow/tfjs-node@2.3.0 --unsafe-perm --force
|
|
||||||
runRebuildCpu
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if [ "$nonInteractiveFlag" = false ]; then
|
npm install @tensorflow/tfjs-backend-cpu@2.3.0 @tensorflow/tfjs-backend-webgl@2.3.0 @tensorflow/tfjs-converter@2.3.0 @tensorflow/tfjs-core@2.3.0 @tensorflow/tfjs-layers@2.3.0 @tensorflow/tfjs-node@2.3.0 --unsafe-perm --force --legacy-peer-deps
|
||||||
echo "Shinobi - Are you installing on Jetson Nano or Xavier?"
|
npm install @tensorflow/tfjs-node-gpu@2.3.0 --unsafe-perm
|
||||||
echo "You must be on JetPack 4.4 for this plugin to install!"
|
customBinaryLocation="node_modules/@tensorflow/tfjs-node-gpu/scripts/custom-binary.json"
|
||||||
echo "(y)es or (N)o"
|
echo '{"tf-lib": "https://cdn.shinobi.video/binaries/tensorflow/2.3.0/libtensorflow.tar.gz"}' > "$customBinaryLocation"
|
||||||
read armCpu
|
npm rebuild @tensorflow/tfjs-node-gpu --build-addon-from-source --unsafe-perm
|
||||||
if [ "$armCpu" = "y" ] || [ "$armCpu" = "Y" ]; then
|
|
||||||
# echo "Shinobi - Is it a Jetson Nano?"
|
|
||||||
# echo "You must be on JetPack 4.4 for this plugin to install!"
|
|
||||||
# echo "(y)es or (N)o"
|
|
||||||
# read isItJetsonNano
|
|
||||||
# echo "Shinobi - You may see Unsupported Errors, please wait while patches are applied."
|
|
||||||
# if [ "$isItJetsonNano" = "y" ] || [ "$isItJetsonNano" = "Y" ]; then
|
|
||||||
installJetson
|
|
||||||
# else
|
|
||||||
# installArm
|
|
||||||
# fi
|
|
||||||
else
|
|
||||||
echo "Shinobi - Do you want to install TensorFlow.js with GPU support? "
|
|
||||||
echo "You can run this installer again to change it."
|
|
||||||
echo "(y)es or (N)o"
|
|
||||||
read nodejsinstall
|
|
||||||
if [ "$nodejsinstall" = "y" ] || [ "$nodejsinstall" = "Y" ]; then
|
|
||||||
installGpuRoute
|
|
||||||
else
|
|
||||||
installNonGpuRoute
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if [ "$installJetsonFlag" = true ]; then
|
|
||||||
installJetson
|
|
||||||
fi
|
|
||||||
#
|
|
||||||
# if [ "$installArmFlag" = true ]; then
|
|
||||||
# installArm
|
|
||||||
# fi
|
|
||||||
|
|
||||||
if [ "$installGpuFlag" = true ]; then
|
|
||||||
installGpuRoute
|
|
||||||
else
|
|
||||||
installNonGpuRoute
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
# # npm audit fix --force
|
# # npm audit fix --force
|
||||||
if [ ! -e "$DIR/conf.json" ]; then
|
if [ ! -e "$DIR/conf.json" ]; then
|
||||||
dontCreateKeyFlag=false
|
|
||||||
echo "Creating conf.json"
|
echo "Creating conf.json"
|
||||||
sudo cp $DIR/conf.sample.json $DIR/conf.json
|
sudo cp $DIR/conf.sample.json $DIR/conf.json
|
||||||
else
|
else
|
||||||
echo "conf.json already exists..."
|
echo "conf.json already exists..."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$dontCreateKeyFlag" = false ]; then
|
tfjsBuildVal="gpu"
|
||||||
tfjsBuildVal="cpu"
|
|
||||||
if [ "$installGpuFlag" = true ]; then
|
|
||||||
tfjsBuildVal="gpu"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Adding Random Plugin Key to Main Configuration"
|
echo "Adding Random Plugin Key to Main Configuration"
|
||||||
node $DIR/../../tools/modifyConfigurationForPlugin.js tensorflow key=$(head -c 64 < /dev/urandom | sha256sum | awk '{print substr($1,1,60)}') tfjsBuild=$tfjsBuildVal
|
node $DIR/../../tools/modifyConfigurationForPlugin.js tensorflow key=$(head -c 64 < /dev/urandom | sha256sum | awk '{print substr($1,1,60)}') tfjsBuild=$tfjsBuildVal
|
||||||
fi
|
|
||||||
|
|
||||||
echo "TF_FORCE_GPU_ALLOW_GROWTH=true" > "$DIR/.env"
|
echo "TF_FORCE_GPU_ALLOW_GROWTH=true" > "$DIR/.env"
|
||||||
echo "#CUDA_VISIBLE_DEVICES=0,2" >> "$DIR/.env"
|
echo "#CUDA_VISIBLE_DEVICES=0,2" >> "$DIR/.env"
|
||||||
|
|
|
@ -6,7 +6,14 @@ $(document).ready(function(e){
|
||||||
var onvifScannerWindow = $('#onvif_probe')
|
var onvifScannerWindow = $('#onvif_probe')
|
||||||
var scanForm = onvifScannerWindow.find('form');
|
var scanForm = onvifScannerWindow.find('form');
|
||||||
var outputBlock = onvifScannerWindow.find('.onvif_result');
|
var outputBlock = onvifScannerWindow.find('.onvif_result');
|
||||||
var checkTimeout
|
var checkTimeout;
|
||||||
|
function addCredentialsToUri(uri,username,password){
|
||||||
|
let newUri = `${uri}`
|
||||||
|
const uriParts = newUri.split('://')
|
||||||
|
uriParts[1] = `${username}:${password}@${uriParts[1]}`
|
||||||
|
newUri = uriParts.join('://')
|
||||||
|
return newUri
|
||||||
|
}
|
||||||
var setAsLoading = function(appearance){
|
var setAsLoading = function(appearance){
|
||||||
if(appearance){
|
if(appearance){
|
||||||
onvifScannerWindow.find('._loading').show()
|
onvifScannerWindow.find('._loading').show()
|
||||||
|
@ -49,7 +56,7 @@ $(document).ready(function(e){
|
||||||
path: pathLocation.pathname,
|
path: pathLocation.pathname,
|
||||||
protocol: theLocation.protocol,
|
protocol: theLocation.protocol,
|
||||||
details: {
|
details: {
|
||||||
auto_host: streamUrl,
|
auto_host: addCredentialsToUri(streamUrl,currentUsername,currentPassword),
|
||||||
muser: currentUsername,
|
muser: currentUsername,
|
||||||
mpass: currentPassword,
|
mpass: currentPassword,
|
||||||
is_onvif: '1',
|
is_onvif: '1',
|
||||||
|
|
Loading…
Reference in New Issue