Alhambra
parent
03898a3382
commit
9be4e6b95a
|
@ -1,8 +1,27 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Get the Ubuntu version
|
||||||
|
UBUNTU_VERSION=$(lsb_release -rs)
|
||||||
|
NODE_MAJOR=18
|
||||||
|
|
||||||
|
# Check if Ubuntu version is 18.04
|
||||||
|
if [ "$UBUNTU_VERSION" = "18.04" ]; then
|
||||||
|
NODE_MAJOR=16
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Installing Node version: $NODE_MAJOR"
|
||||||
|
|
||||||
|
# Update and install necessary packages
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y ca-certificates curl gnupg
|
sudo apt-get install -y ca-certificates curl gnupg
|
||||||
|
|
||||||
|
# Setup NodeSource keyring and sources list
|
||||||
sudo mkdir -p /etc/apt/keyrings
|
sudo mkdir -p /etc/apt/keyrings
|
||||||
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
||||||
NODE_MAJOR=18
|
|
||||||
|
# Add NodeSource repository
|
||||||
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
|
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
|
||||||
|
|
||||||
|
# Update package list and install Node.js
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install nodejs -y
|
sudo apt-get install -y nodejs
|
||||||
|
|
|
@ -56,7 +56,7 @@ if ! [ -x "$(command -v ifconfig)" ]; then
|
||||||
fi
|
fi
|
||||||
echo "============="
|
echo "============="
|
||||||
echo "Shinobi - Installing Node.js"
|
echo "Shinobi - Installing Node.js"
|
||||||
sh $DIR/nodejs-ubuntu.sh
|
bash $DIR/nodejs-ubuntu.sh
|
||||||
if ! [ -x "$(command -v npm)" ]; then
|
if ! [ -x "$(command -v npm)" ]; then
|
||||||
sudo apt install npm -y
|
sudo apt install npm -y
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -3151,12 +3151,30 @@ module.exports = function(s,config,lang){
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
hidden: true,
|
||||||
|
"id": "detectorsSelected",
|
||||||
|
"name": "detail=detectors_selected",
|
||||||
|
"field": lang["Detectors Selected"],
|
||||||
|
"description": lang.fieldTextDetectorsSelected,
|
||||||
|
"default": "all",
|
||||||
|
"attribute": "multiple",
|
||||||
|
"fieldType": "select",
|
||||||
|
"form-group-class": "h_casc_input h_casc_1",
|
||||||
|
"possible": [
|
||||||
|
{
|
||||||
|
"name": `${lang.All} (${lang.Default})`,
|
||||||
|
"value": "all"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "detail=detector_object_ignore_not_move",
|
"name": "detail=detector_object_ignore_not_move",
|
||||||
"field": lang["Ignore Non-Moving"],
|
"field": lang["Ignore Non-Moving"],
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldType": "select",
|
"fieldType": "select",
|
||||||
"selector": "h_obj_ignore_move",
|
"selector": "h_obj_ignore_move",
|
||||||
|
"form-group-class": "h_casc_input h_casc_1",
|
||||||
"possible": [
|
"possible": [
|
||||||
{
|
{
|
||||||
"name": lang.No,
|
"name": lang.No,
|
||||||
|
@ -3183,6 +3201,7 @@ module.exports = function(s,config,lang){
|
||||||
"description": lang["fieldTextDetectorSendFramesObject"],
|
"description": lang["fieldTextDetectorSendFramesObject"],
|
||||||
"default": "1",
|
"default": "1",
|
||||||
"fieldType": "select",
|
"fieldType": "select",
|
||||||
|
"form-group-class": "h_casc_input h_casc_1",
|
||||||
"possible": [
|
"possible": [
|
||||||
{
|
{
|
||||||
"name": lang.No,
|
"name": lang.No,
|
||||||
|
@ -3222,6 +3241,7 @@ module.exports = function(s,config,lang){
|
||||||
"default": "1",
|
"default": "1",
|
||||||
"example": "",
|
"example": "",
|
||||||
"fieldType": "select",
|
"fieldType": "select",
|
||||||
|
"form-group-class": "h_casc_input h_casc_1",
|
||||||
"possible": [
|
"possible": [
|
||||||
{
|
{
|
||||||
"name": lang.No,
|
"name": lang.No,
|
||||||
|
@ -3242,6 +3262,7 @@ module.exports = function(s,config,lang){
|
||||||
"example": "",
|
"example": "",
|
||||||
"selector": "h_det_mot_fir",
|
"selector": "h_det_mot_fir",
|
||||||
"fieldType": "select",
|
"fieldType": "select",
|
||||||
|
"form-group-class": "h_casc_input h_casc_1",
|
||||||
"possible": [
|
"possible": [
|
||||||
{
|
{
|
||||||
"name": lang.No,
|
"name": lang.No,
|
||||||
|
@ -3290,7 +3311,6 @@ module.exports = function(s,config,lang){
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
isAdvanced: true,
|
|
||||||
hidden: true,
|
hidden: true,
|
||||||
"name": lang['Event-Based Recording'],
|
"name": lang['Event-Based Recording'],
|
||||||
"input-mapping": "detector_sip_buffer",
|
"input-mapping": "detector_sip_buffer",
|
||||||
|
|
|
@ -18,10 +18,17 @@
|
||||||
"accountEditError": "Account Edit Error",
|
"accountEditError": "Account Edit Error",
|
||||||
"Monitor Map": "Monitor Map",
|
"Monitor Map": "Monitor Map",
|
||||||
"Geolocation": "Geolocation",
|
"Geolocation": "Geolocation",
|
||||||
|
"Tested on": "Tested on",
|
||||||
|
"Architecture": "Architecture",
|
||||||
|
"Operating Systems": "Operating Systems",
|
||||||
"fieldTextGeolocation": "The map coordinates of this camera in the real world. This will plot a point for your camera on the Monitor Map.",
|
"fieldTextGeolocation": "The map coordinates of this camera in the real world. This will plot a point for your camera on the Monitor Map.",
|
||||||
"playUntilVideoEnd": "Play until video end",
|
"playUntilVideoEnd": "Play until video end",
|
||||||
"Monitor Saved": "Monitor Saved",
|
"Monitor Saved": "Monitor Saved",
|
||||||
"Auto Placement": "Auto Placement",
|
"Auto Placement": "Auto Placement",
|
||||||
|
"Downloaded": "Downloaded",
|
||||||
|
"GPU Required": "GPU Required",
|
||||||
|
"Experimental": "Experimental",
|
||||||
|
"Market": "Market",
|
||||||
"Unmute": "Unmute",
|
"Unmute": "Unmute",
|
||||||
"byUser": "by user",
|
"byUser": "by user",
|
||||||
"accountDeleted": "Account Deleted",
|
"accountDeleted": "Account Deleted",
|
||||||
|
@ -468,6 +475,8 @@
|
||||||
"Max Storage Amount": "Max Storage Amount",
|
"Max Storage Amount": "Max Storage Amount",
|
||||||
"Video Share": "Video Share",
|
"Video Share": "Video Share",
|
||||||
"FileBin": "FileBin",
|
"FileBin": "FileBin",
|
||||||
|
"File Saved": "File Saved",
|
||||||
|
"checkFileBinForNewFile": "Check the FileBin for the newly created file.",
|
||||||
"File Download Ready": "File Download Ready",
|
"File Download Ready": "File Download Ready",
|
||||||
"Timelapse Video Build Complete": "Timelapse Video Build Complete",
|
"Timelapse Video Build Complete": "Timelapse Video Build Complete",
|
||||||
"yourFileDownloadedShortly": "Please wait. Your file will be downloaded shortly.",
|
"yourFileDownloadedShortly": "Please wait. Your file will be downloaded shortly.",
|
||||||
|
@ -535,6 +544,7 @@
|
||||||
"Time-lapse Tool": "Time-lapse Tool",
|
"Time-lapse Tool": "Time-lapse Tool",
|
||||||
"total": "total",
|
"total": "total",
|
||||||
"MB": "MB",
|
"MB": "MB",
|
||||||
|
"All": "All",
|
||||||
"Calendar": "Calendar",
|
"Calendar": "Calendar",
|
||||||
"Leave blank for random.": "Leave blank for random.",
|
"Leave blank for random.": "Leave blank for random.",
|
||||||
"Currently viewing": "Currently viewing",
|
"Currently viewing": "Currently viewing",
|
||||||
|
@ -570,6 +580,10 @@
|
||||||
"clientStreamFailedattemptingReconnect": "Client side stream check failed, attempting reconnect.",
|
"clientStreamFailedattemptingReconnect": "Client side stream check failed, attempting reconnect.",
|
||||||
"Export Video": "Export Video",
|
"Export Video": "Export Video",
|
||||||
"Merge Video": "Merge Video",
|
"Merge Video": "Merge Video",
|
||||||
|
"Merge Videos": "Merge Videos",
|
||||||
|
"Merge": "Merge",
|
||||||
|
"MergeAllSelected": "Merge all selected Videos?",
|
||||||
|
"MergeAllInRange": "Merge all Videos in date range selected?",
|
||||||
"Delete Filter": "Delete Filter",
|
"Delete Filter": "Delete Filter",
|
||||||
"Delete Files": "Delete Files",
|
"Delete Files": "Delete Files",
|
||||||
"confirmDeleteFilter": "Do you want to delete this filter? You cannot recover it.",
|
"confirmDeleteFilter": "Do you want to delete this filter? You cannot recover it.",
|
||||||
|
@ -684,6 +698,7 @@
|
||||||
"Minutes": "Minutes",
|
"Minutes": "Minutes",
|
||||||
"Custom": "Custom",
|
"Custom": "Custom",
|
||||||
"Detector": "Detector",
|
"Detector": "Detector",
|
||||||
|
"Detectors Selected": "Detectors Selected",
|
||||||
"Audio Detector": "Audio Detector",
|
"Audio Detector": "Audio Detector",
|
||||||
"Audio Detection": "Audio Detection",
|
"Audio Detection": "Audio Detection",
|
||||||
"Minimum dB": "Minimum dB",
|
"Minimum dB": "Minimum dB",
|
||||||
|
@ -1093,6 +1108,7 @@
|
||||||
"Can't Connect": "Can't Connect",
|
"Can't Connect": "Can't Connect",
|
||||||
"Video Finished": "Video Finished",
|
"Video Finished": "Video Finished",
|
||||||
"No Monitors Selected": "No Monitors Selected",
|
"No Monitors Selected": "No Monitors Selected",
|
||||||
|
"No Monitor Selected": "No Monitor Selected",
|
||||||
"Nothing Selected": "Nothing Selected",
|
"Nothing Selected": "Nothing Selected",
|
||||||
"makeASelection": "Make a selection and try again.",
|
"makeASelection": "Make a selection and try again.",
|
||||||
"monSavedButNotCopied": "Your monitor was saved but not copied to any other monitor.",
|
"monSavedButNotCopied": "Your monitor was saved but not copied to any other monitor.",
|
||||||
|
@ -1204,6 +1220,7 @@
|
||||||
"Preview": "Preview",
|
"Preview": "Preview",
|
||||||
"Websocket Connected": "Websocket Connected",
|
"Websocket Connected": "Websocket Connected",
|
||||||
"Websocket Disconnected": "Websocket Disconnected",
|
"Websocket Disconnected": "Websocket Disconnected",
|
||||||
|
"Disconnected": "Disconnected",
|
||||||
"Videos Merge": "Videos Merge",
|
"Videos Merge": "Videos Merge",
|
||||||
"Channel ID": "Channel ID",
|
"Channel ID": "Channel ID",
|
||||||
"Recipient ID": "Recipient ID",
|
"Recipient ID": "Recipient ID",
|
||||||
|
@ -1574,6 +1591,7 @@
|
||||||
"MQTT Client": "MQTT Client",
|
"MQTT Client": "MQTT Client",
|
||||||
"Buffer Time from Event": "Buffer Time from Event",
|
"Buffer Time from Event": "Buffer Time from Event",
|
||||||
"detected": "detected",
|
"detected": "detected",
|
||||||
|
"fieldTextDetectorsSelected": "Select which detectors to send frames to.",
|
||||||
"fieldTextEventFilters": "Enable to have all Events honor your Event Filter rules.",
|
"fieldTextEventFilters": "Enable to have all Events honor your Event Filter rules.",
|
||||||
"fieldTextBufferTimeFromEvent": "The amount of seconds to record before the trigger happened. If this is consistently inaccurate you will need to look at the <a target='_blank' href='https://hub.shinobi.video/articles/view/DmWIID78VtvEfnf'>optimization guide</a> or force encoding on the server.",
|
"fieldTextBufferTimeFromEvent": "The amount of seconds to record before the trigger happened. If this is consistently inaccurate you will need to look at the <a target='_blank' href='https://hub.shinobi.video/articles/view/DmWIID78VtvEfnf'>optimization guide</a> or force encoding on the server.",
|
||||||
"fieldTextMode": "This is the primary task of the monitor.",
|
"fieldTextMode": "This is the primary task of the monitor.",
|
||||||
|
@ -1918,6 +1936,8 @@
|
||||||
"HowToConnectDes1": "<b>This feature is available to Mobile License subscribers.</b> To get an API Key please login to your <a href='https: //licenses.shinobi.video/login' target='_blank'>Shinobi<b>Shop</b></a> account and create a key associated to <b>any active Subscription ID</b>. <a href='https://hub.shinobi.video/articles/view/3Yhivc6djTtuBPE' target='_blank'>Learn More.</a>",
|
"HowToConnectDes1": "<b>This feature is available to Mobile License subscribers.</b> To get an API Key please login to your <a href='https: //licenses.shinobi.video/login' target='_blank'>Shinobi<b>Shop</b></a> account and create a key associated to <b>any active Subscription ID</b>. <a href='https://hub.shinobi.video/articles/view/3Yhivc6djTtuBPE' target='_blank'>Learn More.</a>",
|
||||||
"HowToConnectDes2": "If you would like to get access to a private (dedicated) P2P server please create an account at the <a href='https: //licenses.shinobi.video/login' target='_blank'>Shinobi<b>Shop</b></a> and contact us via the Live Chat widget",
|
"HowToConnectDes2": "If you would like to get access to a private (dedicated) P2P server please create an account at the <a href='https: //licenses.shinobi.video/login' target='_blank'>Shinobi<b>Shop</b></a> and contact us via the Live Chat widget",
|
||||||
"User": "User",
|
"User": "User",
|
||||||
|
"Save Unknown Faces": "Save Unknown Faces",
|
||||||
|
"saveUnknownFacesFieldText": "Save Unknown faces to the Face Manager. Manual sorting may still be required.",
|
||||||
"Current Version": "Current Version",
|
"Current Version": "Current Version",
|
||||||
"Default is Global value": "Default is Global value",
|
"Default is Global value": "Default is Global value",
|
||||||
"rejectUnauth": "Ignore server certificate"
|
"rejectUnauth": "Ignore server certificate"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const fsP = require('fs').promises;
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
const fetch = require('node-fetch');
|
const fetch = require('node-fetch');
|
||||||
|
@ -217,6 +218,13 @@ module.exports = (processCwd,config) => {
|
||||||
readStream.pipe(writeStream)
|
readStream.pipe(writeStream)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
async function moveFile(inputFilePath,outputFilePath) {
|
||||||
|
try{
|
||||||
|
await fsP.rm(outputFilePath)
|
||||||
|
}catch(err){}
|
||||||
|
await copyFile(inputFilePath, outputFilePath)
|
||||||
|
await fsP.rm(inputFilePath)
|
||||||
|
}
|
||||||
function hmsToSeconds(str) {
|
function hmsToSeconds(str) {
|
||||||
var p = str.split(':'),
|
var p = str.split(':'),
|
||||||
s = 0, m = 1;
|
s = 0, m = 1;
|
||||||
|
@ -240,6 +248,17 @@ module.exports = (processCwd,config) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function deleteFilesInFolder(folderPath) {
|
||||||
|
try {
|
||||||
|
const files = await fsP.readdir(folderPath);
|
||||||
|
for (const file of files) {
|
||||||
|
const filePath = path.join(folderPath, file);
|
||||||
|
await fsP.rm(filePath, { recursive: true });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error deleting files: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
parseJSON: parseJSON,
|
parseJSON: parseJSON,
|
||||||
stringJSON: stringJSON,
|
stringJSON: stringJSON,
|
||||||
|
@ -261,5 +280,7 @@ module.exports = (processCwd,config) => {
|
||||||
copyFile: copyFile,
|
copyFile: copyFile,
|
||||||
hmsToSeconds,
|
hmsToSeconds,
|
||||||
setDefaultIfUndefined,
|
setDefaultIfUndefined,
|
||||||
|
deleteFilesInFolder,
|
||||||
|
moveFile,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
module.exports = function(s,config,lang,app,io){
|
module.exports = function(s,config,lang,app,io){
|
||||||
|
const {
|
||||||
|
postProcessCompletedMp4Video,
|
||||||
|
} = require('../video/utils.js')(s,config,lang)
|
||||||
const masterDoWorkToo = config.childNodes.masterDoWorkToo;
|
const masterDoWorkToo = config.childNodes.masterDoWorkToo;
|
||||||
const maxCpuPercent = config.childNodes.maxCpuPercent || 75;
|
const maxCpuPercent = config.childNodes.maxCpuPercent || 75;
|
||||||
const maxRamPercent = config.childNodes.maxRamPercent || 75;
|
const maxRamPercent = config.childNodes.maxRamPercent || 75;
|
||||||
|
@ -177,17 +180,21 @@ module.exports = function(s,config,lang,app,io){
|
||||||
filename : filename,
|
filename : filename,
|
||||||
filesizeMB : parseFloat((data.filesize/1048576).toFixed(2))
|
filesizeMB : parseFloat((data.filesize/1048576).toFixed(2))
|
||||||
}
|
}
|
||||||
s.insertDatabaseRow(monitorConfig,insert)
|
s.insertDatabaseRow(monitorConfig,insert,function(response){
|
||||||
s.insertCompletedVideoExtensions.forEach(function(extender){
|
postProcessCompletedMp4Video(response.insertQuery).then((isGood) => {
|
||||||
extender(activeMonitor, monitorConfig, insert)
|
if(!isGood)return console.error(`FAILED VIDEO INSERT`);
|
||||||
|
s.insertCompletedVideoExtensions.forEach(function(extender){
|
||||||
|
extender(activeMonitor, monitorConfig, insert)
|
||||||
|
})
|
||||||
|
//purge over max
|
||||||
|
s.purgeDiskForGroup(data.ke)
|
||||||
|
//send new diskUsage values
|
||||||
|
s.setDiskUsedForGroup(data.ke,insert.filesizeMB)
|
||||||
|
clearTimeout(activeMonitor.recordingChecker)
|
||||||
|
clearTimeout(activeMonitor.streamChecker)
|
||||||
|
resolve(response)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
//purge over max
|
|
||||||
s.purgeDiskForGroup(data.ke)
|
|
||||||
//send new diskUsage values
|
|
||||||
s.setDiskUsedForGroup(data.ke,insert.filesizeMB)
|
|
||||||
clearTimeout(activeMonitor.recordingChecker)
|
|
||||||
clearTimeout(activeMonitor.streamChecker)
|
|
||||||
resolve(response)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,15 @@ module.exports = (s,config,lang) => {
|
||||||
workerProcess.on('message',function(data){
|
workerProcess.on('message',function(data){
|
||||||
if(data.time === 'moment()')data.time = moment();
|
if(data.time === 'moment()')data.time = moment();
|
||||||
switch(data.f){
|
switch(data.f){
|
||||||
|
case'knexQuery':
|
||||||
|
s.knexQuery(...data.args, function(...args){
|
||||||
|
workerProcess.postMessage({
|
||||||
|
f: 'callback',
|
||||||
|
rid: data.rid,
|
||||||
|
args,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
break;
|
||||||
case'debugLog':
|
case'debugLog':
|
||||||
s.debugLog(...data.data)
|
s.debugLog(...data.data)
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -42,6 +42,13 @@ parentPort.on('message',(data) => {
|
||||||
setDefaultConfigOptions()
|
setDefaultConfigOptions()
|
||||||
beginProcessing()
|
beginProcessing()
|
||||||
break;
|
break;
|
||||||
|
case'callback':
|
||||||
|
if(pendingCallbacks[data.rid]){
|
||||||
|
pendingCallbacks[data.rid](...data.args)
|
||||||
|
// console.log(data.rid,typeof pendingCallbacks[data.rid])
|
||||||
|
delete(pendingCallbacks[data.rid])
|
||||||
|
}
|
||||||
|
break;
|
||||||
case'start':case'restart':
|
case'start':case'restart':
|
||||||
setIntervalForCron()
|
setIntervalForCron()
|
||||||
break;
|
break;
|
||||||
|
@ -51,7 +58,7 @@ parentPort.on('message',(data) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
function debugLog(...args){
|
function debugLog(...args){
|
||||||
if(config.debugLog === true){
|
if(config.debugLog === true || config.logCronInfo === true){
|
||||||
console.log(...([`CRON.js DEBUG LOG ${new Date()}`].concat(args)))
|
console.log(...([`CRON.js DEBUG LOG ${new Date()}`].concat(args)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +71,7 @@ function errorLog(...args){
|
||||||
const s = {
|
const s = {
|
||||||
debugLog,
|
debugLog,
|
||||||
}
|
}
|
||||||
|
const pendingCallbacks = {};
|
||||||
function beginProcessing(){
|
function beginProcessing(){
|
||||||
normalLog(`Worker Processing!`)
|
normalLog(`Worker Processing!`)
|
||||||
const {
|
const {
|
||||||
|
@ -74,8 +82,6 @@ function beginProcessing(){
|
||||||
} = require('../basic/utils.js')(process.cwd())
|
} = require('../basic/utils.js')(process.cwd())
|
||||||
const {
|
const {
|
||||||
sqlDate,
|
sqlDate,
|
||||||
knexQuery,
|
|
||||||
knexQueryPromise,
|
|
||||||
initiateDatabaseEngine
|
initiateDatabaseEngine
|
||||||
} = require('../sql/utils.js')(s,config)
|
} = require('../sql/utils.js')(s,config)
|
||||||
var theCronInterval = null
|
var theCronInterval = null
|
||||||
|
@ -111,6 +117,23 @@ function beginProcessing(){
|
||||||
const setDiskUsedForGroup = (groupKey,size,target,videoRow) => {
|
const setDiskUsedForGroup = (groupKey,size,target,videoRow) => {
|
||||||
postMessage({f:'s.setDiskUsedForGroup', ke: groupKey, size: size, target: target, videoRow: videoRow})
|
postMessage({f:'s.setDiskUsedForGroup', ke: groupKey, size: size, target: target, videoRow: videoRow})
|
||||||
}
|
}
|
||||||
|
const knexQuery = (...args) => {
|
||||||
|
const requestId = generateRandomId();
|
||||||
|
const callback = args.pop();
|
||||||
|
pendingCallbacks[requestId] = callback;
|
||||||
|
postMessage({ f: 'knexQuery', args: args, rid: requestId })
|
||||||
|
}
|
||||||
|
const knexQueryPromise = (options) => {
|
||||||
|
return new Promise((resolve,reject) => {
|
||||||
|
knexQuery(options,(err,rows) => {
|
||||||
|
resolve({
|
||||||
|
ok: !err,
|
||||||
|
err: err,
|
||||||
|
rows: rows,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
const getVideoDirectory = function(e){
|
const getVideoDirectory = function(e){
|
||||||
if(e.mid&&!e.id){e.id=e.mid};
|
if(e.mid&&!e.id){e.id=e.mid};
|
||||||
if(e.details&&(e.details instanceof Object)===false){
|
if(e.details&&(e.details instanceof Object)===false){
|
||||||
|
|
|
@ -26,6 +26,9 @@ module.exports = function(s,config,lang,app,io){
|
||||||
const {
|
const {
|
||||||
triggerEvent,
|
triggerEvent,
|
||||||
} = require('./events/utils.js')(s,config,lang)
|
} = require('./events/utils.js')(s,config,lang)
|
||||||
|
const {
|
||||||
|
deleteFilesInFolder,
|
||||||
|
} = require('./basic/utils.js')(process.cwd(), config)
|
||||||
if(config.dropInEventServer === true){
|
if(config.dropInEventServer === true){
|
||||||
if(config.dropInEventForceSaveEvent === undefined)config.dropInEventForceSaveEvent = true
|
if(config.dropInEventForceSaveEvent === undefined)config.dropInEventForceSaveEvent = true
|
||||||
if(config.dropInEventDeleteFileAfterTrigger === undefined)config.dropInEventDeleteFileAfterTrigger = true
|
if(config.dropInEventDeleteFileAfterTrigger === undefined)config.dropInEventDeleteFileAfterTrigger = true
|
||||||
|
@ -142,7 +145,7 @@ module.exports = function(s,config,lang,app,io){
|
||||||
if(config.dropInEventDeleteFileAfterTrigger){
|
if(config.dropInEventDeleteFileAfterTrigger){
|
||||||
clearTimeout(fileQueue[filePath])
|
clearTimeout(fileQueue[filePath])
|
||||||
fileQueue[filePath] = setTimeout(function(){
|
fileQueue[filePath] = setTimeout(function(){
|
||||||
exec('rm -rf ' + filePath,function(err){
|
fs.rm(filePath, { recursive: true },(err) => {
|
||||||
delete(fileQueue[filePath])
|
delete(fileQueue[filePath])
|
||||||
})
|
})
|
||||||
},1000 * 60 * 5)
|
},1000 * 60 * 5)
|
||||||
|
@ -152,7 +155,7 @@ module.exports = function(s,config,lang,app,io){
|
||||||
if(config.dropInEventDeleteFileAfterTrigger){
|
if(config.dropInEventDeleteFileAfterTrigger){
|
||||||
clearTimeout(fileQueueForDeletion[deletionKey])
|
clearTimeout(fileQueueForDeletion[deletionKey])
|
||||||
fileQueueForDeletion[deletionKey] = setTimeout(function(){
|
fileQueueForDeletion[deletionKey] = setTimeout(function(){
|
||||||
exec('rm -rf ' + deletionKey,function(err){
|
fs.rm(filePath, { recursive: true },(err) => {
|
||||||
delete(fileQueueForDeletion[deletionKey])
|
delete(fileQueueForDeletion[deletionKey])
|
||||||
})
|
})
|
||||||
},1000 * 60 * 5)
|
},1000 * 60 * 5)
|
||||||
|
@ -194,7 +197,7 @@ module.exports = function(s,config,lang,app,io){
|
||||||
directory = s.dir.dropInEvents + e.ke + '/' + (e.id || e.mid) + '/'
|
directory = s.dir.dropInEvents + e.ke + '/' + (e.id || e.mid) + '/'
|
||||||
fs.mkdir(directory,function(err){
|
fs.mkdir(directory,function(err){
|
||||||
s.handleFolderError(err)
|
s.handleFolderError(err)
|
||||||
exec('rm -rf "' + directory + '*"',function(){})
|
deleteFilesInFolder(directory)
|
||||||
callback(err,directory)
|
callback(err,directory)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -40,7 +40,7 @@ module.exports = (s,config,lang) => {
|
||||||
const monitorId = options.mid || options.id
|
const monitorId = options.mid || options.id
|
||||||
const groupKey = options.ke
|
const groupKey = options.ke
|
||||||
//if(!frameBuffer || imageSaveEventLock[groupKey + monitorId])return;
|
//if(!frameBuffer || imageSaveEventLock[groupKey + monitorId])return;
|
||||||
if(!frameBuffer || frameBuffer.length === 0 || imageSaveEventLock[groupKey + monitorId]) return;
|
if(!frameBuffer || frameBuffer.length === 0 || imageSaveEventLock[groupKey + monitorId]) return;
|
||||||
const eventTime = options.time
|
const eventTime = options.time
|
||||||
const objectsFound = options.matrices
|
const objectsFound = options.matrices
|
||||||
const monitorConfig = Object.assign({id: monitorId},s.group[groupKey].rawMonitorConfigurations[monitorId])
|
const monitorConfig = Object.assign({id: monitorId},s.group[groupKey].rawMonitorConfigurations[monitorId])
|
||||||
|
@ -678,17 +678,37 @@ module.exports = (s,config,lang) => {
|
||||||
const activeMonitor = s.group[groupKey].activeMonitors[monitorId]
|
const activeMonitor = s.group[groupKey].activeMonitors[monitorId]
|
||||||
const theEmitter = activeMonitor.secondaryDetectorOutput
|
const theEmitter = activeMonitor.secondaryDetectorOutput
|
||||||
if(!activeMonitor.sendingFromSecondaryDetectorOuput){
|
if(!activeMonitor.sendingFromSecondaryDetectorOuput){
|
||||||
s.debugLog('start sending object frames',groupKey,monitorId)
|
const monitorConfig = s.group[groupKey].rawMonitorConfigurations[monitorId]
|
||||||
theEmitter.on('data',activeMonitor.secondaryDetectorOuputContentWriter = (data) => {
|
const monitorDetails = monitorConfig.details;
|
||||||
|
let chosenDetector = monitorDetails.detectors_selected;
|
||||||
|
if(chosenDetector instanceof Array)chosenDetector = chosenDetector.join(',');
|
||||||
|
let sendToDetector = (data) => {
|
||||||
s.ocvTx({
|
s.ocvTx({
|
||||||
f : 'frame',
|
f : 'frame',
|
||||||
mon : s.group[groupKey].rawMonitorConfigurations[monitorId].details,
|
mon : monitorDetails,
|
||||||
ke : groupKey,
|
ke : groupKey,
|
||||||
id : monitorId,
|
id : monitorId,
|
||||||
time : s.formattedTime(),
|
time : s.formattedTime(),
|
||||||
frame : data
|
frame : data
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
if(chosenDetector && !(chosenDetector.includes('all'))){
|
||||||
|
const pluginsGettingIt = chosenDetector.split(',').map(item => item.trim()).filter(item => !!item);
|
||||||
|
sendToDetector = (data) => {
|
||||||
|
for(pluginName of pluginsGettingIt){
|
||||||
|
s.sendToDetector(pluginName, {
|
||||||
|
f : 'frame',
|
||||||
|
mon : monitorDetails,
|
||||||
|
ke : groupKey,
|
||||||
|
id : monitorId,
|
||||||
|
time : s.formattedTime(),
|
||||||
|
frame : data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.debugLog('start sending object frames',groupKey,monitorId)
|
||||||
|
theEmitter.on('data', activeMonitor.secondaryDetectorOuputContentWriter = sendToDetector)
|
||||||
}
|
}
|
||||||
clearTimeout(activeMonitor.sendingFromSecondaryDetectorOuput)
|
clearTimeout(activeMonitor.sendingFromSecondaryDetectorOuput)
|
||||||
activeMonitor.sendingFromSecondaryDetectorOuput = setTimeout(() => {
|
activeMonitor.sendingFromSecondaryDetectorOuput = setTimeout(() => {
|
||||||
|
|
|
@ -87,6 +87,8 @@ module.exports = function(s,config){
|
||||||
createExtension(`onSubscriptionCheck`)
|
createExtension(`onSubscriptionCheck`)
|
||||||
createExtension(`onDataPortMessage`)
|
createExtension(`onDataPortMessage`)
|
||||||
createExtension(`onHttpRequestUpgrade`,null,true)
|
createExtension(`onHttpRequestUpgrade`,null,true)
|
||||||
|
createExtension(`onPluginConnected`)
|
||||||
|
createExtension(`onPluginDisconnected`)
|
||||||
/////// CRON ////////
|
/////// CRON ////////
|
||||||
createExtension(`onCronGroupProcessed`)
|
createExtension(`onCronGroupProcessed`)
|
||||||
createExtension(`onCronGroupProcessedAwaited`)
|
createExtension(`onCronGroupProcessedAwaited`)
|
||||||
|
|
|
@ -790,6 +790,8 @@ module.exports = (s,config,lang) => {
|
||||||
return ``
|
return ``
|
||||||
}
|
}
|
||||||
const getDefaultSubstreamFields = function(monitor){
|
const getDefaultSubstreamFields = function(monitor){
|
||||||
|
const otherInputFlags = []
|
||||||
|
const otherOutputFlags = []
|
||||||
const subStreamFields = parseJSON(monitor.details.substream || {input:{},output:{}})
|
const subStreamFields = parseJSON(monitor.details.substream || {input:{},output:{}})
|
||||||
const inputAndConnectionFields = Object.assign({
|
const inputAndConnectionFields = Object.assign({
|
||||||
"type":"h264",
|
"type":"h264",
|
||||||
|
@ -825,7 +827,15 @@ module.exports = (s,config,lang) => {
|
||||||
"svf":"",
|
"svf":"",
|
||||||
"cust_stream":""
|
"cust_stream":""
|
||||||
},subStreamFields.output);
|
},subStreamFields.output);
|
||||||
|
const isMp4Input = inputAndConnectionFields.type === 'mp4';
|
||||||
|
const inputTypeCanLoop = isMp4Input || inputAndConnectionFields.type === 'local'
|
||||||
|
if(inputTypeCanLoop){
|
||||||
|
otherInputFlags.push('-stream_loop', '-1');
|
||||||
|
if(isMp4Input)otherInputFlags.push('-re');
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
|
otherInputFlags,
|
||||||
|
otherOutputFlags,
|
||||||
inputAndConnectionFields,
|
inputAndConnectionFields,
|
||||||
outputFields,
|
outputFields,
|
||||||
}
|
}
|
||||||
|
|
|
@ -192,6 +192,11 @@ module.exports = function(s,config,lang,app,io){
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
s.notifyFileBinUploaded = function(fileBinInsertQuery){
|
||||||
|
s.tx(Object.assign({
|
||||||
|
f: 'fileBin_item_added',
|
||||||
|
},fileBinInsertQuery),'GRP_'+fileBinInsertQuery.ke);
|
||||||
|
}
|
||||||
s.getFileBinDirectory = getFileBinDirectory
|
s.getFileBinDirectory = getFileBinDirectory
|
||||||
s.getFileBinEntry = getFileBinEntry
|
s.getFileBinEntry = getFileBinEntry
|
||||||
s.getFileBinBuffer = getFileBinBuffer
|
s.getFileBinBuffer = getFileBinBuffer
|
||||||
|
|
|
@ -278,6 +278,15 @@ module.exports = (s,config,lang) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
const sendSubstreamEvent = function(groupKey, monitorId, eventName = 'substream_start'){
|
||||||
|
const activeMonitor = getActiveMonitor(groupKey,monitorId)
|
||||||
|
s.tx({
|
||||||
|
f: eventName,
|
||||||
|
mid: monitorId,
|
||||||
|
ke: groupKey,
|
||||||
|
channel: activeMonitor.subStreamChannel
|
||||||
|
},'GRP_'+groupKey);
|
||||||
|
}
|
||||||
const spawnSubstreamProcess = function(e){
|
const spawnSubstreamProcess = function(e){
|
||||||
// e = monitorConfig
|
// e = monitorConfig
|
||||||
try{
|
try{
|
||||||
|
@ -295,9 +304,13 @@ module.exports = (s,config,lang) => {
|
||||||
substreamConfig.input.fulladdress = substreamConfig.input.fulladdress || s.buildMonitorUrl(monitorConfig)
|
substreamConfig.input.fulladdress = substreamConfig.input.fulladdress || s.buildMonitorUrl(monitorConfig)
|
||||||
substreamConfig.input.rtsp_transport = substreamConfig.input.rtsp_transport || monitorConfig.details.rtsp_transport
|
substreamConfig.input.rtsp_transport = substreamConfig.input.rtsp_transport || monitorConfig.details.rtsp_transport
|
||||||
const {
|
const {
|
||||||
|
otherInputFlags,
|
||||||
|
otherOutputFlags,
|
||||||
inputAndConnectionFields,
|
inputAndConnectionFields,
|
||||||
outputFields,
|
outputFields,
|
||||||
} = getDefaultSubstreamFields(monitorConfig);
|
} = getDefaultSubstreamFields(monitorConfig);
|
||||||
|
ffmpegCommand.push(...otherInputFlags);
|
||||||
|
ffmpegCommand.push(...otherOutputFlags);
|
||||||
([
|
([
|
||||||
buildSubstreamString(channelNumber + config.pipeAddition,e),
|
buildSubstreamString(channelNumber + config.pipeAddition,e),
|
||||||
]).forEach(function(commandStringPart){
|
]).forEach(function(commandStringPart){
|
||||||
|
@ -375,12 +388,7 @@ module.exports = (s,config,lang) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
activeMonitor.subStreamProcess = subStreamProcess
|
activeMonitor.subStreamProcess = subStreamProcess
|
||||||
s.tx({
|
sendSubstreamEvent(groupKey, monitorId)
|
||||||
f: 'substream_start',
|
|
||||||
mid: monitorId,
|
|
||||||
ke: groupKey,
|
|
||||||
channel: activeMonitor.subStreamChannel
|
|
||||||
},'GRP_'+groupKey);
|
|
||||||
return subStreamProcess
|
return subStreamProcess
|
||||||
}catch(err){
|
}catch(err){
|
||||||
s.systemLog(err)
|
s.systemLog(err)
|
||||||
|
@ -403,11 +411,7 @@ module.exports = (s,config,lang) => {
|
||||||
response.hadSubStream = true
|
response.hadSubStream = true
|
||||||
response.closeResponse = closeResponse
|
response.closeResponse = closeResponse
|
||||||
delete(activeMonitor.subStreamProcess)
|
delete(activeMonitor.subStreamProcess)
|
||||||
s.tx({
|
sendSubstreamEvent(activeMonitor.mid, activeMonitor.ke, 'substream_end')
|
||||||
f: 'substream_end',
|
|
||||||
mid: activeMonitor.mid,
|
|
||||||
ke: activeMonitor.ke
|
|
||||||
},'GRP_'+activeMonitor.ke);
|
|
||||||
activeMonitor.subStreamProcessClosing = false
|
activeMonitor.subStreamProcessClosing = false
|
||||||
}
|
}
|
||||||
}catch(err){
|
}catch(err){
|
||||||
|
@ -975,6 +979,7 @@ module.exports = (s,config,lang) => {
|
||||||
const groupKey = e.ke
|
const groupKey = e.ke
|
||||||
const monitorId = e.mid || e.id
|
const monitorId = e.mid || e.id
|
||||||
const activeMonitor = getActiveMonitor(groupKey,monitorId)
|
const activeMonitor = getActiveMonitor(groupKey,monitorId)
|
||||||
|
if(!activeMonitor)return;
|
||||||
clearTimeout(activeMonitor.streamChecker)
|
clearTimeout(activeMonitor.streamChecker)
|
||||||
activeMonitor.streamChecker = setTimeout(function(){
|
activeMonitor.streamChecker = setTimeout(function(){
|
||||||
if(activeMonitor && activeMonitor.isStarted === true){
|
if(activeMonitor && activeMonitor.isStarted === true){
|
||||||
|
@ -988,7 +993,7 @@ module.exports = (s,config,lang) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},60000*1);
|
},60000 * 1);
|
||||||
}
|
}
|
||||||
function resetTimelapseFramesCheck(e){
|
function resetTimelapseFramesCheck(e){
|
||||||
const groupKey = e.ke
|
const groupKey = e.ke
|
||||||
|
@ -1181,7 +1186,6 @@ module.exports = (s,config,lang) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if(e.details.detector === '1'){
|
if(e.details.detector === '1'){
|
||||||
s.ocvTx({f:'init_monitor',id:monitorId,ke:groupKey})
|
|
||||||
//frames from motion detect
|
//frames from motion detect
|
||||||
if(e.details.detector_pam === '1'){
|
if(e.details.detector_pam === '1'){
|
||||||
// activeMonitor.spawn.stdio[3].pipe(activeMonitor.p2p).pipe(activeMonitor.pamDiff)
|
// activeMonitor.spawn.stdio[3].pipe(activeMonitor.p2p).pipe(activeMonitor.pamDiff)
|
||||||
|
@ -1856,5 +1860,6 @@ module.exports = (s,config,lang) => {
|
||||||
setTimedActiveViewerForHttp: setTimedActiveViewerForHttp,
|
setTimedActiveViewerForHttp: setTimedActiveViewerForHttp,
|
||||||
attachMainProcessHandlers: attachMainProcessHandlers,
|
attachMainProcessHandlers: attachMainProcessHandlers,
|
||||||
removeSenstiveInfoFromMonitorConfig,
|
removeSenstiveInfoFromMonitorConfig,
|
||||||
|
sendSubstreamEvent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,18 +43,7 @@ module.exports = function(s,config,lang,app,io){
|
||||||
s.detectorPluginArray = []
|
s.detectorPluginArray = []
|
||||||
s.isAtleatOneDetectorPluginConnected = false
|
s.isAtleatOneDetectorPluginConnected = false
|
||||||
s.addDetectorPlugin = function(name,d){
|
s.addDetectorPlugin = function(name,d){
|
||||||
if(config.useOldPluginConnectionMethod === true){
|
const newDetector = {
|
||||||
s.ocv = {
|
|
||||||
started: s.timeObject(),
|
|
||||||
id: d.id,
|
|
||||||
plug: d.plug,
|
|
||||||
notice: d.notice,
|
|
||||||
isClientPlugin: d.isClientPlugin,
|
|
||||||
isHostPlugin: d.isHostPlugin,
|
|
||||||
connectionType: d.connectionType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.connectedDetectorPlugins[d.plug] = {
|
|
||||||
started: s.timeObject(),
|
started: s.timeObject(),
|
||||||
id: d.id,
|
id: d.id,
|
||||||
plug: d.plug,
|
plug: d.plug,
|
||||||
|
@ -62,15 +51,22 @@ module.exports = function(s,config,lang,app,io){
|
||||||
isClientPlugin: d.isClientPlugin,
|
isClientPlugin: d.isClientPlugin,
|
||||||
isHostPlugin: d.isHostPlugin,
|
isHostPlugin: d.isHostPlugin,
|
||||||
connectionType: d.connectionType
|
connectionType: d.connectionType
|
||||||
|
};
|
||||||
|
if(config.useOldPluginConnectionMethod === true){
|
||||||
|
s.ocv = newDetector
|
||||||
}
|
}
|
||||||
|
s.connectedDetectorPlugins[d.plug] = newDetector
|
||||||
s.resetDetectorPluginArray()
|
s.resetDetectorPluginArray()
|
||||||
|
s.runExtensionsForArray('onPluginConnected', null, [d.plug, newDetector])
|
||||||
}
|
}
|
||||||
s.removeDetectorPlugin = function(name){
|
s.removeDetectorPlugin = function(name){
|
||||||
|
const theDetector = Object.assign({}, s.connectedDetectorPlugins[name])
|
||||||
if(config.oldPluginConnectionMethod === true && s.ocv && s.ocv.plug === name){
|
if(config.oldPluginConnectionMethod === true && s.ocv && s.ocv.plug === name){
|
||||||
delete(s.ocv)
|
delete(s.ocv)
|
||||||
}
|
}
|
||||||
delete(s.connectedDetectorPlugins[name])
|
delete(s.connectedDetectorPlugins[name])
|
||||||
s.resetDetectorPluginArray(name)
|
s.resetDetectorPluginArray(name)
|
||||||
|
s.runExtensionsForArray('onPluginDisconnected', null, [name, theDetector])
|
||||||
}
|
}
|
||||||
s.resetDetectorPluginArray = function(){
|
s.resetDetectorPluginArray = function(){
|
||||||
pluginArray = []
|
pluginArray = []
|
||||||
|
@ -164,6 +160,10 @@ module.exports = function(s,config,lang,app,io){
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
s.sendToDetector = function(pluginName, data){
|
||||||
|
const detector = s.connectedPlugins[pluginName];
|
||||||
|
if(detector)detector.tx(data);
|
||||||
|
}
|
||||||
s.sendDetectorInfoToClient = function(data,txFunction){
|
s.sendDetectorInfoToClient = function(data,txFunction){
|
||||||
s.detectorPluginArray.forEach(function(name){
|
s.detectorPluginArray.forEach(function(name){
|
||||||
var detectorData = Object.assign(data,{
|
var detectorData = Object.assign(data,{
|
||||||
|
@ -363,7 +363,33 @@ module.exports = function(s,config,lang,app,io){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function onMonitorUpdate(monitorConfig){
|
||||||
|
// console.log('Sending Monitor Info to Plugin', monitorConfig.mid)
|
||||||
|
s.sendToAllDetectors({ f: 'monitorUpdate', monitorConfig });
|
||||||
|
}
|
||||||
|
function sendCopyOfAllMonitorConfigs(){
|
||||||
|
const groupKeys = Object.keys(s.group);
|
||||||
|
for(groupKey of groupKeys){
|
||||||
|
const monitorConfigs = Object.values(s.group[groupKey].rawMonitorConfigurations);
|
||||||
|
for(monitorConfig of monitorConfigs){
|
||||||
|
onMonitorUpdate(monitorConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* API : Get List of Connected Plugins
|
||||||
|
*/
|
||||||
|
app.get(config.webPaths.apiPrefix+':auth/plugins/list', async (req,res) => {
|
||||||
|
s.auth(req.params, async (resp) => {
|
||||||
|
s.closeJsonResponse(res,{
|
||||||
|
ok: true,
|
||||||
|
plugins: s.connectedDetectorPlugins
|
||||||
|
})
|
||||||
|
},res,req)
|
||||||
|
})
|
||||||
s.onSocketAuthentication(onSocketAuthentication)
|
s.onSocketAuthentication(onSocketAuthentication)
|
||||||
s.onWebSocketDisconnection(onWebSocketDisconnection)
|
s.onWebSocketDisconnection(onWebSocketDisconnection)
|
||||||
s.onWebSocketConnection(onWebSocketConnection)
|
s.onWebSocketConnection(onWebSocketConnection)
|
||||||
|
s.onMonitorStart(onMonitorUpdate)
|
||||||
|
s.onPluginConnected(sendCopyOfAllMonitorConfigs)
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,12 +94,13 @@ module.exports = async (s,config,lang,app,io,currentUse) => {
|
||||||
const downloadModule = (downloadUrl,packageName) => {
|
const downloadModule = (downloadUrl,packageName) => {
|
||||||
const downloadPath = modulesBasePath + packageName
|
const downloadPath = modulesBasePath + packageName
|
||||||
try{
|
try{
|
||||||
fs.mkdirSync(downloadPath)
|
fs.mkdirSync(downloadPath, { recursive: true })
|
||||||
}catch(err){
|
}catch(err){
|
||||||
s.debugLog(err)
|
s.debugLog(err)
|
||||||
}
|
}
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
fs.mkdir(downloadPath, () => {
|
fs.mkdir(downloadPath, { recursive: true }, (err) => {
|
||||||
|
if(err)console.error(err)
|
||||||
fetchDownloadAndWrite(downloadUrl,downloadPath + '.zip', 1)
|
fetchDownloadAndWrite(downloadUrl,downloadPath + '.zip', 1)
|
||||||
.then((readStream) => {
|
.then((readStream) => {
|
||||||
readStream.pipe(unzipper.Parse())
|
readStream.pipe(unzipper.Parse())
|
||||||
|
@ -228,11 +229,15 @@ module.exports = async (s,config,lang,app,io,currentUse) => {
|
||||||
}
|
}
|
||||||
const enableModule = (name,status) => {
|
const enableModule = (name,status) => {
|
||||||
// set status to `false` to enable
|
// set status to `false` to enable
|
||||||
const modulePath = getModulePath(name)
|
try{
|
||||||
const confJson = getModuleConfiguration(name)
|
const modulePath = getModulePath(name)
|
||||||
const confPath = modulePath + 'conf.json'
|
const confJson = getModuleConfiguration(name)
|
||||||
confJson.enabled = status;
|
const confPath = modulePath + 'conf.json'
|
||||||
fs.writeFileSync(confPath,s.prettyPrint(confJson))
|
confJson.enabled = status;
|
||||||
|
fs.writeFileSync(confPath,s.prettyPrint(confJson))
|
||||||
|
}catch(err){
|
||||||
|
console.error('Failed to Toggle Enable Status for Module.', name, status)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const deleteModule = (name) => {
|
const deleteModule = (name) => {
|
||||||
// requires restart for changes to take effect
|
// requires restart for changes to take effect
|
||||||
|
|
|
@ -34,6 +34,7 @@ module.exports = function(s,config,lang,app,io){
|
||||||
s.createTimelapseFrameAndInsert = function(e,location,filename,eventTime,frameDetails){
|
s.createTimelapseFrameAndInsert = function(e,location,filename,eventTime,frameDetails){
|
||||||
//e = monitor object
|
//e = monitor object
|
||||||
//location = file location
|
//location = file location
|
||||||
|
var monitorId = e.id || e.mid;
|
||||||
var filePath = location + filename
|
var filePath = location + filename
|
||||||
var fileStats = fs.statSync(filePath)
|
var fileStats = fs.statSync(filePath)
|
||||||
var details = Object.assign({},frameDetails || {})
|
var details = Object.assign({},frameDetails || {})
|
||||||
|
@ -43,7 +44,7 @@ module.exports = function(s,config,lang,app,io){
|
||||||
const timeNow = eventTime || new Date()
|
const timeNow = eventTime || new Date()
|
||||||
const queryInfo = {
|
const queryInfo = {
|
||||||
ke: e.ke,
|
ke: e.ke,
|
||||||
mid: e.id,
|
mid: monitorId,
|
||||||
details: s.s(details),
|
details: s.s(details),
|
||||||
filename: filename,
|
filename: filename,
|
||||||
size: fileStats.size,
|
size: fileStats.size,
|
||||||
|
@ -53,7 +54,7 @@ module.exports = function(s,config,lang,app,io){
|
||||||
var currentDate = s.formattedTime(timeNow,'YYYY-MM-DD')
|
var currentDate = s.formattedTime(timeNow,'YYYY-MM-DD')
|
||||||
const childNodeData = {
|
const childNodeData = {
|
||||||
ke: e.ke,
|
ke: e.ke,
|
||||||
mid: e.id,
|
mid: monitorId,
|
||||||
time: currentDate,
|
time: currentDate,
|
||||||
filename: filename,
|
filename: filename,
|
||||||
currentDate: currentDate,
|
currentDate: currentDate,
|
||||||
|
@ -559,8 +560,7 @@ module.exports = function(s,config,lang,app,io){
|
||||||
actionParameter && (
|
actionParameter && (
|
||||||
isRestrictedApiKey && apiKeyPermissions.delete_videos_disallowed ||
|
isRestrictedApiKey && apiKeyPermissions.delete_videos_disallowed ||
|
||||||
isRestricted && !monitorPermissions[`${monitorId}_video_delete`]
|
isRestricted && !monitorPermissions[`${monitorId}_video_delete`]
|
||||||
) ||
|
) || !actionParameter && (
|
||||||
!actionParameter && (
|
|
||||||
isRestrictedApiKey && apiKeyPermissions.watch_videos_disallowed ||
|
isRestrictedApiKey && apiKeyPermissions.watch_videos_disallowed ||
|
||||||
isRestricted && monitorId && !monitorPermissions[`${monitorId}_video_view`]
|
isRestricted && monitorId && !monitorPermissions[`${monitorId}_video_view`]
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const { spawn } = require('child_process')
|
const { spawn } = require('child_process')
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
|
const path = require('path');
|
||||||
|
const moment = require('moment');
|
||||||
|
const fsP = require('fs').promises;
|
||||||
module.exports = (s,config,lang) => {
|
module.exports = (s,config,lang) => {
|
||||||
const {
|
const {
|
||||||
ffprobe,
|
ffprobe,
|
||||||
|
@ -9,7 +12,9 @@ module.exports = (s,config,lang) => {
|
||||||
const {
|
const {
|
||||||
copyFile,
|
copyFile,
|
||||||
hmsToSeconds,
|
hmsToSeconds,
|
||||||
|
moveFile,
|
||||||
} = require('../basic/utils.js')(process.cwd(),config)
|
} = require('../basic/utils.js')(process.cwd(),config)
|
||||||
|
const chunkReadSize = 4096;
|
||||||
// orphanedVideoCheck : new function
|
// orphanedVideoCheck : new function
|
||||||
const checkIfVideoIsOrphaned = (monitor,videosDirectory,filename) => {
|
const checkIfVideoIsOrphaned = (monitor,videosDirectory,filename) => {
|
||||||
const response = {ok: true}
|
const response = {ok: true}
|
||||||
|
@ -593,6 +598,7 @@ module.exports = (s,config,lang) => {
|
||||||
time: video.time,
|
time: video.time,
|
||||||
}
|
}
|
||||||
await s.insertFileBinEntry(fileBinInsertQuery)
|
await s.insertFileBinEntry(fileBinInsertQuery)
|
||||||
|
s.notifyFileBinUploaded(fileBinInsertQuery)
|
||||||
s.tx(Object.assign({
|
s.tx(Object.assign({
|
||||||
f: 'fileBin_item_added',
|
f: 'fileBin_item_added',
|
||||||
slicedVideo: true,
|
slicedVideo: true,
|
||||||
|
@ -604,6 +610,374 @@ module.exports = (s,config,lang) => {
|
||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
const mergingVideos = {};
|
||||||
|
const mergeVideos = async function({
|
||||||
|
groupKey,
|
||||||
|
monitorId,
|
||||||
|
filePaths,
|
||||||
|
outputFilePath,
|
||||||
|
videoCodec = 'libx265',
|
||||||
|
audioCodec = 'aac',
|
||||||
|
onStdout = (data) => {s.systemLog(`${data}`)},
|
||||||
|
onStderr = (data) => {s.systemLog(`${data}`)},
|
||||||
|
}) {
|
||||||
|
if (!Array.isArray(filePaths) || filePaths.length === 0) {
|
||||||
|
throw new Error('First parameter must be a non-empty array of absolute file paths.');
|
||||||
|
}
|
||||||
|
if(mergingVideos[outputFilePath])return;
|
||||||
|
const currentDate = new Date();
|
||||||
|
const fileExtensions = filePaths.map(file => path.extname(file).toLowerCase());
|
||||||
|
const allSameExtension = fileExtensions.every(ext => ext === fileExtensions[0]);
|
||||||
|
const fileList = filePaths.map(file => `file '${file}'`).join('\n');
|
||||||
|
const tempFileListPath = path.join(s.dir.streams, groupKey, monitorId, `video_merge_${currentDate}.txt`);
|
||||||
|
mergingVideos[outputFilePath] = currentDate;
|
||||||
|
try {
|
||||||
|
await fsP.writeFile(tempFileListPath, fileList);
|
||||||
|
let ffmpegArgs;
|
||||||
|
// if (allSameExtension) {
|
||||||
|
// ffmpegArgs = [
|
||||||
|
// '-f', 'concat',
|
||||||
|
// '-safe', '0',
|
||||||
|
// '-i', tempFileListPath,
|
||||||
|
// '-c', 'copy',
|
||||||
|
// '-y',
|
||||||
|
// outputFilePath
|
||||||
|
// ];
|
||||||
|
// } else {
|
||||||
|
ffmpegArgs = [
|
||||||
|
'-loglevel', 'warning',
|
||||||
|
'-f', 'concat',
|
||||||
|
'-safe', '0',
|
||||||
|
'-i', tempFileListPath,
|
||||||
|
'-c:v', videoCodec,
|
||||||
|
'-c:a', audioCodec,
|
||||||
|
'-strict', '-2',
|
||||||
|
'-crf', '1',
|
||||||
|
'-y',
|
||||||
|
outputFilePath
|
||||||
|
];
|
||||||
|
// }
|
||||||
|
s.debugLog(fileList)
|
||||||
|
s.debugLog(ffmpegArgs)
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const ffmpegProcess = spawn(config.ffmpegDir, ffmpegArgs);
|
||||||
|
ffmpegProcess.stdout.on('data', onStdout);
|
||||||
|
ffmpegProcess.stderr.on('data', onStderr);
|
||||||
|
ffmpegProcess.on('close', (code) => {
|
||||||
|
delete(mergingVideos[outputFilePath]);
|
||||||
|
if (code === 0) {
|
||||||
|
console.log(`FFmpeg process exited with code ${code}`);
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(new Error(`FFmpeg process exited with code ${code}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ffmpegProcess.on('error', (err) => {
|
||||||
|
reject(new Error(`FFmpeg error: ${err}`));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
await fsP.unlink(tempFileListPath);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
async function mergeVideosAndBin(videos){
|
||||||
|
const currentTime = new Date();
|
||||||
|
const firstVideo = videos[0];
|
||||||
|
const lastVideo = videos[videos.length - 1];
|
||||||
|
const groupKey = firstVideo.ke;
|
||||||
|
const monitorId = firstVideo.mid;
|
||||||
|
const logTarget = { ke: groupKey, mid: '$USER' };
|
||||||
|
try{
|
||||||
|
try{
|
||||||
|
await fsP.stat(outputFilePath)
|
||||||
|
return outputFilePath
|
||||||
|
}catch(err){
|
||||||
|
|
||||||
|
}
|
||||||
|
const filePaths = videos.map(video => {
|
||||||
|
const monitorConfig = s.group[video.ke].rawMonitorConfigurations[video.mid];
|
||||||
|
const filePath = path.join(s.getVideoDirectory(video), `${s.formattedTime(video.time)}.mp4`);
|
||||||
|
return filePath
|
||||||
|
});
|
||||||
|
const filename = `${s.formattedTime(firstVideo.time)}-${s.formattedTime(lastVideo.time)}-${filePaths.length}.mp4`
|
||||||
|
const fileBinFolder = s.getFileBinDirectory(firstVideo);
|
||||||
|
const outputFilePath = path.join(fileBinFolder, filename);
|
||||||
|
|
||||||
|
s.userLog(logTarget,{
|
||||||
|
type: 'mergeVideos ffmpeg START',
|
||||||
|
msg: {
|
||||||
|
monitorId,
|
||||||
|
numberOfVideos: filePaths.length,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await mergeVideos({
|
||||||
|
groupKey,
|
||||||
|
monitorId,
|
||||||
|
filePaths,
|
||||||
|
outputFilePath,
|
||||||
|
onStdout: (data) => {
|
||||||
|
s.debugLog(data.toString())
|
||||||
|
s.userLog(logTarget,{
|
||||||
|
type: 'mergeVideos ffmpeg LOG',
|
||||||
|
msg: `${data}`
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onStderr: (data) => {
|
||||||
|
s.debugLog(data.toString())
|
||||||
|
s.userLog(logTarget,{
|
||||||
|
type: 'mergeVideos ffmpeg ERROR',
|
||||||
|
msg: `${data}`
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const fileSize = (await fsP.stat(outputFilePath)).size;
|
||||||
|
const fileBinInsertQuery = {
|
||||||
|
ke: groupKey,
|
||||||
|
mid: monitorId,
|
||||||
|
name: filename,
|
||||||
|
size: fileSize,
|
||||||
|
details: {},
|
||||||
|
status: 1,
|
||||||
|
time: currentTime,
|
||||||
|
}
|
||||||
|
await s.insertFileBinEntry(fileBinInsertQuery);
|
||||||
|
s.notifyFileBinUploaded(fileBinInsertQuery);
|
||||||
|
return outputFilePath
|
||||||
|
}catch(err){
|
||||||
|
console.log('mergeVideos process ERROR', err)
|
||||||
|
s.userLog(logTarget,{
|
||||||
|
type: 'mergeVideos process ERROR',
|
||||||
|
msg: `${err.toString()}`
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readChunkForMoov(filePath, start, end) {
|
||||||
|
const stream = fs.createReadStream(filePath, { start, end });
|
||||||
|
let hasMoov = false;
|
||||||
|
|
||||||
|
for await (const chunk of stream) {
|
||||||
|
if (chunk.includes('moov')) {
|
||||||
|
hasMoov = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasMoov;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkMoovAtBeginning(filePath) {
|
||||||
|
return await readChunkForMoov(filePath, 0, chunkReadSize - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkMoovAtEnd(filePath) {
|
||||||
|
const stats = await fs.promises.stat(filePath);
|
||||||
|
const fileSize = stats.size;
|
||||||
|
return await readChunkForMoov(filePath, fileSize - chunkReadSize, fileSize - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function hasMoovAtom(filePath) {
|
||||||
|
const foundAtBeginning = await checkMoovAtBeginning(filePath);
|
||||||
|
|
||||||
|
if (foundAtBeginning) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const foundAtEnd = await checkMoovAtEnd(filePath);
|
||||||
|
return foundAtEnd;
|
||||||
|
}
|
||||||
|
const addMoovAtom = async (inputFilePath, outputFilePath, videoCodec = 'libx264', audioCodec = 'aac') => {
|
||||||
|
try {
|
||||||
|
const ffmpegArgs = [
|
||||||
|
'-i', inputFilePath,
|
||||||
|
'-c:v', videoCodec,
|
||||||
|
];
|
||||||
|
if(audioCodec){
|
||||||
|
ffmpegArgs.push('-c:a', audioCodec, '-strict', '-2')
|
||||||
|
}else{
|
||||||
|
ffmpegArgs.push('-an')
|
||||||
|
}
|
||||||
|
ffmpegArgs.push(
|
||||||
|
'-movflags', '+faststart',
|
||||||
|
'-crf', '0',
|
||||||
|
'-q:a', '0',
|
||||||
|
outputFilePath
|
||||||
|
);
|
||||||
|
console.log(config.ffmpegDir + ' ' + ffmpegArgs.join(' '))
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const ffmpegProcess = spawn(config.ffmpegDir, ffmpegArgs);
|
||||||
|
|
||||||
|
ffmpegProcess.stdout.on('data', (data) => {
|
||||||
|
console.log(`FFmpeg stdout: ${data}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
ffmpegProcess.stderr.on('data', (data) => {
|
||||||
|
console.error(`FFmpeg stderr: ${data}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
ffmpegProcess.on('close', (code) => {
|
||||||
|
if (code === 0) {
|
||||||
|
resolve(outputFilePath);
|
||||||
|
} else {
|
||||||
|
reject(new Error(`FFmpeg process exited with code ${code}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ffmpegProcess.on('error', (err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to re-encode file: ${error.message}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
async function getVideoFrameAsJpeg(filePath, seconds = 7){
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const ffmpegArgs = [
|
||||||
|
'-loglevel', 'warning',
|
||||||
|
'-ss', seconds.toString(),
|
||||||
|
'-i', filePath,
|
||||||
|
'-frames:v', '1',
|
||||||
|
'-q:v', '2',
|
||||||
|
'-f', 'image2pipe',
|
||||||
|
'-vcodec', 'mjpeg',
|
||||||
|
'pipe:1'
|
||||||
|
];
|
||||||
|
const ffmpegProcess = spawn(config.ffmpegDir, ffmpegArgs);
|
||||||
|
let buffer = Buffer.alloc(0);
|
||||||
|
ffmpegProcess.stdout.on('data', (data) => {
|
||||||
|
buffer = Buffer.concat([buffer, data]);
|
||||||
|
});
|
||||||
|
|
||||||
|
ffmpegProcess.stderr.on('data', (data) => {
|
||||||
|
s.debugLog(`getVideoFrameAsJpeg FFmpeg stderr: ${data}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
ffmpegProcess.on('close', (code) => {
|
||||||
|
if (code === 0) {
|
||||||
|
resolve(buffer);
|
||||||
|
} else {
|
||||||
|
reject(new Error(`FFmpeg process exited with code ${code}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ffmpegProcess.on('error', (err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
function getVideoPath(video){
|
||||||
|
const videoPath = path.join(s.getVideoDirectory(video), `${s.formattedTime(video.time)}.${video.ext}`);
|
||||||
|
return videoPath
|
||||||
|
}
|
||||||
|
async function saveVideoFrameToTimelapse(video, secondsIn = 7){
|
||||||
|
// console.error(video)
|
||||||
|
const monitorConfig = s.group[video.ke].rawMonitorConfigurations[video.mid];
|
||||||
|
const activeMonitor = s.group[video.ke].activeMonitors[video.mid];
|
||||||
|
const frameTime = moment(video.time).add(secondsIn, 'seconds');
|
||||||
|
const frameDate = s.formattedTime(frameTime,'YYYY-MM-DD');
|
||||||
|
const timelapseRecordingDirectory = s.getTimelapseFrameDirectory(monitorConfig);
|
||||||
|
const videoPath = getVideoPath(video);
|
||||||
|
const frameFilename = s.formattedTime(frameTime) + '.jpg';
|
||||||
|
const location = timelapseRecordingDirectory + frameDate + '/';
|
||||||
|
const framePath = path.join(location, frameFilename);
|
||||||
|
try{
|
||||||
|
await fsP.stat(framePath)
|
||||||
|
}catch(err){
|
||||||
|
try{
|
||||||
|
const frameBuffer = await getVideoFrameAsJpeg(videoPath, secondsIn);
|
||||||
|
await fsP.mkdir(location, { recursive: true })
|
||||||
|
await fsP.writeFile(framePath, frameBuffer)
|
||||||
|
await s.createTimelapseFrameAndInsert(activeMonitor,location,frameFilename, frameTime._d)
|
||||||
|
}catch(err){
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// console.error('Completed Saving Frame from New Video!', framePath)
|
||||||
|
}
|
||||||
|
function getVideoCodecsFromMonitorConfig(video){
|
||||||
|
const monitorConfig = s.group[video.ke].rawMonitorConfigurations[video.mid];
|
||||||
|
const modeIsRecord = monitorConfig.mode === 'record'
|
||||||
|
let eventBasedVideoCodec = monitorConfig.details.detector_buffer_vcodec
|
||||||
|
let eventBasedAudioCodec = monitorConfig.details.detector_buffer_acodec
|
||||||
|
let recordingVideoCodec = monitorConfig.details.vcodec
|
||||||
|
let recordingAudioCodec = monitorConfig.details.acodec
|
||||||
|
switch(eventBasedVideoCodec){
|
||||||
|
case null:case '':case undefined:case'auto':
|
||||||
|
eventBasedVideoCodec = 'libx264'
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch(eventBasedAudioCodec){
|
||||||
|
case null:case '':case undefined:case'auto':
|
||||||
|
eventBasedAudioCodec = 'aac'
|
||||||
|
break;
|
||||||
|
case'no':
|
||||||
|
eventBasedAudioCodec = null
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch(recordingVideoCodec){
|
||||||
|
case null:case '':case undefined:case'auto':case'default':case'copy':
|
||||||
|
recordingVideoCodec = 'libx264'
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch(recordingAudioCodec){
|
||||||
|
case null:case '':case undefined:case'auto':case'copy':
|
||||||
|
recordingAudioCodec = 'aac'
|
||||||
|
break;
|
||||||
|
case'no':
|
||||||
|
recordingAudioCodec = null
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
videoCodec: modeIsRecord ? recordingVideoCodec : eventBasedVideoCodec,
|
||||||
|
audioCodec: modeIsRecord ? recordingAudioCodec : eventBasedAudioCodec,
|
||||||
|
recordingVideoCodec,
|
||||||
|
recordingAudioCodec,
|
||||||
|
eventBasedVideoCodec,
|
||||||
|
eventBasedAudioCodec,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function postProcessCompletedMp4Video(chosenVideo){
|
||||||
|
try {
|
||||||
|
const video = Object.assign({
|
||||||
|
ext: 'mp4'
|
||||||
|
},chosenVideo);
|
||||||
|
const videoPath = getVideoPath(video);
|
||||||
|
// const moovExists = await hasMoovAtom(videoPath);
|
||||||
|
// if (moovExists) {
|
||||||
|
// s.debugLog('The file already has a moov atom.');
|
||||||
|
// } else {
|
||||||
|
// return true;
|
||||||
|
// // const { videoCodec, audioCodec } = getVideoCodecsFromMonitorConfig(video);
|
||||||
|
// // const tempPath = path.join(s.getVideoDirectory(video), `TEMP_${s.formattedTime(video.time)}.${video.ext}`);
|
||||||
|
// // await addMoovAtom(videoPath, tempPath, videoCodec, audioCodec);
|
||||||
|
// // await moveFile(tempPath, videoPath)
|
||||||
|
// // const newFileSize = (await fsP.stat(videoPath)).size;
|
||||||
|
// // const updateResponse = await s.knexQueryPromise({
|
||||||
|
// // action: "update",
|
||||||
|
// // table: "Videos",
|
||||||
|
// // update: {
|
||||||
|
// // size: newFileSize
|
||||||
|
// // },
|
||||||
|
// // where: [
|
||||||
|
// // ['ke','=',video.ke],
|
||||||
|
// // ['mid','=',video.mid],
|
||||||
|
// // ['time','=',video.time],
|
||||||
|
// // ['end','=',video.end],
|
||||||
|
// // ['ext','=',video.ext],
|
||||||
|
// // ]
|
||||||
|
// // });
|
||||||
|
// }
|
||||||
|
// await saveVideoFrameToTimelapse(video, 0)
|
||||||
|
await saveVideoFrameToTimelapse(video, 7)
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error processing MP4 file:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
return {
|
return {
|
||||||
reEncodeVideoAndReplace,
|
reEncodeVideoAndReplace,
|
||||||
stitchMp4Files,
|
stitchMp4Files,
|
||||||
|
@ -615,5 +989,13 @@ module.exports = (s,config,lang) => {
|
||||||
reEncodeVideoAndBinOriginalAddToQueue,
|
reEncodeVideoAndBinOriginalAddToQueue,
|
||||||
archiveVideo,
|
archiveVideo,
|
||||||
sliceVideo,
|
sliceVideo,
|
||||||
|
mergeVideos,
|
||||||
|
mergeVideosAndBin,
|
||||||
|
saveVideoFrameToTimelapse,
|
||||||
|
postProcessCompletedMp4Video,
|
||||||
|
readChunkForMoov,
|
||||||
|
checkMoovAtBeginning,
|
||||||
|
checkMoovAtEnd,
|
||||||
|
hasMoovAtom
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,9 @@ module.exports = function(s,config,lang){
|
||||||
const {
|
const {
|
||||||
sendVideoToMasterNode,
|
sendVideoToMasterNode,
|
||||||
} = require('./childNode/childUtils.js')(s,config,lang)
|
} = require('./childNode/childUtils.js')(s,config,lang)
|
||||||
|
const {
|
||||||
|
postProcessCompletedMp4Video,
|
||||||
|
} = require('./video/utils.js')(s,config,lang)
|
||||||
/**
|
/**
|
||||||
* Gets the video directory of the supplied video or monitor database row.
|
* Gets the video directory of the supplied video or monitor database row.
|
||||||
* @constructor
|
* @constructor
|
||||||
|
@ -141,7 +144,7 @@ module.exports = function(s,config,lang){
|
||||||
ext: k.ext,
|
ext: k.ext,
|
||||||
size: k.filesize,
|
size: k.filesize,
|
||||||
filesize: k.filesize,
|
filesize: k.filesize,
|
||||||
objects: k.objects,
|
objects: k.objects.substring(0, 510),
|
||||||
time: s.timeObject(k.startTime).format('YYYY-MM-DD HH:mm:ss'),
|
time: s.timeObject(k.startTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
end: s.timeObject(k.endTime).format('YYYY-MM-DD HH:mm:ss')
|
end: s.timeObject(k.endTime).format('YYYY-MM-DD HH:mm:ss')
|
||||||
}
|
}
|
||||||
|
@ -180,8 +183,11 @@ module.exports = function(s,config,lang){
|
||||||
})
|
})
|
||||||
s.insertDatabaseRow(e,k,(err,response) => {
|
s.insertDatabaseRow(e,k,(err,response) => {
|
||||||
if(callback)callback(err,response);
|
if(callback)callback(err,response);
|
||||||
s.insertCompletedVideoExtensions.forEach(function(extender){
|
postProcessCompletedMp4Video(response.insertQuery).then((isGood) => {
|
||||||
extender(e,k,response.insertQuery,response)
|
if(!isGood)return console.error(`FAILED VIDEO INSERT`);
|
||||||
|
s.insertCompletedVideoExtensions.forEach(function(extender){
|
||||||
|
extender(e,k,response.insertQuery,response)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ module.exports = function(s,config,lang,app,io){
|
||||||
spawnSubstreamProcess,
|
spawnSubstreamProcess,
|
||||||
destroySubstreamProcess,
|
destroySubstreamProcess,
|
||||||
removeSenstiveInfoFromMonitorConfig,
|
removeSenstiveInfoFromMonitorConfig,
|
||||||
|
sendSubstreamEvent,
|
||||||
} = require('./monitor/utils.js')(s,config,lang)
|
} = require('./monitor/utils.js')(s,config,lang)
|
||||||
const {
|
const {
|
||||||
sliceVideo,
|
sliceVideo,
|
||||||
|
@ -39,6 +40,7 @@ module.exports = function(s,config,lang,app,io){
|
||||||
reEncodeVideoAndReplace,
|
reEncodeVideoAndReplace,
|
||||||
reEncodeVideoAndBinOriginalAddToQueue,
|
reEncodeVideoAndBinOriginalAddToQueue,
|
||||||
getVideosBasedOnTagFoundInMatrixOfAssociatedEvent,
|
getVideosBasedOnTagFoundInMatrixOfAssociatedEvent,
|
||||||
|
mergeVideosAndBin,
|
||||||
} = require('./video/utils.js')(s,config,lang)
|
} = require('./video/utils.js')(s,config,lang)
|
||||||
s.renderPage = function(req,res,paths,passables,callback){
|
s.renderPage = function(req,res,paths,passables,callback){
|
||||||
passables.window = {}
|
passables.window = {}
|
||||||
|
@ -915,17 +917,39 @@ module.exports = function(s,config,lang,app,io){
|
||||||
const monitorConfig = s.group[groupKey].rawMonitorConfigurations[monitorId]
|
const monitorConfig = s.group[groupKey].rawMonitorConfigurations[monitorId]
|
||||||
const activeMonitor = s.group[groupKey].activeMonitors[monitorId]
|
const activeMonitor = s.group[groupKey].activeMonitors[monitorId]
|
||||||
const substreamConfig = monitorConfig.details.substream
|
const substreamConfig = monitorConfig.details.substream
|
||||||
|
const theAction = req.query.action
|
||||||
if(
|
if(
|
||||||
substreamConfig.output
|
substreamConfig.output
|
||||||
){
|
){
|
||||||
if(!activeMonitor.subStreamProcess){
|
switch(theAction){
|
||||||
response.ok = true
|
case'status':
|
||||||
activeMonitor.allowDestroySubstream = false;
|
response.ok = true
|
||||||
spawnSubstreamProcess(monitorConfig)
|
response.isRunning = !!activeMonitor.subStreamProcess;
|
||||||
}else{
|
response.channel = activeMonitor.subStreamChannel;
|
||||||
activeMonitor.allowDestroySubstream = true
|
break;
|
||||||
await destroySubstreamProcess(activeMonitor)
|
case'stop':
|
||||||
|
activeMonitor.allowDestroySubstream = true
|
||||||
|
await destroySubstreamProcess(activeMonitor)
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if(!activeMonitor.subStreamProcess){
|
||||||
|
response.ok = true
|
||||||
|
activeMonitor.allowDestroySubstream = false;
|
||||||
|
spawnSubstreamProcess(monitorConfig)
|
||||||
|
response.channel = activeMonitor.subStreamChannel;
|
||||||
|
}else{
|
||||||
|
sendSubstreamEvent(groupKey, monitorId)
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
// if(!activeMonitor.subStreamProcess){
|
||||||
|
// response.ok = true
|
||||||
|
// activeMonitor.allowDestroySubstream = false;
|
||||||
|
// spawnSubstreamProcess(monitorConfig)
|
||||||
|
// }else{
|
||||||
|
// activeMonitor.allowDestroySubstream = true
|
||||||
|
// await destroySubstreamProcess(activeMonitor)
|
||||||
|
// }
|
||||||
}else{
|
}else{
|
||||||
response.msg = lang['Invalid Settings']
|
response.msg = lang['Invalid Settings']
|
||||||
}
|
}
|
||||||
|
@ -1970,7 +1994,69 @@ module.exports = function(s,config,lang,app,io){
|
||||||
res.end(s.prettyPrint(response));
|
res.end(s.prettyPrint(response));
|
||||||
})
|
})
|
||||||
},res,req);
|
},res,req);
|
||||||
})
|
});
|
||||||
|
/**
|
||||||
|
* API : Merge Videos and Bin it
|
||||||
|
*/
|
||||||
|
app.post(config.webPaths.apiPrefix+':auth/mergeVideos/:ke/:id', function (req,res){
|
||||||
|
s.auth(req.params, async function(user){
|
||||||
|
const monitorId = req.params.id
|
||||||
|
const groupKey = req.params.ke
|
||||||
|
const {
|
||||||
|
monitorPermissions,
|
||||||
|
monitorRestrictions,
|
||||||
|
} = s.getMonitorsPermitted(user.details,monitorId,'video_view')
|
||||||
|
const {
|
||||||
|
isRestricted,
|
||||||
|
isRestrictedApiKey,
|
||||||
|
apiKeyPermissions,
|
||||||
|
} = s.checkPermission(user);
|
||||||
|
if(
|
||||||
|
isRestrictedApiKey && apiKeyPermissions.watch_videos_disallowed ||
|
||||||
|
isRestricted && (
|
||||||
|
monitorId && !monitorPermissions[`${monitorId}_video_view`] ||
|
||||||
|
monitorRestrictions.length === 0
|
||||||
|
)
|
||||||
|
){
|
||||||
|
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized'], videos: []});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const response = { ok: false }
|
||||||
|
const selectedVideos = s.getPostData(req,'videos');
|
||||||
|
console.log('selected',selectedVideos)
|
||||||
|
if(selectedVideos && selectedVideos.length > 1){
|
||||||
|
const mergedFilePath = await mergeVideosAndBin(selectedVideos);
|
||||||
|
response.ok = !!mergedFilePath;
|
||||||
|
s.closeJsonResponse(res, response);
|
||||||
|
}else{
|
||||||
|
s.sqlQueryBetweenTimesWithPermissions({
|
||||||
|
table: 'Videos',
|
||||||
|
user: user,
|
||||||
|
noCount: true,
|
||||||
|
groupKey,
|
||||||
|
monitorId,
|
||||||
|
startTime: s.getPostData(req,'start'),
|
||||||
|
endTime: s.getPostData(req,'end'),
|
||||||
|
startTimeOperator: s.getPostData(req,'startOperator'),
|
||||||
|
endTimeOperator: s.getPostData(req,'endOperator'),
|
||||||
|
noLimit: s.getPostData(req,'noLimit'),
|
||||||
|
limit: s.getPostData(req,'limit'),
|
||||||
|
archived: s.getPostData(req,'archived'),
|
||||||
|
endIsStartTo: !!s.getPostData(req,'endIsStartTo'),
|
||||||
|
parseRowDetails: false,
|
||||||
|
rowName: 'videos',
|
||||||
|
monitorRestrictions: monitorRestrictions,
|
||||||
|
preliminaryValidationFailed: false
|
||||||
|
}, async ({ videos }) => {
|
||||||
|
if(videos){
|
||||||
|
const mergedFilePath = await mergeVideosAndBin(videos);
|
||||||
|
response.ok = !!mergedFilePath;
|
||||||
|
}
|
||||||
|
s.closeJsonResponse(res, response);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},res,req);
|
||||||
|
});
|
||||||
/**
|
/**
|
||||||
* API : Get Login Tokens
|
* API : Get Login Tokens
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -56,8 +56,11 @@ module.exports = function(__dirname, config){
|
||||||
if(!config.hostPort){config.hostPort = 8082}
|
if(!config.hostPort){config.hostPort = 8082}
|
||||||
if(config.systemLog === undefined){config.systemLog = true}
|
if(config.systemLog === undefined){config.systemLog = true}
|
||||||
if(config.connectionType === undefined)config.connectionType = 'websocket'
|
if(config.connectionType === undefined)config.connectionType = 'websocket'
|
||||||
|
const imageBuffers = {}
|
||||||
s = {
|
s = {
|
||||||
group: {},
|
group: {},
|
||||||
|
monitors: {},
|
||||||
|
monitorInfo: {},
|
||||||
dir: {},
|
dir: {},
|
||||||
isWin: (process.platform === 'win32'),
|
isWin: (process.platform === 'win32'),
|
||||||
s: (json) => {
|
s: (json) => {
|
||||||
|
@ -217,16 +220,26 @@ module.exports = function(__dirname, config){
|
||||||
cn.emit('init',{ok:true,plug:config.plug,notice:config.notice,type:config.type})
|
cn.emit('init',{ok:true,plug:config.plug,notice:config.notice,type:config.type})
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case'init_monitor':
|
case'monitorUpdate':
|
||||||
retryConnection = 0
|
var monitorConfig = d.monitorConfig;
|
||||||
if(s.group[d.ke] && s.group[d.ke][d.id]){
|
var groupKey = monitorConfig.ke;
|
||||||
s.group[d.ke][d.id].numberOfTriggers = 0
|
var monitorId = monitorConfig.mid;
|
||||||
delete(s.group[d.ke][d.id].cords)
|
var monitorDetails = monitorConfig.details;
|
||||||
delete(s.group[d.ke][d.id].buffer)
|
var monitorKey = `${groupKey}${monitorId}`
|
||||||
s.onCameraInitExtensions.forEach((extender) => {
|
if(!s.monitors[monitorKey])s.monitors[monitorKey] = Object.assign({}, monitorConfig);
|
||||||
extender(d,cn,tx)
|
var isObjectDetectionSeparate = monitorDetails.detector_use_detect_object === '1'
|
||||||
})
|
var width = parseFloat(isObjectDetectionSeparate && monitorDetails.detector_scale_x_object ? monitorDetails.detector_scale_x_object : monitorDetails.detector_scale_x)
|
||||||
|
var height = parseFloat(isObjectDetectionSeparate && monitorDetails.detector_scale_y_object ? monitorDetails.detector_scale_y_object : monitorDetails.detector_scale_y)
|
||||||
|
s.monitorInfo[monitorKey] = {
|
||||||
|
isObjectDetectionSeparate,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
}
|
}
|
||||||
|
delete(imageBuffers[monitorKey])
|
||||||
|
for(extender of s.onCameraInitExtensions){
|
||||||
|
extender(monitorConfig, cn, tx)
|
||||||
|
}
|
||||||
|
// console.log(monitorId, 'registered', s.monitorInfo[monitorKey])
|
||||||
break;
|
break;
|
||||||
case'frameFromRam':
|
case'frameFromRam':
|
||||||
if(!s.group[d.ke]){
|
if(!s.group[d.ke]){
|
||||||
|
@ -239,29 +252,22 @@ module.exports = function(__dirname, config){
|
||||||
break;
|
break;
|
||||||
case'frame':
|
case'frame':
|
||||||
try{
|
try{
|
||||||
if(!s.group[d.ke]){
|
const monitorKey = `${d.id}${d.ke}`;
|
||||||
s.group[d.ke]={}
|
imageBuffers[monitorKey]
|
||||||
}
|
if(!imageBuffers[monitorKey]){
|
||||||
if(!s.group[d.ke][d.id]){
|
imageBuffers[monitorKey] = [d.frame];
|
||||||
s.group[d.ke][d.id] = {}
|
|
||||||
s.onCameraInitExtensions.forEach((extender) => {
|
|
||||||
extender(d,cn,tx)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if(!s.group[d.ke][d.id].buffer){
|
|
||||||
s.group[d.ke][d.id].buffer = [d.frame];
|
|
||||||
}else{
|
}else{
|
||||||
s.group[d.ke][d.id].buffer.push(d.frame)
|
imageBuffers[monitorKey].push(d.frame)
|
||||||
}
|
}
|
||||||
if(d.frame[d.frame.length-2] === 0xFF && d.frame[d.frame.length-1] === 0xD9){
|
if(d.frame[d.frame.length-2] === 0xFF && d.frame[d.frame.length-1] === 0xD9){
|
||||||
var buffer = Buffer.concat(s.group[d.ke][d.id].buffer);
|
var buffer = Buffer.concat(imageBuffers[monitorKey]);
|
||||||
processImage(buffer,d,tx)
|
processImage(buffer,d,tx)
|
||||||
s.group[d.ke][d.id].buffer = null
|
imageBuffers[monitorKey] = null
|
||||||
}
|
}
|
||||||
}catch(err){
|
}catch(err){
|
||||||
if(err){
|
if(err){
|
||||||
s.systemLog(err)
|
s.systemLog(err)
|
||||||
delete(s.group[d.ke][d.id].buffer)
|
delete(imageBuffers[monitorKey])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -32,8 +32,11 @@ module.exports = function(__dirname, config){
|
||||||
if(!config.hostPort){config.hostPort = 8082}
|
if(!config.hostPort){config.hostPort = 8082}
|
||||||
if(config.systemLog === undefined){config.systemLog = true}
|
if(config.systemLog === undefined){config.systemLog = true}
|
||||||
if(config.connectionType === undefined)config.connectionType = 'websocket'
|
if(config.connectionType === undefined)config.connectionType = 'websocket'
|
||||||
|
const imageBuffers = {}
|
||||||
s = {
|
s = {
|
||||||
group: {},
|
group: {},
|
||||||
|
monitors: {},
|
||||||
|
monitorInfo: {},
|
||||||
dir: {},
|
dir: {},
|
||||||
isWin: (process.platform === 'win32'),
|
isWin: (process.platform === 'win32'),
|
||||||
s: (json) => {
|
s: (json) => {
|
||||||
|
@ -192,16 +195,26 @@ module.exports = function(__dirname, config){
|
||||||
cn.emit('init',{ok:true,plug:config.plug,notice:config.notice,type:config.type})
|
cn.emit('init',{ok:true,plug:config.plug,notice:config.notice,type:config.type})
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case'init_monitor':
|
case'monitorUpdate':
|
||||||
retryConnection = 0
|
var monitorConfig = d.monitorConfig;
|
||||||
if(s.group[d.ke] && s.group[d.ke][d.id]){
|
var groupKey = monitorConfig.ke;
|
||||||
s.group[d.ke][d.id].numberOfTriggers = 0
|
var monitorId = monitorConfig.mid;
|
||||||
delete(s.group[d.ke][d.id].cords)
|
var monitorDetails = monitorConfig.details;
|
||||||
delete(s.group[d.ke][d.id].buffer)
|
var monitorKey = `${groupKey}${monitorId}`
|
||||||
s.onCameraInitExtensions.forEach((extender) => {
|
if(!s.monitors[monitorKey])s.monitors[monitorKey] = Object.assign({}, monitorConfig);
|
||||||
extender(d,cn,tx)
|
var isObjectDetectionSeparate = monitorDetails.detector_use_detect_object === '1'
|
||||||
})
|
var width = parseFloat(isObjectDetectionSeparate && monitorDetails.detector_scale_x_object ? monitorDetails.detector_scale_x_object : monitorDetails.detector_scale_x)
|
||||||
|
var height = parseFloat(isObjectDetectionSeparate && monitorDetails.detector_scale_y_object ? monitorDetails.detector_scale_y_object : monitorDetails.detector_scale_y)
|
||||||
|
s.monitorInfo[monitorKey] = {
|
||||||
|
isObjectDetectionSeparate,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
}
|
}
|
||||||
|
delete(imageBuffers[monitorKey])
|
||||||
|
for(extender of s.onCameraInitExtensions){
|
||||||
|
extender(monitorConfig, cn, tx)
|
||||||
|
}
|
||||||
|
// console.log(monitorId, 'registered', s.monitorInfo[monitorKey])
|
||||||
break;
|
break;
|
||||||
case'frameFromRam':
|
case'frameFromRam':
|
||||||
if(!s.group[d.ke]){
|
if(!s.group[d.ke]){
|
||||||
|
@ -214,29 +227,22 @@ module.exports = function(__dirname, config){
|
||||||
break;
|
break;
|
||||||
case'frame':
|
case'frame':
|
||||||
try{
|
try{
|
||||||
if(!s.group[d.ke]){
|
const monitorKey = `${d.id}${d.ke}`;
|
||||||
s.group[d.ke]={}
|
imageBuffers[monitorKey]
|
||||||
}
|
if(!imageBuffers[monitorKey]){
|
||||||
if(!s.group[d.ke][d.id]){
|
imageBuffers[monitorKey] = [d.frame];
|
||||||
s.group[d.ke][d.id] = {}
|
|
||||||
s.onCameraInitExtensions.forEach((extender) => {
|
|
||||||
extender(d,cn,tx)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if(!s.group[d.ke][d.id].buffer){
|
|
||||||
s.group[d.ke][d.id].buffer = [d.frame];
|
|
||||||
}else{
|
}else{
|
||||||
s.group[d.ke][d.id].buffer.push(d.frame)
|
imageBuffers[monitorKey].push(d.frame)
|
||||||
}
|
}
|
||||||
if(d.frame[d.frame.length-2] === 0xFF && d.frame[d.frame.length-1] === 0xD9){
|
if(d.frame[d.frame.length-2] === 0xFF && d.frame[d.frame.length-1] === 0xD9){
|
||||||
var buffer = Buffer.concat(s.group[d.ke][d.id].buffer);
|
var buffer = Buffer.concat(imageBuffers[monitorKey]);
|
||||||
processImage(buffer,d,tx)
|
processImage(buffer,d,tx)
|
||||||
s.group[d.ke][d.id].buffer = null
|
imageBuffers[monitorKey] = null
|
||||||
}
|
}
|
||||||
}catch(err){
|
}catch(err){
|
||||||
if(err){
|
if(err){
|
||||||
s.systemLog(err)
|
s.systemLog(err)
|
||||||
delete(s.group[d.ke][d.id].buffer)
|
delete(imageBuffers[monitorKey])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const fetch = require('node-fetch');
|
const fetch = require('node-fetch');
|
||||||
const AdmZip = require('adm-zip');
|
const unzipper = require('unzipper');
|
||||||
const fs = require('fs').promises;
|
const fs = require('fs').promises;
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
|
@ -21,13 +21,14 @@ async function fetchAndDownloadFolder(folderName) {
|
||||||
throw new Error(`Failed to download folder: ${response.statusText}`);
|
throw new Error(`Failed to download folder: ${response.statusText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const buffer = await response.buffer();
|
// Create a temporary path for extraction
|
||||||
const zip = new AdmZip(buffer);
|
|
||||||
|
|
||||||
// Extract the ZIP to a temporary location
|
|
||||||
const tempExtractPath = path.join(process.cwd(), 'temp_extracted');
|
const tempExtractPath = path.join(process.cwd(), 'temp_extracted');
|
||||||
await fs.mkdir(tempExtractPath, { recursive: true });
|
await fs.mkdir(tempExtractPath, { recursive: true });
|
||||||
zip.extractAllTo(tempExtractPath, true);
|
|
||||||
|
// Extract the ZIP using unzipper
|
||||||
|
await response.body
|
||||||
|
.pipe(unzipper.Extract({ path: tempExtractPath }))
|
||||||
|
.promise();
|
||||||
|
|
||||||
// Find the folder ending with the target name
|
// Find the folder ending with the target name
|
||||||
const extractedFolder = (await fs.readdir(tempExtractPath)).find(dir => dir.endsWith(folderName));
|
const extractedFolder = (await fs.readdir(tempExtractPath)).find(dir => dir.endsWith(folderName));
|
||||||
|
@ -66,3 +67,5 @@ if (!folderName) {
|
||||||
console.error('Usage: node script.js <folderName>');
|
console.error('Usage: node script.js <folderName>');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fetchAndDownloadFolder(folderName);
|
||||||
|
|
|
@ -12,3 +12,6 @@
|
||||||
border: 1px solid #009dff;
|
border: 1px solid #009dff;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
#superPluginManager .badge-success {
|
||||||
|
background-color: #2fa523;
|
||||||
|
}
|
||||||
|
|
|
@ -1102,3 +1102,11 @@ function makeButton({ color, link, text, class: classes}){
|
||||||
function replaceBrokenImage(_this){
|
function replaceBrokenImage(_this){
|
||||||
$(_this).attr('src', `${libURL}/libs/img/bg.jpg`)
|
$(_this).attr('src', `${libURL}/libs/img/bg.jpg`)
|
||||||
}
|
}
|
||||||
|
function getQueryString(){
|
||||||
|
var theObject = {}
|
||||||
|
location.search.substring(1).split('&').forEach(function(string){
|
||||||
|
var parts = string.split('=')
|
||||||
|
theObject[parts[0]] = parts[1]
|
||||||
|
})
|
||||||
|
return theObject
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ var loadedLiveGrids = {}
|
||||||
var monitorPops = {}
|
var monitorPops = {}
|
||||||
var liveGridElements = {}
|
var liveGridElements = {}
|
||||||
var runningJpegStreams = {}
|
var runningJpegStreams = {}
|
||||||
|
var containerElement = $(`#monitors_live`)
|
||||||
var liveGrid = $('#monitors_live .stream-element-container')
|
var liveGrid = $('#monitors_live .stream-element-container')
|
||||||
var websocketPath = checkCorrectPathEnding(urlPrefix) + 'socket.io'
|
var websocketPath = checkCorrectPathEnding(urlPrefix) + 'socket.io'
|
||||||
//
|
//
|
||||||
|
@ -75,7 +76,7 @@ function buildLiveGridBlock(monitor){
|
||||||
var monitorDetails = safeJsonParse(monitor.details)
|
var monitorDetails = safeJsonParse(monitor.details)
|
||||||
var monitorLiveId = `monitor_live_${monitor.mid}`
|
var monitorLiveId = `monitor_live_${monitor.mid}`
|
||||||
var subStreamChannel = monitor.subStreamChannel
|
var subStreamChannel = monitor.subStreamChannel
|
||||||
var streamType = subStreamChannel ? monitorDetails.substream ? monitorDetails.substream.output.stream_type : 'hls' : monitorDetails.stream_type
|
var streamType = monitorDetails.stream_type === 'useSubstream' ? monitorDetails.substream.output.stream_type : monitorDetails.stream_type
|
||||||
var streamElement = buildStreamElementHtml(streamType)
|
var streamElement = buildStreamElementHtml(streamType)
|
||||||
var streamBlockInfo = definitions['Monitor Stream Window']
|
var streamBlockInfo = definitions['Monitor Stream Window']
|
||||||
if(!loadedLiveGrids[monitor.mid])loadedLiveGrids[monitor.mid] = {}
|
if(!loadedLiveGrids[monitor.mid])loadedLiveGrids[monitor.mid] = {}
|
||||||
|
@ -99,21 +100,18 @@ function buildLiveGridBlock(monitor){
|
||||||
|
|
||||||
function drawLiveGridBlock(monitorConfig,subStreamChannel){
|
function drawLiveGridBlock(monitorConfig,subStreamChannel){
|
||||||
var monitorId = monitorConfig.mid
|
var monitorId = monitorConfig.mid
|
||||||
if($('#monitor_live_' + monitorId).length === 0){
|
var html = buildLiveGridBlock(monitorConfig)
|
||||||
var html = buildLiveGridBlock(monitorConfig)
|
liveGrid.html(html);
|
||||||
liveGrid.html(html);
|
var theBlock = $('#monitor_live_' + monitorId);
|
||||||
console.log(liveGrid.length,html)
|
var streamElement = theBlock.find('.stream-element')
|
||||||
var theBlock = $('#monitor_live_' + monitorId);
|
liveGridElements[monitorId] = {
|
||||||
var streamElement = theBlock.find('.stream-element')
|
monitorItem: theBlock,
|
||||||
liveGridElements[monitorId] = {
|
streamElement: streamElement,
|
||||||
monitorItem: theBlock,
|
eventObjects: theBlock.find('.stream-objects'),
|
||||||
streamElement: streamElement,
|
motionMeter: theBlock.find('.indifference .progress-bar'),
|
||||||
eventObjects: theBlock.find('.stream-objects'),
|
motionMeterText: theBlock.find('.indifference .progress-bar span'),
|
||||||
motionMeter: theBlock.find('.indifference .progress-bar'),
|
width: streamElement.width(),
|
||||||
motionMeterText: theBlock.find('.indifference .progress-bar span'),
|
height: streamElement.height(),
|
||||||
width: streamElement.width(),
|
|
||||||
height: streamElement.height(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
initiateLiveGridPlayer(loadedMonitors[monitorId],subStreamChannel)
|
initiateLiveGridPlayer(loadedMonitors[monitorId],subStreamChannel)
|
||||||
}
|
}
|
||||||
|
@ -131,15 +129,15 @@ function unmuteVideoPlayer(){
|
||||||
},3000)
|
},3000)
|
||||||
$('.unmute-embed-audio').remove()
|
$('.unmute-embed-audio').remove()
|
||||||
}
|
}
|
||||||
function initiateLiveGridPlayer(monitor,subStreamChannel){
|
function initiateLiveGridPlayer(monitor){
|
||||||
var livePlayerElement = loadedLiveGrids[monitor.mid]
|
var livePlayerElement = loadedLiveGrids[monitor.mid]
|
||||||
var details = monitor.details
|
var details = monitor.details
|
||||||
var groupKey = monitor.ke
|
var groupKey = monitor.ke
|
||||||
var monitorId = monitor.mid
|
var monitorId = monitor.mid
|
||||||
var loadedMonitor = loadedMonitors[monitorId]
|
var loadedMonitor = loadedMonitors[monitorId]
|
||||||
var loadedPlayer = loadedLiveGrids[monitor.mid]
|
var loadedPlayer = loadedLiveGrids[monitor.mid]
|
||||||
var containerElement = $(`#monitor_live_${monitor.mid}`)
|
var subStreamChannel = loadedMonitor.subStreamChannel
|
||||||
var streamType = subStreamChannel ? details.substream ? details.substream.output.stream_type : 'hls' : details.stream_type
|
var streamType = details.stream_type === 'useSubstream' ? details.substream.output.stream_type : details.stream_type
|
||||||
switch(streamType){
|
switch(streamType){
|
||||||
case'jpeg':
|
case'jpeg':
|
||||||
startJpegStream(monitorId)
|
startJpegStream(monitorId)
|
||||||
|
@ -542,10 +540,20 @@ function toggleSubStream(monitorId,callback){
|
||||||
}
|
}
|
||||||
if(monitor.subStreamToggleLock)return false;
|
if(monitor.subStreamToggleLock)return false;
|
||||||
monitor.subStreamToggleLock = true
|
monitor.subStreamToggleLock = true
|
||||||
$.getJSON(getApiPrefix() + '/toggleSubstream/'+$user.ke+'/'+monitorId,function(d){
|
var substreamUrl = getApiPrefix() + '/toggleSubstream/'+$user.ke+'/'+monitorId;
|
||||||
monitor.subStreamToggleLock = false
|
$.getJSON(`${substreamUrl}?action=status`,function(response){
|
||||||
debugLog(d)
|
if(!response.isRunning){
|
||||||
if(callback)callback()
|
$.getJSON(substreamUrl,function(d){
|
||||||
|
monitor.subStreamChannel = d.channel
|
||||||
|
monitor.subStreamToggleLock = false
|
||||||
|
debugLog(d)
|
||||||
|
if(callback)callback()
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
monitor.subStreamChannel = response.channel
|
||||||
|
monitor.subStreamToggleLock = false
|
||||||
|
if(callback)callback()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
$(document).ready(function(e){
|
$(document).ready(function(e){
|
||||||
|
@ -595,8 +603,8 @@ $(document).ready(function(e){
|
||||||
case'monitor_watch_on':
|
case'monitor_watch_on':
|
||||||
var monitorId = d.mid || d.id
|
var monitorId = d.mid || d.id
|
||||||
var loadedMonitor = loadedMonitors[monitorId]
|
var loadedMonitor = loadedMonitors[monitorId]
|
||||||
var subStreamChannel = d.subStreamChannel
|
var subStreamChannel = loadedMonitor.subStreamChannel || d.channel
|
||||||
if(!loadedMonitor.subStreamChannel && loadedMonitor.details.stream_type === 'useSubstream'){
|
if(loadedMonitor.details.stream_type === 'useSubstream'){
|
||||||
toggleSubStream(monitorId,function(){
|
toggleSubStream(monitorId,function(){
|
||||||
drawLiveGridBlock(loadedMonitors[monitorId],subStreamChannel)
|
drawLiveGridBlock(loadedMonitors[monitorId],subStreamChannel)
|
||||||
})
|
})
|
||||||
|
@ -619,7 +627,6 @@ $(document).ready(function(e){
|
||||||
$('body').addClass('jpegMode')
|
$('body').addClass('jpegMode')
|
||||||
break;
|
break;
|
||||||
case'detector_trigger':
|
case'detector_trigger':
|
||||||
console.log(d)
|
|
||||||
var monitorId = d.id
|
var monitorId = d.id
|
||||||
var liveGridElement = liveGridElements[monitorId]
|
var liveGridElement = liveGridElements[monitorId]
|
||||||
if(!window.dontShowDetection && liveGridElement){
|
if(!window.dontShowDetection && liveGridElement){
|
||||||
|
|
|
@ -73,3 +73,11 @@ function drawMatrices(event,options){
|
||||||
})
|
})
|
||||||
theContainer.append(html)
|
theContainer.append(html)
|
||||||
}
|
}
|
||||||
|
function getQueryString(){
|
||||||
|
var theObject = {}
|
||||||
|
location.search.substring(1).split('&').forEach(function(string){
|
||||||
|
var parts = string.split('=')
|
||||||
|
theObject[parts[0]] = parts[1]
|
||||||
|
})
|
||||||
|
return theObject
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ var monSectionPresets = $('#monSectionPresets')
|
||||||
var copySettingsSelector = $('#copy_settings')
|
var copySettingsSelector = $('#copy_settings')
|
||||||
var monitorPresetsSelection = $('#monitorPresetsSelection')
|
var monitorPresetsSelection = $('#monitorPresetsSelection')
|
||||||
var monitorPresetsNameField = $('#monitorPresetsName')
|
var monitorPresetsNameField = $('#monitorPresetsName')
|
||||||
|
var detectorsSelected = $('#detectorsSelected')
|
||||||
var monitorsList = monitorEditorWindow.find('.monitors_list')
|
var monitorsList = monitorEditorWindow.find('.monitors_list')
|
||||||
var editorForm = monitorEditorWindow.find('form')
|
var editorForm = monitorEditorWindow.find('form')
|
||||||
var tagsInput = monitorEditorWindow.find('[name="tags"]')
|
var tagsInput = monitorEditorWindow.find('[name="tags"]')
|
||||||
|
@ -566,7 +567,37 @@ function drawInputMapSelectorHtml(options,parent){
|
||||||
</div>`
|
</div>`
|
||||||
parent.prepend(html)
|
parent.prepend(html)
|
||||||
}
|
}
|
||||||
function importIntoMonitorEditor(options){
|
function getPluginsList(monitorConfig){
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const chosenDetectors = safeJsonParse(monitorConfig.details).detectors_selected || [];
|
||||||
|
$.get(getApiPrefix() + '/plugins/list',function(data){
|
||||||
|
var plugins = data.plugins || {};
|
||||||
|
var pluginNames = Object.keys(plugins)
|
||||||
|
var disconnectedPlugins = chosenDetectors.filter(item => !pluginNames.includes(item));
|
||||||
|
var html = createOptionHtml({
|
||||||
|
value: 'all',
|
||||||
|
label: `${lang.All} (${lang.Default})`
|
||||||
|
});
|
||||||
|
$.each(plugins, function(name, pluginInfo){
|
||||||
|
html += createOptionHtml({
|
||||||
|
value: name,
|
||||||
|
label: name,
|
||||||
|
selected: chosenDetectors.includes(name),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
$.each(disconnectedPlugins, function(n, name){
|
||||||
|
html += createOptionHtml({
|
||||||
|
value: name,
|
||||||
|
label: `${name} (${lang.Disconnected})`,
|
||||||
|
selected: true,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
detectorsSelected.html(html)
|
||||||
|
resolve(plugins)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
async function importIntoMonitorEditor(options){
|
||||||
var monitorConfig = options.values || options
|
var monitorConfig = options.values || options
|
||||||
var monitorId = monitorConfig.mid
|
var monitorId = monitorConfig.mid
|
||||||
var monitorDetails = safeJsonParse(monitorConfig.details);
|
var monitorDetails = safeJsonParse(monitorConfig.details);
|
||||||
|
@ -686,6 +717,9 @@ function importIntoMonitorEditor(options){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
//
|
||||||
|
await getPluginsList(monitorConfig)
|
||||||
|
//
|
||||||
copySettingsSelector.val('0').change()
|
copySettingsSelector.val('0').change()
|
||||||
var tmp = '';
|
var tmp = '';
|
||||||
$.each(loadedMonitors,function(n,monitor){
|
$.each(loadedMonitors,function(n,monitor){
|
||||||
|
@ -1319,9 +1353,11 @@ editorForm.find('[name="type"]').change(function(e){
|
||||||
break;
|
break;
|
||||||
case'detector_plugged':
|
case'detector_plugged':
|
||||||
addDetectorPlugin(d.plug,d)
|
addDetectorPlugin(d.plug,d)
|
||||||
|
if(monitorEditorSelectedMonitor)getPluginsList(monitorEditorSelectedMonitor);
|
||||||
break;
|
break;
|
||||||
case'detector_unplugged':
|
case'detector_unplugged':
|
||||||
removeDetectorPlugin(d.plug)
|
removeDetectorPlugin(d.plug)
|
||||||
|
if(monitorEditorSelectedMonitor)getPluginsList(monitorEditorSelectedMonitor);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1335,6 +1371,7 @@ editorForm.find('[name="type"]').change(function(e){
|
||||||
drawMonitorListToSelector(monitorsList.find('optgroup'),false,'host')
|
drawMonitorListToSelector(monitorsList.find('optgroup'),false,'host')
|
||||||
monitorsList.val(theSelected)
|
monitorsList.val(theSelected)
|
||||||
checkToOpenSideMenu()
|
checkToOpenSideMenu()
|
||||||
|
if(monitorEditorSelectedMonitor)getPluginsList(monitorEditorSelectedMonitor)
|
||||||
}
|
}
|
||||||
addOnTabAway('monitorSettings', function(){
|
addOnTabAway('monitorSettings', function(){
|
||||||
if(isSideBarMenuCollapsed()){
|
if(isSideBarMenuCollapsed()){
|
||||||
|
|
|
@ -1168,6 +1168,9 @@ function getRunningMonitors(asArray){
|
||||||
})
|
})
|
||||||
return asArray ? Object.values(foundMonitors) : foundMonitors
|
return asArray ? Object.values(foundMonitors) : foundMonitors
|
||||||
}
|
}
|
||||||
|
function buildFileBinUrl(data){
|
||||||
|
return apiBaseUrl + '/fileBin/' + data.ke + '/' + data.mid + '/' + data.name
|
||||||
|
}
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
$('body')
|
$('body')
|
||||||
.on('click','[system]',function(){
|
.on('click','[system]',function(){
|
||||||
|
|
|
@ -311,9 +311,6 @@ $(document).ready(function(e){
|
||||||
function downloadTimelapseFrame(frame){
|
function downloadTimelapseFrame(frame){
|
||||||
downloadFile(frame.href,frame.filename)
|
downloadFile(frame.href,frame.filename)
|
||||||
}
|
}
|
||||||
function buildFileBinUrl(data){
|
|
||||||
return apiBaseUrl + '/fileBin/' + data.ke + '/' + data.mid + '/' + data.name
|
|
||||||
}
|
|
||||||
function downloadTimelapseVideo(data){
|
function downloadTimelapseVideo(data){
|
||||||
var downloadUrl = buildFileBinUrl(data)
|
var downloadUrl = buildFileBinUrl(data)
|
||||||
downloadFile(downloadUrl,data.name)
|
downloadFile(downloadUrl,data.name)
|
||||||
|
|
|
@ -442,35 +442,94 @@ function loadEventsData(videoEvents){
|
||||||
loadedEventsInMemory[`${anEvent.mid}${anEvent.time}`] = anEvent
|
loadedEventsInMemory[`${anEvent.mid}${anEvent.time}`] = anEvent
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
function getVideoSearchRequestQueries(options){
|
||||||
|
var searchQuery = options.searchQuery
|
||||||
|
var requestQueries = []
|
||||||
|
var monitorId = options.monitorId
|
||||||
|
var archived = options.archived
|
||||||
|
var customVideoSet = options.customVideoSet
|
||||||
|
var limit = options.limit
|
||||||
|
var eventLimit = options.eventLimit || 300
|
||||||
|
var doLimitOnFames = options.doLimitOnFames || false
|
||||||
|
var eventStartTime
|
||||||
|
var eventEndTime
|
||||||
|
if(options.startDate){
|
||||||
|
eventStartTime = formattedTimeForFilename(options.startDate,false)
|
||||||
|
requestQueries.push(`start=${eventStartTime}`)
|
||||||
|
}
|
||||||
|
if(options.endDate){
|
||||||
|
eventEndTime = formattedTimeForFilename(options.endDate,false)
|
||||||
|
requestQueries.push(`end=${eventEndTime}`)
|
||||||
|
}
|
||||||
|
if(searchQuery){
|
||||||
|
requestQueries.push(`search=${searchQuery}`)
|
||||||
|
}
|
||||||
|
if(archived){
|
||||||
|
requestQueries.push(`archived=1`)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
searchQuery,
|
||||||
|
monitorId,
|
||||||
|
archived,
|
||||||
|
customVideoSet,
|
||||||
|
limit,
|
||||||
|
eventLimit,
|
||||||
|
doLimitOnFames,
|
||||||
|
eventStartTime,
|
||||||
|
eventEndTime,
|
||||||
|
requestQueries,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function mergeVideosAndBin(options,callback){
|
||||||
|
const {
|
||||||
|
searchQuery,
|
||||||
|
monitorId,
|
||||||
|
archived,
|
||||||
|
customVideoSet,
|
||||||
|
limit,
|
||||||
|
eventLimit,
|
||||||
|
doLimitOnFames,
|
||||||
|
eventStartTime,
|
||||||
|
eventEndTime,
|
||||||
|
requestQueries,
|
||||||
|
} = getVideoSearchRequestQueries(options);
|
||||||
|
const videos = options.videos.map(video => {
|
||||||
|
const newVideo = {
|
||||||
|
ke: video.ke,
|
||||||
|
mid: video.mid,
|
||||||
|
time: video.time,
|
||||||
|
end: video.end,
|
||||||
|
ext: video.ext,
|
||||||
|
saveDir: video.saveDir,
|
||||||
|
details: video.details,
|
||||||
|
};
|
||||||
|
delete(newVideo.timelapseFrames)
|
||||||
|
return newVideo
|
||||||
|
});
|
||||||
|
console.log(videos)
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
$.post(`${getApiPrefix(`mergeVideos`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([limit ? `limit=${limit}` : `noLimit=1`]).join('&')}`, {
|
||||||
|
videos,
|
||||||
|
},function(data){
|
||||||
|
resolve(data)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
function getVideos(options,callback,noEvents){
|
function getVideos(options,callback,noEvents){
|
||||||
return new Promise((resolve,reject) => {
|
return new Promise((resolve,reject) => {
|
||||||
options = options ? options : {}
|
options = options ? options : {}
|
||||||
var searchQuery = options.searchQuery
|
const {
|
||||||
var requestQueries = []
|
searchQuery,
|
||||||
var monitorId = options.monitorId
|
monitorId,
|
||||||
var archived = options.archived
|
archived,
|
||||||
var customVideoSet = options.customVideoSet
|
customVideoSet,
|
||||||
var limit = options.limit
|
limit,
|
||||||
var eventLimit = options.eventLimit || 300
|
eventLimit,
|
||||||
var doLimitOnFames = options.doLimitOnFames || false
|
doLimitOnFames,
|
||||||
var eventStartTime
|
eventStartTime,
|
||||||
var eventEndTime
|
eventEndTime,
|
||||||
// var startDate = options.startDate
|
requestQueries,
|
||||||
// var endDate = options.endDate
|
} = getVideoSearchRequestQueries(options);
|
||||||
if(options.startDate){
|
|
||||||
eventStartTime = formattedTimeForFilename(options.startDate,false)
|
|
||||||
requestQueries.push(`start=${eventStartTime}`)
|
|
||||||
}
|
|
||||||
if(options.endDate){
|
|
||||||
eventEndTime = formattedTimeForFilename(options.endDate,false)
|
|
||||||
requestQueries.push(`end=${eventEndTime}`)
|
|
||||||
}
|
|
||||||
if(searchQuery){
|
|
||||||
requestQueries.push(`search=${searchQuery}`)
|
|
||||||
}
|
|
||||||
if(archived){
|
|
||||||
requestQueries.push(`archived=1`)
|
|
||||||
}
|
|
||||||
$.getJSON(`${getApiPrefix(customVideoSet ? customVideoSet : searchQuery ? `videosByEventTag` : `videos`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([limit ? `limit=${limit}` : `noLimit=1`]).join('&')}`,function(data){
|
$.getJSON(`${getApiPrefix(customVideoSet ? customVideoSet : searchQuery ? `videosByEventTag` : `videos`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([limit ? `limit=${limit}` : `noLimit=1`]).join('&')}`,function(data){
|
||||||
var videos = data.videos.map((video) => {
|
var videos = data.videos.map((video) => {
|
||||||
return Object.assign({},video,{
|
return Object.assign({},video,{
|
||||||
|
|
|
@ -189,7 +189,7 @@ $(document).ready(function(e){
|
||||||
<div>${timeAgo(file.time)}</div>
|
<div>${timeAgo(file.time)}</div>
|
||||||
<div><small><b>${lang.Start} :</b> ${formattedTime(file.time, 'DD-MM-YYYY hh:mm:ss AA')}</small></div>
|
<div><small><b>${lang.Start} :</b> ${formattedTime(file.time, 'DD-MM-YYYY hh:mm:ss AA')}</small></div>
|
||||||
<div><small><b>${lang.End} :</b> ${formattedTime(file.end, 'DD-MM-YYYY hh:mm:ss AA')}</small></div>`,
|
<div><small><b>${lang.End} :</b> ${formattedTime(file.end, 'DD-MM-YYYY hh:mm:ss AA')}</small></div>`,
|
||||||
objects: file.objects,
|
objects: `<div style="word-break: break-word;max-width:125px;">${file.objects}</div>`,
|
||||||
tags: `
|
tags: `
|
||||||
${file.ext ? `<span class="badge badge-${file.ext ==='webm' ? `primary` : 'danger'}">${file.ext}</span>` : ''}
|
${file.ext ? `<span class="badge badge-${file.ext ==='webm' ? `primary` : 'danger'}">${file.ext}</span>` : ''}
|
||||||
${!isLocalVideo ? `<span class="badge badge-success">${file.type}</span>` : ''}
|
${!isLocalVideo ? `<span class="badge badge-success">${file.type}</span>` : ''}
|
||||||
|
@ -274,6 +274,43 @@ $(document).ready(function(e){
|
||||||
var downloadUrl = buildNewFileLink(data)
|
var downloadUrl = buildNewFileLink(data)
|
||||||
downloadFile(downloadUrl,data.name)
|
downloadFile(downloadUrl,data.name)
|
||||||
}
|
}
|
||||||
|
function mergeSelectedVideos(){
|
||||||
|
var videos = getSelectedRows(true)
|
||||||
|
var dateRange = getSelectedTime(dateSelector);
|
||||||
|
var searchQuery = objectTagSearchField.val() || null;
|
||||||
|
var startDate = dateRange.startDate;
|
||||||
|
var endDate = dateRange.endDate;
|
||||||
|
var monitorId = monitorsList.val();
|
||||||
|
var wantsArchivedVideo = getVideoSetSelected() === 'archive';
|
||||||
|
if(!monitorId){
|
||||||
|
new PNotify({
|
||||||
|
title: lang['No Monitor Selected'],
|
||||||
|
text: lang['No Monitor Found, Ignoring Request'],
|
||||||
|
type: 'danger',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
$.confirm.create({
|
||||||
|
title: lang["Merge Videos"],
|
||||||
|
body: `${videos.length > 0 ? lang.MergeAllSelected : lang.MergeAllInRange}`,
|
||||||
|
clickOptions: {
|
||||||
|
title: '<i class="fa fa-check"></i> ' + lang.Save,
|
||||||
|
class: 'btn-success btn-sm'
|
||||||
|
},
|
||||||
|
clickCallback: async function(){
|
||||||
|
console.log('Merging Video...')
|
||||||
|
var result = await mergeVideosAndBin({
|
||||||
|
monitorId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
videos,
|
||||||
|
searchQuery,
|
||||||
|
archived: wantsArchivedVideo,
|
||||||
|
});
|
||||||
|
console.log('Merged Video! Check Filebin.')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
$('body')
|
$('body')
|
||||||
.on('click','.open-videosTable',function(e){
|
.on('click','.open-videosTable',function(e){
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@ -307,6 +344,11 @@ $(document).ready(function(e){
|
||||||
zipVideosAndDownloadWithConfirm(videos)
|
zipVideosAndDownloadWithConfirm(videos)
|
||||||
return false;
|
return false;
|
||||||
})
|
})
|
||||||
|
.on('click','.merge-selected-videos',function(e){
|
||||||
|
e.preventDefault()
|
||||||
|
mergeSelectedVideos();
|
||||||
|
return false;
|
||||||
|
})
|
||||||
.on('click','.refresh-data',function(e){
|
.on('click','.refresh-data',function(e){
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
drawVideosTableViewElements()
|
drawVideosTableViewElements()
|
||||||
|
@ -410,6 +452,14 @@ $(document).ready(function(e){
|
||||||
})
|
})
|
||||||
onWebSocketEvent((data) => {
|
onWebSocketEvent((data) => {
|
||||||
switch(data.f){
|
switch(data.f){
|
||||||
|
case'fileBin_item_added':
|
||||||
|
new PNotify({
|
||||||
|
title: lang['File Saved'],
|
||||||
|
text: `${lang.checkFileBinForNewFile}<br><br><a class="btn btn-sm btn-success" href="${buildFileBinUrl(data)}" download>${lang.Download}</a>`,
|
||||||
|
type: 'success',
|
||||||
|
sticky: true,
|
||||||
|
})
|
||||||
|
break;
|
||||||
case'video_delete':
|
case'video_delete':
|
||||||
case'video_delete_cloud':
|
case'video_delete_cloud':
|
||||||
if(tabTree.name === 'videosTableView'){
|
if(tabTree.name === 'videosTableView'){
|
||||||
|
|
|
@ -10,6 +10,14 @@ $(document).ready(function(){
|
||||||
var theWindow = $(window);
|
var theWindow = $(window);
|
||||||
var lastWindowWidth = theWindow.width()
|
var lastWindowWidth = theWindow.width()
|
||||||
var lastWindowHeight = theWindow.height()
|
var lastWindowHeight = theWindow.height()
|
||||||
|
function getQueryString(){
|
||||||
|
var theObject = {}
|
||||||
|
location.search.substring(1).split('&').forEach(function(string){
|
||||||
|
var parts = string.split('=')
|
||||||
|
theObject[parts[0]] = parts[1]
|
||||||
|
})
|
||||||
|
return theObject
|
||||||
|
}
|
||||||
function featureIsActivated(showNotice){
|
function featureIsActivated(showNotice){
|
||||||
if(userHasSubscribed){
|
if(userHasSubscribed){
|
||||||
return true
|
return true
|
||||||
|
@ -61,6 +69,7 @@ $(document).ready(function(){
|
||||||
|
|
||||||
function selectMonitor(monitorId, css){
|
function selectMonitor(monitorId, css){
|
||||||
css = css || {};
|
css = css || {};
|
||||||
|
var embedHost = getQueryString().host || `/`;
|
||||||
var isSelected = selectedMonitors[monitorId]
|
var isSelected = selectedMonitors[monitorId]
|
||||||
if(isSelected)return;
|
if(isSelected)return;
|
||||||
var numberOfSelected = Object.keys(selectedMonitors)
|
var numberOfSelected = Object.keys(selectedMonitors)
|
||||||
|
@ -69,7 +78,7 @@ $(document).ready(function(){
|
||||||
}
|
}
|
||||||
++selectedMonitorsCount
|
++selectedMonitorsCount
|
||||||
selectedMonitors[monitorId] = Object.assign({}, loadedMonitors[monitorId]);
|
selectedMonitors[monitorId] = Object.assign({}, loadedMonitors[monitorId]);
|
||||||
wallViewCanvas.append(`<div class="wallview-video p-0 m-0" live-stream="${monitorId}" style="left:${css.left || 0}px;top:${css.top || 0}px;width:${css.width ? css.width + 'px' : '50vw'};height:${css.height ? css.height + 'px' : '50vh'};"><div class="overlay"><div class="wallview-item-controls text-end"><a class="btn btn-sm btn-outline-danger wallview-item-close"><i class="fa fa-times"></i></a></div></div><iframe src="${getApiPrefix('embed')}/${monitorId}/fullscreen%7Cjquery%7Crelative?host=/"></iframe></div>`)
|
wallViewCanvas.append(`<div class="wallview-video p-0 m-0" live-stream="${monitorId}" style="left:${css.left || 0}px;top:${css.top || 0}px;width:${css.width ? css.width + 'px' : '50vw'};height:${css.height ? css.height + 'px' : '50vh'};"><div class="overlay"><div class="wallview-item-controls text-end"><a class="btn btn-sm btn-outline-danger wallview-item-close"><i class="fa fa-times"></i></a></div></div><iframe src="${getApiPrefix('embed')}/${monitorId}/fullscreen%7Cjquery%7Crelative?host=${embedHost}"></iframe></div>`)
|
||||||
wallViewCanvas.find(`[live-stream="${monitorId}"]`)
|
wallViewCanvas.find(`[live-stream="${monitorId}"]`)
|
||||||
.draggable({
|
.draggable({
|
||||||
grid: [40, 40],
|
grid: [40, 40],
|
||||||
|
|
|
@ -1,21 +1,69 @@
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
var loadedModules = {}
|
var loadedModules = {}
|
||||||
|
var downloadablePlugins = {}
|
||||||
|
var theEnclosure = $('#superPluginManager')
|
||||||
var listElement = $('#pluginManagerList')
|
var listElement = $('#pluginManagerList')
|
||||||
|
var downloadListElement = $('#pluginManagerDownloadble')
|
||||||
var quickSelect = $('#pluginQuickSelect')
|
var quickSelect = $('#pluginQuickSelect')
|
||||||
var pluginDownloadForm = $('#downloadNewPlugin')
|
var pluginDownloadForm = $('#downloadNewPlugin')
|
||||||
var pluginCommandLine = $('#pluginCommandLine')
|
var pluginCommandLine = $('#pluginCommandLine')
|
||||||
|
var pluginDownloadableSearch = $('#pluginManagerDownloadbleListSearch')
|
||||||
var getModules = function(callback) {
|
var getModules = function(callback) {
|
||||||
$.get(superApiPrefix + $user.sessionKey + '/plugins/list',callback)
|
$.get(superApiPrefix + $user.sessionKey + '/plugins/list',callback)
|
||||||
}
|
}
|
||||||
|
function getDlPluginId(plugin){
|
||||||
|
return `${plugin.name}_${plugin.link}_${plugin.dir}`
|
||||||
|
}
|
||||||
|
function drawDownloadablePlugins(data){
|
||||||
|
var html = ''
|
||||||
|
$.each(data,function(n,plugin){
|
||||||
|
html += `
|
||||||
|
<div class="col-md-6" dl-plugin="${getDlPluginId(plugin)}">
|
||||||
|
<div class="card bg-dark text-white mb-3">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="pb-1">
|
||||||
|
${plugin.type.map(item => `<span class="small">${item}</span>`).join(', ')}
|
||||||
|
${plugin.gpuRequired ? `<span class="badge badge-primary">${plugin.gpuRequired instanceof Array ? plugin.gpuRequired.join(', ') : lang['GPU Required']}</span>` : ''}
|
||||||
|
${plugin.experimental ? `<span class="badge badge-warning">${lang.Experimental}</span>` : ''}
|
||||||
|
</div>
|
||||||
|
<h4 class="title">${plugin.name}</h4>
|
||||||
|
<div><span class="small">${lang['Tested on']}</span> : ${plugin.os.map(item => `<span class="small">${item}</span>`).join(', ')}</div>
|
||||||
|
<div class="pb-1"><span class="badge badge-warning">${plugin.engine}</span> ${plugin.arch.map(item => `<span title="${lang.Architecture}" class="badge badge-primary">${item}</span>`).join(' ')}</div>
|
||||||
|
<div><a class="btn btn-info btn-sm cursor-pointer download">${lang.Download}</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
|
downloadListElement.html(html)
|
||||||
|
}
|
||||||
|
function filterDownloadablePlugins(theSearch = '') {
|
||||||
|
var searchQuery = theSearch.trim().toLowerCase();
|
||||||
|
if(searchQuery === ''){
|
||||||
|
downloadListElement.find(`[dl-plugin]`).show()
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var rows = Object.values(downloadablePlugins);
|
||||||
|
var filtered = []
|
||||||
|
rows.forEach((row) => {
|
||||||
|
var searchInString = JSON.stringify(row).toLowerCase();
|
||||||
|
var theElement = downloadListElement.find(`[dl-plugin="${getDlPluginId(row)}"]`)
|
||||||
|
console.log(searchInString)
|
||||||
|
if(searchInString.indexOf(searchQuery) > -1){
|
||||||
|
theElement.show()
|
||||||
|
}else{
|
||||||
|
theElement.hide()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
function getDownloadableModules(callback) {
|
function getDownloadableModules(callback) {
|
||||||
return new Promise((resolve,reject) => {
|
return new Promise((resolve,reject) => {
|
||||||
const pluginListUrl = `https://cdn.shinobi.video/plugins/list.json`
|
const pluginListUrl = `https://cdn.shinobi.video/plugins/list.json`
|
||||||
$.getJSON(pluginListUrl,function(data){
|
$.getJSON(pluginListUrl,function(data){
|
||||||
var html = ''
|
|
||||||
$.each(data,function(n,plugin){
|
$.each(data,function(n,plugin){
|
||||||
html += `<option value="${plugin.link}${plugin.dir ? `,${plugin.dir}` : ''}">${plugin.name}</option>`
|
downloadablePlugins[getDlPluginId(plugin)] = plugin;
|
||||||
})
|
})
|
||||||
quickSelect.html(html)
|
drawDownloadablePlugins(data)
|
||||||
resolve(data)
|
resolve(data)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -99,7 +147,11 @@ $(document).ready(function(){
|
||||||
$.post(superApiPrefix + $user.sessionKey + '/plugins/download',{
|
$.post(superApiPrefix + $user.sessionKey + '/plugins/download',{
|
||||||
downloadUrl: url,
|
downloadUrl: url,
|
||||||
packageRoot: packageRoot,
|
packageRoot: packageRoot,
|
||||||
},callback)
|
},function(data){
|
||||||
|
setTimeout(function(){
|
||||||
|
callback(data)
|
||||||
|
},3000)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -328,13 +380,14 @@ $(document).ready(function(){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
pluginDownloadForm.submit(function(e){
|
theEnclosure.on('click','[dl-plugin] .download',function(e){
|
||||||
e.preventDefault();
|
var pluginName = $(this).parents('[dl-plugin]').attr('dl-plugin')
|
||||||
var el = $(this)
|
console.log(pluginName)
|
||||||
var form = el.serializeObject()
|
var theDlPlugin = downloadablePlugins[pluginName]
|
||||||
downloadModule(form.downloadUrl,form.packageRoot,function(data){
|
downloadModule(theDlPlugin.link,theDlPlugin.dir,function(data){
|
||||||
console.log(data)
|
|
||||||
if(data.ok){
|
if(data.ok){
|
||||||
|
$('[data-bs-target="#pluginManagerList"],#pluginManagerList').addClass('active')
|
||||||
|
$('[data-bs-target="#pluginManagerDownloadbleList"],#pluginManagerDownloadbleList').removeClass('active')
|
||||||
var theModule = data.newModule
|
var theModule = data.newModule
|
||||||
theModule.config.enabled = false
|
theModule.config.enabled = false
|
||||||
drawModuleBlock(theModule)
|
drawModuleBlock(theModule)
|
||||||
|
@ -351,7 +404,6 @@ $(document).ready(function(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return false
|
|
||||||
})
|
})
|
||||||
$('#pluginQuickSelectExec').click(function(){
|
$('#pluginQuickSelectExec').click(function(){
|
||||||
var currentVal = quickSelect.val()
|
var currentVal = quickSelect.val()
|
||||||
|
@ -362,6 +414,10 @@ $(document).ready(function(){
|
||||||
pluginDownloadForm.find(`[name="packageRoot"]`).val(packageRoot)
|
pluginDownloadForm.find(`[name="packageRoot"]`).val(packageRoot)
|
||||||
pluginDownloadForm.submit()
|
pluginDownloadForm.submit()
|
||||||
})
|
})
|
||||||
|
pluginDownloadableSearch.keyup(function(){
|
||||||
|
var searchQuery = $(this).val()
|
||||||
|
filterDownloadablePlugins(searchQuery)
|
||||||
|
})
|
||||||
function getObjectAlphabetically(theObject,key){
|
function getObjectAlphabetically(theObject,key){
|
||||||
return Object.values(theObject).sort(function( a, b ) {
|
return Object.values(theObject).sort(function( a, b ) {
|
||||||
const aName = new Date(a[key]).getTime()
|
const aName = new Date(a[key]).getTime()
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
<li><a class="dropdown-item unarchive-selected-videos cursor-pointer"><%- lang.Unarchive %></a></li>
|
<li><a class="dropdown-item unarchive-selected-videos cursor-pointer"><%- lang.Unarchive %></a></li>
|
||||||
<li><a class="dropdown-item compress-selected-videos cursor-pointer"><%- lang.Compress %></a></li>
|
<li><a class="dropdown-item compress-selected-videos cursor-pointer"><%- lang.Compress %></a></li>
|
||||||
<li><a class="dropdown-item zip-selected-videos cursor-pointer"><%- lang['Zip and Download'] %></a></li>
|
<li><a class="dropdown-item zip-selected-videos cursor-pointer"><%- lang['Zip and Download'] %></a></li>
|
||||||
|
<li><a class="dropdown-item merge-selected-videos cursor-pointer"><%- lang['Merge'] %></a></li>
|
||||||
<!-- <li><a class="dropdown-item merge-selected-videos cursor-pointer"><%- lang.Merge %></a></li> -->
|
<!-- <li><a class="dropdown-item merge-selected-videos cursor-pointer"><%- lang.Merge %></a></li> -->
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item delete-selected-videos cursor-pointer"><%- lang.Delete %></a></li>
|
<li><a class="dropdown-item delete-selected-videos cursor-pointer"><%- lang.Delete %></a></li>
|
||||||
|
|
|
@ -1,29 +1,18 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card bg-dark grey mt-1">
|
<div class="card bg-dark grey mt-1">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<%- lang['Download Plugins'] %>
|
<%- lang['Plugin Manager'] %>
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<p><%- lang.pluginDownloadText %></p>
|
|
||||||
<div class="form-group">
|
|
||||||
<select class="form-control" id="pluginQuickSelect">
|
|
||||||
<% [
|
|
||||||
{
|
|
||||||
"label": "tensorflow-4-1-0",
|
|
||||||
"value": "https://gitlab.com/Shinobi-Systems/shinobi-plugins/-/archive/master/shinobi-plugins-master.zip,plugins/tensorflow-4-1-0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "tensorflow-2-3-0",
|
|
||||||
"value": "https://gitlab.com/Shinobi-Systems/shinobi-plugins/-/archive/master/shinobi-plugins-master.zip,plugins/tensorflow-2-3-0"
|
|
||||||
}
|
|
||||||
].forEach((option) => { %>
|
|
||||||
<option value="<%- option.value %>"><%- option.label %></option>
|
|
||||||
<% }) %>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div><button id="pluginQuickSelectExec" class="btn btn-round btn-block btn-default mb-0"><i class="fa fa-download"></i> <%- lang.Download %></button></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<ul class="nav nav-tabs nav-tabs-neutral justify-content-center bg-dark" id="tablist" role="tablist">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" data-bs-toggle="tab" data-bs-target="#pluginManagerList" role="tab"><%-lang['Downloaded']%></a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" data-bs-toggle="tab" data-bs-target="#pluginManagerDownloadbleList" role="tab"><%-lang['Download Plugins']%></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
|
@ -40,8 +29,16 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pt-4 pb-0 m-0" id="pluginManagerList">
|
<div class="tab-content pt-4 pb-0 m-0">
|
||||||
|
<div class="tab-pane active" id="pluginManagerList"></div>
|
||||||
|
<div class="tab-pane" id="pluginManagerDownloadbleList">
|
||||||
|
<div class="form-group">
|
||||||
|
<input id="pluginManagerDownloadbleListSearch" type="text" placeholder="<%- lang.Search %>" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="row d-flex" id="pluginManagerDownloadble">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<link rel="stylesheet" href="<%-window.libURL%>assets/css/super.pluginManager.css">
|
<link rel="stylesheet" href="<%-window.libURL%>assets/css/super.pluginManager.css">
|
||||||
<script src="<%-window.libURL%>assets/js/super.pluginManager.js" type="text/javascript"></script>
|
<script src="<%-window.libURL%>assets/js/super.pluginManager.js" type="text/javascript"></script>
|
||||||
|
|
Loading…
Reference in New Issue