Alhambra
parent
03898a3382
commit
9be4e6b95a
|
@ -1,8 +1,27 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Get the Ubuntu version
|
||||
UBUNTU_VERSION=$(lsb_release -rs)
|
||||
NODE_MAJOR=18
|
||||
|
||||
# Check if Ubuntu version is 18.04
|
||||
if [ "$UBUNTU_VERSION" = "18.04" ]; then
|
||||
NODE_MAJOR=16
|
||||
fi
|
||||
|
||||
echo "Installing Node version: $NODE_MAJOR"
|
||||
|
||||
# Update and install necessary packages
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ca-certificates curl gnupg
|
||||
|
||||
# Setup NodeSource keyring and sources list
|
||||
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
|
||||
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
|
||||
|
||||
# Update package list and install Node.js
|
||||
sudo apt-get update
|
||||
sudo apt-get install nodejs -y
|
||||
sudo apt-get install -y nodejs
|
||||
|
|
|
@ -56,7 +56,7 @@ if ! [ -x "$(command -v ifconfig)" ]; then
|
|||
fi
|
||||
echo "============="
|
||||
echo "Shinobi - Installing Node.js"
|
||||
sh $DIR/nodejs-ubuntu.sh
|
||||
bash $DIR/nodejs-ubuntu.sh
|
||||
if ! [ -x "$(command -v npm)" ]; then
|
||||
sudo apt install npm -y
|
||||
fi
|
||||
|
|
|
@ -3151,12 +3151,30 @@ module.exports = function(s,config,lang){
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
hidden: true,
|
||||
"id": "detectorsSelected",
|
||||
"name": "detail=detectors_selected",
|
||||
"field": lang["Detectors Selected"],
|
||||
"description": lang.fieldTextDetectorsSelected,
|
||||
"default": "all",
|
||||
"attribute": "multiple",
|
||||
"fieldType": "select",
|
||||
"form-group-class": "h_casc_input h_casc_1",
|
||||
"possible": [
|
||||
{
|
||||
"name": `${lang.All} (${lang.Default})`,
|
||||
"value": "all"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "detail=detector_object_ignore_not_move",
|
||||
"field": lang["Ignore Non-Moving"],
|
||||
"default": "0",
|
||||
"fieldType": "select",
|
||||
"selector": "h_obj_ignore_move",
|
||||
"form-group-class": "h_casc_input h_casc_1",
|
||||
"possible": [
|
||||
{
|
||||
"name": lang.No,
|
||||
|
@ -3183,6 +3201,7 @@ module.exports = function(s,config,lang){
|
|||
"description": lang["fieldTextDetectorSendFramesObject"],
|
||||
"default": "1",
|
||||
"fieldType": "select",
|
||||
"form-group-class": "h_casc_input h_casc_1",
|
||||
"possible": [
|
||||
{
|
||||
"name": lang.No,
|
||||
|
@ -3222,6 +3241,7 @@ module.exports = function(s,config,lang){
|
|||
"default": "1",
|
||||
"example": "",
|
||||
"fieldType": "select",
|
||||
"form-group-class": "h_casc_input h_casc_1",
|
||||
"possible": [
|
||||
{
|
||||
"name": lang.No,
|
||||
|
@ -3242,6 +3262,7 @@ module.exports = function(s,config,lang){
|
|||
"example": "",
|
||||
"selector": "h_det_mot_fir",
|
||||
"fieldType": "select",
|
||||
"form-group-class": "h_casc_input h_casc_1",
|
||||
"possible": [
|
||||
{
|
||||
"name": lang.No,
|
||||
|
@ -3290,7 +3311,6 @@ module.exports = function(s,config,lang){
|
|||
]
|
||||
},
|
||||
{
|
||||
isAdvanced: true,
|
||||
hidden: true,
|
||||
"name": lang['Event-Based Recording'],
|
||||
"input-mapping": "detector_sip_buffer",
|
||||
|
|
|
@ -18,10 +18,17 @@
|
|||
"accountEditError": "Account Edit Error",
|
||||
"Monitor Map": "Monitor Map",
|
||||
"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.",
|
||||
"playUntilVideoEnd": "Play until video end",
|
||||
"Monitor Saved": "Monitor Saved",
|
||||
"Auto Placement": "Auto Placement",
|
||||
"Downloaded": "Downloaded",
|
||||
"GPU Required": "GPU Required",
|
||||
"Experimental": "Experimental",
|
||||
"Market": "Market",
|
||||
"Unmute": "Unmute",
|
||||
"byUser": "by user",
|
||||
"accountDeleted": "Account Deleted",
|
||||
|
@ -468,6 +475,8 @@
|
|||
"Max Storage Amount": "Max Storage Amount",
|
||||
"Video Share": "Video Share",
|
||||
"FileBin": "FileBin",
|
||||
"File Saved": "File Saved",
|
||||
"checkFileBinForNewFile": "Check the FileBin for the newly created file.",
|
||||
"File Download Ready": "File Download Ready",
|
||||
"Timelapse Video Build Complete": "Timelapse Video Build Complete",
|
||||
"yourFileDownloadedShortly": "Please wait. Your file will be downloaded shortly.",
|
||||
|
@ -535,6 +544,7 @@
|
|||
"Time-lapse Tool": "Time-lapse Tool",
|
||||
"total": "total",
|
||||
"MB": "MB",
|
||||
"All": "All",
|
||||
"Calendar": "Calendar",
|
||||
"Leave blank for random.": "Leave blank for random.",
|
||||
"Currently viewing": "Currently viewing",
|
||||
|
@ -570,6 +580,10 @@
|
|||
"clientStreamFailedattemptingReconnect": "Client side stream check failed, attempting reconnect.",
|
||||
"Export Video": "Export 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 Files": "Delete Files",
|
||||
"confirmDeleteFilter": "Do you want to delete this filter? You cannot recover it.",
|
||||
|
@ -684,6 +698,7 @@
|
|||
"Minutes": "Minutes",
|
||||
"Custom": "Custom",
|
||||
"Detector": "Detector",
|
||||
"Detectors Selected": "Detectors Selected",
|
||||
"Audio Detector": "Audio Detector",
|
||||
"Audio Detection": "Audio Detection",
|
||||
"Minimum dB": "Minimum dB",
|
||||
|
@ -1093,6 +1108,7 @@
|
|||
"Can't Connect": "Can't Connect",
|
||||
"Video Finished": "Video Finished",
|
||||
"No Monitors Selected": "No Monitors Selected",
|
||||
"No Monitor Selected": "No Monitor Selected",
|
||||
"Nothing Selected": "Nothing Selected",
|
||||
"makeASelection": "Make a selection and try again.",
|
||||
"monSavedButNotCopied": "Your monitor was saved but not copied to any other monitor.",
|
||||
|
@ -1204,6 +1220,7 @@
|
|||
"Preview": "Preview",
|
||||
"Websocket Connected": "Websocket Connected",
|
||||
"Websocket Disconnected": "Websocket Disconnected",
|
||||
"Disconnected": "Disconnected",
|
||||
"Videos Merge": "Videos Merge",
|
||||
"Channel ID": "Channel ID",
|
||||
"Recipient ID": "Recipient ID",
|
||||
|
@ -1574,6 +1591,7 @@
|
|||
"MQTT Client": "MQTT Client",
|
||||
"Buffer Time from Event": "Buffer Time from Event",
|
||||
"detected": "detected",
|
||||
"fieldTextDetectorsSelected": "Select which detectors to send frames to.",
|
||||
"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.",
|
||||
"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>",
|
||||
"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",
|
||||
"Save Unknown Faces": "Save Unknown Faces",
|
||||
"saveUnknownFacesFieldText": "Save Unknown faces to the Face Manager. Manual sorting may still be required.",
|
||||
"Current Version": "Current Version",
|
||||
"Default is Global value": "Default is Global value",
|
||||
"rejectUnauth": "Ignore server certificate"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const fs = require('fs');
|
||||
const fsP = require('fs').promises;
|
||||
const path = require('path');
|
||||
const moment = require('moment');
|
||||
const fetch = require('node-fetch');
|
||||
|
@ -217,6 +218,13 @@ module.exports = (processCwd,config) => {
|
|||
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) {
|
||||
var p = str.split(':'),
|
||||
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 {
|
||||
parseJSON: parseJSON,
|
||||
stringJSON: stringJSON,
|
||||
|
@ -261,5 +280,7 @@ module.exports = (processCwd,config) => {
|
|||
copyFile: copyFile,
|
||||
hmsToSeconds,
|
||||
setDefaultIfUndefined,
|
||||
deleteFilesInFolder,
|
||||
moveFile,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
const fs = require('fs');
|
||||
module.exports = function(s,config,lang,app,io){
|
||||
const {
|
||||
postProcessCompletedMp4Video,
|
||||
} = require('../video/utils.js')(s,config,lang)
|
||||
const masterDoWorkToo = config.childNodes.masterDoWorkToo;
|
||||
const maxCpuPercent = config.childNodes.maxCpuPercent || 75;
|
||||
const maxRamPercent = config.childNodes.maxRamPercent || 75;
|
||||
|
@ -177,17 +180,21 @@ module.exports = function(s,config,lang,app,io){
|
|||
filename : filename,
|
||||
filesizeMB : parseFloat((data.filesize/1048576).toFixed(2))
|
||||
}
|
||||
s.insertDatabaseRow(monitorConfig,insert)
|
||||
s.insertCompletedVideoExtensions.forEach(function(extender){
|
||||
extender(activeMonitor, monitorConfig, insert)
|
||||
s.insertDatabaseRow(monitorConfig,insert,function(response){
|
||||
postProcessCompletedMp4Video(response.insertQuery).then((isGood) => {
|
||||
if(!isGood)return console.error(`FAILED VIDEO INSERT`);
|
||||
s.insertCompletedVideoExtensions.forEach(function(extender){
|
||||
extender(activeMonitor, monitorConfig, insert)
|
||||
})
|
||||
//purge over max
|
||||
s.purgeDiskForGroup(data.ke)
|
||||
//send new diskUsage values
|
||||
s.setDiskUsedForGroup(data.ke,insert.filesizeMB)
|
||||
clearTimeout(activeMonitor.recordingChecker)
|
||||
clearTimeout(activeMonitor.streamChecker)
|
||||
resolve(response)
|
||||
})
|
||||
})
|
||||
//purge over max
|
||||
s.purgeDiskForGroup(data.ke)
|
||||
//send new diskUsage values
|
||||
s.setDiskUsedForGroup(data.ke,insert.filesizeMB)
|
||||
clearTimeout(activeMonitor.recordingChecker)
|
||||
clearTimeout(activeMonitor.streamChecker)
|
||||
resolve(response)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -13,6 +13,15 @@ module.exports = (s,config,lang) => {
|
|||
workerProcess.on('message',function(data){
|
||||
if(data.time === 'moment()')data.time = moment();
|
||||
switch(data.f){
|
||||
case'knexQuery':
|
||||
s.knexQuery(...data.args, function(...args){
|
||||
workerProcess.postMessage({
|
||||
f: 'callback',
|
||||
rid: data.rid,
|
||||
args,
|
||||
})
|
||||
});
|
||||
break;
|
||||
case'debugLog':
|
||||
s.debugLog(...data.data)
|
||||
break;
|
||||
|
|
|
@ -42,6 +42,13 @@ parentPort.on('message',(data) => {
|
|||
setDefaultConfigOptions()
|
||||
beginProcessing()
|
||||
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':
|
||||
setIntervalForCron()
|
||||
break;
|
||||
|
@ -51,7 +58,7 @@ parentPort.on('message',(data) => {
|
|||
}
|
||||
})
|
||||
function debugLog(...args){
|
||||
if(config.debugLog === true){
|
||||
if(config.debugLog === true || config.logCronInfo === true){
|
||||
console.log(...([`CRON.js DEBUG LOG ${new Date()}`].concat(args)))
|
||||
}
|
||||
}
|
||||
|
@ -64,6 +71,7 @@ function errorLog(...args){
|
|||
const s = {
|
||||
debugLog,
|
||||
}
|
||||
const pendingCallbacks = {};
|
||||
function beginProcessing(){
|
||||
normalLog(`Worker Processing!`)
|
||||
const {
|
||||
|
@ -74,8 +82,6 @@ function beginProcessing(){
|
|||
} = require('../basic/utils.js')(process.cwd())
|
||||
const {
|
||||
sqlDate,
|
||||
knexQuery,
|
||||
knexQueryPromise,
|
||||
initiateDatabaseEngine
|
||||
} = require('../sql/utils.js')(s,config)
|
||||
var theCronInterval = null
|
||||
|
@ -111,6 +117,23 @@ function beginProcessing(){
|
|||
const setDiskUsedForGroup = (groupKey,size,target,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){
|
||||
if(e.mid&&!e.id){e.id=e.mid};
|
||||
if(e.details&&(e.details instanceof Object)===false){
|
||||
|
|
|
@ -26,6 +26,9 @@ module.exports = function(s,config,lang,app,io){
|
|||
const {
|
||||
triggerEvent,
|
||||
} = require('./events/utils.js')(s,config,lang)
|
||||
const {
|
||||
deleteFilesInFolder,
|
||||
} = require('./basic/utils.js')(process.cwd(), config)
|
||||
if(config.dropInEventServer === true){
|
||||
if(config.dropInEventForceSaveEvent === undefined)config.dropInEventForceSaveEvent = true
|
||||
if(config.dropInEventDeleteFileAfterTrigger === undefined)config.dropInEventDeleteFileAfterTrigger = true
|
||||
|
@ -142,7 +145,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
if(config.dropInEventDeleteFileAfterTrigger){
|
||||
clearTimeout(fileQueue[filePath])
|
||||
fileQueue[filePath] = setTimeout(function(){
|
||||
exec('rm -rf ' + filePath,function(err){
|
||||
fs.rm(filePath, { recursive: true },(err) => {
|
||||
delete(fileQueue[filePath])
|
||||
})
|
||||
},1000 * 60 * 5)
|
||||
|
@ -152,7 +155,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
if(config.dropInEventDeleteFileAfterTrigger){
|
||||
clearTimeout(fileQueueForDeletion[deletionKey])
|
||||
fileQueueForDeletion[deletionKey] = setTimeout(function(){
|
||||
exec('rm -rf ' + deletionKey,function(err){
|
||||
fs.rm(filePath, { recursive: true },(err) => {
|
||||
delete(fileQueueForDeletion[deletionKey])
|
||||
})
|
||||
},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) + '/'
|
||||
fs.mkdir(directory,function(err){
|
||||
s.handleFolderError(err)
|
||||
exec('rm -rf "' + directory + '*"',function(){})
|
||||
deleteFilesInFolder(directory)
|
||||
callback(err,directory)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -40,7 +40,7 @@ module.exports = (s,config,lang) => {
|
|||
const monitorId = options.mid || options.id
|
||||
const groupKey = options.ke
|
||||
//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 objectsFound = options.matrices
|
||||
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 theEmitter = activeMonitor.secondaryDetectorOutput
|
||||
if(!activeMonitor.sendingFromSecondaryDetectorOuput){
|
||||
s.debugLog('start sending object frames',groupKey,monitorId)
|
||||
theEmitter.on('data',activeMonitor.secondaryDetectorOuputContentWriter = (data) => {
|
||||
const monitorConfig = s.group[groupKey].rawMonitorConfigurations[monitorId]
|
||||
const monitorDetails = monitorConfig.details;
|
||||
let chosenDetector = monitorDetails.detectors_selected;
|
||||
if(chosenDetector instanceof Array)chosenDetector = chosenDetector.join(',');
|
||||
let sendToDetector = (data) => {
|
||||
s.ocvTx({
|
||||
f : 'frame',
|
||||
mon : s.group[groupKey].rawMonitorConfigurations[monitorId].details,
|
||||
mon : monitorDetails,
|
||||
ke : groupKey,
|
||||
id : monitorId,
|
||||
time : s.formattedTime(),
|
||||
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)
|
||||
activeMonitor.sendingFromSecondaryDetectorOuput = setTimeout(() => {
|
||||
|
|
|
@ -87,6 +87,8 @@ module.exports = function(s,config){
|
|||
createExtension(`onSubscriptionCheck`)
|
||||
createExtension(`onDataPortMessage`)
|
||||
createExtension(`onHttpRequestUpgrade`,null,true)
|
||||
createExtension(`onPluginConnected`)
|
||||
createExtension(`onPluginDisconnected`)
|
||||
/////// CRON ////////
|
||||
createExtension(`onCronGroupProcessed`)
|
||||
createExtension(`onCronGroupProcessedAwaited`)
|
||||
|
|
|
@ -790,6 +790,8 @@ module.exports = (s,config,lang) => {
|
|||
return ``
|
||||
}
|
||||
const getDefaultSubstreamFields = function(monitor){
|
||||
const otherInputFlags = []
|
||||
const otherOutputFlags = []
|
||||
const subStreamFields = parseJSON(monitor.details.substream || {input:{},output:{}})
|
||||
const inputAndConnectionFields = Object.assign({
|
||||
"type":"h264",
|
||||
|
@ -825,7 +827,15 @@ module.exports = (s,config,lang) => {
|
|||
"svf":"",
|
||||
"cust_stream":""
|
||||
},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 {
|
||||
otherInputFlags,
|
||||
otherOutputFlags,
|
||||
inputAndConnectionFields,
|
||||
outputFields,
|
||||
}
|
||||
|
|
|
@ -192,6 +192,11 @@ module.exports = function(s,config,lang,app,io){
|
|||
})
|
||||
})
|
||||
}
|
||||
s.notifyFileBinUploaded = function(fileBinInsertQuery){
|
||||
s.tx(Object.assign({
|
||||
f: 'fileBin_item_added',
|
||||
},fileBinInsertQuery),'GRP_'+fileBinInsertQuery.ke);
|
||||
}
|
||||
s.getFileBinDirectory = getFileBinDirectory
|
||||
s.getFileBinEntry = getFileBinEntry
|
||||
s.getFileBinBuffer = getFileBinBuffer
|
||||
|
|
|
@ -278,6 +278,15 @@ module.exports = (s,config,lang) => {
|
|||
}
|
||||
})
|
||||
}
|
||||
const sendSubstreamEvent = function(groupKey, monitorId, eventName = 'substream_start'){
|
||||
const activeMonitor = getActiveMonitor(groupKey,monitorId)
|
||||
s.tx({
|
||||
f: eventName,
|
||||
mid: monitorId,
|
||||
ke: groupKey,
|
||||
channel: activeMonitor.subStreamChannel
|
||||
},'GRP_'+groupKey);
|
||||
}
|
||||
const spawnSubstreamProcess = function(e){
|
||||
// e = monitorConfig
|
||||
try{
|
||||
|
@ -295,9 +304,13 @@ module.exports = (s,config,lang) => {
|
|||
substreamConfig.input.fulladdress = substreamConfig.input.fulladdress || s.buildMonitorUrl(monitorConfig)
|
||||
substreamConfig.input.rtsp_transport = substreamConfig.input.rtsp_transport || monitorConfig.details.rtsp_transport
|
||||
const {
|
||||
otherInputFlags,
|
||||
otherOutputFlags,
|
||||
inputAndConnectionFields,
|
||||
outputFields,
|
||||
} = getDefaultSubstreamFields(monitorConfig);
|
||||
ffmpegCommand.push(...otherInputFlags);
|
||||
ffmpegCommand.push(...otherOutputFlags);
|
||||
([
|
||||
buildSubstreamString(channelNumber + config.pipeAddition,e),
|
||||
]).forEach(function(commandStringPart){
|
||||
|
@ -375,12 +388,7 @@ module.exports = (s,config,lang) => {
|
|||
}
|
||||
})
|
||||
activeMonitor.subStreamProcess = subStreamProcess
|
||||
s.tx({
|
||||
f: 'substream_start',
|
||||
mid: monitorId,
|
||||
ke: groupKey,
|
||||
channel: activeMonitor.subStreamChannel
|
||||
},'GRP_'+groupKey);
|
||||
sendSubstreamEvent(groupKey, monitorId)
|
||||
return subStreamProcess
|
||||
}catch(err){
|
||||
s.systemLog(err)
|
||||
|
@ -403,11 +411,7 @@ module.exports = (s,config,lang) => {
|
|||
response.hadSubStream = true
|
||||
response.closeResponse = closeResponse
|
||||
delete(activeMonitor.subStreamProcess)
|
||||
s.tx({
|
||||
f: 'substream_end',
|
||||
mid: activeMonitor.mid,
|
||||
ke: activeMonitor.ke
|
||||
},'GRP_'+activeMonitor.ke);
|
||||
sendSubstreamEvent(activeMonitor.mid, activeMonitor.ke, 'substream_end')
|
||||
activeMonitor.subStreamProcessClosing = false
|
||||
}
|
||||
}catch(err){
|
||||
|
@ -975,6 +979,7 @@ module.exports = (s,config,lang) => {
|
|||
const groupKey = e.ke
|
||||
const monitorId = e.mid || e.id
|
||||
const activeMonitor = getActiveMonitor(groupKey,monitorId)
|
||||
if(!activeMonitor)return;
|
||||
clearTimeout(activeMonitor.streamChecker)
|
||||
activeMonitor.streamChecker = setTimeout(function(){
|
||||
if(activeMonitor && activeMonitor.isStarted === true){
|
||||
|
@ -988,7 +993,7 @@ module.exports = (s,config,lang) => {
|
|||
}
|
||||
})
|
||||
}
|
||||
},60000*1);
|
||||
},60000 * 1);
|
||||
}
|
||||
function resetTimelapseFramesCheck(e){
|
||||
const groupKey = e.ke
|
||||
|
@ -1181,7 +1186,6 @@ module.exports = (s,config,lang) => {
|
|||
})
|
||||
}
|
||||
if(e.details.detector === '1'){
|
||||
s.ocvTx({f:'init_monitor',id:monitorId,ke:groupKey})
|
||||
//frames from motion detect
|
||||
if(e.details.detector_pam === '1'){
|
||||
// activeMonitor.spawn.stdio[3].pipe(activeMonitor.p2p).pipe(activeMonitor.pamDiff)
|
||||
|
@ -1856,5 +1860,6 @@ module.exports = (s,config,lang) => {
|
|||
setTimedActiveViewerForHttp: setTimedActiveViewerForHttp,
|
||||
attachMainProcessHandlers: attachMainProcessHandlers,
|
||||
removeSenstiveInfoFromMonitorConfig,
|
||||
sendSubstreamEvent,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,18 +43,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
s.detectorPluginArray = []
|
||||
s.isAtleatOneDetectorPluginConnected = false
|
||||
s.addDetectorPlugin = function(name,d){
|
||||
if(config.useOldPluginConnectionMethod === true){
|
||||
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] = {
|
||||
const newDetector = {
|
||||
started: s.timeObject(),
|
||||
id: d.id,
|
||||
plug: d.plug,
|
||||
|
@ -62,15 +51,22 @@ module.exports = function(s,config,lang,app,io){
|
|||
isClientPlugin: d.isClientPlugin,
|
||||
isHostPlugin: d.isHostPlugin,
|
||||
connectionType: d.connectionType
|
||||
};
|
||||
if(config.useOldPluginConnectionMethod === true){
|
||||
s.ocv = newDetector
|
||||
}
|
||||
s.connectedDetectorPlugins[d.plug] = newDetector
|
||||
s.resetDetectorPluginArray()
|
||||
s.runExtensionsForArray('onPluginConnected', null, [d.plug, newDetector])
|
||||
}
|
||||
s.removeDetectorPlugin = function(name){
|
||||
const theDetector = Object.assign({}, s.connectedDetectorPlugins[name])
|
||||
if(config.oldPluginConnectionMethod === true && s.ocv && s.ocv.plug === name){
|
||||
delete(s.ocv)
|
||||
}
|
||||
delete(s.connectedDetectorPlugins[name])
|
||||
s.resetDetectorPluginArray(name)
|
||||
s.runExtensionsForArray('onPluginDisconnected', null, [name, theDetector])
|
||||
}
|
||||
s.resetDetectorPluginArray = function(){
|
||||
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.detectorPluginArray.forEach(function(name){
|
||||
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.onWebSocketDisconnection(onWebSocketDisconnection)
|
||||
s.onWebSocketConnection(onWebSocketConnection)
|
||||
s.onMonitorStart(onMonitorUpdate)
|
||||
s.onPluginConnected(sendCopyOfAllMonitorConfigs)
|
||||
}
|
||||
|
|
|
@ -94,12 +94,13 @@ module.exports = async (s,config,lang,app,io,currentUse) => {
|
|||
const downloadModule = (downloadUrl,packageName) => {
|
||||
const downloadPath = modulesBasePath + packageName
|
||||
try{
|
||||
fs.mkdirSync(downloadPath)
|
||||
fs.mkdirSync(downloadPath, { recursive: true })
|
||||
}catch(err){
|
||||
s.debugLog(err)
|
||||
}
|
||||
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)
|
||||
.then((readStream) => {
|
||||
readStream.pipe(unzipper.Parse())
|
||||
|
@ -228,11 +229,15 @@ module.exports = async (s,config,lang,app,io,currentUse) => {
|
|||
}
|
||||
const enableModule = (name,status) => {
|
||||
// set status to `false` to enable
|
||||
const modulePath = getModulePath(name)
|
||||
const confJson = getModuleConfiguration(name)
|
||||
const confPath = modulePath + 'conf.json'
|
||||
confJson.enabled = status;
|
||||
fs.writeFileSync(confPath,s.prettyPrint(confJson))
|
||||
try{
|
||||
const modulePath = getModulePath(name)
|
||||
const confJson = getModuleConfiguration(name)
|
||||
const confPath = modulePath + 'conf.json'
|
||||
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) => {
|
||||
// requires restart for changes to take effect
|
||||
|
|
|
@ -34,6 +34,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
s.createTimelapseFrameAndInsert = function(e,location,filename,eventTime,frameDetails){
|
||||
//e = monitor object
|
||||
//location = file location
|
||||
var monitorId = e.id || e.mid;
|
||||
var filePath = location + filename
|
||||
var fileStats = fs.statSync(filePath)
|
||||
var details = Object.assign({},frameDetails || {})
|
||||
|
@ -43,7 +44,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
const timeNow = eventTime || new Date()
|
||||
const queryInfo = {
|
||||
ke: e.ke,
|
||||
mid: e.id,
|
||||
mid: monitorId,
|
||||
details: s.s(details),
|
||||
filename: filename,
|
||||
size: fileStats.size,
|
||||
|
@ -53,7 +54,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
var currentDate = s.formattedTime(timeNow,'YYYY-MM-DD')
|
||||
const childNodeData = {
|
||||
ke: e.ke,
|
||||
mid: e.id,
|
||||
mid: monitorId,
|
||||
time: currentDate,
|
||||
filename: filename,
|
||||
currentDate: currentDate,
|
||||
|
@ -559,8 +560,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
actionParameter && (
|
||||
isRestrictedApiKey && apiKeyPermissions.delete_videos_disallowed ||
|
||||
isRestricted && !monitorPermissions[`${monitorId}_video_delete`]
|
||||
) ||
|
||||
!actionParameter && (
|
||||
) || !actionParameter && (
|
||||
isRestrictedApiKey && apiKeyPermissions.watch_videos_disallowed ||
|
||||
isRestricted && monitorId && !monitorPermissions[`${monitorId}_video_view`]
|
||||
)
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
const fs = require('fs')
|
||||
const { spawn } = require('child_process')
|
||||
const async = require('async');
|
||||
const path = require('path');
|
||||
const moment = require('moment');
|
||||
const fsP = require('fs').promises;
|
||||
module.exports = (s,config,lang) => {
|
||||
const {
|
||||
ffprobe,
|
||||
|
@ -9,7 +12,9 @@ module.exports = (s,config,lang) => {
|
|||
const {
|
||||
copyFile,
|
||||
hmsToSeconds,
|
||||
moveFile,
|
||||
} = require('../basic/utils.js')(process.cwd(),config)
|
||||
const chunkReadSize = 4096;
|
||||
// orphanedVideoCheck : new function
|
||||
const checkIfVideoIsOrphaned = (monitor,videosDirectory,filename) => {
|
||||
const response = {ok: true}
|
||||
|
@ -593,6 +598,7 @@ module.exports = (s,config,lang) => {
|
|||
time: video.time,
|
||||
}
|
||||
await s.insertFileBinEntry(fileBinInsertQuery)
|
||||
s.notifyFileBinUploaded(fileBinInsertQuery)
|
||||
s.tx(Object.assign({
|
||||
f: 'fileBin_item_added',
|
||||
slicedVideo: true,
|
||||
|
@ -604,6 +610,374 @@ module.exports = (s,config,lang) => {
|
|||
}
|
||||
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 {
|
||||
reEncodeVideoAndReplace,
|
||||
stitchMp4Files,
|
||||
|
@ -615,5 +989,13 @@ module.exports = (s,config,lang) => {
|
|||
reEncodeVideoAndBinOriginalAddToQueue,
|
||||
archiveVideo,
|
||||
sliceVideo,
|
||||
mergeVideos,
|
||||
mergeVideosAndBin,
|
||||
saveVideoFrameToTimelapse,
|
||||
postProcessCompletedMp4Video,
|
||||
readChunkForMoov,
|
||||
checkMoovAtBeginning,
|
||||
checkMoovAtEnd,
|
||||
hasMoovAtom
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,9 @@ module.exports = function(s,config,lang){
|
|||
const {
|
||||
sendVideoToMasterNode,
|
||||
} = 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.
|
||||
* @constructor
|
||||
|
@ -141,7 +144,7 @@ module.exports = function(s,config,lang){
|
|||
ext: k.ext,
|
||||
size: 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'),
|
||||
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) => {
|
||||
if(callback)callback(err,response);
|
||||
s.insertCompletedVideoExtensions.forEach(function(extender){
|
||||
extender(e,k,response.insertQuery,response)
|
||||
postProcessCompletedMp4Video(response.insertQuery).then((isGood) => {
|
||||
if(!isGood)return console.error(`FAILED VIDEO INSERT`);
|
||||
s.insertCompletedVideoExtensions.forEach(function(extender){
|
||||
extender(e,k,response.insertQuery,response)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
spawnSubstreamProcess,
|
||||
destroySubstreamProcess,
|
||||
removeSenstiveInfoFromMonitorConfig,
|
||||
sendSubstreamEvent,
|
||||
} = require('./monitor/utils.js')(s,config,lang)
|
||||
const {
|
||||
sliceVideo,
|
||||
|
@ -39,6 +40,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
reEncodeVideoAndReplace,
|
||||
reEncodeVideoAndBinOriginalAddToQueue,
|
||||
getVideosBasedOnTagFoundInMatrixOfAssociatedEvent,
|
||||
mergeVideosAndBin,
|
||||
} = require('./video/utils.js')(s,config,lang)
|
||||
s.renderPage = function(req,res,paths,passables,callback){
|
||||
passables.window = {}
|
||||
|
@ -915,17 +917,39 @@ module.exports = function(s,config,lang,app,io){
|
|||
const monitorConfig = s.group[groupKey].rawMonitorConfigurations[monitorId]
|
||||
const activeMonitor = s.group[groupKey].activeMonitors[monitorId]
|
||||
const substreamConfig = monitorConfig.details.substream
|
||||
const theAction = req.query.action
|
||||
if(
|
||||
substreamConfig.output
|
||||
){
|
||||
if(!activeMonitor.subStreamProcess){
|
||||
response.ok = true
|
||||
activeMonitor.allowDestroySubstream = false;
|
||||
spawnSubstreamProcess(monitorConfig)
|
||||
}else{
|
||||
activeMonitor.allowDestroySubstream = true
|
||||
await destroySubstreamProcess(activeMonitor)
|
||||
switch(theAction){
|
||||
case'status':
|
||||
response.ok = true
|
||||
response.isRunning = !!activeMonitor.subStreamProcess;
|
||||
response.channel = activeMonitor.subStreamChannel;
|
||||
break;
|
||||
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{
|
||||
response.msg = lang['Invalid Settings']
|
||||
}
|
||||
|
@ -1970,7 +1994,69 @@ module.exports = function(s,config,lang,app,io){
|
|||
res.end(s.prettyPrint(response));
|
||||
})
|
||||
},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
|
||||
*/
|
||||
|
|
|
@ -56,8 +56,11 @@ module.exports = function(__dirname, config){
|
|||
if(!config.hostPort){config.hostPort = 8082}
|
||||
if(config.systemLog === undefined){config.systemLog = true}
|
||||
if(config.connectionType === undefined)config.connectionType = 'websocket'
|
||||
const imageBuffers = {}
|
||||
s = {
|
||||
group: {},
|
||||
monitors: {},
|
||||
monitorInfo: {},
|
||||
dir: {},
|
||||
isWin: (process.platform === 'win32'),
|
||||
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})
|
||||
}
|
||||
break;
|
||||
case'init_monitor':
|
||||
retryConnection = 0
|
||||
if(s.group[d.ke] && s.group[d.ke][d.id]){
|
||||
s.group[d.ke][d.id].numberOfTriggers = 0
|
||||
delete(s.group[d.ke][d.id].cords)
|
||||
delete(s.group[d.ke][d.id].buffer)
|
||||
s.onCameraInitExtensions.forEach((extender) => {
|
||||
extender(d,cn,tx)
|
||||
})
|
||||
case'monitorUpdate':
|
||||
var monitorConfig = d.monitorConfig;
|
||||
var groupKey = monitorConfig.ke;
|
||||
var monitorId = monitorConfig.mid;
|
||||
var monitorDetails = monitorConfig.details;
|
||||
var monitorKey = `${groupKey}${monitorId}`
|
||||
if(!s.monitors[monitorKey])s.monitors[monitorKey] = Object.assign({}, monitorConfig);
|
||||
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;
|
||||
case'frameFromRam':
|
||||
if(!s.group[d.ke]){
|
||||
|
@ -239,29 +252,22 @@ module.exports = function(__dirname, config){
|
|||
break;
|
||||
case'frame':
|
||||
try{
|
||||
if(!s.group[d.ke]){
|
||||
s.group[d.ke]={}
|
||||
}
|
||||
if(!s.group[d.ke][d.id]){
|
||||
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];
|
||||
const monitorKey = `${d.id}${d.ke}`;
|
||||
imageBuffers[monitorKey]
|
||||
if(!imageBuffers[monitorKey]){
|
||||
imageBuffers[monitorKey] = [d.frame];
|
||||
}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){
|
||||
var buffer = Buffer.concat(s.group[d.ke][d.id].buffer);
|
||||
var buffer = Buffer.concat(imageBuffers[monitorKey]);
|
||||
processImage(buffer,d,tx)
|
||||
s.group[d.ke][d.id].buffer = null
|
||||
imageBuffers[monitorKey] = null
|
||||
}
|
||||
}catch(err){
|
||||
if(err){
|
||||
s.systemLog(err)
|
||||
delete(s.group[d.ke][d.id].buffer)
|
||||
delete(imageBuffers[monitorKey])
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -32,8 +32,11 @@ module.exports = function(__dirname, config){
|
|||
if(!config.hostPort){config.hostPort = 8082}
|
||||
if(config.systemLog === undefined){config.systemLog = true}
|
||||
if(config.connectionType === undefined)config.connectionType = 'websocket'
|
||||
const imageBuffers = {}
|
||||
s = {
|
||||
group: {},
|
||||
monitors: {},
|
||||
monitorInfo: {},
|
||||
dir: {},
|
||||
isWin: (process.platform === 'win32'),
|
||||
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})
|
||||
}
|
||||
break;
|
||||
case'init_monitor':
|
||||
retryConnection = 0
|
||||
if(s.group[d.ke] && s.group[d.ke][d.id]){
|
||||
s.group[d.ke][d.id].numberOfTriggers = 0
|
||||
delete(s.group[d.ke][d.id].cords)
|
||||
delete(s.group[d.ke][d.id].buffer)
|
||||
s.onCameraInitExtensions.forEach((extender) => {
|
||||
extender(d,cn,tx)
|
||||
})
|
||||
case'monitorUpdate':
|
||||
var monitorConfig = d.monitorConfig;
|
||||
var groupKey = monitorConfig.ke;
|
||||
var monitorId = monitorConfig.mid;
|
||||
var monitorDetails = monitorConfig.details;
|
||||
var monitorKey = `${groupKey}${monitorId}`
|
||||
if(!s.monitors[monitorKey])s.monitors[monitorKey] = Object.assign({}, monitorConfig);
|
||||
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;
|
||||
case'frameFromRam':
|
||||
if(!s.group[d.ke]){
|
||||
|
@ -214,29 +227,22 @@ module.exports = function(__dirname, config){
|
|||
break;
|
||||
case'frame':
|
||||
try{
|
||||
if(!s.group[d.ke]){
|
||||
s.group[d.ke]={}
|
||||
}
|
||||
if(!s.group[d.ke][d.id]){
|
||||
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];
|
||||
const monitorKey = `${d.id}${d.ke}`;
|
||||
imageBuffers[monitorKey]
|
||||
if(!imageBuffers[monitorKey]){
|
||||
imageBuffers[monitorKey] = [d.frame];
|
||||
}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){
|
||||
var buffer = Buffer.concat(s.group[d.ke][d.id].buffer);
|
||||
var buffer = Buffer.concat(imageBuffers[monitorKey]);
|
||||
processImage(buffer,d,tx)
|
||||
s.group[d.ke][d.id].buffer = null
|
||||
imageBuffers[monitorKey] = null
|
||||
}
|
||||
}catch(err){
|
||||
if(err){
|
||||
s.systemLog(err)
|
||||
delete(s.group[d.ke][d.id].buffer)
|
||||
delete(imageBuffers[monitorKey])
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const fetch = require('node-fetch');
|
||||
const AdmZip = require('adm-zip');
|
||||
const unzipper = require('unzipper');
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
|
||||
|
@ -21,13 +21,14 @@ async function fetchAndDownloadFolder(folderName) {
|
|||
throw new Error(`Failed to download folder: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const buffer = await response.buffer();
|
||||
const zip = new AdmZip(buffer);
|
||||
|
||||
// Extract the ZIP to a temporary location
|
||||
// Create a temporary path for extraction
|
||||
const tempExtractPath = path.join(process.cwd(), 'temp_extracted');
|
||||
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
|
||||
const extractedFolder = (await fs.readdir(tempExtractPath)).find(dir => dir.endsWith(folderName));
|
||||
|
@ -66,3 +67,5 @@ if (!folderName) {
|
|||
console.error('Usage: node script.js <folderName>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
fetchAndDownloadFolder(folderName);
|
||||
|
|
|
@ -12,3 +12,6 @@
|
|||
border: 1px solid #009dff;
|
||||
border-radius: 10px;
|
||||
}
|
||||
#superPluginManager .badge-success {
|
||||
background-color: #2fa523;
|
||||
}
|
||||
|
|
|
@ -1102,3 +1102,11 @@ function makeButton({ color, link, text, class: classes}){
|
|||
function replaceBrokenImage(_this){
|
||||
$(_this).attr('src', `${libURL}/libs/img/bg.jpg`)
|
||||
}
|
||||
function getQueryString(){
|
||||
var theObject = {}
|
||||
location.search.substring(1).split('&').forEach(function(string){
|
||||
var parts = string.split('=')
|
||||
theObject[parts[0]] = parts[1]
|
||||
})
|
||||
return theObject
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ var loadedLiveGrids = {}
|
|||
var monitorPops = {}
|
||||
var liveGridElements = {}
|
||||
var runningJpegStreams = {}
|
||||
var containerElement = $(`#monitors_live`)
|
||||
var liveGrid = $('#monitors_live .stream-element-container')
|
||||
var websocketPath = checkCorrectPathEnding(urlPrefix) + 'socket.io'
|
||||
//
|
||||
|
@ -75,7 +76,7 @@ function buildLiveGridBlock(monitor){
|
|||
var monitorDetails = safeJsonParse(monitor.details)
|
||||
var monitorLiveId = `monitor_live_${monitor.mid}`
|
||||
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 streamBlockInfo = definitions['Monitor Stream Window']
|
||||
if(!loadedLiveGrids[monitor.mid])loadedLiveGrids[monitor.mid] = {}
|
||||
|
@ -99,21 +100,18 @@ function buildLiveGridBlock(monitor){
|
|||
|
||||
function drawLiveGridBlock(monitorConfig,subStreamChannel){
|
||||
var monitorId = monitorConfig.mid
|
||||
if($('#monitor_live_' + monitorId).length === 0){
|
||||
var html = buildLiveGridBlock(monitorConfig)
|
||||
liveGrid.html(html);
|
||||
console.log(liveGrid.length,html)
|
||||
var theBlock = $('#monitor_live_' + monitorId);
|
||||
var streamElement = theBlock.find('.stream-element')
|
||||
liveGridElements[monitorId] = {
|
||||
monitorItem: theBlock,
|
||||
streamElement: streamElement,
|
||||
eventObjects: theBlock.find('.stream-objects'),
|
||||
motionMeter: theBlock.find('.indifference .progress-bar'),
|
||||
motionMeterText: theBlock.find('.indifference .progress-bar span'),
|
||||
width: streamElement.width(),
|
||||
height: streamElement.height(),
|
||||
}
|
||||
var html = buildLiveGridBlock(monitorConfig)
|
||||
liveGrid.html(html);
|
||||
var theBlock = $('#monitor_live_' + monitorId);
|
||||
var streamElement = theBlock.find('.stream-element')
|
||||
liveGridElements[monitorId] = {
|
||||
monitorItem: theBlock,
|
||||
streamElement: streamElement,
|
||||
eventObjects: theBlock.find('.stream-objects'),
|
||||
motionMeter: theBlock.find('.indifference .progress-bar'),
|
||||
motionMeterText: theBlock.find('.indifference .progress-bar span'),
|
||||
width: streamElement.width(),
|
||||
height: streamElement.height(),
|
||||
}
|
||||
initiateLiveGridPlayer(loadedMonitors[monitorId],subStreamChannel)
|
||||
}
|
||||
|
@ -131,15 +129,15 @@ function unmuteVideoPlayer(){
|
|||
},3000)
|
||||
$('.unmute-embed-audio').remove()
|
||||
}
|
||||
function initiateLiveGridPlayer(monitor,subStreamChannel){
|
||||
function initiateLiveGridPlayer(monitor){
|
||||
var livePlayerElement = loadedLiveGrids[monitor.mid]
|
||||
var details = monitor.details
|
||||
var groupKey = monitor.ke
|
||||
var monitorId = monitor.mid
|
||||
var loadedMonitor = loadedMonitors[monitorId]
|
||||
var loadedPlayer = loadedLiveGrids[monitor.mid]
|
||||
var containerElement = $(`#monitor_live_${monitor.mid}`)
|
||||
var streamType = subStreamChannel ? details.substream ? details.substream.output.stream_type : 'hls' : details.stream_type
|
||||
var subStreamChannel = loadedMonitor.subStreamChannel
|
||||
var streamType = details.stream_type === 'useSubstream' ? details.substream.output.stream_type : details.stream_type
|
||||
switch(streamType){
|
||||
case'jpeg':
|
||||
startJpegStream(monitorId)
|
||||
|
@ -542,10 +540,20 @@ function toggleSubStream(monitorId,callback){
|
|||
}
|
||||
if(monitor.subStreamToggleLock)return false;
|
||||
monitor.subStreamToggleLock = true
|
||||
$.getJSON(getApiPrefix() + '/toggleSubstream/'+$user.ke+'/'+monitorId,function(d){
|
||||
monitor.subStreamToggleLock = false
|
||||
debugLog(d)
|
||||
if(callback)callback()
|
||||
var substreamUrl = getApiPrefix() + '/toggleSubstream/'+$user.ke+'/'+monitorId;
|
||||
$.getJSON(`${substreamUrl}?action=status`,function(response){
|
||||
if(!response.isRunning){
|
||||
$.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){
|
||||
|
@ -595,8 +603,8 @@ $(document).ready(function(e){
|
|||
case'monitor_watch_on':
|
||||
var monitorId = d.mid || d.id
|
||||
var loadedMonitor = loadedMonitors[monitorId]
|
||||
var subStreamChannel = d.subStreamChannel
|
||||
if(!loadedMonitor.subStreamChannel && loadedMonitor.details.stream_type === 'useSubstream'){
|
||||
var subStreamChannel = loadedMonitor.subStreamChannel || d.channel
|
||||
if(loadedMonitor.details.stream_type === 'useSubstream'){
|
||||
toggleSubStream(monitorId,function(){
|
||||
drawLiveGridBlock(loadedMonitors[monitorId],subStreamChannel)
|
||||
})
|
||||
|
@ -619,7 +627,6 @@ $(document).ready(function(e){
|
|||
$('body').addClass('jpegMode')
|
||||
break;
|
||||
case'detector_trigger':
|
||||
console.log(d)
|
||||
var monitorId = d.id
|
||||
var liveGridElement = liveGridElements[monitorId]
|
||||
if(!window.dontShowDetection && liveGridElement){
|
||||
|
|
|
@ -73,3 +73,11 @@ function drawMatrices(event,options){
|
|||
})
|
||||
theContainer.append(html)
|
||||
}
|
||||
function getQueryString(){
|
||||
var theObject = {}
|
||||
location.search.substring(1).split('&').forEach(function(string){
|
||||
var parts = string.split('=')
|
||||
theObject[parts[0]] = parts[1]
|
||||
})
|
||||
return theObject
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ var monSectionPresets = $('#monSectionPresets')
|
|||
var copySettingsSelector = $('#copy_settings')
|
||||
var monitorPresetsSelection = $('#monitorPresetsSelection')
|
||||
var monitorPresetsNameField = $('#monitorPresetsName')
|
||||
var detectorsSelected = $('#detectorsSelected')
|
||||
var monitorsList = monitorEditorWindow.find('.monitors_list')
|
||||
var editorForm = monitorEditorWindow.find('form')
|
||||
var tagsInput = monitorEditorWindow.find('[name="tags"]')
|
||||
|
@ -566,7 +567,37 @@ function drawInputMapSelectorHtml(options,parent){
|
|||
</div>`
|
||||
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 monitorId = monitorConfig.mid
|
||||
var monitorDetails = safeJsonParse(monitorConfig.details);
|
||||
|
@ -686,6 +717,9 @@ function importIntoMonitorEditor(options){
|
|||
}
|
||||
}
|
||||
});
|
||||
//
|
||||
await getPluginsList(monitorConfig)
|
||||
//
|
||||
copySettingsSelector.val('0').change()
|
||||
var tmp = '';
|
||||
$.each(loadedMonitors,function(n,monitor){
|
||||
|
@ -1319,9 +1353,11 @@ editorForm.find('[name="type"]').change(function(e){
|
|||
break;
|
||||
case'detector_plugged':
|
||||
addDetectorPlugin(d.plug,d)
|
||||
if(monitorEditorSelectedMonitor)getPluginsList(monitorEditorSelectedMonitor);
|
||||
break;
|
||||
case'detector_unplugged':
|
||||
removeDetectorPlugin(d.plug)
|
||||
if(monitorEditorSelectedMonitor)getPluginsList(monitorEditorSelectedMonitor);
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
@ -1335,6 +1371,7 @@ editorForm.find('[name="type"]').change(function(e){
|
|||
drawMonitorListToSelector(monitorsList.find('optgroup'),false,'host')
|
||||
monitorsList.val(theSelected)
|
||||
checkToOpenSideMenu()
|
||||
if(monitorEditorSelectedMonitor)getPluginsList(monitorEditorSelectedMonitor)
|
||||
}
|
||||
addOnTabAway('monitorSettings', function(){
|
||||
if(isSideBarMenuCollapsed()){
|
||||
|
|
|
@ -1168,6 +1168,9 @@ function getRunningMonitors(asArray){
|
|||
})
|
||||
return asArray ? Object.values(foundMonitors) : foundMonitors
|
||||
}
|
||||
function buildFileBinUrl(data){
|
||||
return apiBaseUrl + '/fileBin/' + data.ke + '/' + data.mid + '/' + data.name
|
||||
}
|
||||
$(document).ready(function(){
|
||||
$('body')
|
||||
.on('click','[system]',function(){
|
||||
|
|
|
@ -311,9 +311,6 @@ $(document).ready(function(e){
|
|||
function downloadTimelapseFrame(frame){
|
||||
downloadFile(frame.href,frame.filename)
|
||||
}
|
||||
function buildFileBinUrl(data){
|
||||
return apiBaseUrl + '/fileBin/' + data.ke + '/' + data.mid + '/' + data.name
|
||||
}
|
||||
function downloadTimelapseVideo(data){
|
||||
var downloadUrl = buildFileBinUrl(data)
|
||||
downloadFile(downloadUrl,data.name)
|
||||
|
|
|
@ -442,35 +442,94 @@ function loadEventsData(videoEvents){
|
|||
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){
|
||||
return new Promise((resolve,reject) => {
|
||||
options = options ? 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
|
||||
// var startDate = options.startDate
|
||||
// var endDate = options.endDate
|
||||
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`)
|
||||
}
|
||||
const {
|
||||
searchQuery,
|
||||
monitorId,
|
||||
archived,
|
||||
customVideoSet,
|
||||
limit,
|
||||
eventLimit,
|
||||
doLimitOnFames,
|
||||
eventStartTime,
|
||||
eventEndTime,
|
||||
requestQueries,
|
||||
} = getVideoSearchRequestQueries(options);
|
||||
$.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) => {
|
||||
return Object.assign({},video,{
|
||||
|
|
|
@ -189,7 +189,7 @@ $(document).ready(function(e){
|
|||
<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.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: `
|
||||
${file.ext ? `<span class="badge badge-${file.ext ==='webm' ? `primary` : 'danger'}">${file.ext}</span>` : ''}
|
||||
${!isLocalVideo ? `<span class="badge badge-success">${file.type}</span>` : ''}
|
||||
|
@ -274,6 +274,43 @@ $(document).ready(function(e){
|
|||
var downloadUrl = buildNewFileLink(data)
|
||||
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')
|
||||
.on('click','.open-videosTable',function(e){
|
||||
e.preventDefault()
|
||||
|
@ -307,6 +344,11 @@ $(document).ready(function(e){
|
|||
zipVideosAndDownloadWithConfirm(videos)
|
||||
return false;
|
||||
})
|
||||
.on('click','.merge-selected-videos',function(e){
|
||||
e.preventDefault()
|
||||
mergeSelectedVideos();
|
||||
return false;
|
||||
})
|
||||
.on('click','.refresh-data',function(e){
|
||||
e.preventDefault()
|
||||
drawVideosTableViewElements()
|
||||
|
@ -410,6 +452,14 @@ $(document).ready(function(e){
|
|||
})
|
||||
onWebSocketEvent((data) => {
|
||||
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_cloud':
|
||||
if(tabTree.name === 'videosTableView'){
|
||||
|
|
|
@ -10,6 +10,14 @@ $(document).ready(function(){
|
|||
var theWindow = $(window);
|
||||
var lastWindowWidth = theWindow.width()
|
||||
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){
|
||||
if(userHasSubscribed){
|
||||
return true
|
||||
|
@ -61,6 +69,7 @@ $(document).ready(function(){
|
|||
|
||||
function selectMonitor(monitorId, css){
|
||||
css = css || {};
|
||||
var embedHost = getQueryString().host || `/`;
|
||||
var isSelected = selectedMonitors[monitorId]
|
||||
if(isSelected)return;
|
||||
var numberOfSelected = Object.keys(selectedMonitors)
|
||||
|
@ -69,7 +78,7 @@ $(document).ready(function(){
|
|||
}
|
||||
++selectedMonitorsCount
|
||||
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}"]`)
|
||||
.draggable({
|
||||
grid: [40, 40],
|
||||
|
|
|
@ -1,21 +1,69 @@
|
|||
$(document).ready(function(){
|
||||
var loadedModules = {}
|
||||
var downloadablePlugins = {}
|
||||
var theEnclosure = $('#superPluginManager')
|
||||
var listElement = $('#pluginManagerList')
|
||||
var downloadListElement = $('#pluginManagerDownloadble')
|
||||
var quickSelect = $('#pluginQuickSelect')
|
||||
var pluginDownloadForm = $('#downloadNewPlugin')
|
||||
var pluginCommandLine = $('#pluginCommandLine')
|
||||
var pluginDownloadableSearch = $('#pluginManagerDownloadbleListSearch')
|
||||
var getModules = function(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) {
|
||||
return new Promise((resolve,reject) => {
|
||||
const pluginListUrl = `https://cdn.shinobi.video/plugins/list.json`
|
||||
$.getJSON(pluginListUrl,function(data){
|
||||
var html = ''
|
||||
$.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)
|
||||
})
|
||||
})
|
||||
|
@ -99,7 +147,11 @@ $(document).ready(function(){
|
|||
$.post(superApiPrefix + $user.sessionKey + '/plugins/download',{
|
||||
downloadUrl: url,
|
||||
packageRoot: packageRoot,
|
||||
},callback)
|
||||
},function(data){
|
||||
setTimeout(function(){
|
||||
callback(data)
|
||||
},3000)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -328,13 +380,14 @@ $(document).ready(function(){
|
|||
break;
|
||||
}
|
||||
})
|
||||
pluginDownloadForm.submit(function(e){
|
||||
e.preventDefault();
|
||||
var el = $(this)
|
||||
var form = el.serializeObject()
|
||||
downloadModule(form.downloadUrl,form.packageRoot,function(data){
|
||||
console.log(data)
|
||||
theEnclosure.on('click','[dl-plugin] .download',function(e){
|
||||
var pluginName = $(this).parents('[dl-plugin]').attr('dl-plugin')
|
||||
console.log(pluginName)
|
||||
var theDlPlugin = downloadablePlugins[pluginName]
|
||||
downloadModule(theDlPlugin.link,theDlPlugin.dir,function(data){
|
||||
if(data.ok){
|
||||
$('[data-bs-target="#pluginManagerList"],#pluginManagerList').addClass('active')
|
||||
$('[data-bs-target="#pluginManagerDownloadbleList"],#pluginManagerDownloadbleList').removeClass('active')
|
||||
var theModule = data.newModule
|
||||
theModule.config.enabled = false
|
||||
drawModuleBlock(theModule)
|
||||
|
@ -351,7 +404,6 @@ $(document).ready(function(){
|
|||
}
|
||||
}
|
||||
})
|
||||
return false
|
||||
})
|
||||
$('#pluginQuickSelectExec').click(function(){
|
||||
var currentVal = quickSelect.val()
|
||||
|
@ -362,6 +414,10 @@ $(document).ready(function(){
|
|||
pluginDownloadForm.find(`[name="packageRoot"]`).val(packageRoot)
|
||||
pluginDownloadForm.submit()
|
||||
})
|
||||
pluginDownloadableSearch.keyup(function(){
|
||||
var searchQuery = $(this).val()
|
||||
filterDownloadablePlugins(searchQuery)
|
||||
})
|
||||
function getObjectAlphabetically(theObject,key){
|
||||
return Object.values(theObject).sort(function( a, b ) {
|
||||
const aName = new Date(a[key]).getTime()
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
<li><a class="dropdown-item unarchive-selected-videos cursor-pointer"><%- lang.Unarchive %></a></li>
|
||||
<li><a class="dropdown-item 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 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><a class="dropdown-item delete-selected-videos cursor-pointer"><%- lang.Delete %></a></li>
|
||||
|
|
|
@ -1,29 +1,18 @@
|
|||
<div class="row">
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card bg-dark grey mt-1">
|
||||
<div class="card-header">
|
||||
<%- lang['Download Plugins'] %>
|
||||
</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>
|
||||
<%- lang['Plugin Manager'] %>
|
||||
</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 class="col-md-6">
|
||||
|
@ -40,8 +29,16 @@
|
|||
</form>
|
||||
</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>
|
||||
<link rel="stylesheet" href="<%-window.libURL%>assets/css/super.pluginManager.css">
|
||||
<script src="<%-window.libURL%>assets/js/super.pluginManager.js" type="text/javascript"></script>
|
||||
|
|
Loading…
Reference in New Issue