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 @@ @@ -244,14 +243,12 @@ $.ccio.ws.on('f',function(d){ d.msg = 'Saved Preferences' break; case'edit_account': - d.msg='Account Edited'; $.each(d.form,function(n,v){ $.ccio.accounts[d.ke][n]=v }) $('[ke="'+d.ke+'"] .mail').text(d.form.mail) break; case'add_account': - d.msg='Account Created'; $.ccio.tm(0,d,'#accounts-list') $.aN.selected = $.ccio.accounts[d.ke] break;