Merge branch 'dev' into dashboard-v3

email-send-options-from-account
Moe 2021-09-22 12:33:49 -07:00
commit 638fa438c5
42 changed files with 6072 additions and 1021 deletions

View File

@ -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.

View File

@ -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 "============="

View File

@ -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"]

View File

@ -66,6 +66,8 @@ RUN apt install -y software-properties-common \
tar \
x264
RUN apt install -y zip
RUN apt install -y \
ffmpeg \
git \

View File

@ -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": {

View File

@ -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",

File diff suppressed because it is too large Load Diff

View File

@ -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)
}
};
}

View File

@ -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,
}
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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]

View File

@ -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){

View File

@ -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()
})

View File

@ -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)
}

View File

@ -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)

View File

@ -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

View File

@ -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')

View File

@ -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,
}

View File

@ -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)
}

View File

@ -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: {

View File

@ -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',

View File

@ -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.'
);
}
}
};

View File

@ -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,

View File

@ -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)

View File

@ -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){

View File

@ -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,
}
}

View File

@ -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,

View File

@ -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)

View File

@ -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",

4
plugins/platerecognizer/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
conf.json
dist
models
node_modules

View File

@ -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

View File

@ -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"
}
],
```

View File

@ -0,0 +1,10 @@
{
"plug": "PlateRecognizer",
"host": "localhost",
"platerecognizerApiKey": "11111111111111111",
"port": 8080,
"hostPort": 58084,
"key": "1234567890",
"mode": "client",
"type": "detector"
}

3737
plugins/platerecognizer/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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
}

View File

@ -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()
}

View File

@ -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

View File

@ -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>

View File

@ -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)

View File

@ -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,

View File

@ -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>