Merge branch 'dev' into dashboard-v3
commit
638fa438c5
|
@ -87,27 +87,17 @@ docker run -d --name='Shinobi' -p '8080:8080/tcp' -v "/dev/shm/Shinobi/streams":
|
|||
|
||||
### Environment Variables
|
||||
|
||||
| Environment Variable | Description | Default |
|
||||
|----------------------|----------------------------------------------------------------------|--------------------|
|
||||
| SUBSCRIPTION_ID | **THIS IS NOT REQUIRED**. If you are a subscriber to any of the Shinobi services you may use that key as the value for this parameter. If you have donated by PayPal you may use your Transaction ID to activate the license as well. | *None* |
|
||||
| DB_USER | Username that the Shinobi process will connect to the database with. | majesticflame |
|
||||
| DB_PASSWORD | Password that the Shinobi process will connect to the database with. | *None* |
|
||||
| DB_HOST | Address that the Shinobi process will connect to the database with. | localhost |
|
||||
| DB_DATABASE | Database that the Shinobi process will interact with. | ccio |
|
||||
| DB_DISABLE_INCLUDED | Disable included database to use your own. Set to `true` to disable.| false |
|
||||
| PLUGIN_KEYS | The object containing connection keys for plugins running in client mode (non-host, default). | {} |
|
||||
| SSL_ENABLED | Enable or Disable SSL. | false |
|
||||
| SSL_COUNTRY | Country Code for SSL. | CA |
|
||||
| SSL_STATE | Province/State Code for SSL. | BC |
|
||||
| SSL_LOCATION | Location of where SSL key is being used. | Vancouver |
|
||||
| SSL_ORGANIZATION | Company Name associated to key. | Shinobi Systems |
|
||||
| SSL_ORGANIZATION_UNIT | Department associated to key. | IT Department |
|
||||
| SSL_COMMON_NAME | Common Name associated to key. | nvr.ninja |
|
||||
> Environment Variables have been disabled. You must now make changes in the conf.json itself when it is mounted on the host.
|
||||
> If conf.json does not exist inside the mounted folder then you may create it and Shinobi will use that on next container start.
|
||||
|
||||
> You must add (to the docker container) `/config/ssl/server.key` and `/config/ssl/server.cert`. The `/config` folder is mapped to `$HOME/Shinobi/config` on the host by default with the quick run methods. Place `key` and `cert` in `$HOME/Shinobi/config/ssl`. If `SSL_ENABLED=true` and these files don't exist they will be generated with `openssl`.
|
||||
|
||||
> 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
|
||||
|
||||
Modifying `conf.json` or Superuser credentials.
|
||||
|
|
|
@ -84,7 +84,7 @@ elif [ ! -e "./conf.json" ]; then
|
|||
cp conf.sample.json conf.json
|
||||
fi
|
||||
sudo sed -i -e 's/change_this_to_something_very_random__just_anything_other_than_this/'"$cronKey"'/g' conf.json
|
||||
node tools/modifyConfiguration.js cpuUsageMarker=CPU subscriptionId=$SUBSCRIPTION_ID thisIsDocker=true pluginKeys="$PLUGIN_KEYS" db="$DATABASE_CONFIG" ssl="$SSL_CONFIG"
|
||||
# node tools/modifyConfiguration.js cpuUsageMarker=CPU subscriptionId=$SUBSCRIPTION_ID thisIsDocker=true pluginKeys="$PLUGIN_KEYS" db="$DATABASE_CONFIG" ssl="$SSL_CONFIG"
|
||||
|
||||
|
||||
echo "============="
|
||||
|
|
|
@ -104,7 +104,7 @@ VOLUME ["/config"]
|
|||
VOLUME ["/customAutoLoad"]
|
||||
VOLUME ["/var/lib/mysql"]
|
||||
|
||||
EXPOSE 8080
|
||||
EXPOSE 8080 443 21 25
|
||||
|
||||
ENTRYPOINT ["sh","/home/Shinobi/Docker/init.sh"]
|
||||
|
||||
|
|
|
@ -66,6 +66,8 @@ RUN apt install -y software-properties-common \
|
|||
tar \
|
||||
x264
|
||||
|
||||
RUN apt install -y zip
|
||||
|
||||
RUN apt install -y \
|
||||
ffmpeg \
|
||||
git \
|
||||
|
|
|
@ -393,8 +393,6 @@ module.exports = function(s,config,lang){
|
|||
"default": "10",
|
||||
"example": "",
|
||||
"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",
|
||||
|
@ -2266,7 +2264,7 @@ module.exports = function(s,config,lang){
|
|||
hidden: true,
|
||||
"name": "detail=detector_trigger",
|
||||
"field": lang['Trigger Record'],
|
||||
"description": "This will order the camera to record if it is set to \"Watch-Only\" when a motion even is detected.",
|
||||
"description": "This will order the camera to record if it is set to \"Watch-Only\" when an Event is detected.",
|
||||
"default": "0",
|
||||
"example": "",
|
||||
"form-group-class": "h_det_input h_det_1",
|
||||
|
@ -4017,6 +4015,23 @@ module.exports = function(s,config,lang){
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "detail=notify_useRawSnapshot",
|
||||
"field": lang['Use Raw Snapshot'],
|
||||
"default": "0",
|
||||
"example": "1",
|
||||
"fieldType": "select",
|
||||
"possible": [
|
||||
{
|
||||
"name": lang.No,
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"name": lang.Yes,
|
||||
"value": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
"Logging": {
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
"Session Key": "Session Key",
|
||||
"Active Monitors": "Active Monitors",
|
||||
"Storage Use": "Storage Use",
|
||||
"Use Raw Snapshot": "Use Raw Snapshot",
|
||||
"Login": "Login",
|
||||
"Welcome": "Welcome!",
|
||||
"API Key Action Failed": "API Key Action Failed",
|
||||
|
|
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){
|
||||
var noiseFilterArray = {};
|
||||
const groupKey = jsonData.rawMonitorConfig.ke
|
||||
const monitorId = jsonData.rawMonitorConfig.mid
|
||||
const triggerTimer = {}
|
||||
var pamDiff
|
||||
var p2p
|
||||
var regionJson
|
||||
try{
|
||||
regionJson = JSON.parse(jsonData.rawMonitorConfig.details.cords)
|
||||
}catch(err){
|
||||
regionJson = jsonData.rawMonitorConfig.details.cords
|
||||
}
|
||||
var width,
|
||||
height,
|
||||
globalSensitivity,
|
||||
globalColorThreshold,
|
||||
fullFrame = false
|
||||
if(jsonData.rawMonitorConfig.details.detector_scale_x===''||jsonData.rawMonitorConfig.details.detector_scale_y===''){
|
||||
width = jsonData.rawMonitorConfig.details.detector_scale_x;
|
||||
height = jsonData.rawMonitorConfig.details.detector_scale_y;
|
||||
}
|
||||
else{
|
||||
width = jsonData.rawMonitorConfig.width
|
||||
height = jsonData.rawMonitorConfig.height
|
||||
}
|
||||
if(jsonData.rawMonitorConfig.details.detector_sensitivity===''){
|
||||
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)
|
||||
const {
|
||||
// see libs/detectorUtils.js for more parameters and functions
|
||||
//
|
||||
config,
|
||||
groupKey,
|
||||
monitorId,
|
||||
monitorDetails,
|
||||
completeMonitorConfig,
|
||||
pamDetectorIsEnabled,
|
||||
//
|
||||
attachPamPipeDrivers,
|
||||
//
|
||||
getAcceptedTriggers,
|
||||
getRegionsWithMinimumChange,
|
||||
getRegionsBelowMaximumChange,
|
||||
getRegionsWithThresholdMet,
|
||||
filterTheNoise,
|
||||
filterTheNoiseFromMultipleRegions,
|
||||
//
|
||||
buildDetectorObject,
|
||||
buildTriggerEvent,
|
||||
sendDetectedData,
|
||||
} = require('./libs/detectorUtils.js')(jsonData,pamDiffResponder)
|
||||
return function(cameraProcess){
|
||||
if(pamDetectorIsEnabled){
|
||||
attachPamPipeDrivers(cameraProcess)
|
||||
}
|
||||
}
|
||||
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 () {};
|
||||
|
||||
var jsonData = JSON.parse(fs.readFileSync(process.argv[3],'utf8'))
|
||||
const config = jsonData.globalInfo.config
|
||||
const ffmpegAbsolutePath = process.argv[2].trim()
|
||||
const ffmpegCommandString = jsonData.cmd
|
||||
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')
|
||||
}
|
||||
process.logData = writeToStderr
|
||||
if(!process.argv[2] || !process.argv[3]){
|
||||
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'){
|
||||
try{
|
||||
const attachPamDetector = require(__dirname + '/detector.js')(jsonData,stdioWriters[3])
|
||||
attachPamDetector(cameraProcess,(err)=>{
|
||||
writeToStderr(err)
|
||||
})
|
||||
const attachPamDetector = require(config.monitorDetectorDaemonPath ? config.monitorDetectorDaemonPath : __dirname + '/detector.js')(jsonData,stdioWriters[3])
|
||||
attachPamDetector(cameraProcess)
|
||||
}catch(err){
|
||||
writeToStderr(err.stack)
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
Object.keys(options).forEach((key) => {
|
||||
const value = options[key]
|
||||
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){
|
||||
newOptions[key] = value
|
||||
}
|
||||
|
|
|
@ -371,6 +371,7 @@ module.exports = function(s,config,lang){
|
|||
}
|
||||
const moveCameraPtzToMatrix = function(event,trackingTarget){
|
||||
if(moveLock[event.ke + event.id])return;
|
||||
trackingTarget = trackingTarget || 'person'
|
||||
const imgHeight = event.details.imgHeight
|
||||
const imgWidth = event.details.imgWidth
|
||||
const thresholdX = imgWidth * 0.125
|
||||
|
@ -378,7 +379,7 @@ module.exports = function(s,config,lang){
|
|||
const imageCenterX = imgWidth / 2
|
||||
const imageCenterY = imgHeight / 2
|
||||
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'))
|
||||
if(!largestMatrix)return;
|
||||
const monitorConfig = s.group[event.ke].rawMonitorConfigurations[event.id]
|
||||
|
|
|
@ -179,6 +179,8 @@ module.exports = async (s,config,lang,app,io) => {
|
|||
const moduleName = shinobiModule.name
|
||||
s.customAutoLoadModules[moduleName] = {}
|
||||
var customModulePath = modulesBasePath + '/' + moduleName
|
||||
s.debugLog(customModulePath)
|
||||
s.debugLog(JSON.stringify(shinobiModule,null,3))
|
||||
if(shinobiModule.isIgnitor){
|
||||
s.customAutoLoadModules[moduleName].type = 'file'
|
||||
try{
|
||||
|
@ -219,6 +221,7 @@ module.exports = async (s,config,lang,app,io) => {
|
|||
case'blocks':
|
||||
fs.readdir(thirdLevelName,function(err,webFolderContents){
|
||||
webFolderContents.forEach(function(filename){
|
||||
if(!filename)return;
|
||||
var fullPath = thirdLevelName + '/' + filename
|
||||
var blockPrefix = ''
|
||||
switch(true){
|
||||
|
@ -329,6 +332,7 @@ module.exports = async (s,config,lang,app,io) => {
|
|||
adminLibsCss: [],
|
||||
superPageBlocks: [],
|
||||
superLibsJs: [],
|
||||
superRawJs: [],
|
||||
superLibsCss: []
|
||||
}
|
||||
fs.readdir(modulesBasePath,function(err,folderContents){
|
||||
|
|
|
@ -84,7 +84,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
plug: "dropInEvent",
|
||||
reason: reason
|
||||
},
|
||||
doObjectDetection: (s.isAtleatOneDetectorPluginConnected && s.group[ke].rawMonitorConfigurations[mid].details.detector_use_detect_object === '1')
|
||||
},config.dropInEventForceSaveEvent)
|
||||
}
|
||||
if(search(filename,'.txt')){
|
||||
|
@ -310,7 +309,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
plug: "dropInEvent",
|
||||
reason: reasonTag
|
||||
},
|
||||
doObjectDetection: (s.isAtleatOneDetectorPluginConnected && details.detector_use_detect_object === '1')
|
||||
},config.dropInEventForceSaveEvent)
|
||||
callback()
|
||||
})
|
||||
|
|
|
@ -18,6 +18,9 @@ module.exports = (s,config,lang,app,io) => {
|
|||
const {
|
||||
moveCameraPtzToMatrix
|
||||
} = require('../control/ptz.js')(s,config,lang)
|
||||
const {
|
||||
cutVideoLength
|
||||
} = require('../video/utils.js')(s,config,lang)
|
||||
async function saveImageFromEvent(options,frameBuffer){
|
||||
const monitorId = options.mid || options.id
|
||||
const groupKey = options.ke
|
||||
|
@ -425,13 +428,24 @@ module.exports = (s,config,lang,app,io) => {
|
|||
if(activeMonitor && activeMonitor.eventBasedRecording && activeMonitor.eventBasedRecording.process){
|
||||
const eventBasedRecording = activeMonitor.eventBasedRecording
|
||||
const monitorConfig = s.group[groupKey].rawMonitorConfigurations[monitorId]
|
||||
const videoLength = monitorConfig.details.detector_send_video_length
|
||||
const recordingDirectory = s.getVideoDirectory(monitorConfig)
|
||||
const fileTime = eventBasedRecording.lastFileTime
|
||||
const filename = `${fileTime}.mp4`
|
||||
response.filename = `${filename}`
|
||||
response.filePath = `${recordingDirectory}${filename}`
|
||||
eventBasedRecording.process.on('close',function(){
|
||||
setTimeout(() => {
|
||||
setTimeout(async () => {
|
||||
if(!isNaN(videoLength)){
|
||||
const cutResponse = await cutVideoLength({
|
||||
ke: groupKey,
|
||||
mid: monitorId,
|
||||
filePath: response.filePath,
|
||||
cutLength: parseInt(videoLength),
|
||||
})
|
||||
response.filename = cutResponse.filename
|
||||
response.filePath = cutResponse.filePath
|
||||
}
|
||||
resolve(response)
|
||||
},1000)
|
||||
})
|
||||
|
@ -639,6 +653,12 @@ module.exports = (s,config,lang,app,io) => {
|
|||
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){
|
||||
sendFramesFromSecondaryOutput(d.ke,d.id)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,11 @@ module.exports = function(s,config){
|
|||
s.onSocketAuthenticationExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onUserLogExtensions = []
|
||||
s.onUserLog = function(callback){
|
||||
s.onUserLogExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.loadGroupExtensions = []
|
||||
s.loadGroupExtender = function(callback){
|
||||
s.loadGroupExtensions.push(callback)
|
||||
|
|
|
@ -63,11 +63,18 @@ module.exports = async (s,config,lang,onFinish) => {
|
|||
}
|
||||
},null,3),'utf8')
|
||||
var cameraCommandParams = [
|
||||
__dirname + '/cameraThread/singleCamera.js',
|
||||
config.monitorDaemonPath ? config.monitorDaemonPath : __dirname + '/cameraThread/singleCamera.js',
|
||||
config.ffmpegDir,
|
||||
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){
|
||||
s.systemLog(err)
|
||||
return null
|
||||
|
|
|
@ -24,6 +24,7 @@ module.exports = function(s,config,lang){
|
|||
splitForFFPMEG,
|
||||
} = require('./ffmpeg/utils.js')(s,config,lang)
|
||||
const {
|
||||
processKill,
|
||||
cameraDestroy,
|
||||
monitorConfigurationMigrator,
|
||||
} = require('./monitor/utils.js')(s,config,lang)
|
||||
|
@ -202,14 +203,14 @@ module.exports = function(s,config,lang){
|
|||
})
|
||||
snapProcess.on('error', (data) => {
|
||||
console.log(data)
|
||||
snapProcess.terminate()
|
||||
processKill(snapProcess)
|
||||
})
|
||||
snapProcess.on('exit', (code) => {
|
||||
clearTimeout(snapProcessTimeout)
|
||||
sendTempImage()
|
||||
})
|
||||
var snapProcessTimeout = setTimeout(function(){
|
||||
snapProcess.terminate()
|
||||
processKill(snapProcess)
|
||||
},dynamicTimeout)
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
|
@ -909,7 +910,14 @@ module.exports = function(s,config,lang){
|
|||
if(object.substr(object.length - 1) !== '}')theJson += '}'
|
||||
if(object.substr(0,1) !== '{')theJson = '{' + theJson
|
||||
var data = JSON.parse(theJson)
|
||||
triggerEvent(data)
|
||||
switch(data.f){
|
||||
case'trigger':
|
||||
triggerEvent(data)
|
||||
break;
|
||||
case's.tx':
|
||||
s.tx(data.data,data.to)
|
||||
break;
|
||||
}
|
||||
})
|
||||
}catch(err){
|
||||
console.log('There was an error parsing a detector event')
|
||||
|
|
|
@ -6,6 +6,40 @@ module.exports = (s,config,lang) => {
|
|||
splitForFFPMEG,
|
||||
} = require('../ffmpeg/utils.js')(s,config,lang)
|
||||
const getUpdateableFields = require('./updatedFields.js')
|
||||
const processKill = (proc) => {
|
||||
const response = {ok: true}
|
||||
return new Promise((resolve,reject) => {
|
||||
function sendError(err){
|
||||
response.ok = false
|
||||
response.err = err
|
||||
resolve(response)
|
||||
}
|
||||
try{
|
||||
proc.stdin.write("q\r\n")
|
||||
setTimeout(() => {
|
||||
if(proc && proc.kill){
|
||||
if(s.isWin){
|
||||
spawn("taskkill", ["/pid", proc.pid, '/t'])
|
||||
}else{
|
||||
proc.kill('SIGTERM')
|
||||
}
|
||||
setTimeout(function(){
|
||||
try{
|
||||
proc.kill()
|
||||
resolve(response)
|
||||
}catch(err){
|
||||
s.debugLog(err)
|
||||
sendError(err)
|
||||
}
|
||||
},1000)
|
||||
}
|
||||
},1000)
|
||||
}catch(err){
|
||||
s.debugLog(err)
|
||||
sendError(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
const cameraDestroy = function(e,p){
|
||||
if(
|
||||
s.group[e.ke] &&
|
||||
|
@ -72,27 +106,9 @@ module.exports = (s,config,lang) => {
|
|||
if(activeMonitor.childNode){
|
||||
s.cx({f:'kill',d:s.cleanMonitorObject(e)},activeMonitor.childNodeId)
|
||||
}else{
|
||||
try{
|
||||
proc.stdin.write("q\r\n")
|
||||
setTimeout(() => {
|
||||
if(proc && proc.kill){
|
||||
if(s.isWin){
|
||||
spawn("taskkill", ["/pid", proc.pid, '/t'])
|
||||
}else{
|
||||
proc.kill('SIGTERM')
|
||||
}
|
||||
setTimeout(function(){
|
||||
try{
|
||||
proc.kill()
|
||||
}catch(err){
|
||||
s.debugLog(err)
|
||||
}
|
||||
},1000)
|
||||
}
|
||||
},1000)
|
||||
}catch(err){
|
||||
s.debugLog(err)
|
||||
}
|
||||
processKill(proc).then((response) => {
|
||||
s.debugLog(`cameraDestroy`,response)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -130,21 +146,11 @@ module.exports = (s,config,lang) => {
|
|||
completeRequest()
|
||||
})
|
||||
var snapProcessTimeout = setTimeout(function(){
|
||||
var pid = snapProcess.pid
|
||||
if(s.isWin){
|
||||
spawn("taskkill", ["/pid", pid, '/t'])
|
||||
}else{
|
||||
process.kill(-pid, 'SIGTERM')
|
||||
}
|
||||
setTimeout(function(){
|
||||
if(s.isWin === false){
|
||||
treekill(pid)
|
||||
}else{
|
||||
snapProcess.kill()
|
||||
}
|
||||
processKill(snapProcess).then((response) => {
|
||||
s.debugLog(`createSnapshot-snapProcessTimeout`,response)
|
||||
completeRequest()
|
||||
},10000)
|
||||
},30000)
|
||||
})
|
||||
},5000)
|
||||
})
|
||||
}
|
||||
const addCredentialsToStreamLink = (options) => {
|
||||
|
@ -192,6 +198,7 @@ module.exports = (s,config,lang) => {
|
|||
return {
|
||||
cameraDestroy: cameraDestroy,
|
||||
createSnapshot: createSnapshot,
|
||||
processKill: processKill,
|
||||
addCredentialsToStreamLink: addCredentialsToStreamLink,
|
||||
monitorConfigurationMigrator: monitorConfigurationMigrator,
|
||||
}
|
||||
|
|
|
@ -1,6 +1,17 @@
|
|||
var fs = require("fs")
|
||||
module.exports = function(s,config,lang){
|
||||
require('./notifications/email.js')(s,config,lang)
|
||||
require('./notifications/discordBot.js')(s,config,lang)
|
||||
require('./notifications/telegram.js')(s,config,lang)
|
||||
async function getSnapshot(d,monitorConfig){
|
||||
d.screenshotBuffer = d.screenshotBuffer || d.frame
|
||||
if(!d.screenshotBuffer || (monitorConfig.details.notify_useRawSnapshot === '1' && !d.usingRawSnapshotBuffer)){
|
||||
d.usingRawSnapshotBuffer = true
|
||||
const { screenShot, isStaticFile } = await s.getRawSnapshotFromMonitor(monitorConfig,{
|
||||
secondsInward: monitorConfig.details.snap_seconds_inward
|
||||
})
|
||||
d.screenshotBuffer = screenShot
|
||||
}
|
||||
}
|
||||
require('./notifications/email.js')(s,config,lang,getSnapshot)
|
||||
require('./notifications/discordBot.js')(s,config,lang,getSnapshot)
|
||||
require('./notifications/telegram.js')(s,config,lang,getSnapshot)
|
||||
require('./notifications/pushover.js')(s,config,lang,getSnapshot)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
var fs = require("fs")
|
||||
var Discord = require("discord.js")
|
||||
module.exports = function(s,config,lang){
|
||||
module.exports = function(s,config,lang,getSnapshot){
|
||||
const {
|
||||
getEventBasedRecordingUponCompletion,
|
||||
} = require('../events/utils.js')(s,config,lang)
|
||||
|
@ -81,7 +81,6 @@ module.exports = function(s,config,lang){
|
|||
videoPath = siftedVideoFileFromRam.filePath
|
||||
videoName = siftedVideoFileFromRam.filename
|
||||
}
|
||||
console.log(videoPath,videoName)
|
||||
if(videoPath){
|
||||
sendMessage({
|
||||
author: {
|
||||
|
@ -103,13 +102,7 @@ module.exports = function(s,config,lang){
|
|||
],d.ke)
|
||||
}
|
||||
}
|
||||
d.screenshotBuffer = d.screenshotBuffer || d.frame
|
||||
if(!d.screenshotBuffer){
|
||||
const { screenShot, isStaticFile } = await s.getRawSnapshotFromMonitor(monitorConfig,{
|
||||
secondsInward: monitorConfig.details.snap_seconds_inward
|
||||
})
|
||||
d.screenshotBuffer = screenShot
|
||||
}
|
||||
await getSnapshot(d,monitorConfig)
|
||||
if(d.screenshotBuffer){
|
||||
sendMessage({
|
||||
author: {
|
||||
|
|
|
@ -3,7 +3,7 @@ const {
|
|||
template,
|
||||
checkEmail,
|
||||
} = require("./emailUtils.js")
|
||||
module.exports = function(s,config,lang){
|
||||
module.exports = function(s,config,lang,getSnapshot){
|
||||
const {
|
||||
getEventBasedRecordingUponCompletion,
|
||||
} = require('../events/utils.js')(s,config,lang)
|
||||
|
@ -193,13 +193,7 @@ module.exports = function(s,config,lang){
|
|||
})
|
||||
}
|
||||
}
|
||||
d.screenshotBuffer = d.screenshotBuffer || d.frame
|
||||
if(!d.screenshotBuffer){
|
||||
const {screenShot, isStaticFile} = await s.getRawSnapshotFromMonitor(monitorConfig,{
|
||||
secondsInward: monitorConfig.details.snap_seconds_inward
|
||||
})
|
||||
if(screenShot)d.screenshotBuffer = screenShot
|
||||
}
|
||||
await getSnapshot(d,monitorConfig)
|
||||
sendMail([
|
||||
{
|
||||
filename: d.screenshotName + '.jpg',
|
||||
|
|
|
@ -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.'
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,5 +1,5 @@
|
|||
var fs = require("fs")
|
||||
module.exports = function(s,config,lang){
|
||||
module.exports = function(s,config,lang,getSnapshot){
|
||||
const {
|
||||
getEventBasedRecordingUponCompletion,
|
||||
} = require('../events/utils.js')(s,config,lang)
|
||||
|
@ -87,13 +87,7 @@ module.exports = function(s,config,lang){
|
|||
],d.ke)
|
||||
}
|
||||
}
|
||||
d.screenshotBuffer = d.screenshotBuffer || d.frame
|
||||
if(!d.screenshotBuffer){
|
||||
const { screenShot, isStaticFile } = await s.getRawSnapshotFromMonitor(monitorConfig,{
|
||||
secondsInward: monitorConfig.details.snap_seconds_inward
|
||||
})
|
||||
d.screenshotBuffer = screenShot
|
||||
}
|
||||
await getSnapshot(d,monitorConfig)
|
||||
if(d.screenshotBuffer){
|
||||
sendMessage({
|
||||
title: lang.Event+' - '+d.screenshotName,
|
||||
|
|
|
@ -4,14 +4,14 @@ module.exports = (config) => {
|
|||
var currentlyUpdating = false
|
||||
return {
|
||||
getSystemInfo: (s) => {
|
||||
return {
|
||||
const response = {
|
||||
"Time Started": s.timeStarted,
|
||||
"Time Ready": s.timeReady,
|
||||
Versions: {
|
||||
"Shinobi": s.currentVersion,
|
||||
"Node.js": process.version,
|
||||
"FFmpeg": s.ffmpegVersion,
|
||||
"isActivated": config.userHasSubscribed,
|
||||
"isActivated": config.userHasSubscribed
|
||||
},
|
||||
Machine: {
|
||||
"CPU Core Count": s.coreCount,
|
||||
|
@ -19,6 +19,8 @@ module.exports = (config) => {
|
|||
"Operating System Platform": s.platform,
|
||||
},
|
||||
}
|
||||
if(s.expiryDate)response.Versions["License Expires On"] = s.expiryDate
|
||||
return response
|
||||
},
|
||||
getConfiguration: () => {
|
||||
return new Promise((resolve,reject) => {
|
||||
|
@ -32,11 +34,7 @@ module.exports = (config) => {
|
|||
try{
|
||||
if(config.thisIsDocker){
|
||||
const dockerConfigFile = '/config/conf.json'
|
||||
fs.stat(dockerConfigFile,(err) => {
|
||||
if(!err){
|
||||
fs.writeFile(dockerConfigFile,JSON.stringify(postBody,null,3),function(){})
|
||||
}
|
||||
})
|
||||
fs.writeFileSync(dockerConfigFile,JSON.stringify(postBody,null,3))
|
||||
}
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
|
|
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.loadGroupExtensions.forEach(function(extender){
|
||||
|
|
|
@ -176,8 +176,42 @@ module.exports = (s,config,lang) => {
|
|||
finish()
|
||||
}
|
||||
}
|
||||
function cutVideoLength(options){
|
||||
return new Promise((resolve,reject) => {
|
||||
const response = {ok: false}
|
||||
const inputFilePath = options.filePath
|
||||
const monitorId = options.mid
|
||||
const groupKey = options.ke
|
||||
const cutLength = options.cutLength || 10
|
||||
const tempDirectory = s.getStreamsDirectory(options)
|
||||
let fileExt = inputFilePath.split('.')
|
||||
fileExt = fileExt[fileExt.length -1]
|
||||
const filename = `${s.gid(10)}.${fileExt}`
|
||||
const videoOutPath = `${tempDirectory}`
|
||||
const cuttingProcess = spawn(config.ffmpegDir,['-loglevel','warning','-i', inputFilePath, '-c','copy','-t',`${cutLength}`,videoOutPath])
|
||||
cuttingProcess.stderr.on('data',(data) => {
|
||||
const err = data.toString()
|
||||
s.debugLog('cutVideoLength',options,err)
|
||||
})
|
||||
cuttingProcess.on('close',(data) => {
|
||||
fs.stat(videoOutPath,(err) => {
|
||||
if(!err){
|
||||
response.filename = filename
|
||||
response.filePath = videoOutPath
|
||||
setTimeout(() => {
|
||||
s.file('delete',videoOutPath)
|
||||
},1000 * 60 * 3)
|
||||
}else{
|
||||
s.debugLog('cutVideoLength:readFile',options,err)
|
||||
}
|
||||
resolve(response)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
return {
|
||||
orphanedVideoCheck: orphanedVideoCheck,
|
||||
scanForOrphanedVideos: scanForOrphanedVideos,
|
||||
cutVideoLength: cutVideoLength,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1361,7 +1361,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
}
|
||||
break;
|
||||
}
|
||||
d.doObjectDetection = (!d.details.matrices || d.details.matrices.length === 0) && (s.isAtleatOneDetectorPluginConnected && details.detector_use_detect_object === '1')
|
||||
triggerEvent(d)
|
||||
s.closeJsonResponse(res,{
|
||||
ok: true,
|
||||
|
@ -1489,7 +1488,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
let video = req.files.video;
|
||||
var time = new Date(parseInt(video.name.split('.')[0]))
|
||||
var filename = s.formattedTime(time) + '.' + monitor.ext
|
||||
video.mv(s.getVideoDirectory(monitor) + filename,function(){
|
||||
video.mv(s.getVideoDirectory(monitor) + filename,function(){
|
||||
s.insertCompletedVideo(monitor,{
|
||||
file: filename,
|
||||
events: s.group[groupKey].activeMonitors[monitorId].detector_motion_count,
|
||||
|
|
|
@ -254,11 +254,7 @@ module.exports = function(s,config,lang,app){
|
|||
try{
|
||||
if(config.thisIsDocker){
|
||||
const dockerSuperFile = '/config/super.json'
|
||||
fs.stat(dockerSuperFile,(err) => {
|
||||
if(!err){
|
||||
fs.writeFile(dockerSuperFile,JSON.stringify(currentSuperUserList,null,3),function(){})
|
||||
}
|
||||
})
|
||||
fs.writeFileSync(dockerSuperFile,JSON.stringify(currentSuperUserList,null,3))
|
||||
}
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
"node-ssh": "^11.1.1",
|
||||
"node-telegram-bot-api": "^0.52.0",
|
||||
"nodemailer": "^6.4.11",
|
||||
"node-pushover": "^1.0.0",
|
||||
"pam-diff": "^1.0.0",
|
||||
"path": "^0.12.7",
|
||||
"pipe2pam": "^0.6.2",
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
conf.json
|
||||
dist
|
||||
models
|
||||
node_modules
|
|
@ -0,0 +1,19 @@
|
|||
#!/bin/bash
|
||||
DIR=$(dirname $0)
|
||||
echo "Removing existing Node.js modules..."
|
||||
rm -rf $DIR/node_modules
|
||||
|
||||
nonInteractiveFlag=false
|
||||
|
||||
if [ ! -e "$DIR/conf.json" ]; then
|
||||
dontCreateKeyFlag=false
|
||||
echo "Creating conf.json"
|
||||
sudo cp $DIR/conf.sample.json $DIR/conf.json
|
||||
else
|
||||
echo "conf.json already exists..."
|
||||
fi
|
||||
|
||||
if [ "$dontCreateKeyFlag" = false ]; then
|
||||
echo "Adding Random Plugin Key to Main Configuration"
|
||||
node $DIR/../../tools/modifyConfigurationForPlugin.js platerecognizer key=$(head -c 64 < /dev/urandom | sha256sum | awk '{print substr($1,1,60)}')
|
||||
fi
|
|
@ -0,0 +1,78 @@
|
|||
# PlateRecognizer
|
||||
|
||||
> PlateRecognizer is a cloud-based service. You must sign up at http://platerecognizer.com to use this plugin. **In your purchase notes to http://platerecognizer.com be sure to indicate it will be used with Shinobi.**
|
||||
|
||||
1. Go to the Shinobi directory. **/home/Shinobi** is the default directory.
|
||||
|
||||
```
|
||||
cd /home/Shinobi/plugins/platerecognizer
|
||||
```
|
||||
|
||||
2. Install
|
||||
|
||||
```
|
||||
sh INSTALL.sh
|
||||
```
|
||||
|
||||
3. Start the plugin.
|
||||
|
||||
```
|
||||
pm2 start shinobi-platerecognizer.js
|
||||
```
|
||||
|
||||
4. Save to startup list. **OPTIONAL**
|
||||
|
||||
```
|
||||
pm2 save
|
||||
```
|
||||
|
||||
Doing this will reveal options in the monitor configuration. Shinobi does not need to be restarted when a plugin is initiated or stopped.
|
||||
|
||||
## Run the plugin as a Host
|
||||
> The main app (Shinobi) will be the client and the plugin will be the host. The purpose of allowing this method is so that you can use one plugin for multiple Shinobi instances. Allowing you to easily manage connections without starting multiple processes.
|
||||
|
||||
Edit your plugins configuration file. Set the `hostPort` **to be different** than the `listening port for camera.js`.
|
||||
|
||||
```
|
||||
nano conf.json
|
||||
```
|
||||
|
||||
Here is a sample of a Host configuration for the plugin.
|
||||
- `platerecognizerApiKey` is your API Key given by http://platerecognizer.com.
|
||||
- `plug` is the name of the plugin corresponding in the main configuration file.
|
||||
- `https` choose if you want to use SSL or not. Default is `false`.
|
||||
- `hostPort` can be any available port number. **Don't make this the same port number as Shinobi.** Default is `8082`.
|
||||
- `type` tells the main application (Shinobi) what kind of plugin it is. In this case it is a detector.
|
||||
|
||||
```
|
||||
{
|
||||
"plug":"PlateRecognizer",
|
||||
"platerecognizerApiKey": "11111111111111111",
|
||||
"hostPort":8082,
|
||||
"key":"1234567890",
|
||||
"mode":"host",
|
||||
"type":"detector"
|
||||
}
|
||||
```
|
||||
|
||||
Now modify the **main configuration file** located in the main directory of Shinobi.
|
||||
|
||||
```
|
||||
nano conf.json
|
||||
```
|
||||
|
||||
Add the `plugins` array if you don't already have it. Add the following *object inside the array*.
|
||||
|
||||
```
|
||||
"plugins":[
|
||||
{
|
||||
"id" : "PlateRecognizer",
|
||||
"https" : false,
|
||||
"host" : "localhost",
|
||||
"port" : 8082,
|
||||
"key" : "1234567890",
|
||||
"mode" : "host",
|
||||
"type" : "detector"
|
||||
}
|
||||
],
|
||||
```
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"plug": "PlateRecognizer",
|
||||
"host": "localhost",
|
||||
"platerecognizerApiKey": "11111111111111111",
|
||||
"port": 8080,
|
||||
"hostPort": 58084,
|
||||
"key": "1234567890",
|
||||
"mode": "client",
|
||||
"type": "detector"
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "shinobi-platerecognizer",
|
||||
"author": "Moe Alam",
|
||||
"version": "1.0.0",
|
||||
"description": "Object Detection plugin for DeepStack",
|
||||
"main": "shinobi-platerecognizer.js",
|
||||
"dependencies": {
|
||||
"request": "^2.88.0",
|
||||
"express": "^4.16.2",
|
||||
"moment": "^2.19.2",
|
||||
"socket.io": "^2.3.0",
|
||||
"socket.io-client": "^2.3.0"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"bin": "shinobi-platerecognizer.js",
|
||||
"pkg": {
|
||||
"targets": [
|
||||
"node12"
|
||||
],
|
||||
"scripts": [
|
||||
"../pluginBase.js"
|
||||
],
|
||||
"assets": []
|
||||
},
|
||||
"disabled": true
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
//
|
||||
// Shinobi - Tensorflow Plugin
|
||||
// Copyright (C) 2016-2025 Elad Bar, Moe Alam
|
||||
//
|
||||
// Base Init >>
|
||||
const fs = require('fs');
|
||||
const config = require('./conf.json')
|
||||
const fetch = require('node-fetch');
|
||||
const FormData = require('form-data');
|
||||
var s
|
||||
const {
|
||||
workerData
|
||||
} = require('worker_threads');
|
||||
|
||||
if(workerData && workerData.ok === true){
|
||||
try{
|
||||
s = require('../pluginWorkerBase.js')(__dirname,config)
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
try{
|
||||
s = require('./pluginWorkerBase.js')(__dirname,config)
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
return console.log(config.plug,'WORKER : Plugin start has failed. pluginBase.js was not found.')
|
||||
}
|
||||
}
|
||||
}else{
|
||||
try{
|
||||
s = require('../pluginBase.js')(__dirname,config)
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
try{
|
||||
s = require('./pluginBase.js')(__dirname,config)
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
return console.log(config.plug,'Plugin start has failed. pluginBase.js was not found.')
|
||||
}
|
||||
}
|
||||
try{
|
||||
s = require('../pluginBase.js')(__dirname,config)
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
try{
|
||||
const {
|
||||
haltMessage,
|
||||
checkStartTime,
|
||||
setStartTime,
|
||||
} = require('../pluginCheck.js')
|
||||
|
||||
if(!checkStartTime()){
|
||||
console.log(haltMessage,new Date())
|
||||
s.disconnectWebSocket()
|
||||
return
|
||||
}
|
||||
setStartTime()
|
||||
}catch(err){
|
||||
console.log(`pluginCheck failed`)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// Base Init />>
|
||||
|
||||
const licensePlateRegion = config.licensePlateRegion || 'us'
|
||||
const platerecognizerApiKey = config.platerecognizerApiKey || '111111111111111111'
|
||||
if(!config.platerecognizerApiKey){
|
||||
console.log('No Plate Recognizer API Key set.')
|
||||
console.log('set conf.json value for `platerecognizerApiKey`')
|
||||
return process.exit()
|
||||
}
|
||||
const baseUrl = config.platerecognizerEndpoint || "https://api.platerecognizer.com/v1/plate-reader/"
|
||||
|
||||
function platerecognizerRequest(d,frameBuffer){
|
||||
return new Promise((resolve,reject) => {
|
||||
try{
|
||||
let body = new FormData();
|
||||
frameBufferToPath(d,frameBuffer).then((filePath) => {
|
||||
body.append('upload', fs.createReadStream(filePath));
|
||||
// Or body.append('upload', base64Image);
|
||||
body.append('regions', licensePlateRegion); // Change to your country
|
||||
fetch(baseUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"Authorization": `Token ${platerecognizerApiKey}`
|
||||
},
|
||||
body: body
|
||||
}).then(res => res.json())
|
||||
.then((json) => {
|
||||
let predictions = []
|
||||
try{
|
||||
const response = json || {results: []}
|
||||
predictions = response["results"] || []
|
||||
}catch(err){
|
||||
console.log(json)
|
||||
console.log(err)
|
||||
console.log(body)
|
||||
}
|
||||
resolve(predictions);
|
||||
fs.unlink(filePath,function(){
|
||||
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
})
|
||||
}catch(err){
|
||||
resolve([])
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
function addVehicleMatrix(v,mats){
|
||||
const label = v.vehicle["type"]
|
||||
const confidence = v.vehicle["score"]
|
||||
const y_min = v.vehicle["ymin"]
|
||||
const x_min = v.vehicle["xmin"]
|
||||
const y_max = v.vehicle["ymax"]
|
||||
const x_max = v.vehicle["xmax"]
|
||||
const vehicleWidth = x_max - x_min
|
||||
const vehicleHeight = y_max - y_min
|
||||
mats.push({
|
||||
x: x_min,
|
||||
y: y_min,
|
||||
width: vehicleWidth,
|
||||
height: vehicleHeight,
|
||||
tag: label,
|
||||
confidence: confidence,
|
||||
})
|
||||
}
|
||||
function frameBufferToPath(d,buffer){
|
||||
return new Promise((resolve,reject) => {
|
||||
const tmpFile = s.gid(5)+'.jpg'
|
||||
if(!fs.existsSync(s.dir.streams)){
|
||||
fs.mkdirSync(s.dir.streams);
|
||||
}
|
||||
frameDirectory = s.dir.streams+d.ke+'/'+d.id+'/'
|
||||
fs.writeFile(frameDirectory+tmpFile,buffer,function(err){
|
||||
if(err) return s.systemLog(err);
|
||||
try{
|
||||
resolve(frameDirectory+tmpFile)
|
||||
}catch(error){
|
||||
console.error('Catch: ' + error);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
s.detectObject = async function(frameBuffer,d,tx,frameLocation,callback){
|
||||
const timeStart = new Date()
|
||||
const predictions = await platerecognizerRequest(d,frameBuffer)
|
||||
if(predictions.length > 0) {
|
||||
const mats = []
|
||||
predictions.forEach(function(v){
|
||||
const label = v["plate"]
|
||||
const confidence = v["score"]
|
||||
const y_min = v.box["ymin"]
|
||||
const x_min = v.box["xmin"]
|
||||
const y_max = v.box["ymax"]
|
||||
const x_max = v.box["xmax"]
|
||||
const width = x_max - x_min
|
||||
const height = y_max - y_min
|
||||
mats.push({
|
||||
x: x_min,
|
||||
y: y_min,
|
||||
width: width,
|
||||
height: height,
|
||||
tag: label,
|
||||
confidence: confidence,
|
||||
})
|
||||
addVehicleMatrix(v,mats)
|
||||
})
|
||||
const isObjectDetectionSeparate = d.mon.detector_pam === '1' && d.mon.detector_use_detect_object === '1'
|
||||
const width = parseFloat(isObjectDetectionSeparate && d.mon.detector_scale_y_object ? d.mon.detector_scale_y_object : d.mon.detector_scale_y)
|
||||
const height = parseFloat(isObjectDetectionSeparate && d.mon.detector_scale_x_object ? d.mon.detector_scale_x_object : d.mon.detector_scale_x)
|
||||
|
||||
tx({
|
||||
f:'trigger',
|
||||
id:d.id,
|
||||
ke:d.ke,
|
||||
details:{
|
||||
plug: config.plug,
|
||||
name: `PlateRecognizer`,
|
||||
reason: 'object',
|
||||
matrices: mats,
|
||||
imgHeight: width,
|
||||
imgWidth: height,
|
||||
},
|
||||
frame: frameBuffer
|
||||
})
|
||||
}
|
||||
callback()
|
||||
}
|
|
@ -18,136 +18,16 @@ wget -O $DIR/package.json https://cdn.shinobi.video/binaries/tensorflow/2.3.0/pa
|
|||
echo "Removing existing Tensorflow Node.js modules..."
|
||||
rm -rf $DIR/node_modules
|
||||
npm install yarn -g --unsafe-perm --force
|
||||
|
||||
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
|
||||
}
|
||||
npm install dotenv
|
||||
|
||||
|
||||
if [ "$nonInteractiveFlag" = false ]; then
|
||||
echo "Shinobi - Are you installing on Jetson Nano or Xavier?"
|
||||
echo "You must be on JetPack 4.4 for this plugin to install!"
|
||||
echo "(y)es or (N)o"
|
||||
read armCpu
|
||||
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
|
||||
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
|
||||
npm install @tensorflow/tfjs-node-gpu@2.3.0 --unsafe-perm
|
||||
customBinaryLocation="node_modules/@tensorflow/tfjs-node-gpu/scripts/custom-binary.json"
|
||||
echo '{"tf-lib": "https://cdn.shinobi.video/binaries/tensorflow/2.3.0/libtensorflow.tar.gz"}' > "$customBinaryLocation"
|
||||
npm rebuild @tensorflow/tfjs-node-gpu --build-addon-from-source --unsafe-perm
|
||||
|
||||
|
||||
if [ "$installGpuFlag" = true ]; then
|
||||
installGpuRoute
|
||||
else
|
||||
installNonGpuRoute
|
||||
fi
|
||||
fi
|
||||
# # npm audit fix --force
|
||||
if [ ! -e "$DIR/conf.json" ]; then
|
||||
dontCreateKeyFlag=false
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
<script src="https://cdn.shinobi.video/js/socket.io.js"></script>
|
||||
<script src="https://cdn.shinobi.video/js/jquery.min.js"></script>
|
||||
<!-- my messy GUI -->
|
||||
<style>
|
||||
body {position:relative;}
|
||||
.shinobi_stream{position:absolute;width:100%;height:100%;top:0;left:0;}
|
||||
iframe.stream-element{border:0;}
|
||||
|
||||
|
||||
.stream-objects{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10}
|
||||
.stream-objects .tag{position:absolute;top:5px;left:5px;background:#d9534f;color:#fff;font-family:monospace;font-size:80%;border-radius: 15px;padding:3px 5px;line-height: 1}
|
||||
.stream-objects .stream-detected-object{position:absolute;top:0;left:0;border:3px dotted red;background:transparent;border-radius:5px}
|
||||
.stream-objects .stream-detected-point{position:absolute;top:0;left:0;border:3px solid yellow;background:transparent;border-radius:5px}
|
||||
.stream-objects .point{position:absolute;top:0;left:0;border:3px solid red;border-radius:50%}
|
||||
|
||||
</style>
|
||||
<!-- Full Screen -->
|
||||
<style>
|
||||
body,html{overflow: hidden;height:100%}
|
||||
*{margin:0;padding:0;border:0}
|
||||
.stream-element,.shinobi_stream{position:absolute;top:0;left:0;height:100%}
|
||||
.shinobi_stream video{object-fit: fill}
|
||||
</style>
|
||||
<!-- Full Screen /-->
|
||||
<div class="shinobi_stream" id="stream_container">
|
||||
<div class="stream-objects"></div>
|
||||
<img class="stream-element">
|
||||
</div>
|
||||
<!-- the juice, i mean js -->
|
||||
<script>
|
||||
function getQueryString(){
|
||||
var theObject = {}
|
||||
location.search.substring(1).split('&').forEach(function(string){
|
||||
var parts = string.split('=')
|
||||
theObject[parts[0]] = parts[1]
|
||||
})
|
||||
return theObject
|
||||
}
|
||||
const bodyWidth = $('body').width()
|
||||
const bodyHeight = $('body').height()
|
||||
const queryStringValues = getQueryString()
|
||||
let shinobiOrigin = queryStringValues.shinobiOrigin
|
||||
const monitorId = queryStringValues.monitorId
|
||||
const groupKey = queryStringValues.groupKey
|
||||
const apiKey = queryStringValues.apiKey
|
||||
let loadedWebsocketConnection = null
|
||||
const streamObjectsContainer = $('.stream-objects')
|
||||
if(shinobiOrigin.charAt(shinobiOrigin.length - 1) === '/'){
|
||||
shinobiOrigin = shinobiOrigin.slice(0, -1)
|
||||
}
|
||||
function initMonitorStream(d){
|
||||
$('#stream_container .stream-element').attr('src',`${shinobiOrigin}/${apiKey}/mjpeg/${groupKey}/${monitorId}`)
|
||||
$(window).resize();
|
||||
}
|
||||
function drawMatrices(event){
|
||||
var theContainer = streamObjectsContainer
|
||||
var height = bodyHeight
|
||||
var width = bodyWidth
|
||||
var widthRatio = width / event.details.imgWidth
|
||||
var heightRatio = height / event.details.imgHeight
|
||||
var objectTagGroup = event.details.reason === 'motion' ? 'motion' : event.details.name
|
||||
theContainer.find(`.stream-detected-object[name="${objectTagGroup}"]`).remove()
|
||||
var html = ''
|
||||
$.each(event.details.matrices,function(n,matrix){
|
||||
html += `<div class="stream-detected-object" name="${objectTagGroup}" style="height:${heightRatio * matrix.height}px;width:${widthRatio * matrix.width}px;top:${heightRatio * matrix.y}px;left:${widthRatio * matrix.x}px;">`
|
||||
if(matrix.tag)html += `<span class="tag">${matrix.tag}</span>`
|
||||
html += '</div>'
|
||||
})
|
||||
theContainer.append(html)
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
$.get(`${shinobiOrigin}/${apiKey}/monitor/${groupKey}/${monitorId}`,function(data){
|
||||
if(
|
||||
!loadedWebsocketConnection ||
|
||||
loadedWebsocketConnection.connected === false
|
||||
){
|
||||
loadedWebsocketConnection = io(shinobiOrigin);
|
||||
loadedWebsocketConnection.on('f',function (d){
|
||||
switch(d.f){
|
||||
case'monitor_watch_off':
|
||||
case'monitor_watch_on':
|
||||
initMonitorStream(d)
|
||||
break;
|
||||
case'detector_trigger':
|
||||
if(d.id !== monitorId)return false;
|
||||
if(
|
||||
d.details.matrices &&
|
||||
d.details.matrices.length > 0
|
||||
){
|
||||
drawMatrices(d)
|
||||
}
|
||||
console.log({
|
||||
ke: groupKey,
|
||||
mid: monitorId,
|
||||
log: {
|
||||
type: 'Event Occurred',
|
||||
msg: d.details,
|
||||
}
|
||||
})
|
||||
break;
|
||||
}
|
||||
})
|
||||
}
|
||||
loadedWebsocketConnection.emit('e',{
|
||||
f: 'init',
|
||||
auth: apiKey,
|
||||
id: monitorId,
|
||||
ke: groupKey
|
||||
})
|
||||
})
|
||||
$(window).resize(function(){
|
||||
$('.stream-element').attr('width',bodyWidth)
|
||||
$('.stream-element').attr('height',bodyHeight)
|
||||
})
|
||||
})
|
||||
</script>
|
|
@ -59,11 +59,7 @@ processArgv.forEach(function(val) {
|
|||
try{
|
||||
if(config.thisIsDocker){
|
||||
const dockerConfigFile = '/config/conf.json'
|
||||
fs.stat(dockerConfigFile,(err) => {
|
||||
if(!err){
|
||||
fs.writeFile(dockerConfigFile,JSON.stringify(config,null,3),function(){})
|
||||
}
|
||||
})
|
||||
fs.writeFileSync(dockerConfigFile,JSON.stringify(config,null,3))
|
||||
}
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<form id="hey-activate" class="card shadow mb-3">
|
||||
<div class="card-header">
|
||||
<%- lang['Not Activated'] %>
|
||||
<%- config.hasSubcribed ? lang['Activated'] : lang['Not Activated'] %>
|
||||
</div>
|
||||
<div class="card-body" style="min-height:auto">
|
||||
<div class="form-group">
|
||||
<input name="subscriptionId" id="pass" tabindex="2" class="form-control wide-text" placeholder="License Key / Subscription ID">
|
||||
<input name="subscriptionId" tabindex="2" class="form-control wide-text" placeholder="License Key / Subscription ID">
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<button class="btn btn-sm btn-round btn-block btn-success" type="submit"><%- lang.Save %></button>
|
||||
|
@ -42,10 +42,11 @@
|
|||
noticeTitle = lang.Activated
|
||||
noticeText = lang.activatedText
|
||||
noticeType = 'success'
|
||||
heyActivateCard.remove()
|
||||
heyActivateCard.find('[name="subscriptionId"]').val('')
|
||||
}else{
|
||||
heyActivateCardSubmit.html(lang.Save)
|
||||
}
|
||||
heyActivateCard.find('.card-header').html(noticeTitle)
|
||||
new PNotify({
|
||||
title: noticeTitle,
|
||||
text: noticeText,
|
||||
|
|
|
@ -103,9 +103,7 @@
|
|||
<div class="tab-pane active" id="accounts" role="tabpanel">
|
||||
<div class="row">
|
||||
<div class="col-md-5 text-left">
|
||||
<% if(!config.userHasSubscribed){ %>
|
||||
<% include blocks/heyActivate.ejs %>
|
||||
<% } %>
|
||||
<% include blocks/heyActivate.ejs %>
|
||||
<div class="card shadow mb-3">
|
||||
<pre class="super-system-info card-body mb-0">
|
||||
|
||||
|
@ -349,4 +347,7 @@ $('body')
|
|||
<% customAutoLoad.superLibsJs.forEach(function(lib){ %>
|
||||
<script src="<%-window.libURL%>libs/js/<%-lib%>"></script>
|
||||
<% }) %>
|
||||
<% customAutoLoad.superRawJs.forEach(function(scriptData){ %>
|
||||
<script><%- scriptData %></script>
|
||||
<% }) %>
|
||||
</html>
|
||||
|
|
Loading…
Reference in New Issue