diff --git a/conf.sample.json b/conf.sample.json
index 26bcbf9e..cb375061 100644
--- a/conf.sample.json
+++ b/conf.sample.json
@@ -25,7 +25,7 @@
"auth": {
"user": "your_email@gmail.com",
"pass": "your_password_or_app_specific_password"
- }
+ }
},
"cron":{},
"pluginKeys":{}
diff --git a/languages/en_CA.json b/languages/en_CA.json
index d637a950..54047166 100644
--- a/languages/en_CA.json
+++ b/languages/en_CA.json
@@ -12,6 +12,8 @@
"accountAdded": "Account Added",
"accountAddedText": "Account has been added.",
"monitorDeleted": "Monitor Deleted",
+ "accountCreationError": "Account Creation Error",
+ "accountEditError": "Account Edit Error",
"Unmute": "Unmute",
"byUser": "by user",
"accountDeleted": "Account Deleted",
@@ -34,7 +36,6 @@
"Active Monitors": "Active Monitors",
"Storage Use": "Storage Use",
"Use Raw Snapshot": "Use Raw Snapshot",
- "Account Edited": "Account Edited",
"Failed to Edit Account": "Failed to Edit Account",
"How to Connect": "How to Connect",
"Login": "Login",
@@ -559,7 +560,14 @@
"InvalidJSONText": "Please ensure this is a valid JSON string for Shinobi monitor configuration.",
"Passwords don't match": "Passwords don't match",
"Email address is in use.": "Email address is in use.",
+ "Account Created": "Account Created",
+ "Account Edited": "Account Edited",
+ "Compression Info": "Compression Info",
+ "Compression Error": "Compression Error",
"Group Key is in use.": "Group Key is in use.",
+ "adminAccountCreatedMsg": "Your account has been created. Access the account at the primary login page (Non-Superuser).",
+ "adminAccountEditedMsg": "Your account has been edited. You may need to restart Shinobi for some changes to take effect.",
+ "createSubAccountsInfo": "If you are looking to share cameras with another account you may create them in the Sub-Account manager of your non-superuser Dashboard.",
"Create Sub-Accounts at /admin": "Create Sub-Accounts at /admin",
"No Events found for this video": "No Events found for this video",
"Video and Time Span (Minutes)": "Video and Time Span (Minutes)",
@@ -1057,6 +1065,7 @@
"Monitor mode is already": "Monitor mode is already",
"Monitor or Key does not exist.": "Monitor or Key does not exist.",
"No Group with this key exists": "No Group with this key exists",
+ "Group with this key exists already": "Group with this key exists already",
"Downloaded!": "Downloaded!",
"Downloading...": "Downloading...",
"Loading...": "Loading...",
diff --git a/libs/control/ptz.js b/libs/control/ptz.js
index 0e18f7a9..708da2e3 100644
--- a/libs/control/ptz.js
+++ b/libs/control/ptz.js
@@ -360,10 +360,19 @@ module.exports = function(s,config,lang){
})
},7000)
}
- const getLargestMatrix = (matrices) => {
+ const getLargestMatrix = (matrices,imgWidth,imgHeight) => {
var largestMatrix = {width: 0, height: 0}
+ const maxWidth = imgWidth * 0.95;
+ const maxHeight = imgHeight * 0.95;
matrices.forEach((matrix) => {
- if(matrix.width > largestMatrix.width && matrix.height > largestMatrix.height)largestMatrix = matrix
+ const matrixWidth = matrix.width
+ const matrixHeight = matrix.height
+ if(
+ matrixWidth > largestMatrix.width &&
+ matrixHeight > largestMatrix.height &&
+ matrixWidth <= maxWidth &&
+ matrixHeight <= maxHeight
+ )largestMatrix = matrix;
})
return largestMatrix.x ? largestMatrix : null
}
@@ -377,7 +386,7 @@ module.exports = function(s,config,lang){
const imageCenterX = imgWidth / 2
const imageCenterY = imgHeight / 2
const matrices = event.details.matrices || []
- const largestMatrix = getLargestMatrix(matrices.filter(matrix => trackingTarget.indexOf(matrix.tag) > -1))
+ const largestMatrix = getLargestMatrix(matrices.filter(matrix => trackingTarget.indexOf(matrix.tag) > -1),imgWidth,imgHeight)
if(!largestMatrix)return;
const monitorConfig = s.group[event.ke].rawMonitorConfigurations[event.id]
const invertedVerticalAxis = monitorConfig.details.control_invert_y === '1'
@@ -414,6 +423,7 @@ module.exports = function(s,config,lang){
}
}
function setHomePositionPreset(e){
+ const monitorId = e.mid || e.id
return new Promise((resolve) => {
setTimeout(() => {
setPresetForCurrentPosition({
diff --git a/libs/cron.js b/libs/cron.js
index b6b60b4a..55e9065c 100644
--- a/libs/cron.js
+++ b/libs/cron.js
@@ -7,7 +7,9 @@ module.exports = (s,config,lang) => {
if(config.doCronAsWorker===undefined)config.doCronAsWorker = true;
const startWorker = () => {
const pathToWorkerScript = __dirname + `/cron/worker.js`
- const workerProcess = new Worker(pathToWorkerScript)
+ const workerProcess = new Worker(pathToWorkerScript,{
+ workerData: config
+ })
workerProcess.on('message',function(data){
if(data.time === 'moment()')data.time = moment();
switch(data.f){
diff --git a/libs/cron/worker.js b/libs/cron/worker.js
index e7a08a58..0c011f72 100644
--- a/libs/cron/worker.js
+++ b/libs/cron/worker.js
@@ -3,8 +3,8 @@ const path = require('path');
const moment = require('moment');
const exec = require('child_process').exec;
const spawn = require('child_process').spawn;
-const { parentPort, isMainThread } = require('worker_threads');
-const config = require(process.cwd() + '/conf.json')
+const { parentPort, isMainThread, workerData } = require('worker_threads');
+const config = workerData
process.on('uncaughtException', function (err) {
errorLog('uncaughtException',err);
});
diff --git a/libs/events/utils.js b/libs/events/utils.js
index 4350b9d3..679824b5 100644
--- a/libs/events/utils.js
+++ b/libs/events/utils.js
@@ -590,9 +590,6 @@ module.exports = (s,config,lang,app,io) => {
if(autoCompressionEnabled){
reEncodeVideoAndBinOriginalAddToQueue({
video: response.insertQuery,
- targetVideoCodec: 'vp9',
- targetAudioCodec: 'libopus',
- targetQuality: '-q:v 1 -q:a 1',
targetExtension: 'webm',
doSlowly: false,
automated: true,
diff --git a/libs/monitor/utils.js b/libs/monitor/utils.js
index 67958b1a..1b3147b7 100644
--- a/libs/monitor/utils.js
+++ b/libs/monitor/utils.js
@@ -73,6 +73,7 @@ module.exports = (s,config,lang) => {
doResolve(response)
}
try{
+ proc.removeAllListeners()
proc.on('exit',() => {
response.msg = 'proc.on.exit'
clearTimeout(killTimer)
@@ -148,6 +149,7 @@ module.exports = (s,config,lang) => {
clearInterval(activeMonitor.getMonitorCpuUsage);
clearInterval(activeMonitor.objectCountIntervals);
clearTimeout(activeMonitor.timeoutToRestart)
+ clearTimeout(activeMonitor.fatalErrorTimeout);
delete(activeMonitor.onvifConnection)
// if(activeMonitor.onChildNodeExit){
// activeMonitor.onChildNodeExit()
@@ -709,7 +711,7 @@ module.exports = (s,config,lang) => {
clearTimeout(activeMonitor.trigger_timer)
delete(activeMonitor.trigger_timer)
clearInterval(activeMonitor.detector_notrigger_timeout)
- clearTimeout(activeMonitor.err_fatal_timeout);
+ clearTimeout(activeMonitor.fatalErrorTimeout);
activeMonitor.isStarted = false
activeMonitor.isRecording = false
s.tx({f:'monitor_stopping',mid:monitorId,ke:groupKey,time:s.formattedTime()},'GRP_'+groupKey);
@@ -1119,7 +1121,7 @@ module.exports = (s,config,lang) => {
}
if(e.details.record_timelapse === '1'){
var timelapseRecordingDirectory = s.getTimelapseFrameDirectory(e)
- activeMonitor.spawn.stdio[7].on('data',function(data){
+ activeMonitor.spawn.stdio[7].on('data', async function(data){
var fileStream = activeMonitor.recordTimelapseWriter
if(!fileStream){
var currentDate = s.formattedTime(null,'YYYY-MM-DD')
@@ -1219,7 +1221,7 @@ module.exports = (s,config,lang) => {
activeMonitor.spawn.stdout.on('data',frameToStreamPrimary)
}
if(e.details.stream_channels && e.details.stream_channels !== ''){
- e.details.stream_channels.forEach((fields,number) => {
+ s.parseJSON(e.details.stream_channels,{}).forEach((fields,number) => {
attachStreamChannelHandlers({
ke: groupKey,
mid: monitorId,
@@ -1267,9 +1269,6 @@ module.exports = (s,config,lang) => {
s.debugLog('Queue Automatic Compression',response.insertQuery)
reEncodeVideoAndBinOriginalAddToQueue({
video: response.insertQuery,
- targetVideoCodec: 'vp9',
- targetAudioCodec: 'libopus',
- targetQuality: '-q:v 1 -q:a 1',
targetExtension: 'webm',
doSlowly: false,
automated: true,
@@ -1285,6 +1284,19 @@ module.exports = (s,config,lang) => {
}
})
}
+ async function doFatalErrorCatch(e,d){
+ const groupKey = e.ke
+ const monitorId = e.mid || e.id
+ if(activeMonitor.isStarted === true){
+ const activeMonitor = getActiveMonitor(groupKey,monitorId)
+ activeMonitor.isStarted = false
+ await cameraDestroy(e)
+ activeMonitor.isStarted = true
+ fatalError(e,d)
+ }else{
+ await cameraDestroy(e)
+ }
+ }
function cameraFilterFfmpegLog(e){
var checkLog = function(d,x){return d.indexOf(x)>-1}
const groupKey = e.ke
@@ -1323,30 +1335,18 @@ module.exports = (s,config,lang) => {
break;
case checkLog(d,'Connection refused'):
case checkLog(d,'Connection timed out'):
- //restart
- activeMonitor.timeoutToRestart = setTimeout(function(){
- s.userLog(e,{type:lang['Connection timed out'],msg:lang['Retrying...']});
- fatalError(e,'Connection timed out');
- },1000)
- break;
case checkLog(d,'Immediate exit requested'):
- await cameraDestroy(e)
- activeMonitor.timeoutToRestart = setTimeout(() => {
- launchMonitorProcesses(e)
- },15000)
- break;
case checkLog(d,'mjpeg_decode_dc'):
case checkLog(d,'bad vlc'):
+ case checkLog(d,'does not contain an image sequence pattern or a pattern is invalid.'):
case checkLog(d,'error dc'):
- await cameraDestroy(e)
- activeMonitor.timeoutToRestart = setTimeout(() => {
- launchMonitorProcesses(e)
- },15000)
+ // activeMonitor.timeoutToRestart = setTimeout(() => {
+ // doFatalErrorCatch(e,d)
+ // },15000)
break;
case checkLog(d,'No route to host'):
- await cameraDestroy(e)
- activeMonitor.timeoutToRestart = setTimeout(() => {
- launchMonitorProcesses(e)
+ activeMonitor.timeoutToRestart = setTimeout(async () => {
+ doFatalErrorCatch(e,d)
},60000)
break;
}
@@ -1425,6 +1425,7 @@ module.exports = (s,config,lang) => {
const monitorConfig = theGroup.rawMonitorConfigurations[monitorId]
const doPingTest = e.type !== 'socket' && e.type !== 'dashcam' && e.protocol !== 'udp' && e.type !== 'local' && e.details.skip_ping !== '1';
const startMonitorInQueue = theGroup.startMonitorInQueue
+ if(!activeMonitor.isStarted)return;
// e = monitor object
clearTimeout(activeMonitor.resetFatalErrorCountTimer)
activeMonitor.resetFatalErrorCountTimer = setTimeout(()=>{
@@ -1601,17 +1602,18 @@ module.exports = (s,config,lang) => {
const groupKey = e.ke
const monitorId = e.mid || e.id
const activeMonitor = getActiveMonitor(groupKey,monitorId)
- const monitorDetails = getMonitorConfiguration(groupKey,monitorId).details
+ const monitorConfig = copyMonitorConfiguration(groupKey,monitorId)
+ const monitorDetails = monitorConfig.details
const maxCount = !monitorDetails.fatal_max || isNaN(monitorDetails.fatal_max) ? 0 : parseFloat(monitorDetails.fatal_max);
- clearTimeout(activeMonitor.err_fatal_timeout);
+ clearTimeout(activeMonitor.fatalErrorTimeout);
++activeMonitor.errorFatalCount;
if(activeMonitor.isStarted === true){
- activeMonitor.err_fatal_timeout = setTimeout(function(){
+ activeMonitor.fatalErrorTimeout = setTimeout(function(){
if(maxCount !== 0 && activeMonitor.errorFatalCount > maxCount){
s.userLog(e,{type:lang["Fatal Error"],msg:lang.onFatalErrorExit});
s.camera('stop',{mid:monitorId,ke:groupKey})
}else{
- launchMonitorProcesses(s.cleanMonitorObject(e))
+ launchMonitorProcesses(monitorConfig)
};
},5000);
}else{
@@ -1623,7 +1625,6 @@ module.exports = (s,config,lang) => {
status: lang.Died,
code: 7
});
- const monitorConfig = copyMonitorConfiguration(groupKey,monitorId)
s.onMonitorDiedExtensions.forEach(function(extender){
extender(monitorConfig,e)
})
@@ -1753,6 +1754,7 @@ module.exports = (s,config,lang) => {
copyMonitorConfiguration,
getMonitorConfiguration,
isGroupBelowMaxMonitorCount,
+ setNoEventsDetector,
cameraDestroy: cameraDestroy,
createSnapshot: createSnapshot,
processKill: processKill,
diff --git a/libs/notification.js b/libs/notification.js
index 296f462a..a8ad9c48 100644
--- a/libs/notification.js
+++ b/libs/notification.js
@@ -24,4 +24,5 @@ module.exports = function(s,config,lang){
require('./notifications/pushover.js')(s,config,lang,getSnapshot)
require('./notifications/webhook.js')(s,config,lang,getSnapshot)
require('./notifications/mqtt.js')(s,config,lang,getSnapshot)
+ require('./notifications/matrix.js')(s,config,lang,getSnapshot)
}
diff --git a/libs/notifications/matrix.js b/libs/notifications/matrix.js
new file mode 100644
index 00000000..81e23cd7
--- /dev/null
+++ b/libs/notifications/matrix.js
@@ -0,0 +1,386 @@
+const fs = require("fs")
+const fetch = require("node-fetch")
+module.exports = function(s,config,lang,getSnapshot){
+ const {
+ getEventBasedRecordingUponCompletion,
+ } = require('../events/utils.js')(s,config,lang)
+ //matrix bot
+ if(config.matrixBot === true){
+ const sdk = require("matrix-js-sdk")
+ try{
+ function sendFile(file,groupKey){
+ const client = s.group[groupKey].matrixBot
+ const roomId = s.group[groupKey].matrixBotRoom
+ const {
+ buffer,
+ name,
+ type,
+ info,
+ opttype,
+ } = file;
+ client.uploadContent(buffer, {
+ name: name,
+ type: opttype,
+ }).then(function(url) {
+ const content = {
+ msgtype: type || "m.file",
+ body: name,
+ info: info,
+ url: url.content_uri
+ };
+ client.sendMessage(roomId, content);
+ }).catch(err => {
+ s.userLog({
+ ke: groupKey,
+ mid: '$USER'
+ },{
+ type: 'matrix.org Error',
+ msg: err
+ });
+ })
+ }
+ const sendMessage = function(data,files,groupKey){
+ if(!data)data = {};
+ const client = s.group[groupKey].matrixBot
+ const roomId = s.group[groupKey].matrixBotRoom
+ if(!client){
+ s.userLog({ke:groupKey,mid:'$USER'},{type:'matrix.org Error'})
+ return
+ }
+ if(client.sendMessage){
+ if(data.text){
+ const sendBody = {
+ "body": data.text,
+ "msgtype": "m.text"
+ }
+ client.sendMessage(roomId, sendBody);
+ }
+ files.forEach((file) => {
+ sendFile(file,groupKey)
+ });
+ }else{
+ s.userLog({
+ ke: groupKey,
+ mid: '$USER'
+ },{
+ type: 'matrix.org Error',
+ msg: '!sendMessage'
+ });
+ }
+ }
+ const onEventTriggerBeforeFilterForMatrixBot = function(d,filter){
+ filter.matrixBot = false
+ }
+ const onEventTriggerForMatrixBot = async (d,filter) => {
+ const monitorConfig = s.group[d.ke].rawMonitorConfigurations[d.id]
+ // d = event object
+ const isEnabled = filter.matrixBot || monitorConfig.details.detector_matrixbot === '1' || monitorConfig.details.notify_matrix === '1'
+ if(s.group[d.ke].matrixBot && isEnabled && !s.group[d.ke].activeMonitors[d.id].detector_matrixbot){
+ var detector_matrixbot_timeout
+ if(!monitorConfig.details.detector_matrixbot_timeout||monitorConfig.details.detector_matrixbot_timeout===''){
+ detector_matrixbot_timeout = 1000 * 60 * 10;
+ }else{
+ detector_matrixbot_timeout = parseFloat(monitorConfig.details.detector_matrixbot_timeout) * 1000 * 60;
+ }
+ s.group[d.ke].activeMonitors[d.id].detector_matrixbot = setTimeout(function(){
+ clearTimeout(s.group[d.ke].activeMonitors[d.id].detector_matrixbot);
+ s.group[d.ke].activeMonitors[d.id].detector_matrixbot = null
+ },detector_matrixbot_timeout)
+ await getSnapshot(d,monitorConfig)
+ if(d.screenshotBuffer){
+ const imageEventText = `${lang.Event} ${d.screenshotName} ${d.currentTimestamp}`
+ sendMessage({
+ text: imageEventText,
+ },[
+ {
+ buffer: d.screenshotBuffer,
+ name: d.screenshotName+'.jpg',
+ type: 'm.image',
+ opttype: 'image/jpeg',
+ info: {
+ mimetype: 'image/jpeg',
+ },
+ }
+ ],d.ke)
+ }
+ if(monitorConfig.details.detector_matrixbot_send_video === '1'){
+ let videoPath = null
+ let videoName = null
+ const eventBasedRecording = await getEventBasedRecordingUponCompletion({
+ ke: d.ke,
+ mid: d.mid
+ })
+ if(eventBasedRecording.filePath){
+ videoPath = eventBasedRecording.filePath
+ videoName = eventBasedRecording.filename
+ }else{
+ const siftedVideoFileFromRam = await s.mergeDetectorBufferChunks(d)
+ videoPath = siftedVideoFileFromRam.filePath
+ videoName = siftedVideoFileFromRam.filename
+ }
+ if(videoPath){
+ sendMessage({},[
+ {
+ buffer: await fs.promises.readFile(videoPath),
+ name: videoName,
+ type: 'm.video',
+ opttype: 'video/mp4',
+ info: {
+ mimetype: 'video/mp4',
+ },
+ }
+ ],d.ke)
+ }
+ }
+ }
+ }
+ const onTwoFactorAuthCodeNotificationForMatrixBot = function(user){
+ // r = user
+ if(r.details.factor_matrixbot === '1'){
+ const eventText = `${user.lang['2-Factor Authentication']} : ${user.lang['Enter this code to proceed']} **${factorAuthKey}** ${user.lang.FactorAuthText1}`
+ sendMessage({
+ text: eventText,
+ },[],d.ke)
+ }
+ }
+ const loadMatrixBotForUser = function(user){
+ const userDetails = s.parseJSON(user.details);
+ //matrixbot
+ if(!s.group[user.ke].matrixBot &&
+ config.matrixBot === true &&
+ userDetails.matrixbot === '1' &&
+ userDetails.matrixbot_baseUrl &&
+ userDetails.matrixbot_accessToken &&
+ userDetails.matrixbot_userId &&
+ userDetails.matrixbot_roomId
+ ){
+ s.debugLog(`!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!`)
+ s.debugLog(`Matrix Connecting... ${userDetails.matrixbot_baseUrl}`)
+ const client = sdk.createClient({
+ baseUrl: userDetails.matrixbot_baseUrl,
+ accessToken: userDetails.matrixbot_accessToken,
+ userId: userDetails.matrixbot_userId
+ });
+ s.group[user.ke].matrixBot = client
+ s.group[user.ke].matrixBotRoom = userDetails.matrixbot_roomId
+ client.startClient();
+ client.once('sync', function(state, prevState, res) {
+ s.userLog({
+ ke: user.ke,
+ mid: '$USER'
+ },{
+ type: 'matrix.org',
+ msg: lang.Ready
+ })
+ });
+ }
+ }
+ const unloadMatrixBotForUser = function(user){
+ if(s.group[user.ke].matrixBot && s.group[user.ke].matrixBot.stopClient){
+ s.group[user.ke].matrixBot.stopClient()
+ delete(s.group[user.ke].matrixBot)
+ }
+ }
+ const onDetectorNoTriggerTimeoutForMatrixBot = function(e){
+ //e = monitor object
+ var currentTime = new Date()
+ if(e.details.detector_notrigger_matrixbot === '1'){
+ var html = '*'+lang.NoMotionEmailText2+' ' + (e.details.detector_notrigger_timeout || 10) + ' '+lang.minutes+'.*\n'
+ html += '**' + lang['Monitor Name'] + '** : '+e.name + '\n'
+ html += '**' + lang['Monitor ID'] + '** : '+e.id + '\n'
+ html += currentTime
+ const eventText = `${lang['"No Motion" Detector']} : ${html}`
+ sendMessage({
+ text: eventText,
+ },[],e.ke)
+ }
+ }
+ const onMonitorUnexpectedExitForMatrixBot = (monitorConfig) => {
+ const isEnabled = monitorConfig.details.detector_matrixbot === '1' || monitorConfig.details.notify_matrix === '1'
+ if(isEnabled && monitorConfig.details.notify_onUnexpectedExit === '1'){
+ const ffmpegCommand = s.group[monitorConfig.ke].activeMonitors[monitorConfig.mid].ffmpeg
+ const description = lang['Process Crashed for Monitor'] + '\n' + ffmpegCommand
+ const currentTime = new Date()
+ const eventText = `${lang['Process Unexpected Exit']} : ${monitorConfig.name} : ${description}`
+ sendMessage({
+ text: eventText,
+ },[],monitorConfig.ke)
+ }
+ }
+ s.loadGroupAppExtender(loadMatrixBotForUser)
+ s.unloadGroupAppExtender(unloadMatrixBotForUser)
+ s.onTwoFactorAuthCodeNotification(onTwoFactorAuthCodeNotificationForMatrixBot)
+ s.onEventTrigger(onEventTriggerForMatrixBot)
+ s.onEventTriggerBeforeFilter(onEventTriggerBeforeFilterForMatrixBot)
+ s.onDetectorNoTriggerTimeout(onDetectorNoTriggerTimeoutForMatrixBot)
+ s.onMonitorUnexpectedExit(onMonitorUnexpectedExitForMatrixBot)
+ s.definitions["Monitor Settings"].blocks["Notifications"].info[0].info.push(
+ {
+ "name": "detail=notify_matrix",
+ "field": "Matrix.org",
+ "description": "",
+ "default": "0",
+ "example": "",
+ "selector": "h_det_matrixbot",
+ "fieldType": "select",
+ "possible": [
+ {
+ "name": lang.No,
+ "value": "0"
+ },
+ {
+ "name": lang.Yes,
+ "value": "1"
+ }
+ ]
+ }
+ )
+ s.definitions["Monitor Settings"].blocks["Notifications"].info.push({
+ "evaluation": "$user.details.use_matrixbot !== '0'",
+ isFormGroupGroup: true,
+ "name": "Matrix.org",
+ "color": "purple",
+ "section-class": "h_det_matrixbot_input h_det_matrixbot_1",
+ "info": [
+ {
+ "name": "detail=detector_matrixbot_send_video",
+ "field": lang["Attach Video Clip"] + ` (${lang['on Event']})`,
+ "description": "",
+ "default": "0",
+ "example": "",
+ "fieldType": "select",
+ "possible": [
+ {
+ "name": lang.No,
+ "value": "0"
+ },
+ {
+ "name": lang.Yes,
+ "value": "1"
+ }
+ ]
+ },
+ {
+ "name": "detail=detector_matrixbot_timeout",
+ "field": lang['Allow Next Alert'] + ` (${lang['on Event']})`,
+ "description": "",
+ "default": "10",
+ "example": "",
+ "possible": ""
+ },
+ {
+ "name": "detail=detector_notrigger_matrixbot",
+ "field": lang['No Trigger'],
+ "description": lang.noTriggerText,
+ "default": "0",
+ "example": "",
+ "fieldType": "select",
+ "possible": [
+ {
+ "name": lang.No,
+ "value": "0"
+ },
+ {
+ "name": lang.Yes,
+ "value": "1"
+ }
+ ]
+ },
+ ]
+ })
+ s.definitions["Account Settings"].blocks["2-Factor Authentication"].info.push({
+ "name": "detail=factor_matrixbot",
+ "field": 'Matrix.org',
+ "default": "1",
+ "example": "",
+ "fieldType": "select",
+ "possible": [
+ {
+ "name": lang.No,
+ "value": "0"
+ },
+ {
+ "name": lang.Yes,
+ "value": "1"
+ }
+ ]
+ })
+ s.definitions["Account Settings"].blocks["Matrix.org"] = {
+ "evaluation": "$user.details.use_matrixbot !== '0'",
+ "name": "Matrix.org",
+ "color": "purple",
+ "info": [
+ {
+ "name": "detail=matrixbot",
+ "selector":"u_matrixbot_bot",
+ "field": lang.Enabled,
+ "default": "0",
+ "example": "",
+ "fieldType": "select",
+ "possible": [
+ {
+ "name": lang.No,
+ "value": "0"
+ },
+ {
+ "name": lang.Yes,
+ "value": "1"
+ }
+ ]
+ },
+ {
+ hidden: true,
+ "name": "detail=matrixbot_baseUrl",
+ "placeholder": "",
+ "field": lang["Host"],
+ "form-group-class":"u_matrixbot_bot_input u_matrixbot_bot_1",
+ },
+ {
+ hidden: true,
+ "name": "detail=matrixbot_userId",
+ "placeholder": "xxxxxxxxxxxxxxxxxx",
+ "field": lang["User ID"],
+ "form-group-class":"u_matrixbot_bot_input u_matrixbot_bot_1",
+ },
+ {
+ hidden: true,
+ "name": "detail=matrixbot_roomId",
+ "placeholder": "xxxxxxxxxxxxxxxxxx",
+ "field": lang["Room ID"],
+ "form-group-class":"u_matrixbot_bot_input u_matrixbot_bot_1",
+ },
+ {
+ hidden: true,
+ "name": "detail=matrixbot_accessToken",
+ "fieldType": "password",
+ "placeholder": "XXXXXXXXXXXXXXXXXXXXXXXX",
+ "field": lang.Token,
+ "form-group-class":"u_matrixbot_bot_input u_matrixbot_bot_1",
+ },
+ ]
+ }
+ s.definitions["Event Filters"].blocks["Action for Selected"].info.push({
+ "name": "actions=matrixBot",
+ "field": 'Matrix.org',
+ "fieldType": "select",
+ "form-group-class": "actions-row",
+ "default": "",
+ "example": "1",
+ "possible": [
+ {
+ "name": lang['Original Choice'],
+ "value": "",
+ "selected": true
+ },
+ {
+ "name": lang.Yes,
+ "value": "1",
+ }
+ ]
+ })
+ }catch(err){
+ console.log(err)
+ console.log('Could not start Matrix bot, please run "npm install matrix-js-sdk" inside the Shinobi folder.')
+ }
+ }
+}
diff --git a/libs/video/utils.js b/libs/video/utils.js
index 732e7ce6..76e2e0eb 100644
--- a/libs/video/utils.js
+++ b/libs/video/utils.js
@@ -323,13 +323,14 @@ module.exports = (s,config,lang) => {
function reEncodeVideoAndBinOriginalAddToQueue(data){
const groupKey = data.video.ke
if(!reEncodeVideoAndBinOriginalQueue[groupKey]){
- reEncodeVideoAndBinOriginalQueue[groupKey] = async.queue(async function(data, callback) {
- const response = await reEncodeVideoAndBinOriginal(data)
- callback(response)
+ reEncodeVideoAndBinOriginalQueue[groupKey] = async.queue(function(data, callback) {
+ reEncodeVideoAndBinOriginal(data).then((response) => {
+ callback(response)
+ })
}, 1);
}
return new Promise((resolve) => {
- reEncodeVideoAndBinOriginalQueue[groupKey].push(data,(response) => {
+ reEncodeVideoAndBinOriginalQueue[groupKey].push(data, function(response){
resolve(response)
})
})
@@ -348,7 +349,7 @@ module.exports = (s,config,lang) => {
targetAudioCodec = targetAudioCodec || `copy`
targetQuality = targetQuality || ``
onPercentChange = onPercentChange || function(){};
- if(!targetVideoCodec || !targetAudioCodec){
+ if(!targetVideoCodec || !targetAudioCodec || !targetQuality){
switch(targetExtension){
case'mp4':
targetVideoCodec = `libx264`
@@ -359,7 +360,7 @@ module.exports = (s,config,lang) => {
case'mkv':
targetVideoCodec = `vp9`
targetAudioCodec = `libopus`
- targetQuality = `-q:v 1 -q:a 1`
+ targetQuality = `-q:v 1 -b:a 96K`
break;
}
}
@@ -427,6 +428,8 @@ module.exports = (s,config,lang) => {
},'GRP_'+groupKey);
onPercentChange(percent)
s.debugLog('stderr',outputFilePath,`${percent}%`)
+ }else{
+ s.debugLog('stderr',lang['Compression Info'],text)
}
})
videoBuildProcess.on('exit',async function(data){
diff --git a/libs/webServerPaths.js b/libs/webServerPaths.js
index 99808716..104fa3b3 100644
--- a/libs/webServerPaths.js
+++ b/libs/webServerPaths.js
@@ -790,8 +790,9 @@ module.exports = function(s,config,lang,app,io){
r[n].status = activeMonitor.monitorStatus
r[n].code = activeMonitor.monitorStatusCode
r[n].subStreamChannel = activeMonitor.subStreamChannel
+ r[n].subStreamActive = !!activeMonitor.subStreamProcess
}
- var buildStreamURL = function(type,channelNumber){
+ function getStreamUrl(type,channelNumber){
var streamURL
if(channelNumber){channelNumber = '/'+channelNumber}else{channelNumber=''}
switch(type){
@@ -810,7 +811,22 @@ module.exports = function(s,config,lang,app,io){
case'mp4':
streamURL='/'+req.params.auth+'/mp4/'+v.ke+'/'+v.mid+channelNumber+'/s.mp4'
break;
+ case'useSubstream':
+ try{
+ const monitorConfig = s.group[v.ke].rawMonitorConfigurations[v.mid]
+ const monitorDetails = monitorConfig.details
+ const subStreamChannelNumber = 1 + (monitorDetails.stream_channels || []).length
+ const subStreamType = monitorConfig.details.substream.output.stream_type
+ streamURL = getStreamUrl(subStreamType,subStreamChannelNumber)
+ }catch(err){
+ s.debugLog(err)
+ }
+ break;
}
+ return streamURL
+ }
+ var buildStreamURL = function(type,channelNumber){
+ var streamURL = getStreamUrl(type,channelNumber)
if(streamURL){
if(!r[n].streamsSortedByType[type]){
r[n].streamsSortedByType[type]=[]
@@ -1374,6 +1390,7 @@ module.exports = function(s,config,lang,app,io){
dataPipe.pipe(res)
}).catch((err) => {
console.error('onGetVideoData ERROR',err,videoDetails)
+ res.status(404)
res.end(user.lang['File Not Found in Database'])
})
}else{
@@ -1383,6 +1400,7 @@ module.exports = function(s,config,lang,app,io){
})
}
}else{
+ res.status(404)
res.end(user.lang['File Not Found in Database'])
}
})
@@ -1463,6 +1481,7 @@ module.exports = function(s,config,lang,app,io){
if(videoRow){
sendVideo(videoRow)
}else{
+ res.status(404)
res.end(user.lang['File Not Found in Database'])
}
})
@@ -1829,9 +1848,6 @@ module.exports = function(s,config,lang,app,io){
response.ok = true
reEncodeVideoAndBinOriginalAddToQueue({
video: r,
- targetVideoCodec: 'vp9',
- targetAudioCodec: 'libopus',
- targetQuality: '-q:v 1 -q:a 1',
targetExtension: 'webm',
doSlowly: false
}).then((encodeResponse) => {
diff --git a/libs/webServerSuperPaths.js b/libs/webServerSuperPaths.js
index 91833487..187aac37 100644
--- a/libs/webServerSuperPaths.js
+++ b/libs/webServerSuperPaths.js
@@ -292,36 +292,40 @@ module.exports = function(s,config,lang,app){
//found address already exists
endData.msg = lang['Email address is in use.'];
}else{
- endData.ok = true
//create new
//user id
form.uid = s.gid()
//check to see if custom key set
- if(!form.ke||form.ke===''){
- form.ke=s.gid()
+ if(!form.ke){
+ form.ke = s.gid()
}else{
- form.ke = form.ke.replace(/[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, '')
+ form.ke = form.ke.replace(/[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, '').trim()
}
- //check if "details" is object
- if(form.details instanceof Object){
- form.details = JSON.stringify(form.details)
- }
- //write user to db
- s.knexQuery({
- action: "insert",
- table: "Users",
- insert: {
- ke: form.ke,
- uid: form.uid,
- mail: form.mail,
- pass: s.createHash(form.pass),
- details: form.details
+ if(!s.group[form.ke]){
+ endData.ok = true
+ //check if "details" is object
+ if(form.details instanceof Object){
+ form.details = JSON.stringify(form.details)
}
- })
- s.tx({f:'add_account',details:form.details,ke:form.ke,uid:form.uid,mail:form.mail},'$')
- endData.user = Object.assign(form,{})
- //init user
- s.loadGroup(form)
+ //write user to db
+ s.knexQuery({
+ action: "insert",
+ table: "Users",
+ insert: {
+ ke: form.ke,
+ uid: form.uid,
+ mail: form.mail,
+ pass: s.createHash(form.pass),
+ details: form.details
+ }
+ });
+ s.tx({f:'add_account',details:form.details,ke:form.ke,uid:form.uid,mail:form.mail},'$')
+ endData.user = Object.assign(form,{})
+ //init user
+ s.loadGroup(form)
+ }else{
+ endData.msg = lang["Group with this key exists already"]
+ }
}
close()
})
diff --git a/web/pages/blocks/mainpermissions.ejs b/web/pages/blocks/mainpermissions.ejs
index 4293f523..96674867 100644
--- a/web/pages/blocks/mainpermissions.ejs
+++ b/web/pages/blocks/mainpermissions.ejs
@@ -35,9 +35,36 @@ function saveAccountSettings(){
postData.account = $.aN.selected
}
$.post(superApiPrefix + $user.sessionKey+'/accounts/'+webPath,postData,function(data){
- console.log(data)
- if(data.ok === true){
- $.aN.e.modal('hide')
+ if(webPath === 'editAdmin'){
+ if(data.ok === true){
+ $.aN.e.modal('hide')
+ new PNotify({
+ title: lang['Account Edited'],
+ text: lang.adminAccountEditedMsg,
+ type: 'success'
+ })
+ }else{
+ new PNotify({
+ title: lang.accountEditError,
+ text: lang['Failed to Edit Account'],
+ type: 'error'
+ })
+ }
+ }else{
+ if(data.ok === true){
+ $.aN.e.modal('hide')
+ new PNotify({
+ title: lang['Account Created'],
+ text: lang.adminAccountCreatedMsg,
+ type: 'success'
+ })
+ }else{
+ new PNotify({
+ title: lang.accountCreationError,
+ text: data.msg + '
' + lang.createSubAccountsInfo,
+ type: 'error'
+ })
+ }
}
})
}
@@ -54,7 +81,11 @@ $.aN.e.on('change','[name="mail"]',function(){
var thisVal = $(this).val()
$.each(loadedUsers,function(n,user){
if($.aN.selected && user.ke !== $.aN.selected.ke && thisVal.toLowerCase() === user.mail.toLowerCase()){
- new PNotify({text:lang['Email address is in use.'],type:'error'})
+ new PNotify({
+ title: lang.accountCreationError,
+ text: lang['Email address is in use.'],
+ type: 'error'
+ })
}
})
})
@@ -63,7 +94,11 @@ $.aN.e.on('change','[name="ke"]',function(){
var thisVal = $(this).val()
$.each(loadedUsers,function(n,user){
if(!$.aN.modeIsEdit() && user.ke === thisVal){
- new PNotify({text:lang['Group Key is in use.'] + ' ' + lang['Create Sub-Accounts at /admin'],type:'error'})
+ new PNotify({
+ title: lang.accountCreationError,
+ text: lang['Group Key is in use.'] + ' ' + lang.createSubAccountsInfo,
+ type: 'error'
+ })
}
})
})
diff --git a/web/pages/super.ejs b/web/pages/super.ejs
index a22369e4..55233f13 100644
--- a/web/pages/super.ejs
+++ b/web/pages/super.ejs
@@ -54,8 +54,7 @@