master
Moe 2024-09-28 16:55:50 +00:00
parent 03898a3382
commit 9be4e6b95a
36 changed files with 1136 additions and 217 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,3 +12,6 @@
border: 1px solid #009dff; border: 1px solid #009dff;
border-radius: 10px; border-radius: 10px;
} }
#superPluginManager .badge-success {
background-color: #2fa523;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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