mirror of
https://gitlab.com/Shinobi-Systems/ShinobiCE.git
synced 2025-03-09 15:40:15 +00:00
Son Goku
- Rebased sql, test, web, defintions, languages, INSTALL, and libs folders.
This commit is contained in:
parent
24de55e45a
commit
d0b12e92e7
362 changed files with 21716 additions and 7018 deletions
|
@ -27,7 +27,7 @@ if [ ! -e "./super.json" ]; then
|
|||
fi
|
||||
echo "Shinobi - Run yum update"
|
||||
sudo yum update -y
|
||||
sudo yum install make -y
|
||||
sudo yum install make zip -y
|
||||
echo "============="
|
||||
echo "Shinobi - Do you want to Install FFMPEG?"
|
||||
echo "(y)es or (N)o"
|
||||
|
@ -143,21 +143,14 @@ if [ "$mysqlDefaultData" = "y" ] || [ "$mysqlDefaultData" = "Y" ]; then
|
|||
echo "====================================="
|
||||
echo "====================================="
|
||||
fi
|
||||
if [ ! "$sqliteormariadb" = "M" ] && [ ! "$sqliteormariadb" = "m" ]; then
|
||||
echo "====================================="
|
||||
echo "||===== Install Completed =====||"
|
||||
echo "====================================="
|
||||
echo "|| Login with the Superuser and create a new user!!"
|
||||
echo "||==================================="
|
||||
echo "|| Open http://$(ifconfig | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p'):8080/super in your web browser."
|
||||
echo "||==================================="
|
||||
echo "|| Default Superuser : admin@shinobi.video"
|
||||
echo "|| Default Password : admin"
|
||||
echo "====================================="
|
||||
echo "====================================="
|
||||
else
|
||||
echo "+=================================+"
|
||||
echo "||===== Install Completed =====||"
|
||||
echo "|| Access the main Shinobi panel at http://$(ifconfig | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p'):8080 in your web browser."
|
||||
echo "+=================================+"
|
||||
fi
|
||||
echo "====================================="
|
||||
echo "||===== Install Completed =====||"
|
||||
echo "====================================="
|
||||
echo "|| Login with the Superuser and create a new user!!"
|
||||
echo "||==================================="
|
||||
echo "|| Open http://$(ifconfig | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p'):8080/super in your web browser."
|
||||
echo "||==================================="
|
||||
echo "|| Default Superuser : admin@shinobi.video"
|
||||
echo "|| Default Password : admin"
|
||||
echo "====================================="
|
||||
echo "====================================="
|
||||
|
|
41
INSTALL/openalpr-cpu-easy.sh
Normal file
41
INSTALL/openalpr-cpu-easy.sh
Normal file
|
@ -0,0 +1,41 @@
|
|||
# Install prerequisites
|
||||
DIR=`dirname $0`
|
||||
INSTALLERS_DIR="$DIR"
|
||||
echo "-----------------------------------"
|
||||
if ! [ -x "$(command -v opencv_version)" ]; then
|
||||
echo "Installing OpenCV"
|
||||
dos2unix $INSTALLERS_DIR/opencv-cuda.sh
|
||||
sh $INSTALLERS_DIR/opencv-cuda.sh
|
||||
else
|
||||
echo "OpenCV found... : $(opencv_version)"
|
||||
fi
|
||||
# this includes all the ones missing from OpenALPR's guide.
|
||||
sudo apt install libtesseract-dev git cmake build-essential libleptonica-dev -y
|
||||
sudo apt install liblog4cplus-dev libcurl3-dev -y
|
||||
sudo apt install libleptonica-dev -y
|
||||
sudo apt install libcurl4-openssl-dev -y
|
||||
sudo apt install liblog4cplus-dev -y
|
||||
sudo apt install beanstalkd -y
|
||||
sudo apt install openjdk-8-jdk -y
|
||||
|
||||
# Clone the latest code from GitHub
|
||||
git clone https://github.com/openalpr/openalpr.git
|
||||
|
||||
# Setup the build directory
|
||||
cd openalpr/src
|
||||
mkdir build
|
||||
cd build
|
||||
|
||||
# setup the compile environment
|
||||
cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_INSTALL_SYSCONFDIR:PATH=/etc ..
|
||||
|
||||
# compile the library
|
||||
make
|
||||
|
||||
# Install the binaries/libraries to your local system (prefix is /usr)
|
||||
sudo make install
|
||||
|
||||
# Test the library
|
||||
wget http://plates.openalpr.com/h786poj.jpg -O lp.jpg
|
||||
alpr lp.jpg
|
||||
rm lp.jpg
|
|
@ -1,4 +1,17 @@
|
|||
# Install prerequisites
|
||||
DIR=`dirname $0`
|
||||
INSTALLERS_DIR="$DIR"
|
||||
echo "-----------------------------------"
|
||||
if ! [ -x "$(command -v opencv_version)" ]; then
|
||||
echo "Installing OpenCV"
|
||||
dos2unix $INSTALLERS_DIR/opencv-cuda.sh
|
||||
sh $INSTALLERS_DIR/opencv-cuda.sh
|
||||
else
|
||||
echo "OpenCV found... : $(opencv_version)"
|
||||
fi
|
||||
# get tesseract repo because ubuntu repo is serving a broken version
|
||||
sudo add-apt-repository ppa:alex-p/tesseract-ocr -y
|
||||
sudo apt-get update
|
||||
# this includes all the ones missing from OpenALPR's guide.
|
||||
sudo apt install libtesseract-dev git cmake build-essential libleptonica-dev -y
|
||||
sudo apt install liblog4cplus-dev libcurl3-dev -y
|
||||
|
@ -28,4 +41,4 @@ sudo make install
|
|||
# Test the library
|
||||
wget http://plates.openalpr.com/h786poj.jpg -O lp.jpg
|
||||
alpr lp.jpg
|
||||
rm lp.jpg
|
||||
rm lp.jpg
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
# OpenCV CUDA
|
||||
if [ $(dpkg-query -W -f='${Status}' git 2>/dev/null | grep -c "ok installed") -eq 0 ]; then
|
||||
echo "Installing Git..."
|
||||
|
@ -77,4 +77,4 @@ read opencvuninstall
|
|||
if [ "$opencvuninstall" = "y" ] || [ "$opencvuninstall" = "Y" ]; then
|
||||
rm -rf opencv
|
||||
rm -rf opencv_contrib
|
||||
fi
|
||||
fi
|
|
@ -115,15 +115,6 @@ sudo chmod -R 755 .
|
|||
touch INSTALL/installed.txt
|
||||
dos2unix /home/Shinobi/INSTALL/shinobi
|
||||
ln -s /home/Shinobi/INSTALL/shinobi /usr/bin/shinobi
|
||||
if [ "$mysqlDefaultData" = "y" ] || [ "$mysqlDefaultData" = "Y" ]; then
|
||||
echo "=====================================" > INSTALL/installed.txt
|
||||
echo "======= Login Credentials =======" >> INSTALL/installed.txt
|
||||
echo "|| Username : $userEmail" >> INSTALL/installed.txt
|
||||
echo "|| Password : $userPasswordPlain" >> INSTALL/installed.txt
|
||||
echo "|| API Key : $apiKey" >> INSTALL/installed.txt
|
||||
echo "=====================================" >> INSTALL/installed.txt
|
||||
echo "=====================================" >> INSTALL/installed.txt
|
||||
fi
|
||||
echo "Shinobi - Start Shinobi and set to start on boot?"
|
||||
echo "(y)es or (N)o"
|
||||
read startShinobi
|
||||
|
@ -134,31 +125,14 @@ if [ "$startShinobi" = "y" ] || [ "$startShinobi" = "Y" ]; then
|
|||
sudo pm2 save
|
||||
sudo pm2 list
|
||||
fi
|
||||
if [ "$mysqlDefaultData" = "y" ] || [ "$mysqlDefaultData" = "Y" ]; then
|
||||
echo "details written to INSTALL/installed.txt"
|
||||
echo "====================================="
|
||||
echo "======= Login Credentials ======="
|
||||
echo "|| Username : $userEmail"
|
||||
echo "|| Password : $userPasswordPlain"
|
||||
echo "|| API Key : $apiKey"
|
||||
echo "====================================="
|
||||
echo "====================================="
|
||||
fi
|
||||
if [ ! "$sqliteormariadb" = "M" ] && [ ! "$sqliteormariadb" = "m" ]; then
|
||||
echo "====================================="
|
||||
echo "||===== Install Completed =====||"
|
||||
echo "====================================="
|
||||
echo "|| Login with the Superuser and create a new user!!"
|
||||
echo "||==================================="
|
||||
echo "|| Open http://$(/sbin/ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1):8080/super in your web browser."
|
||||
echo "||==================================="
|
||||
echo "|| Default Superuser : admin@shinobi.video"
|
||||
echo "|| Default Password : admin"
|
||||
echo "====================================="
|
||||
echo "====================================="
|
||||
else
|
||||
echo "+=================================+"
|
||||
echo "||===== Install Completed =====||"
|
||||
echo "|| Access the main Shinobi panel at http://$(/sbin/ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1):8080 in your web browser."
|
||||
echo "+=================================+"
|
||||
fi
|
||||
echo "====================================="
|
||||
echo "||===== Install Completed =====||"
|
||||
echo "====================================="
|
||||
echo "|| Login with the Superuser and create a new user!!"
|
||||
echo "||==================================="
|
||||
echo "|| Open http://$(/sbin/ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1):8080/super in your web browser."
|
||||
echo "||==================================="
|
||||
echo "|| Default Superuser : admin@shinobi.video"
|
||||
echo "|| Default Password : admin"
|
||||
echo "====================================="
|
||||
echo "====================================="
|
||||
|
|
114
INSTALL/shinobi
114
INSTALL/shinobi
|
@ -1,30 +1,68 @@
|
|||
#!/bin/bash
|
||||
installationDirectory="/home/Shinobi"
|
||||
if [ ! "$1" ]; then
|
||||
if [ ! -e "/etc/shinobisystems/path.txt" ]; then
|
||||
installationDirectory="/home/Shinobi"
|
||||
else
|
||||
installationDirectory=$(cat /etc/shinobisystems/cctv.txt)
|
||||
fi
|
||||
cd $installationDirectory
|
||||
currentBuild=$(git show --oneline -s)
|
||||
gitOrigin=$(git remote show origin)
|
||||
splitBuildString=($currentBuild)
|
||||
currentCommitNumber=${splitBuildString[0]}
|
||||
if [[ $gitOrigin == *'ShinobiCE'* ]]; then
|
||||
repo="CE"
|
||||
else
|
||||
repo="Pro"
|
||||
fi
|
||||
if [[ $@ == *'help'* ]] || [ ! "$1" ]; then
|
||||
echo "========================================================="
|
||||
echo "==!! Shinobi : The Open Source CCTV and NVR Solution !!=="
|
||||
echo "========================================================="
|
||||
echo "You are missing function parameters."
|
||||
echo "Example : shinobi [command] .."
|
||||
echo "Example : shinobi flush restart logs"
|
||||
if [ ! "$1" ]; then
|
||||
echo "You are missing function parameters."
|
||||
echo "Example : shinobi [command] .."
|
||||
echo "Example : shinobi flush restart logs"
|
||||
else
|
||||
echo "Hello there! if you need support come on over"
|
||||
echo "to the Shinobi Community Chat! :)"
|
||||
echo "https://discordapp.com/invite/mdhmvuH/"
|
||||
fi
|
||||
echo "========================================================="
|
||||
echo "Your available options for COMMAND are as follows"
|
||||
echo "========================================================="
|
||||
echo "| start or s :"
|
||||
echo "| start :"
|
||||
echo "|--> Start camera.js and cron.js under PM2 (Process Manager)"
|
||||
echo "-"
|
||||
echo "| restart or r :"
|
||||
echo "| restart :"
|
||||
echo "|--> Restart all processes running under the PM2 daemon."
|
||||
echo "-"
|
||||
echo "| stop, exit, or e :"
|
||||
echo "| stop, exit :"
|
||||
echo "|--> Stop all processes running under the PM2 daemon."
|
||||
echo "-"
|
||||
echo "| version :"
|
||||
echo "|--> get version of your current build by git."
|
||||
echo "-"
|
||||
echo "| logs :"
|
||||
echo "|--> Get PM2 log stream with last 100 lines."
|
||||
echo "-"
|
||||
echo "| clear, flush, or f :"
|
||||
echo "| update :"
|
||||
echo "|--> Update via Git."
|
||||
echo "-"
|
||||
echo "| getMaster :"
|
||||
echo "|--> Switch to the Master Branch (For Pro Repo only)."
|
||||
echo "-"
|
||||
echo "| getDev :"
|
||||
echo "|--> Switch to the Development Branch (For Pro Repo only)."
|
||||
echo "-"
|
||||
echo "| clear, flush :"
|
||||
echo "|--> Clear all PM2 logs."
|
||||
echo "-"
|
||||
echo "| bootupEnable :"
|
||||
echo "|--> Start Shinobi on OS reboot."
|
||||
echo "-"
|
||||
echo "| bootupDisable :"
|
||||
echo "|--> Disable starting Shinobi on OS reboot."
|
||||
echo "-"
|
||||
echo "| kill :"
|
||||
echo "|--> Stop the entire PM2 daemon."
|
||||
fi
|
||||
|
@ -34,7 +72,8 @@ fi
|
|||
if [[ $@ == *'restart'* ]]; then
|
||||
proccessAlive=$(pm2 list | grep camera)
|
||||
if [ "$proccessAlive" ]; then
|
||||
pm2 restart all
|
||||
pm2 restart $installationDirectory/camera.js
|
||||
pm2 restart $installationDirectory/cron.js
|
||||
else
|
||||
echo "Shinobi process is not running."
|
||||
fi
|
||||
|
@ -58,11 +97,64 @@ fi
|
|||
if [[ $@ == *'stop'* ]] || [[ $@ == *'exit'* ]]; then
|
||||
proccessAlive=$(pm2 list | grep camera)
|
||||
if [ "$proccessAlive" ]; then
|
||||
pm2 kill
|
||||
pm2 stop $installationDirectory/camera.js
|
||||
pm2 stop $installationDirectory/cron.js
|
||||
else
|
||||
echo "Shinobi process is not running."
|
||||
fi
|
||||
fi
|
||||
if [[ $@ == *'version'* ]]; then
|
||||
echo "Build ID : $currentCommitNumber"
|
||||
if [[ $repo == "Pro" ]]; then
|
||||
echo "Repository : Shinobi Pro"
|
||||
else
|
||||
echo "Repository : Shinobi CE"
|
||||
fi
|
||||
echo $currentBuild
|
||||
fi
|
||||
if [[ $@ == *'bootupEnable'* ]] || [[ $@ == *'bootupenable'* ]]; then
|
||||
pm2 startup
|
||||
pm2 save
|
||||
fi
|
||||
if [[ $@ == *'bootupDisable'* ]] || [[ $@ == *'bootupdisable'* ]]; then
|
||||
pm2 unstartup
|
||||
pm2 save
|
||||
fi
|
||||
if [[ $@ == *'getDev'* ]] || [[ $@ == *'getdev'* ]]; then
|
||||
if [[ $repo == "Pro" ]]; then
|
||||
git checkout dev
|
||||
echo "Shinobi - Restart Shinobi to make the changes take affect."
|
||||
else
|
||||
echo "Shinobi - Cannot use \"getDev\" with Shinobi CE"
|
||||
fi
|
||||
fi
|
||||
if [[ $@ == *'getMaster'* ]] || [[ $@ == *'getmaster'* ]]; then
|
||||
if [[ $repo == "Pro" ]]; then
|
||||
git checkout master
|
||||
echo "Shinobi - Restart Shinobi to make the changes take affect."
|
||||
else
|
||||
echo "Shinobi - Cannot use \"getMaster\" with Shinobi CE"
|
||||
fi
|
||||
fi
|
||||
if [[ $@ == *'update'* ]]; then
|
||||
echo "============="
|
||||
echo "Shinobi - Are you sure you want to update? This will restart Shinobi."
|
||||
echo "(y)es or (N)o"
|
||||
read updateshinobi
|
||||
if [ "$updateshinobi" = "y" ] || [ "$updateshinobi" = "Y" ]; then
|
||||
echo "Beginning Update Process..."
|
||||
pm2 stop $installationDirectory/camera.js
|
||||
pm2 stop $installationDirectory/cron.js
|
||||
npm install --unsafe-perm
|
||||
npm audit fix --force
|
||||
git reset --hard
|
||||
git pull
|
||||
pm2 start $installationDirectory/camera.js
|
||||
pm2 start $installationDirectory/cron.js
|
||||
else
|
||||
echo "Cancelled Update Process."
|
||||
fi
|
||||
fi
|
||||
if [[ $@ == *'kill'* ]]; then
|
||||
pm2 kill
|
||||
fi
|
||||
|
|
|
@ -3,6 +3,7 @@ if [ -e "INSTALL/installed.txt" ]; then
|
|||
echo "Starting Shinobi"
|
||||
pm2 start camera.js
|
||||
pm2 start cron.js
|
||||
pm2 logs
|
||||
fi
|
||||
if [ ! -e "INSTALL/installed.txt" ]; then
|
||||
chmod +x INSTALL/now.sh&&INSTALL/now.sh
|
||||
|
|
41
INSTALL/terminalCommands.js
Normal file
41
INSTALL/terminalCommands.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
var fs = require('fs');
|
||||
var moment = require('moment');
|
||||
var exec = require('child_process').exec;
|
||||
var execSync = require('child_process').execSync;
|
||||
s = {
|
||||
isWin: (process.platform === 'win32' || process.platform === 'win64'),
|
||||
mainDirectory: __dirname.split('/INSTALL')[0]
|
||||
}
|
||||
var createTerminalCommands = function(callback){
|
||||
var next = function(){
|
||||
if(callback)callback()
|
||||
}
|
||||
if(!s.isWin){
|
||||
var etcPath = '/etc/shinobisystems/'
|
||||
console.log('Creating "' + etcPath + '"...')
|
||||
var createPathFile = function(){
|
||||
var pathTxt = etcPath + 'cctv.txt'
|
||||
console.log('Creating "' + pathTxt + '"...')
|
||||
fs.writeFile(pathTxt,s.mainDirectory,function(err){
|
||||
if(err)console.log(err)
|
||||
fs.chmod(pathTxt,0o777,function(err){
|
||||
if(err)console.log(err)
|
||||
console.log('Linking "' + s.mainDirectory + '/INSTALL/shinobi" to "/usr/bin/shinobi"...')
|
||||
fs.symlink(s.mainDirectory + '/INSTALL/shinobi', '/usr/bin/shinobi', next)
|
||||
console.log('You can now use `shinobi` in terminal.')
|
||||
})
|
||||
})
|
||||
}
|
||||
fs.stat(etcPath,function(err,stat){
|
||||
if(!err && stat){
|
||||
createPathFile()
|
||||
}else{
|
||||
fs.mkdir(etcPath,createPathFile)
|
||||
}
|
||||
})
|
||||
}else{
|
||||
//no commands for windows yet
|
||||
next()
|
||||
}
|
||||
}
|
||||
createTerminalCommands()
|
|
@ -30,12 +30,17 @@ if [ ! -e "./super.json" ]; then
|
|||
echo "* You can edit these settings in \"super.json\" located in the Shinobi directory."
|
||||
sudo cp super.sample.json super.json
|
||||
fi
|
||||
if ! [ -x "$(command -v ifconfig)" ]; then
|
||||
echo "============="
|
||||
echo "Shinobi - Installing Net-Tools"
|
||||
sudo apt install net-tools -y
|
||||
fi
|
||||
if ! [ -x "$(command -v node)" ]; then
|
||||
echo "============="
|
||||
echo "Shinobi - Installing Node.js"
|
||||
wget https://deb.nodesource.com/setup_9.x
|
||||
chmod +x setup_9.x
|
||||
./setup_9.x
|
||||
wget https://deb.nodesource.com/setup_8.x
|
||||
chmod +x setup_8.x
|
||||
./setup_8.x
|
||||
sudo apt install nodejs -y
|
||||
else
|
||||
echo "Node.js Found..."
|
||||
|
@ -44,7 +49,7 @@ fi
|
|||
if ! [ -x "$(command -v npm)" ]; then
|
||||
sudo apt install npm -y
|
||||
fi
|
||||
sudo apt install make -y
|
||||
sudo apt install make zip -y
|
||||
if ! [ -x "$(command -v ffmpeg)" ]; then
|
||||
if [ "$getubuntuversion" = "16" ] || [ "$getubuntuversion" < "16" ]; then
|
||||
echo "============="
|
||||
|
@ -132,21 +137,14 @@ if [ "$startShinobi" = "y" ] || [ "$startShinobi" = "y" ]; then
|
|||
sudo pm2 save
|
||||
sudo pm2 list
|
||||
fi
|
||||
if [ ! "$sqliteormariadb" = "M" ] && [ ! "$sqliteormariadb" = "m" ]; then
|
||||
echo "====================================="
|
||||
echo "||===== Install Completed =====||"
|
||||
echo "====================================="
|
||||
echo "|| Login with the Superuser and create a new user!!"
|
||||
echo "||==================================="
|
||||
echo "|| Open http://$(ifconfig | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p'):8080/super in your web browser."
|
||||
echo "||==================================="
|
||||
echo "|| Default Superuser : admin@shinobi.video"
|
||||
echo "|| Default Password : admin"
|
||||
echo "====================================="
|
||||
echo "====================================="
|
||||
else
|
||||
echo "+=================================+"
|
||||
echo "||===== Install Completed =====||"
|
||||
echo "|| Access the main Shinobi panel at http://$(ifconfig | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p'):8080 in your web browser."
|
||||
echo "+=================================+"
|
||||
fi
|
||||
echo "====================================="
|
||||
echo "||===== Install Completed =====||"
|
||||
echo "====================================="
|
||||
echo "|| Login with the Superuser and create a new user!!"
|
||||
echo "||==================================="
|
||||
echo "|| Open http://$(ifconfig | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p'):8080/super in your web browser."
|
||||
echo "||==================================="
|
||||
echo "|| Default Superuser : admin@shinobi.video"
|
||||
echo "|| Default Password : admin"
|
||||
echo "====================================="
|
||||
echo "====================================="
|
||||
|
|
20
camera.js
20
camera.js
|
@ -15,16 +15,20 @@ var loadLib = function(lib){
|
|||
}
|
||||
//process handlers
|
||||
var s = loadLib('process')(process,__dirname)
|
||||
//load extender functions
|
||||
loadLib('extenders')(s)
|
||||
//configuration loader
|
||||
var config = loadLib('config')(s)
|
||||
//language loader
|
||||
var lang = loadLib('language')(s,config)
|
||||
//code test module
|
||||
loadLib('codeTester')(s,config,lang)
|
||||
//basic functions
|
||||
loadLib('basic')(s,config)
|
||||
//load extender functions
|
||||
loadLib('extenders')(s,config)
|
||||
//video processing engine
|
||||
loadLib('ffmpeg')(s,config,function(){
|
||||
loadLib('ffmpeg')(s,config,function(ffmpeg){
|
||||
//ffmpeg coProcessor
|
||||
loadLib('ffmpegCoProcessor')(s,config,lang,ffmpeg)
|
||||
//database connection : mysql, sqlite3..
|
||||
loadLib('sql')(s,config)
|
||||
//working directories : videos, streams, fileBin..
|
||||
|
@ -60,9 +64,17 @@ loadLib('ffmpeg')(s,config,function(){
|
|||
//cluster module
|
||||
loadLib('childNode')(s,config,lang,app,io)
|
||||
//cloud uploaders : amazon s3, webdav, backblaze b2..
|
||||
loadLib('cloudUploaders')(s,config,lang)
|
||||
loadLib('uploaders')(s,config,lang)
|
||||
//notifiers : discord..
|
||||
loadLib('notification')(s,config,lang)
|
||||
//notifiers : discord..
|
||||
loadLib('rtmpserver')(s,config,lang)
|
||||
//dropInEvents server (file manipulation to create event trigger)
|
||||
loadLib('dropInEvents')(s,config,lang,app,io)
|
||||
//custom module loader
|
||||
loadLib('customAutoLoad')(s,config,lang,app,io)
|
||||
//scheduling engine
|
||||
loadLib('scheduler')(s,config,lang,app,io)
|
||||
//on-start actions, daemon(s) starter
|
||||
loadLib('startup')(s,config,lang)
|
||||
})
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"port": 8080,
|
||||
"passwordType": "sha256",
|
||||
"detectorMergePamRegionTriggers": true,
|
||||
"addStorage": [
|
||||
{"name":"second","path":"__DIR__/videos2"}
|
||||
],
|
||||
|
|
2
cron.js
2
cron.js
|
@ -170,7 +170,7 @@ s.localToUtc = function(time){
|
|||
return moment(time).utc()
|
||||
}
|
||||
s.nameToTime = function(x){x=x.replace('.webm','').replace('.mp4','').split('T'),x[1]=x[1].replace(/-/g,':');x=x.join(' ');return x;}
|
||||
io = require('socket.io-client')('ws://'+config.ip+':'+config.port);//connect to master
|
||||
io = require('socket.io-client')('ws://'+config.ip+':'+config.port,{transports:['websocket']});//connect to master
|
||||
s.cx = function(x){x.cronKey=config.cron.key;return io.emit('cron',x)}
|
||||
//emulate master socket emitter
|
||||
s.tx = function(x,y){s.cx({f:'s.tx',data:x,to:y})}
|
||||
|
|
|
@ -766,6 +766,14 @@
|
|||
"example": "",
|
||||
"possible": ""
|
||||
},
|
||||
{
|
||||
"name": "detail=detector_send_video_length",
|
||||
"field": "Notification Video Length",
|
||||
"description": "In seconds. The length of the video that gets sent to your Notification service, like Email or Discord.",
|
||||
"default": "10",
|
||||
"example": "",
|
||||
"possible": ""
|
||||
},
|
||||
{
|
||||
"name": "detail=watchdog_reset",
|
||||
"field": "Timeout Reset on Next Event",
|
||||
|
@ -835,8 +843,16 @@
|
|||
{
|
||||
"name": "detail=detector_fps",
|
||||
"field": "Detector Rate (FPS)",
|
||||
"description": "How many frames a second to send to the motion detector; 1 is the default.",
|
||||
"default": "1",
|
||||
"description": "How many frames a second to send to the motion detector; 2 is the default.",
|
||||
"default": "2",
|
||||
"example": "",
|
||||
"possible": ""
|
||||
},
|
||||
{
|
||||
"name": "detail=detector_buffer_seconds",
|
||||
"field": "Recorded Buffer",
|
||||
"description": "How many seconds before the event to include in the recorded video.",
|
||||
"default": "5",
|
||||
"example": "",
|
||||
"possible": ""
|
||||
},
|
||||
|
|
857
languages/cz.json
Normal file
857
languages/cz.json
Normal file
|
@ -0,0 +1,857 @@
|
|||
{
|
||||
"Shinobi": "Shinobi",
|
||||
"superAdminTitle": "Shinobi : Super Admin",
|
||||
"failedLoginText1": "Příliš mnoho neúspěšných přihlášení. Vyčkejte 15minut před dalším pokusem.",
|
||||
"failedLoginText2": "Prosím zkontrolujte přihlašovací údaje.",
|
||||
"Time Left": "Zbývající čas",
|
||||
"Login": "Uživatel",
|
||||
"Authenticate": "Ověřit",
|
||||
"Dashboard": "Dashboard",
|
||||
"Streamer": "Streamer",
|
||||
"Admin": "Admin",
|
||||
"Superuser": "Superuser",
|
||||
"Dashcam": "Dashcam",
|
||||
"Email": "Email",
|
||||
"Username": "Uživatel",
|
||||
"Profile": "Profil",
|
||||
"Password": "Heslo",
|
||||
"Password Again": "Heslo znovu",
|
||||
"Remember Me": "Zamapatovat si mě",
|
||||
"RAM": "RAM",
|
||||
"CPU": "CPU",
|
||||
"on": "na",
|
||||
"Power Viewer": "Power Viewer",
|
||||
"Power Video Viewer": "Power Video Viewer",
|
||||
"Time-lapse": "Time-lapse",
|
||||
"Montage": "Montáž",
|
||||
"Accounts": "Účty",
|
||||
"Settings": "Nastavení",
|
||||
"Recording FPS": "Recording FPS",
|
||||
"Input Selector": "Výběr vstupu",
|
||||
"Input Settings": "Nastavení vstupu",
|
||||
"Connection": "Připojení",
|
||||
"Video Set": "Video Set",
|
||||
"API": "API",
|
||||
"ONVIF": "ONVIF",
|
||||
"FFprobe": "Sonda",
|
||||
"Monitor States": "Monitorovat stavy",
|
||||
"Schedule": "Plán",
|
||||
"Schedules": "Plány",
|
||||
"Monitor States and Schedules": "Monitorovat stavy a plány",
|
||||
"Filters": "Filtry",
|
||||
"Full URL Path": "Plná URL cesta",
|
||||
"Logs": "Logy",
|
||||
"Full Stream URL": "Full Stream URL",
|
||||
"Manual": "Manuální",
|
||||
"List Toggle": "Přepnout zobrazení",
|
||||
"Hide List": "Skrýt seznam",
|
||||
"Motion GUI": "Motion GUI",
|
||||
"Motion": "Pohyb",
|
||||
"Global Detector Settings": "Globální nastavení detekce",
|
||||
"Motion Detection": "Detekce pohybu",
|
||||
"Object Detection": "Detekce objektů",
|
||||
"JPEG Mode": "Režim JPEG",
|
||||
"Order Streams": "Řazení streamů",
|
||||
"Hide Notes": "Skrýt poznámky",
|
||||
"Example": "Příklad",
|
||||
"Logout": "Odhlásit",
|
||||
"Closed": "Uzavřeno",
|
||||
"Ended": "Skončilo",
|
||||
"Options": "Možnosti",
|
||||
"Started": "Začalo",
|
||||
"Monitor": "Monitor",
|
||||
"Filename": "Soubor",
|
||||
"Size (mb)": "Velikost (MB)",
|
||||
"Watch": "Sledovat",
|
||||
"Download": "Stáhnout",
|
||||
"Delete": "Smazat",
|
||||
"Fix": "Opravit",
|
||||
"Use HTML5 Play Method": "Použít HTML5 Přehrávání",
|
||||
"Connection timed out": "Připojení vypršelo",
|
||||
"skipPingText1": "Zkuste Nastavit \"Vynechat Ping\" na Ano.",
|
||||
"Ping Failed": "Ping Selhal",
|
||||
"Zoom In": "Přiblížení <small>URL Adresa</small>",
|
||||
"Zoom Out": "Oddálení <small>URL Adresa</small>",
|
||||
"Enable Nightvision": "Zapni Noční Vidění",
|
||||
"Disable Nightvision": "Vypni Noční Vidění",
|
||||
"Current": "Aktuální",
|
||||
"Monitors": "Monitory",
|
||||
"Video": "Video",
|
||||
"Themes": "Témata",
|
||||
"Videos": "Videa",
|
||||
"Events": "Události",
|
||||
"Streams": "Streamy",
|
||||
"Snapshot": "Snapshot",
|
||||
"Snapshots": "Snapshoty",
|
||||
"Date Range": "Časové období",
|
||||
"Event Limit": "Limit Událostí",
|
||||
"No Data": "Žádná Data",
|
||||
"Live View": "Živý Pohled",
|
||||
"New Monitor": "Nový Monitor",
|
||||
"Please Check Your Settings": "Prosím Zkontrolujte Nastavení",
|
||||
"migrateText1": "<b>Typ Vstupu</b> nelze parsovat. Prosím nastavte manuálně.",
|
||||
"Add": "Přidat",
|
||||
"Save": "Uložit",
|
||||
"Close": "Zavřít",
|
||||
"Secure": "Secure",
|
||||
"Check": "Ověřit",
|
||||
"Stop": "Stop",
|
||||
"Confirm": "Potvrdit",
|
||||
"Enable": "Povolit",
|
||||
"Enabled": "Povoleno",
|
||||
"API Key": "API Klíč",
|
||||
"API Keys": "API Klíče",
|
||||
"Group Key": "Klíč Skupiny",
|
||||
"Allowed IPs": "Povolené IP",
|
||||
"Separate with commas, no spaces": "Oddělit čárkami, bez mezer",
|
||||
"Can Get Monitors": "Can Get Monitory",
|
||||
"Can Get Logs": "Can Get Logs",
|
||||
"Can Authenticate Websocket": "Can Authenticate Websocket",
|
||||
"Can Control Monitors": "Can Control Monitory",
|
||||
"Can View Snapshots": "Can View Snapshots",
|
||||
"Can View Streams": "Can View Streams",
|
||||
"Can View Videos": "Can View Videos",
|
||||
"Can View Monitor": "Can View Monitor",
|
||||
"Can Change User Settings": "Can Change User Settings",
|
||||
"Can Create and Delete Monitors": "Can Create and Delete Monitory",
|
||||
"Can Edit Monitor": "Can Edit Monitor",
|
||||
"Can Delete Videos": "Can Delete Videos",
|
||||
"Delete Video": "Delete Video",
|
||||
"Can View Videos and Events": "Can View Videos and Events",
|
||||
"Can Delete Videos and Events": "Can Delete Videos and Events",
|
||||
"Saved Filters": "Uložené filtry",
|
||||
"Saved Presets": "Uložené předvolby",
|
||||
"Saved Schedules": "Uložené časovače",
|
||||
"Filter Name": "Název filtru",
|
||||
"Find Where": "Find Where",
|
||||
"Reason": "Důvod",
|
||||
"Detection Engine": "Detection Engine",
|
||||
"X Point": "Bod X",
|
||||
"Y Point": "Bod Y",
|
||||
"Sort By": "Řadit dle",
|
||||
"Start Time": "Start Time",
|
||||
"End Time": "End Time",
|
||||
"Time": "Čas",
|
||||
"Monitor ID": "Monitor ID",
|
||||
"File Type": "Typ souboru",
|
||||
"Filesize": "Velikost souboru",
|
||||
"Video Status": "Stav Videa",
|
||||
"Preferences": "Možnosti",
|
||||
"Equal to": "Rovno",
|
||||
"Not Equal to": "Není rovno",
|
||||
"Greater Than or Equal to": "Větší než nebo rovno",
|
||||
"Greater Than": "Větší než",
|
||||
"Less Than": "Menší než",
|
||||
"Less Than or Equal to": "Menší než nebo rovno",
|
||||
"Contains": "Obsahuje",
|
||||
"Does Not Contain": "Neobsahuje",
|
||||
"AND": "A",
|
||||
"OR": "Nebo",
|
||||
"Like": "Jako",
|
||||
"Matches": "Odpovída",
|
||||
"Not Matches": "Neodpovídá",
|
||||
"In": "In",
|
||||
"Not In": "Not In",
|
||||
"ASC": "Vzestupně",
|
||||
"DESC": "Sestupně",
|
||||
"Action for Selected": "Action for Selected",
|
||||
"Search": "Hledat",
|
||||
"No": "Ne",
|
||||
"Yes": "Ano",
|
||||
"Start": "Start",
|
||||
"End": "Konec",
|
||||
"Archive": "Archiv",
|
||||
"Email Details": "Email Details",
|
||||
"Delete Matches": "Delete Matches",
|
||||
"Delete selected": "Delete selected",
|
||||
"Execute Command": "Execute Command",
|
||||
"for Global Access": "for Global Access",
|
||||
"Help": "Pomoc",
|
||||
"Don't show this anymore": "Již nezobrazovat",
|
||||
"Chat on Discord": "Chat on Discord",
|
||||
"Documentation": "Dokumentace",
|
||||
"All Monitors": "Všechny monitory",
|
||||
"Motion Meter": "Motion Meter",
|
||||
"FFmpegTip": "FFprobe is a simple multimedia streams analyzer. You can use it to output all kinds of information about an input including duration, frame rate, frame size, etc.",
|
||||
"Complete Stream URL": "Complete Stream URL",
|
||||
"ONVIF Scanner": "ONVIF Scanner",
|
||||
"ONVIFEventsNotAvailable": "ONVIF události njesou dostupné",
|
||||
"ONVIFnotCompliantProfileT": "Camera is not ONVIF Profile T Compliant",
|
||||
"Scan Settings": "Nastavení scanu",
|
||||
"ONVIFnote": "Discover ONVIF devices on networks outside your own or leave it blank to scan your current network. <br>Username and Password can be left blank.",
|
||||
"Range or Single": "Range or Single",
|
||||
"IP Address": "IP Addresa",
|
||||
"Port": "Port",
|
||||
"Camera Username": "CAM Uživatel",
|
||||
"Camera Password": "CAM Heslo",
|
||||
"Found Devices": "Zařízení nalezeno",
|
||||
"Switch on for Still Image": "Switch on for Still Image",
|
||||
"Live Stream Toggle": "Live Stream Toggle",
|
||||
"RegionNote": "Points are only saved when you press <b>Save</b> on the <b>Monitor Settings</b> window.",
|
||||
"Points": "Points <small>When adding points click on the edge of the polygon.</small>",
|
||||
"Indifference": "Indifference",
|
||||
"Max Indifference": "Max Indifference",
|
||||
"Trigger Threshold": "Trigger Threshold",
|
||||
"Color Threshold": "Color Threshold",
|
||||
"Region Name": "Název Regionu",
|
||||
"Regions": "Regiony",
|
||||
"Again": "Znovu",
|
||||
"Account Info": "Info o účtu",
|
||||
"blankPassword": "Nechcete-li měnit heslo, ponechte prázdné",
|
||||
"2-Factor Authentication": "Dvou-faktorové přihlášení",
|
||||
"Use Max Storage Amount": "Použít max. velikost uložiště",
|
||||
"Max Storage Amount": "Maximum uložiště <small>v Megabajtech</small>",
|
||||
"Number of Days to keep": "Počet dní uchování",
|
||||
"Monitor Groups": "Skupiny monitorů",
|
||||
"Group Name": "Název skupiny",
|
||||
"Show Thumbnails in Video List": "Show Thumbnails in Video List",
|
||||
"WebDAV": "WebDAV",
|
||||
"Backblaze B2": "Backblaze B2",
|
||||
"Backblaze Error": "Backblaze Error",
|
||||
"Could not create Bucket.": "Could not create Bucket.",
|
||||
"Amazon S3": "Amazon S3",
|
||||
"Database": "Databáze",
|
||||
"Database Not Found": "Databáze nenalezena",
|
||||
"User Not Found": "Uživatel nenalezen",
|
||||
"Save Links to Database": "Save Links to Database",
|
||||
"Upload File": "Nahrát soubor",
|
||||
"Bucket": "Bucket",
|
||||
"Region": "Region",
|
||||
"Use Global Amazon S3 Video Storage": "Use Global Amazon S3 Video Storage",
|
||||
"Use Global Backblaze B2 Video Storage": "Use Global Backblaze B2 Video Storage",
|
||||
"Use Global WebDAV Video Storage": "Use Global WebDAV Video Storage",
|
||||
"Amazon S3 Upload Error": "Amazon S3 Upload Error",
|
||||
"accountId": "Account ID",
|
||||
"applicationKey": "Application Key",
|
||||
"aws_accessKeyId": "Access Key Id",
|
||||
"aws_secretAccessKey": "Secret Access Key",
|
||||
"Discord Bot": "Discord Bot",
|
||||
"URL": "URL",
|
||||
"Operating Hours": "Operating Hours",
|
||||
"Autosave": "Autosave",
|
||||
"Save Directory": "Save Directory",
|
||||
"CSS": "CSS <small>Style your dashboard.</small>",
|
||||
"Force Monitors Per Row": "Force Monitory Per Row",
|
||||
"Monitors per row": "Monitory per row <small>for Montage</small>",
|
||||
"Browser Console Log": "Browser Console Log",
|
||||
"Log Stream": "Log Stream",
|
||||
"Privileges": "Privileges",
|
||||
"All Monitors and Privileges": "All Monitory and Privileges",
|
||||
"Permissions": "Permissions",
|
||||
"Time-lapse Tool": "Time-lapse Tool",
|
||||
"total": "total",
|
||||
"MB": "MB",
|
||||
"Calendar": "Calendar",
|
||||
"Leave blank for random.": "Leave blank for random.",
|
||||
"Currently viewing": "Currently viewing",
|
||||
"Status Indicator": "Status Indicator",
|
||||
"Show Logs": "Show Logs",
|
||||
"Videos List": "Videos List",
|
||||
"Monitor Settings": "Monitor Settings",
|
||||
"Enlarge": "Enlarge",
|
||||
"Fullscreen": "Fullscreen",
|
||||
"Value": "Value",
|
||||
"Idle": "Idle",
|
||||
"Disabled": "Disabled",
|
||||
"Record": "Record",
|
||||
"Watch Only": "Watch Only",
|
||||
"Toggle Sidebar": "Toggle Sidebar",
|
||||
"Add Monitor": "Add Monitor",
|
||||
"Start Recording": "Start Recording",
|
||||
"Set to Watch Only": "Set to Watch Only",
|
||||
"Save as": "Ukládat jako",
|
||||
"Add New": "Přidat",
|
||||
"Zip and Download": "Stáhnout jako ZIP",
|
||||
"Export Selected Videos": "Exportovat vybraná videa",
|
||||
"Delete Selected Videos": "Smazat vybraná videa",
|
||||
"DeleteSelectedVideosMsg": "Do you want to delete these videos? You cannot recover them.",
|
||||
"ExportSelectedVideosMsg": "Do you want to export these videos? It may take some time to zip and download.",
|
||||
"clientStreamFailedattemptingReconnect": "Client side ctream check failed, attempting reconnect.",
|
||||
"Export Video": "Export Video",
|
||||
"Delete Filter": "Delete Filter",
|
||||
"confirmDeleteFilter": "Do you want to delete this filter? You cannot recover it.",
|
||||
"Fix Video": "Fix Video",
|
||||
"FixVideoMsg": "Do you want to fix this video? You cannot undo this action.",
|
||||
"DeleteVideoMsg": "Do you want to delete this video? You cannot recover it.",
|
||||
"dropBoxSuccess": "Success! Files saved to your Dropbox.",
|
||||
"API Key Deleted": "API Key Deleted",
|
||||
"APIKeyDeletedText": "Key has been deleted. It will no longer work.",
|
||||
"API Key Added": "API Key Added",
|
||||
"APIKeyAddedText": "You may use this key now.",
|
||||
"Update": "Update",
|
||||
"Update to Master": "Update to Master",
|
||||
"Update to Development": "Update to Development",
|
||||
"Filters Updated": "Filters Updated",
|
||||
"FiltersUpdatedText": "Your changes have been saved and applied.",
|
||||
"Settings Changed": "Settings Changed",
|
||||
"SettingsChangedText": "Your settings have been saved and applied. Some settings may require a refresh of this page.",
|
||||
"Are you sure?": "Are you sure?",
|
||||
"Import Monitor Configuration": "Import Monitor Configuration",
|
||||
"ImportMultiMonitorConfigurationText": "Doing this will overrwrite any monitory with IDs existing in the import file.",
|
||||
"ImportMonitorConfigurationText": "Doing this will overrwrite any changes currently not saved. Imported changes will only be applied when you press <b>Save</b>.",
|
||||
"Paste JSON here.": "Paste JSON here.",
|
||||
"Delete Monitor": "Delete Monitor",
|
||||
"DeleteMonitorText": "Do you want to delete this monitor? You cannot recover it. You can choose for the files to remain in the filesystem. If you choose to recreate a monitor with the same ID the videos and events will become visible in the dashboard.",
|
||||
"DeleteMonitorsText": "Do you want to delete these monitory? You cannot recover them. You can choose to keep the files for these IDs in the filesystem. If you choose to recreate a monitor with one of the IDs the videos and events will become visible in the dashboard.",
|
||||
"Invalid JSON": "Invalid JSON",
|
||||
"Invalid Data": "Invalid Data",
|
||||
"Name cannot be empty.": "Name cannot be empty.",
|
||||
"Start Time cannot be empty.": "Start Time cannot be empty.",
|
||||
"Must be atleast one row": "Must be atleast one row",
|
||||
"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.",
|
||||
"Group Key is in use.": "Group Key is in use.",
|
||||
"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)",
|
||||
"Video Length (minutes) and Motion Count per video": "Video Length (minutes) and Motion Count per video",
|
||||
"Counts of Motion": "Counts of Motion",
|
||||
"Unable to Launch": "Unable to Launch",
|
||||
"UnabletoLaunchText": "Please save new monitor first. Then attempt to launch the region editor.",
|
||||
"NoVideosFoundForDateRange": "No Videos found in this date range. Try setting the start date further back.",
|
||||
"NoLogsFoundForDateRange": "No Logs found in this date range. Try widening the date range.",
|
||||
"monitorEditFailedMaxReached": "Your account has reached the maximum number of cameras that can be created. Speak to an administrator if you would like this changed.",
|
||||
"in": "in",
|
||||
"ago": "ago",
|
||||
"a few seconds": "a few seconds",
|
||||
"a minute": "a minute",
|
||||
"minutes": "minutes",
|
||||
"an hour": "an hour",
|
||||
"hours": "hours",
|
||||
"a day": "a day",
|
||||
"days": "days",
|
||||
"a month": "a month",
|
||||
"months": "months",
|
||||
"a year": "a year",
|
||||
"years": "years",
|
||||
"Identity": "Identity",
|
||||
"Input": "Input",
|
||||
"Input Feed": "Input Feed",
|
||||
"Stream": "Stream",
|
||||
"Stream Timestamp": "Stream Timestamp",
|
||||
"Stream Watermark": "Stream Watermark",
|
||||
"JPEG API": "JPEG API <small>Snapshot (cgi-bin)</small>",
|
||||
"Raw H.264 Stream": "Raw H.264 Stream",
|
||||
"Recording": "Recording",
|
||||
"Recording Timestamp": "Recording Timestamp",
|
||||
"Recording Watermark": "Recording Watermark",
|
||||
"Region Editor": "Region Editor",
|
||||
"Detector Filters": "Detector Filters",
|
||||
"Filter for Objects only": "Filter for Objects only",
|
||||
"Custom": "Custom",
|
||||
"Detector": "Detector",
|
||||
"Audio Detector": "Audio Detector",
|
||||
"Audio Detection": "Audio Detection",
|
||||
"Minimum dB": "Minimum dB",
|
||||
"Maximum dB": "Maximum dB",
|
||||
"Connected": "Connected",
|
||||
"Not Saved": "Not Saved",
|
||||
"Not Connected": "Not Connected",
|
||||
"Lisence Plate Detector": "Lisence Plate Detector",
|
||||
"OpenCV Cascades": "OpenCV Cascades",
|
||||
"Refresh List of Cascades": "Refresh List of Cascades",
|
||||
"\"No Motion\" Detector": "\"No Motion\" Detector",
|
||||
"Control": "Control",
|
||||
"Grouping": "Grouping <small>Add groups in <b>Settings</b></small>",
|
||||
"Detector Grouping": "Detector Grouping <small>Add groups in <b>Settings</b></small>",
|
||||
"Detector Recording Process Exited Prematurely. Restarting.": "Detector Recording Process Exited Prematurely. Restarting.",
|
||||
"Detector Recording Complete": "Detector Recording Complete",
|
||||
"Clear Recorder Process": "Clear Recorder Process",
|
||||
"Logging": "Logging",
|
||||
"IdentityText1": "This is how the system will identify the data for this stream. You cannot change the <b>Monitor ID</b> once you have pressed save. If you want you can make the <b>Monitor ID</b> more human readable before you continue.",
|
||||
"IdentityText2": "You can duplicate a monitor by modifying the <b>Monitor ID</b> then pressing save. You <b>cannot</b> use the ID of a monitor that already exists or it will save over that monitor's database information.",
|
||||
"noSpecialCharacters": "No spaces or special characters.",
|
||||
"NotesPlacholder": "Comments you want to leave for this cameras settings.",
|
||||
"InputText1": "This section tells Shinobi how to consume a stream. For optimal performance try tuning your camera's internal settings. Find the following options and set them as shown. To find your camera you can use the <b>built in ONVIF Scanner</b> of Shinobi. Some ONVIF cameras require the use of a management tool to modify their internal settings. If you can't find your cameras you can try <a href=\"https://s3.amazonaws.com/cloudcamio/odm-v2.2.250.msi\">ONVIF Device Manager for Windows</a>.",
|
||||
"InputText2": "<ul><li><b>Framerate (FPS) :</b> High : 10 - 15 FPS, Low : 2-5 FPS</li><li><b>I-frame interval :</b> 1</li><li><b>Bit Rate Type :</b> VBR (Variable Bit Rate)</li><li><b>Bit Rate :</b> between 256kbps - 1000kbps</li></ul>",
|
||||
"InputText3": "If you need help figuring out what input type your camera is you can take a look in the <a href=\"http://shinobi.video/docs/cameras\" target=\"_blank\">Camera URLs List</a> on the Shinobi website.",
|
||||
"StreamText": "<p>This section will designate the primary method of streaming out and its settings. This stream will be displayed in the dashboard. If you choose to use HLS, JPEG, or MJPEG then you can consume the stream through other programs.</p><p class=\"h_st_input h_st_jpeg\">Using JPEG stream essentially turns off the primary stream and uses the snapshot bin to get frames.</p>",
|
||||
"DetectorText": "<p>When the Width and Height boxes are shown you should set them to 640x480 or below. This will optimize the read speed of frames.</p>",
|
||||
"RecordingText": "It is recommended that you set <b>Record File Type</b> to <b class=\"h_t_input h_t_jpeg h_t_socket\">WebM</b><b class=\"h_t_input h_t_mjpeg h_t_h264 h_t_hls h_t_mp4 h_t_local\">MP4</b> and <b>Video Codec</b> to <b class=\"h_t_input h_t_jpeg h_t_socket\">libvpx</b><b class=\"h_t_input h_t_h264 h_t_hls h_t_mp4\">copy or </b><b class=\"h_t_input h_t_mjpeg h_t_h264 h_t_hls h_t_mp4 h_t_local\">libx264</b> because your <b>Input Type</b> is set to <b class=\"h_t_text\"></b>.",
|
||||
"Mode": "Mód",
|
||||
"Name": "Název",
|
||||
"Skip Ping": "Vynechat Ping",
|
||||
"Retry Connection": "Retry Connection <small>Number of times allowed to fail</small>",
|
||||
"Notes": "Notes",
|
||||
"Input Type": "Input Type",
|
||||
"Connection Type": "Connection Type",
|
||||
"RTSP Transport": "RTSP Transport",
|
||||
"Host": "Host",
|
||||
"Force Port": "Force Port",
|
||||
"Path": "Path",
|
||||
"Monitor Capture Rate": "Monitor Capture Rate <small>(FPS)</small>",
|
||||
"Analyzation Duration": "Analyzation Duration",
|
||||
"Probe Size": "Probe Size",
|
||||
"Stream Type": "Stream Type",
|
||||
"# of Allow MJPEG Clients": "# of Allow MJPEG Clients <small>0 for infinite</small>",
|
||||
"HLS Video Encoder": "Video Encoder",
|
||||
"HLS Audio Encoder": "Audio Encoder",
|
||||
"HLS Segment Length": "Segment Length <small>in Seconds</small>",
|
||||
"HLS Preset": "Preset Template",
|
||||
"HLS List Size": "List Size",
|
||||
"Traditional Recording": "Traditional Recording",
|
||||
"Buffer Preview": "Buffer Preview",
|
||||
"HLS Start Number": "HLS Start Number",
|
||||
"HLS Live Start Index": "HLS Live Start Index",
|
||||
"Check Signal Interval": "Check Signal Interval <small>in Minutes</small>",
|
||||
"Log Signal Event": "Log Signal Event <small>Client side only</small>",
|
||||
"Quality": "Quality <small>1 is High, 23 is Low</small>",
|
||||
"Rate": "Rate <small>(FPS)</small>",
|
||||
"Width": "Width",
|
||||
"Height": "Height",
|
||||
"Rotate": "Rotate",
|
||||
"Primary Engine": "Primary Engine",
|
||||
"Video Filter": "Video Filter",
|
||||
"Font Path": "Font Path",
|
||||
"Font Size": "Font Size",
|
||||
"Text Color": "Text Color",
|
||||
"Text Box Color": "Text Box Color",
|
||||
"Position X": "Position X",
|
||||
"Position Y": "Position Y",
|
||||
"Image Location": "Image Location <small>Absolute Path or leave blank to use global</small>",
|
||||
"Image Position": "Image Position",
|
||||
"Frame Rate": "Frame Rate <small>(FPS)</small>",
|
||||
"Image Width": "Image Width",
|
||||
"Image Height": "Image Height",
|
||||
"Record File Type": "Record File Type",
|
||||
"Video Codec": "Video Codec",
|
||||
"Delete Monitor States Preset": "Delete Monitor States Preset",
|
||||
"Delete Monitor State?": "Delete Monitor State",
|
||||
"deleteMonitorStateText1": "Do you want to delete this Monitor States Preset?",
|
||||
"deleteMonitorStateText2": "Do you want to delete this Monitor's Preset?",
|
||||
"Search Images": "Hledat Images",
|
||||
"Launch in New Window": "Launch in New Window",
|
||||
"Preset": "Preset",
|
||||
"Presets": "Presets",
|
||||
"possibleInternalError": "Possible Internal Error",
|
||||
"sizePurgeLockedText": "The Size Purge Lock (deleteOverMax) appears to have failed to unlock. Unlocking now...",
|
||||
"Use coProcessor": "Use coProcessor",
|
||||
"Audio Codec": "Audio Codec",
|
||||
"Video Record Rate": "Video Record Rate <small>(FPS)</small>",
|
||||
"Record Width": "Record Width",
|
||||
"Record Height": "Record Height",
|
||||
"Double Quote Directory": "Double Quote Directory <small>Some directories have spaces. Using this may crash some cameras.</small>",
|
||||
"Recording Segment Interval": "Recording Segment Interval <small>in minutes</small>",
|
||||
"Record Video Filter": "Record Video Filter",
|
||||
"Input Flags": "Input Flags",
|
||||
"Snapshot Flags": "Snapshot Flags",
|
||||
"Detector Flags": "Detector Flags",
|
||||
"Stream Flags": "Stream Flags",
|
||||
"Stream to YouTube": "Stream to YouTube",
|
||||
"Stream to YouTube Flags": "Stream to YouTube Flags",
|
||||
"Recording Flags": "Recording Flags",
|
||||
"Traditional Recording Flags": "Traditional Recording Flags",
|
||||
"Output Method": "Output Method",
|
||||
"Webhook": "Webhook",
|
||||
"Event Webhook Error": "Event Webhook Error",
|
||||
"Webhook URL": "Webhook URL",
|
||||
"Command on Trigger": "Command on Trigger",
|
||||
"Command": "Command",
|
||||
"Allow Next Command": "Allow Next Command <small>in Minutes</small>",
|
||||
"Allow Next Trigger": "Allow Next Trigger <small>in Milliseconds</small>",
|
||||
"Save Events to SQL": "Save Events to SQL",
|
||||
"Email on Trigger": "Email on Trigger <small>Emails go to the main account holder's login address.</small>",
|
||||
"Attach Video Clip": "Attach Video Clip",
|
||||
"Error While Decoding": "Error While Decoding",
|
||||
"ErrorWhileDecodingText": "Your hardware may have an unstable connection to the network. Check your network connections.",
|
||||
"Discord": "Discord",
|
||||
"Discord Alert on Trigger": "Discord Alert on Trigger",
|
||||
"Allow Next Email": "Allow Next Email <small>in Minutes</small>",
|
||||
"Allow Next Discord Alert": "Allow Next Discord Alert <small>in Minutes</small>",
|
||||
"DiscordLoggedIn": "Discord Bot Authenticated",
|
||||
"DiscordFailedText": "Sending to Discord Failed",
|
||||
"DiscordErrorText": "Sending to Discord caused an Error",
|
||||
"DiscordNotEnabledText": "Discord Bot Not Enabled, Enable it in your Account Settings.",
|
||||
"How to Record": "How to Record",
|
||||
"Trigger Record": "Trigger Record",
|
||||
"Recording Timeout": "Recording Timeout <small>in Minutes</small>",
|
||||
"Timeout Reset on Next Motion": "Timeout Reset on Next Motion",
|
||||
"Timeout Reset on Next Event": "Timeout Reset on Next Event",
|
||||
"Delete Motionless Video": "Delete Motionless Video",
|
||||
"Send Frames": "Send Frames <small>Push frames to be analyzed</small>",
|
||||
"Detector Rate": "Detector Rate <small>(FPS)</small>",
|
||||
"Feed-in Image Width": "Feed-in Image Width",
|
||||
"Feed-in Image Height": "Feed-in Image Height",
|
||||
"Check for Motion First": "Check for Motion First",
|
||||
"Detect Objects": "Detect Objects <small class=\"\">See below</small>",
|
||||
"Full Frame Detection": "Full Frame Detection",
|
||||
"Country of Plates": "Country of Plates",
|
||||
"Email on No Motion": "Email on \"No Motion\"",
|
||||
"Timeout": "Timeout",
|
||||
"Controllable": "Controllable",
|
||||
"Custom Base URL": "Custom Base URL <small>Leave blank to use Host URL</small>",
|
||||
"Stop URL": "Stop URL",
|
||||
"Stop Command": "Stop Command",
|
||||
"Digest Authentication": "Digest Authentication",
|
||||
"URL Stop Timeout": "URL Stop Timeout <small>Run stop URL after X milliseconds</small>",
|
||||
"Center": "Center <small>URL Address</small>",
|
||||
"Left": "Left <small>URL Address</small>",
|
||||
"Left Stop": "Left Stop <small>URL Address</small>",
|
||||
"Right": "Right <small>URL Address</small>",
|
||||
"Right Stop": "Right Stop <small>URL Address</small>",
|
||||
"Up": "Up <small>URL Address</small>",
|
||||
"Up Stop": "Up Stop <small>URL Address</small>",
|
||||
"Down": "Down <small>URL Address</small>",
|
||||
"Down Stop": "Down Stop <small>URL Address</small>",
|
||||
"Enable Night Vision": "Enable Night Vision <small>URL Address</small>",
|
||||
"Disable Night Vision": "Disable Night Vision <small>URL Address</small>",
|
||||
"Zoom Out Stop": "Zoom Out Stop <small>URL Address</small>",
|
||||
"Zoom In Stop": "Zoom In Stop <small>URL Address</small>",
|
||||
"Log Level": "Log Level",
|
||||
"Save Log in SQL": "Save Log in SQL <small>This can fill up quickly.</small>",
|
||||
"JPEG": "JPEG",
|
||||
"Web Page": "Web Page",
|
||||
"MJPEG": "MJPEG",
|
||||
"H.264 / H.265 / H.265+": "H.264 / H.265 / H.265+",
|
||||
"HLS (.m3u8)": "HLS (.m3u8)",
|
||||
"MPEG-4 (.mp4 / .ts)": "MPEG-4 (.mp4 / .ts)",
|
||||
"Shinobi Streamer": "Shinobi Streamer",
|
||||
"Dashcam (Streamer v2)": "Dashcam (Streamer v2)",
|
||||
"Local": "Local",
|
||||
"Raw": "Raw",
|
||||
"HTTP": "HTTP",
|
||||
"HTTPS": "HTTPS",
|
||||
"RTSP": "RTSP",
|
||||
"RTMP": "RTMP",
|
||||
"RTMPS": "RTMPS",
|
||||
"UDP": "UDP",
|
||||
"Auto": "Auto",
|
||||
"TCP": "TCP",
|
||||
"Base64 over Websocket": "Base64 over Websocket",
|
||||
"Websocket": "Websocket",
|
||||
"JPEG (Auto Enables JPEG API)": "JPEG (Auto Enables JPEG API)",
|
||||
"HLS (includes Audio)": "HLS (includes Audio)",
|
||||
"MPEG-DASH (includes Audio)": "MPEG-DASH (includes Audio)",
|
||||
"libx264": "libx264",
|
||||
"libx265": "libx265",
|
||||
"copy": "copy",
|
||||
"Audio": "Audio",
|
||||
"Mute Audio": "Vypnout zvuk",
|
||||
"No Audio": "Bez Zvuku",
|
||||
"Popout Monitor on Event": "Vyskakovací okno při události",
|
||||
"aac": "aac",
|
||||
"ac3": "ac3",
|
||||
"libmp3lame": "libmp3lame",
|
||||
"No Rotation": "No Rotation",
|
||||
"180 Degrees": "180 Degrees",
|
||||
"90 Counter Clockwise and Vertical Flip (default)": "90 Counter Clockwise and Vertical Flip (default)",
|
||||
"90 Clockwise": "90 Clockwise",
|
||||
"90 Clockwise and Vertical Flip": "90 Clockwise and Vertical Flip",
|
||||
"Top Right": "Top Right",
|
||||
"Top Left": "Top Left",
|
||||
"Bottom Right": "Bottom Right",
|
||||
"Bottom Left": "Bottom Left",
|
||||
"WebM (libvpx)": "WebM (libvpx)",
|
||||
"Poseidon": "Poseidon",
|
||||
"HEVC (H.265)": "HEVC (H.265)",
|
||||
"MP4 (copy, libx264, libx265)": "MP4 (copy, libx264, libx265)",
|
||||
"Default": "Default",
|
||||
"libvpx (Default)": "libvpx (Default)",
|
||||
"libvpx-vp9": "libvpx-vp9",
|
||||
"libx264 (Default)": "libx264 (Default)",
|
||||
"libvorbis (Default)": "libvorbis (Default)",
|
||||
"libopus": "libopus",
|
||||
"aac (Default)": "aac (Default)",
|
||||
"Traditional (Watch-Only, Includes Buffer)": "Traditional (Watch-Only, Includes Buffer)",
|
||||
"Hotswap Modes (Watch-Only)": "Hotswap Modes (Watch-Only)",
|
||||
"Delete Motionless Videos (Record)": "Delete Motionless Videos (Record)",
|
||||
"US": "US",
|
||||
"EU": "EU",
|
||||
"Silent": "Silent",
|
||||
"Fatal": "Fatal",
|
||||
"on Error": "on Error",
|
||||
"All Warnings": "All Warnings",
|
||||
"Warning": "Warning",
|
||||
"Debug": "Debug",
|
||||
"Export": "Exportovat",
|
||||
"Import": "Importovat",
|
||||
"Simple": "Jednoduché",
|
||||
"Advanced": "Rozšířené",
|
||||
"Error Connecting": "Chyba připojení",
|
||||
"DB Lost.. Retrying..": "Database Lost.. Retrying..",
|
||||
"Retrying...": "Nový pokus...",
|
||||
"Filter Matches": "Filter Matches",
|
||||
"FilterMatchesText1": "This filter has met conditions.",
|
||||
"FilterMatchesText2": "záznamů nalezeno.",
|
||||
"Executed": "Executed",
|
||||
"Deleted": "Deleted",
|
||||
"Query": "Query",
|
||||
"Request": "Request",
|
||||
"System": "System",
|
||||
"Restart Core": "Restart Core",
|
||||
"Restart CRON": "Restart CRON",
|
||||
"Flush PM2 Logs": "Flush PM2 Logs",
|
||||
"Filter ID": "Filter ID",
|
||||
"Webdav Error": "Chyba WebDAV",
|
||||
"WebdavErrorTextTryCreatingDir": "Cannot save. Trying to create directory.",
|
||||
"WebdavErrorTextCreatingDir": "Nelze vytvořit složku.",
|
||||
"File Not Exist": "Soubor neexistuje",
|
||||
"No Videos Found": "Nenalezena žádná videa",
|
||||
"FileNotExistText": "Cannot save non existant file. Something went wrong.",
|
||||
"CameraNotRecordingText": "Settings may be incompatible. Check encoders. Restarting...",
|
||||
"Camera is not running": "Kamera neběží",
|
||||
"Camera is not recording": "Kamera nenahrává",
|
||||
"Camera is not streaming": "Kamera nestreamuje",
|
||||
"Process Started": "Proces spuštěn",
|
||||
"Restarting Process": "Restartuji proces",
|
||||
"Restarting": "Restartuji",
|
||||
"Starting": "Startuje",
|
||||
"Watching": "Sledování",
|
||||
"Recording": "Nahrávání",
|
||||
"Stopped": "Zastaveno",
|
||||
"Died": "Selhalo",
|
||||
"Restart": "Restart",
|
||||
"Monitor Stopped": "Monitor zastaven",
|
||||
"MonitorStoppedText": "Monitor session has been ordered to stop.",
|
||||
"Monitor Idling": "Monitor Idling",
|
||||
"MonitorIdlingText": "Monitor session has been ordered to idle.",
|
||||
"NoMotionEmailText1": "Nezaznamenán pohyb na",
|
||||
"NoMotionEmailText2": "Nebyl detekován žádný pohyb detected na kameře pro",
|
||||
"Monitor Name": "Název monitoru",
|
||||
"Mp4Frag": "Mp4Frag",
|
||||
"Status Changed": "Stav byl změněn",
|
||||
"Monitor is now Idle": "Monitor je nečinný",
|
||||
"Monitor is now Disabled": "Monitor neaktivní",
|
||||
"Monitor is now Watching": "Monitor sleduje",
|
||||
"Monitor is now Recording": "Monitor nahrává",
|
||||
"Space Used": "Využité místo",
|
||||
"Processor": "Procesor",
|
||||
"coProcessor": "koprocesor",
|
||||
"coProcessor Stopped": "koprocesor zastaven",
|
||||
"coProcessor Started": "koprocesor spuštěn",
|
||||
"coProcessorTextStarted": "coProcessor has started for CPU only outputs.",
|
||||
"coProcessorTextStopped": "coProcessor has ended.",
|
||||
"Process Unexpected Exit": "Process Unexpected Exit",
|
||||
"coProcess Unexpected Exit": "coProcess Unexpected Exit",
|
||||
"Process Crashed for Monitor": "Process Crashed for Monitor",
|
||||
"coProcess Crashed for Monitor": "coProcess Crashed for Monitor",
|
||||
"FFmpegCantStart": "FFmpeg Couldn't Start",
|
||||
"FFmpegCantStartText": "The recording engine for this camera could not start. There may be something wrong with your camera configuration. If there are any logs other than this one please post them in the <b>Issues</b> on Github.",
|
||||
"JPEG Error": "JPEG Error",
|
||||
"JPEGErrorText": "There was an issue getting data from your camera.",
|
||||
"Fatal Maximum Reached": "Fatal Maximum Reached, Stopping Camera.",
|
||||
"FatalMaximumReachedText": "JPEG Error was fatal.",
|
||||
"Incorrect Settings Chosen": "Incorrect Settings Chosen",
|
||||
"Can't Connect": "Can't Connect",
|
||||
"Video Finished": "Video Finished",
|
||||
"No Monitors Selected": "No Monitory Selected",
|
||||
"monSavedButNotCopied": "Your monitor was saved but not copied to any other monitor.",
|
||||
"No Monitor Found, Ignoring Request": "No Monitor Found, Ignoring Request",
|
||||
"Event": "Event",
|
||||
"CPU used by this stream": "CPU used by this stream",
|
||||
"Detector Buffer": "Detector Buffer",
|
||||
"EventText1": "Triggered a motion event at",
|
||||
"EventText2": "Could not email image, file was not accessible",
|
||||
"MailError": "MAIL ERROR : Could not send email, Check conf.json. Skipping any features relying on mailing.",
|
||||
"updateKeyText1": "\"updateKey\" is missing from \"conf.json\", cannot do updates this way until you add it.",
|
||||
"updateKeyText2": "\"updateKey\" is incorrect.",
|
||||
"Control Error": "Control Error",
|
||||
"Database row does not exist": "Database row does not exist",
|
||||
"File Delete Error": "File Delete Error",
|
||||
"List of Videos Delete Error": "List of Videos Delete Error",
|
||||
"postDataBroken": "Check the format of the JSON. Ensure it is stringified and defined under 'data'",
|
||||
"ControlErrorText1": "Control is not enabled",
|
||||
"ControlErrorText2": "Check your connection details. You may need to point the Base URL at port 8000 or 80. Check your authentication info.",
|
||||
"NotAuthorizedText1": "Not Authorized, Submit init command with \"auth\",\"ke\", and \"uid\"",
|
||||
"Fields cannot be empty": "Fields cannot be empty",
|
||||
"Email and Password fields cannot be empty": "Email and Password fields cannot be empty",
|
||||
"AccountEditText1": "Could not edit. Refresh page if problem continues.",
|
||||
"Not an Administrator Account": "Not an Administrator Account",
|
||||
"superAdminText": "\"super.json\" does not exist. Please rename \"super.sample.json\" to \"super.json\".",
|
||||
"Enter this code to proceed": "Enter this code to proceed",
|
||||
"FactorAuthText1": "The code will only be active for 15 minutes. If you login again the timer will be reset to 15 minutes with the same code.",
|
||||
"monitorEditText1": "Invalid Data, Check to see this is a valid import string.",
|
||||
"monitorEditText2": "Invalid Details String. Check to see it is a JSON string and not a regular object being passed.",
|
||||
"Monitor Updated by user": "Monitor Updated by user.",
|
||||
"Monitor Added by user": "Monitor Added by user.",
|
||||
"monitorGetText1": "incomplete request, remove last slash in URL or put acceptable value.",
|
||||
"Monitor mode changed": "Monitor mode changed",
|
||||
"Reset Timer": "Reset Timer",
|
||||
"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",
|
||||
"Success": "Success",
|
||||
"Trigger Successful": "Trigger Successful",
|
||||
"No such file": "No such file",
|
||||
"h265BrowserText1": "If you are trying to play an H.265 file, you may need to download it and open it in another application like VLC.",
|
||||
"modifyVideoText1": "Method doesn't exist. Check to make sure that the last value of the URL is not blank.",
|
||||
"CPU indicator will not work. Continuing...": "CPU indicator will not work. Continuing...",
|
||||
"startUpText0": "Checking Disk Used..",
|
||||
"startUpText1": "Completed Checking Disk Used.",
|
||||
"startUpText2": "all users checked, wait to close open files and remove files over user limit",
|
||||
"startUpText3": "waiting to give unfinished video check some time. 3 seconds.",
|
||||
"startUpText4": "Starting Monitory... Please Wait...",
|
||||
"startUpText5": "Shinobi is ready.",
|
||||
"startUpText6": "Orphaned Videos Found and Inserted",
|
||||
"Migrator": "Migrator",
|
||||
"Thumbnail": "Thumbnail",
|
||||
"Host Type": "Host Type",
|
||||
"Edit": "Edit",
|
||||
"Show Matrices": "Show Matrices",
|
||||
"Show Matrix": "Show Matrix",
|
||||
"No Monitor ID Present in Form": "No Monitor ID Present in Form",
|
||||
"State Configuration has no monitory associated": "State Configuration has no monitory associated",
|
||||
"State Configuration Not Found": "State Configuration Not Found",
|
||||
"Inserted State Configuration": "Inserted State Configuration",
|
||||
"Edited State Configuration": "Edited State Configuration",
|
||||
"Deleted State Configuration": "Deleted State Configuration",
|
||||
"Schedule Configuration Not Found": "Schedule Configuration Not Found",
|
||||
"Inserted Schedule Configuration": "Inserted Schedule Configuration",
|
||||
"Edited Schedule Configuration": "Edited Schedule Configuration",
|
||||
"Deleted Schedule Configuration": "Deleted Schedule Configuration",
|
||||
"Dashboard Language": "Jazyk prostředí",
|
||||
"Form Data Not Found": "Form Data Not Found",
|
||||
"File Not Found": "File Not Found",
|
||||
"File Not Found in Filesystem": "File Not Found in Filesystem",
|
||||
"File Not Found in Database": "File Not Found in Database",
|
||||
"No Monitor Exists with this ID.": "No Monitor Exists with this ID.",
|
||||
"Cannot watch a monitor that isn't running.": "Cannot watch a monitor that isn't running.",
|
||||
"Not Permitted": "Not Permitted",
|
||||
"notPermitted1": "This action is not permitted by the administrator of your account.'",
|
||||
"Not Authorized": "Not Authorized",
|
||||
"Generate Subtitles": "Generate Subtitles",
|
||||
"Video Limit":"Video Limit",
|
||||
"Preview":"Preview",
|
||||
"Websocket Connected":"Websocket Connected",
|
||||
"Websocket Disconnected":"Websocket Disconnected",
|
||||
"Token":"Token",
|
||||
"Channel ID":"Channel ID",
|
||||
"New Authentication Token":"New Authentication Token",
|
||||
"All Logs":"All Logs",
|
||||
"For Group":"For Group",
|
||||
"Basic Authentication":"Basic Authentication",
|
||||
"Superuser Logs":"Superuser Logs",
|
||||
"Authentication Failed":"Authentication Failed",
|
||||
"Max Number of Cameras":"Max Number of Cameras",
|
||||
"Can edit Max Storage":"Can edit Max Storage",
|
||||
"Can edit Max Days":"Can edit Max Days",
|
||||
"in Days":"in Days",
|
||||
"Can edit how long to keep Logs":"Can edit how long to keep Logs",
|
||||
"Can use Admin Panel":"Can use Admin Panel",
|
||||
"Can use Discord Bot":"Can use Discord Bot",
|
||||
"Can use WebDAV":"Can use WebDAV",
|
||||
"Can use Amazon S3":"Can use Amazon S3",
|
||||
"Can use LDAP":"Can use LDAP",
|
||||
"Can View Logs":"Can View Logs",
|
||||
"Can edit how long to keep Events":"Can edit how long to keep Events",
|
||||
"Leave blank for unlimited":"Leave blank for unlimited",
|
||||
"Limited":"Limited",
|
||||
"All Privileges":"All Privileges",
|
||||
"LDAP":"LDAP",
|
||||
"LDAP Success":"LDAP Success",
|
||||
"LDAP User Authenticated":"LDAP User Authenticated",
|
||||
"LDAP User is New":"LDAP User is New",
|
||||
"Creating New Account":"Creating New Account",
|
||||
"bindDN":"bindDN",
|
||||
"Bind Credentials":"Bind Credentials (Password)",
|
||||
"Search Base":"Search Base",
|
||||
"Configuration":"Nastavení",
|
||||
"Blank for No Change":"Blank for No Change",
|
||||
"Pop":"Pop",
|
||||
"Recording FPS Change on Start":"Recording FPS Change on Start",
|
||||
"Save Frames to Events":"Save Frames to Events",
|
||||
"Search Filter":"Search Filter",
|
||||
"h264_cuvid": "H.264 CUVID",
|
||||
"hevc_cuvid": "H.265 CUVID",
|
||||
"mjpeg_cuvid": "MJPEG CUVID",
|
||||
"mpeg4_cuvid": "MPEG4 CUVID",
|
||||
"h264_qsv": "H.264 (Quick Sync Video)",
|
||||
"hevc_qsv": "H.265 (Quick Sync Video)",
|
||||
"vp8_qsv": "VP8 (Quick Sync Video)",
|
||||
"mpeg2_qsv": "MPEG2 (Quick Sync Video)",
|
||||
"h264_mmal": "H.264 (Raspberry Pi)",
|
||||
"mpeg2_mmal": "MPEG-2 (Raspberry Pi)",
|
||||
"mpeg4_mmal": "MPEG-4 (Raspberry Pi)",
|
||||
"h264_omx": "H.264 openMAX (Raspberry Pi)",
|
||||
"h264_vaapi": "H.264 VA-API (Intel HW Accel)",
|
||||
"h264_nvenc": "H.264 NVENC (NVIDIA HW Accel)",
|
||||
"hevc_nvenc": "H.265 NVENC (NVIDIA HW Accel)",
|
||||
"hevc_vaapi": "H.265 VA-API (Intel HW Accel)",
|
||||
"vp8_cuvid": "VP8 NVENC (NVIDIA HW Accel)",
|
||||
"vp9_cuvid": "VP9 NVENC (NVIDIA HW Accel)",
|
||||
"hwaccel": "Acceleration Engine",
|
||||
"hwaccel_vcodec": "Video Decoder",
|
||||
"hwaccel_device": "HWAccel Device",
|
||||
"Get Logs to Client": "Get Logs to Client",
|
||||
"Hardware Accelerated": "Hardware Accelerated",
|
||||
"Accelerator": "Accelerator",
|
||||
"drm": "DRM object sharing",
|
||||
"qsv": "qsv",
|
||||
"dxva2": "dxva2 (DirectX Video, Windows)",
|
||||
"vdpau": "vdpau",
|
||||
"vaapi": "vaapi (VA-API)",
|
||||
"vda": "vda (Apple VDA Hardware Acceleration)",
|
||||
"videotoolbox": "videotoolbox",
|
||||
"cuvid": "cuvid (NVIDIA NVENC)",
|
||||
"Main": "Main",
|
||||
"Storage Location": "Storage Location",
|
||||
"Recommended": "Recommended",
|
||||
"Please Wait for Completion": "Please Wait for Completion, Depending on the number of files selected this may take some time. Refresh to check again.",
|
||||
"flv": "flv",
|
||||
"FLV": "FLV",
|
||||
"FLV Stream Type": "FLV Stream Type",
|
||||
"Link Shinobi": "Link Shinobi",
|
||||
"Show Stream HUD":"Show Stream HUD",
|
||||
"Call Method":"Call Method",
|
||||
"Gender":"Gender",
|
||||
"Emotion":"Emotion",
|
||||
"Age":"Age",
|
||||
"Object":"Object",
|
||||
"Uniform":"Uniform",
|
||||
"Pose":"Pose",
|
||||
"Male":"Male",
|
||||
"Female":"Female",
|
||||
"Channel":"Channel",
|
||||
"Stream Key":"Stream Key",
|
||||
"Server URL":"Server URL",
|
||||
"Video Bit Rate":"Video Bit Rate",
|
||||
"Audio Bit Rate":"Audio Bit Rate",
|
||||
"RTMP Stream Flags":"RTMP Stream Flags",
|
||||
"RTMP Stream":"RTMP Stream",
|
||||
"Stream Channel":"Stream Channel",
|
||||
"Confidence":"Confidence",
|
||||
"Trainer Engine":"Trainer Engine",
|
||||
"Train":"Train",
|
||||
"TrainConfirm":"Are you sure you want to begin training? This can take more than 12 hours with over 500 images. This will consume a large amount of resources, like RAM or CPU.",
|
||||
"Batch":"Batch",
|
||||
"Subdivision":"Subdivision",
|
||||
"Map":"Map",
|
||||
"Add Map":"Add Map",
|
||||
"Add Input Feed":"Add Input Feed",
|
||||
"Add Channel":"Add Channel",
|
||||
"Automatic":"Automatic",
|
||||
"Max Latency":"Max Latency",
|
||||
"Loop Stream":"Loop Stream",
|
||||
"Object Tag":"Object Tag",
|
||||
"Noise Filter":"Noise Filter",
|
||||
"Noise Filter Range":"Noise Filter Range",
|
||||
"TV Channel":"TV Channel",
|
||||
"Channel ID":"Channel ID",
|
||||
"TV Channel ID":"TV Channel ID",
|
||||
"TV Channel Group":"TV Channel Group",
|
||||
"Emotion Average":"Emotion Average",
|
||||
"Require Object to be in Region":"Require Object to be in Region",
|
||||
"Show Regions of Interest":"Show Regions of Interest",
|
||||
"Confidence of Detection":"Confidence of Detection",
|
||||
"Edit Selected":"Edit Selected",
|
||||
"Copy Settings":"Copy Settings",
|
||||
"Copy to Settings":"Copy to Settings",
|
||||
"Copy Group Settings":"Copy Group Settings",
|
||||
"Copy Connection Settings":"Copy Connection Settings",
|
||||
"Copy Custom Settings":"Copy Custom Settings",
|
||||
"Copy Logging Settings":"Copy Logging Settings",
|
||||
"Copy JPEG API Settings":"Copy JPEG API Settings",
|
||||
"Copy Input Settings":"Copy Input Settings",
|
||||
"Copy Stream Settings":"Copy Stream Settings",
|
||||
"Copy Stream Channel Settings":"Copy Stream Channel Settings",
|
||||
"Copy Recording Settings":"Copy Recording Settings",
|
||||
"Copy Detector Settings":"Copy Detector Settings",
|
||||
"Monitors to Copy to":"Monitory to Copy to",
|
||||
"Notification Sound":"Zvuk notifikace",
|
||||
"Alert Sound":"Zvuk upozornění",
|
||||
"Alert Sound Delay":"Zpoždění zvukového upozornění",
|
||||
"powerVideoEventLimit":"You have set a high event limit. Are you sure you want to make this request?",
|
||||
"There are no monitors that you can view with this account.":"There are no monitory that you can view with this account.",
|
||||
"Use Built-In":"Použít vestavěné"
|
||||
}
|
|
@ -34,6 +34,10 @@
|
|||
"API": "API",
|
||||
"ONVIF": "ONVIF",
|
||||
"FFprobe": "Probe",
|
||||
"Monitor States": "Monitor States",
|
||||
"Schedule": "Schedule",
|
||||
"Schedules": "Schedules",
|
||||
"Monitor States and Schedules": "Monitor States and Schedules",
|
||||
"Filters": "Filters",
|
||||
"Full URL Path": "Full URL Path",
|
||||
"Logs": "Logs",
|
||||
|
@ -44,6 +48,8 @@
|
|||
"Motion GUI": "Motion GUI",
|
||||
"Motion": "Motion",
|
||||
"Global Detector Settings": "Global Detector Settings",
|
||||
"Trigger Group to Record": "Trigger Group to Record",
|
||||
"Trigger Camera Groups": "Trigger Camera Groups",
|
||||
"Motion Detection": "Motion Detection",
|
||||
"Object Detection": "Object Detection",
|
||||
"JPEG Mode": "JPEG Mode",
|
||||
|
@ -116,6 +122,8 @@
|
|||
"Can View Videos and Events": "Can View Videos and Events",
|
||||
"Can Delete Videos and Events": "Can Delete Videos and Events",
|
||||
"Saved Filters": "Saved Filters",
|
||||
"Saved Presets": "Saved Presets",
|
||||
"Saved Schedules": "Saved Schedules",
|
||||
"Filter Name": "Filter Name",
|
||||
"Find Where": "Find Where",
|
||||
"Reason": "Reason",
|
||||
|
@ -168,6 +176,15 @@
|
|||
"Motion Meter": "Motion Meter",
|
||||
"FFmpegTip": "FFprobe is a simple multimedia streams analyzer. You can use it to output all kinds of information about an input including duration, frame rate, frame size, etc.",
|
||||
"Complete Stream URL": "Complete Stream URL",
|
||||
"Primary Input": "Primary Input",
|
||||
"All streams in first feed": "All streams in first feed",
|
||||
"First stream in feed": "First stream in feed",
|
||||
"Second stream in feed": "Second stream in feed",
|
||||
"Video streams only": "Video streams only",
|
||||
"Video stream only from first feed": "Video stream only from first feed",
|
||||
"Audio streams only": "Audio streams only",
|
||||
"Audio stream only from first feed": "Audio stream only from first feed",
|
||||
"ONVIF Port": "ONVIF Port",
|
||||
"ONVIF Scanner": "ONVIF Scanner",
|
||||
"ONVIFEventsNotAvailable": "ONVIF Events not Available",
|
||||
"ONVIFnotCompliantProfileT": "Camera is not ONVIF Profile T Compliant",
|
||||
|
@ -198,18 +215,29 @@
|
|||
"Number of Days to keep": "Number of Days to keep",
|
||||
"Monitor Groups": "Monitor Groups",
|
||||
"Group Name": "Group Name",
|
||||
"Show Thumbnails in Video List": "Show Thumbnails in Video List",
|
||||
"WebDAV": "WebDAV",
|
||||
"Backblaze B2": "Backblaze B2",
|
||||
"Backblaze Error": "Backblaze Error",
|
||||
"SFTP (SSH File Transfer)": "SFTP (SSH File Transfer)",
|
||||
"SFTP Error": "SFTP Error",
|
||||
"SFTP": "SFTP",
|
||||
"Could not create Bucket.": "Could not create Bucket.",
|
||||
"Amazon S3": "Amazon S3",
|
||||
"Wasabi Hot Cloud Storage": "Wasabi Hot Cloud Storage",
|
||||
"Database": "Database",
|
||||
"Database Not Found": "Database Not Found",
|
||||
"User Not Found": "User Not Found",
|
||||
"Save Links to Database": "Save Links to Database",
|
||||
"Upload File": "Upload File",
|
||||
"Bucket": "Bucket",
|
||||
"Region": "Region",
|
||||
"Use Global Amazon S3 Video Storage": "Use Global Amazon S3 Video Storage",
|
||||
"Use Global Wasabi Hot Cloud Storage Video Storage": "Use Global Wasabi Hot Cloud Storage Video Storage",
|
||||
"Use Global Backblaze B2 Video Storage": "Use Global Backblaze B2 Video Storage",
|
||||
"Use Global WebDAV Video Storage": "Use Global WebDAV Video Storage",
|
||||
"Amazon S3 Upload Error": "Amazon S3 Upload Error",
|
||||
"Wasabi Hot Cloud Storage Upload Error": "Wasabi Hot Cloud Storage Upload Error",
|
||||
"accountId": "Account ID",
|
||||
"applicationKey": "Application Key",
|
||||
"aws_accessKeyId": "Access Key Id",
|
||||
|
@ -250,13 +278,17 @@
|
|||
"Set to Watch Only": "Set to Watch Only",
|
||||
"Save as": "Save as",
|
||||
"Add New": "Add New",
|
||||
"Merge and Download": "Merge and Download",
|
||||
"Zip and Download": "Zip and Download",
|
||||
"Merge Selected Videos": "Merge Selected Videos",
|
||||
"Export Selected Videos": "Export Selected Videos",
|
||||
"Delete Selected Videos": "Delete Selected Videos",
|
||||
"DeleteSelectedVideosMsg": "Do you want to delete these videos? You cannot recover them.",
|
||||
"ExportSelectedVideosMsg": "Do you want to export these videos? It may take some time to zip and download.",
|
||||
"MergeSelectedVideosMsg": "Do you want to merge these videos? It may take some time to merge and download. The moment the connection is closed the file will be deleted. Ensure you keep the browser open until it is complete.",
|
||||
"clientStreamFailedattemptingReconnect": "Client side ctream check failed, attempting reconnect.",
|
||||
"Export Video": "Export Video",
|
||||
"Merge Video": "Merge Video",
|
||||
"Delete Filter": "Delete Filter",
|
||||
"confirmDeleteFilter": "Do you want to delete this filter? You cannot recover it.",
|
||||
"Fix Video": "Fix Video",
|
||||
|
@ -283,9 +315,15 @@
|
|||
"DeleteMonitorText": "Do you want to delete this monitor? You cannot recover it. You can choose for the files to remain in the filesystem. If you choose to recreate a monitor with the same ID the videos and events will become visible in the dashboard.",
|
||||
"DeleteMonitorsText": "Do you want to delete these monitors? You cannot recover them. You can choose to keep the files for these IDs in the filesystem. If you choose to recreate a monitor with one of the IDs the videos and events will become visible in the dashboard.",
|
||||
"Invalid JSON": "Invalid JSON",
|
||||
"Invalid Data": "Invalid Data",
|
||||
"Name cannot be empty.": "Name cannot be empty.",
|
||||
"Start Time cannot be empty.": "Start Time cannot be empty.",
|
||||
"Must be atleast one row": "Must be atleast one row",
|
||||
"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.",
|
||||
"Group Key is in use.": "Group Key is in use.",
|
||||
"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)",
|
||||
"Video Length (minutes) and Motion Count per video": "Video Length (minutes) and Motion Count per video",
|
||||
|
@ -324,16 +362,23 @@
|
|||
"Filter for Objects only": "Filter for Objects only",
|
||||
"Custom": "Custom",
|
||||
"Detector": "Detector",
|
||||
"Audio Detector": "Audio Detector",
|
||||
"Audio Detection": "Audio Detection",
|
||||
"Minimum dB": "Minimum dB",
|
||||
"Maximum dB": "Maximum dB",
|
||||
"Connected": "Connected",
|
||||
"Not Saved": "Not Saved",
|
||||
"Not Connected": "Not Connected",
|
||||
"Lisence Plate Detector": "Lisence Plate Detector",
|
||||
"License Plate Detector": "License Plate Detector",
|
||||
"OpenCV Cascades": "OpenCV Cascades",
|
||||
"Refresh List of Cascades": "Refresh List of Cascades",
|
||||
"\"No Motion\" Detector": "\"No Motion\" Detector",
|
||||
"Control": "Control",
|
||||
"Grouping": "Grouping <small>Add groups in <b>Settings</b></small>",
|
||||
"Detector Grouping": "Detector Grouping <small>Add groups in <b>Settings</b></small>",
|
||||
"Detector Recording Process Exited Prematurely. Restarting.": "Detector Recording Process Exited Prematurely. Restarting.",
|
||||
"Detector Recording Complete": "Detector Recording Complete",
|
||||
"Clear Recorder Process": "Clear Recorder Process",
|
||||
"Logging": "Logging",
|
||||
"IdentityText1": "This is how the system will identify the data for this stream. You cannot change the <b>Monitor ID</b> once you have pressed save. If you want you can make the <b>Monitor ID</b> more human readable before you continue.",
|
||||
"IdentityText2": "You can duplicate a monitor by modifying the <b>Monitor ID</b> then pressing save. You <b>cannot</b> use the ID of a monitor that already exists or it will save over that monitor's database information.",
|
||||
|
@ -367,6 +412,7 @@
|
|||
"HLS Preset": "Preset Template",
|
||||
"HLS List Size": "List Size",
|
||||
"Traditional Recording": "Traditional Recording",
|
||||
"Recorded Buffer": "Recorded Buffer",
|
||||
"Buffer Preview": "Buffer Preview",
|
||||
"HLS Start Number": "HLS Start Number",
|
||||
"HLS Live Start Index": "HLS Live Start Index",
|
||||
|
@ -391,8 +437,19 @@
|
|||
"Image Width": "Image Width",
|
||||
"Image Height": "Image Height",
|
||||
"Record File Type": "Record File Type",
|
||||
"Notification Video Length": "Notification Video Length",
|
||||
"Video Codec": "Video Codec",
|
||||
"Delete Monitor States Preset": "Delete Monitor States Preset",
|
||||
"Delete Monitor State?": "Delete Monitor State",
|
||||
"deleteMonitorStateText1": "Do you want to delete this Monitor States Preset?",
|
||||
"deleteMonitorStateText2": "Do you want to delete this Monitor's Preset?",
|
||||
"Search Images": "Search Images",
|
||||
"Launch in New Window": "Launch in New Window",
|
||||
"Preset": "Preset",
|
||||
"Presets": "Presets",
|
||||
"possibleInternalError": "Possible Internal Error",
|
||||
"sizePurgeLockedText": "The Size Purge Lock (deleteOverMax) appears to have failed to unlock. Unlocking now...",
|
||||
"Use coProcessor": "Use coProcessor",
|
||||
"Audio Codec": "Audio Codec",
|
||||
"Video Record Rate": "Video Record Rate <small>(FPS)</small>",
|
||||
"Record Width": "Record Width",
|
||||
|
@ -407,6 +464,7 @@
|
|||
"Stream to YouTube": "Stream to YouTube",
|
||||
"Stream to YouTube Flags": "Stream to YouTube Flags",
|
||||
"Recording Flags": "Recording Flags",
|
||||
"Traditional Recording Flags": "Traditional Recording Flags",
|
||||
"Output Method": "Output Method",
|
||||
"Webhook": "Webhook",
|
||||
"Event Webhook Error": "Event Webhook Error",
|
||||
|
@ -418,10 +476,13 @@
|
|||
"Save Events to SQL": "Save Events to SQL",
|
||||
"Email on Trigger": "Email on Trigger <small>Emails go to the main account holder's login address.</small>",
|
||||
"Attach Video Clip": "Attach Video Clip",
|
||||
"Error While Decoding": "Error While Decoding",
|
||||
"ErrorWhileDecodingText": "Your hardware may have an unstable connection to the network. Check your network connections.",
|
||||
"Discord": "Discord",
|
||||
"Discord Alert on Trigger": "Discord Alert on Trigger",
|
||||
"Allow Next Email": "Allow Next Email <small>in Minutes</small>",
|
||||
"Allow Next Discord Alert": "Allow Next Discord Alert <small>in Minutes</small>",
|
||||
"DiscordLoggedIn": "Discord Bot Authenticated",
|
||||
"DiscordFailedText": "Sending to Discord Failed",
|
||||
"DiscordErrorText": "Sending to Discord caused an Error",
|
||||
"DiscordNotEnabledText": "Discord Bot Not Enabled, Enable it in your Account Settings.",
|
||||
|
@ -633,6 +694,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",
|
||||
"Success": "Success",
|
||||
"Trigger Successful": "Trigger Successful",
|
||||
"No such file": "No such file",
|
||||
"h265BrowserText1": "If you are trying to play an H.265 file, you may need to download it and open it in another application like VLC.",
|
||||
|
@ -657,6 +719,10 @@
|
|||
"Inserted State Configuration": "Inserted State Configuration",
|
||||
"Edited State Configuration": "Edited State Configuration",
|
||||
"Deleted State Configuration": "Deleted State Configuration",
|
||||
"Schedule Configuration Not Found": "Schedule Configuration Not Found",
|
||||
"Inserted Schedule Configuration": "Inserted Schedule Configuration",
|
||||
"Edited Schedule Configuration": "Edited Schedule Configuration",
|
||||
"Deleted Schedule Configuration": "Deleted Schedule Configuration",
|
||||
"Dashboard Language": "Dashboard Language",
|
||||
"Form Data Not Found": "Form Data Not Found",
|
||||
"File Not Found": "File Not Found",
|
||||
|
@ -672,6 +738,7 @@
|
|||
"Preview":"Preview",
|
||||
"Websocket Connected":"Websocket Connected",
|
||||
"Websocket Disconnected":"Websocket Disconnected",
|
||||
"Videos Merge":"Videos Merge",
|
||||
"Token":"Token",
|
||||
"Channel ID":"Channel ID",
|
||||
"New Authentication Token":"New Authentication Token",
|
||||
|
@ -689,10 +756,13 @@
|
|||
"Can use Discord Bot":"Can use Discord Bot",
|
||||
"Can use WebDAV":"Can use WebDAV",
|
||||
"Can use Amazon S3":"Can use Amazon S3",
|
||||
"Can use SFTP":"Can use SFTP",
|
||||
"Can use Wasabi Hot Cloud Storage":"Can use Wasabi Hot Cloud Storage",
|
||||
"Can use LDAP":"Can use LDAP",
|
||||
"Can View Logs":"Can View Logs",
|
||||
"Can edit how long to keep Events":"Can edit how long to keep Events",
|
||||
"Leave blank for unlimited":"Leave blank for unlimited",
|
||||
"privateKey":"Private Key",
|
||||
"Limited":"Limited",
|
||||
"All Privileges":"All Privileges",
|
||||
"LDAP":"LDAP",
|
||||
|
@ -768,6 +838,11 @@
|
|||
"RTMP Stream":"RTMP Stream",
|
||||
"Stream Channel":"Stream Channel",
|
||||
"Confidence":"Confidence",
|
||||
"Trainer Engine":"Trainer Engine",
|
||||
"Train":"Train",
|
||||
"TrainConfirm":"Are you sure you want to begin training? This can take more than 12 hours with over 500 images. This will consume a large amount of resources, like RAM or CPU.",
|
||||
"Batch":"Batch",
|
||||
"Subdivision":"Subdivision",
|
||||
"Map":"Map",
|
||||
"Add Map":"Add Map",
|
||||
"Add Input Feed":"Add Input Feed",
|
||||
|
@ -783,6 +858,7 @@
|
|||
"TV Channel ID":"TV Channel ID",
|
||||
"TV Channel Group":"TV Channel Group",
|
||||
"Emotion Average":"Emotion Average",
|
||||
"Require Object to be in Region":"Require Object to be in Region",
|
||||
"Show Regions of Interest":"Show Regions of Interest",
|
||||
"Confidence of Detection":"Confidence of Detection",
|
||||
"Edit Selected":"Edit Selected",
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"\"No Motion\" Detector": "\"Aucun Mouvement\" Détecteur De",
|
||||
"# of Allow MJPEG Clients": "# of Allow MJPEG Clients <small>0 for infinite</small>",
|
||||
"\"No Motion\" Detector": "\"Aucun Mouvement\" Détecteur",
|
||||
"# of Allow MJPEG Clients": "# d'autoriser les clients MJPEG <small>0 pour l'infini</small>",
|
||||
"180 Degrees": "180 Degrés",
|
||||
"2-Factor Authentication": "Une Authentification À 2 Facteurs",
|
||||
"2-Factor Authentication": "Authentification À 2 Facteurs",
|
||||
"90 Clockwise": "90 dans le sens des Aiguilles",
|
||||
"90 Clockwise and Vertical Flip": "90 vers la gauche et Vertical Flip",
|
||||
"90 Clockwise and Vertical Flip": "90 vers la gauche et rotation Vertical",
|
||||
"90 Counter Clockwise and Vertical Flip (default)": "90 dans le sens antihoraire et la rotation Verticale (par défaut)",
|
||||
"API": "API",
|
||||
"API Key": "Clé API",
|
||||
|
@ -15,30 +15,30 @@
|
|||
"APIKeyDeletedText": "Clé a été supprimé. Elle ne fonctionnera plus.",
|
||||
"ASC": "ASC",
|
||||
"Accelerator": "Accélérateur",
|
||||
"Account Info": "Les Informations De Compte",
|
||||
"Account Info": "Les Informations Du Compte",
|
||||
"AccountEditText1": "Ne pouvait pas modifier. Actualiser la page si le problème persiste.",
|
||||
"Accounts": "Comptes",
|
||||
"Action for Selected": "Action Sélectionnés",
|
||||
"Add": "Ajouter",
|
||||
"Add Channel": "Ajouter Un Canal",
|
||||
"Add Input Feed": "Ajoutez D'Entrée D'Alimentation",
|
||||
"Add Input Feed": "Ajouter un flux d'entrée",
|
||||
"Add Map": "Ajouter La Carte",
|
||||
"Add Monitor": "Ajouter Moniteur",
|
||||
"Add New": "Ajouter De Nouveaux",
|
||||
"Add New": "Ajouter Nouveau",
|
||||
"Admin": "Admin",
|
||||
"Advanced": "Avancé",
|
||||
"Again": "Encore",
|
||||
"Age": "L'âge",
|
||||
"Age": "Age",
|
||||
"All Logs": "Tous Les Journaux",
|
||||
"All Monitors": "Tous Les Moniteurs",
|
||||
"All Monitors and Privileges": "Tous les Moniteurs et Privilèges",
|
||||
"All Privileges": "Tous Les Privilèges",
|
||||
"All Warnings": "Tous Les Avertissements",
|
||||
"Allow Next Command": "Permettre Prochaine Commande <small>en quelques Minutes</small>",
|
||||
"Allow Next Email": "Permettre Prochain Email <small>en quelques Minutes</small>",
|
||||
"Allow Next Trigger": "Permettre à Côté de Déclenchement <small>en Millisecondes</small>",
|
||||
"Allowed IPs": "Permis IPs",
|
||||
"Analyzation Duration": "Analyzation Durée",
|
||||
"Allow Next Command": "Autoriser la commande suivante <small>en quelques Minutes</small>",
|
||||
"Allow Next Email": "Autoriser le prochain Email <small>en quelques Minutes</small>",
|
||||
"Allow Next Trigger": "Autoriser le déclencheur suivant <small>en Millisecondes</small>",
|
||||
"Allowed IPs": "IPs autorisés",
|
||||
"Analyzation Duration": "Durée d'analyse",
|
||||
"Archive": "Archive",
|
||||
"Are you sure?": "Êtes-vous sûr?",
|
||||
"Audio Bit Rate": "Le Débit Binaire Audio",
|
||||
|
@ -51,49 +51,49 @@
|
|||
"Base64 over Websocket": "Base64 sur Websocket",
|
||||
"Basic Authentication": "L'Authentification De Base",
|
||||
"Bind Credentials": "Lier Les Informations D'Identification (Mot De Passe)",
|
||||
"Blank for No Change": "Vide pour Pas Changer",
|
||||
"Blank for No Change": "Vide pour aucun changement",
|
||||
"Bottom Left": "En Bas À Gauche",
|
||||
"Bottom Right": "En Bas À Droite",
|
||||
"Browser Console Log": "Le Navigateur De La Console De Log",
|
||||
"Buffer Preview": "Tampon Aperçu",
|
||||
"Browser Console Log": "Journal de la console du navigateur",
|
||||
"Buffer Preview": "Aperçu de la mémoire tampon",
|
||||
"CPU": "CPU",
|
||||
"CPU indicator will not work. Continuing...": "CPU indicateur ne fonctionnera pas. Continue...",
|
||||
"CPU indicator will not work. Continuing...": "L'indicateur du CPU ne fonctionnera pas. En continuant ...",
|
||||
"CSS": "CSS <small>Style de votre tableau de bord.</small>",
|
||||
"Calendar": "Calendrier",
|
||||
"Call Method": "Appel De La Méthode",
|
||||
"Call Method": "Méthode d'appel",
|
||||
"Camera Password": "Mot De Passe De La Caméra",
|
||||
"Camera Username": "Nom D'Utilisateur De La Caméra",
|
||||
"Camera is not recording": "Appareil photo n'est pas de l'enregistrement",
|
||||
"Camera is not recording": "La caméra n'enregistre pas",
|
||||
"Camera is not streaming": "La caméra n'est pas en streaming",
|
||||
"CameraNotRecordingText": "Les paramètres peuvent être incompatibles. Vérifier les encodeurs. Le redémarrage...",
|
||||
"Can Authenticate Websocket": "Peut Authentifier Websocket",
|
||||
"Can Control Monitors": "Peut Moniteurs De Contrôle",
|
||||
"Can Delete Videos": "Pouvez Supprimer Des Vidéos",
|
||||
"Can Delete Videos and Events": "Pouvez Supprimer des Vidéos et des Événements",
|
||||
"Can Edit Monitor": "Pouvez Modifier Moniteur",
|
||||
"Can Get Logs": "Pouvez Obtenir Les Journaux",
|
||||
"Can Get Monitors": "Pouvez Obtenir Des Moniteurs",
|
||||
"Can View Monitor": "Peut Moniteur De Vue",
|
||||
"Can Control Monitors": "Peut contrôler les moniteurs",
|
||||
"Can Delete Videos": "Peut supprimer des vidéos",
|
||||
"Can Delete Videos and Events": "Peut supprimer des vidéos et des événements",
|
||||
"Can Edit Monitor": "Peut modifier le moniteur",
|
||||
"Can Get Logs": "Peut Obtenir Les Journaux",
|
||||
"Can Get Monitors": "Peut Obtenir Des Moniteurs",
|
||||
"Can View Monitor": "Peut afficher le moniteur",
|
||||
"Can View Snapshots": "Peut Afficher Des Instantanés",
|
||||
"Can View Streams": "Peut Afficher Des Flux",
|
||||
"Can View Videos": "Peut Visionner Des Vidéos",
|
||||
"Can View Videos and Events": "Pouvez Visualiser des Vidéos et des Événements",
|
||||
"Can edit Max Days": "Pouvez éditer Max Jours",
|
||||
"Can edit Max Storage": "Pouvez éditer Max de Stockage",
|
||||
"Can edit how long to keep Events": "Pouvez modifier la durée de conservation des Événements",
|
||||
"Can edit how long to keep Logs": "Pouvez modifier la durée de conservation des Journaux d'",
|
||||
"Can use Admin Panel": "Pouvez utiliser le Panneau d'administration",
|
||||
"Can edit Max Days": "Peut éditer le maximum de Jours",
|
||||
"Can edit Max Storage": "Peut éditer maximum de Stockage",
|
||||
"Can edit how long to keep Events": "Peut modifier la durée de conservation des Événements",
|
||||
"Can edit how long to keep Logs": "Peut modifier la durée de conservation des Journaux d'",
|
||||
"Can use Admin Panel": "Peut utiliser le Panneau d'administration",
|
||||
"Can use LDAP": "Peut utiliser le protocole LDAP",
|
||||
"Can use WebDAV": "Pouvez utiliser WebDAV",
|
||||
"Can use WebDAV": "Peut utiliser WebDAV",
|
||||
"Can't Connect": "Ne peut pas se Connecter",
|
||||
"Cannot watch a monitor that isn't running.": "Impossible de regarder un moniteur qui n'est pas en cours d'exécution.",
|
||||
"Center": "Centre d' <small>Adresse URL</small>",
|
||||
"Channel": "Canal",
|
||||
"Channel ID": "ID de canal",
|
||||
"Chat on Discord": "Chat sur la Discorde",
|
||||
"Chat on Discord": "Discuter sur Discord",
|
||||
"Check": "Vérifier",
|
||||
"Check Signal Interval": "Vérifier le Signal de l'Intervalle <small>en Minutes</small>",
|
||||
"Check for Motion First": "Vérifier le Mouvement de la Première",
|
||||
"Check Signal Interval": "Vérifier l'intervalle du signal <small>en Minutes</small>",
|
||||
"Check for Motion First": "Vérifier le Mouvement d'abord",
|
||||
"Close": "Fermer",
|
||||
"Closed": "Fermé",
|
||||
"Command": "Commande",
|
||||
|
@ -107,42 +107,42 @@
|
|||
"Connection": "Connexion",
|
||||
"Connection Type": "Type De Connexion",
|
||||
"Control": "Contrôle",
|
||||
"Control Error": "Erreur De Contrôle De La",
|
||||
"Control Error": "Erreur De Contrôle",
|
||||
"ControlErrorText1": "Le contrôle n'est pas activé",
|
||||
"Controllable": "Contrôlable",
|
||||
"Copy Connection Settings": "Copie Des Paramètres De Connexion",
|
||||
"Copy Custom Settings": "Copie Des Paramètres Personnalisés",
|
||||
"Copy Detector Settings": "Copie Réglages Du Détecteur",
|
||||
"Copy Group Settings": "Copier Les Paramètres De Groupe",
|
||||
"Copy Connection Settings": "Copier les paramètres de connexion",
|
||||
"Copy Custom Settings": "Copier les paramètres personnalisés",
|
||||
"Copy Detector Settings": "Copier les réglages de Détection",
|
||||
"Copy Group Settings": "Copier Les Paramètres Du Groupe",
|
||||
"Copy Input Settings": "Copier Les Paramètres D'Entrée",
|
||||
"Copy Logging Settings": "Copie Des Paramètres De Journalisation",
|
||||
"Copy Recording Settings": "Copier Les Paramètres D'Enregistrement",
|
||||
"Copy Settings": "Paramètres De Copie",
|
||||
"Copy Stream Channel Settings": "Copie De Flux De Paramètres De Canal",
|
||||
"Copy Stream Channel Settings": "Copier les réglages du canal",
|
||||
"Copy Stream Settings": "Copier Les Paramètres De Flux",
|
||||
"Copy to Settings": "Copie des Paramètres de",
|
||||
"Country of Plates": "Pays de Plaques",
|
||||
"Counts of Motion": "Comtes de Mouvement",
|
||||
"Creating New Account": "La Création D'Un Nouveau Compte",
|
||||
"Copy to Settings": "Copier dans les paramètres",
|
||||
"Country of Plates": "Pays de Plaques d'immatriculation",
|
||||
"Counts of Motion": "Comptes de mouvement",
|
||||
"Creating New Account": "Créer un nouveau compte",
|
||||
"Current": "Actuel",
|
||||
"Currently viewing": "Visualisez actuellement",
|
||||
"Currently viewing": "En cours de visionnage",
|
||||
"Custom": "Personnalisé",
|
||||
"Custom Base URL": "Base d'URL personnalisée <small>Laissez le champ vide pour utiliser l'Hôte de l'URL</small>",
|
||||
"DB Lost.. Retrying..": "Base De Données Perdues.. Réessayer..",
|
||||
"DESC": "DESC",
|
||||
"Dashboard": "Tableau de bord",
|
||||
"Dashboard Language": "Langue Du Tableau De Bord",
|
||||
"Dashboard Language": "Langue du Tableau De Bord",
|
||||
"Dashcam": "Dashcam",
|
||||
"Dashcam (Streamer v2)": "Dashcam (Streamer v2)",
|
||||
"Date Range": "La Plage De Dates",
|
||||
"Date Range": "Plage De Dates",
|
||||
"Debug": "Debug",
|
||||
"Default": "Par défaut",
|
||||
"Delete": "Supprimer",
|
||||
"Delete Filter": "Supprimer Le Filtre",
|
||||
"Delete Matches": "Supprimer Les Matchs",
|
||||
"Delete Monitor": "Supprimer Moniteur",
|
||||
"Delete Motionless Video": "Supprimer Immobile Vidéo",
|
||||
"Delete Motionless Videos (Record)": "Supprimer Immobile Vidéos (Enregistrement)",
|
||||
"Delete Filter": "Supprimer le Filtre",
|
||||
"Delete Matches": "Supprimer les correspondances",
|
||||
"Delete Monitor": "Supprimer le Moniteur",
|
||||
"Delete Motionless Video": "Supprimer la vidéo immobile",
|
||||
"Delete Motionless Videos (Record)": "Supprimer des vidéos immobiles (Enregistrer)",
|
||||
"Delete Selected Videos": "Supprimer La Sélection Des Vidéos",
|
||||
"Delete Video": "Supprimer La Vidéo",
|
||||
"Delete selected": "Supprimer la sélection",
|
||||
|
@ -152,30 +152,30 @@
|
|||
"DeleteVideoMsg": "Voulez-vous supprimer cette vidéo? Vous ne pouvez pas le récupérer.",
|
||||
"Deleted": "Supprimé",
|
||||
"Detect Objects": "Détecter les Objets <small class=\"\">Voir ci-dessous</small>",
|
||||
"Detector": "Détecteur de",
|
||||
"Detector": "Détecteur",
|
||||
"Detector Buffer": "Détecteur De Tampon",
|
||||
"Detector Flags": "Détecteur De Drapeaux",
|
||||
"Detector Grouping": "Détecteur de Groupement <small>d'Ajouter des groupes de <b>Paramètres</b></small>",
|
||||
"Detector Rate": "Détecteur de Taux <small>(FPS)</small>",
|
||||
"Detector Grouping": "Groupement de détecteurs <small>d'Ajouter des groupes de <b>Paramètres</b></small>",
|
||||
"Detector Rate": "Taux de détecteur <small>(FPS)</small>",
|
||||
"DetectorText": "<p>Lorsque les zones Largeur et Hauteur sont affichés, vous devriez les mettre à 640x480 ou ci-dessous. Cela permettra d'optimiser la vitesse de lecture d'images.</p>",
|
||||
"Disable Night Vision": "Désactiver la Vision de Nuit <small>Adresse URL</small>",
|
||||
"Disable Nightvision": "Désactiver Nightvision",
|
||||
"Disable Night Vision": "Désactiver la vision nocturne <small>Adresse URL</small>",
|
||||
"Disable Nightvision": "Désactiver la vision nocturne",
|
||||
"Disabled": "Désactivé",
|
||||
"Documentation": "La Documentation",
|
||||
"Documentation": "Documentation",
|
||||
"Don't show this anymore": "Ne pas afficher plus",
|
||||
"Double Quote Directory": "Devis Double Répertoire de <small>Certains répertoires sont séparés par des espaces. L'utilisation de ce peut se bloquer certains appareils photo.</small>",
|
||||
"Double Quote Directory": "Devis Double Répertoire <small>Certains répertoires sont séparés par des espaces. L'utilisation de ce peut se bloquer certaines cameras.</small>",
|
||||
"Down": "Bas <small>Adresse URL</small>",
|
||||
"Down Stop": "Vers le bas Arrêter <small>Adresse URL</small>",
|
||||
"Down Stop": "Arrêt bas <small>Adresse URL</small>",
|
||||
"Download": "Télécharger",
|
||||
"EU": "L'UE",
|
||||
"EU": "UE",
|
||||
"Edit": "Modifier",
|
||||
"Edit Selected": "Modifier Les",
|
||||
"Edit Selected": "Modifier la sélection",
|
||||
"Email": "E-mail",
|
||||
"Email Details": "E-Mail Les Détails",
|
||||
"Email Details": "Détails de l'e-mail",
|
||||
"Email on No Motion": "E-mail sur \"Aucun Mouvement\"",
|
||||
"Email on Trigger": "E-mail sur la Gâchette <small>e-Mails passent à la principale du titulaire de compte de connexion l'adresse.</small>",
|
||||
"Emotion": "L'émotion",
|
||||
"Emotion Average": "L'Émotion De La Moyenne",
|
||||
"Emotion Average": "Emotion Moyenne",
|
||||
"Enable": "Activer",
|
||||
"Enable Night Vision": "Activer la Vision Nocturne <small>Adresse URL</small>",
|
||||
"Enable Nightvision": "Activer La Vision Nocturne",
|
||||
|
@ -184,13 +184,13 @@
|
|||
"End Time": "Heure De Fin",
|
||||
"Ended": "Terminé",
|
||||
"Enlarge": "Agrandir",
|
||||
"Enter this code to proceed": "Entrez ce code pour procéder",
|
||||
"Enter this code to proceed": "Entrez ce code pour continuer",
|
||||
"Equal to": "Égal à",
|
||||
"Error Connecting": "Erreur Lors De La Connexion",
|
||||
"Event": "L'événement",
|
||||
"Event Limit": "Cas Limite",
|
||||
"EventText1": "Déclenché un mouvement à",
|
||||
"EventText2": "Pourrait pas l'email de l'image, le fichier n'est pas accessible",
|
||||
"Event Limit": "Limite d'événement",
|
||||
"EventText1": "Mouvement détectée à",
|
||||
"EventText2": "Impossible d'envoyer l'image par l'email, le fichier n'est pas accessible",
|
||||
"Events": "Les événements",
|
||||
"Example": "Exemple",
|
||||
"Execute Command": "Exécuter La Commande",
|
||||
|
@ -198,22 +198,22 @@
|
|||
"Export": "L'exportation",
|
||||
"FFmpegCantStart": "FFmpeg ne Pouvait pas Commencer",
|
||||
"FFmpegCantStartText": "Le moteur d'enregistrement pour cette caméra n'a pas pu démarrer. Il y a peut être quelque chose de mal avec votre configuration de la caméra. S'il y a des journaux autres que celui-ci, veuillez les poster dans les <b>Questions</b> sur Github.",
|
||||
"FFmpegTip": "FFprobe est un simple flux multimédias de l'analyseur. Vous pouvez l'utiliser pour afficher toutes sortes d'informations sur les intrants, y compris la durée, taux d'armature, taille de l'image, etc.",
|
||||
"FFmpegTip": "FFprobe est un analyseur de flux multimédia simple. Vous pouvez l’utiliser pour générer toutes sortes d’informations sur une entrée, notamment la durée, la cadence, la taille, etc.",
|
||||
"FFprobe": "Sonde",
|
||||
"FLV": "FLV",
|
||||
"FLV Stream Type": "FLV Type de Flux",
|
||||
"FactorAuthText1": "Le code ne sera actif pendant 15 minutes. Si vous vous connectez de nouveau la minuterie est réinitialisée à 15 minutes avec le même code.",
|
||||
"Fatal": "Fatale",
|
||||
"Fatal Maximum Reached": "Fatale Maximum Atteint, L'Arrêt De La Caméra.",
|
||||
"FatalMaximumReachedText": "JPEG Erreur a été fatale.",
|
||||
"Feed-in Image Height": "De rachat à la Hauteur de l'Image",
|
||||
"Feed-in Image Width": "Alimentation sur la Largeur de l'Image",
|
||||
"FactorAuthText1": "Le code ne sera actif que pendant 15 minutes. Si vous vous reconnectez, le chronomètre sera réinitialisé à 15 minutes avec le même code.",
|
||||
"Fatal": "Fatal",
|
||||
"Fatal Maximum Reached": "Maximum fatal atteint, L'Arrêt De La Caméra.",
|
||||
"FatalMaximumReachedText": "Erreur JPEG fatale.",
|
||||
"Feed-in Image Height": "Hauteur de l'image d'alimentation",
|
||||
"Feed-in Image Width": "Largeur de l'image d'alimentation",
|
||||
"Female": "Femelle",
|
||||
"Fields cannot be empty": "Les champs ne peut pas être vide",
|
||||
"File Not Exist": "Le Fichier N'Existe Pas",
|
||||
"File Not Exist": "Fichier non existant",
|
||||
"File Not Found": "Fichier Non Trouvé",
|
||||
"File Type": "Type De Fichier",
|
||||
"FileNotExistText": "Impossible d'enregistrer non existant fichier. Quelque chose s'est mal passé.",
|
||||
"FileNotExistText": "Impossible de sauvegarder le fichier non existant. Quelque chose s'est mal passé.",
|
||||
"Filename": "Filename",
|
||||
"Filesize": "La taille du fichier",
|
||||
"Filter ID": "Filtre ID",
|
||||
|
@ -228,7 +228,7 @@
|
|||
"Fix": "Fix",
|
||||
"Fix Video": "Corriger La Vidéo",
|
||||
"FixVideoMsg": "Voulez-vous corriger cette vidéo? Vous ne pouvez pas annuler cette action.",
|
||||
"Flush PM2 Logs": "Rincer les PM2 Journaux",
|
||||
"Flush PM2 Logs": "Nettoyer les Journaux PM2",
|
||||
"Font Path": "Le Chemin De Police",
|
||||
"Font Size": "Taille De La Police",
|
||||
"For Group": "Pour Le Groupe",
|
||||
|
@ -242,20 +242,20 @@
|
|||
"Gender": "Genre",
|
||||
"Generate Subtitles": "Générer Des Sous-Titres",
|
||||
"Get Logs to Client": "Obtenir les Journaux du Client",
|
||||
"Greater Than": "Plus De",
|
||||
"Greater Than": "Plus grand que",
|
||||
"Greater Than or Equal to": "Supérieure ou Égale à",
|
||||
"Group Key": "La Clé De Groupe",
|
||||
"Group Key": "Clé de groupe",
|
||||
"Group Name": "Nom Du Groupe",
|
||||
"Grouping": "Groupement <small>d'Ajouter des groupes de <b>Paramètres</b></small>",
|
||||
"H.264 / H.265 / H.265+": "H. 264 / H. 265 / H. 265 ",
|
||||
"HLS (.m3u8)": "HLS (.m3u8)",
|
||||
"HLS (includes Audio)": "HLS (Audio)",
|
||||
"HLS (includes Audio)": "HLS (inclut l'audio)",
|
||||
"HLS Audio Encoder": "Encodeur Audio",
|
||||
"HLS List Size": "La Taille De La Liste",
|
||||
"HLS List Size": "Taille de la liste HLS",
|
||||
"HLS Live Start Index": "HLS Vivre Index de Début",
|
||||
"HLS Preset": "Modèle Préréglé",
|
||||
"HLS Segment Length": "La Longueur du Segment <small>en quelques Secondes</small>",
|
||||
"HLS Start Number": "HLS Nombre de Départ",
|
||||
"HLS Start Number": "Numéro de départ HLS",
|
||||
"HLS Video Encoder": "Encodeur Vidéo",
|
||||
"HTTP": "HTTP",
|
||||
"HTTPS": "HTTPS",
|
||||
|
@ -265,47 +265,47 @@
|
|||
"Hide List": "Masquer La Liste",
|
||||
"Hide Notes": "Masquer Les Notes",
|
||||
"Host": "Accueil",
|
||||
"Hotswap Modes (Watch-Only)": "Hotswap Modes (Watch)",
|
||||
"How to Record": "Comment faire pour Enregistrer",
|
||||
"Hotswap Modes (Watch-Only)": "Modes Hotswap (Regarder Uniquement)",
|
||||
"How to Record": "Comment enregistrer",
|
||||
"IP Address": "Adresse IP",
|
||||
"Identity": "Identité",
|
||||
"IdentityText1": "C'est la façon dont le système permettra d'identifier les données pour ce flux. Vous ne pouvez pas modifier le <b>Moniteur ID</b> une fois que vous avez appuyé sur la touche enregistrer. Si vous le souhaitez, vous pouvez faire le <b>Moniteur ID</b> plus lisible par l'homme avant de continuer.",
|
||||
"IdentityText2": "Vous pouvez dupliquer l'écran en modifiant le <b>Moniteur ID</b> , puis appuyez sur save. Vous <b>ne peut pas</b> utiliser l'ID d'un moniteur qui existe déjà ou qu'il permettra d'économiser plus que surveiller les informations de base de données.",
|
||||
"IdentityText1": "Voici comment le système identifiera les données pour ce flux. Vous ne pouvez pas changer <b> l'identifiant du moniteur </ b> une fois que vous avez appuyé sur Enregistrer. Si vous le souhaitez, vous pouvez rendre <b> l'identifiant du moniteur </ b> plus lisible par l'homme avant de continuer.",
|
||||
"IdentityText2": "Vous pouvez dupliquer l'écran en modifiant <b>l'ID du moniteur </b> , puis appuyez sur save. Vous <b>ne pouvez pas</b> utiliser l'ID d'un moniteur existant, sinon les informations de la base de données de ce moniteur seront sauvegardées.",
|
||||
"Idle": "Inactif",
|
||||
"Image Height": "La Hauteur De L'Image",
|
||||
"Image Location": "Emplacement de l'Image <small>de Chemin d'accès Absolu ou laissez le champ vide pour utiliser mondiale</small>",
|
||||
"Image Location": "Emplacement de l'Image <small>Chemin absolu ou laisser vide pour utiliser global</small>",
|
||||
"Image Position": "La Position De L'Image",
|
||||
"Image Width": "La Largeur De L'Image",
|
||||
"Import": "L'importation",
|
||||
"Import Monitor Configuration": "L'Importation De Configuration Du Moniteur De",
|
||||
"ImportMonitorConfigurationText": "En faisant cela, vous overrwrite toutes les modifications ne sont pas enregistrées. Les modifications importées ne sera appliquée que lorsque vous appuyez sur <b>Enregistrer</b>.",
|
||||
"ImportMultiMonitorConfigurationText": "En faisant cela, vous overrwrite tous les moniteurs avec des Id existant dans le fichier d'importation.",
|
||||
"Import Monitor Configuration": "Importer la configuration du moniteur",
|
||||
"ImportMonitorConfigurationText": "Cela écrasera toutes les modifications actuellement non enregistrées. Les modifications importées ne seront appliquées que si vous appuyez sur <b> Enregistrer </ b>.",
|
||||
"ImportMultiMonitorConfigurationText": "Cela écrasera tous les moniteurs dont l'ID existe dans le fichier d'importation.",
|
||||
"In": "Dans",
|
||||
"Incorrect Settings Chosen": "Des Paramètres Incorrects Choisi",
|
||||
"Indifference": "L'indifférence",
|
||||
"Incorrect Settings Chosen": "Réglages incorrects choisis",
|
||||
"Indifference": "Indifférence",
|
||||
"Input": "Entrée",
|
||||
"Input Feed": "Entrée D'Alimentation",
|
||||
"Input Flags": "Entrée Des Drapeaux",
|
||||
"Input Feed": "Flux d'entrée",
|
||||
"Input Flags": "Drapeaux d'entrée",
|
||||
"Input Selector": "Sélecteur D'Entrée",
|
||||
"Input Settings": "Les Paramètres D'Entrée",
|
||||
"Input Settings": "Paramètres D'Entrée",
|
||||
"Input Type": "Type D'Entrée",
|
||||
"InputText1": "Cette section indique Shinobi comment utiliser un flux. Pour des performances optimales, essayez de régler votre appareil photo les réglages internes. Trouvez les options suivantes et de les définir comme illustré. Pour trouver votre appareil photo vous pouvez utiliser le <b>construit en ONVIF Scanner</b> de Shinobi. Certaines caméras ONVIF nécessitent l'utilisation d'un outil de gestion pour modifier leurs paramètres internes. Si vous ne trouvez pas votre appareil, vous pouvez essayer <a href=\"https://s3.amazonaws.com/cloudcamio/odm-v2.2.250.msi\">ONVIF le Gestionnaire de Périphériques de Windows</a>.",
|
||||
"InputText2": "<ul><li><b>Framerate (FPS) :</b> Haut : 10 - 15 FPS, Faible : 2 à 5 FPS</li><li><b>I-frame interval :</b> 80</li><li><b>Taux de bits Type :</b> CBR (Constant Bit rate)</li><li><b>Débit binaire :</b> entre 256kbps - 500kbps</li></ul>",
|
||||
"InputText3": "Si vous avez besoin d'aide pour déterminer ce type d'entrée de votre appareil photo vous pouvez prendre un coup d'oeil à la <a href=\"http://shinobi.video/docs/cameras\" target=\"_blank\">Caméra Liste d'URLs</a> sur le Shinobi site web.",
|
||||
"Invalid JSON": "Invalid JSON",
|
||||
"InvalidJSONText": "Veuillez vous assurer que c'est une chaîne JSON valide pour les Shinobi de configuration du moniteur.",
|
||||
"InputText1": "Cette section explique à Shinobi comment utiliser un flux. Pour des performances optimales, essayez de régler les paramètres internes de votre appareil photo. Recherchez les options suivantes et configurez-les comme indiqué. Pour trouver votre appareil photo, vous pouvez utiliser le <b> scanner ONVIF intégré </ b> de Shinobi. Certaines caméras ONVIF nécessitent l'utilisation d'un outil de gestion pour modifier leurs paramètres internes. Si vous ne trouvez pas vos caméras, vous pouvez utiliser le <a href=\"https://s3.amazonaws.com/cloudcamio/odm-v2.2.250.msi\"> gestionnaire de périphériques ONVIF pour Windows </a>.",
|
||||
"InputText2": "<ul><li><b>Fréquence d'image (FPS) :</b> Haut : 10 - 15 FPS, Faible : 2 à 5 FPS</li><li><b>Intervalle I-frame :</b> 80</li><li><b>Type de débit :</b> CBR (débit binaire constant)</li><li><b>Débit binaire :</b> entre 256kbps - 500kbps</li></ul>",
|
||||
"InputText3": "Si vous avez besoin d'aide pour déterminer le type d'entrée de votre caméra, consultez la <a href=\"http://shinobi.video/docs/cameras\" target=\"_blank\">Liste des URL de la caméra s</a> le site Web de Shinobi.",
|
||||
"Invalid JSON": "JSON invalide",
|
||||
"InvalidJSONText": "Assurez-vous qu'il s'agit d'une chaîne JSON valide pour la configuration du moniteur Shinobi.",
|
||||
"JPEG": "JPEG",
|
||||
"JPEG (Auto Enables JPEG API)": "JPEG (Auto Permet JPEG API)",
|
||||
"JPEG (Auto Enables JPEG API)": "JPEG (Activation automatique de l'API JPEG)",
|
||||
"JPEG API": "JPEG API <small>Instantané (cgi-bin)</small>",
|
||||
"JPEG Error": "JPEG Erreur",
|
||||
"JPEG Mode": "Le Mode JPEG",
|
||||
"JPEGErrorText": "Il y avait un problème dans l'obtention de données de votre appareil photo.",
|
||||
"JPEG Error": "Erreur JPEG",
|
||||
"JPEG Mode": "Mode JPEG",
|
||||
"JPEGErrorText": "Il y avait un problème dans l'obtention de données de votre camera.",
|
||||
"LDAP": "LDAP",
|
||||
"LDAP Success": "LDAP Succès",
|
||||
"LDAP User Authenticated": "LDAP de l'Utilisateur Authentifié",
|
||||
"LDAP User Authenticated": "Utilisateur LDAP authentifié",
|
||||
"LDAP User is New": "Utilisateur LDAP est Nouveau",
|
||||
"Leave blank for random.": "Laissez vide pour aléatoire.",
|
||||
"Leave blank for unlimited": "Laissez vide pour un nombre illimité de",
|
||||
"Leave blank for unlimited": "Laissez vide pour illimité",
|
||||
"Left": "Gauche <small>Adresse URL</small>",
|
||||
"Left Stop": "Butée gauche, <small>Adresse URL</small>",
|
||||
"Less Than": "Moins De",
|
||||
|
@ -313,14 +313,14 @@
|
|||
"Like": "Comme",
|
||||
"Limited": "Limitée",
|
||||
"Link Shinobi": "Lien Shinobi",
|
||||
"Lisence Plate Detector": "Lisence Détecteur De Plaque",
|
||||
"Lisence Plate Detector": "Détecteur de plaques Lisence",
|
||||
"List Toggle": "Liste De Bascule",
|
||||
"Live Stream Toggle": "Live Stream De La Bascule",
|
||||
"Live Stream Toggle": "Flux en direct bascule",
|
||||
"Live View": "Vue En Direct",
|
||||
"Local": "Local",
|
||||
"Log Level": "Le Niveau De Journal",
|
||||
"Log Signal Event": "Journal du Signal d'Événements <small>côté Client seulement</small>",
|
||||
"Logging": "La journalisation",
|
||||
"Log Level": "Niveau de journalisation",
|
||||
"Log Signal Event": "Consigner un événement de signal <small>Côté client uniquement</small>",
|
||||
"Logging": "Journalisation",
|
||||
"Login": "Connexion",
|
||||
"Logout": "Déconnexion",
|
||||
"Logs": "Les journaux",
|
||||
|
@ -336,24 +336,24 @@
|
|||
"Manual": "Manuel",
|
||||
"Map": "Carte",
|
||||
"Matches": "Matchs",
|
||||
"Max Latency": "Max Latence",
|
||||
"Max Number of Cameras": "Le Nombre maximum de Caméras",
|
||||
"Max Storage Amount": "Max de la Quantité de Stockage <small>en mo</small>",
|
||||
"Max Latency": "Latence maximale",
|
||||
"Max Number of Cameras": "Nombre maximum de caméras",
|
||||
"Max Storage Amount": "Quantité de stockage maximum <small>en mo</small>",
|
||||
"Mode": "Mode",
|
||||
"Monitor": "Moniteur",
|
||||
"Monitor Added by user": "Moniteur Ajoutés par l'utilisateur.",
|
||||
"Monitor Capture Rate": "Surveiller les Taux de Capture <small>(FPS)</small>",
|
||||
"Monitor Groups": "La Surveillance De Groupes",
|
||||
"Monitor Groups": "Groupes de surveillance",
|
||||
"Monitor ID": "Moniteur ID",
|
||||
"Monitor Idling": "Surveiller La Marche Au Ralenti",
|
||||
"Monitor Name": "Nom De Moniteur",
|
||||
"Monitor Settings": "Paramètres Du Moniteur",
|
||||
"Monitor Stopped": "Moniteur Arrêté",
|
||||
"Monitor Updated by user": "Surveiller les mises à Jour par l'utilisateur.",
|
||||
"Monitor mode changed": "Le mode moniteur changé",
|
||||
"Monitor Updated by user": "Moniteur mis à jour par l'utilisateur.",
|
||||
"Monitor mode changed": "Le mode moniteur a changé",
|
||||
"Monitor mode is already": "Le mode Monitor est déjà",
|
||||
"Monitor or Key does not exist.": "Le moniteur ou la Clé n'existe pas.",
|
||||
"MonitorIdlingText": "Surveiller la session a été commandé au ralenti.",
|
||||
"MonitorIdlingText": "La session du moniteur a été commandée pour être inactive.",
|
||||
"MonitorStoppedText": "Surveiller la session a été ordonné d'arrêter.",
|
||||
"Monitors": "Les moniteurs",
|
||||
"Monitors per row": "Surveille par ligne <small>de Montage</small>",
|
||||
|
@ -364,41 +364,41 @@
|
|||
"Name": "Nom",
|
||||
"New Authentication Token": "Nouveau Jeton D'Authentification",
|
||||
"New Monitor": "Nouveau Moniteur",
|
||||
"No": "Pas de",
|
||||
"No": "Non",
|
||||
"No Audio": "Pas D'Audio",
|
||||
"No Data": "Pas De Données",
|
||||
"No Events found for this video": "Aucun événement trouvé pour cette vidéo",
|
||||
"No Group with this key exists": "Pas de Groupe avec cette clé existe",
|
||||
"No Monitor Exists with this ID.": "Pas de Moniteur Existe à cet ID.",
|
||||
"No Monitor Found, Ignoring Request": "Pas De Moniteur Trouvé, En Ignorant La Demande",
|
||||
"No Monitors Selected": "Pas De Moniteurs Sélectionnés",
|
||||
"No Group with this key exists": "Aucun groupe avec cette clé n'existe",
|
||||
"No Monitor Exists with this ID.": "Aucun moniteur n'existe avec cet ID.",
|
||||
"No Monitor Found, Ignoring Request": "Aucun moniteur trouvé, demande ignorée",
|
||||
"No Monitors Selected": "Aucun moniteur sélectionné",
|
||||
"No Rotation": "Pas De Rotation",
|
||||
"No Videos Found": "Pas De Vidéos Trouvées",
|
||||
"No such file": "Pas de tel fichier",
|
||||
"No such file": "Ce fichier n'existe pas",
|
||||
"NoMotionEmailText1": "Pas de Mouvement pour",
|
||||
"NoMotionEmailText2": "Il n'y a pas été de tout mouvement détecté sur l'appareil photo pour",
|
||||
"NoVideosFoundForDateRange": "Pas de Vidéos trouvées dans cette plage de dates. Essayez de définir la date de début de la plus en arrière.",
|
||||
"NoMotionEmailText2": "Aucun mouvement n'a été détecté sur la caméra depuis",
|
||||
"NoVideosFoundForDateRange": "Aucune vidéo trouvée dans cette plage de dates. Essayez de définir la date de début plus en arrière.",
|
||||
"Noise Filter": "Filtre De Bruit",
|
||||
"Not Authorized": "Pas Autorisé",
|
||||
"Not Connected": "Pas Connecté",
|
||||
"Not Equal to": "Pas Égal à",
|
||||
"Not In": "Pas Dans",
|
||||
"Not Matches": "Pas De Matchs",
|
||||
"Not Matches": "Pas de correspondance",
|
||||
"Not Permitted": "Pas Autorisés",
|
||||
"Not Saved": "Pas Enregistrées",
|
||||
"Not Saved": "Non enregistré",
|
||||
"Not an Administrator Account": "Pas un Compte d'Administrateur",
|
||||
"NotAuthorizedText1": "Pas Autorisé, Soumettre commande init avec \"auth\",\"ke\", et \"uid\"",
|
||||
"NotAuthorizedText1": "Non autorisé, soumettre la commande init avec \"auth\",\"ke\", et \"uid\"",
|
||||
"Notes": "Notes",
|
||||
"NotesPlacholder": "Les commentaires que vous voulez quitter pour ce caméras de paramètres.",
|
||||
"NotesPlacholder": "Commentaires que vous souhaitez laisser pour les paramètres de cette caméra.",
|
||||
"Number of Days to keep": "Nombre de Jours de conservation",
|
||||
"ONVIF": "ONVIF",
|
||||
"ONVIF Scanner": "ONVIF Scanner",
|
||||
"ONVIFnote": "Découvrez ONVIF appareils sur les réseaux à l'extérieur de votre propre ou la laisser vide pour analyser votre réseau actuel. <br>Nom d'utilisateur et le Mot de passe peut être laissé en blanc.",
|
||||
"ONVIFnote": "Découvrez les périphériques ONVIF sur des réseaux extérieurs aux vôtres ou laissez-le vide pour analyser votre réseau actuel. <br> Le Nom d'utilisateur et le Mot de passe peut être laissé vide.",
|
||||
"Object": "Objet",
|
||||
"Object Tag": "Balise Object",
|
||||
"OpenCV Cascades": "OpenCV Cascades",
|
||||
"Options": "Options",
|
||||
"Order Streams": "Afin De Ruisseaux",
|
||||
"Order Streams": "Ordre des flux",
|
||||
"Output Method": "Méthode De Sortie",
|
||||
"Password": "Mot de passe",
|
||||
"Password Again": "Mot De Passe À Nouveau",
|
||||
|
@ -407,7 +407,7 @@
|
|||
"Path": "Chemin",
|
||||
"Permissions": "Les autorisations",
|
||||
"Please Wait for Completion": "Veuillez Attendre la fin, Selon le nombre de fichiers sélectionnés, cela peut prendre un certain temps. Actualiser pour vérifier de nouveau.",
|
||||
"Points": "Des Points <small>Lors de l'ajout de points de cliquer sur le bord du polygone.</small>",
|
||||
"Points": "Points <small>Lorsque vous ajoutez des points, cliquez sur le bord du polygone.</small>",
|
||||
"Pop": "Pop",
|
||||
"Port": "Port",
|
||||
"Pose": "Poser",
|
||||
|
@ -419,38 +419,38 @@
|
|||
"Preferences": "Préférences",
|
||||
"Preset": "Preset",
|
||||
"Preview": "Aperçu",
|
||||
"Probe Size": "Longueur De La Sonde",
|
||||
"Probe Size": "Taille de la Sonde",
|
||||
"Process Crashed for Monitor": "Le processus s'est Écrasé pour Surveiller",
|
||||
"Process Unexpected Exit": "Processus Inattendu De Sortie",
|
||||
"Profile": "Profil",
|
||||
"Quality": "La qualité de l' <small>1 est Élevée, 23 est Faible</small>",
|
||||
"Quality": "Qualité <small>1 est Élevée, 23 est Faible</small>",
|
||||
"Query": "Requête",
|
||||
"RAM": "RAM",
|
||||
"RTMP Stream": "Flux RTMP",
|
||||
"RTMP Stream Flags": "Flux RTMP Drapeaux",
|
||||
"RTSP": "RTSP",
|
||||
"RTSP Transport": "RTSP de Transport",
|
||||
"RTSP Transport": "Transport RTSP",
|
||||
"Range or Single": "Gamme ou Seul",
|
||||
"Rate": "Taux <small>(FPS)</small>",
|
||||
"Raw": "Raw",
|
||||
"Raw H.264 Stream": "Raw H. 264 Flux",
|
||||
"Recommended": "Recommandé",
|
||||
"Record": "Enregistrement",
|
||||
"Record File Type": "Enregistrement De Type De Fichier",
|
||||
"Record Height": "Record De Hauteur",
|
||||
"Record Video Filter": "Enregistrement Vidéo De Filtre",
|
||||
"Record Width": "Enregistrement De Largeur",
|
||||
"Record File Type": "Type de fichier d'enregistrement",
|
||||
"Record Height": "Enregistrer Hauteur",
|
||||
"Record Video Filter": "Enregistrer le filtre vidéo",
|
||||
"Record Width": "Enregistrer Largeur",
|
||||
"Recording": "Enregistrement",
|
||||
"Recording FPS": "L'enregistrement de FPS",
|
||||
"Recording FPS Change on Start": "Enregistrement FPS Changement sur Démarrer",
|
||||
"Recording Flags": "L'Enregistrement Des Drapeaux",
|
||||
"Recording Segment Interval": "Enregistrement Segment de l'Intervalle <small>en minutes</small>",
|
||||
"Recording Timeout": "L'enregistrement de Délai d'attente <small>en Minutes</small>",
|
||||
"Recording Timestamp": "L'Enregistrement D'Horodatage",
|
||||
"Recording Watermark": "Enregistrement Filigrane",
|
||||
"RecordingText": "Il est recommandé que vous définissez <b>Enregistrement de Type de Fichier</b> pour <b class=\"h_t_input h_t_jpeg h_t_socket\">WebM</b><b class=\"h_t_input h_t_mjpeg h_t_h264 h_t_hls h_t_mp4 h_t_local\">MP4</b> et <b>Vidéo Codec</b> pour <b class=\"h_t_input h_t_jpeg h_t_socket\">libvpx</b><b class=\"h_t_input h_t_h264 h_t_hls h_t_mp4\">copie ou </b><b class=\"h_t_input h_t_mjpeg h_t_h264 h_t_hls h_t_mp4 h_t_local\">libx264</b> parce que votre <b>Type d'Entrée</b> est définie sur <b class=\"h_t_text\"></b>.",
|
||||
"Recording FPS": "L'enregistrement FPS",
|
||||
"Recording FPS Change on Start": "Enregistrer le changement de FPS au démarrage",
|
||||
"Recording Flags": "Drapeaux d'enregistrement",
|
||||
"Recording Segment Interval": "Enregistrement de l'intervalle de segment <small>en minutes</small>",
|
||||
"Recording Timeout": "Délai d'enregistrement <small>en Minutes</small>",
|
||||
"Recording Timestamp": "Enregistrement de l'horodatage",
|
||||
"Recording Watermark": "Filigrane d'enregistrement",
|
||||
"RecordingText": "Il est recommandé de définir <b>Type de fichier d'enregistrement</b> sur <b class=\"h_t_input h_t_jpeg h_t_socket\">WebM</b><b class=\"h_t_input h_t_mjpeg h_t_h264 h_t_hls h_t_mp4 h_t_local\">MP4</b> et <b>Vidéo Codec</b> pour <b class=\"h_t_input h_t_jpeg h_t_socket\">libvpx</b><b class=\"h_t_input h_t_h264 h_t_hls h_t_mp4\">ou copie </b><b class=\"h_t_input h_t_mjpeg h_t_h264 h_t_hls h_t_mp4 h_t_local\">libx264</b> parce que votre <b>Type d'Entrée</b> est définie sur <b class=\"h_t_text\"></b>.",
|
||||
"Refresh List of Cascades": "Actualiser la Liste des Cascades",
|
||||
"Region Editor": "Région De L'Éditeur",
|
||||
"Region Editor": "Éditeur de région",
|
||||
"Region Name": "Nom De La Région",
|
||||
"RegionNote": "Les Points ne sont enregistrées que lorsque vous appuyez sur <b>Enregistrer</b> sur les <b>Paramètres du Moniteur</b> de la fenêtre.",
|
||||
"Regions": "Les régions",
|
||||
|
@ -458,14 +458,14 @@
|
|||
"Request": "Demande",
|
||||
"Reset Timer": "Réinitialiser La Minuterie",
|
||||
"Restart": "Redémarrer",
|
||||
"Restart CRON": "Redémarrage du CRON",
|
||||
"Restart Core": "Le Redémarrage De Base",
|
||||
"Restarting Process": "Le Redémarrage Du Processus De",
|
||||
"Retry Connection": "Nouvelle tentative de Connexion <small>Nombre de fois que le droit à l'échec</small>",
|
||||
"Restart CRON": "Redémarrer CRON",
|
||||
"Restart Core": "Redémarrer le noyau",
|
||||
"Restarting Process": "Processus de redémarrage",
|
||||
"Retry Connection": "Nouvelle tentative de Connexion <small>Nombre d'échecs autorisés</small>",
|
||||
"Retrying...": "Réessayer...",
|
||||
"Right": "Droit de l' <small>Adresse URL</small>",
|
||||
"Right Stop": "Droit d'Arrêter <small>Adresse URL</small>",
|
||||
"Rotate": "Faites tourner",
|
||||
"Rotate": "Tourner",
|
||||
"Save": "Enregistrer",
|
||||
"Save Directory": "Répertoire De Sauvegarde",
|
||||
"Save Events to SQL": "Enregistrer les Événements dans le SQL",
|
||||
|
@ -481,20 +481,20 @@
|
|||
"Send Frames": "Envoyer des Images <small>Pousser images à analyser</small>",
|
||||
"Separate with commas, no spaces": "Séparés par des virgules, sans espace",
|
||||
"Server URL": "URL du serveur",
|
||||
"Set to Watch Only": "Mis à Regarder Seulement",
|
||||
"Set to Watch Only": "Définir pour regarder uniquement",
|
||||
"Settings": "Paramètres",
|
||||
"Settings Changed": "La Modification Des Réglages",
|
||||
"Settings Changed": "Paramètres modifiés",
|
||||
"SettingsChangedText": "Vos paramètres sont enregistrés et appliqués. Certains paramètres peuvent nécessiter une actualisation de cette page.",
|
||||
"Shinobi": "Shinobi",
|
||||
"Shinobi Streamer": "Shinobi Streamer",
|
||||
"Show Logs": "Afficher Les Journaux D'",
|
||||
"Show Regions of Interest": "Montrer les Régions d'Intérêt",
|
||||
"Show Logs": "Afficher Les Journaux",
|
||||
"Show Regions of Interest": "Afficher les Régions d'Intérêt",
|
||||
"Show Stream HUD": "Afficher les Flux de PALETTE",
|
||||
"Silent": "Silencieux",
|
||||
"Simple": "Simple",
|
||||
"Size (mb)": "Taille (mo)",
|
||||
"Snapshot": "Instantané",
|
||||
"Snapshot Flags": "Instantané De Drapeaux",
|
||||
"Snapshot Flags": "Drapeaux Instantanés",
|
||||
"Snapshots": "Instantanés",
|
||||
"Sort By": "Trier Par",
|
||||
"Start": "Démarrer",
|
||||
|
@ -508,12 +508,12 @@
|
|||
"Stream": "Flux",
|
||||
"Stream Channel": "Canal De Flux De Données",
|
||||
"Stream Flags": "Flux De Drapeaux",
|
||||
"Stream Key": "Flux De Clé",
|
||||
"Stream Timestamp": "Flux D'Horodatage",
|
||||
"Stream Key": "Clé de Flux",
|
||||
"Stream Timestamp": "Horodatage du Flux",
|
||||
"Stream Type": "Type De Flux",
|
||||
"Stream Watermark": "Flux De Filigrane",
|
||||
"Stream to YouTube": "Stream sur YouTube",
|
||||
"Stream to YouTube Flags": "Stream sur YouTube Drapeaux",
|
||||
"Stream to YouTube Flags": "Diffuser vers des drapeaux YouTube",
|
||||
"StreamText": "<p>Cette section permettra de désigner le flux primaire, de méthode et de ses paramètres. Ce flux sera affiché dans le tableau de bord. Si vous choisissez d'utiliser HLS, JPEG, MJPEG, alors vous pouvez consommer du courant à travers d'autres programmes.</p><p class=\"h_st_input h_st_jpeg\">JPEG flux essentiellement désactive le principal cours d'eau et les utilisations de l'instantané bin pour obtenir des images.</p>",
|
||||
"Streamer": "Streamer",
|
||||
"Streams": "Flux",
|
||||
|
@ -522,49 +522,49 @@
|
|||
"Switch on for Still Image": "Interrupteur pour Toujours à l'Image",
|
||||
"System": "Système",
|
||||
"TCP": "TCP",
|
||||
"TV Channel": "Chaîne de TÉLÉVISION",
|
||||
"TV Channel Group": "Chaîne de TÉLÉVISION du Groupe",
|
||||
"TV Channel ID": "Chaîne de TÉLÉVISION ID",
|
||||
"Text Box Color": "Texte De La Boîte De Couleur",
|
||||
"Text Color": "La Couleur Du Texte",
|
||||
"TV Channel": "Chaîne TV",
|
||||
"TV Channel Group": "Groupe de chaînes TV",
|
||||
"TV Channel ID": "Identifiant de chaîne TV",
|
||||
"Text Box Color": "Couleur de la Zone de Texte",
|
||||
"Text Color": "Couleur du Texte",
|
||||
"Themes": "Thèmes",
|
||||
"There are no monitors that you can view with this account.": "Il n'y a pas les moniteurs que vous pouvez afficher avec ce compte.",
|
||||
"Time-lapse": "Time-lapse",
|
||||
"Time-lapse Tool": "Time-lapse de l'Outil",
|
||||
"Timeout": "Timeout",
|
||||
"Timeout Reset on Next Motion": "Délai de Réinitialisation sur le Prochain Mouvement",
|
||||
"Time-lapse Tool": "Outil Time-lapse",
|
||||
"Timeout": "Délai d'attente",
|
||||
"Timeout Reset on Next Motion": "Délai d'attente réinitialisé à au prochain mouvement",
|
||||
"Toggle Sidebar": "Basculer La Barre Latérale",
|
||||
"Top Left": "En Haut À Gauche",
|
||||
"Top Right": "En Haut À Droite",
|
||||
"Traditional (Watch-Only, Includes Buffer)": "Traditionnel (Watch-Seulement, Comprend Tampon)",
|
||||
"Traditional Recording": "Traditionnelle Enregistrement",
|
||||
"Trigger Record": "Déclencheur D'Enregistrement",
|
||||
"Trigger Successful": "Déclencheur De Succès",
|
||||
"Traditional (Watch-Only, Includes Buffer)": "Traditionnel (Regarder uniquement, tampon inclus)",
|
||||
"Traditional Recording": "Enregistrement Traditionnel",
|
||||
"Trigger Record": "Enregistrement de Déclenchement",
|
||||
"Trigger Successful": "Déclencheur Réussi",
|
||||
"UDP": "UDP",
|
||||
"URL": "URL",
|
||||
"URL Stop Timeout": "URL Arrêter Délai <small>Run stop URL après X millisecondes</small>",
|
||||
"URL Stop Timeout": "URL Arrêter le délai d'attente <small>Run stop URL après X millisecondes</small>",
|
||||
"US": "NOUS",
|
||||
"Unable to Launch": "Impossible de Lancer",
|
||||
"UnabletoLaunchText": "Veuillez enregistrer le nouveau moniteur en premier. Tentez ensuite de lancer la région de l'éditeur.",
|
||||
"UnabletoLaunchText": "Enregistrez d'abord le nouveau moniteur. Essayez ensuite de lancer l'éditeur de région.",
|
||||
"Uniform": "Uniforme",
|
||||
"Up": "Jusqu' <small>Adresse URL</small>",
|
||||
"Up Stop": "D'Arrêter <small>Adresse URL</small>",
|
||||
"Update": "Mise à jour",
|
||||
"Update to Development": "Mise à jour pour le Développement",
|
||||
"Update to Master": "Mise à jour de Maître",
|
||||
"Use Built-In": "Utilisez La Fonction De",
|
||||
"Update to Master": "Mise à jour du Maître",
|
||||
"Use Built-In": "Utilisez La Fonction intégré",
|
||||
"Username": "Nom d'utilisateur",
|
||||
"Value": "Valeur",
|
||||
"Video": "Vidéo",
|
||||
"Video Bit Rate": "Le Débit Binaire Vidéo",
|
||||
"Video Bit Rate": "Bitrate vidéo",
|
||||
"Video Codec": "Codec Vidéo",
|
||||
"Video Filter": "Filtre Vidéo",
|
||||
"Video Finished": "Vidéo Fini",
|
||||
"Video Length (minutes) and Motion Count per video": "Durée de la vidéo (en minutes) et le Mouvement Comte par vidéo",
|
||||
"Video Limit": "Vidéo Limite",
|
||||
"Video Record Rate": "Vidéo vitesse d'Enregistrement <small>(IPS)</small>",
|
||||
"Video Finished": "Vidéo Terminée",
|
||||
"Video Length (minutes) and Motion Count per video": "Durée de la vidéo (minutes) et nombre de mouvements par vidéo",
|
||||
"Video Limit": "Limite Vidéo",
|
||||
"Video Record Rate": "Taux d'enregistrement vidéo <small>(IPS)</small>",
|
||||
"Video Status": "État De La Vidéo",
|
||||
"Video and Time Span (Minutes)": "La vidéo et la Durée (en Minutes)",
|
||||
"Video and Time Span (Minutes)": "Vidéo et Durée (minutes)",
|
||||
"Videos": "Vidéos",
|
||||
"Videos List": "Liste Des Vidéos",
|
||||
"Warning": "Avertissement",
|
||||
|
@ -573,7 +573,7 @@
|
|||
"WebDAV": "WebDAV",
|
||||
"WebM (libvpx)": "WebM (libvpx)",
|
||||
"Webdav Error": "Webdav Erreur",
|
||||
"WebdavErrorText": "Impossible d'enregistrer. Avez-vous rendre l'appareil dossiers à l'intérieur de votre choisi répertoire de sauvegarde?",
|
||||
"WebdavErrorText": "Impossible de sauvegarder. Essayer de créer un répertoire dans votre dossier de sauvegarde",
|
||||
"Webhook": "Webhook",
|
||||
"Webhook URL": "Webhook URL",
|
||||
"Websocket": "Websocket",
|
||||
|
@ -581,10 +581,10 @@
|
|||
"Websocket Disconnected": "Websocket Déconnecté",
|
||||
"Width": "Largeur",
|
||||
"Yes": "Oui",
|
||||
"Zoom In": "Zoom Dans l' <small>Adresse URL</small>",
|
||||
"Zoom In Stop": "Zoom Arrêter <small>Adresse URL</small>",
|
||||
"Zoom Out": "Zoom <small>Adresse URL</small>",
|
||||
"Zoom Out Stop": "Zoom Arrêter <small>Adresse URL</small>",
|
||||
"Zoom In": "Agrandir <small>Adresse URL</small>",
|
||||
"Zoom In Stop": "Agrandir Arrêter <small>Adresse URL</small>",
|
||||
"Zoom Out": "Dézoomer <small>Adresse URL</small>",
|
||||
"Zoom Out Stop": "Dézoomer Arrêter <small>Adresse URL</small>",
|
||||
"a day": "un jour",
|
||||
"a few seconds": "quelques secondes",
|
||||
"a minute": "une minute",
|
||||
|
@ -639,7 +639,7 @@
|
|||
"monitorEditFailedMaxReached": "Votre compte a atteint le nombre maximum de caméras qui peuvent être créés. Parler à un administrateur si vous souhaitez que cela a changé.",
|
||||
"monitorEditText1": "Des Données non valides, Vérifiez la validité de l'importation de la chaîne.",
|
||||
"monitorEditText2": "Non Valide Les Détails De La Chaîne. Vérifiez pour voir c'est une chaîne JSON et non pas un objet normal d'être passé.",
|
||||
"monitorGetText1": "demande incomplète, supprimer le dernier slash dans l'URL ou mettre valeur acceptable.",
|
||||
"monitorGetText1": "demande incomplète, supprimer la dernière barre oblique de l'URL ou saisir une valeur acceptable",
|
||||
"months": "mois",
|
||||
"mpeg2_qsv": "MPEG2 (Quick Sync Video)",
|
||||
"mpeg4_cuvid": "MPEG4 CUVID",
|
||||
|
@ -666,5 +666,5 @@
|
|||
"vp8_cuvid": "VP8 NVENC (NVIDIA HW Accel)",
|
||||
"vp8_qsv": "VP8 (Quick Sync Video)",
|
||||
"vp9_cuvid": "VP9 NVENC (NVIDIA HW Accel)",
|
||||
"years": "ans"
|
||||
"years": "années"
|
||||
}
|
||||
|
|
1
libs/.gitignore
vendored
Normal file
1
libs/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
customAutoLoad
|
13
libs/auth.js
13
libs/auth.js
|
@ -35,6 +35,10 @@ module.exports = function(s,config,lang){
|
|||
//check IP address of connecting user
|
||||
var finish=function(user){
|
||||
if(s.api[params.auth].ip.indexOf('0.0.0.0')>-1||s.api[params.auth].ip.indexOf(params.ip)>-1){
|
||||
if(!user.lang){
|
||||
var details = s.parseJSON(user.details).lang
|
||||
user.lang = s.getDefinitonFile(user.details.lang) || s.copySystemDefaultLanguage()
|
||||
}
|
||||
cb(user);
|
||||
}else{
|
||||
failed();
|
||||
|
@ -43,7 +47,10 @@ module.exports = function(s,config,lang){
|
|||
//check if auth key is user's temporary session key
|
||||
if(s.group[params.ke]&&s.group[params.ke].users&&s.group[params.ke].users[params.auth]){
|
||||
s.group[params.ke].users[params.auth].permissions={};
|
||||
cb(s.group[params.ke].users[params.auth]);
|
||||
if(!s.group[params.ke].users[params.auth].lang){
|
||||
s.group[params.ke].users[params.auth].lang = s.copySystemDefaultLanguage()
|
||||
}
|
||||
cb(s.group[params.ke].users[params.auth])
|
||||
}else{
|
||||
//check if key is already in memory to save query time
|
||||
if(s.api[params.auth]&&s.api[params.auth].details){
|
||||
|
@ -185,6 +192,10 @@ module.exports = function(s,config,lang){
|
|||
if(userFound === true){
|
||||
return true
|
||||
}else{
|
||||
if(res)res.end(s.prettyPrint({
|
||||
ok: false,
|
||||
msg: lang['Not Authorized']
|
||||
}))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ module.exports = function(s,config){
|
|||
if(s.isWin===true){
|
||||
cmd = "Taskkill /IM ffmpeg.exe /F"
|
||||
}else{
|
||||
cmd = "ps aux | grep -ie ffmpeg | awk '{print $2}' | xargs kill -9"
|
||||
cmd = "pkill -9 ffmpeg"
|
||||
}
|
||||
exec(cmd,{detached: true})
|
||||
};
|
||||
|
@ -31,12 +31,14 @@ module.exports = function(s,config){
|
|||
}
|
||||
}
|
||||
s.parseJSON = function(string){
|
||||
var parsed
|
||||
try{
|
||||
string = JSON.parse(string)
|
||||
parsed = JSON.parse(string)
|
||||
}catch(err){
|
||||
|
||||
}
|
||||
return string
|
||||
if(!parsed)parsed = string
|
||||
return parsed
|
||||
}
|
||||
s.stringJSON = function(json){
|
||||
try{
|
||||
|
@ -224,4 +226,30 @@ module.exports = function(s,config){
|
|||
break;
|
||||
}
|
||||
}
|
||||
s.createTimeout = function(timeoutVar,timeoutLength,defaultLength,multiplier,callback){
|
||||
var theTimeout
|
||||
if(!multiplier)multiplier = 1000 * 60
|
||||
if(!timeoutLength || timeoutLength === ''){
|
||||
theTimeout = defaultLength
|
||||
}else{
|
||||
theTimeout = parseFloat(timeoutLength) * multiplier
|
||||
}
|
||||
clearTimeout(timeoutVar)
|
||||
timeoutVar = setTimeout(function(){
|
||||
clearTimeout(timeoutVar)
|
||||
delete(timeoutVar)
|
||||
if(callback)callback()
|
||||
},theTimeout)
|
||||
}
|
||||
Object.defineProperty(Array.prototype, 'chunk', {
|
||||
value: function(chunkSize){
|
||||
var temporal = [];
|
||||
|
||||
for (var i = 0; i < this.length; i+= chunkSize){
|
||||
temporal.push(this.slice(i,i+chunkSize));
|
||||
}
|
||||
|
||||
return temporal;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,7 +2,26 @@ var fs = require('fs');
|
|||
var exec = require('child_process').exec;
|
||||
var spawn = require('child_process').spawn;
|
||||
var webdav = require("webdav-fs");
|
||||
var ssh2SftpClient = require('node-ssh')
|
||||
module.exports = function(s,config,lang){
|
||||
var addCloudUploader = function(opt){
|
||||
s.loadGroupAppExtender(opt.loadGroupAppExtender)
|
||||
s.unloadGroupAppExtender(opt.unloadGroupAppExtender)
|
||||
s.insertCompletedVideoExtender(opt.insertCompletedVideoExtender)
|
||||
s.deleteVideoFromCloudExtensions[opt.name] = opt.deleteVideoFromCloudExtensions
|
||||
s.cloudDiskUseStartupExtensions[opt.name] = opt.cloudDiskUseStartupExtensions
|
||||
s.beforeAccountSave(opt.beforeAccountSave)
|
||||
s.onAccountSave(opt.onAccountSave)
|
||||
s.cloudDisksLoader(opt.name)
|
||||
}
|
||||
var addSimpleUploader = function(opt){
|
||||
s.loadGroupAppExtender(opt.loadGroupAppExtender)
|
||||
s.unloadGroupAppExtender(opt.unloadGroupAppExtender)
|
||||
s.insertCompletedVideoExtender(opt.insertCompletedVideoExtender)
|
||||
s.beforeAccountSave(opt.beforeAccountSave)
|
||||
s.onAccountSave(opt.onAccountSave)
|
||||
s.onMonitorSave(opt.onMonitorSave)
|
||||
}
|
||||
// WebDAV
|
||||
var beforeAccountSaveForWebDav = function(d){
|
||||
//d = save event
|
||||
|
@ -20,33 +39,33 @@ module.exports = function(s,config,lang){
|
|||
}
|
||||
var loadWebDavForUser = function(e){
|
||||
// e = user
|
||||
var ar = JSON.parse(e.details);
|
||||
if(ar.webdav_use_global === '1' && config.cloudUploaders && config.cloudUploaders.WebDAV){
|
||||
var userDetails = JSON.parse(e.details);
|
||||
if(userDetails.webdav_use_global === '1' && config.cloudUploaders && config.cloudUploaders.WebDAV){
|
||||
// {
|
||||
// webdav_user: "",
|
||||
// webdav_pass: "",
|
||||
// webdav_url: "",
|
||||
// webdav_dir: "",
|
||||
// }
|
||||
ar = Object.assign(ar,config.cloudUploaders.WebDAV)
|
||||
userDetails = Object.assign(userDetails,config.cloudUploaders.WebDAV)
|
||||
}
|
||||
//owncloud/webdav
|
||||
if(!s.group[e.ke].webdav &&
|
||||
ar.webdav_user&&
|
||||
ar.webdav_user!==''&&
|
||||
ar.webdav_pass&&
|
||||
ar.webdav_pass!==''&&
|
||||
ar.webdav_url&&
|
||||
ar.webdav_url!==''
|
||||
userDetails.webdav_user&&
|
||||
userDetails.webdav_user!==''&&
|
||||
userDetails.webdav_pass&&
|
||||
userDetails.webdav_pass!==''&&
|
||||
userDetails.webdav_url&&
|
||||
userDetails.webdav_url!==''
|
||||
){
|
||||
if(!ar.webdav_dir||ar.webdav_dir===''){
|
||||
ar.webdav_dir='/'
|
||||
if(!userDetails.webdav_dir||userDetails.webdav_dir===''){
|
||||
userDetails.webdav_dir='/'
|
||||
}
|
||||
ar.webdav_dir = s.checkCorrectPathEnding(ar.webdav_dir)
|
||||
userDetails.webdav_dir = s.checkCorrectPathEnding(userDetails.webdav_dir)
|
||||
s.group[e.ke].webdav = webdav(
|
||||
ar.webdav_url,
|
||||
ar.webdav_user,
|
||||
ar.webdav_pass
|
||||
userDetails.webdav_url,
|
||||
userDetails.webdav_user,
|
||||
userDetails.webdav_pass
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -174,8 +193,8 @@ module.exports = function(s,config,lang){
|
|||
}
|
||||
var loadAmazonS3ForUser = function(e){
|
||||
// e = user
|
||||
var ar = JSON.parse(e.details)
|
||||
if(ar.aws_use_global === '1' && config.cloudUploaders && config.cloudUploaders.AmazonS3){
|
||||
var userDetails = JSON.parse(e.details)
|
||||
if(userDetails.aws_use_global === '1' && config.cloudUploaders && config.cloudUploaders.AmazonS3){
|
||||
// {
|
||||
// aws_accessKeyId: "",
|
||||
// aws_secretAccessKey: "",
|
||||
|
@ -183,30 +202,30 @@ module.exports = function(s,config,lang){
|
|||
// aws_s3_bucket: "",
|
||||
// aws_s3_dir: "",
|
||||
// }
|
||||
ar = Object.assign(ar,config.cloudUploaders.AmazonS3)
|
||||
userDetails = Object.assign(userDetails,config.cloudUploaders.AmazonS3)
|
||||
}
|
||||
//Amazon S3
|
||||
if(!s.group[e.ke].aws &&
|
||||
!s.group[e.ke].aws_s3 &&
|
||||
ar.aws_s3 !== '0' &&
|
||||
ar.aws_accessKeyId !== ''&&
|
||||
ar.aws_secretAccessKey &&
|
||||
ar.aws_secretAccessKey !== ''&&
|
||||
ar.aws_region &&
|
||||
ar.aws_region !== ''&&
|
||||
ar.aws_s3_bucket !== ''
|
||||
userDetails.aws_s3 !== '0' &&
|
||||
userDetails.aws_accessKeyId !== ''&&
|
||||
userDetails.aws_secretAccessKey &&
|
||||
userDetails.aws_secretAccessKey !== ''&&
|
||||
userDetails.aws_region &&
|
||||
userDetails.aws_region !== ''&&
|
||||
userDetails.aws_s3_bucket !== ''
|
||||
){
|
||||
if(!ar.aws_s3_dir || ar.aws_s3_dir === '/'){
|
||||
ar.aws_s3_dir = ''
|
||||
if(!userDetails.aws_s3_dir || userDetails.aws_s3_dir === '/'){
|
||||
userDetails.aws_s3_dir = ''
|
||||
}
|
||||
if(ar.aws_s3_dir !== ''){
|
||||
ar.aws_s3_dir = s.checkCorrectPathEnding(ar.aws_s3_dir)
|
||||
if(userDetails.aws_s3_dir !== ''){
|
||||
userDetails.aws_s3_dir = s.checkCorrectPathEnding(userDetails.aws_s3_dir)
|
||||
}
|
||||
s.group[e.ke].aws = new require("aws-sdk")
|
||||
s.group[e.ke].aws.config = new s.group[e.ke].aws.Config({
|
||||
accessKeyId: ar.aws_accessKeyId,
|
||||
secretAccessKey: ar.aws_secretAccessKey,
|
||||
region: ar.aws_region
|
||||
accessKeyId: userDetails.aws_accessKeyId,
|
||||
secretAccessKey: userDetails.aws_secretAccessKey,
|
||||
region: userDetails.aws_region
|
||||
})
|
||||
s.group[e.ke].aws_s3 = new s.group[e.ke].aws.S3();
|
||||
}
|
||||
|
@ -296,63 +315,67 @@ module.exports = function(s,config,lang){
|
|||
}
|
||||
}
|
||||
var loadBackblazeB2ForUser = function(e){
|
||||
var ar = JSON.parse(e.details);
|
||||
var userDetails = JSON.parse(e.details);
|
||||
try{
|
||||
if(ar.b2_use_global === '1' && config.cloudUploaders && config.cloudUploaders.BackblazeB2){
|
||||
if(userDetails.b2_use_global === '1' && config.cloudUploaders && config.cloudUploaders.BackblazeB2){
|
||||
// {
|
||||
// bb_b2_accountId: "",
|
||||
// bb_b2_applicationKey: "",
|
||||
// bb_b2_bucket: "",
|
||||
// bb_b2_dir: "",
|
||||
// }
|
||||
ar = Object.assign(ar,config.cloudUploaders.BackblazeB2)
|
||||
userDetails = Object.assign(userDetails,config.cloudUploaders.BackblazeB2)
|
||||
}
|
||||
if(!s.group[e.ke].bb_b2 &&
|
||||
ar.bb_b2_accountId &&
|
||||
ar.bb_b2_accountId !=='' &&
|
||||
ar.bb_b2_applicationKey &&
|
||||
ar.bb_b2_applicationKey !=='' &&
|
||||
ar.bb_b2_bucket &&
|
||||
ar.bb_b2_bucket !== ''
|
||||
userDetails.bb_b2_accountId &&
|
||||
userDetails.bb_b2_accountId !=='' &&
|
||||
userDetails.bb_b2_applicationKey &&
|
||||
userDetails.bb_b2_applicationKey !=='' &&
|
||||
userDetails.bb_b2_bucket &&
|
||||
userDetails.bb_b2_bucket !== ''
|
||||
){
|
||||
var B2 = require('backblaze-b2')
|
||||
if(!ar.bb_b2_dir || ar.bb_b2_dir === '/'){
|
||||
ar.bb_b2_dir = ''
|
||||
if(!userDetails.bb_b2_dir || userDetails.bb_b2_dir === '/'){
|
||||
userDetails.bb_b2_dir = ''
|
||||
}
|
||||
if(ar.bb_b2_dir !== ''){
|
||||
ar.bb_b2_dir = s.checkCorrectPathEnding(ar.bb_b2_dir)
|
||||
if(userDetails.bb_b2_dir !== ''){
|
||||
userDetails.bb_b2_dir = s.checkCorrectPathEnding(userDetails.bb_b2_dir)
|
||||
}
|
||||
var b2 = new B2({
|
||||
accountId: ar.bb_b2_accountId,
|
||||
applicationKey: ar.bb_b2_applicationKey
|
||||
})
|
||||
s.group[e.ke].bb_b2 = b2
|
||||
var backblazeErr = function(err){
|
||||
// console.log(err)
|
||||
s.userLog({mid:'$USER',ke:e.ke},{type:lang['Backblaze Error'],msg:err.data})
|
||||
s.userLog({mid:'$USER',ke:e.ke},{type:lang['Backblaze Error'],msg:err.data || err})
|
||||
}
|
||||
b2.authorize().then(function(resp){
|
||||
s.group[e.ke].bb_b2_downloadUrl = resp.data.downloadUrl
|
||||
b2.listBuckets().then(function(resp){
|
||||
var buckets = resp.data.buckets
|
||||
var bucketN = -2
|
||||
buckets.forEach(function(item,n){
|
||||
if(item.bucketName === ar.bb_b2_bucket){
|
||||
bucketN = n
|
||||
var createB2Connection = function(){
|
||||
var b2 = new B2({
|
||||
accountId: userDetails.bb_b2_accountId,
|
||||
applicationKey: userDetails.bb_b2_applicationKey
|
||||
})
|
||||
b2.authorize().then(function(resp){
|
||||
s.group[e.ke].bb_b2_downloadUrl = resp.data.downloadUrl
|
||||
b2.listBuckets().then(function(resp){
|
||||
var buckets = resp.data.buckets
|
||||
var bucketN = -2
|
||||
buckets.forEach(function(item,n){
|
||||
if(item.bucketName === userDetails.bb_b2_bucket){
|
||||
bucketN = n
|
||||
}
|
||||
})
|
||||
if(bucketN > -1){
|
||||
s.group[e.ke].bb_b2_bucketId = buckets[bucketN].bucketId
|
||||
}else{
|
||||
b2.createBucket(
|
||||
userDetails.bb_b2_bucket,
|
||||
'allPublic'
|
||||
).then(function(resp){
|
||||
s.group[e.ke].bb_b2_bucketId = resp.data.bucketId
|
||||
}).catch(backblazeErr)
|
||||
}
|
||||
})
|
||||
if(bucketN > -1){
|
||||
s.group[e.ke].bb_b2_bucketId = buckets[bucketN].bucketId
|
||||
}else{
|
||||
b2.createBucket(
|
||||
ar.bb_b2_bucket,
|
||||
'allPublic'
|
||||
).then(function(resp){
|
||||
s.group[e.ke].bb_b2_bucketId = resp.data.bucketId
|
||||
}).catch(backblazeErr)
|
||||
}
|
||||
}).catch(backblazeErr)
|
||||
}).catch(backblazeErr)
|
||||
}).catch(backblazeErr)
|
||||
s.group[e.ke].bb_b2 = b2
|
||||
}
|
||||
createB2Connection()
|
||||
s.group[e.ke].bb_b2_refreshTimer = setTimeout(createB2Connection,1000 * 60 * 60)
|
||||
}
|
||||
}catch(err){
|
||||
s.debugLog(err)
|
||||
|
@ -360,6 +383,7 @@ module.exports = function(s,config,lang){
|
|||
}
|
||||
var unloadBackblazeB2ForUser = function(user){
|
||||
s.group[user.ke].bb_b2 = null
|
||||
clearTimeout(s.group[user.ke].bb_b2_refreshTimer)
|
||||
}
|
||||
var deleteVideoFromBackblazeB2 = function(e,video,callback){
|
||||
// e = user
|
||||
|
@ -431,156 +455,259 @@ module.exports = function(s,config,lang){
|
|||
})
|
||||
}
|
||||
}
|
||||
//Wasabi Hot Cloud Storage
|
||||
var beforeAccountSaveForWasabiHotCloudStorage = function(d){
|
||||
//d = save event
|
||||
d.form.details.whcs_use_global=d.d.whcs_use_global
|
||||
d.form.details.use_whcs=d.d.use_whcs
|
||||
}
|
||||
var cloudDiskUseStartupForWasabiHotCloudStorage = function(group,userDetails){
|
||||
group.cloudDiskUse['whcs'].name = 'Wasabi Hot Cloud Storage'
|
||||
group.cloudDiskUse['whcs'].sizeLimitCheck = (userDetails.use_whcs_size_limit === '1')
|
||||
if(!userDetails.whcs_size_limit || userDetails.whcs_size_limit === ''){
|
||||
group.cloudDiskUse['whcs'].sizeLimit = 10000
|
||||
}else{
|
||||
group.cloudDiskUse['whcs'].sizeLimit = parseFloat(userDetails.whcs_size_limit)
|
||||
}
|
||||
}
|
||||
var loadWasabiHotCloudStorageForUser = function(e){
|
||||
// e = user
|
||||
var userDetails = JSON.parse(e.details)
|
||||
if(userDetails.whcs_use_global === '1' && config.cloudUploaders && config.cloudUploaders.WasabiHotCloudStorage){
|
||||
// {
|
||||
// whcs_accessKeyId: "",
|
||||
// whcs_secretAccessKey: "",
|
||||
// whcs_region: "",
|
||||
// whcs_bucket: "",
|
||||
// whcs_dir: "",
|
||||
// }
|
||||
userDetails = Object.assign(userDetails,config.cloudUploaders.WasabiHotCloudStorage)
|
||||
}
|
||||
//Wasabi Hot Cloud Storage
|
||||
if(!s.group[e.ke].whcs &&
|
||||
userDetails.whcs !== '0' &&
|
||||
userDetails.whcs_accessKeyId !== ''&&
|
||||
userDetails.whcs_secretAccessKey &&
|
||||
userDetails.whcs_secretAccessKey !== ''&&
|
||||
userDetails.whcs_region &&
|
||||
userDetails.whcs_region !== ''&&
|
||||
userDetails.whcs_bucket !== ''
|
||||
){
|
||||
if(!userDetails.whcs_dir || userDetails.whcs_dir === '/'){
|
||||
userDetails.whcs_dir = ''
|
||||
}
|
||||
if(userDetails.whcs_dir !== ''){
|
||||
userDetails.whcs_dir = s.checkCorrectPathEnding(userDetails.whcs_dir)
|
||||
}
|
||||
var AWS = new require("aws-sdk")
|
||||
s.group[e.ke].whcs = AWS
|
||||
var wasabiEndpoint = new AWS.Endpoint('s3.wasabisys.com')
|
||||
s.group[e.ke].whcs.config = new s.group[e.ke].whcs.Config({
|
||||
endpoint: wasabiEndpoint,
|
||||
accessKeyId: userDetails.whcs_accessKeyId,
|
||||
secretAccessKey: userDetails.whcs_secretAccessKey,
|
||||
region: userDetails.whcs_region
|
||||
})
|
||||
s.group[e.ke].whcs = new s.group[e.ke].whcs.S3();
|
||||
}
|
||||
}
|
||||
var unloadWasabiHotCloudStorageForUser = function(user){
|
||||
s.group[user.ke].whcs = null
|
||||
}
|
||||
var deleteVideoFromWasabiHotCloudStorage = function(e,video,callback){
|
||||
// e = user
|
||||
try{
|
||||
var videoDetails = JSON.parse(video.details)
|
||||
}catch(err){
|
||||
var videoDetails = video.details
|
||||
}
|
||||
if(!videoDetails.location){
|
||||
videoDetails.location = video.href.split('wasabisys.com')[1]
|
||||
}
|
||||
s.group[e.ke].whcs.deleteObject({
|
||||
Bucket: s.group[e.ke].init.whcs_bucket,
|
||||
Key: videoDetails.location,
|
||||
}, function(err, data) {
|
||||
if (err) console.log(err);
|
||||
callback()
|
||||
});
|
||||
}
|
||||
var uploadVideoToWasabiHotCloudStorage = function(e,k){
|
||||
//e = video object
|
||||
//k = temporary values
|
||||
if(!k)k={};
|
||||
//cloud saver - Wasabi Hot Cloud Storage
|
||||
if(s.group[e.ke].whcs && s.group[e.ke].init.use_whcs !== '0' && s.group[e.ke].init.whcs_save === '1'){
|
||||
var ext = k.filename.split('.')
|
||||
ext = ext[ext.length - 1]
|
||||
var fileStream = fs.createReadStream(k.dir+k.filename);
|
||||
fileStream.on('error', function (err) {
|
||||
console.error(err)
|
||||
})
|
||||
var saveLocation = s.group[e.ke].init.whcs_dir+e.ke+'/'+e.mid+'/'+k.filename
|
||||
s.group[e.ke].whcs.upload({
|
||||
Bucket: s.group[e.ke].init.whcs_bucket,
|
||||
Key: saveLocation,
|
||||
Body:fileStream,
|
||||
ACL:'public-read',
|
||||
ContentType:'video/'+ext
|
||||
},function(err,data){
|
||||
if(err){
|
||||
s.userLog(e,{type:lang['Wasabi Hot Cloud Storage Upload Error'],msg:err})
|
||||
}
|
||||
if(s.group[e.ke].init.whcs_log === '1' && data && data.Location){
|
||||
var save = [
|
||||
e.mid,
|
||||
e.ke,
|
||||
k.startTime,
|
||||
1,
|
||||
s.s({
|
||||
type : 'whcs',
|
||||
location : saveLocation
|
||||
}),
|
||||
k.filesize,
|
||||
k.endTime,
|
||||
data.Location
|
||||
]
|
||||
s.sqlQuery('INSERT INTO `Cloud Videos` (mid,ke,time,status,details,size,end,href) VALUES (?,?,?,?,?,?,?,?)',save)
|
||||
s.setCloudDiskUsedForGroup(e,{
|
||||
amount : k.filesizeMB,
|
||||
storageType : 'whcs'
|
||||
})
|
||||
s.purgeCloudDiskForGroup(e,'whcs')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
//SFTP
|
||||
// var beforeAccountSaveForSftp = function(d){
|
||||
// //d = save event
|
||||
// d.form.details.use_sftp = d.d.use_sftp
|
||||
// }
|
||||
// var cloudDiskUseStartupForSftp = function(group,userDetails){
|
||||
// group.cloudDiskUse['sftp'].name = 'SFTP'
|
||||
// group.cloudDiskUse['sftp'].sizeLimitCheck = (userDetails.use_aws_s3_size_limit === '1')
|
||||
// if(!userDetails.aws_s3_size_limit || userDetails.aws_s3_size_limit === ''){
|
||||
// group.cloudDiskUse['sftp'].sizeLimit = 10000
|
||||
// }else{
|
||||
// group.cloudDiskUse['sftp'].sizeLimit = parseFloat(userDetails.aws_s3_size_limit)
|
||||
// }
|
||||
// }
|
||||
// var loadSftpForUser = function(e){
|
||||
// // e = user
|
||||
// var ar = JSON.parse(e.details);
|
||||
// //SFTP
|
||||
// if(!s.group[e.ke].sftp &&
|
||||
// !s.group[e.ke].sftp &&
|
||||
// ar.sftp !== '0' &&
|
||||
// ar.sftp_accessKeyId !== ''&&
|
||||
// ar.sftp_secretAccessKey &&
|
||||
// ar.sftp_secretAccessKey !== ''&&
|
||||
// ar.sftp_region &&
|
||||
// ar.sftp_region !== ''&&
|
||||
// ar.sftp_bucket !== ''
|
||||
// ){
|
||||
// if(!ar.sftp_dir || ar.sftp_dir === '/'){
|
||||
// ar.sftp_dir = ''
|
||||
// }
|
||||
// if(ar.sftp_dir !== ''){
|
||||
// ar.sftp_dir = s.checkCorrectPathEnding(ar.sftp_dir)
|
||||
// }
|
||||
// s.group[e.ke].sftp = new s.group[e.ke].sftp.S3();
|
||||
// s.group[e.ke].sftp = new require('ssh2-sftp-client')();
|
||||
// var connectionDetails = {
|
||||
// host: ar.sftp_host,
|
||||
// port: ar.sftp_port
|
||||
// }
|
||||
// if(!ar.sftp_port)ar.sftp_port = 22
|
||||
// if(ar.sftp_username)connectionDetails.username = ar.sftp_username
|
||||
// if(ar.sftp_password)connectionDetails.password = ar.sftp_password
|
||||
// if(ar.sftp_privateKey)connectionDetails.privateKey = ar.sftp_privateKey
|
||||
// sftp.connect(connectionDetails).then(() => {
|
||||
// return sftp.list('/pathname');
|
||||
// }).then((data) => {
|
||||
// console.log(data, 'the data info');
|
||||
// }).catch((err) => {
|
||||
// console.log(err, 'catch error');
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// var unloadSftpForUser = function(user){
|
||||
// s.group[user.ke].sftp = null
|
||||
// }
|
||||
// var deleteVideoFromSftp = function(e,video,callback){
|
||||
// // e = user
|
||||
// try{
|
||||
// var videoDetails = JSON.parse(video.details)
|
||||
// }catch(err){
|
||||
// var videoDetails = video.details
|
||||
// }
|
||||
// s.group[e.ke].sftp.deleteObject({
|
||||
// Bucket: s.group[e.ke].init.sftp_bucket,
|
||||
// Key: videoDetails.location,
|
||||
// }, function(err, data) {
|
||||
// if (err) console.log(err);
|
||||
// callback()
|
||||
// });
|
||||
// }
|
||||
// var uploadVideoToSftp = function(e,k){
|
||||
// //e = video object
|
||||
// //k = temporary values
|
||||
// if(!k)k={};
|
||||
// //cloud saver - SFTP
|
||||
// if(s.group[e.ke].sftp && s.group[e.ke].init.use_sftp !== '0' && s.group[e.ke].init.sftp_save === '1'){
|
||||
// var fileStream = fs.createReadStream(k.dir+k.filename);
|
||||
// fileStream.on('error', function (err) {
|
||||
// console.error(err)
|
||||
// })
|
||||
// var saveLocation = s.group[e.ke].init.sftp_dir+e.ke+'/'+e.mid+'/'+k.filename
|
||||
// s.group[e.ke].sftp.upload({
|
||||
// Bucket: s.group[e.ke].init.sftp_bucket,
|
||||
// Key: saveLocation,
|
||||
// Body:fileStream,
|
||||
// ACL:'public-read'
|
||||
// },function(err,data){
|
||||
// if(err){
|
||||
// s.userLog(e,{type:lang['SFTP Upload Error'],msg:err})
|
||||
// }
|
||||
// if(s.group[e.ke].init.sftp_log === '1' && data && data.Location){
|
||||
// var save = [
|
||||
// e.mid,
|
||||
// e.ke,
|
||||
// k.startTime,
|
||||
// 1,
|
||||
// s.s({
|
||||
// type : 'sftp',
|
||||
// location : saveLocation
|
||||
// }),
|
||||
// k.filesize,
|
||||
// k.endTime,
|
||||
// data.Location
|
||||
// ]
|
||||
// s.sqlQuery('INSERT INTO `Cloud Videos` (mid,ke,time,status,details,size,end,href) VALUES (?,?,?,?,?,?,?,?)',save)
|
||||
// s.setCloudDiskUsedForGroup(e,{
|
||||
// amount : k.filesizeMB,
|
||||
// storageType : 'sftp'
|
||||
// })
|
||||
// s.purgeCloudDiskForGroup(e,'sftp')
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
var sftpErr = function(err){
|
||||
// console.log(err)
|
||||
s.userLog({mid:'$USER',ke:e.ke},{type:lang['SFTP Error'],msg:err.data || err})
|
||||
}
|
||||
var beforeAccountSaveForSftp = function(d){
|
||||
//d = save event
|
||||
d.form.details.use_sftp = d.d.use_sftp
|
||||
}
|
||||
var loadSftpForUser = function(e){
|
||||
// e = user
|
||||
var userDetails = JSON.parse(e.details);
|
||||
//SFTP
|
||||
if(!s.group[e.ke].sftp &&
|
||||
!s.group[e.ke].sftp &&
|
||||
userDetails.sftp !== '0' &&
|
||||
userDetails.sftp_host &&
|
||||
userDetails.sftp_host !== ''&&
|
||||
userDetails.sftp_port &&
|
||||
userDetails.sftp_port !== ''
|
||||
){
|
||||
if(!userDetails.sftp_dir || userDetails.sftp_dir === '/'){
|
||||
userDetails.sftp_dir = ''
|
||||
}
|
||||
if(userDetails.sftp_dir !== ''){
|
||||
userDetails.sftp_dir = s.checkCorrectPathEnding(userDetails.sftp_dir)
|
||||
}
|
||||
var sftp = new ssh2SftpClient()
|
||||
var connectionDetails = {
|
||||
host: userDetails.sftp_host,
|
||||
port: userDetails.sftp_port
|
||||
}
|
||||
if(!userDetails.sftp_port)connectionDetails.port = 22
|
||||
if(userDetails.sftp_username && userDetails.sftp_username !== '')connectionDetails.username = userDetails.sftp_username
|
||||
if(userDetails.sftp_password && userDetails.sftp_password !== '')connectionDetails.password = userDetails.sftp_password
|
||||
if(userDetails.sftp_privateKey && userDetails.sftp_privateKey !== '')connectionDetails.privateKey = userDetails.sftp_privateKey
|
||||
sftp.connect(connectionDetails).catch(sftpErr)
|
||||
s.group[e.ke].sftp = sftp
|
||||
}
|
||||
}
|
||||
var unloadSftpForUser = function(user){
|
||||
if(s.group[user.ke].sftp && s.group[user.ke].sftp.end)s.group[user.ke].sftp.end().then(function(){
|
||||
s.group[user.ke].sftp = null
|
||||
})
|
||||
}
|
||||
var uploadVideoToSftp = function(e,k){
|
||||
//e = video object
|
||||
//k = temporary values
|
||||
if(!k)k={};
|
||||
//cloud saver - SFTP
|
||||
if(s.group[e.ke].sftp && s.group[e.ke].init.use_sftp !== '0' && s.group[e.ke].init.sftp_save === '1'){
|
||||
var localPath = k.dir + k.filename
|
||||
var saveLocation = s.group[e.ke].init.sftp_dir + e.ke + '/' + e.mid + '/' + k.filename
|
||||
s.group[e.ke].sftp.putFile(localPath, saveLocation).catch(sftpErr)
|
||||
}
|
||||
}
|
||||
var createSftpDirectory = function(monitorConfig){
|
||||
var monitorSaveDirectory = s.group[monitorConfig.ke].init.sftp_dir + monitorConfig.ke + '/' + monitorConfig.mid
|
||||
s.group[monitorConfig.ke].sftp.mkdir(monitorSaveDirectory, true).catch(function(err){
|
||||
if(err.code !== 'ERR_ASSERTION'){
|
||||
sftpErr(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
var onMonitorSaveForSftp = function(monitorConfig){
|
||||
if(s.group[monitorConfig.ke].sftp && s.group[monitorConfig.ke].init.use_sftp !== '0' && s.group[monitorConfig.ke].init.sftp_save === '1'){
|
||||
createSftpDirectory(monitorConfig)
|
||||
}
|
||||
}
|
||||
var onAccountSaveForSftp = function(group,userDetails,user){
|
||||
if(s.group[user.ke] && s.group[user.ke].sftp && s.group[user.ke].init.use_sftp !== '0' && s.group[user.ke].init.sftp_save === '1'){
|
||||
Object.keys(s.group[user.ke].mon_conf).forEach(function(monitorId){
|
||||
createSftpDirectory(s.group[user.ke].mon_conf[monitorId])
|
||||
})
|
||||
}
|
||||
}
|
||||
//add the extenders
|
||||
//webdav
|
||||
s.loadGroupAppExtender(loadWebDavForUser)
|
||||
s.unloadGroupAppExtender(unloadWebDavForUser)
|
||||
s.insertCompletedVideoExtender(uploadVideoToWebDav)
|
||||
s.deleteVideoFromCloudExtensions['webdav'] = deleteVideoFromWebDav
|
||||
s.cloudDiskUseStartupExtensions['webdav'] = cloudDiskUseStartupForWebDav
|
||||
s.beforeAccountSave(beforeAccountSaveForWebDav)
|
||||
s.onAccountSave(cloudDiskUseStartupForWebDav)
|
||||
s.cloudDisksLoader('webdav')
|
||||
addCloudUploader({
|
||||
name: 'webdav',
|
||||
loadGroupAppExtender: loadWebDavForUser,
|
||||
unloadGroupAppExtender: unloadWebDavForUser,
|
||||
insertCompletedVideoExtender: uploadVideoToWebDav,
|
||||
deleteVideoFromCloudExtensions: deleteVideoFromWebDav,
|
||||
cloudDiskUseStartupExtensions: cloudDiskUseStartupForWebDav,
|
||||
beforeAccountSave: beforeAccountSaveForWebDav,
|
||||
onAccountSave: cloudDiskUseStartupForWebDav,
|
||||
})
|
||||
//amazon s3
|
||||
s.loadGroupAppExtender(loadAmazonS3ForUser)
|
||||
s.unloadGroupAppExtender(unloadAmazonS3ForUser)
|
||||
s.insertCompletedVideoExtender(uploadVideoToAmazonS3)
|
||||
s.deleteVideoFromCloudExtensions['s3'] = deleteVideoFromAmazonS3
|
||||
s.cloudDiskUseStartupExtensions['s3'] = cloudDiskUseStartupForAmazonS3
|
||||
s.beforeAccountSave(beforeAccountSaveForAmazonS3)
|
||||
s.onAccountSave(cloudDiskUseStartupForAmazonS3)
|
||||
s.cloudDisksLoader('s3')
|
||||
addCloudUploader({
|
||||
name: 's3',
|
||||
loadGroupAppExtender: loadAmazonS3ForUser,
|
||||
unloadGroupAppExtender: unloadAmazonS3ForUser,
|
||||
insertCompletedVideoExtender: uploadVideoToAmazonS3,
|
||||
deleteVideoFromCloudExtensions: deleteVideoFromAmazonS3,
|
||||
cloudDiskUseStartupExtensions: cloudDiskUseStartupForAmazonS3,
|
||||
beforeAccountSave: beforeAccountSaveForAmazonS3,
|
||||
onAccountSave: cloudDiskUseStartupForAmazonS3,
|
||||
})
|
||||
//backblaze b2
|
||||
s.loadGroupAppExtender(loadBackblazeB2ForUser)
|
||||
s.unloadGroupAppExtender(unloadBackblazeB2ForUser)
|
||||
s.insertCompletedVideoExtender(uploadVideoToBackblazeB2)
|
||||
s.deleteVideoFromCloudExtensions['b2'] = deleteVideoFromBackblazeB2
|
||||
s.cloudDiskUseStartupExtensions['b2'] = cloudDiskUseStartupForBackblazeB2
|
||||
s.beforeAccountSave(beforeAccountSaveForBackblazeB2)
|
||||
s.onAccountSave(cloudDiskUseStartupForBackblazeB2)
|
||||
s.cloudDisksLoader('b2')
|
||||
//SFTP
|
||||
// s.loadGroupAppExtender(loadSftpForUser)
|
||||
// s.unloadGroupAppExtender(unloadSftpForUser)
|
||||
// s.insertCompletedVideoExtender(uploadVideoToSftp)
|
||||
// s.deleteVideoFromCloudExtensions['sftp'] = deleteVideoFromSftp
|
||||
// s.cloudDiskUseStartupExtensions['sftp'] = cloudDiskUseStartupForSftp
|
||||
// s.beforeAccountSave(beforeAccountSaveForSftp)
|
||||
// s.onAccountSave(cloudDiskUseStartupForSftp)
|
||||
// s.cloudDisksLoader('sftp')
|
||||
addCloudUploader({
|
||||
name: 'b2',
|
||||
loadGroupAppExtender: loadBackblazeB2ForUser,
|
||||
unloadGroupAppExtender: unloadBackblazeB2ForUser,
|
||||
insertCompletedVideoExtender: uploadVideoToBackblazeB2,
|
||||
deleteVideoFromCloudExtensions: deleteVideoFromBackblazeB2,
|
||||
cloudDiskUseStartupExtensions: cloudDiskUseStartupForBackblazeB2,
|
||||
beforeAccountSave: beforeAccountSaveForBackblazeB2,
|
||||
onAccountSave: cloudDiskUseStartupForBackblazeB2,
|
||||
})
|
||||
//wasabi
|
||||
addCloudUploader({
|
||||
name: 'whcs',
|
||||
loadGroupAppExtender: loadWasabiHotCloudStorageForUser,
|
||||
unloadGroupAppExtender: unloadWasabiHotCloudStorageForUser,
|
||||
insertCompletedVideoExtender: uploadVideoToWasabiHotCloudStorage,
|
||||
deleteVideoFromCloudExtensions: deleteVideoFromWasabiHotCloudStorage,
|
||||
cloudDiskUseStartupExtensions: cloudDiskUseStartupForWasabiHotCloudStorage,
|
||||
beforeAccountSave: beforeAccountSaveForWasabiHotCloudStorage,
|
||||
onAccountSave: cloudDiskUseStartupForWasabiHotCloudStorage,
|
||||
})
|
||||
//SFTP (Simple Uploader)
|
||||
addSimpleUploader({
|
||||
name: 'sftp',
|
||||
loadGroupAppExtender: loadSftpForUser,
|
||||
unloadGroupAppExtender: unloadSftpForUser,
|
||||
insertCompletedVideoExtender: uploadVideoToSftp,
|
||||
beforeAccountSave: beforeAccountSaveForSftp,
|
||||
onAccountSave: onAccountSaveForSftp,
|
||||
onMonitorSave: onMonitorSaveForSftp,
|
||||
})
|
||||
}
|
||||
|
|
67
libs/codeTester.js
Normal file
67
libs/codeTester.js
Normal file
|
@ -0,0 +1,67 @@
|
|||
var fs = require('fs');
|
||||
var execSync = require('child_process').execSync;
|
||||
module.exports = function(s,config,lang){
|
||||
var onFFmpegLoaded = function(ffmpeg){
|
||||
if(process.argv[2] && process.argv[2].indexOf('test') > -1){
|
||||
config.testMode = true
|
||||
}
|
||||
if(config.testMode === true){
|
||||
config.videosDir = s.mainDirectory + '/videosTest/'
|
||||
config.port = 9999
|
||||
if(config.childNodes && config.childNodes.enabled === true && config.childNodes.mode === 'master'){
|
||||
config.childNodes.port = 9998
|
||||
}
|
||||
s.ffmpegFunctions = ffmpeg
|
||||
}
|
||||
}
|
||||
var onBeforeDatabaseLoad = function(ffmpeg){
|
||||
if(config.testMode === true){
|
||||
try{
|
||||
execSync('rm ' + s.mainDirectory + '/shinobi-test.sqlite')
|
||||
}catch(err){
|
||||
|
||||
}
|
||||
try{
|
||||
require('sqlite3')
|
||||
}catch(err){
|
||||
execSync('npm install sqlite3 --unsafe-perm')
|
||||
}
|
||||
execSync('cp ' + s.mainDirectory + '/sql/shinobi.sample.sqlite ' + s.mainDirectory + '/shinobi-test.sqlite')
|
||||
config.databaseType = 'sqlite3'
|
||||
config.db = {
|
||||
filename: s.mainDirectory + "/shinobi-test.sqlite"
|
||||
}
|
||||
}
|
||||
}
|
||||
var onProcessReady = function(){
|
||||
if(config.testMode === true){
|
||||
s.location.super = s.mainDirectory + '/super-test.json'
|
||||
fs.writeFileSync(s.location.super,s.s([
|
||||
{
|
||||
"mail":"admin@shinobi.video",
|
||||
"pass":"21232f297a57a5a743894a0e4a801fc3",
|
||||
"tokens":[
|
||||
"111"
|
||||
]
|
||||
}
|
||||
],null,3))
|
||||
setTimeout(function(){
|
||||
require(s.mainDirectory + '/test/run.js')(s,config,lang,io)
|
||||
},500)
|
||||
}
|
||||
}
|
||||
var onProcessExit = function(){
|
||||
if(config.testMode === true){
|
||||
execSync('rm ' + s.mainDirectory + '/shinobi-test.sqlite')
|
||||
execSync('rm ' + s.location.super)
|
||||
execSync('rm -rf ' + config.videosDir)
|
||||
console.log('---- Temporary Files Cleaned Up')
|
||||
process.exit()
|
||||
}
|
||||
}
|
||||
//attach event handlers
|
||||
s.onFFmpegLoaded(onFFmpegLoaded)
|
||||
s.onBeforeDatabaseLoad(onBeforeDatabaseLoad)
|
||||
s.onProcessReady(onProcessReady)
|
||||
s.onProcessExit(onProcessExit)
|
||||
}
|
|
@ -34,10 +34,11 @@ module.exports = function(s){
|
|||
if(config.databaseLogs === undefined){config.databaseLogs=false}
|
||||
if(config.useUTC === undefined){config.useUTC=false}
|
||||
if(config.iconURL === undefined){config.iconURL = "https://shinobi.video/libs/assets/icon/apple-touch-icon-152x152.png"}
|
||||
if(config.pipeAddition === undefined){config.pipeAddition=7}else{config.pipeAddition=parseInt(config.pipeAddition)}
|
||||
if(config.pipeAddition === undefined){config.pipeAddition=10}else{config.pipeAddition=parseInt(config.pipeAddition)}
|
||||
if(config.hideCloudSaveUrls === undefined){config.hideCloudSaveUrls = true}
|
||||
if(config.insertOrphans === undefined){config.insertOrphans = true}
|
||||
if(config.orphanedVideoCheckMax === undefined){config.orphanedVideoCheckMax = 20}
|
||||
if(config.detectorMergePamRegionTriggers === undefined){config.detectorMergePamRegionTriggers = false}
|
||||
//Child Nodes
|
||||
if(config.childNodes === undefined)config.childNodes = {};
|
||||
//enabled
|
||||
|
@ -50,6 +51,12 @@ module.exports = function(s){
|
|||
if(config.childNodes.key === undefined)config.childNodes.key = [
|
||||
'3123asdasdf1dtj1hjk23sdfaasd12asdasddfdbtnkkfgvesra3asdsd3123afdsfqw345'
|
||||
];
|
||||
|
||||
if(config.cron.key === 'change_this_to_something_very_random__just_anything_other_than_this'){
|
||||
console.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
|
||||
console.error('!! Change your cron key in your conf.json. !!')
|
||||
console.error(`!! If you're running Shinobi remotely you should do this now. !!`)
|
||||
console.error('!! You can do this in the Super User panel or from terminal. !!')
|
||||
console.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
|
134
libs/customAutoLoad.js
Normal file
134
libs/customAutoLoad.js
Normal file
|
@ -0,0 +1,134 @@
|
|||
var fs = require('fs')
|
||||
var express = require('express')
|
||||
module.exports = function(s,config,lang,app,io){
|
||||
s.customAutoLoadModules = {}
|
||||
s.customAutoLoadTree = {
|
||||
pages: [],
|
||||
PageBlocks: [],
|
||||
LibsJs: [],
|
||||
LibsCss: [],
|
||||
adminPageBlocks: [],
|
||||
adminLibsJs: [],
|
||||
adminLibsCss: [],
|
||||
superPageBlocks: [],
|
||||
superLibsJs: [],
|
||||
superLibsCss: []
|
||||
}
|
||||
var folderPath = __dirname + '/customAutoLoad'
|
||||
var search = function(searchFor,searchIn){return searchIn.indexOf(searchFor) > -1}
|
||||
fs.readdir(folderPath,function(err,folderContents){
|
||||
if(!err && folderContents){
|
||||
folderContents.forEach(function(filename){
|
||||
s.customAutoLoadModules[filename] = {}
|
||||
var customModulePath = folderPath + '/' + filename
|
||||
if(filename.indexOf('.js') > -1){
|
||||
s.customAutoLoadModules[filename].type = 'file'
|
||||
try{
|
||||
require(customModulePath)(s,config,lang,app,io)
|
||||
}catch(err){
|
||||
console.log('Failed to Load Module : ' + filename)
|
||||
console.log(err)
|
||||
}
|
||||
}else{
|
||||
if(fs.lstatSync(customModulePath).isDirectory()){
|
||||
s.customAutoLoadModules[filename].type = 'folder'
|
||||
try{
|
||||
require(customModulePath)(s,config,lang,app,io)
|
||||
fs.readdir(customModulePath,function(err,folderContents){
|
||||
folderContents.forEach(function(name){
|
||||
switch(name){
|
||||
case'web':
|
||||
var webFolder = s.checkCorrectPathEnding(customModulePath) + 'web/'
|
||||
fs.readdir(webFolder,function(err,webFolderContents){
|
||||
webFolderContents.forEach(function(name){
|
||||
switch(name){
|
||||
case'libs':
|
||||
case'pages':
|
||||
if(name === 'libs'){
|
||||
if(config.webPaths.home !== '/'){
|
||||
app.use('/libs',express.static(webFolder + '/libs'))
|
||||
}
|
||||
app.use(s.checkCorrectPathEnding(config.webPaths.home)+'libs',express.static(webFolder + '/libs'))
|
||||
app.use(s.checkCorrectPathEnding(config.webPaths.admin)+'libs',express.static(webFolder + '/libs'))
|
||||
app.use(s.checkCorrectPathEnding(config.webPaths.super)+'libs',express.static(webFolder + '/libs'))
|
||||
}
|
||||
var libFolder = webFolder + name + '/'
|
||||
fs.readdir(libFolder,function(err,webFolderContents){
|
||||
webFolderContents.forEach(function(libName){
|
||||
var thirdLevelName = libFolder + libName
|
||||
switch(libName){
|
||||
case'js':
|
||||
case'css':
|
||||
case'blocks':
|
||||
fs.readdir(thirdLevelName,function(err,webFolderContents){
|
||||
webFolderContents.forEach(function(filename){
|
||||
var fullPath = thirdLevelName + '/' + filename
|
||||
var blockPrefix = ''
|
||||
switch(true){
|
||||
case search('super.',filename):
|
||||
blockPrefix = 'super'
|
||||
break;
|
||||
case search('admin.',filename):
|
||||
blockPrefix = 'admin'
|
||||
break;
|
||||
}
|
||||
switch(libName){
|
||||
case'js':
|
||||
s.customAutoLoadTree[blockPrefix + 'LibsJs'].push(filename)
|
||||
break;
|
||||
case'css':
|
||||
s.customAutoLoadTree[blockPrefix + 'LibsCss'].push(filename)
|
||||
break;
|
||||
case'blocks':
|
||||
s.customAutoLoadTree[blockPrefix + 'PageBlocks'].push(fullPath)
|
||||
break;
|
||||
}
|
||||
})
|
||||
})
|
||||
break;
|
||||
default:
|
||||
if(libName.indexOf('.ejs') > -1){
|
||||
s.customAutoLoadTree.pages.push(thirdLevelName)
|
||||
}
|
||||
break;
|
||||
}
|
||||
})
|
||||
})
|
||||
break;
|
||||
}
|
||||
})
|
||||
})
|
||||
break;
|
||||
case'languages':
|
||||
var languagesFolder = s.checkCorrectPathEnding(customModulePath) + 'languages/'
|
||||
fs.readdir(languagesFolder,function(err,files){
|
||||
if(err)return console.log(err);
|
||||
files.forEach(function(filename){
|
||||
var fileData = require(languagesFolder + filename)
|
||||
var rule = filename.replace('.json','')
|
||||
if(config.language === rule){
|
||||
lang = Object.assign(lang,fileData)
|
||||
}
|
||||
if(s.loadedLanguages[rule]){
|
||||
s.loadedLanguages[rule] = Object.assign(s.loadedLanguages[rule],fileData)
|
||||
}else{
|
||||
s.loadedLanguages[rule] = Object.assign(s.copySystemDefaultLanguage(),fileData)
|
||||
}
|
||||
})
|
||||
})
|
||||
break;
|
||||
}
|
||||
})
|
||||
})
|
||||
}catch(err){
|
||||
console.log('Failed to Load Module : ' + filename)
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}else{
|
||||
fs.mkdirSync(folderPath)
|
||||
}
|
||||
})
|
||||
}
|
288
libs/detector.js
288
libs/detector.js
|
@ -1,6 +1,11 @@
|
|||
var P2P = require('pipe2pam');
|
||||
// Matrix In Region Libs >
|
||||
var SAT = require('sat')
|
||||
var V = SAT.Vector;
|
||||
var P = SAT.Polygon;
|
||||
// Matrix In Region Libs />
|
||||
var P2P = require('pipe2pam')
|
||||
// pamDiff is based on https://www.npmjs.com/package/pam-diff
|
||||
var PamDiff = require('./detectorPamDiff.js');
|
||||
var PamDiff = require('pam-diff')
|
||||
module.exports = function(s,config){
|
||||
s.createPamDiffEngine = function(e){
|
||||
var width,
|
||||
|
@ -46,61 +51,146 @@ module.exports = function(s,config){
|
|||
[width,height],
|
||||
[width,0]
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
e.triggerTimer = {}
|
||||
|
||||
var regions = s.createPamDiffRegionArray(regionJson,globalColorThreshold,globalSensitivity,fullFrame)
|
||||
|
||||
s.group[e.ke].mon[e.id].pamDiff = new PamDiff({
|
||||
var pamDiffOptions = {
|
||||
grayscale: 'luminosity',
|
||||
regions : regions.forPam,
|
||||
drawMatrix : e.details.detector_show_matrix
|
||||
});
|
||||
s.group[e.ke].mon[e.id].p2p = new P2P();
|
||||
var sendTrigger = function(trigger){
|
||||
var detectorObject = {
|
||||
f:'trigger',
|
||||
id:e.id,
|
||||
ke:e.ke,
|
||||
name:trigger.name,
|
||||
details:{
|
||||
plug:'built-in',
|
||||
name:trigger.name,
|
||||
reason:'motion',
|
||||
confidence:trigger.percent
|
||||
},
|
||||
plates:[],
|
||||
imgHeight:e.details.detector_scale_y,
|
||||
imgWidth:e.details.detector_scale_x
|
||||
}
|
||||
if(trigger.matrix)detectorObject.details.matrices = [trigger.matrix]
|
||||
var region = Object.values(regionJson).find(x => x.name == detectorObject.name)
|
||||
s.checkMaximumSensitivity(e, region, detectorObject, function() {
|
||||
s.checkTriggerThreshold(e, region, detectorObject, function() {
|
||||
detectorObject.doObjectDetection = (s.ocv && e.details.detector_use_detect_object === '1')
|
||||
s.triggerEvent(detectorObject)
|
||||
})
|
||||
})
|
||||
regions : regions.forPam
|
||||
}
|
||||
if(e.details.detector_noise_filter==='1'){
|
||||
if(!s.group[e.ke].mon[e.id].noiseFilterArray)s.group[e.ke].mon[e.id].noiseFilterArray = {}
|
||||
var noiseFilterArray = s.group[e.ke].mon[e.id].noiseFilterArray
|
||||
Object.keys(regions.notForPam).forEach(function(name){
|
||||
if(!noiseFilterArray[name])noiseFilterArray[name]=[];
|
||||
})
|
||||
s.group[e.ke].mon[e.id].pamDiff.on('diff', (data) => {
|
||||
data.trigger.forEach(function(trigger){
|
||||
s.filterTheNoise(e,noiseFilterArray,regions,trigger,function(){
|
||||
sendTrigger(trigger)
|
||||
if(e.details.detector_show_matrix==='1'){
|
||||
pamDiffOptions.response = 'bounds'
|
||||
}
|
||||
s.group[e.ke].mon[e.id].pamDiff = new PamDiff(pamDiffOptions);
|
||||
s.group[e.ke].mon[e.id].p2p = new P2P()
|
||||
var regionArray = Object.values(regionJson)
|
||||
if(config.detectorMergePamRegionTriggers === true){
|
||||
// merge pam triggers for performance boost
|
||||
var buildTriggerEvent = function(trigger){
|
||||
var detectorObject = {
|
||||
f:'trigger',
|
||||
id:e.id,
|
||||
ke:e.ke,
|
||||
name:trigger.name,
|
||||
details:{
|
||||
plug:'built-in',
|
||||
name:trigger.name,
|
||||
reason:'motion',
|
||||
confidence:trigger.percent
|
||||
},
|
||||
plates:[],
|
||||
imgHeight:e.details.detector_scale_y,
|
||||
imgWidth:e.details.detector_scale_x
|
||||
}
|
||||
if(trigger.merged){
|
||||
if(trigger.matrices)detectorObject.details.matrices = trigger.matrices
|
||||
var filteredCount = 0
|
||||
var filteredCountSuccess = 0
|
||||
trigger.merged.forEach(function(triggerPiece){
|
||||
var region = regionArray.find(x => x.name == triggerPiece.name)
|
||||
s.checkMaximumSensitivity(e, region, detectorObject, function(err1) {
|
||||
s.checkTriggerThreshold(e, region, detectorObject, function(err2) {
|
||||
++filteredCount
|
||||
if(!err1 && !err2)++filteredCountSuccess
|
||||
if(filteredCount === trigger.merged.length && filteredCountSuccess > 0){
|
||||
detectorObject.doObjectDetection = (s.isAtleatOneDetectorPluginConnected && e.details.detector_use_detect_object === '1')
|
||||
s.triggerEvent(detectorObject)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}else{
|
||||
if(trigger.matrix)detectorObject.details.matrices = [trigger.matrix]
|
||||
var region = regionArray.find(x => x.name == detectorObject.name)
|
||||
s.checkMaximumSensitivity(e, region, detectorObject, function(err1) {
|
||||
s.checkTriggerThreshold(e, region, detectorObject, function(err2) {
|
||||
if(!err1 && !err2){
|
||||
detectorObject.doObjectDetection = (s.isAtleatOneDetectorPluginConnected && e.details.detector_use_detect_object === '1')
|
||||
s.triggerEvent(detectorObject)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
if(e.details.detector_noise_filter==='1'){
|
||||
if(!s.group[e.ke].mon[e.id].noiseFilterArray)s.group[e.ke].mon[e.id].noiseFilterArray = {}
|
||||
var noiseFilterArray = s.group[e.ke].mon[e.id].noiseFilterArray
|
||||
Object.keys(regions.notForPam).forEach(function(name){
|
||||
if(!noiseFilterArray[name])noiseFilterArray[name]=[];
|
||||
})
|
||||
s.group[e.ke].mon[e.id].pamDiff.on('diff', (data) => {
|
||||
var filteredCount = 0
|
||||
var filteredCountSuccess = 0
|
||||
data.trigger.forEach(function(trigger){
|
||||
s.filterTheNoise(e,noiseFilterArray,regions,trigger,function(err){
|
||||
++filteredCount
|
||||
if(!err)++filteredCountSuccess
|
||||
if(filteredCount === data.trigger.length && filteredCountSuccess > 0){
|
||||
buildTriggerEvent(s.mergePamTriggers(data))
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}else{
|
||||
s.group[e.ke].mon[e.id].pamDiff.on('diff', (data) => {
|
||||
buildTriggerEvent(s.mergePamTriggers(data))
|
||||
})
|
||||
}
|
||||
}else{
|
||||
s.group[e.ke].mon[e.id].pamDiff.on('diff', (data) => {
|
||||
data.trigger.forEach(sendTrigger)
|
||||
})
|
||||
//config.detectorMergePamRegionTriggers NOT true
|
||||
//original behaviour, all regions have their own event.
|
||||
var buildTriggerEvent = function(trigger){
|
||||
var detectorObject = {
|
||||
f:'trigger',
|
||||
id:e.id,
|
||||
ke:e.ke,
|
||||
name:trigger.name,
|
||||
details:{
|
||||
plug:'built-in',
|
||||
name:trigger.name,
|
||||
reason:'motion',
|
||||
confidence:trigger.percent
|
||||
},
|
||||
plates:[],
|
||||
imgHeight:e.details.detector_scale_y,
|
||||
imgWidth:e.details.detector_scale_x
|
||||
}
|
||||
if(trigger.matrix)detectorObject.details.matrices = [trigger.matrix]
|
||||
var region = Object.values(regionJson).find(x => x.name == detectorObject.name)
|
||||
s.checkMaximumSensitivity(e, region, detectorObject, function(err1) {
|
||||
s.checkTriggerThreshold(e, region, detectorObject, function(err2) {
|
||||
if(!err1 && ! err2){
|
||||
detectorObject.doObjectDetection = (s.isAtleatOneDetectorPluginConnected && e.details.detector_use_detect_object === '1')
|
||||
s.triggerEvent(detectorObject)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
if(e.details.detector_noise_filter==='1'){
|
||||
if(!s.group[e.ke].mon[e.id].noiseFilterArray)s.group[e.ke].mon[e.id].noiseFilterArray = {}
|
||||
var noiseFilterArray = s.group[e.ke].mon[e.id].noiseFilterArray
|
||||
Object.keys(regions.notForPam).forEach(function(name){
|
||||
if(!noiseFilterArray[name])noiseFilterArray[name]=[];
|
||||
})
|
||||
s.group[e.ke].mon[e.id].pamDiff.on('diff', (data) => {
|
||||
data.trigger.forEach(function(trigger){
|
||||
s.filterTheNoise(e,noiseFilterArray,regions,trigger,function(){
|
||||
s.createMatrixFromPamTrigger(trigger)
|
||||
buildTriggerEvent(trigger)
|
||||
})
|
||||
})
|
||||
})
|
||||
}else{
|
||||
s.group[e.ke].mon[e.id].pamDiff.on('diff', (data) => {
|
||||
data.trigger.forEach(function(trigger){
|
||||
s.createMatrixFromPamTrigger(trigger)
|
||||
buildTriggerEvent(trigger)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,17 +254,20 @@ module.exports = function(s,config){
|
|||
theNoise = theNoise / noiseFilterArray[trigger.name].length;
|
||||
var triggerPercentWithoutNoise = trigger.percent - theNoise;
|
||||
if(triggerPercentWithoutNoise > regions.notForPam[trigger.name].sensitivity){
|
||||
callback(trigger)
|
||||
callback(null,trigger)
|
||||
}else{
|
||||
callback(true)
|
||||
}
|
||||
}
|
||||
|
||||
s.checkMaximumSensitivity = function(monitor, region, detectorObject, success) {
|
||||
s.checkMaximumSensitivity = function(monitor, region, detectorObject, callback) {
|
||||
var logName = detectorObject.id + ':' + detectorObject.name
|
||||
var globalMaxSensitivity = parseInt(monitor.details.detector_max_sensitivity) || undefined
|
||||
var maxSensitivity = parseInt(region.max_sensitivity) || globalMaxSensitivity
|
||||
if (maxSensitivity === undefined || detectorObject.details.confidence <= maxSensitivity) {
|
||||
success()
|
||||
callback(null)
|
||||
} else {
|
||||
callback(true)
|
||||
if (monitor.triggerTimer[detectorObject.name] !== undefined) {
|
||||
clearTimeout(monitor.triggerTimer[detectorObject.name].timeout)
|
||||
monitor.triggerTimer[detectorObject.name] = undefined
|
||||
|
@ -182,10 +275,10 @@ module.exports = function(s,config){
|
|||
}
|
||||
}
|
||||
|
||||
s.checkTriggerThreshold = function(monitor, region, detectorObject, success){
|
||||
s.checkTriggerThreshold = function(monitor, region, detectorObject, callback){
|
||||
var threshold = parseInt(region.threshold) || globalThreshold
|
||||
if (threshold <= 1) {
|
||||
success()
|
||||
callback(null)
|
||||
} else {
|
||||
if (monitor.triggerTimer[detectorObject.name] === undefined) {
|
||||
monitor.triggerTimer[detectorObject.name] = {
|
||||
|
@ -194,10 +287,11 @@ module.exports = function(s,config){
|
|||
}
|
||||
}
|
||||
if (--monitor.triggerTimer[detectorObject.name].count == 0) {
|
||||
success()
|
||||
callback(null)
|
||||
clearTimeout(monitor.triggerTimer[detectorObject.name].timeout)
|
||||
monitor.triggerTimer[detectorObject.name] = undefined
|
||||
} else {
|
||||
callback(true)
|
||||
var fps = parseFloat(monitor.details.detector_fps) || 2
|
||||
if (monitor.triggerTimer[detectorObject.name].timeout !== null)
|
||||
clearTimeout(monitor.triggerTimer[detectorObject.name].timeout)
|
||||
|
@ -207,4 +301,92 @@ module.exports = function(s,config){
|
|||
}
|
||||
}
|
||||
}
|
||||
s.mergePamTriggers = function(data){
|
||||
if(data.trigger.length > 1){
|
||||
var n = 0
|
||||
var sum = 0
|
||||
var name = []
|
||||
var matrices = []
|
||||
data.trigger.forEach(function(trigger){
|
||||
name.push(trigger.name + ' ('+trigger.percent+'%)')
|
||||
++n
|
||||
sum += trigger.percent
|
||||
s.createMatrixFromPamTrigger(trigger)
|
||||
if(trigger.matrix)matrices.push(trigger.matrix)
|
||||
})
|
||||
var average = sum / n
|
||||
name = name.join(', ')
|
||||
if(matrices.length === 0)matrices = null
|
||||
var trigger = {
|
||||
name: name,
|
||||
percent: parseInt(average),
|
||||
matrices: matrices,
|
||||
merged: data.trigger
|
||||
}
|
||||
}else{
|
||||
var trigger = data.trigger[0]
|
||||
s.createMatrixFromPamTrigger(trigger)
|
||||
trigger.matrices = [trigger.matrix]
|
||||
}
|
||||
return trigger
|
||||
}
|
||||
s.isAtleastOneMatrixInRegion = function(regions,matrices,callback){
|
||||
var regionPolys = []
|
||||
var matrixPoints = []
|
||||
regions.forEach(function(region,n){
|
||||
var polyPoints = []
|
||||
region.points.forEach(function(point){
|
||||
polyPoints.push(new V(parseInt(point[0]),parseInt(point[1])))
|
||||
})
|
||||
regionPolys[n] = new P(new V(0,0), polyPoints)
|
||||
})
|
||||
var collisions = []
|
||||
var foundInRegion = false
|
||||
matrices.forEach(function(matrix){
|
||||
var matrixPoints = [
|
||||
new V(matrix.x,matrix.y),
|
||||
new V(matrix.width,matrix.y),
|
||||
new V(matrix.width,matrix.height),
|
||||
new V(matrix.x,matrix.height)
|
||||
]
|
||||
var matrixPoly = new P(new V(0,0), matrixPoints)
|
||||
regionPolys.forEach(function(region,n){
|
||||
var response = new SAT.Response()
|
||||
var collided = SAT.testPolygonPolygon(matrixPoly, region, response)
|
||||
if(collided === true){
|
||||
collisions.push({
|
||||
matrix: matrix,
|
||||
region: regions[n]
|
||||
})
|
||||
foundInRegion = true
|
||||
}
|
||||
})
|
||||
})
|
||||
if(callback)callback(foundInRegion,collisions)
|
||||
return foundInRegion
|
||||
}
|
||||
s.createMatrixFromPamTrigger = function(trigger){
|
||||
if(
|
||||
trigger.minX &&
|
||||
trigger.maxX &&
|
||||
trigger.minY &&
|
||||
trigger.maxY
|
||||
){
|
||||
var coordinates = [
|
||||
{"x" : trigger.minX, "y" : trigger.minY},
|
||||
{"x" : trigger.maxX, "y" : trigger.minY},
|
||||
{"x" : trigger.maxX, "y" : trigger.maxY}
|
||||
]
|
||||
var width = Math.sqrt( Math.pow(coordinates[1].x - coordinates[0].x, 2) + Math.pow(coordinates[1].y - coordinates[0].y, 2));
|
||||
var height = Math.sqrt( Math.pow(coordinates[2].x - coordinates[1].x, 2) + Math.pow(coordinates[2].y - coordinates[1].y, 2))
|
||||
trigger.matrix = {
|
||||
x: coordinates[0].x,
|
||||
y: coordinates[0].y,
|
||||
width: width,
|
||||
height: height,
|
||||
tag: trigger.name
|
||||
}
|
||||
}
|
||||
return trigger
|
||||
}
|
||||
}
|
||||
|
|
208
libs/dropInEvents.js
Normal file
208
libs/dropInEvents.js
Normal file
|
@ -0,0 +1,208 @@
|
|||
var fs = require('fs')
|
||||
var execSync = require('child_process').execSync
|
||||
module.exports = function(s,config,lang,app,io){
|
||||
if(config.dropInEventServer === true){
|
||||
if(config.dropInEventDeleteFileAfterTrigger === undefined)config.dropInEventDeleteFileAfterTrigger = true
|
||||
var authenticateUser = function(username,password,callback){
|
||||
var splitUsername = username.split('@')
|
||||
if(splitUsername[1] !== 'Shinobi' && splitUsername[1] !== 'shinobi'){
|
||||
s.sqlQuery('SELECT ke,uid FROM Users WHERE mail=? AND (pass=? OR pass=?)',[
|
||||
username,
|
||||
password,
|
||||
s.createHash(password)
|
||||
],function(err,r){
|
||||
var user
|
||||
if(r && r[0]){
|
||||
user = r[0]
|
||||
}
|
||||
callback(err,user)
|
||||
})
|
||||
}else{
|
||||
s.sqlQuery('SELECT ke,uid FROM API WHERE code=? AND ke=?',[
|
||||
splitUsername[0], //code
|
||||
password //ke
|
||||
],function(err,r){
|
||||
var apiKey
|
||||
if(r && r[0]){
|
||||
apiKey = r[0]
|
||||
}
|
||||
callback(err,apiKey)
|
||||
})
|
||||
}
|
||||
}
|
||||
var beforeMonitorsLoadedOnStartup = function(){
|
||||
if(!config.dropInEventsDir){
|
||||
config.dropInEventsDir = s.dir.streams + 'dropInEvents/'
|
||||
}
|
||||
s.dir.dropInEvents = s.checkCorrectPathEnding(config.dropInEventsDir)
|
||||
//dropInEvents dir
|
||||
if(!fs.existsSync(s.dir.dropInEvents)){
|
||||
fs.mkdirSync(s.dir.dropInEvents)
|
||||
}
|
||||
}
|
||||
var getDropInEventDir = function(monitorConfig){
|
||||
var ke = monitorConfig.ke
|
||||
var mid = monitorConfig.mid
|
||||
var groupEventDropDir = s.dir.dropInEvents + ke
|
||||
var monitorEventDropDir = groupEventDropDir + '/' + mid + '/'
|
||||
return monitorEventDropDir
|
||||
}
|
||||
var onMonitorStop = function(monitorConfig){
|
||||
var ke = monitorConfig.ke
|
||||
var mid = monitorConfig.mid
|
||||
if(s.group[monitorConfig.ke].mon[monitorConfig.mid].dropInEventWatcher){
|
||||
s.group[monitorConfig.ke].mon[monitorConfig.mid].dropInEventWatcher.close()
|
||||
delete(s.group[monitorConfig.ke].mon[monitorConfig.mid].dropInEventWatcher)
|
||||
}
|
||||
var monitorEventDropDir = getDropInEventDir(monitorConfig)
|
||||
if(fs.existsSync(monitorEventDropDir))execSync('rm -rf ' + monitorEventDropDir)
|
||||
}
|
||||
var onMonitorInit = function(monitorConfig){
|
||||
onMonitorStop(monitorConfig)
|
||||
var ke = monitorConfig.ke
|
||||
var mid = monitorConfig.mid
|
||||
var monitorEventDropDir = getDropInEventDir(monitorConfig)
|
||||
var groupEventDropDir = s.dir.dropInEvents + ke
|
||||
if(!fs.existsSync(groupEventDropDir)){
|
||||
fs.mkdirSync(groupEventDropDir)
|
||||
}
|
||||
var monitorEventDropDir = groupEventDropDir + '/' + mid + '/'
|
||||
if(!fs.existsSync(monitorEventDropDir)){
|
||||
fs.mkdirSync(monitorEventDropDir)
|
||||
}
|
||||
var fileQueue = {}
|
||||
s.group[monitorConfig.ke].mon[monitorConfig.mid].dropInEventFileQueue = fileQueue
|
||||
var eventTrigger = function(eventType,filename){
|
||||
var filePath = monitorEventDropDir + filename
|
||||
if(filename.indexOf('.jpg') > -1 || filename.indexOf('.jpeg') > -1){
|
||||
var snapPath = s.dir.streams + ke + '/' + mid + '/s.jpg'
|
||||
fs.unlink(snapPath,function(err){
|
||||
fs.createReadStream(filePath).pipe(fs.createWriteStream(snapPath))
|
||||
s.triggerEvent({
|
||||
id: mid,
|
||||
ke: ke,
|
||||
details: {
|
||||
confidence: 100,
|
||||
name: filename,
|
||||
plug: "dropInEvent",
|
||||
reason: "dropInEvent"
|
||||
}
|
||||
})
|
||||
})
|
||||
}else{
|
||||
s.triggerEvent({
|
||||
id: mid,
|
||||
ke: ke,
|
||||
details: {
|
||||
confidence: 100,
|
||||
name: filename,
|
||||
plug: "dropInEvent",
|
||||
reason: "ftpServer"
|
||||
}
|
||||
})
|
||||
}
|
||||
if(config.dropInEventDeleteFileAfterTrigger){
|
||||
setTimeout(function(){
|
||||
fs.unlink(filePath,function(err){
|
||||
|
||||
})
|
||||
},1000 * 60 * 5)
|
||||
}
|
||||
}
|
||||
var directoryWatch = fs.watch(monitorEventDropDir,function(eventType,filename){
|
||||
if(fs.existsSync(monitorEventDropDir + filename)){
|
||||
clearTimeout(fileQueue[filename])
|
||||
fileQueue[filename] = setTimeout(function(){
|
||||
eventTrigger(eventType,filename)
|
||||
},1200)
|
||||
}
|
||||
})
|
||||
s.group[monitorConfig.ke].mon[monitorConfig.mid].dropInEventWatcher = directoryWatch
|
||||
}
|
||||
// FTP Server
|
||||
if(config.ftpServer === true){
|
||||
if(!config.ftpServerPort)config.ftpServerPort = 21
|
||||
if(!config.ftpServerUrl)config.ftpServerUrl = `ftp://0.0.0.0:${config.ftpServerPort}`
|
||||
config.ftpServerUrl = config.ftpServerUrl.replace('{{PORT}}',config.ftpServerPort)
|
||||
const FtpSrv = require('ftp-srv')
|
||||
const ftpServer = new FtpSrv({
|
||||
url: config.ftpServerUrl,
|
||||
// log:{trace:function(){},error:function(){},child:function(){},info:function(){},warn:function(){}
|
||||
})
|
||||
|
||||
ftpServer.on('login', (data, resolve, reject) => {
|
||||
var username = data.username
|
||||
var password = data.password
|
||||
authenticateUser(username,password,function(err,user){
|
||||
if(user){
|
||||
resolve({root: s.dir.dropInEvents + user.ke})
|
||||
}else{
|
||||
// reject(new Error('Failed Authorization'))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
ftpServer.listen().then(() => {
|
||||
s.systemLog(`FTP Server running on port ${config.ftpServerPort}...`)
|
||||
}).catch(function(err){
|
||||
s.systemLog(err)
|
||||
})
|
||||
}
|
||||
//add extensions
|
||||
s.beforeMonitorsLoadedOnStartup(beforeMonitorsLoadedOnStartup)
|
||||
s.onMonitorInit(onMonitorInit)
|
||||
s.onMonitorStop(onMonitorStop)
|
||||
}
|
||||
// SMTP Server
|
||||
// allow starting SMTP server without dropInEventServer
|
||||
if(config.smtpServer === true){
|
||||
var SMTPServer = require("smtp-server").SMTPServer;
|
||||
if(!config.smtpServerPort && (config.smtpServerSsl && config.smtpServerSsl.enabled !== false || config.ssl)){config.smtpServerPort = 465}else if(!config.smtpServerPort){config.smtpServerPort = 25}
|
||||
var smtpOptions = {
|
||||
onAuth(auth, session, callback) {
|
||||
var username = auth.username
|
||||
var password = auth.password
|
||||
authenticateUser(username,password,function(err,user){
|
||||
if(user){
|
||||
callback(null, {user: user.ke})
|
||||
}else{
|
||||
callback(new Error(lang.failedLoginText2))
|
||||
}
|
||||
})
|
||||
},
|
||||
onRcptTo(address, session, callback) {
|
||||
var split = address.address.split('@')
|
||||
var monitorId = split[0]
|
||||
var ke = session.user
|
||||
if(s.group[ke].mon_conf[monitorId] && s.group[ke].mon[monitorId].isStarted === true){
|
||||
s.triggerEvent({
|
||||
id: monitorId,
|
||||
ke: ke,
|
||||
details: {
|
||||
confidence: 100,
|
||||
name: address.address,
|
||||
plug: "dropInEvent",
|
||||
reason: "smtpServer"
|
||||
}
|
||||
})
|
||||
}else{
|
||||
return callback(new Error(lang['No Monitor Exists with this ID.']))
|
||||
}
|
||||
callback()
|
||||
}
|
||||
}
|
||||
if(config.smtpServerSsl && config.smtpServerSsl.enabled !== false || config.ssl && config.ssl.cert && config.ssl.key){
|
||||
var key = config.ssl.key || fs.readFileSync(config.smtpServerSsl.key)
|
||||
var cert = config.ssl.cert || fs.readFileSync(config.smtpServerSsl.cert)
|
||||
smtpOptions = Object.assign(smtpOptions,{
|
||||
secure: true,
|
||||
key: config.ssl.key,
|
||||
cert: config.ssl.cert
|
||||
})
|
||||
}
|
||||
var server = new SMTPServer(smtpOptions)
|
||||
server.listen(config.smtpServerPort,function(){
|
||||
s.systemLog(`SMTP Server running on port ${config.smtpServerPort}...`)
|
||||
})
|
||||
}
|
||||
}
|
130
libs/events.js
130
libs/events.js
|
@ -4,6 +4,25 @@ var exec = require('child_process').exec;
|
|||
var spawn = require('child_process').spawn;
|
||||
var request = require('request');
|
||||
module.exports = function(s,config,lang){
|
||||
var addEventDetailsToString = function(eventData,string,addOps){
|
||||
//d = event data
|
||||
if(!addOps)addOps = {}
|
||||
var newString = string + ''
|
||||
var d = Object.assign(eventData,addOps)
|
||||
var detailString = s.stringJSON(d.details)
|
||||
newString = newString
|
||||
.replace(/{{TIME}}/g,d.currentTimestamp)
|
||||
.replace(/{{REGION_NAME}}/g,d.details.name)
|
||||
.replace(/{{SNAP_PATH}}/g,s.dir.streams+'/'+d.ke+'/'+d.id+'/s.jpg')
|
||||
.replace(/{{MONITOR_ID}}/g,d.id)
|
||||
.replace(/{{GROUP_KEY}}/g,d.ke)
|
||||
.replace(/{{DETAILS}}/g,detailString)
|
||||
if(d.details.confidence){
|
||||
newString = newString
|
||||
.replace(/{{CONFIDENCE}}/g,d.details.confidence)
|
||||
}
|
||||
return newString
|
||||
}
|
||||
s.filterEvents = function(x,d){
|
||||
switch(x){
|
||||
case'archive':
|
||||
|
@ -36,15 +55,13 @@ module.exports = function(s,config,lang){
|
|||
s.onEventTriggerBeforeFilterExtensions.forEach(function(extender){
|
||||
extender(d,filter)
|
||||
})
|
||||
if(s.group[d.ke].mon[d.id].open){
|
||||
d.details.videoTime = s.group[d.ke].mon[d.id].open;
|
||||
}
|
||||
var detailString = JSON.stringify(d.details);
|
||||
if(!s.group[d.ke]||!s.group[d.ke].mon[d.id]){
|
||||
return s.systemLog(lang['No Monitor Found, Ignoring Request'])
|
||||
}
|
||||
d.mon=s.group[d.ke].mon_conf[d.id];
|
||||
var currentConfig = s.group[d.ke].mon[d.id].details
|
||||
var hasMatrices = (d.details.matrices && d.details.matrices.length > 0)
|
||||
//read filters
|
||||
if(
|
||||
currentConfig.use_detector_filters === '1' &&
|
||||
|
@ -160,7 +177,7 @@ module.exports = function(s,config,lang){
|
|||
})
|
||||
if(d.details.matrices && d.details.matrices.length === 0 || filter.halt === true){
|
||||
return
|
||||
}else if(d.details.matrices && d.details.matrices.length > 0){
|
||||
}else if(hasMatrices){
|
||||
var reviewedMatrix = []
|
||||
d.details.matrices.forEach(function(matrix){
|
||||
if(matrix)reviewedMatrix.push(matrix)
|
||||
|
@ -193,6 +210,16 @@ module.exports = function(s,config,lang){
|
|||
return
|
||||
}
|
||||
}
|
||||
// check if object should be in region
|
||||
if(hasMatrices && currentConfig.detector_obj_region === '1'){
|
||||
var regions = s.group[d.ke].mon[d.id].parsedObjects.cords
|
||||
var isMatrixInRegions = s.isAtleastOneMatrixInRegion(regions,d.details.matrices)
|
||||
if(isMatrixInRegions){
|
||||
s.debugLog('Matrix in region!')
|
||||
}else{
|
||||
return
|
||||
}
|
||||
}
|
||||
// check modified indifference
|
||||
if(filter.indifference !== false && d.details.confidence < parseFloat(filter.indifference)){
|
||||
// fails indifference check for modified indifference
|
||||
|
@ -209,13 +236,29 @@ module.exports = function(s,config,lang){
|
|||
frame : s.group[d.ke].mon[d.id].lastJpegDetectorFrame
|
||||
})
|
||||
}else{
|
||||
if(currentConfig.detector_multi_trigger === '1'){
|
||||
s.getCamerasForMultiTrigger(d.mon).forEach(function(monitor){
|
||||
if(monitor.mid !== d.id){
|
||||
s.triggerEvent({
|
||||
id: monitor.mid,
|
||||
ke: monitor.ke,
|
||||
details: {
|
||||
confidence: 100,
|
||||
name: "multiTrigger",
|
||||
plug: d.details.plug,
|
||||
reason: d.details.reason
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
//save this detection result in SQL, only coords. not image.
|
||||
if(filter.save && currentConfig.detector_save==='1'){
|
||||
s.sqlQuery('INSERT INTO Events (ke,mid,details) VALUES (?,?,?)',[d.ke,d.id,detailString])
|
||||
if(filter.save && currentConfig.detector_save === '1'){
|
||||
s.sqlQuery('INSERT INTO Events (ke,mid,details,time) VALUES (?,?,?,?)',[d.ke,d.id,detailString,new Date()])
|
||||
}
|
||||
if(currentConfig.detector_notrigger === '1'){
|
||||
var detector_notrigger_timeout
|
||||
if(!currentConfig.detector_notrigger_timeout||currentConfig.detector_notrigger_timeout===''){
|
||||
if(!currentConfig.detector_notrigger_timeout||currentConfig.detector_notrigger_timeout === ''){
|
||||
detector_notrigger_timeout = 10
|
||||
}
|
||||
detector_notrigger_timeout = parseFloat(currentConfig.detector_notrigger_timeout)*1000*60;
|
||||
|
@ -270,17 +313,7 @@ module.exports = function(s,config,lang){
|
|||
})
|
||||
|
||||
if(filter.webhook && currentConfig.detector_webhook === '1'){
|
||||
var detector_webhook_url = currentConfig.detector_webhook_url
|
||||
.replace(/{{TIME}}/g,d.currentTimestamp)
|
||||
.replace(/{{REGION_NAME}}/g,d.details.name)
|
||||
.replace(/{{SNAP_PATH}}/g,s.dir.streams+'/'+d.ke+'/'+d.id+'/s.jpg')
|
||||
.replace(/{{MONITOR_ID}}/g,d.id)
|
||||
.replace(/{{GROUP_KEY}}/g,d.ke)
|
||||
.replace(/{{DETAILS}}/g,detailString)
|
||||
if(d.details.confidence){
|
||||
detector_webhook_url = detector_webhook_url
|
||||
.replace(/{{CONFIDENCE}}/g,d.details.confidence)
|
||||
}
|
||||
var detector_webhook_url = addEventDetailsToString(d,currentConfig.detector_webhook_url)
|
||||
request({url:detector_webhook_url,method:'GET',encoding:null},function(err,data){
|
||||
if(err){
|
||||
s.userLog(d,{type:lang["Event Webhook Error"],msg:{error:err,data:data}})
|
||||
|
@ -289,28 +322,8 @@ module.exports = function(s,config,lang){
|
|||
}
|
||||
|
||||
if(filter.command && currentConfig.detector_command_enable === '1' && !s.group[d.ke].mon[d.id].detector_command){
|
||||
var detector_command_timeout
|
||||
if(!currentConfig.detector_command_timeout||currentConfig.detector_command_timeout===''){
|
||||
detector_command_timeout = 1000*60*10;
|
||||
}else{
|
||||
detector_command_timeout = parseFloat(currentConfig.detector_command_timeout)*1000*60;
|
||||
}
|
||||
s.group[d.ke].mon[d.id].detector_command=setTimeout(function(){
|
||||
clearTimeout(s.group[d.ke].mon[d.id].detector_command);
|
||||
delete(s.group[d.ke].mon[d.id].detector_command);
|
||||
|
||||
},detector_command_timeout);
|
||||
var detector_command = currentConfig.detector_command
|
||||
.replace(/{{TIME}}/g,d.currentTimestamp)
|
||||
.replace(/{{REGION_NAME}}/g,d.details.name)
|
||||
.replace(/{{SNAP_PATH}}/g,s.dir.streams+'/'+d.ke+'/'+d.id+'/s.jpg')
|
||||
.replace(/{{MONITOR_ID}}/g,d.id)
|
||||
.replace(/{{GROUP_KEY}}/g,d.ke)
|
||||
.replace(/{{DETAILS}}/g,detailString)
|
||||
if(d.details.confidence){
|
||||
detector_command = detector_command
|
||||
.replace(/{{CONFIDENCE}}/g,d.details.confidence)
|
||||
}
|
||||
s.createTimeout(s.group[d.ke].mon[d.id].detector_command,currentConfig.detector_command_timeout,10)
|
||||
var detector_command = addEventDetailsToString(d,currentConfig.detector_command)
|
||||
exec(detector_command,{detached: true})
|
||||
}
|
||||
}
|
||||
|
@ -319,14 +332,18 @@ module.exports = function(s,config,lang){
|
|||
s.tx(d.cx,'DETECTOR_'+d.ke+d.id);
|
||||
}
|
||||
s.createEventBasedRecording = function(d){
|
||||
d.mon = s.group[d.ke].mon_conf[d.id]
|
||||
var currentConfig = s.group[d.ke].mon[d.id].details
|
||||
if(currentConfig.detector !== '1'){
|
||||
return
|
||||
}
|
||||
var detector_timeout
|
||||
if(!currentConfig.detector_timeout||currentConfig.detector_timeout===''){
|
||||
detector_timeout = 10
|
||||
}else{
|
||||
detector_timeout = parseFloat(currentConfig.detector_timeout)
|
||||
}
|
||||
if(currentConfig.watchdog_reset !== '1' || !s.group[d.ke].mon[d.id].eventBasedRecording.timeout){
|
||||
if(currentConfig.watchdog_reset === '1' || !s.group[d.ke].mon[d.id].eventBasedRecording.timeout){
|
||||
clearTimeout(s.group[d.ke].mon[d.id].eventBasedRecording.timeout)
|
||||
s.group[d.ke].mon[d.id].eventBasedRecording.timeout = setTimeout(function(){
|
||||
s.group[d.ke].mon[d.id].eventBasedRecording.allowEnd = true
|
||||
|
@ -336,40 +353,28 @@ module.exports = function(s,config,lang){
|
|||
},detector_timeout * 1000 * 60)
|
||||
}
|
||||
if(!s.group[d.ke].mon[d.id].eventBasedRecording.process){
|
||||
if(!d.auth){
|
||||
d.auth = s.gid(60)
|
||||
}
|
||||
if(!s.api[d.auth]){
|
||||
s.api[d.auth] = {
|
||||
system: 1,
|
||||
ip: '0.0.0.0',
|
||||
details: {},
|
||||
lang: lang
|
||||
}
|
||||
}
|
||||
s.group[d.ke].mon[d.id].eventBasedRecording.allowEnd = false;
|
||||
var runRecord = function(){
|
||||
var filename = s.formattedTime()+'.mp4'
|
||||
s.userLog(d,{type:"Traditional Recording",msg:"Started"})
|
||||
s.userLog(d,{type:lang["Traditional Recording"],msg:lang["Started"]})
|
||||
//-t 00:'+s.timeObject(new Date(detector_timeout * 1000 * 60)).format('mm:ss')+'
|
||||
s.group[d.ke].mon[d.id].eventBasedRecording.process = spawn(config.ffmpegDir,s.splitForFFPMEG(('-loglevel warning -analyzeduration 1000000 -probesize 1000000 -re -i http://'+config.ip+':'+config.port+'/'+d.auth+'/hls/'+d.ke+'/'+d.id+'/detectorStream.m3u8 -c:v copy -strftime 1 "'+s.getVideoDirectory(d.mon) + filename + '"')))
|
||||
s.group[d.ke].mon[d.id].eventBasedRecording.process = spawn(config.ffmpegDir,s.splitForFFPMEG(('-loglevel warning -analyzeduration 1000000 -probesize 1000000 -re -i "'+s.dir.streams+'/'+d.ke+'/'+d.id+'/detectorStream.m3u8" -c:v copy -strftime 1 "'+s.getVideoDirectory(d.mon) + filename + '"')))
|
||||
var ffmpegError='';
|
||||
var error
|
||||
s.group[d.ke].mon[d.id].eventBasedRecording.process.stderr.on('data',function(data){
|
||||
s.userLog(d,{type:"Traditional Recording",msg:data.toString()})
|
||||
s.userLog(d,{type:lang["Traditional Recording"],msg:data.toString()})
|
||||
})
|
||||
s.group[d.ke].mon[d.id].eventBasedRecording.process.on('close',function(){
|
||||
if(!s.group[d.ke].mon[d.id].eventBasedRecording.allowEnd){
|
||||
s.userLog(d,{type:"Traditional Recording",msg:"Detector Recording Process Exited Prematurely. Restarting."})
|
||||
s.userLog(d,{type:lang["Traditional Recording"],msg:lang["Detector Recording Process Exited Prematurely. Restarting."]})
|
||||
runRecord()
|
||||
return
|
||||
}
|
||||
s.insertCompletedVideo(d.mon,{
|
||||
file : filename
|
||||
})
|
||||
s.userLog(d,{type:"Traditional Recording",msg:"Detector Recording Complete"})
|
||||
delete(s.api[d.auth])
|
||||
s.userLog(d,{type:"Traditional Recording",msg:'Clear Recorder Process'})
|
||||
s.userLog(d,{type:lang["Traditional Recording"],msg:lang["Detector Recording Complete"]})
|
||||
s.userLog(d,{type:lang["Traditional Recording"],msg:lang["Clear Recorder Process"]})
|
||||
delete(s.group[d.ke].mon[d.id].eventBasedRecording.process)
|
||||
clearTimeout(s.group[d.ke].mon[d.id].eventBasedRecording.timeout)
|
||||
delete(s.group[d.ke].mon[d.id].eventBasedRecording.timeout)
|
||||
|
@ -385,5 +390,12 @@ module.exports = function(s,config,lang){
|
|||
s.group[e.ke].mon[e.id].eventBasedRecording.allowEnd = true;
|
||||
s.group[e.ke].mon[e.id].eventBasedRecording.process.kill('SIGTERM');
|
||||
}
|
||||
// var stackedProcesses = s.group[e.ke].mon[e.id].eventBasedRecording.stackable
|
||||
// Object.keys(stackedProcesses).forEach(function(key){
|
||||
// var item = stackedProcesses[key]
|
||||
// clearTimeout(item.timeout)
|
||||
// item.allowEnd = true;
|
||||
// item.process.kill('SIGTERM');
|
||||
// })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,15 @@
|
|||
module.exports = function(s,config){
|
||||
////// USER //////
|
||||
s.onSocketAuthenticationExtensions = []
|
||||
s.onSocketAuthentication = function(callback){
|
||||
s.onSocketAuthenticationExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.loadGroupExtensions = []
|
||||
s.loadGroupExtender = function(callback){
|
||||
s.loadGroupExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.loadGroupAppExtensions = []
|
||||
s.loadGroupAppExtender = function(callback){
|
||||
s.loadGroupAppExtensions.push(callback)
|
||||
|
@ -30,6 +40,11 @@ module.exports = function(s,config){
|
|||
s.onTwoFactorAuthCodeNotificationExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onStalePurgeLockExtensions = []
|
||||
s.onStalePurgeLock = function(callback){
|
||||
s.onStalePurgeLockExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.cloudDiskUseStartupExtensions = {}
|
||||
|
||||
////// EVENTS //////
|
||||
|
@ -51,8 +66,81 @@ module.exports = function(s,config){
|
|||
s.onMonitorInit = function(callback){
|
||||
s.onMonitorInitExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onMonitorStartExtensions = []
|
||||
s.onMonitorStart = function(callback){
|
||||
s.onMonitorStartExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onMonitorStopExtensions = []
|
||||
s.onMonitorStop = function(callback){
|
||||
s.onMonitorStopExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onMonitorSaveExtensions = []
|
||||
s.onMonitorSave = function(callback){
|
||||
s.onMonitorSaveExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onMonitorUnexpectedExitExtensions = []
|
||||
s.onMonitorUnexpectedExit = function(callback){
|
||||
s.onMonitorUnexpectedExitExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onDetectorNoTriggerTimeoutExtensions = []
|
||||
s.onDetectorNoTriggerTimeout = function(callback){
|
||||
s.onDetectorNoTriggerTimeoutExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onFfmpegCameraStringCreationExtensions = []
|
||||
s.onFfmpegCameraStringCreation = function(callback){
|
||||
s.onFfmpegCameraStringCreationExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onMonitorPingFailedExtensions = []
|
||||
s.onMonitorPingFailed = function(callback){
|
||||
s.onMonitorPingFailedExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onMonitorDiedExtensions = []
|
||||
s.onMonitorDied = function(callback){
|
||||
s.onMonitorDiedExtensions.push(callback)
|
||||
}
|
||||
|
||||
///////// SYSTEM ////////
|
||||
s.onProcessReadyExtensions = []
|
||||
s.onProcessReady = function(callback){
|
||||
s.onProcessReadyExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onProcessExitExtensions = []
|
||||
s.onProcessExit = function(callback){
|
||||
s.onProcessExitExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onBeforeDatabaseLoadExtensions = []
|
||||
s.onBeforeDatabaseLoad = function(callback){
|
||||
s.onBeforeDatabaseLoadExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onFFmpegLoadedExtensions = []
|
||||
s.onFFmpegLoaded = function(callback){
|
||||
s.onFFmpegLoadedExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.beforeMonitorsLoadedOnStartupExtensions = []
|
||||
s.beforeMonitorsLoadedOnStartup = function(callback){
|
||||
s.beforeMonitorsLoadedOnStartupExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onWebSocketConnectionExtensions = []
|
||||
s.onWebSocketConnection = function(callback){
|
||||
s.onWebSocketConnectionExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onWebSocketDisconnectionExtensions = []
|
||||
s.onWebSocketDisconnection = function(callback){
|
||||
s.onWebSocketDisconnectionExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
}
|
||||
|
|
108
libs/ffmpeg.js
108
libs/ffmpeg.js
|
@ -104,6 +104,9 @@ module.exports = function(s,config,onFinish){
|
|||
ffmpeg.completeCheck = function(){
|
||||
ffmpeg.checkVersion(function(){
|
||||
ffmpeg.checkHwAccelMethods(function(){
|
||||
s.onFFmpegLoadedExtensions.forEach(function(extender){
|
||||
extender(ffmpeg)
|
||||
})
|
||||
onFinish(ffmpeg)
|
||||
})
|
||||
})
|
||||
|
@ -132,7 +135,9 @@ module.exports = function(s,config,onFinish){
|
|||
string += ' -map '+v.map
|
||||
})
|
||||
}else{
|
||||
string += ' -map 0:0'
|
||||
var primaryMap = '0:0'
|
||||
if(e.details.primary_input && e.details.primary_input !== '')primaryMap = e.details.primary_input
|
||||
string += ' -map ' + primaryMap
|
||||
}
|
||||
}
|
||||
return string;
|
||||
|
@ -379,7 +384,16 @@ module.exports = function(s,config,onFinish){
|
|||
//x = temporary values
|
||||
//check if CUDA is enabled
|
||||
e.isStreamer = (e.type === 'dashcam'|| e.type === 'socket')
|
||||
if(e.details.accelerator === '1' && e.details.hwaccel === 'cuvid' && e.details.hwaccel_vcodec === ('h264_cuvid' || 'hevc_cuvid' || 'mjpeg_cuvid' || 'mpeg4_cuvid')){
|
||||
e.coProcessor = false
|
||||
if(
|
||||
e.details.use_coprocessor === '1' &&
|
||||
e.details.accelerator === '1' &&
|
||||
e.isStreamer === false &&
|
||||
(!e.details.input_maps || e.details.input_maps.length === 0) &&
|
||||
(e.details.snap === '1' || e.details.stream_type === 'mjpeg' || e.details.stream_type === 'b64' || e.details.detector === '1')
|
||||
){
|
||||
e.coProcessor = true
|
||||
}else if(e.details.accelerator === '1' && e.details.hwaccel === 'cuvid' && e.details.hwaccel_vcodec === ('h264_cuvid' || 'hevc_cuvid' || 'mjpeg_cuvid' || 'mpeg4_cuvid')){
|
||||
e.cudaEnabled = true
|
||||
}
|
||||
//
|
||||
|
@ -560,9 +574,11 @@ module.exports = function(s,config,onFinish){
|
|||
x.pipe+=x.preset_stream+x.stream_acodec+x.stream_vcodec+' -f hls'+x.cust_stream+' -hls_time '+x.hls_time+' -hls_list_size '+x.hls_list_size+' -start_number 0 -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist "'+e.sdir+'s.m3u8"';
|
||||
break;
|
||||
case'mjpeg':
|
||||
if(e.coProcessor === false){
|
||||
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -q:v '+e.details.stream_quality;
|
||||
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
|
||||
x.pipe+=' -an -c:v mjpeg -f mpjpeg -boundary_tag shinobi'+x.cust_stream+x.stream_video_filters+' pipe:1';
|
||||
}
|
||||
break;
|
||||
case'h265':
|
||||
x.cust_stream+=' -movflags +frag_keyframe+empty_moov+default_base_moof -metadata title="Shinobi H.265 Stream" -reset_timestamps 1'
|
||||
|
@ -575,9 +591,11 @@ module.exports = function(s,config,onFinish){
|
|||
x.pipe+=' -f hevc'+x.stream_acodec+x.stream_vcodec+x.cust_stream+' pipe:1';
|
||||
break;
|
||||
case'b64':case'':case undefined:case null://base64
|
||||
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -q:v '+e.details.stream_quality;
|
||||
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
|
||||
x.pipe+=' -an -c:v mjpeg -f image2pipe'+x.cust_stream+x.stream_video_filters+' pipe:1';
|
||||
if(e.coProcessor === false){
|
||||
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -q:v '+e.details.stream_quality;
|
||||
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
|
||||
x.pipe+=' -an -c:v mjpeg -f image2pipe'+x.cust_stream+x.stream_video_filters+' pipe:1';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
x.pipe=''
|
||||
|
@ -585,11 +603,12 @@ module.exports = function(s,config,onFinish){
|
|||
}
|
||||
if(e.details.stream_channels){
|
||||
e.details.stream_channels.forEach(function(v,n){
|
||||
// if(v.stream_type === 'mjpeg')e.coProcessor = true;
|
||||
x.pipe += s.createStreamChannel(e,n+config.pipeAddition,v)
|
||||
})
|
||||
}
|
||||
//api - snapshot bin/ cgi.bin (JPEG Mode)
|
||||
if(e.details.snap === '1'){
|
||||
if(e.details.snap === '1' && e.coProcessor === false){
|
||||
if(e.details.input_map_choices&&e.details.input_map_choices.snap){
|
||||
//add input feed map
|
||||
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.snap)
|
||||
|
@ -628,7 +647,7 @@ module.exports = function(s,config,onFinish){
|
|||
x.dimensions = e.details.stream_scale_x+'x'+e.details.stream_scale_y;
|
||||
}
|
||||
//record - segmenting
|
||||
x.segment=' -f segment -segment_atclocktime 1 -reset_timestamps 1 -strftime 1 -segment_list pipe:2 -segment_time '+(60*e.cutoff)+' "'+e.dir+'%Y-%m-%dT%H-%M-%S.'+e.ext+'"';
|
||||
x.segment=' -f segment -segment_format_options movflags=faststart+frag_keyframe+empty_moov -segment_atclocktime 1 -reset_timestamps 1 -strftime 1 -segment_list pipe:2 -segment_time '+(60*e.cutoff)+' "'+e.dir+'%Y-%m-%dT%H-%M-%S.'+e.ext+'"';
|
||||
//record - set defaults for extension, video quality
|
||||
switch(e.ext){
|
||||
case'mp4':
|
||||
|
@ -732,40 +751,67 @@ module.exports = function(s,config,onFinish){
|
|||
x.record_string+=x.vcodec+x.record_fps+x.record_video_filters+x.record_dimensions+x.segment;
|
||||
}
|
||||
}
|
||||
ffmpeg.buildAudioDetector = function(e,x){
|
||||
if(e.details.detector_audio === '1'){
|
||||
if(e.details.input_map_choices&&e.details.input_map_choices.detector_audio){
|
||||
//add input feed map
|
||||
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.detector_audio)
|
||||
}else{
|
||||
x.pipe += ' -map 0:a'
|
||||
}
|
||||
x.pipe += ' -acodec pcm_s16le -f s16le -ac 1 -ar 16000 pipe:6'
|
||||
}
|
||||
}
|
||||
ffmpeg.buildMainDetector = function(e,x){
|
||||
//e = monitor object
|
||||
//x = temporary values
|
||||
x.cust_detect = ' '
|
||||
//detector - plugins, motion
|
||||
if(e.details.detector === '1' && e.details.detector_send_frames === '1'){
|
||||
if(e.details.input_map_choices&&e.details.input_map_choices.detector){
|
||||
var sendFramesGlobally = (e.details.detector_send_frames === '1')
|
||||
var sendFramesToObjectDetector = (e.details.detector_send_frames_object !== '0' && e.details.detector_use_detect_object === '1')
|
||||
if(e.details.detector === '1' && (sendFramesGlobally || sendFramesToObjectDetector) && e.coProcessor === false){
|
||||
if(sendFramesGlobally && e.details.input_map_choices && e.details.input_map_choices.detector){
|
||||
//add input feed map
|
||||
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.detector)
|
||||
}
|
||||
if(!e.details.detector_fps||e.details.detector_fps===''){e.details.detector_fps=2}
|
||||
if(e.details.detector_scale_x&&e.details.detector_scale_x!==''&&e.details.detector_scale_y&&e.details.detector_scale_y!==''){x.dratio=' -s '+e.details.detector_scale_x+'x'+e.details.detector_scale_y}else{x.dratio=' -s 320x240'}
|
||||
if(!e.details.detector_fps || e.details.detector_fps === ''){x.detector_fps = 2}else{x.detector_fps = parseInt(e.details.detector_fps)}
|
||||
if(e.details.detector_scale_x && e.details.detector_scale_x !== '' && e.details.detector_scale_y && e.details.detector_scale_y !== ''){x.dratio=' -s '+e.details.detector_scale_x+'x'+e.details.detector_scale_y}else{x.dratio=' -s 320x240'}
|
||||
if(e.details.cust_detect&&e.details.cust_detect!==''){x.cust_detect+=e.details.cust_detect;}
|
||||
x.detector_vf = ['fps='+e.details.detector_fps]
|
||||
if(sendFramesGlobally)x.pipe += ' -r ' + x.detector_fps + x.dratio + x.cust_detect
|
||||
x.detector_vf = []
|
||||
if(e.cudaEnabled){
|
||||
x.detector_vf.push('hwdownload,format=nv12')
|
||||
}
|
||||
x.detector_vf = '-vf "'+x.detector_vf.join(',')+'"'
|
||||
if(e.details.detector_pam==='1'){
|
||||
if(e.cudaEnabled){
|
||||
if(sendFramesGlobally && x.detector_vf.length > 0)x.pipe += ' -vf "'+x.detector_vf.join(',')+'"'
|
||||
|
||||
var h264Output = ' -q:v 1 -an -c:v libx264 -f hls -tune zerolatency -g 1 -hls_time 2 -hls_list_size 3 -start_number 0 -live_start_index 3 -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist "'+e.sdir+'detectorStreamX.m3u8"'
|
||||
if(e.details.detector_pam === '1'){
|
||||
if(sendFramesGlobally && e.cudaEnabled){
|
||||
x.pipe += ' -vf "hwdownload,format=nv12"'
|
||||
}
|
||||
x.pipe+=' -an -c:v pam -pix_fmt gray -f image2pipe -r '+e.details.detector_fps+x.cust_detect+x.dratio+' pipe:3'
|
||||
if(sendFramesGlobally)x.pipe += ' -an -c:v pam -pix_fmt gray -f image2pipe pipe:3'
|
||||
if(e.details.detector_use_detect_object === '1'){
|
||||
//for object detection
|
||||
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.detector)
|
||||
x.pipe += ' -f singlejpeg '+x.detector_vf+x.cust_detect+x.dratio+' pipe:4';
|
||||
if(e.details.detector_scale_x_object&&e.details.detector_scale_x_object!==''&&e.details.detector_scale_y_object&&e.details.detector_scale_y_object!==''){x.dobjratio=' -s '+e.details.detector_scale_x_object+'x'+e.details.detector_scale_y_object}else{x.dobjratio=x.dratio}
|
||||
x.pipe += ' -r ' + x.detector_fps + x.dobjratio + x.cust_detect
|
||||
if(e.details.detector_h264 === '1'){
|
||||
x.pipe += h264Output
|
||||
}else{
|
||||
x.pipe += ' -an -f singlejpeg pipe:4'
|
||||
}
|
||||
}
|
||||
}else if(sendFramesGlobally){
|
||||
if(e.details.detector_h264 === '1'){
|
||||
x.pipe += h264Output
|
||||
}else{
|
||||
x.pipe += ' -an -f singlejpeg pipe:3'
|
||||
}
|
||||
}else{
|
||||
x.pipe+=' -f image2pipe '+x.detector_vf+x.cust_detect+x.dratio+' pipe:3';
|
||||
}
|
||||
}
|
||||
//Traditional Recording Buffer
|
||||
if(e.details.detector=='1'&&e.details.detector_trigger=='1'&&e.details.detector_record_method==='sip'){
|
||||
if(e.details.cust_sip_record && e.details.cust_sip_record !== ''){x.pipe += ' ' + e.details.cust_sip_record}
|
||||
if(e.details.input_map_choices&&e.details.input_map_choices.detector_sip_buffer){
|
||||
//add input feed map
|
||||
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.detector_sip_buffer)
|
||||
|
@ -809,7 +855,6 @@ module.exports = function(s,config,onFinish){
|
|||
if(!e.details.detector_buffer_hls_list_size||e.details.detector_buffer_hls_list_size===''){e.details.detector_buffer_hls_list_size='4'}
|
||||
if(!e.details.detector_buffer_start_number||e.details.detector_buffer_start_number===''){e.details.detector_buffer_start_number='0'}
|
||||
if(!e.details.detector_buffer_live_start_index||e.details.detector_buffer_live_start_index===''){e.details.detector_buffer_live_start_index='-3'}
|
||||
|
||||
if(e.details.detector_buffer_vcodec.indexOf('_vaapi')>-1){
|
||||
if(x.hwaccel.indexOf('-vaapi_device')>-1){
|
||||
x.detector_buffer_filters.push('format=nv12')
|
||||
|
@ -830,7 +875,13 @@ module.exports = function(s,config,onFinish){
|
|||
if(x.detector_buffer_filters.length>0){
|
||||
x.pipe+=' -vf '+x.detector_buffer_filters.join(',')
|
||||
}
|
||||
x.pipe+=x.detector_buffer_fps+x.detector_buffer_acodec+' -c:v '+e.details.detector_buffer_vcodec+' -f hls -tune '+e.details.detector_buffer_tune+' -g '+e.details.detector_buffer_g+' -hls_time '+e.details.detector_buffer_hls_time+' -hls_list_size '+e.details.detector_buffer_hls_list_size+' -start_number '+e.details.detector_buffer_start_number+' -live_start_index '+e.details.detector_buffer_live_start_index+' -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist "'+e.sdir+'detectorStream.m3u8"'
|
||||
x.pipe += x.detector_buffer_fps+x.detector_buffer_acodec+' -c:v '+e.details.detector_buffer_vcodec+' -f hls -tune '+e.details.detector_buffer_tune+' -g '+e.details.detector_buffer_g+' -hls_time '+e.details.detector_buffer_hls_time+' -hls_list_size '+e.details.detector_buffer_hls_list_size+' -start_number '+e.details.detector_buffer_start_number+' -live_start_index '+e.details.detector_buffer_live_start_index+' -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist "'+e.sdir+'detectorStream.m3u8"'
|
||||
}
|
||||
}
|
||||
ffmpeg.buildCoProcessorFeed = function(e,x){
|
||||
if(e.coProcessor === true){
|
||||
// the coProcessor ffmpeg consumes this HLS stream (no audio, frames only)
|
||||
x.pipe += ' -q:v 1 -an -c:v copy -f hls -tune zerolatency -g 1 -hls_time 2 -hls_list_size 3 -start_number 0 -live_start_index 3 -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist "'+e.sdir+'coProcessor.m3u8"'
|
||||
}
|
||||
}
|
||||
ffmpeg.assembleMainPieces = function(e,x){
|
||||
|
@ -849,6 +900,10 @@ module.exports = function(s,config,onFinish){
|
|||
case'mjpeg':
|
||||
x.ffmpegCommandString += ' -reconnect 1 -f mjpeg'+x.cust_input+x.hwaccel+' -i "'+e.url+'"';
|
||||
break;
|
||||
case'rtmp':
|
||||
if(!e.details.rtmp_key)e.details.rtmp_key = ''
|
||||
x.ffmpegCommandString += x.cust_input+x.hwaccel+` -i "rtmp://127.0.0.1:1935/${e.ke + '_' + e.mid + '_' + e.details.rtmp_key}"`;
|
||||
break;
|
||||
case'h264':case'hls':case'mp4':
|
||||
x.ffmpegCommandString += x.cust_input+x.hwaccel+' -i "'+e.url+'"';
|
||||
break;
|
||||
|
@ -883,7 +938,12 @@ module.exports = function(s,config,onFinish){
|
|||
ffmpeg.buildMainInput(e,x)
|
||||
ffmpeg.buildMainStream(e,x)
|
||||
ffmpeg.buildMainRecording(e,x)
|
||||
ffmpeg.buildAudioDetector(e,x)
|
||||
ffmpeg.buildMainDetector(e,x)
|
||||
ffmpeg.buildCoProcessorFeed(e,x)
|
||||
s.onFfmpegCameraStringCreationExtensions.forEach(function(extender){
|
||||
extender(e,x)
|
||||
})
|
||||
ffmpeg.assembleMainPieces(e,x)
|
||||
ffmpeg.createPipeArray(e,x)
|
||||
//hold ffmpeg command for log stream
|
||||
|
@ -895,9 +955,9 @@ module.exports = function(s,config,onFinish){
|
|||
}
|
||||
if(!config.ffmpegDir){
|
||||
ffmpeg.checkForWindows(function(){
|
||||
ffmpeg.checkForUnix(function(){
|
||||
ffmpeg.checkForFfbinary(function(){
|
||||
ffmpeg.checkForNpmStatic(function(){
|
||||
ffmpeg.checkForFfbinary(function(){
|
||||
ffmpeg.checkForNpmStatic(function(){
|
||||
ffmpeg.checkForUnix(function(){
|
||||
console.log('No FFmpeg found.')
|
||||
})
|
||||
})
|
||||
|
|
210
libs/ffmpegCoProcessor.js
Normal file
210
libs/ffmpegCoProcessor.js
Normal file
|
@ -0,0 +1,210 @@
|
|||
var spawn = require('child_process').spawn;
|
||||
module.exports = function(s,config,lang,ffmpeg){
|
||||
ffmpeg.buildCoProcessorInput = function(e,x){
|
||||
if(e.details.userLoglevel&&e.details.userLoglevel!==''){x.loglevel='-loglevel '+e.details.userLoglevel;}else{x.loglevel='-loglevel error'}
|
||||
x.input = x.loglevel+' -re -i '+e.sdir+'coProcessor.m3u8'
|
||||
}
|
||||
ffmpeg.buildCoProcessorStream = function(e,x){
|
||||
x.stream_video_filters = []
|
||||
//stream - timestamp
|
||||
if(e.details.stream_timestamp&&e.details.stream_timestamp=="1"&&e.details.vcodec!=='copy'){
|
||||
//font
|
||||
if(e.details.stream_timestamp_font&&e.details.stream_timestamp_font!==''){x.stream_timestamp_font=e.details.stream_timestamp_font}else{x.stream_timestamp_font='/usr/share/fonts/truetype/freefont/FreeSans.ttf'}
|
||||
//position x
|
||||
if(e.details.stream_timestamp_x&&e.details.stream_timestamp_x!==''){x.stream_timestamp_x=e.details.stream_timestamp_x}else{x.stream_timestamp_x='(w-tw)/2'}
|
||||
//position y
|
||||
if(e.details.stream_timestamp_y&&e.details.stream_timestamp_y!==''){x.stream_timestamp_y=e.details.stream_timestamp_y}else{x.stream_timestamp_y='0'}
|
||||
//text color
|
||||
if(e.details.stream_timestamp_color&&e.details.stream_timestamp_color!==''){x.stream_timestamp_color=e.details.stream_timestamp_color}else{x.stream_timestamp_color='white'}
|
||||
//box color
|
||||
if(e.details.stream_timestamp_box_color&&e.details.stream_timestamp_box_color!==''){x.stream_timestamp_box_color=e.details.stream_timestamp_box_color}else{x.stream_timestamp_box_color='0x00000000@1'}
|
||||
//text size
|
||||
if(e.details.stream_timestamp_font_size&&e.details.stream_timestamp_font_size!==''){x.stream_timestamp_font_size=e.details.stream_timestamp_font_size}else{x.stream_timestamp_font_size='10'}
|
||||
|
||||
x.stream_video_filters.push('drawtext=fontfile='+x.stream_timestamp_font+':text=\'%{localtime}\':x='+x.stream_timestamp_x+':y='+x.stream_timestamp_y+':fontcolor='+x.stream_timestamp_color+':box=1:boxcolor='+x.stream_timestamp_box_color+':fontsize='+x.stream_timestamp_font_size);
|
||||
}
|
||||
//stream - watermark for -vf
|
||||
if(e.details.stream_watermark&&e.details.stream_watermark=="1"&&e.details.stream_watermark_location&&e.details.stream_watermark_location!==''){
|
||||
switch(e.details.stream_watermark_position){
|
||||
case'tl'://top left
|
||||
x.stream_watermark_position='10:10'
|
||||
break;
|
||||
case'tr'://top right
|
||||
x.stream_watermark_position='main_w-overlay_w-10:10'
|
||||
break;
|
||||
case'bl'://bottom left
|
||||
x.stream_watermark_position='10:main_h-overlay_h-10'
|
||||
break;
|
||||
default://bottom right
|
||||
x.stream_watermark_position='(main_w-overlay_w-10)/2:(main_h-overlay_h-10)/2'
|
||||
break;
|
||||
}
|
||||
x.stream_video_filters.push('movie='+e.details.stream_watermark_location+'[watermark],[in][watermark]overlay='+x.stream_watermark_position+'[out]');
|
||||
}
|
||||
//stream - rotation
|
||||
if(e.details.rotate_stream&&e.details.rotate_stream!==""&&e.details.rotate_stream!=="no"&&e.details.stream_vcodec!=='copy'){
|
||||
x.stream_video_filters.push('transpose='+e.details.rotate_stream);
|
||||
}
|
||||
if(e.details.svf&&e.details.svf!==''){
|
||||
x.stream_video_filters.push(e.details.svf)
|
||||
}
|
||||
if(x.stream_video_filters.length>0){
|
||||
x.stream_video_filters=' -vf '+x.stream_video_filters.join(',')
|
||||
}else{
|
||||
x.stream_video_filters=''
|
||||
}
|
||||
if(e.details.cust_stream&&e.details.cust_stream!==''){x.cust_stream=' '+e.details.cust_stream}else{x.cust_stream=''}
|
||||
if(e.details.stream_fps&&e.details.stream_fps!==''){x.stream_fps=' -r '+e.details.stream_fps}else{x.stream_fps=''}
|
||||
if(e.details.stream_vcodec !== 'copy' || e.details.stream_type === 'mjpeg' || e.details.stream_type === 'b64'){
|
||||
x.cust_stream += x.stream_fps
|
||||
}
|
||||
switch(e.details.stream_type){
|
||||
case'mjpeg':
|
||||
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -q:v '+e.details.stream_quality;
|
||||
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
|
||||
x.pipe += ' -an -c:v mjpeg -f mpjpeg -boundary_tag shinobi'+x.cust_stream+x.stream_video_filters+' pipe:1';
|
||||
break;
|
||||
case'b64':case'':case undefined:case null://base64
|
||||
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -q:v '+e.details.stream_quality;
|
||||
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
|
||||
x.pipe += ' -an -c:v mjpeg -f image2pipe'+x.cust_stream+x.stream_video_filters+' pipe:1';
|
||||
break;
|
||||
}
|
||||
}
|
||||
ffmpeg.buildCoProcessorDetector = function(e,x){
|
||||
//detector frames
|
||||
x.cust_detect=' '
|
||||
if(e.details.detector === '1'){
|
||||
if(e.details.detector_fps && e.details.detector_fps !== ''){
|
||||
x.detector_fps = e.details.detector_fps
|
||||
}else{
|
||||
x.detector_fps = '2'
|
||||
}
|
||||
if(e.details.detector_scale_x && e.details.detector_scale_x !== '' && e.details.detector_scale_y && e.details.detector_scale_y !== ''){
|
||||
x.dratio=' -s '+e.details.detector_scale_x+'x'+e.details.detector_scale_y
|
||||
}else{
|
||||
x.dratio=' -s 320x240'
|
||||
}
|
||||
|
||||
if(e.details.cust_detect&&e.details.cust_detect!==''){x.cust_detect+=e.details.cust_detect;}
|
||||
if(e.details.detector_pam==='1'){
|
||||
x.pipe += ' -an -c:v pam -pix_fmt gray -f image2pipe -r '+x.detector_fps+x.cust_detect+x.dratio+' pipe:3'
|
||||
if(e.details.detector_use_detect_object === '1'){
|
||||
if(e.details.detector_use_motion === '1'){
|
||||
if(e.details.detector_scale_x_object && e.details.detector_scale_x_object !== '' && e.details.detector_scale_y_object && e.details.detector_scale_y_object !== ''){
|
||||
x.dratio=' -s '+e.details.detector_scale_x_object+'x'+e.details.detector_scale_y_object
|
||||
}
|
||||
if(e.details.detector_fps_object && e.details.detector_fps_object !== ''){
|
||||
x.detector_fps = e.details.detector_fps_object
|
||||
}
|
||||
}
|
||||
//for object detection
|
||||
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.detector)
|
||||
x.pipe += ' -f singlejpeg -vf fps='+x.detector_fps+x.cust_detect+x.dratio+' pipe:4';
|
||||
}
|
||||
}else{
|
||||
x.pipe+=' -f singlejpeg -vf fps='+x.detector_fps+x.cust_detect+x.dratio+' pipe:3';
|
||||
}
|
||||
}
|
||||
}
|
||||
ffmpeg.buildCoProcessorJpegApi = function(e,x){
|
||||
//snapshot frames
|
||||
if(e.details.snap === '1'){
|
||||
if(!e.details.snap_fps || e.details.snap_fps === ''){e.details.snap_fps = 1}
|
||||
if(e.details.snap_vf && e.details.snap_vf !== ''){x.snap_vf=' -vf '+e.details.snap_vf}else{x.snap_vf=''}
|
||||
if(e.details.snap_scale_x && e.details.snap_scale_x !== '' && e.details.snap_scale_y && e.details.snap_scale_y !== ''){x.snap_ratio = ' -s '+e.details.snap_scale_x+'x'+e.details.snap_scale_y}else{x.snap_ratio=''}
|
||||
if(e.details.cust_snap && e.details.cust_snap !== ''){x.cust_snap = ' '+e.details.cust_snap}else{x.cust_snap=''}
|
||||
x.pipe += ' -update 1 -r '+e.details.snap_fps+x.cust_snap+x.snap_ratio+x.snap_vf+' "'+e.sdir+'s.jpg" -y';
|
||||
}
|
||||
}
|
||||
ffmpeg.buildCoProcessorPipeArray = function(e,x){
|
||||
x.stdioPipes = [];
|
||||
var times = config.pipeAddition;
|
||||
if(e.details.stream_channels){
|
||||
times+=e.details.stream_channels.length
|
||||
}
|
||||
for(var i=0; i < times; i++){
|
||||
x.stdioPipes.push('pipe')
|
||||
}
|
||||
}
|
||||
s.ffmpegCoProcessor = function(e){
|
||||
if(e.coProcessor === false)return;
|
||||
var x = {}
|
||||
x.pipe = ''
|
||||
ffmpeg.buildCoProcessorInput(e,x)
|
||||
ffmpeg.buildCoProcessorStream(e,x)
|
||||
ffmpeg.buildCoProcessorDetector(e,x)
|
||||
ffmpeg.buildCoProcessorJpegApi(e,x)
|
||||
ffmpeg.buildCoProcessorPipeArray(e,x)
|
||||
var commandString = x.input + x.pipe
|
||||
if(commandString === x.input){
|
||||
return false
|
||||
}
|
||||
s.group[e.ke].mon[e.mid].coProcessorCmd = commandString
|
||||
return spawn(config.ffmpegDir,s.splitForFFPMEG((commandString).replace(/\s+/g,' ').trim()),{detached: true,stdio:x.stdioPipes})
|
||||
}
|
||||
s.coSpawnLauncher = function(e){
|
||||
if(s.group[e.ke].mon[e.id].isStarted === true && e.coProcessor === true){
|
||||
s.coSpawnClose(e)
|
||||
s.group[e.ke].mon[e.id].coSpawnProcessor = s.ffmpegCoProcessor(e)
|
||||
if(s.group[e.ke].mon[e.id].coSpawnProcessor === false){
|
||||
return
|
||||
}
|
||||
s.userLog(e,{type:lang['coProcessor Started'],msg:{msg:lang.coProcessorTextStarted,cmd:s.group[e.ke].mon[e.id].coProcessorCmd}});
|
||||
s.group[e.ke].mon[e.id].coSpawnProcessorExit = function(){
|
||||
s.userLog(e,{type:lang['coProcess Unexpected Exit'],msg:{msg:lang['coProcess Crashed for Monitor']+' : '+e.id,cmd:s.group[e.ke].mon[e.id].coProcessorCmd}});
|
||||
setTimeout(function(){
|
||||
s.coSpawnLauncher(e)
|
||||
},2000)
|
||||
}
|
||||
s.group[e.ke].mon[e.id].coSpawnProcessor.on('end',s.group[e.ke].mon[e.id].coSpawnProcessorExit)
|
||||
s.group[e.ke].mon[e.id].coSpawnProcessor.on('exit',s.group[e.ke].mon[e.id].coSpawnProcessorExit)
|
||||
var checkLog = function(d,x){return d.indexOf(x)>-1;}
|
||||
s.group[e.ke].mon[e.id].coSpawnProcessor.stderr.on('data',function(d){
|
||||
d=d.toString();
|
||||
switch(true){
|
||||
case checkLog(d,'deprecated pixel format used'):
|
||||
case checkLog(d,'[hls @'):
|
||||
case checkLog(d,'Past duration'):
|
||||
case checkLog(d,'Last message repeated'):
|
||||
case checkLog(d,'pkt->duration = 0'):
|
||||
case checkLog(d,'Non-monotonous DTS'):
|
||||
case checkLog(d,'NULL @'):
|
||||
return
|
||||
break;
|
||||
}
|
||||
s.userLog(e,{type:lang.coProcessor,msg:d});
|
||||
})
|
||||
if(e.frame_to_stream){
|
||||
s.group[e.ke].mon[e.id].coSpawnProcessor.stdout.on('data',e.frame_to_stream)
|
||||
}
|
||||
if(e.details.detector === '1'){
|
||||
s.ocvTx({f:'init_monitor',id:e.id,ke:e.ke})
|
||||
//frames from motion detect
|
||||
if(e.details.detector_pam === '1'){
|
||||
s.createPamDiffEngine(e)
|
||||
s.group[e.ke].mon[e.id].coSpawnProcessor.stdio[3].pipe(s.group[e.ke].mon[e.id].p2p).pipe(s.group[e.ke].mon[e.id].pamDiff)
|
||||
if(e.details.detector_use_detect_object === '1'){
|
||||
s.group[e.ke].mon[e.id].coSpawnProcessor.stdio[4].on('data',function(d){
|
||||
s.group[e.ke].mon[e.id].lastJpegDetectorFrame = d
|
||||
})
|
||||
}
|
||||
}else if(s.isAtleatOneDetectorPluginConnected){
|
||||
s.group[e.ke].mon[e.id].coSpawnProcessor.stdio[3].on('data',function(d){
|
||||
s.ocvTx({f:'frame',mon:s.group[e.ke].mon_conf[e.id].details,ke:e.ke,id:e.id,time:s.formattedTime(),frame:d});
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
s.coSpawnClose = function(e){
|
||||
if(s.group[e.ke].mon[e.id].coSpawnProcessor){
|
||||
s.group[e.ke].mon[e.id].coSpawnProcessor.removeListener('end',s.group[e.ke].mon[e.id].coSpawnProcessorExit);
|
||||
s.group[e.ke].mon[e.id].coSpawnProcessor.removeListener('exit',s.group[e.ke].mon[e.id].coSpawnProcessorExit);
|
||||
s.group[e.ke].mon[e.id].coSpawnProcessor.stdin.pause()
|
||||
s.group[e.ke].mon[e.id].coSpawnProcessor.kill()
|
||||
delete(s.group[e.ke].mon[e.id].coSpawnProcessor)
|
||||
s.userLog(e,{type:lang['coProcessor Stopped'],msg:{msg:lang.coProcessorTextStopped+' : '+e.id}});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,42 +18,50 @@ module.exports = function(s,config){
|
|||
var definitions = require(s.location.definitions+'/en_CA.json');
|
||||
}
|
||||
//load languages dynamically
|
||||
s.copySystemDefaultLanguage = function(){
|
||||
//en_CA
|
||||
return Object.assign(lang,{})
|
||||
}
|
||||
s.loadedLanguages={}
|
||||
s.loadedLanguages[config.language]=lang;
|
||||
s.loadedLanguages[config.language] = s.copySystemDefaultLanguage()
|
||||
s.getLanguageFile = function(rule){
|
||||
if(rule && rule !== ''){
|
||||
var file = s.loadedLanguages[file]
|
||||
if(!file){
|
||||
try{
|
||||
s.loadedLanguages[rule] = require(s.location.languages+'/'+rule+'.json')
|
||||
s.loadedLanguages[rule] = Object.assign(lang,s.loadedLanguages[rule])
|
||||
s.loadedLanguages[rule] = Object.assign(s.copySystemDefaultLanguage(),s.loadedLanguages[rule])
|
||||
file = s.loadedLanguages[rule]
|
||||
}catch(err){
|
||||
file = lang
|
||||
file = s.copySystemDefaultLanguage()
|
||||
}
|
||||
}
|
||||
}else{
|
||||
file = lang
|
||||
file = s.copySystemDefaultLanguage()
|
||||
}
|
||||
return file
|
||||
}
|
||||
//load defintions dynamically
|
||||
s.copySystemDefaultDefinitions = function(){
|
||||
//en_CA
|
||||
return Object.assign(definitions,{})
|
||||
}
|
||||
s.loadedDefinitons={}
|
||||
s.loadedDefinitons[config.language]=definitions;
|
||||
s.loadedDefinitons[config.language] = s.copySystemDefaultDefinitions()
|
||||
s.getDefinitonFile = function(rule){
|
||||
if(rule && rule !== ''){
|
||||
var file = s.loadedDefinitons[file]
|
||||
if(!file){
|
||||
try{
|
||||
s.loadedDefinitons[rule] = require(s.location.definitions+'/'+rule+'.json')
|
||||
s.loadedDefinitons[rule] = Object.assign(definitions,s.loadedDefinitons[rule])
|
||||
s.loadedDefinitons[rule] = Object.assign(s.copySystemDefaultDefinitions(),s.loadedDefinitons[rule])
|
||||
file = s.loadedDefinitons[rule]
|
||||
}catch(err){
|
||||
file = definitions
|
||||
file = s.copySystemDefaultDefinitions()
|
||||
}
|
||||
}
|
||||
}else{
|
||||
file = definitions
|
||||
file = s.copySystemDefaultDefinitions()
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
|
311
libs/monitor.js
311
libs/monitor.js
|
@ -6,6 +6,7 @@ var Mp4Frag = require('mp4frag');
|
|||
var onvif = require('node-onvif');
|
||||
var request = require('request');
|
||||
var connectionTester = require('connection-tester')
|
||||
var SoundDetection = require('shinobi-sound-detection')
|
||||
var URL = require('url')
|
||||
module.exports = function(s,config,lang){
|
||||
s.initiateMonitorObject = function(e){
|
||||
|
@ -21,6 +22,7 @@ module.exports = function(s,config,lang){
|
|||
if(!s.group[e.ke].mon[e.mid].eventBasedRecording){s.group[e.ke].mon[e.mid].eventBasedRecording={}};
|
||||
if(!s.group[e.ke].mon[e.mid].watch){s.group[e.ke].mon[e.mid].watch={}};
|
||||
if(!s.group[e.ke].mon[e.mid].fixingVideos){s.group[e.ke].mon[e.mid].fixingVideos={}};
|
||||
if(!s.group[e.ke].mon[e.mid].parsedObjects){s.group[e.ke].mon[e.mid].parsedObjects={}};
|
||||
if(!s.group[e.ke].mon[e.mid].isStarted){s.group[e.ke].mon[e.mid].isStarted = false};
|
||||
if(s.group[e.ke].mon[e.mid].delete){clearTimeout(s.group[e.ke].mon[e.mid].delete)}
|
||||
if(!s.group[e.ke].mon_conf){s.group[e.ke].mon_conf={}}
|
||||
|
@ -103,7 +105,7 @@ module.exports = function(s,config,lang){
|
|||
var snapBuffer = []
|
||||
var snapProcess = spawn(config.ffmpegDir,('-loglevel quiet -re -i '+url+options+' -frames:v 1 -f image2pipe pipe:1').split(' '),{detached: true})
|
||||
snapProcess.stdout.on('data',function(data){
|
||||
snapBuffer.push(data)
|
||||
if(snapBuffer)snapBuffer.push(data)
|
||||
})
|
||||
snapProcess.stderr.on('data',function(data){
|
||||
console.log(data.toString())
|
||||
|
@ -163,6 +165,9 @@ module.exports = function(s,config,lang){
|
|||
var streamDirItems = fs.readdirSync(pathDir)
|
||||
var items = []
|
||||
var copiedItems = []
|
||||
var videoLength = s.group[monitor.ke].mon_conf[monitor.id].details.detector_send_video_length
|
||||
if(!videoLength || videoLength === '')videoLength = '10'
|
||||
if(videoLength.length === 1)videoLength = '0' + videoLength
|
||||
var createMerged = function(copiedItems){
|
||||
var allts = pathDir+items.join('_')
|
||||
fs.stat(allts,function(err,stats){
|
||||
|
@ -170,7 +175,7 @@ module.exports = function(s,config,lang){
|
|||
//not exist
|
||||
var cat = 'cat '+copiedItems.join(' ')+' > '+allts
|
||||
exec(cat,function(){
|
||||
var merger = spawn(config.ffmpegDir,s.splitForFFPMEG(('-re -i '+allts+' -acodec copy -vcodec copy '+pathDir+mergedFile)))
|
||||
var merger = spawn(config.ffmpegDir,s.splitForFFPMEG(('-re -i '+allts+' -acodec copy -vcodec copy -t 00:00:' + videoLength + ' '+pathDir+mergedFile)))
|
||||
merger.stderr.on('data',function(data){
|
||||
s.userLog(monitor,{type:"Buffer Merge",msg:data.toString()})
|
||||
})
|
||||
|
@ -198,7 +203,7 @@ module.exports = function(s,config,lang){
|
|||
}
|
||||
})
|
||||
items.sort()
|
||||
items = items.slice(items.length - 5,items.length)
|
||||
// items = items.slice(items.length - 5,items.length)
|
||||
items.forEach(function(filename){
|
||||
try{
|
||||
var tempFilename = filename.split('.')
|
||||
|
@ -218,6 +223,64 @@ module.exports = function(s,config,lang){
|
|||
})
|
||||
return items
|
||||
}
|
||||
s.mergeRecordedVideos = function(videoRows,groupKey,callback){
|
||||
var tempDir = s.dir.streams + groupKey + '/'
|
||||
var pathDir = s.dir.fileBin + groupKey + '/'
|
||||
var streamDirItems = fs.readdirSync(pathDir)
|
||||
var items = []
|
||||
var mergedFile = []
|
||||
videoRows.forEach(function(video){
|
||||
var filepath = s.getVideoDirectory(video) + s.formattedTime(video.time) + '.' + video.ext
|
||||
if(
|
||||
filepath.indexOf('.mp4') > -1
|
||||
// || filename.indexOf('.webm') > -1
|
||||
){
|
||||
mergedFile.push(s.formattedTime(video.time))
|
||||
items.push(filepath)
|
||||
}
|
||||
})
|
||||
mergedFile.sort()
|
||||
mergedFile = mergedFile.join('_') + '.mp4'
|
||||
var mergedFilepath = pathDir + mergedFile
|
||||
var mergedRawFilepath = pathDir + 'raw_' + mergedFile
|
||||
items.sort()
|
||||
fs.stat(mergedFilepath,function(err,stats){
|
||||
if(err){
|
||||
//not exist
|
||||
var tempScriptPath = tempDir + s.gid(5) + '.sh'
|
||||
var cat = 'cat '+items.join(' ')+' > '+mergedRawFilepath
|
||||
fs.writeFileSync(tempScriptPath,cat,'utf8')
|
||||
exec('sh ' + tempScriptPath,function(){
|
||||
s.userLog({
|
||||
ke: groupKey,
|
||||
mid: '$USER'
|
||||
},{type:lang['Videos Merge'],msg:mergedFile})
|
||||
var merger = spawn(config.ffmpegDir,s.splitForFFPMEG(('-re -loglevel warning -i ' + mergedRawFilepath + ' -acodec copy -vcodec copy ' + mergedFilepath)))
|
||||
merger.stderr.on('data',function(data){
|
||||
s.userLog({
|
||||
ke: groupKey,
|
||||
mid: '$USER'
|
||||
},{type:lang['Videos Merge'],msg:data.toString()})
|
||||
})
|
||||
merger.on('close',function(){
|
||||
s.file('delete',mergedRawFilepath)
|
||||
s.file('delete',tempScriptPath)
|
||||
setTimeout(function(){
|
||||
fs.stat(mergedFilepath,function(err,stats){
|
||||
if(!err)s.file('delete',mergedFilepath)
|
||||
})
|
||||
},1000 * 60 * 60 * 24)
|
||||
delete(merger)
|
||||
callback(mergedFilepath,mergedFile)
|
||||
})
|
||||
})
|
||||
}else{
|
||||
//file exist
|
||||
callback(mergedFilepath,mergedFile)
|
||||
}
|
||||
})
|
||||
return items
|
||||
}
|
||||
|
||||
s.cameraDestroy = function(x,e,p){
|
||||
if(s.group[e.ke]&&s.group[e.ke].mon[e.id]&&s.group[e.ke].mon[e.id].spawn !== undefined){
|
||||
|
@ -266,6 +329,7 @@ module.exports = function(s,config,lang){
|
|||
if(s.group[e.ke].mon[e.id].childNode){
|
||||
s.cx({f:'kill',d:s.cleanMonitorObject(e)},s.group[e.ke].mon[e.id].childNodeId)
|
||||
}else{
|
||||
s.coSpawnClose(e)
|
||||
if(!x||x===1){return};
|
||||
p=x.pid;
|
||||
if(s.group[e.ke].mon_conf[e.id].type===('dashcam'||'socket'||'jpeg'||'pipe')){
|
||||
|
@ -282,12 +346,20 @@ module.exports = function(s,config,lang){
|
|||
s.cameraCheckObjectsInDetails = function(e){
|
||||
//parse Objects
|
||||
(['detector_cascades','cords','detector_filters','input_map_choices']).forEach(function(v){
|
||||
if(e.details&&e.details[v]&&(e.details[v] instanceof Object)===false){
|
||||
if(e.details && e.details[v]){
|
||||
try{
|
||||
if(e.details[v] === '') e.details[v] = '{}'
|
||||
e.details[v]=JSON.parse(e.details[v]);
|
||||
if(!e.details[v])e.details[v]={};
|
||||
s.group[e.ke].mon[e.id].details = e.details;
|
||||
if(!e.details[v] || e.details[v] === '')e.details[v] = '{}'
|
||||
e.details[v] = s.parseJSON(e.details[v])
|
||||
if(!e.details[v])e.details[v] = {}
|
||||
s.group[e.ke].mon[e.id].details = e.details
|
||||
switch(v){
|
||||
case'cords':
|
||||
s.group[e.ke].mon[e.id].parsedObjects[v] = Object.values(s.parseJSON(e.details[v]))
|
||||
break;
|
||||
default:
|
||||
s.group[e.ke].mon[e.id].parsedObjects[v] = s.parseJSON(e.details[v])
|
||||
break;
|
||||
}
|
||||
}catch(err){
|
||||
|
||||
}
|
||||
|
@ -595,6 +667,14 @@ module.exports = function(s,config,lang){
|
|||
// exec('chmod -R 777 '+e.sdir,function(err){
|
||||
//
|
||||
// })
|
||||
var binDir = s.dir.fileBin + e.ke + '/'
|
||||
if (!fs.existsSync(binDir)){
|
||||
fs.mkdirSync(binDir)
|
||||
}
|
||||
binDir = s.dir.fileBin + e.ke + '/' + e.id + '/'
|
||||
if (!fs.existsSync(binDir)){
|
||||
fs.mkdirSync(binDir)
|
||||
}
|
||||
return setStreamDir
|
||||
}
|
||||
s.stripAuthFromHost = function(e){
|
||||
|
@ -741,6 +821,9 @@ module.exports = function(s,config,lang){
|
|||
}
|
||||
s.fatalCameraError(e,'Process Unexpected Exit');
|
||||
s.orphanedVideoCheck(e,2,null,true)
|
||||
s.onMonitorUnexpectedExitExtensions.forEach(function(extender){
|
||||
extender(Object.assign(s.group[e.ke].mon_conf[e.id],{}),e)
|
||||
})
|
||||
}
|
||||
}
|
||||
s.group[e.ke].mon[e.id].spawn.on('end',s.group[e.ke].mon[e.id].spawn_exit)
|
||||
|
@ -773,7 +856,47 @@ module.exports = function(s,config,lang){
|
|||
if(e.type==='jpeg'){
|
||||
s.cameraPullJpegStream(e)
|
||||
}
|
||||
if(e.details.detector === '1'){
|
||||
if(e.details.detector_audio === '1'){
|
||||
var triggerLevel
|
||||
var triggerLevelMax
|
||||
if(e.details.detector_audio_min_db && e.details.detector_audio_min_db !== ''){
|
||||
triggerLevel = parseInt(e.details.detector_audio_min_db)
|
||||
}else{
|
||||
triggerLevel = 5
|
||||
}
|
||||
if(e.details.detector_audio_max_db && e.details.detector_audio_max_db !== ''){
|
||||
triggerLevelMax = parseInt(e.details.detector_audio_max_db)
|
||||
}
|
||||
var audioDetector = new SoundDetection({
|
||||
format: {
|
||||
bitDepth: 16,
|
||||
numberOfChannels: 1,
|
||||
signed: true
|
||||
},
|
||||
triggerLevel: triggerLevel,
|
||||
triggerLevelMax: triggerLevelMax
|
||||
},function(dB) {
|
||||
s.triggerEvent({
|
||||
f:'trigger',
|
||||
id:e.id,
|
||||
ke:e.ke,
|
||||
name: 'db',
|
||||
details:{
|
||||
plug:'audio',
|
||||
name:'db',
|
||||
reason:'soundChange',
|
||||
confidence:dB
|
||||
},
|
||||
plates:[],
|
||||
imgHeight:e.details.detector_scale_y,
|
||||
imgWidth:e.details.detector_scale_x
|
||||
})
|
||||
})
|
||||
s.group[e.ke].mon[e.id].audioDetector = audioDetector
|
||||
audioDetector.start()
|
||||
s.group[e.ke].mon[e.id].spawn.stdio[6].pipe(audioDetector.streamDecoder)
|
||||
}
|
||||
if(e.details.detector === '1' && e.coProcessor === false){
|
||||
s.ocvTx({f:'init_monitor',id:e.id,ke:e.ke})
|
||||
//frames from motion detect
|
||||
if(e.details.detector_pam === '1'){
|
||||
|
@ -784,34 +907,10 @@ module.exports = function(s,config,lang){
|
|||
s.group[e.ke].mon[e.id].lastJpegDetectorFrame = d
|
||||
})
|
||||
}
|
||||
}else if(s.ocv){
|
||||
if(s.ocv.connectionType !== 'ram'){
|
||||
s.group[e.ke].mon[e.id].spawn.stdio[3].on('data',function(d){
|
||||
s.ocvTx({f:'frame',mon:s.group[e.ke].mon_conf[e.id].details,ke:e.ke,id:e.id,time:s.formattedTime(),frame:d});
|
||||
})
|
||||
}else{
|
||||
s.group[e.ke].mon[e.id].spawn.stdio[3].on('data',function(d){
|
||||
if(!s.group[e.ke].mon[e.id].detectorFrameSaveBuffer){
|
||||
s.group[e.ke].mon[e.id].detectorFrameSaveBuffer=[d]
|
||||
}else{
|
||||
s.group[e.ke].mon[e.id].detectorFrameSaveBuffer.push(d)
|
||||
}
|
||||
if(d[d.length-2] === 0xFF && d[d.length-1] === 0xD9){
|
||||
var buffer = Buffer.concat(s.group[e.ke].mon[e.id].detectorFrameSaveBuffer);
|
||||
var frameLocation = s.dir.streams + e.ke + '/' + e.id + '/' + s.gid(5) + '.jpg'
|
||||
if(s.ocv){
|
||||
fs.writeFile(frameLocation,buffer,function(err){
|
||||
if(err){
|
||||
s.debugLog(err)
|
||||
}else{
|
||||
s.ocvTx({f:'frameFromRam',mon:s.group[e.ke].mon_conf[e.id].details,ke:e.ke,id:e.id,time:s.formattedTime(),frameLocation:frameLocation})
|
||||
}
|
||||
})
|
||||
}
|
||||
s.group[e.ke].mon[e.id].detectorFrameSaveBuffer = null;
|
||||
}
|
||||
})
|
||||
}
|
||||
}else if(s.isAtleatOneDetectorPluginConnected){
|
||||
s.group[e.ke].mon[e.id].spawn.stdio[3].on('data',function(d){
|
||||
s.ocvTx({f:'frame',mon:s.group[e.ke].mon_conf[e.id].details,ke:e.ke,id:e.id,time:s.formattedTime(),frame:d});
|
||||
})
|
||||
}
|
||||
}
|
||||
//frames to stream
|
||||
|
@ -862,7 +961,11 @@ module.exports = function(s,config,lang){
|
|||
break;
|
||||
}
|
||||
if(e.frameToStream){
|
||||
s.group[e.ke].mon[e.id].spawn.stdout.on('data',e.frameToStream)
|
||||
if(e.coProcessor === true && e.details.stream_type === ('b64'||'mjpeg')){
|
||||
|
||||
}else{
|
||||
s.group[e.ke].mon[e.id].spawn.stdout.on('data',e.frameToStream)
|
||||
}
|
||||
}
|
||||
if(e.details.stream_channels && e.details.stream_channels !== ''){
|
||||
var createStreamEmitter = function(channel,number){
|
||||
|
@ -908,6 +1011,13 @@ module.exports = function(s,config,lang){
|
|||
s.group[e.ke].mon[e.id].spawn.stderr.on('data',function(d){
|
||||
d=d.toString();
|
||||
switch(true){
|
||||
case checkLog(d,'No space left on device'):
|
||||
s.checkUserPurgeLock(e.ke)
|
||||
s.purgeDiskForGroup(e)
|
||||
break;
|
||||
case checkLog(d,'error while decoding'):
|
||||
s.userLog(e,{type:lang['Error While Decoding'],msg:lang.ErrorWhileDecodingText});
|
||||
break;
|
||||
case checkLog(d,'[hls @'):
|
||||
case checkLog(d,'Past duration'):
|
||||
case checkLog(d,'Last message repeated'):
|
||||
|
@ -935,10 +1045,11 @@ module.exports = function(s,config,lang){
|
|||
case checkLog(d,'mjpeg_decode_dc'):
|
||||
case checkLog(d,'bad vlc'):
|
||||
case checkLog(d,'error dc'):
|
||||
case checkLog(d,'No route to host'):
|
||||
s.launchMonitorProcesses(e)
|
||||
break;
|
||||
case /T[0-9][0-9]-[0-9][0-9]-[0-9][0-9]./.test(d):
|
||||
var filename = d.split('.')[0]+'.'+e.ext
|
||||
var filename = d.split('.')[0].split(' [')[0].trim()+'.'+e.ext
|
||||
s.insertCompletedVideo(e,{
|
||||
file : filename
|
||||
},function(err){
|
||||
|
@ -996,7 +1107,7 @@ module.exports = function(s,config,lang){
|
|||
e.detector_notrigger_timeout = parseFloat(e.details.detector_notrigger_timeout)*1000*60;
|
||||
s.group[e.ke].mon[e.id].detector_notrigger_timeout_function = function(){
|
||||
s.onDetectorNoTriggerTimeoutExtensions.forEach(function(extender){
|
||||
extender(r,e)
|
||||
extender(e)
|
||||
})
|
||||
}
|
||||
clearInterval(s.group[e.ke].mon[e.id].detector_notrigger_timeout)
|
||||
|
@ -1009,7 +1120,11 @@ module.exports = function(s,config,lang){
|
|||
if(s.group[e.ke].mon[e.id].isStarted === true){
|
||||
fs.stat(e.sdir+'s.jpg',function(err,snap){
|
||||
var notStreaming = function(){
|
||||
s.launchMonitorProcesses(e)
|
||||
if(e.coProcessor === true){
|
||||
s.coSpawnLauncher(e)
|
||||
}else{
|
||||
s.launchMonitorProcesses(e)
|
||||
}
|
||||
s.userLog(e,{type:lang['Camera is not streaming'],msg:{msg:lang['Restarting Process']}})
|
||||
s.orphanedVideoCheck(e,2,null,true)
|
||||
}
|
||||
|
@ -1034,13 +1149,6 @@ module.exports = function(s,config,lang){
|
|||
//check if ffmpeg is recording
|
||||
s.group[e.ke].mon[e.id].fswatch = fs.watch(e.dir, {encoding : 'utf8'}, (event, filename) => {
|
||||
switch(event){
|
||||
case'rename':
|
||||
try{
|
||||
s.group[e.ke].mon[e.id].open = filename.split('.')[0]
|
||||
}catch(err){
|
||||
s.debugLog('Failed to split filename : ',filename)
|
||||
}
|
||||
break;
|
||||
case'change':
|
||||
s.resetRecordingCheck(e)
|
||||
break;
|
||||
|
@ -1094,11 +1202,22 @@ module.exports = function(s,config,lang){
|
|||
){
|
||||
s.cameraFilterFfmpegLog(e)
|
||||
}
|
||||
if(e.coProcessor === true){
|
||||
setTimeout(function(){
|
||||
s.coSpawnLauncher(e)
|
||||
},6000)
|
||||
}
|
||||
s.onMonitorStartExtensions.forEach(function(extender){
|
||||
extender(Object.assign(s.group[e.ke].mon_conf[e.id],{}),e)
|
||||
})
|
||||
}else{
|
||||
s.onMonitorPingFailedExtensions.forEach(function(extender){
|
||||
extender(Object.assign(s.group[e.ke].mon_conf[e.id],{}),e)
|
||||
})
|
||||
s.userLog(e,{type:lang["Ping Failed"],msg:lang.skipPingText1});
|
||||
s.fatalCameraError(e,"Ping Failed");return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(
|
||||
e.type !== 'socket' &&
|
||||
e.type !== 'dashcam' &&
|
||||
|
@ -1136,6 +1255,9 @@ module.exports = function(s,config,lang){
|
|||
if(o.success === true){
|
||||
startVideoProcessor()
|
||||
}else{
|
||||
s.onMonitorPingFailedExtensions.forEach(function(extender){
|
||||
extender(Object.assign(s.group[e.ke].mon_conf[e.id],{}),e)
|
||||
})
|
||||
s.userLog(e,{type:lang["Ping Failed"],msg:lang.skipPingText1});
|
||||
s.fatalCameraError(e,"Ping Failed");return;
|
||||
}
|
||||
|
@ -1200,7 +1322,10 @@ module.exports = function(s,config,lang){
|
|||
}else{
|
||||
s.cameraDestroy(s.group[e.ke].mon[e.id].spawn,e)
|
||||
}
|
||||
s.sendMonitorStatus({id:e.id,ke:e.ke,status:lang.Died});
|
||||
s.sendMonitorStatus({id:e.id,ke:e.ke,status:lang.Died})
|
||||
s.onMonitorDiedExtensions.forEach(function(extender){
|
||||
extender(Object.assign(s.group[e.ke].mon_conf[e.id],{}),e)
|
||||
})
|
||||
}
|
||||
s.isWatchCountable = function(d){
|
||||
try{
|
||||
|
@ -1307,6 +1432,9 @@ module.exports = function(s,config,lang){
|
|||
}
|
||||
s.tx(txData,'GRP_'+form.ke)
|
||||
callback(!endData.ok,endData)
|
||||
s.onMonitorSaveExtensions.forEach(function(extender){
|
||||
extender(Object.assign(s.group[form.ke].mon_conf[form.mid],{}),form,endData)
|
||||
})
|
||||
})
|
||||
}
|
||||
s.camera = function(x,e,cn){
|
||||
|
@ -1399,6 +1527,9 @@ module.exports = function(s,config,lang){
|
|||
var wantedStatus = lang.Idle
|
||||
}
|
||||
s.sendMonitorStatus({id:e.id,ke:e.ke,status:wantedStatus})
|
||||
s.onMonitorStopExtensions.forEach(function(extender){
|
||||
extender(Object.assign(s.group[e.ke].mon_conf[e.id],{}),e)
|
||||
})
|
||||
break;
|
||||
case'start':case'record'://watch or record monitor url
|
||||
s.initiateMonitorObject({ke:e.ke,mid:e.id})
|
||||
|
@ -1420,8 +1551,8 @@ module.exports = function(s,config,lang){
|
|||
s.group[e.ke].mon[e.mid].isRecording = false
|
||||
}
|
||||
//set up fatal error handler
|
||||
if(e.details.fatal_max===''){
|
||||
e.details.fatal_max = 10
|
||||
if(e.details.fatal_max === ''){
|
||||
e.details.fatal_max = 0
|
||||
}else{
|
||||
e.details.fatal_max = parseFloat(e.details.fatal_max)
|
||||
}
|
||||
|
@ -1439,4 +1570,78 @@ module.exports = function(s,config,lang){
|
|||
}
|
||||
if(typeof cn === 'function'){setTimeout(function(){cn()},1000)}
|
||||
}
|
||||
//
|
||||
s.activateMonitorStates = function(groupKey,stateName,user,callback){
|
||||
var endData = {
|
||||
ok: false
|
||||
}
|
||||
s.findPreset([groupKey,'monitorStates',stateName],function(notFound,preset){
|
||||
if(notFound === false){
|
||||
var sqlQuery = 'SELECT * FROM Monitors WHERE ke=? AND '
|
||||
var monitorQuery = []
|
||||
var sqlQueryValues = [groupKey]
|
||||
var monitorPresets = {}
|
||||
preset.details.monitors.forEach(function(monitor){
|
||||
monitorQuery.push('mid=?')
|
||||
sqlQueryValues.push(monitor.mid)
|
||||
monitorPresets[monitor.mid] = monitor
|
||||
})
|
||||
sqlQuery += '('+monitorQuery.join(' OR ')+')'
|
||||
s.sqlQuery(sqlQuery,sqlQueryValues,function(err,monitors){
|
||||
if(monitors && monitors[0]){
|
||||
monitors.forEach(function(monitor){
|
||||
s.checkDetails(monitor)
|
||||
s.checkDetails(monitorPresets[monitor.mid])
|
||||
var monitorPreset = monitorPresets[monitor.mid]
|
||||
monitorPreset.details = Object.assign(monitor.details,monitorPreset.details)
|
||||
monitor = s.cleanMonitorObjectForDatabase(Object.assign(monitor,monitorPreset))
|
||||
monitor.details = JSON.stringify(monitor.details)
|
||||
s.addOrEditMonitor(Object.assign(monitor,{}),function(err,endData){
|
||||
|
||||
},user)
|
||||
})
|
||||
endData.ok = true
|
||||
s.tx({f:'change_group_state',ke:groupKey,name:stateName},'GRP_'+groupKey)
|
||||
callback(endData)
|
||||
}else{
|
||||
endData.msg = user.lang['State Configuration has no monitors associated']
|
||||
callback(endData)
|
||||
}
|
||||
})
|
||||
}else{
|
||||
endData.msg = user.lang['State Configuration Not Found']
|
||||
callback(endData)
|
||||
}
|
||||
})
|
||||
}
|
||||
s.getCamerasForMultiTrigger = function(monitor){
|
||||
var list={}
|
||||
var cameras=[]
|
||||
var group
|
||||
try{
|
||||
group=JSON.parse(monitor.details.group_detector_multi)
|
||||
if(!group){group=[]}
|
||||
}catch(err){
|
||||
group=[]
|
||||
}
|
||||
group.forEach(function(b){
|
||||
Object.keys(s.group[monitor.ke].mon_conf).forEach(function(v){
|
||||
try{
|
||||
var groups = JSON.parse(s.group[monitor.ke].mon_conf[v].details.groups)
|
||||
if(!groups){
|
||||
groups=[]
|
||||
}
|
||||
}catch(err){
|
||||
groups=[]
|
||||
}
|
||||
if(!list[v]&&groups.indexOf(b)>-1){
|
||||
list[v]={}
|
||||
if(s.group[monitor.ke].mon_conf[v].mode !== 'stop'){
|
||||
cameras.push(Object.assign({},s.group[monitor.ke].mon_conf[v]))
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
return cameras
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
var fs = require("fs")
|
||||
var Discord = require("discord.js")
|
||||
module.exports = function(s,config,lang){
|
||||
//discord bot
|
||||
|
@ -21,16 +22,27 @@ module.exports = function(s,config,lang){
|
|||
text: "Shinobi Systems"
|
||||
}
|
||||
},data)
|
||||
bot.channels.get(s.group[groupKey].init.discordbot_channel).send({
|
||||
embed: sendBody,
|
||||
files: files
|
||||
}).catch(err => {
|
||||
if(err){
|
||||
s.userLog({ke:groupKey,mid:'$USER'},{type:lang.DiscordErrorText,msg:err})
|
||||
s.group[groupKey].discordBot = null
|
||||
s.loadGroupApps({ke:groupKey})
|
||||
}
|
||||
})
|
||||
var discordChannel = bot.channels.get(s.group[groupKey].init.discordbot_channel)
|
||||
if(discordChannel && discordChannel.send){
|
||||
discordChannel.send({
|
||||
embed: sendBody,
|
||||
files: files
|
||||
}).catch(err => {
|
||||
if(err){
|
||||
s.userLog({ke:groupKey,mid:'$USER'},{type:lang.DiscordErrorText,msg:err})
|
||||
s.group[groupKey].discordBot = null
|
||||
s.loadGroupApps({ke:groupKey})
|
||||
}
|
||||
})
|
||||
}else{
|
||||
s.userLog({
|
||||
ke: groupKey,
|
||||
mid: '$USER'
|
||||
},{
|
||||
type: lang.DiscordErrorText,
|
||||
msg: 'Check the Channel ID'
|
||||
})
|
||||
}
|
||||
}
|
||||
var onEventTriggerBeforeFilterForDiscord = function(d,filter){
|
||||
filter.discord = true
|
||||
|
@ -131,7 +143,13 @@ module.exports = function(s,config,lang){
|
|||
){
|
||||
s.group[user.ke].discordBot = new Discord.Client()
|
||||
s.group[user.ke].discordBot.on('ready', () => {
|
||||
console.log(`${user.mail} : Discord Bot Logged in as ${s.group[user.ke].discordBot.user.tag}!`)
|
||||
s.userLog({
|
||||
ke: user.ke,
|
||||
mid: '$USER'
|
||||
},{
|
||||
type: lang.DiscordLoggedIn,
|
||||
msg: s.group[user.ke].discordBot.user.tag
|
||||
})
|
||||
})
|
||||
s.group[user.ke].discordBot.login(ar.discordbot_token)
|
||||
}
|
||||
|
|
|
@ -17,28 +17,76 @@ module.exports = function(s,config,lang){
|
|||
break;
|
||||
}
|
||||
}
|
||||
//multi plugin connections
|
||||
s.connectedPlugins={}
|
||||
s.connectedPlugins = {}
|
||||
s.connectedDetectorPlugins = {}
|
||||
s.detectorPluginArray = []
|
||||
s.isAtleatOneDetectorPluginConnected = false
|
||||
s.addDetectorPlugin = function(name,d){
|
||||
s.connectedDetectorPlugins[d.plug] = {
|
||||
started: s.timeObject(),
|
||||
id: d.id,
|
||||
plug: d.plug,
|
||||
notice: d.notice,
|
||||
connectionType: d.connectionType
|
||||
}
|
||||
s.resetDetectorPluginArray()
|
||||
}
|
||||
s.removeDetectorPlugin = function(name){
|
||||
delete(s.connectedDetectorPlugins[name])
|
||||
s.resetDetectorPluginArray(name)
|
||||
}
|
||||
s.resetDetectorPluginArray = function(){
|
||||
pluginArray = []
|
||||
Object.keys(s.connectedPlugins).forEach(function(name){
|
||||
var plugin = s.connectedPlugins[name]
|
||||
if(plugin.plugged === true && plugin.type === 'detector'){
|
||||
pluginArray.push(name)
|
||||
}
|
||||
})
|
||||
if(pluginArray.length > 0)s.isAtleatOneDetectorPluginConnected = true
|
||||
s.detectorPluginArray = pluginArray
|
||||
}
|
||||
s.sendToAllDetectors = function(data){
|
||||
s.detectorPluginArray.forEach(function(name){
|
||||
s.connectedPlugins[name].tx(data)
|
||||
})
|
||||
}
|
||||
s.sendDetectorInfoToClient = function(data,txFunction){
|
||||
s.detectorPluginArray.forEach(function(name){
|
||||
var detectorData = Object.assign(data,{
|
||||
notice: s.connectedDetectorPlugins[name].notice,
|
||||
plug: name
|
||||
})
|
||||
txFunction(detectorData)
|
||||
})
|
||||
}
|
||||
// s.sendToDetectorsInChain = function(){
|
||||
//
|
||||
// }
|
||||
s.pluginInitiatorSuccess=function(mode,d,cn){
|
||||
s.systemLog('pluginInitiatorSuccess',d)
|
||||
if(mode==='client'){
|
||||
//is in client mode (camera.js is client)
|
||||
cn.pluginEngine=d.plug
|
||||
if(!s.connectedPlugins[d.plug]){
|
||||
s.connectedPlugins[d.plug]={plug:d.plug}
|
||||
if(!s.connectedPlugins[d.plug]){
|
||||
s.connectedPlugins[d.plug]={
|
||||
plug: d.plug,
|
||||
type: d.type
|
||||
}
|
||||
}
|
||||
s.connectedPlugins[d.plug].plugged = true
|
||||
if(mode==='client'){
|
||||
s.connectedPlugins[d.plug].tx = function(x){return cn.emit('f',x)}
|
||||
//is in client mode (camera.js is client)
|
||||
cn.pluginEngine = d.plug
|
||||
s.systemLog('Connected to plugin : Detector - '+d.plug+' - '+d.type)
|
||||
switch(d.type){
|
||||
default:case'detector':
|
||||
s.ocv = {
|
||||
started: s.timeObject(),
|
||||
cn.detectorPlugin = d.plug
|
||||
s.addDetectorPlugin(d.plug,{
|
||||
id: cn.id,
|
||||
plug: d.plug,
|
||||
notice: d.notice,
|
||||
isClientPlugin: true,
|
||||
connectionType: d.connectionType
|
||||
};
|
||||
cn.ocv = 1;
|
||||
})
|
||||
s.tx({f:'detector_plugged',plug:d.plug,notice:d.notice},'CPU')
|
||||
break;
|
||||
}
|
||||
|
@ -46,21 +94,18 @@ module.exports = function(s,config,lang){
|
|||
//is in host mode (camera.js is client)
|
||||
switch(d.type){
|
||||
default:case'detector':
|
||||
s.ocv = {
|
||||
started:s.timeObject(),
|
||||
s.addDetectorPlugin(d.plug,{
|
||||
id:"host",
|
||||
plug:d.plug,
|
||||
notice:d.notice,
|
||||
isHostPlugin:true,
|
||||
connectionType: d.connectionType
|
||||
};
|
||||
})
|
||||
s.tx({f:'detector_plugged',plug:d.plug,notice:d.notice},'CPU')
|
||||
break;
|
||||
}
|
||||
}
|
||||
s.connectedPlugins[d.plug].plugged=true
|
||||
s.tx({f:'readPlugins',ke:d.ke},'CPU')
|
||||
s.ocvTx({f:'api_key',key:d.plug})
|
||||
s.api[d.plug]={pluginEngine:d.plug,permissions:{},details:{},ip:'0.0.0.0'};
|
||||
}
|
||||
s.pluginInitiatorFail=function(mode,d,cn){
|
||||
if(s.connectedPlugins[d.plug])s.connectedPlugins[d.plug].plugged=false
|
||||
|
@ -73,7 +118,10 @@ module.exports = function(s,config,lang){
|
|||
}
|
||||
if(config.plugins&&config.plugins.length>0){
|
||||
config.plugins.forEach(function(v){
|
||||
s.connectedPlugins[v.id]={plug:v.id}
|
||||
s.connectedPlugins[v.id]={
|
||||
plug: v.id,
|
||||
type: v.type
|
||||
}
|
||||
if(v.enabled===false){return}
|
||||
if(v.mode==='host'){
|
||||
//is in host mode (camera.js is client)
|
||||
|
@ -102,7 +150,13 @@ module.exports = function(s,config,lang){
|
|||
socket.on('ocv',s.pluginEventController);
|
||||
socket.on('disconnect', function(){
|
||||
s.connectedPlugins[v.id].plugged=false
|
||||
delete(s.api[v.id])
|
||||
if(v.type === 'detector'){
|
||||
s.tx({f:'detector_unplugged',plug:v.id},'CPU')
|
||||
s.removeDetectorPlugin(v.id)
|
||||
s.sendDetectorInfoToClient({f:'detector_plugged'},function(data){
|
||||
s.tx(data,'CPU')
|
||||
})
|
||||
}
|
||||
s.systemLog('Plugin Disconnected : '+v.id)
|
||||
s.connectedPlugins[v.id].reconnector = setInterval(function(){
|
||||
if(socket.connected===true){
|
||||
|
|
|
@ -7,6 +7,9 @@ module.exports = function(process,__dirname){
|
|||
});
|
||||
// [CTRL] + [C] = exit
|
||||
process.on('SIGINT', function() {
|
||||
s.onProcessExitExtensions.forEach(function(extender){
|
||||
extender()
|
||||
})
|
||||
console.log('Shinobi is Exiting...')
|
||||
process.exit();
|
||||
});
|
||||
|
|
23
libs/rtmpserver.js
Normal file
23
libs/rtmpserver.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
module.exports = function(s,config,lang){
|
||||
if(config.rtmpServer){
|
||||
var defaultRtmpServerConfig = {
|
||||
port: 1935,
|
||||
chunk_size: 60000,
|
||||
gop_cache: true,
|
||||
ping: 60,
|
||||
ping_timeout: 30
|
||||
}
|
||||
var runningRtmpServerConfig
|
||||
if(config.rtmpServer instanceof Object === 'false'){
|
||||
runningRtmpServerConfig = defaultRtmpServerConfig
|
||||
}else{
|
||||
runningRtmpServerConfig = Object.assign(defaultRtmpServerConfig,config.rtmpServer)
|
||||
}
|
||||
s.systemLog(`RTMP Server Running on port ${runningRtmpServerConfig.port}...`)
|
||||
var NodeRtmpServer = require('./rtmpserver/node_rtmp_server')
|
||||
var nmcs = new NodeRtmpServer({
|
||||
rtmp: runningRtmpServerConfig
|
||||
})
|
||||
nmcs.run()
|
||||
}
|
||||
}
|
1184
libs/rtmpserver/node_core_amf.js
Normal file
1184
libs/rtmpserver/node_core_amf.js
Normal file
File diff suppressed because it is too large
Load diff
501
libs/rtmpserver/node_core_av.js
Normal file
501
libs/rtmpserver/node_core_av.js
Normal file
|
@ -0,0 +1,501 @@
|
|||
//
|
||||
// Created by Mingliang Chen on 17/12/21.
|
||||
// illuspas[a]gmail.com
|
||||
// Copyright (c) 2018 Nodemedia. All rights reserved.
|
||||
//
|
||||
|
||||
const Bitop = require('./node_core_bitop');
|
||||
const AAC_SAMPLE_RATE = [
|
||||
96000, 88200, 64000, 48000,
|
||||
44100, 32000, 24000, 22050,
|
||||
16000, 12000, 11025, 8000,
|
||||
7350, 0, 0, 0
|
||||
];
|
||||
|
||||
const AAC_CHANNELS = [
|
||||
0, 1, 2, 3, 4, 5, 6, 8
|
||||
];
|
||||
|
||||
const AUDIO_CODEC_NAME = [
|
||||
'',
|
||||
'ADPCM',
|
||||
'MP3',
|
||||
'LinearLE',
|
||||
'Nellymoser16',
|
||||
'Nellymoser8',
|
||||
'Nellymoser',
|
||||
'G711A',
|
||||
'G711U',
|
||||
'',
|
||||
'AAC',
|
||||
'Speex',
|
||||
'',
|
||||
'',
|
||||
'MP3-8K',
|
||||
'DeviceSpecific',
|
||||
'Uncompressed'
|
||||
];
|
||||
|
||||
const AUDIO_SOUND_RATE = [
|
||||
5512, 11025, 22050, 44100
|
||||
];
|
||||
|
||||
const VIDEO_CODEC_NAME = [
|
||||
'',
|
||||
'Jpeg',
|
||||
'Sorenson-H263',
|
||||
'ScreenVideo',
|
||||
'On2-VP6',
|
||||
'On2-VP6-Alpha',
|
||||
'ScreenVideo2',
|
||||
'H264',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'H265'
|
||||
];
|
||||
|
||||
function getObjectType(bitop) {
|
||||
let audioObjectType = bitop.read(5);
|
||||
if (audioObjectType === 31) {
|
||||
audioObjectType = bitop.read(6) + 32;
|
||||
}
|
||||
return audioObjectType;
|
||||
}
|
||||
|
||||
function getSampleRate(bitop, info) {
|
||||
info.sampling_index = bitop.read(4);
|
||||
return info.sampling_index == 0x0f ? bitop.read(24) : AAC_SAMPLE_RATE[info.sampling_index];
|
||||
}
|
||||
|
||||
function readAACSpecificConfig(aacSequenceHeader) {
|
||||
let info = {};
|
||||
let bitop = new Bitop(aacSequenceHeader);
|
||||
bitop.read(16);
|
||||
info.object_type = getObjectType(bitop);
|
||||
info.sample_rate = getSampleRate(bitop, info);
|
||||
info.chan_config = bitop.read(4);
|
||||
if (info.chan_config < AAC_CHANNELS.length) {
|
||||
info.channels = AAC_CHANNELS[info.chan_config];
|
||||
}
|
||||
info.sbr = -1;
|
||||
info.ps = -1;
|
||||
if (info.object_type == 5 || info.object_type == 29) {
|
||||
if (info.object_type == 29) {
|
||||
info.ps = 1;
|
||||
}
|
||||
info.ext_object_type = 5;
|
||||
info.sbr = 1;
|
||||
info.sample_rate = getSampleRate(bitop, info);
|
||||
info.object_type = getObjectType(bitop);
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
function getAACProfileName(info) {
|
||||
switch (info.object_type) {
|
||||
case 1:
|
||||
return 'Main';
|
||||
case 2:
|
||||
if (info.ps > 0) {
|
||||
return 'HEv2';
|
||||
}
|
||||
if (info.sbr > 0) {
|
||||
return 'HE';
|
||||
}
|
||||
return 'LC';
|
||||
case 3:
|
||||
return 'SSR';
|
||||
case 4:
|
||||
return 'LTP';
|
||||
case 5:
|
||||
return 'SBR';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function readH264SpecificConfig(avcSequenceHeader) {
|
||||
let info = {};
|
||||
let profile_idc, width, height, crop_left, crop_right,
|
||||
crop_top, crop_bottom, frame_mbs_only, n, cf_idc,
|
||||
num_ref_frames;
|
||||
let bitop = new Bitop(avcSequenceHeader);
|
||||
bitop.read(48);
|
||||
info.width = 0;
|
||||
info.height = 0;
|
||||
|
||||
do {
|
||||
info.profile = bitop.read(8);
|
||||
info.compat = bitop.read(8);
|
||||
info.level = bitop.read(8);
|
||||
info.nalu = (bitop.read(8) & 0x03) + 1;
|
||||
info.nb_sps = bitop.read(8) & 0x1F;
|
||||
if (info.nb_sps == 0) {
|
||||
break;
|
||||
}
|
||||
/* nal size */
|
||||
bitop.read(16);
|
||||
|
||||
/* nal type */
|
||||
if (bitop.read(8) != 0x67) {
|
||||
break;
|
||||
}
|
||||
/* SPS */
|
||||
profile_idc = bitop.read(8);
|
||||
|
||||
/* flags */
|
||||
bitop.read(8);
|
||||
|
||||
/* level idc */
|
||||
bitop.read(8);
|
||||
|
||||
/* SPS id */
|
||||
bitop.read_golomb();
|
||||
|
||||
if (profile_idc == 100 || profile_idc == 110 ||
|
||||
profile_idc == 122 || profile_idc == 244 || profile_idc == 44 ||
|
||||
profile_idc == 83 || profile_idc == 86 || profile_idc == 118) {
|
||||
/* chroma format idc */
|
||||
cf_idc = bitop.read_golomb();
|
||||
|
||||
if (cf_idc == 3) {
|
||||
|
||||
/* separate color plane */
|
||||
bitop.read(1);
|
||||
}
|
||||
|
||||
/* bit depth luma - 8 */
|
||||
bitop.read_golomb();
|
||||
|
||||
/* bit depth chroma - 8 */
|
||||
bitop.read_golomb();
|
||||
|
||||
/* qpprime y zero transform bypass */
|
||||
bitop.read(1);
|
||||
|
||||
/* seq scaling matrix present */
|
||||
if (bitop.read(1)) {
|
||||
|
||||
for (n = 0; n < (cf_idc != 3 ? 8 : 12); n++) {
|
||||
|
||||
/* seq scaling list present */
|
||||
if (bitop.read(1)) {
|
||||
|
||||
/* TODO: scaling_list()
|
||||
if (n < 6) {
|
||||
} else {
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* log2 max frame num */
|
||||
bitop.read_golomb();
|
||||
|
||||
/* pic order cnt type */
|
||||
switch (bitop.read_golomb()) {
|
||||
case 0:
|
||||
|
||||
/* max pic order cnt */
|
||||
bitop.read_golomb();
|
||||
break;
|
||||
|
||||
case 1:
|
||||
|
||||
/* delta pic order alwys zero */
|
||||
bitop.read(1);
|
||||
|
||||
/* offset for non-ref pic */
|
||||
bitop.read_golomb();
|
||||
|
||||
/* offset for top to bottom field */
|
||||
bitop.read_golomb();
|
||||
|
||||
/* num ref frames in pic order */
|
||||
num_ref_frames = bitop.read_golomb();
|
||||
|
||||
for (n = 0; n < num_ref_frames; n++) {
|
||||
|
||||
/* offset for ref frame */
|
||||
bitop.read_golomb();
|
||||
}
|
||||
}
|
||||
|
||||
/* num ref frames */
|
||||
info.avc_ref_frames = bitop.read_golomb();
|
||||
|
||||
/* gaps in frame num allowed */
|
||||
bitop.read(1);
|
||||
|
||||
/* pic width in mbs - 1 */
|
||||
width = bitop.read_golomb();
|
||||
|
||||
/* pic height in map units - 1 */
|
||||
height = bitop.read_golomb();
|
||||
|
||||
/* frame mbs only flag */
|
||||
frame_mbs_only = bitop.read(1);
|
||||
|
||||
if (!frame_mbs_only) {
|
||||
|
||||
/* mbs adaprive frame field */
|
||||
bitop.read(1);
|
||||
}
|
||||
|
||||
/* direct 8x8 inference flag */
|
||||
bitop.read(1);
|
||||
|
||||
/* frame cropping */
|
||||
if (bitop.read(1)) {
|
||||
|
||||
crop_left = bitop.read_golomb();
|
||||
crop_right = bitop.read_golomb();
|
||||
crop_top = bitop.read_golomb();
|
||||
crop_bottom = bitop.read_golomb();
|
||||
|
||||
} else {
|
||||
crop_left = 0;
|
||||
crop_right = 0;
|
||||
crop_top = 0;
|
||||
crop_bottom = 0;
|
||||
}
|
||||
info.level = info.level / 10.0;
|
||||
info.width = (width + 1) * 16 - (crop_left + crop_right) * 2;
|
||||
info.height = (2 - frame_mbs_only) * (height + 1) * 16 - (crop_top + crop_bottom) * 2;
|
||||
|
||||
} while (0);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
function HEVCParsePtl(bitop, hevc, max_sub_layers_minus1) {
|
||||
let general_ptl = {};
|
||||
|
||||
general_ptl.profile_space = bitop.read(2);
|
||||
general_ptl.tier_flag = bitop.read(1);
|
||||
general_ptl.profile_idc = bitop.read(5);
|
||||
general_ptl.profile_compatibility_flags = bitop.read(32);
|
||||
general_ptl.general_progressive_source_flag = bitop.read(1);
|
||||
general_ptl.general_interlaced_source_flag = bitop.read(1);
|
||||
general_ptl.general_non_packed_constraint_flag = bitop.read(1);
|
||||
general_ptl.general_frame_only_constraint_flag = bitop.read(1);
|
||||
bitop.read(32);
|
||||
bitop.read(12);
|
||||
general_ptl.level_idc = bitop.read(8);
|
||||
|
||||
general_ptl.sub_layer_profile_present_flag = [];
|
||||
general_ptl.sub_layer_level_present_flag = [];
|
||||
|
||||
for (let i = 0; i < max_sub_layers_minus1; i++) {
|
||||
general_ptl.sub_layer_profile_present_flag[i] = bitop.read(1);
|
||||
general_ptl.sub_layer_level_present_flag[i] = bitop.read(1);
|
||||
}
|
||||
|
||||
if (max_sub_layers_minus1 > 0) {
|
||||
for (let i = max_sub_layers_minus1; i < 8; i++) {
|
||||
bitop.read(2)
|
||||
}
|
||||
}
|
||||
|
||||
general_ptl.sub_layer_profile_space = [];
|
||||
general_ptl.sub_layer_tier_flag = [];
|
||||
general_ptl.sub_layer_profile_idc = [];
|
||||
general_ptl.sub_layer_profile_compatibility_flag = [];
|
||||
general_ptl.sub_layer_progressive_source_flag = [];
|
||||
general_ptl.sub_layer_interlaced_source_flag = [];
|
||||
general_ptl.sub_layer_non_packed_constraint_flag = [];
|
||||
general_ptl.sub_layer_frame_only_constraint_flag = [];
|
||||
general_ptl.sub_layer_level_idc = [];
|
||||
|
||||
for (let i = 0; i < max_sub_layers_minus1; i++) {
|
||||
if (general_ptl.sub_layer_profile_present_flag[i]) {
|
||||
general_ptl.sub_layer_profile_space[i] = bitop.read(2);
|
||||
general_ptl.sub_layer_tier_flag[i] = bitop.read(1);
|
||||
general_ptl.sub_layer_profile_idc[i] = bitop.read(5);
|
||||
general_ptl.sub_layer_profile_compatibility_flag[i] = bitop.read(32);
|
||||
general_ptl.sub_layer_progressive_source_flag[i] = bitop.read(1);
|
||||
general_ptl.sub_layer_interlaced_source_flag[i] = bitop.read(1);
|
||||
general_ptl.sub_layer_non_packed_constraint_flag[i] = bitop.read(1);
|
||||
general_ptl.sub_layer_frame_only_constraint_flag[i] = bitop.read(1);
|
||||
bitop.read(32);
|
||||
bitop.read(12);
|
||||
}
|
||||
if (general_ptl.sub_layer_level_present_flag[i]) {
|
||||
general_ptl.sub_layer_level_idc[i] = bitop.read(8);
|
||||
}
|
||||
else {
|
||||
general_ptl.sub_layer_level_idc[i] = 1;
|
||||
}
|
||||
}
|
||||
return general_ptl;
|
||||
}
|
||||
|
||||
function HEVCParseSPS(SPS, hevc) {
|
||||
let psps = {};
|
||||
let NumBytesInNALunit = SPS.length;
|
||||
let NumBytesInRBSP = 0;
|
||||
let rbsp_array = [];
|
||||
let bitop = new Bitop(SPS);
|
||||
|
||||
bitop.read(1);//forbidden_zero_bit
|
||||
bitop.read(6);//nal_unit_type
|
||||
bitop.read(6);//nuh_reserved_zero_6bits
|
||||
bitop.read(3);//nuh_temporal_id_plus1
|
||||
|
||||
for (let i = 2; i < NumBytesInNALunit; i++) {
|
||||
if (i + 2 < NumBytesInNALunit && bitop.look(24) == 0x000003) {
|
||||
rbsp_array.push(bitop.read(8));
|
||||
rbsp_array.push(bitop.read(8));
|
||||
i += 2;
|
||||
let emulation_prevention_three_byte = bitop.read(8); /* equal to 0x03 */
|
||||
} else {
|
||||
rbsp_array.push(bitop.read(8));
|
||||
}
|
||||
}
|
||||
let rbsp = Buffer.from(rbsp_array);
|
||||
let rbspBitop = new Bitop(rbsp);
|
||||
psps.sps_video_parameter_set_id = rbspBitop.read(4);
|
||||
psps.sps_max_sub_layers_minus1 = rbspBitop.read(3);
|
||||
psps.sps_temporal_id_nesting_flag = rbspBitop.read(1);
|
||||
psps.profile_tier_level = HEVCParsePtl(rbspBitop, hevc, psps.sps_max_sub_layers_minus1);
|
||||
psps.sps_seq_parameter_set_id = rbspBitop.read_golomb();
|
||||
psps.chroma_format_idc = rbspBitop.read_golomb();
|
||||
if (psps.chroma_format_idc == 3) {
|
||||
psps.separate_colour_plane_flag = rbspBitop.read(1);
|
||||
} else {
|
||||
psps.separate_colour_plane_flag = 0;
|
||||
}
|
||||
psps.pic_width_in_luma_samples = rbspBitop.read_golomb();
|
||||
psps.pic_height_in_luma_samples = rbspBitop.read_golomb();
|
||||
psps.conformance_window_flag = rbspBitop.read(1);
|
||||
if (psps.conformance_window_flag) {
|
||||
let vert_mult = 1 + (psps.chroma_format_idc < 2);
|
||||
let horiz_mult = 1 + (psps.chroma_format_idc < 3);
|
||||
psps.conf_win_left_offset = rbspBitop.read_golomb() * horiz_mult;
|
||||
psps.conf_win_right_offset = rbspBitop.read_golomb() * horiz_mult;
|
||||
psps.conf_win_top_offset = rbspBitop.read_golomb() * vert_mult;
|
||||
psps.conf_win_bottom_offset = rbspBitop.read_golomb() * vert_mult;
|
||||
}
|
||||
// Logger.debug(psps);
|
||||
return psps;
|
||||
}
|
||||
|
||||
function readHEVCSpecificConfig(hevcSequenceHeader) {
|
||||
let info = {};
|
||||
info.width = 0;
|
||||
info.height = 0;
|
||||
info.profile = 0;
|
||||
info.level = 0;
|
||||
// let bitop = new Bitop(hevcSequenceHeader);
|
||||
// bitop.read(48);
|
||||
hevcSequenceHeader = hevcSequenceHeader.slice(5);
|
||||
|
||||
do {
|
||||
let hevc = {};
|
||||
if (hevcSequenceHeader.length < 23) {
|
||||
break;
|
||||
}
|
||||
|
||||
hevc.configurationVersion = hevcSequenceHeader[0];
|
||||
if (hevc.configurationVersion != 1) {
|
||||
break;
|
||||
}
|
||||
hevc.general_profile_space = (hevcSequenceHeader[1] >> 6) & 0x03;
|
||||
hevc.general_tier_flag = (hevcSequenceHeader[1] >> 5) & 0x01;
|
||||
hevc.general_profile_idc = hevcSequenceHeader[1] & 0x1F;
|
||||
hevc.general_profile_compatibility_flags = (hevcSequenceHeader[2] << 24) | (hevcSequenceHeader[3] << 16) | (hevcSequenceHeader[4] << 8) | hevcSequenceHeader[5];
|
||||
hevc.general_constraint_indicator_flags = ((hevcSequenceHeader[6] << 24) | (hevcSequenceHeader[7] << 16) | (hevcSequenceHeader[8] << 8) | hevcSequenceHeader[9]);
|
||||
hevc.general_constraint_indicator_flags = (hevc.general_constraint_indicator_flags << 16) | (hevcSequenceHeader[10] << 8) | hevcSequenceHeader[11];
|
||||
hevc.general_level_idc = hevcSequenceHeader[12];
|
||||
hevc.min_spatial_segmentation_idc = ((hevcSequenceHeader[13] & 0x0F) << 8) | hevcSequenceHeader[14];
|
||||
hevc.parallelismType = hevcSequenceHeader[15] & 0x03;
|
||||
hevc.chromaFormat = hevcSequenceHeader[16] & 0x03;
|
||||
hevc.bitDepthLumaMinus8 = hevcSequenceHeader[17] & 0x07;
|
||||
hevc.bitDepthChromaMinus8 = hevcSequenceHeader[18] & 0x07;
|
||||
hevc.avgFrameRate = (hevcSequenceHeader[19] << 8) | hevcSequenceHeader[20];
|
||||
hevc.constantFrameRate = (hevcSequenceHeader[21] >> 6) & 0x03;
|
||||
hevc.numTemporalLayers = (hevcSequenceHeader[21] >> 3) & 0x07;
|
||||
hevc.temporalIdNested = (hevcSequenceHeader[21] >> 2) & 0x01;
|
||||
hevc.lengthSizeMinusOne = hevcSequenceHeader[21] & 0x03;
|
||||
let numOfArrays = hevcSequenceHeader[22];
|
||||
let p = hevcSequenceHeader.slice(23);
|
||||
for (let i = 0; i < numOfArrays; i++) {
|
||||
if (p.length < 3) {
|
||||
brak;
|
||||
}
|
||||
let nalutype = p[0];
|
||||
let n = (p[1]) << 8 | p[2];
|
||||
// Logger.debug(nalutype, n);
|
||||
p = p.slice(3);
|
||||
for (let j = 0; j < n; j++) {
|
||||
if (p.length < 2) {
|
||||
break;
|
||||
}
|
||||
k = (p[0] << 8) | p[1];
|
||||
// Logger.debug('k', k);
|
||||
if (p.length < 2 + k) {
|
||||
break;
|
||||
}
|
||||
p = p.slice(2);
|
||||
if (nalutype == 33) {
|
||||
//SPS
|
||||
let sps = Buffer.alloc(k);
|
||||
p.copy(sps, 0, 0, k);
|
||||
// Logger.debug(sps, sps.length);
|
||||
hevc.psps = HEVCParseSPS(sps, hevc);
|
||||
info.profile = hevc.general_profile_idc;
|
||||
info.level = hevc.general_level_idc / 30.0;
|
||||
info.width = hevc.psps.pic_width_in_luma_samples - (hevc.psps.conf_win_left_offset + hevc.psps.conf_win_right_offset);
|
||||
info.height = hevc.psps.pic_height_in_luma_samples - (hevc.psps.conf_win_top_offset + hevc.psps.conf_win_bottom_offset);
|
||||
}
|
||||
p = p.slice(k);
|
||||
}
|
||||
}
|
||||
} while (0);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
function readAVCSpecificConfig(avcSequenceHeader) {
|
||||
let codec_id = avcSequenceHeader[0] & 0x0f;
|
||||
if (codec_id == 7) {
|
||||
return readH264SpecificConfig(avcSequenceHeader);
|
||||
} else if (codec_id == 12) {
|
||||
return readHEVCSpecificConfig(avcSequenceHeader);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getAVCProfileName(info) {
|
||||
switch (info.profile) {
|
||||
case 1:
|
||||
return 'Main';
|
||||
case 2:
|
||||
return 'Main 10';
|
||||
case 3:
|
||||
return 'Main Still Picture';
|
||||
case 66:
|
||||
return 'Baseline';
|
||||
case 77:
|
||||
return 'Main';
|
||||
case 100:
|
||||
return 'High';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
AUDIO_SOUND_RATE,
|
||||
AUDIO_CODEC_NAME,
|
||||
VIDEO_CODEC_NAME,
|
||||
readAACSpecificConfig,
|
||||
getAACProfileName,
|
||||
readAVCSpecificConfig,
|
||||
getAVCProfileName,
|
||||
};
|
54
libs/rtmpserver/node_core_bitop.js
Normal file
54
libs/rtmpserver/node_core_bitop.js
Normal file
|
@ -0,0 +1,54 @@
|
|||
|
||||
class Bitop {
|
||||
constructor(buffer) {
|
||||
this.buffer = buffer;
|
||||
this.buflen = buffer.length;
|
||||
this.bufpos = 0;
|
||||
this.bufoff = 0;
|
||||
this.iserro = false;
|
||||
}
|
||||
|
||||
read(n) {
|
||||
let v = 0;
|
||||
let d = 0;
|
||||
while (n) {
|
||||
if (n < 0 || this.bufpos >= this.buflen) {
|
||||
this.iserro = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
this.iserro = false;
|
||||
d = this.bufoff + n > 8 ? 8 - this.bufoff : n;
|
||||
|
||||
v <<= d;
|
||||
v += (this.buffer[this.bufpos] >> (8 - this.bufoff - d)) & (0xff >> (8 - d))
|
||||
|
||||
this.bufoff += d;
|
||||
n -= d;
|
||||
|
||||
if (this.bufoff == 8) {
|
||||
this.bufpos++;
|
||||
this.bufoff = 0;
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
look(n) {
|
||||
let p = this.bufpos;
|
||||
let o = this.bufoff;
|
||||
let v = this.read(n);
|
||||
this.bufpos = p;
|
||||
this.bufoff = o;
|
||||
return v;
|
||||
}
|
||||
|
||||
read_golomb() {
|
||||
let n;
|
||||
for (n = 0; this.read(1) == 0 && !this.iserro; n++);
|
||||
return (1 << n) + this.read(n) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Bitop;
|
||||
|
17
libs/rtmpserver/node_core_ctx.js
Normal file
17
libs/rtmpserver/node_core_ctx.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
//
|
||||
// Created by Mingliang Chen on 18/3/2.
|
||||
// illuspas[a]gmail.com
|
||||
// Copyright (c) 2018 Nodemedia. All rights reserved.
|
||||
//
|
||||
const EventEmitter = require('events');
|
||||
|
||||
let sessions = new Map();
|
||||
let publishers = new Map();
|
||||
let idlePlayers = new Set();
|
||||
let nodeEvent = new EventEmitter();
|
||||
let stat = {
|
||||
inbytes: 0,
|
||||
outbytes: 0,
|
||||
accepted: 0
|
||||
};
|
||||
module.exports = { sessions, publishers, idlePlayers, nodeEvent, stat };
|
53
libs/rtmpserver/node_core_logger.js
Normal file
53
libs/rtmpserver/node_core_logger.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
const chalk = require('chalk');
|
||||
|
||||
LOG_TYPES = {
|
||||
NONE: 0,
|
||||
ERROR: 1,
|
||||
NORMAL: 2,
|
||||
DEBUG: 3,
|
||||
FFDEBUG: 4
|
||||
};
|
||||
|
||||
let logType = LOG_TYPES.NORMAL;
|
||||
|
||||
const setLogType = (type) => {
|
||||
if (typeof type !== 'number') return;
|
||||
|
||||
logType = type;
|
||||
};
|
||||
|
||||
const logTime = () => {
|
||||
let nowDate = new Date();
|
||||
return nowDate.toLocaleDateString() + ' ' + nowDate.toLocaleTimeString([], { hour12: false });
|
||||
};
|
||||
|
||||
const log = (...args) => {
|
||||
if (logType < LOG_TYPES.NORMAL) return;
|
||||
|
||||
console.log(logTime(), process.pid, chalk.bold.green('[INFO]'), ...args);
|
||||
};
|
||||
|
||||
const error = (...args) => {
|
||||
if (logType < LOG_TYPES.ERROR) return;
|
||||
|
||||
console.log(logTime(), process.pid, chalk.bold.red('[ERROR]'), ...args);
|
||||
};
|
||||
|
||||
const debug = (...args) => {
|
||||
if (logType < LOG_TYPES.DEBUG) return;
|
||||
|
||||
console.log(logTime(), process.pid, chalk.bold.blue('[DEBUG]'), ...args);
|
||||
};
|
||||
|
||||
const ffdebug = (...args) => {
|
||||
if (logType < LOG_TYPES.FFDEBUG) return;
|
||||
|
||||
console.log(logTime(), process.pid, chalk.bold.blue('[FFDEBUG]'), ...args);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
LOG_TYPES,
|
||||
setLogType,
|
||||
|
||||
log, error, debug, ffdebug
|
||||
}
|
95
libs/rtmpserver/node_core_utils.js
Normal file
95
libs/rtmpserver/node_core_utils.js
Normal file
|
@ -0,0 +1,95 @@
|
|||
//
|
||||
// Created by Mingliang Chen on 17/8/23.
|
||||
// illuspas[a]gmail.com
|
||||
// Copyright (c) 2018 Nodemedia. All rights reserved.
|
||||
//
|
||||
const Crypto = require('crypto');
|
||||
const EventEmitter = require('events');
|
||||
const { spawn } = require('child_process');
|
||||
const readline = require('readline');
|
||||
const context = require('./node_core_ctx');
|
||||
|
||||
function generateNewSessionID() {
|
||||
let sessionID = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWKYZ0123456789';
|
||||
const numPossible = possible.length;
|
||||
do {
|
||||
for (let i = 0; i < 8; i++) {
|
||||
sessionID += possible.charAt((Math.random() * numPossible) | 0);
|
||||
}
|
||||
} while (context.sessions.has(sessionID))
|
||||
return sessionID;
|
||||
}
|
||||
|
||||
function genRandomName() {
|
||||
let name = '';
|
||||
const possible = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||
const numPossible = possible.length;
|
||||
for (let i = 0; i < 4; i++) {
|
||||
name += possible.charAt((Math.random() * numPossible) | 0);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
function verifyAuth(signStr, streamId, secretKey) {
|
||||
if (signStr === undefined) {
|
||||
return false;
|
||||
}
|
||||
let now = Date.now() / 1000 | 0;
|
||||
let exp = parseInt(signStr.split('-')[0]);
|
||||
let shv = signStr.split('-')[1];
|
||||
let str = streamId + '-' + exp + '-' + secretKey;
|
||||
if (exp < now) {
|
||||
return false;
|
||||
}
|
||||
let md5 = Crypto.createHash('md5');
|
||||
let ohv = md5.update(str).digest('hex');
|
||||
return shv === ohv;
|
||||
}
|
||||
|
||||
function getFFmpegVersion(ffpath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let ffmpeg_exec = spawn(ffpath, ['-version']);
|
||||
let version = '';
|
||||
ffmpeg_exec.on('error', (e) => {
|
||||
reject(e);
|
||||
});
|
||||
ffmpeg_exec.stdout.on('data', (data) => {
|
||||
try {
|
||||
version = data.toString().split(/(?:\r\n|\r|\n)/g)[0].split('\ ')[2];
|
||||
} catch (e) {
|
||||
}
|
||||
});
|
||||
ffmpeg_exec.on('close', (code) => {
|
||||
resolve(version);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getFFmpegUrl() {
|
||||
let url = '';
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
url = 'https://ffmpeg.zeranoe.com/builds/macos64/static/ffmpeg-latest-macos64-static.zip';
|
||||
break;
|
||||
case 'win32':
|
||||
url = 'https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-latest-win64-static.zip | https://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-latest-win32-static.zip';
|
||||
break;
|
||||
case 'linux':
|
||||
url = 'https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-64bit-static.tar.xz | https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-32bit-static.tar.xz';
|
||||
break;
|
||||
default:
|
||||
url = 'http://ffmpeg.org/download.html';
|
||||
break;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generateNewSessionID,
|
||||
verifyAuth,
|
||||
genRandomName,
|
||||
getFFmpegVersion,
|
||||
getFFmpegUrl
|
||||
}
|
113
libs/rtmpserver/node_rtmp_handshake.js
Normal file
113
libs/rtmpserver/node_rtmp_handshake.js
Normal file
|
@ -0,0 +1,113 @@
|
|||
//
|
||||
// Created by Mingliang Chen on 17/8/1.
|
||||
// illuspas[a]gmail.com
|
||||
// Copyright (c) 2018 Nodemedia. All rights reserved.
|
||||
//
|
||||
// const Logger = require('./node_core_logger');
|
||||
|
||||
const Crypto = require('crypto');
|
||||
|
||||
const MESSAGE_FORMAT_0 = 0;
|
||||
const MESSAGE_FORMAT_1 = 1;
|
||||
const MESSAGE_FORMAT_2 = 2;
|
||||
|
||||
const RTMP_SIG_SIZE = 1536;
|
||||
const SHA256DL = 32;
|
||||
|
||||
const RandomCrud = Buffer.from([
|
||||
0xf0, 0xee, 0xc2, 0x4a, 0x80, 0x68, 0xbe, 0xe8,
|
||||
0x2e, 0x00, 0xd0, 0xd1, 0x02, 0x9e, 0x7e, 0x57,
|
||||
0x6e, 0xec, 0x5d, 0x2d, 0x29, 0x80, 0x6f, 0xab,
|
||||
0x93, 0xb8, 0xe6, 0x36, 0xcf, 0xeb, 0x31, 0xae
|
||||
])
|
||||
|
||||
const GenuineFMSConst = 'Genuine Adobe Flash Media Server 001';
|
||||
const GenuineFMSConstCrud = Buffer.concat([Buffer.from(GenuineFMSConst, 'utf8'), RandomCrud]);
|
||||
|
||||
const GenuineFPConst = 'Genuine Adobe Flash Player 001';
|
||||
const GenuineFPConstCrud = Buffer.concat([Buffer.from(GenuineFPConst, 'utf8'), RandomCrud]);
|
||||
|
||||
function calcHmac(data, key) {
|
||||
let hmac = Crypto.createHmac('sha256', key);
|
||||
hmac.update(data);
|
||||
return hmac.digest();
|
||||
}
|
||||
|
||||
function GetClientGenuineConstDigestOffset(buf) {
|
||||
let offset = buf[0] + buf[1] + buf[2] + buf[3];
|
||||
offset = (offset % 728) + 12;
|
||||
return offset;
|
||||
}
|
||||
|
||||
function GetServerGenuineConstDigestOffset(buf) {
|
||||
let offset = buf[0] + buf[1] + buf[2] + buf[3];
|
||||
offset = (offset % 728) + 776;
|
||||
return offset;
|
||||
}
|
||||
|
||||
function detectClientMessageFormat(clientsig) {
|
||||
let computedSignature, msg, providedSignature, sdl;
|
||||
sdl = GetServerGenuineConstDigestOffset(clientsig.slice(772, 776));
|
||||
msg = Buffer.concat([clientsig.slice(0, sdl), clientsig.slice(sdl + SHA256DL)], 1504);
|
||||
computedSignature = calcHmac(msg, GenuineFPConst);
|
||||
providedSignature = clientsig.slice(sdl, sdl + SHA256DL);
|
||||
if (computedSignature.equals(providedSignature)) {
|
||||
return MESSAGE_FORMAT_2;
|
||||
}
|
||||
sdl = GetClientGenuineConstDigestOffset(clientsig.slice(8, 12));
|
||||
msg = Buffer.concat([clientsig.slice(0, sdl), clientsig.slice(sdl + SHA256DL)], 1504);
|
||||
computedSignature = calcHmac(msg, GenuineFPConst);
|
||||
providedSignature = clientsig.slice(sdl, sdl + SHA256DL);
|
||||
if (computedSignature.equals(providedSignature)) {
|
||||
return MESSAGE_FORMAT_1;
|
||||
}
|
||||
return MESSAGE_FORMAT_0;
|
||||
}
|
||||
|
||||
function generateS1(messageFormat) {
|
||||
let randomBytes = Crypto.randomBytes(RTMP_SIG_SIZE - 8);
|
||||
let handshakeBytes = Buffer.concat([Buffer.from([0, 0, 0, 0, 1, 2, 3, 4]), randomBytes], RTMP_SIG_SIZE);
|
||||
|
||||
let serverDigestOffset
|
||||
if (messageFormat === 1) {
|
||||
serverDigestOffset = GetClientGenuineConstDigestOffset(handshakeBytes.slice(8, 12));
|
||||
} else {
|
||||
serverDigestOffset = GetServerGenuineConstDigestOffset(handshakeBytes.slice(772, 776));
|
||||
}
|
||||
|
||||
msg = Buffer.concat([handshakeBytes.slice(0, serverDigestOffset), handshakeBytes.slice(serverDigestOffset + SHA256DL)], RTMP_SIG_SIZE - SHA256DL);
|
||||
hash = calcHmac(msg, GenuineFMSConst);
|
||||
hash.copy(handshakeBytes, serverDigestOffset, 0, 32);
|
||||
return handshakeBytes;
|
||||
}
|
||||
|
||||
function generateS2(messageFormat, clientsig, callback) {
|
||||
let randomBytes = Crypto.randomBytes(RTMP_SIG_SIZE - 32);
|
||||
let challengeKeyOffset;
|
||||
if (messageFormat === 1) {
|
||||
challengeKeyOffset = GetClientGenuineConstDigestOffset(clientsig.slice(8, 12));
|
||||
} else {
|
||||
challengeKeyOffset = GetServerGenuineConstDigestOffset(clientsig.slice(772, 776));
|
||||
}
|
||||
let challengeKey = clientsig.slice(challengeKeyOffset, challengeKeyOffset + 32);
|
||||
let hash = calcHmac(challengeKey, GenuineFMSConstCrud);
|
||||
let signature = calcHmac(randomBytes, hash);
|
||||
let s2Bytes = Buffer.concat([randomBytes, signature], RTMP_SIG_SIZE);
|
||||
return s2Bytes
|
||||
}
|
||||
|
||||
function generateS0S1S2(clientsig) {
|
||||
let clientType = Buffer.alloc(1, 3);
|
||||
let messageFormat = detectClientMessageFormat(clientsig);
|
||||
let allBytes;
|
||||
if (messageFormat === MESSAGE_FORMAT_0) {
|
||||
// Logger.debug('[rtmp handshake] using simple handshake.');
|
||||
allBytes = Buffer.concat([clientType, clientsig, clientsig]);
|
||||
} else {
|
||||
// Logger.debug('[rtmp handshake] using complex handshake.');
|
||||
allBytes = Buffer.concat([clientType, generateS1(messageFormat), generateS2(messageFormat, clientsig)]);
|
||||
}
|
||||
return allBytes;
|
||||
}
|
||||
|
||||
module.exports = { generateS0S1S2 };
|
50
libs/rtmpserver/node_rtmp_server.js
Normal file
50
libs/rtmpserver/node_rtmp_server.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// Created by Mingliang Chen on 17/8/1.
|
||||
// illuspas[a]gmail.com
|
||||
// Copyright (c) 2018 Nodemedia. All rights reserved.
|
||||
//
|
||||
// const Logger = require('./node_core_logger');
|
||||
|
||||
const Net = require('net');
|
||||
const NodeRtmpSession = require('./node_rtmp_session');
|
||||
const NodeCoreUtils = require('./node_core_utils');
|
||||
|
||||
const context = require('./node_core_ctx');
|
||||
|
||||
const RTMP_PORT = 1935;
|
||||
|
||||
class NodeRtmpServer {
|
||||
constructor(config) {
|
||||
config.rtmp.port = this.port = config.rtmp.port ? config.rtmp.port : RTMP_PORT;
|
||||
this.tcpServer = Net.createServer((socket) => {
|
||||
let session = new NodeRtmpSession(config, socket);
|
||||
session.run();
|
||||
})
|
||||
}
|
||||
|
||||
run() {
|
||||
this.tcpServer.listen(this.port, () => {
|
||||
// Logger.log(`Node Media Rtmp Server started on port: ${this.port}`);
|
||||
});
|
||||
|
||||
this.tcpServer.on('error', (e) => {
|
||||
// Logger.error(`Node Media Rtmp Server ${e}`);
|
||||
});
|
||||
|
||||
this.tcpServer.on('close', () => {
|
||||
// Logger.log('Node Media Rtmp Server Close.');
|
||||
});
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.tcpServer.close();
|
||||
context.sessions.forEach((session, id) => {
|
||||
if (session instanceof NodeRtmpSession) {
|
||||
session.socket.destroy();
|
||||
context.sessions.delete(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NodeRtmpServer
|
1248
libs/rtmpserver/node_rtmp_session.js
Normal file
1248
libs/rtmpserver/node_rtmp_session.js
Normal file
File diff suppressed because it is too large
Load diff
263
libs/scheduler.js
Normal file
263
libs/scheduler.js
Normal file
|
@ -0,0 +1,263 @@
|
|||
module.exports = function(s,config,lang,app,io){
|
||||
s.schedules = {}
|
||||
//Get all Schedules
|
||||
s.getAllSchedules = function(callback){
|
||||
s.schedules = {}
|
||||
s.sqlQuery('SELECT * FROM Schedules',function(err,rows){
|
||||
rows.forEach(function(schedule){
|
||||
s.updateSchedule(schedule)
|
||||
})
|
||||
if(callback)callback()
|
||||
})
|
||||
}
|
||||
//update schedule
|
||||
s.updateSchedule = function(row){
|
||||
var schedule = Object.assign(row,{})
|
||||
if(!s.schedules[schedule.ke])s.schedules[schedule.ke] = {}
|
||||
s.checkDetails(schedule)
|
||||
if(!s.schedules[schedule.ke][schedule.name]){
|
||||
s.schedules[schedule.ke][schedule.name] = schedule
|
||||
}else{
|
||||
s.schedules[schedule.ke][schedule.name] = Object.assign(s.schedules[schedule.ke][schedule.name],schedule)
|
||||
}
|
||||
}
|
||||
//check time in schedule
|
||||
s.checkTimeAgainstSchedule = function(start,end,callback){
|
||||
try{
|
||||
if(
|
||||
start
|
||||
){
|
||||
var checkStartTime = new Date()
|
||||
var startSplit = start.split(':')
|
||||
var startHour = parseInt(startSplit[0])
|
||||
var startMin = parseInt(startSplit[1])
|
||||
checkStartTime.setHours(startHour)
|
||||
checkStartTime.setMinutes(startMin)
|
||||
if(end){
|
||||
var checkEndTime = new Date()
|
||||
var endSplit = end.split(':')
|
||||
var endHour = parseInt(endSplit[0])
|
||||
var endMin = parseInt(endSplit[1])
|
||||
checkEndTime.setHours(endHour)
|
||||
checkEndTime.setMinutes(endMin)
|
||||
}
|
||||
var currentDate = new Date()
|
||||
if(
|
||||
(
|
||||
currentDate >= checkStartTime &&
|
||||
currentDate <= checkEndTime
|
||||
) ||
|
||||
currentDate >= checkStartTime && !end
|
||||
){
|
||||
callback()
|
||||
}else{
|
||||
callback({
|
||||
currentDate : currentDate,
|
||||
startTime : checkStartTime,
|
||||
endTime : checkEndTime
|
||||
})
|
||||
}
|
||||
}else{
|
||||
callback()
|
||||
}
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
callback()
|
||||
}
|
||||
}
|
||||
//check all Schedules
|
||||
s.checkSchedules = function(v,callback){
|
||||
var groupKeys = Object.keys(s.schedules)
|
||||
groupKeys.forEach(function(key){
|
||||
var scheduleNames = Object.keys(s.schedules[key])
|
||||
scheduleNames.forEach(function(name){
|
||||
var schedule = s.schedules[key][name]
|
||||
if(!schedule.active && schedule.enabled === 1 && schedule.start && schedule.details.monitorStates){
|
||||
s.checkTimeAgainstSchedule(schedule.start,schedule.end,function(err){
|
||||
if(!err){
|
||||
schedule.active = true
|
||||
var monitorStates = schedule.details.monitorStates
|
||||
monitorStates.forEach(function(stateName){
|
||||
s.activateMonitorStates(key,stateName,{
|
||||
ke: key,
|
||||
uid: 'System',
|
||||
details: {},
|
||||
permissions: {},
|
||||
lang: lang
|
||||
},function(endData){
|
||||
// console.log(endData)
|
||||
})
|
||||
})
|
||||
}else{
|
||||
schedule.active = false
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
//
|
||||
s.findSchedule = function(groupKey,name,callback){
|
||||
//presetQueryVals = [ke, type, name]
|
||||
s.sqlQuery("SELECT * FROM Schedules WHERE ke=? AND name=? LIMIT 1",[groupKey,name],function(err,schedules){
|
||||
var schedule
|
||||
var notFound = false
|
||||
if(schedules && schedules[0]){
|
||||
schedule = schedules[0]
|
||||
s.checkDetails(schedule)
|
||||
}else{
|
||||
notFound = true
|
||||
}
|
||||
callback(notFound,schedule)
|
||||
})
|
||||
}
|
||||
//
|
||||
var onProcessReady = function(){
|
||||
s.getAllSchedules(function(){
|
||||
s.checkSchedules()
|
||||
})
|
||||
setInterval(function(){
|
||||
s.checkSchedules()
|
||||
},1000 * 60 * 5)
|
||||
}
|
||||
/**
|
||||
* WebServerPath : API : Get Schedule
|
||||
*/
|
||||
app.all([
|
||||
config.webPaths.apiPrefix+':auth/schedule/:ke',
|
||||
config.webPaths.adminApiPrefix+':auth/schedule/:ke',
|
||||
config.webPaths.apiPrefix+':auth/schedule/:ke/:name',
|
||||
config.webPaths.adminApiPrefix+':auth/schedule/:ke/:name',
|
||||
config.webPaths.apiPrefix+':auth/schedules/:ke',
|
||||
config.webPaths.adminApiPrefix+':auth/schedules/:ke',
|
||||
config.webPaths.apiPrefix+':auth/schedules/:ke/:name',
|
||||
config.webPaths.adminApiPrefix+':auth/schedules/:ke/:name',
|
||||
],function (req,res){
|
||||
s.auth(req.params,function(user){
|
||||
var endData = {
|
||||
ok : false
|
||||
}
|
||||
if(user.details.sub){
|
||||
endData.msg = user.lang['Not Permitted']
|
||||
s.closeJsonResponse(res,endData)
|
||||
return
|
||||
}
|
||||
var theQuery = "SELECT * FROM Schedules WHERE ke=?"
|
||||
var theQueryValues = [req.params.ke]
|
||||
if(req.params.name){
|
||||
theQuery += ' AND name=?'
|
||||
theQueryValues.push(req.params.name)
|
||||
}
|
||||
s.sqlQuery(theQuery,theQueryValues,function(err,schedules){
|
||||
if(schedules && schedules[0]){
|
||||
endData.ok = true
|
||||
schedules.forEach(function(schedule){
|
||||
s.checkDetails(schedule)
|
||||
})
|
||||
endData.schedules = schedules
|
||||
}else{
|
||||
endData.msg = user.lang['Not Found']
|
||||
}
|
||||
s.closeJsonResponse(res,endData)
|
||||
})
|
||||
})
|
||||
})
|
||||
/**
|
||||
* WebServerPath : API : Update Schedule
|
||||
*/
|
||||
app.all([
|
||||
config.webPaths.apiPrefix+':auth/schedule/:ke/:name/:action',
|
||||
config.webPaths.adminApiPrefix+':auth/schedule/:ke/:name/:action',
|
||||
config.webPaths.apiPrefix+':auth/schedules/:ke/:name/:action',
|
||||
config.webPaths.adminApiPrefix+':auth/schedules/:ke/:name/:action'
|
||||
],function (req,res){
|
||||
s.auth(req.params,function(user){
|
||||
var endData = {
|
||||
ok : false
|
||||
}
|
||||
if(user.details.sub){
|
||||
endData.msg = user.lang['Not Permitted']
|
||||
s.closeJsonResponse(res,endData)
|
||||
return
|
||||
}
|
||||
switch(req.params.action){
|
||||
case'insert':case'edit':
|
||||
var form = s.getPostData(req)
|
||||
s.checkDetails(form)
|
||||
if(!form || !form.details){
|
||||
endData.msg = user.lang['Form Data Not Found']
|
||||
s.closeJsonResponse(res,endData)
|
||||
return
|
||||
}
|
||||
form.enabled = parseInt(form.enabled) || 1;
|
||||
s.findSchedule(req.params.ke,req.params.name,function(notFound,preset){
|
||||
if(notFound === true){
|
||||
endData.msg = lang["Inserted Schedule Configuration"]
|
||||
var insertData = {
|
||||
ke: req.params.ke,
|
||||
name: req.params.name,
|
||||
details: s.stringJSON(form.details),
|
||||
start: form.start,
|
||||
end: form.end,
|
||||
enabled: form.enabled
|
||||
}
|
||||
s.sqlQuery('INSERT INTO Schedules ('+Object.keys(insertData).join(',')+') VALUES (?,?,?,?,?,?)',Object.values(insertData))
|
||||
s.tx({
|
||||
f: 'add_schedule',
|
||||
insertData: insertData,
|
||||
ke: req.params.ke,
|
||||
name: req.params.name
|
||||
},'GRP_'+req.params.ke)
|
||||
}else{
|
||||
endData.msg = lang["Edited Schedule Configuration"]
|
||||
var insertData = {
|
||||
details: s.stringJSON(form.details),
|
||||
start: form.start,
|
||||
end: form.end,
|
||||
enabled: form.enabled,
|
||||
ke: req.params.ke,
|
||||
name: req.params.name
|
||||
}
|
||||
s.sqlQuery('UPDATE Schedules SET details=?,start=?,end=?,enabled=? WHERE ke=? AND name=?',Object.values(insertData))
|
||||
s.tx({
|
||||
f: 'edit_schedule',
|
||||
insertData: insertData,
|
||||
ke: req.params.ke,
|
||||
name: req.params.name
|
||||
},'GRP_'+req.params.ke)
|
||||
}
|
||||
s.updateSchedule({
|
||||
ke: req.params.ke,
|
||||
name: req.params.name,
|
||||
details: s.stringJSON(form.details),
|
||||
start: form.start,
|
||||
end: form.end,
|
||||
enabled: form.enabled
|
||||
})
|
||||
endData.ok = true
|
||||
s.closeJsonResponse(res,endData)
|
||||
})
|
||||
break;
|
||||
case'delete':
|
||||
s.findSchedule(req.params.ke,req.params.name,function(notFound,schedule){
|
||||
if(notFound === true){
|
||||
endData.msg = user.lang['Schedule Configuration Not Found']
|
||||
s.closeJsonResponse(res,endData)
|
||||
}else{
|
||||
s.sqlQuery('DELETE FROM Schedules WHERE ke=? AND name=?',[req.params.ke,req.params.name],function(err){
|
||||
if(!err){
|
||||
endData.msg = lang["Deleted Schedule Configuration"]
|
||||
endData.ok = true
|
||||
if(s.schedules[schedule.ke])delete(s.schedules[schedule.ke][schedule.name])
|
||||
}
|
||||
s.closeJsonResponse(res,endData)
|
||||
})
|
||||
}
|
||||
})
|
||||
break;
|
||||
}
|
||||
})
|
||||
})
|
||||
//bind events
|
||||
s.onProcessReady(onProcessReady)
|
||||
}
|
|
@ -9,12 +9,8 @@ module.exports = function(s,config,lang,io){
|
|||
s.clientSocketConnection = {}
|
||||
//send data to detector plugin
|
||||
s.ocvTx=function(data){
|
||||
if(!s.ocv){return}
|
||||
if(s.ocv.isClientPlugin===true){
|
||||
s.tx(data,s.ocv.id)
|
||||
}else{
|
||||
s.connectedPlugins[s.ocv.plug].tx(data)
|
||||
}
|
||||
// chaining coming in future update
|
||||
s.sendToAllDetectors(data)
|
||||
}
|
||||
//send data to socket client function
|
||||
s.tx = function(z,y,x){if(x){return x.broadcast.to(y).emit('f',z)};io.to(y).emit('f',z);}
|
||||
|
@ -446,8 +442,8 @@ module.exports = function(s,config,lang,io){
|
|||
s.group[d.ke].mon={}
|
||||
if(!s.group[d.ke].mon){s.group[d.ke].mon={}}
|
||||
}
|
||||
if(s.ocv){
|
||||
tx({f:'detector_plugged',plug:s.ocv.plug,notice:s.ocv.notice})
|
||||
if(s.isAtleatOneDetectorPluginConnected){
|
||||
s.sendDetectorInfoToClient({f:'detector_plugged'},tx)
|
||||
s.ocvTx({f:'readPlugins',ke:d.ke})
|
||||
}
|
||||
tx({f:'users_online',users:s.group[d.ke].users})
|
||||
|
@ -477,6 +473,9 @@ module.exports = function(s,config,lang,io){
|
|||
console.log(err)
|
||||
}
|
||||
})
|
||||
s.onSocketAuthenticationExtensions.forEach(function(extender){
|
||||
extender(r,cn)
|
||||
})
|
||||
}
|
||||
s.sqlQuery('SELECT ke,uid,auth,mail,details FROM Users WHERE ke=? AND auth=? AND uid=?',[d.ke,d.auth,d.uid],function(err,r) {
|
||||
if(r&&r[0]){
|
||||
|
@ -512,12 +511,23 @@ module.exports = function(s,config,lang,io){
|
|||
s.ocvTx(d.data)
|
||||
break;
|
||||
case'monitorOrder':
|
||||
if(d.monitorOrder&&d.monitorOrder instanceof Object){
|
||||
if(d.monitorOrder && d.monitorOrder instanceof Object){
|
||||
s.sqlQuery('SELECT details FROM Users WHERE uid=? AND ke=?',[cn.uid,cn.ke],function(err,r){
|
||||
if(r&&r[0]){
|
||||
r=JSON.parse(r[0].details);
|
||||
r.monitorOrder=d.monitorOrder;
|
||||
s.sqlQuery('UPDATE Users SET details=? WHERE uid=? AND ke=?',[JSON.stringify(r),cn.uid,cn.ke])
|
||||
if(r && r[0]){
|
||||
details = JSON.parse(r[0].details)
|
||||
details.monitorOrder = d.monitorOrder
|
||||
s.sqlQuery('UPDATE Users SET details=? WHERE uid=? AND ke=?',[s.s(details),cn.uid,cn.ke])
|
||||
}
|
||||
})
|
||||
}
|
||||
break;
|
||||
case'monitorListOrder':
|
||||
if(d.monitorListOrder && d.monitorListOrder instanceof Object){
|
||||
s.sqlQuery('SELECT details FROM Users WHERE uid=? AND ke=?',[cn.uid,cn.ke],function(err,r){
|
||||
if(r && r[0]){
|
||||
details = JSON.parse(r[0].details)
|
||||
details.monitorListOrder = d.monitorListOrder
|
||||
s.sqlQuery('UPDATE Users SET details=? WHERE uid=? AND ke=?',[s.s(details),cn.uid,cn.ke])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1355,22 +1365,29 @@ module.exports = function(s,config,lang,io){
|
|||
}
|
||||
}
|
||||
if(cn.pluginEngine){
|
||||
s.connectedPlugins[cn.pluginEngine].plugged=false
|
||||
s.connectedPlugins[cn.pluginEngine].plugged = false
|
||||
s.tx({f:'plugin_engine_unplugged',plug:cn.pluginEngine},'CPU')
|
||||
delete(s.api[cn.pluginEngine])
|
||||
}
|
||||
if(cn.cron){
|
||||
delete(s.cron);
|
||||
}
|
||||
if(cn.ocv){
|
||||
s.tx({f:'detector_unplugged',plug:s.ocv.plug},'CPU')
|
||||
delete(s.ocv);
|
||||
delete(s.api[cn.id])
|
||||
if(cn.detectorPlugin){
|
||||
s.tx({f:'detector_unplugged',plug:cn.detectorPlugin},'CPU')
|
||||
s.removeDetectorPlugin(cn.detectorPlugin)
|
||||
s.sendDetectorInfoToClient({f:'detector_plugged'},function(data){
|
||||
s.tx(data,'CPU')
|
||||
})
|
||||
}
|
||||
if(cn.superSessionKey){
|
||||
delete(s.superUsersApi[cn.superSessionKey])
|
||||
}
|
||||
s.onWebSocketDisconnectionExtensions.forEach(function(extender){
|
||||
extender(cn)
|
||||
})
|
||||
delete(s.clientSocketConnection[cn.id])
|
||||
})
|
||||
s.onWebSocketConnectionExtensions.forEach(function(extender){
|
||||
extender(cn)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
|
70
libs/sql.js
70
libs/sql.js
|
@ -1,12 +1,24 @@
|
|||
moment = require('moment')
|
||||
module.exports = function(s,config){
|
||||
s.onBeforeDatabaseLoadExtensions.forEach(function(extender){
|
||||
extender(config)
|
||||
})
|
||||
//sql/database connection with knex
|
||||
s.databaseOptions = {
|
||||
client: config.databaseType,
|
||||
connection: config.db,
|
||||
}
|
||||
var isSqlite = false
|
||||
if(s.databaseOptions.client.indexOf('sqlite')>-1){
|
||||
isSqlite = true
|
||||
s.databaseOptions.client = 'sqlite3';
|
||||
s.databaseOptions.useNullAsDefault = true;
|
||||
try{
|
||||
require('sqlite3')
|
||||
}catch(err){
|
||||
console.log('Installing SQlite3 Module...')
|
||||
require('child_process').execSync('npm install sqlite3 --unsafe-perm')
|
||||
}
|
||||
}
|
||||
if(s.databaseOptions.client === 'sqlite3' && s.databaseOptions.connection.filename === undefined){
|
||||
s.databaseOptions.connection.filename = s.mainDirectory+"/shinobi.sqlite"
|
||||
|
@ -38,7 +50,7 @@ module.exports = function(s,config){
|
|||
return newQuery
|
||||
}
|
||||
s.stringToSqlTime = function(value){
|
||||
newValue = new Date(value.replace('T',' '))
|
||||
newValue = new Date(s.nameToTime(value)).valueOf()
|
||||
return newValue
|
||||
}
|
||||
s.sqlQuery = function(query,values,onMoveOn,hideLog){
|
||||
|
@ -48,6 +60,11 @@ module.exports = function(s,config){
|
|||
var values = [];
|
||||
}
|
||||
if(!onMoveOn){onMoveOn=function(){}}
|
||||
// if(s.databaseOptions.client === 'pg'){
|
||||
// query = query
|
||||
// .replace(/ NOT LIKE /g," NOT ILIKE ")
|
||||
// .replace(/ LIKE /g," ILIKE ")
|
||||
// }
|
||||
var mergedQuery = s.mergeQueryValues(query,values)
|
||||
s.debugLog('s.sqlQuery QUERY',mergedQuery)
|
||||
if(!s.databaseEngine || !s.databaseEngine.raw){
|
||||
|
@ -73,25 +90,54 @@ module.exports = function(s,config){
|
|||
}
|
||||
})
|
||||
}
|
||||
s.openDatabaseTable = function(tableName){
|
||||
return s.databaseEngine(tableName)
|
||||
}
|
||||
s.connectDatabase = function(){
|
||||
s.databaseEngine = require('knex')(s.databaseOptions)
|
||||
}
|
||||
s.preQueries = function(){
|
||||
//add Cloud Videos table, will remove in future
|
||||
s.sqlQuery('CREATE TABLE IF NOT EXISTS `Cloud Videos` (`mid` varchar(50) NOT NULL,`ke` varchar(50) DEFAULT NULL,`href` text NOT NULL,`size` float DEFAULT NULL,`time` timestamp NULL DEFAULT NULL,`end` timestamp NULL DEFAULT NULL,`status` int(1) DEFAULT \'0\' COMMENT \'0:Complete,1:Read,2:Archive\',`details` text) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;',[],function(err){
|
||||
// if(err)console.log(err)
|
||||
var knex = s.databaseEngine
|
||||
var mySQLtail = ''
|
||||
if(config.databaseType === 'mysql'){
|
||||
mySQLtail = ' ENGINE=InnoDB DEFAULT CHARSET=utf8'
|
||||
}
|
||||
//add Presets table and modernize
|
||||
var createPresetsTableQuery = 'CREATE TABLE IF NOT EXISTS `Presets` ( `ke` varchar(50) DEFAULT NULL, `name` text, `details` text, `type` varchar(50) DEFAULT NULL)'
|
||||
s.sqlQuery( createPresetsTableQuery + mySQLtail + ';',[],function(err){
|
||||
if(err)console.error(err)
|
||||
if(config.databaseType === 'sqlite3'){
|
||||
var aQuery = "ALTER TABLE Presets RENAME TO _Presets_old;"
|
||||
aQuery += createPresetsTableQuery
|
||||
aQuery += "INSERT INTO Presets (`ke`, `name`, `details`, `type`) SELECT `ke`, `name`, `details`, `type` FROM _Presets_old;COMMIT;DROP TABLE _Presets_old;"
|
||||
}else{
|
||||
s.sqlQuery('ALTER TABLE `Presets` CHANGE COLUMN `type` `type` VARCHAR(50) NULL DEFAULT NULL AFTER `details`;',[],function(err){
|
||||
if(err)console.error(err)
|
||||
},true)
|
||||
}
|
||||
},true)
|
||||
//add monitorStates to Preset ENUM
|
||||
s.sqlQuery('ALTER TABLE `Presets` CHANGE COLUMN `type` `type` VARCHAR(50) NULL DEFAULT NULL AFTER `details`;',[],function(err){
|
||||
// if(err)console.log(err)
|
||||
//add Schedules table, will remove in future
|
||||
s.sqlQuery("CREATE TABLE IF NOT EXISTS `Schedules` (`ke` varchar(50) DEFAULT NULL,`name` text,`details` text,`start` varchar(10) DEFAULT NULL,`end` varchar(10) DEFAULT NULL,`enabled` int(1) NOT NULL DEFAULT '1')" + mySQLtail + ';',[],function(err){
|
||||
if(err)console.error(err)
|
||||
},true)
|
||||
//add Cloud Videos table, will remove in future
|
||||
s.sqlQuery('CREATE TABLE IF NOT EXISTS `Cloud Videos` (`mid` varchar(50) NOT NULL,`ke` varchar(50) DEFAULT NULL,`href` text NOT NULL,`size` float DEFAULT NULL,`time` timestamp NULL DEFAULT NULL,`end` timestamp NULL DEFAULT NULL,`status` int(1) DEFAULT \'0\',`details` text)' + mySQLtail + ';',[],function(err){
|
||||
if(err)console.error(err)
|
||||
},true)
|
||||
//create Files table
|
||||
s.sqlQuery('CREATE TABLE IF NOT EXISTS `Files` (`ke` varchar(50) NOT NULL,`mid` varchar(50) NOT NULL,`name` tinytext NOT NULL,`size` float NOT NULL DEFAULT \'0\',`details` text NOT NULL,`status` int(1) NOT NULL DEFAULT \'0\') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;',[],function(err){
|
||||
// if(err)console.log(err)
|
||||
var createFilesTableQuery = "CREATE TABLE IF NOT EXISTS `Files` (`ke` varchar(50) NOT NULL,`mid` varchar(50) NOT NULL,`name` tinytext NOT NULL,`size` float NOT NULL DEFAULT '0',`details` text NOT NULL,`status` int(1) NOT NULL DEFAULT '0',`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP)"
|
||||
s.sqlQuery(createFilesTableQuery + mySQLtail + ';',[],function(err){
|
||||
if(err)console.error(err)
|
||||
//add time column to Files table
|
||||
s.sqlQuery('ALTER TABLE `Files` ADD COLUMN `time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `status`;',[],function(err){
|
||||
// if(err)console.log(err)
|
||||
},true)
|
||||
if(config.databaseType === 'sqlite3'){
|
||||
var aQuery = "ALTER TABLE Files RENAME TO _Files_old;"
|
||||
aQuery += createPresetsTableQuery
|
||||
aQuery += "INSERT INTO Files (`ke`, `mid`, `name`, `details`, `size`, `status`, `time`) SELECT `ke`, `mid`, `name`, `details`, `size`, `status`, `time` FROM _Files_old;COMMIT;DROP TABLE _Files_old;"
|
||||
}else{
|
||||
s.sqlQuery('ALTER TABLE `Files` ADD COLUMN `time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `status`;',[],function(err){
|
||||
if(err && err.sqlMessage.indexOf('Duplicate') === -1)console.error(err)
|
||||
},true)
|
||||
}
|
||||
},true)
|
||||
delete(s.preQueries)
|
||||
}
|
||||
|
|
|
@ -9,10 +9,34 @@ module.exports = function(s,config,lang,io){
|
|||
console.log('Node.js version : '+execSync("node -v"))
|
||||
s.processReady = function(){
|
||||
s.systemLog(lang.startUpText5)
|
||||
s.onProcessReadyExtensions.forEach(function(extender){
|
||||
extender(true)
|
||||
})
|
||||
process.send('ready')
|
||||
}
|
||||
var checkForTerminalCommands = function(callback){
|
||||
var next = function(){
|
||||
if(callback)callback()
|
||||
}
|
||||
if(!s.isWin){
|
||||
var etcPath = '/etc/shinobisystems/cctv.txt'
|
||||
fs.stat(etcPath,function(err,stat){
|
||||
if(err || !stat){
|
||||
exec('node '+ s.mainDirectory + '/INSTALL/terminalCommands.js',function(err){
|
||||
if(err)console.log(err)
|
||||
})
|
||||
}
|
||||
next()
|
||||
})
|
||||
}else{
|
||||
next()
|
||||
}
|
||||
}
|
||||
var loadedAccounts = []
|
||||
var loadMonitors = function(callback){
|
||||
s.beforeMonitorsLoadedOnStartupExtensions.forEach(function(extender){
|
||||
extender()
|
||||
})
|
||||
s.systemLog(lang.startUpText4)
|
||||
//preliminary monitor start
|
||||
s.sqlQuery('SELECT * FROM Monitors', function(err,monitors) {
|
||||
|
@ -147,6 +171,9 @@ module.exports = function(s,config,lang,io){
|
|||
})
|
||||
})
|
||||
},10000)
|
||||
//hourly check to see if sizePurge has failed to unlock
|
||||
//checks to see if request count is the number of monitors + 10
|
||||
s.checkForStalePurgeLocks()
|
||||
//run prerequsite queries, load users and monitors
|
||||
if(config.childNodes.mode !== 'child'){
|
||||
//sql/database connection with knex
|
||||
|
@ -154,11 +181,13 @@ module.exports = function(s,config,lang,io){
|
|||
//run prerequsite queries
|
||||
s.preQueries()
|
||||
setTimeout(function(){
|
||||
//load administrators (groups)
|
||||
loadAdminUsers(function(){
|
||||
//load monitors (for groups)
|
||||
loadMonitors(function(){
|
||||
s.processReady()
|
||||
checkForTerminalCommands(function(){
|
||||
//load administrators (groups)
|
||||
loadAdminUsers(function(){
|
||||
//load monitors (for groups)
|
||||
loadMonitors(function(){
|
||||
s.processReady()
|
||||
})
|
||||
})
|
||||
})
|
||||
},1500)
|
||||
|
|
11
libs/uploaders.js
Normal file
11
libs/uploaders.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
module.exports = function(s,config,lang){
|
||||
var loadLib = function(lib){
|
||||
return require('./uploaders/' + lib + '.js')
|
||||
}
|
||||
loadLib('loader')(s,config,lang)
|
||||
loadLib('backblazeB2')(s,config,lang)
|
||||
loadLib('amazonS3')(s,config,lang)
|
||||
loadLib('webdav')(s,config,lang)
|
||||
loadLib('wasabi')(s,config,lang)
|
||||
loadLib('sftp')(s,config,lang)
|
||||
}
|
137
libs/uploaders/amazonS3.js
Normal file
137
libs/uploaders/amazonS3.js
Normal file
|
@ -0,0 +1,137 @@
|
|||
var fs = require('fs');
|
||||
module.exports = function(s,config,lang){
|
||||
//Amazon S3
|
||||
var beforeAccountSaveForAmazonS3 = function(d){
|
||||
//d = save event
|
||||
d.form.details.aws_use_global=d.d.aws_use_global
|
||||
d.form.details.use_aws_s3=d.d.use_aws_s3
|
||||
}
|
||||
var cloudDiskUseStartupForAmazonS3 = function(group,userDetails){
|
||||
group.cloudDiskUse['s3'].name = 'Amazon S3'
|
||||
group.cloudDiskUse['s3'].sizeLimitCheck = (userDetails.use_aws_s3_size_limit === '1')
|
||||
if(!userDetails.aws_s3_size_limit || userDetails.aws_s3_size_limit === ''){
|
||||
group.cloudDiskUse['s3'].sizeLimit = 10000
|
||||
}else{
|
||||
group.cloudDiskUse['s3'].sizeLimit = parseFloat(userDetails.aws_s3_size_limit)
|
||||
}
|
||||
}
|
||||
var loadAmazonS3ForUser = function(e){
|
||||
// e = user
|
||||
var userDetails = JSON.parse(e.details)
|
||||
if(userDetails.aws_use_global === '1' && config.cloudUploaders && config.cloudUploaders.AmazonS3){
|
||||
// {
|
||||
// aws_accessKeyId: "",
|
||||
// aws_secretAccessKey: "",
|
||||
// aws_region: "",
|
||||
// aws_s3_bucket: "",
|
||||
// aws_s3_dir: "",
|
||||
// }
|
||||
userDetails = Object.assign(userDetails,config.cloudUploaders.AmazonS3)
|
||||
}
|
||||
//Amazon S3
|
||||
if(!s.group[e.ke].aws &&
|
||||
!s.group[e.ke].aws_s3 &&
|
||||
userDetails.aws_s3 !== '0' &&
|
||||
userDetails.aws_accessKeyId !== ''&&
|
||||
userDetails.aws_secretAccessKey &&
|
||||
userDetails.aws_secretAccessKey !== ''&&
|
||||
userDetails.aws_region &&
|
||||
userDetails.aws_region !== ''&&
|
||||
userDetails.aws_s3_bucket !== ''
|
||||
){
|
||||
if(!userDetails.aws_s3_dir || userDetails.aws_s3_dir === '/'){
|
||||
userDetails.aws_s3_dir = ''
|
||||
}
|
||||
if(userDetails.aws_s3_dir !== ''){
|
||||
userDetails.aws_s3_dir = s.checkCorrectPathEnding(userDetails.aws_s3_dir)
|
||||
}
|
||||
s.group[e.ke].aws = new require("aws-sdk")
|
||||
s.group[e.ke].aws.config = new s.group[e.ke].aws.Config({
|
||||
accessKeyId: userDetails.aws_accessKeyId,
|
||||
secretAccessKey: userDetails.aws_secretAccessKey,
|
||||
region: userDetails.aws_region
|
||||
})
|
||||
s.group[e.ke].aws_s3 = new s.group[e.ke].aws.S3();
|
||||
}
|
||||
}
|
||||
var unloadAmazonS3ForUser = function(user){
|
||||
s.group[user.ke].aws = null
|
||||
s.group[user.ke].aws_s3 = null
|
||||
}
|
||||
var deleteVideoFromAmazonS3 = function(e,video,callback){
|
||||
// e = user
|
||||
try{
|
||||
var videoDetails = JSON.parse(video.details)
|
||||
}catch(err){
|
||||
var videoDetails = video.details
|
||||
}
|
||||
if(!videoDetails.location){
|
||||
videoDetails.location = video.href.split('.amazonaws.com')[1]
|
||||
}
|
||||
s.group[e.ke].aws_s3.deleteObject({
|
||||
Bucket: s.group[e.ke].init.aws_s3_bucket,
|
||||
Key: videoDetails.location,
|
||||
}, function(err, data) {
|
||||
if (err) console.log(err);
|
||||
callback()
|
||||
});
|
||||
}
|
||||
var uploadVideoToAmazonS3 = function(e,k){
|
||||
//e = video object
|
||||
//k = temporary values
|
||||
if(!k)k={};
|
||||
//cloud saver - amazon s3
|
||||
if(s.group[e.ke].aws_s3 && s.group[e.ke].init.use_aws_s3 !== '0' && s.group[e.ke].init.aws_s3_save === '1'){
|
||||
var ext = k.filename.split('.')
|
||||
ext = ext[ext.length - 1]
|
||||
var fileStream = fs.createReadStream(k.dir+k.filename);
|
||||
fileStream.on('error', function (err) {
|
||||
console.error(err)
|
||||
})
|
||||
var saveLocation = s.group[e.ke].init.aws_s3_dir+e.ke+'/'+e.mid+'/'+k.filename
|
||||
s.group[e.ke].aws_s3.upload({
|
||||
Bucket: s.group[e.ke].init.aws_s3_bucket,
|
||||
Key: saveLocation,
|
||||
Body:fileStream,
|
||||
ACL:'public-read',
|
||||
ContentType:'video/'+ext
|
||||
},function(err,data){
|
||||
if(err){
|
||||
s.userLog(e,{type:lang['Amazon S3 Upload Error'],msg:err})
|
||||
}
|
||||
if(s.group[e.ke].init.aws_s3_log === '1' && data && data.Location){
|
||||
var save = [
|
||||
e.mid,
|
||||
e.ke,
|
||||
k.startTime,
|
||||
1,
|
||||
s.s({
|
||||
type : 's3',
|
||||
location : saveLocation
|
||||
}),
|
||||
k.filesize,
|
||||
k.endTime,
|
||||
data.Location
|
||||
]
|
||||
s.sqlQuery('INSERT INTO `Cloud Videos` (mid,ke,time,status,details,size,end,href) VALUES (?,?,?,?,?,?,?,?)',save)
|
||||
s.setCloudDiskUsedForGroup(e,{
|
||||
amount : k.filesizeMB,
|
||||
storageType : 's3'
|
||||
})
|
||||
s.purgeCloudDiskForGroup(e,'s3')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
//amazon s3
|
||||
s.addCloudUploader({
|
||||
name: 's3',
|
||||
loadGroupAppExtender: loadAmazonS3ForUser,
|
||||
unloadGroupAppExtender: unloadAmazonS3ForUser,
|
||||
insertCompletedVideoExtender: uploadVideoToAmazonS3,
|
||||
deleteVideoFromCloudExtensions: deleteVideoFromAmazonS3,
|
||||
cloudDiskUseStartupExtensions: cloudDiskUseStartupForAmazonS3,
|
||||
beforeAccountSave: beforeAccountSaveForAmazonS3,
|
||||
onAccountSave: cloudDiskUseStartupForAmazonS3,
|
||||
})
|
||||
}
|
170
libs/uploaders/backblazeB2.js
Normal file
170
libs/uploaders/backblazeB2.js
Normal file
|
@ -0,0 +1,170 @@
|
|||
var fs = require('fs');
|
||||
module.exports = function(s,config,lang){
|
||||
//Backblaze B2
|
||||
var beforeAccountSaveForBackblazeB2 = function(d){
|
||||
//d = save event
|
||||
d.form.details.b2_use_global=d.d.b2_use_global
|
||||
d.form.details.use_bb_b2=d.d.use_bb_b2
|
||||
}
|
||||
var cloudDiskUseStartupForBackblazeB2 = function(group,userDetails){
|
||||
group.cloudDiskUse['b2'].name = 'Backblaze B2'
|
||||
group.cloudDiskUse['b2'].sizeLimitCheck = (userDetails.use_bb_b2_size_limit === '1')
|
||||
if(!userDetails.bb_b2_size_limit || userDetails.bb_b2_size_limit === ''){
|
||||
group.cloudDiskUse['b2'].sizeLimit = 10000
|
||||
}else{
|
||||
group.cloudDiskUse['b2'].sizeLimit = parseFloat(userDetails.bb_b2_size_limit)
|
||||
}
|
||||
}
|
||||
var loadBackblazeB2ForUser = function(e){
|
||||
var userDetails = JSON.parse(e.details);
|
||||
try{
|
||||
if(userDetails.b2_use_global === '1' && config.cloudUploaders && config.cloudUploaders.BackblazeB2){
|
||||
// {
|
||||
// bb_b2_accountId: "",
|
||||
// bb_b2_applicationKey: "",
|
||||
// bb_b2_bucket: "",
|
||||
// bb_b2_dir: "",
|
||||
// }
|
||||
userDetails = Object.assign(userDetails,config.cloudUploaders.BackblazeB2)
|
||||
}
|
||||
if(!s.group[e.ke].bb_b2 &&
|
||||
userDetails.bb_b2_accountId &&
|
||||
userDetails.bb_b2_accountId !=='' &&
|
||||
userDetails.bb_b2_applicationKey &&
|
||||
userDetails.bb_b2_applicationKey !=='' &&
|
||||
userDetails.bb_b2_bucket &&
|
||||
userDetails.bb_b2_bucket !== ''
|
||||
){
|
||||
var B2 = require('backblaze-b2')
|
||||
if(!userDetails.bb_b2_dir || userDetails.bb_b2_dir === '/'){
|
||||
userDetails.bb_b2_dir = ''
|
||||
}
|
||||
if(userDetails.bb_b2_dir !== ''){
|
||||
userDetails.bb_b2_dir = s.checkCorrectPathEnding(userDetails.bb_b2_dir)
|
||||
}
|
||||
var backblazeErr = function(err){
|
||||
// console.log(err)
|
||||
s.userLog({mid:'$USER',ke:e.ke},{type:lang['Backblaze Error'],msg:err.data || err})
|
||||
}
|
||||
var createB2Connection = function(){
|
||||
var b2 = new B2({
|
||||
accountId: userDetails.bb_b2_accountId,
|
||||
applicationKey: userDetails.bb_b2_applicationKey
|
||||
})
|
||||
b2.authorize().then(function(resp){
|
||||
s.group[e.ke].bb_b2_downloadUrl = resp.data.downloadUrl
|
||||
b2.listBuckets().then(function(resp){
|
||||
var buckets = resp.data.buckets
|
||||
var bucketN = -2
|
||||
buckets.forEach(function(item,n){
|
||||
if(item.bucketName === userDetails.bb_b2_bucket){
|
||||
bucketN = n
|
||||
}
|
||||
})
|
||||
if(bucketN > -1){
|
||||
s.group[e.ke].bb_b2_bucketId = buckets[bucketN].bucketId
|
||||
}else{
|
||||
b2.createBucket(
|
||||
userDetails.bb_b2_bucket,
|
||||
'allPublic'
|
||||
).then(function(resp){
|
||||
s.group[e.ke].bb_b2_bucketId = resp.data.bucketId
|
||||
}).catch(backblazeErr)
|
||||
}
|
||||
}).catch(backblazeErr)
|
||||
}).catch(backblazeErr)
|
||||
s.group[e.ke].bb_b2 = b2
|
||||
}
|
||||
createB2Connection()
|
||||
s.group[e.ke].bb_b2_refreshTimer = setInterval(createB2Connection,1000 * 60 * 60)
|
||||
}
|
||||
}catch(err){
|
||||
s.debugLog(err)
|
||||
}
|
||||
}
|
||||
var unloadBackblazeB2ForUser = function(user){
|
||||
s.group[user.ke].bb_b2 = null
|
||||
clearInterval(s.group[user.ke].bb_b2_refreshTimer)
|
||||
}
|
||||
var deleteVideoFromBackblazeB2 = function(e,video,callback){
|
||||
// e = user
|
||||
try{
|
||||
var videoDetails = JSON.parse(video.details)
|
||||
}catch(err){
|
||||
var videoDetails = video.details
|
||||
}
|
||||
s.group[e.ke].bb_b2.deleteFileVersion({
|
||||
fileId: videoDetails.fileId,
|
||||
fileName: videoDetails.fileName
|
||||
}).then(function(resp){
|
||||
// console.log('deleteFileVersion',resp.data)
|
||||
}).catch(function(err){
|
||||
console.log('deleteFileVersion',err)
|
||||
})
|
||||
}
|
||||
var uploadVideoToBackblazeB2 = function(e,k){
|
||||
//e = video object
|
||||
//k = temporary values
|
||||
if(!k)k={};
|
||||
//cloud saver - Backblaze B2
|
||||
if(s.group[e.ke].bb_b2 && s.group[e.ke].init.use_bb_b2 !== '0' && s.group[e.ke].init.bb_b2_save === '1'){
|
||||
var backblazeErr = function(err){
|
||||
// console.log(err)
|
||||
s.userLog({mid:'$USER',ke:e.ke},{type:lang['Backblaze Error'],msg:err.data})
|
||||
}
|
||||
fs.readFile(k.dir+k.filename,function(err,data){
|
||||
var backblazeSavePath = s.group[e.ke].init.bb_b2_dir+e.ke+'/'+e.mid+'/'+k.filename
|
||||
var getUploadUrl = function(bucketId,callback){
|
||||
s.group[e.ke].bb_b2.getUploadUrl(bucketId).then(function(resp){
|
||||
callback(resp.data)
|
||||
}).catch(backblazeErr)
|
||||
}
|
||||
getUploadUrl(s.group[e.ke].bb_b2_bucketId,function(req){
|
||||
s.group[e.ke].bb_b2.uploadFile({
|
||||
uploadUrl: req.uploadUrl,
|
||||
uploadAuthToken: req.authorizationToken,
|
||||
filename: backblazeSavePath,
|
||||
data: data,
|
||||
onUploadProgress: null
|
||||
}).then(function(resp){
|
||||
if(s.group[e.ke].init.bb_b2_log === '1' && resp.data.fileId){
|
||||
var backblazeDownloadUrl = s.group[e.ke].bb_b2_downloadUrl + '/file/' + s.group[e.ke].init.bb_b2_bucket + '/' + backblazeSavePath
|
||||
var save = [
|
||||
e.mid,
|
||||
e.ke,
|
||||
k.startTime,
|
||||
1,
|
||||
s.s({
|
||||
type : 'b2',
|
||||
bucketId : resp.data.bucketId,
|
||||
fileId : resp.data.fileId,
|
||||
fileName : resp.data.fileName
|
||||
}),
|
||||
k.filesize,
|
||||
k.endTime,
|
||||
backblazeDownloadUrl
|
||||
]
|
||||
s.sqlQuery('INSERT INTO `Cloud Videos` (mid,ke,time,status,details,size,end,href) VALUES (?,?,?,?,?,?,?,?)',save)
|
||||
s.setCloudDiskUsedForGroup(e,{
|
||||
amount : k.filesizeMB,
|
||||
storageType : 'b2'
|
||||
})
|
||||
s.purgeCloudDiskForGroup(e,'b2')
|
||||
}
|
||||
}).catch(backblazeErr)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
//backblaze b2
|
||||
s.addCloudUploader({
|
||||
name: 'b2',
|
||||
loadGroupAppExtender: loadBackblazeB2ForUser,
|
||||
unloadGroupAppExtender: unloadBackblazeB2ForUser,
|
||||
insertCompletedVideoExtender: uploadVideoToBackblazeB2,
|
||||
deleteVideoFromCloudExtensions: deleteVideoFromBackblazeB2,
|
||||
cloudDiskUseStartupExtensions: cloudDiskUseStartupForBackblazeB2,
|
||||
beforeAccountSave: beforeAccountSaveForBackblazeB2,
|
||||
onAccountSave: cloudDiskUseStartupForBackblazeB2,
|
||||
})
|
||||
}
|
20
libs/uploaders/loader.js
Normal file
20
libs/uploaders/loader.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
module.exports = function(s){
|
||||
s.addCloudUploader = function(opt){
|
||||
s.loadGroupAppExtender(opt.loadGroupAppExtender)
|
||||
s.unloadGroupAppExtender(opt.unloadGroupAppExtender)
|
||||
s.insertCompletedVideoExtender(opt.insertCompletedVideoExtender)
|
||||
s.deleteVideoFromCloudExtensions[opt.name] = opt.deleteVideoFromCloudExtensions
|
||||
s.cloudDiskUseStartupExtensions[opt.name] = opt.cloudDiskUseStartupExtensions
|
||||
s.beforeAccountSave(opt.beforeAccountSave)
|
||||
s.onAccountSave(opt.onAccountSave)
|
||||
s.cloudDisksLoader(opt.name)
|
||||
}
|
||||
s.addSimpleUploader = function(opt){
|
||||
s.loadGroupAppExtender(opt.loadGroupAppExtender)
|
||||
s.unloadGroupAppExtender(opt.unloadGroupAppExtender)
|
||||
s.insertCompletedVideoExtender(opt.insertCompletedVideoExtender)
|
||||
s.beforeAccountSave(opt.beforeAccountSave)
|
||||
s.onAccountSave(opt.onAccountSave)
|
||||
s.onMonitorSave(opt.onMonitorSave)
|
||||
}
|
||||
}
|
90
libs/uploaders/sftp.js
Normal file
90
libs/uploaders/sftp.js
Normal file
|
@ -0,0 +1,90 @@
|
|||
var fs = require('fs');
|
||||
var ssh2SftpClient = require('node-ssh')
|
||||
module.exports = function(s,config,lang){
|
||||
//SFTP
|
||||
var sftpErr = function(err){
|
||||
// console.log(err)
|
||||
s.userLog({mid:'$USER',ke:e.ke},{type:lang['SFTP Error'],msg:err.data || err})
|
||||
}
|
||||
var beforeAccountSaveForSftp = function(d){
|
||||
//d = save event
|
||||
d.form.details.use_sftp = d.d.use_sftp
|
||||
}
|
||||
var loadSftpForUser = function(e){
|
||||
// e = user
|
||||
var userDetails = JSON.parse(e.details);
|
||||
//SFTP
|
||||
if(!s.group[e.ke].sftp &&
|
||||
!s.group[e.ke].sftp &&
|
||||
userDetails.sftp !== '0' &&
|
||||
userDetails.sftp_host &&
|
||||
userDetails.sftp_host !== ''&&
|
||||
userDetails.sftp_port &&
|
||||
userDetails.sftp_port !== ''
|
||||
){
|
||||
if(!userDetails.sftp_dir || userDetails.sftp_dir === '/'){
|
||||
userDetails.sftp_dir = ''
|
||||
}
|
||||
if(userDetails.sftp_dir !== ''){
|
||||
userDetails.sftp_dir = s.checkCorrectPathEnding(userDetails.sftp_dir)
|
||||
}
|
||||
var sftp = new ssh2SftpClient()
|
||||
var connectionDetails = {
|
||||
host: userDetails.sftp_host,
|
||||
port: userDetails.sftp_port
|
||||
}
|
||||
if(!userDetails.sftp_port)connectionDetails.port = 22
|
||||
if(userDetails.sftp_username && userDetails.sftp_username !== '')connectionDetails.username = userDetails.sftp_username
|
||||
if(userDetails.sftp_password && userDetails.sftp_password !== '')connectionDetails.password = userDetails.sftp_password
|
||||
if(userDetails.sftp_privateKey && userDetails.sftp_privateKey !== '')connectionDetails.privateKey = userDetails.sftp_privateKey
|
||||
sftp.connect(connectionDetails).catch(sftpErr)
|
||||
s.group[e.ke].sftp = sftp
|
||||
}
|
||||
}
|
||||
var unloadSftpForUser = function(user){
|
||||
if(s.group[user.ke].sftp && s.group[user.ke].sftp.end)s.group[user.ke].sftp.end().then(function(){
|
||||
s.group[user.ke].sftp = null
|
||||
})
|
||||
}
|
||||
var uploadVideoToSftp = function(e,k){
|
||||
//e = video object
|
||||
//k = temporary values
|
||||
if(!k)k={};
|
||||
//cloud saver - SFTP
|
||||
if(s.group[e.ke].sftp && s.group[e.ke].init.use_sftp !== '0' && s.group[e.ke].init.sftp_save === '1'){
|
||||
var localPath = k.dir + k.filename
|
||||
var saveLocation = s.group[e.ke].init.sftp_dir + e.ke + '/' + e.mid + '/' + k.filename
|
||||
s.group[e.ke].sftp.putFile(localPath, saveLocation).catch(sftpErr)
|
||||
}
|
||||
}
|
||||
var createSftpDirectory = function(monitorConfig){
|
||||
var monitorSaveDirectory = s.group[monitorConfig.ke].init.sftp_dir + monitorConfig.ke + '/' + monitorConfig.mid
|
||||
s.group[monitorConfig.ke].sftp.mkdir(monitorSaveDirectory, true).catch(function(err){
|
||||
if(err.code !== 'ERR_ASSERTION'){
|
||||
sftpErr(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
var onMonitorSaveForSftp = function(monitorConfig){
|
||||
if(s.group[monitorConfig.ke].sftp && s.group[monitorConfig.ke].init.use_sftp !== '0' && s.group[monitorConfig.ke].init.sftp_save === '1'){
|
||||
createSftpDirectory(monitorConfig)
|
||||
}
|
||||
}
|
||||
var onAccountSaveForSftp = function(group,userDetails,user){
|
||||
if(s.group[user.ke] && s.group[user.ke].sftp && s.group[user.ke].init.use_sftp !== '0' && s.group[user.ke].init.sftp_save === '1'){
|
||||
Object.keys(s.group[user.ke].mon_conf).forEach(function(monitorId){
|
||||
createSftpDirectory(s.group[user.ke].mon_conf[monitorId])
|
||||
})
|
||||
}
|
||||
}
|
||||
//SFTP (Simple Uploader)
|
||||
s.addSimpleUploader({
|
||||
name: 'sftp',
|
||||
loadGroupAppExtender: loadSftpForUser,
|
||||
unloadGroupAppExtender: unloadSftpForUser,
|
||||
insertCompletedVideoExtender: uploadVideoToSftp,
|
||||
beforeAccountSave: beforeAccountSaveForSftp,
|
||||
onAccountSave: onAccountSaveForSftp,
|
||||
onMonitorSave: onMonitorSaveForSftp,
|
||||
})
|
||||
}
|
138
libs/uploaders/wasabi.js
Normal file
138
libs/uploaders/wasabi.js
Normal file
|
@ -0,0 +1,138 @@
|
|||
var fs = require('fs');
|
||||
module.exports = function(s,config,lang){
|
||||
//Wasabi Hot Cloud Storage
|
||||
var beforeAccountSaveForWasabiHotCloudStorage = function(d){
|
||||
//d = save event
|
||||
d.form.details.whcs_use_global=d.d.whcs_use_global
|
||||
d.form.details.use_whcs=d.d.use_whcs
|
||||
}
|
||||
var cloudDiskUseStartupForWasabiHotCloudStorage = function(group,userDetails){
|
||||
group.cloudDiskUse['whcs'].name = 'Wasabi Hot Cloud Storage'
|
||||
group.cloudDiskUse['whcs'].sizeLimitCheck = (userDetails.use_whcs_size_limit === '1')
|
||||
if(!userDetails.whcs_size_limit || userDetails.whcs_size_limit === ''){
|
||||
group.cloudDiskUse['whcs'].sizeLimit = 10000
|
||||
}else{
|
||||
group.cloudDiskUse['whcs'].sizeLimit = parseFloat(userDetails.whcs_size_limit)
|
||||
}
|
||||
}
|
||||
var loadWasabiHotCloudStorageForUser = function(e){
|
||||
// e = user
|
||||
var userDetails = JSON.parse(e.details)
|
||||
if(userDetails.whcs_use_global === '1' && config.cloudUploaders && config.cloudUploaders.WasabiHotCloudStorage){
|
||||
// {
|
||||
// whcs_accessKeyId: "",
|
||||
// whcs_secretAccessKey: "",
|
||||
// whcs_region: "",
|
||||
// whcs_bucket: "",
|
||||
// whcs_dir: "",
|
||||
// }
|
||||
userDetails = Object.assign(userDetails,config.cloudUploaders.WasabiHotCloudStorage)
|
||||
}
|
||||
//Wasabi Hot Cloud Storage
|
||||
if(!s.group[e.ke].whcs &&
|
||||
userDetails.whcs !== '0' &&
|
||||
userDetails.whcs_accessKeyId !== ''&&
|
||||
userDetails.whcs_secretAccessKey &&
|
||||
userDetails.whcs_secretAccessKey !== ''&&
|
||||
userDetails.whcs_region &&
|
||||
userDetails.whcs_region !== ''&&
|
||||
userDetails.whcs_bucket !== ''
|
||||
){
|
||||
if(!userDetails.whcs_dir || userDetails.whcs_dir === '/'){
|
||||
userDetails.whcs_dir = ''
|
||||
}
|
||||
if(userDetails.whcs_dir !== ''){
|
||||
userDetails.whcs_dir = s.checkCorrectPathEnding(userDetails.whcs_dir)
|
||||
}
|
||||
var AWS = new require("aws-sdk")
|
||||
s.group[e.ke].whcs = AWS
|
||||
var wasabiEndpoint = new AWS.Endpoint('s3.wasabisys.com')
|
||||
s.group[e.ke].whcs.config = new s.group[e.ke].whcs.Config({
|
||||
endpoint: wasabiEndpoint,
|
||||
accessKeyId: userDetails.whcs_accessKeyId,
|
||||
secretAccessKey: userDetails.whcs_secretAccessKey,
|
||||
region: userDetails.whcs_region
|
||||
})
|
||||
s.group[e.ke].whcs = new s.group[e.ke].whcs.S3();
|
||||
}
|
||||
}
|
||||
var unloadWasabiHotCloudStorageForUser = function(user){
|
||||
s.group[user.ke].whcs = null
|
||||
}
|
||||
var deleteVideoFromWasabiHotCloudStorage = function(e,video,callback){
|
||||
// e = user
|
||||
try{
|
||||
var videoDetails = JSON.parse(video.details)
|
||||
}catch(err){
|
||||
var videoDetails = video.details
|
||||
}
|
||||
if(!videoDetails.location){
|
||||
videoDetails.location = video.href.split('wasabisys.com')[1]
|
||||
}
|
||||
s.group[e.ke].whcs.deleteObject({
|
||||
Bucket: s.group[e.ke].init.whcs_bucket,
|
||||
Key: videoDetails.location,
|
||||
}, function(err, data) {
|
||||
if (err) console.log(err);
|
||||
callback()
|
||||
});
|
||||
}
|
||||
var uploadVideoToWasabiHotCloudStorage = function(e,k){
|
||||
//e = video object
|
||||
//k = temporary values
|
||||
if(!k)k={};
|
||||
//cloud saver - Wasabi Hot Cloud Storage
|
||||
if(s.group[e.ke].whcs && s.group[e.ke].init.use_whcs !== '0' && s.group[e.ke].init.whcs_save === '1'){
|
||||
var ext = k.filename.split('.')
|
||||
ext = ext[ext.length - 1]
|
||||
var fileStream = fs.createReadStream(k.dir+k.filename);
|
||||
fileStream.on('error', function (err) {
|
||||
console.error(err)
|
||||
})
|
||||
var saveLocation = s.group[e.ke].init.whcs_dir+e.ke+'/'+e.mid+'/'+k.filename
|
||||
s.group[e.ke].whcs.upload({
|
||||
Bucket: s.group[e.ke].init.whcs_bucket,
|
||||
Key: saveLocation,
|
||||
Body:fileStream,
|
||||
ACL:'public-read',
|
||||
ContentType:'video/'+ext
|
||||
},function(err,data){
|
||||
if(err){
|
||||
s.userLog(e,{type:lang['Wasabi Hot Cloud Storage Upload Error'],msg:err})
|
||||
}
|
||||
if(s.group[e.ke].init.whcs_log === '1' && data && data.Location){
|
||||
var save = [
|
||||
e.mid,
|
||||
e.ke,
|
||||
k.startTime,
|
||||
1,
|
||||
s.s({
|
||||
type : 'whcs',
|
||||
location : saveLocation
|
||||
}),
|
||||
k.filesize,
|
||||
k.endTime,
|
||||
data.Location
|
||||
]
|
||||
s.sqlQuery('INSERT INTO `Cloud Videos` (mid,ke,time,status,details,size,end,href) VALUES (?,?,?,?,?,?,?,?)',save)
|
||||
s.setCloudDiskUsedForGroup(e,{
|
||||
amount : k.filesizeMB,
|
||||
storageType : 'whcs'
|
||||
})
|
||||
s.purgeCloudDiskForGroup(e,'whcs')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
//wasabi
|
||||
s.addCloudUploader({
|
||||
name: 'whcs',
|
||||
loadGroupAppExtender: loadWasabiHotCloudStorageForUser,
|
||||
unloadGroupAppExtender: unloadWasabiHotCloudStorageForUser,
|
||||
insertCompletedVideoExtender: uploadVideoToWasabiHotCloudStorage,
|
||||
deleteVideoFromCloudExtensions: deleteVideoFromWasabiHotCloudStorage,
|
||||
cloudDiskUseStartupExtensions: cloudDiskUseStartupForWasabiHotCloudStorage,
|
||||
beforeAccountSave: beforeAccountSaveForWasabiHotCloudStorage,
|
||||
onAccountSave: cloudDiskUseStartupForWasabiHotCloudStorage,
|
||||
})
|
||||
}
|
169
libs/uploaders/webdav.js
Normal file
169
libs/uploaders/webdav.js
Normal file
|
@ -0,0 +1,169 @@
|
|||
var fs = require('fs');
|
||||
var webdav = require("webdav-fs");
|
||||
module.exports = function(s,config,lang){
|
||||
// WebDAV
|
||||
var beforeAccountSaveForWebDav = function(d){
|
||||
//d = save event
|
||||
d.form.details.webdav_use_global=d.d.webdav_use_global
|
||||
d.form.details.use_webdav=d.d.use_webdav
|
||||
}
|
||||
var cloudDiskUseStartupForWebDav = function(group,userDetails){
|
||||
group.cloudDiskUse['webdav'].name = 'WebDAV'
|
||||
group.cloudDiskUse['webdav'].sizeLimitCheck = (userDetails.use_webdav_size_limit === '1')
|
||||
if(!userDetails.webdav_size_limit || userDetails.webdav_size_limit === ''){
|
||||
group.cloudDiskUse['webdav'].sizeLimit = 10000
|
||||
}else{
|
||||
group.cloudDiskUse['webdav'].sizeLimit = parseFloat(userDetails.webdav_size_limit)
|
||||
}
|
||||
}
|
||||
var loadWebDavForUser = function(e){
|
||||
// e = user
|
||||
var userDetails = JSON.parse(e.details);
|
||||
if(userDetails.webdav_use_global === '1' && config.cloudUploaders && config.cloudUploaders.WebDAV){
|
||||
// {
|
||||
// webdav_user: "",
|
||||
// webdav_pass: "",
|
||||
// webdav_url: "",
|
||||
// webdav_dir: "",
|
||||
// }
|
||||
userDetails = Object.assign(userDetails,config.cloudUploaders.WebDAV)
|
||||
}
|
||||
//owncloud/webdav
|
||||
if(!s.group[e.ke].webdav &&
|
||||
userDetails.webdav_user&&
|
||||
userDetails.webdav_user!==''&&
|
||||
userDetails.webdav_pass&&
|
||||
userDetails.webdav_pass!==''&&
|
||||
userDetails.webdav_url&&
|
||||
userDetails.webdav_url!==''
|
||||
){
|
||||
if(!userDetails.webdav_dir||userDetails.webdav_dir===''){
|
||||
userDetails.webdav_dir='/'
|
||||
}
|
||||
userDetails.webdav_dir = s.checkCorrectPathEnding(userDetails.webdav_dir)
|
||||
s.group[e.ke].webdav = webdav(
|
||||
userDetails.webdav_url,
|
||||
userDetails.webdav_user,
|
||||
userDetails.webdav_pass
|
||||
)
|
||||
}
|
||||
}
|
||||
var unloadWebDavForUser = function(user){
|
||||
s.group[user.ke].webdav = null
|
||||
}
|
||||
var deleteVideoFromWebDav = function(e,video,callback){
|
||||
// e = user
|
||||
try{
|
||||
var videoDetails = JSON.parse(video.details)
|
||||
}catch(err){
|
||||
var videoDetails = video.details
|
||||
}
|
||||
if(!videoDetails.location){
|
||||
var prefix = s.addUserPassToUrl(s.checkCorrectPathEnding(s.group[e.ke].init.webdav_url),s.group[e.ke].init.webdav_user,s.group[e.ke].init.webdav_pass)
|
||||
videoDetails.location = video.href.replace(prefix,'')
|
||||
}
|
||||
s.group[e.ke].webdav.unlink(videoDetails.location, function(err) {
|
||||
if (err) console.log(videoDetails.location,err)
|
||||
callback()
|
||||
})
|
||||
}
|
||||
var uploadVideoToWebDav = function(e,k){
|
||||
//e = video object
|
||||
//k = temporary values
|
||||
if(!k)k={};
|
||||
//cloud saver - webdav
|
||||
var wfs = s.group[e.ke].webdav
|
||||
if(wfs && s.group[e.ke].init.use_webdav !== '0' && s.group[e.ke].init.webdav_save === "1"){
|
||||
var webdavUploadDir = s.group[e.ke].init.webdav_dir+e.ke+'/'+e.mid+'/'
|
||||
var startWebDavUpload = function(){
|
||||
s.group[e.ke].mon[e.id].webdavDirExist = true
|
||||
var wfsWriteStream =
|
||||
fs.createReadStream(k.dir + k.filename).pipe(wfs.createWriteStream(webdavUploadDir + k.filename))
|
||||
if(s.group[e.ke].init.webdav_log === '1'){
|
||||
var webdavRemoteUrl = s.addUserPassToUrl(s.checkCorrectPathEnding(s.group[e.ke].init.webdav_url),s.group[e.ke].init.webdav_user,s.group[e.ke].init.webdav_pass) + s.group[e.ke].init.webdav_dir + e.ke + '/'+e.mid+'/'+k.filename
|
||||
var save = [
|
||||
e.mid,
|
||||
e.ke,
|
||||
k.startTime,
|
||||
1,
|
||||
s.s({
|
||||
type : 'webdav',
|
||||
location : webdavUploadDir + k.filename
|
||||
}),
|
||||
k.filesize,
|
||||
k.endTime,
|
||||
webdavRemoteUrl
|
||||
]
|
||||
s.sqlQuery('INSERT INTO `Cloud Videos` (mid,ke,time,status,details,size,end,href) VALUES (?,?,?,?,?,?,?,?)',save)
|
||||
s.setCloudDiskUsedForGroup(e,{
|
||||
amount : k.filesizeMB,
|
||||
storageType : 'webdav'
|
||||
})
|
||||
s.purgeCloudDiskForGroup(e,'webdav')
|
||||
}
|
||||
}
|
||||
if(s.group[e.ke].mon[e.id].webdavDirExist !== true){
|
||||
//check if webdav dir exist
|
||||
var parentPoint = 0
|
||||
var webDavParentz = webdavUploadDir.split('/')
|
||||
var webDavParents = []
|
||||
webDavParentz.forEach(function(v){
|
||||
if(v && v !== '')webDavParents.push(v)
|
||||
})
|
||||
var stitchPieces = './'
|
||||
var lastParentCheck = function(){
|
||||
++parentPoint
|
||||
if(parentPoint === webDavParents.length){
|
||||
startWebDavUpload()
|
||||
}
|
||||
checkPathPiece(webDavParents[parentPoint])
|
||||
}
|
||||
var checkPathPiece = function(pathPiece){
|
||||
if(pathPiece && pathPiece !== ''){
|
||||
stitchPieces += pathPiece + '/'
|
||||
wfs.stat(stitchPieces, function(error, stats) {
|
||||
if(error){
|
||||
reply = {
|
||||
status : error.status,
|
||||
msg : lang.WebdavErrorTextTryCreatingDir,
|
||||
dir : stitchPieces,
|
||||
}
|
||||
s.userLog(e,{type:lang['Webdav Error'],msg:reply})
|
||||
wfs.mkdir(stitchPieces, function(error) {
|
||||
if(error){
|
||||
reply = {
|
||||
status : error.status,
|
||||
msg : lang.WebdavErrorTextCreatingDir,
|
||||
dir : stitchPieces,
|
||||
}
|
||||
s.userLog(e,{type:lang['Webdav Error'],msg:reply})
|
||||
}else{
|
||||
lastParentCheck()
|
||||
}
|
||||
})
|
||||
}else{
|
||||
lastParentCheck()
|
||||
}
|
||||
})
|
||||
}else{
|
||||
++parentPoint
|
||||
}
|
||||
}
|
||||
checkPathPiece(webDavParents[0])
|
||||
}else{
|
||||
startWebDavUpload()
|
||||
}
|
||||
}
|
||||
}
|
||||
//webdav
|
||||
s.addCloudUploader({
|
||||
name: 'webdav',
|
||||
loadGroupAppExtender: loadWebDavForUser,
|
||||
unloadGroupAppExtender: unloadWebDavForUser,
|
||||
insertCompletedVideoExtender: uploadVideoToWebDav,
|
||||
deleteVideoFromCloudExtensions: deleteVideoFromWebDav,
|
||||
cloudDiskUseStartupExtensions: cloudDiskUseStartupForWebDav,
|
||||
beforeAccountSave: beforeAccountSaveForWebDav,
|
||||
onAccountSave: cloudDiskUseStartupForWebDav,
|
||||
})
|
||||
}
|
58
libs/user.js
58
libs/user.js
|
@ -15,7 +15,7 @@ module.exports = function(s,config){
|
|||
if(s.group[e.ke].sizePurgeQueue.length > 0){
|
||||
checkQueue()
|
||||
}else{
|
||||
s.group[e.ke].sizePurging=false
|
||||
s.group[e.ke].sizePurging = false
|
||||
s.sendDiskUsedAmountToClients(e)
|
||||
}
|
||||
}
|
||||
|
@ -116,6 +116,9 @@ module.exports = function(s,config){
|
|||
s.tx({f:'log',ke:e.ke,mid:e.mid,log:x,time:s.timeObject()},'GRPLOG_'+e.ke);
|
||||
}
|
||||
s.loadGroup = function(e){
|
||||
s.loadGroupExtensions.forEach(function(extender){
|
||||
extender(e)
|
||||
})
|
||||
if(!s.group[e.ke]){
|
||||
s.group[e.ke]={}
|
||||
}
|
||||
|
@ -145,7 +148,7 @@ module.exports = function(s,config){
|
|||
ar=JSON.parse(r.details);
|
||||
//load extenders
|
||||
s.loadGroupAppExtensions.forEach(function(extender){
|
||||
extender(r)
|
||||
extender(r,ar)
|
||||
})
|
||||
//disk Used Emitter
|
||||
if(!s.group[e.ke].diskUsedEmitter){
|
||||
|
@ -257,11 +260,11 @@ module.exports = function(s,config){
|
|||
d.form.details.use_admin=d.d.use_admin
|
||||
d.form.details.use_ldap=d.d.use_ldap
|
||||
//check
|
||||
if(d.d.edit_days=="0"){
|
||||
d.form.details.days=d.d.days;
|
||||
if(d.d.edit_days == "0"){
|
||||
d.form.details.days = d.d.days;
|
||||
}
|
||||
if(d.d.edit_size=="0"){
|
||||
d.form.details.size=d.d.size;
|
||||
if(d.d.edit_size == "0"){
|
||||
d.form.details.size = d.d.size;
|
||||
}
|
||||
if(d.d.sub){
|
||||
d.form.details.sub=d.d.sub;
|
||||
|
@ -292,7 +295,7 @@ module.exports = function(s,config){
|
|||
var userDetails = JSON.parse(d.form.details)
|
||||
s.group[d.ke].sizeLimit = parseFloat(newSize)
|
||||
s.onAccountSaveExtensions.forEach(function(extender){
|
||||
extender(s.group[d.ke],userDetails)
|
||||
extender(s.group[d.ke],userDetails,user)
|
||||
})
|
||||
s.unloadGroupAppExtensions.forEach(function(extender){
|
||||
extender(user)
|
||||
|
@ -305,4 +308,45 @@ module.exports = function(s,config){
|
|||
}
|
||||
})
|
||||
}
|
||||
s.findPreset = function(presetQueryVals,callback){
|
||||
//presetQueryVals = [ke, type, name]
|
||||
s.sqlQuery("SELECT * FROM Presets WHERE ke=? AND type=? AND name=? LIMIT 1",presetQueryVals,function(err,presets){
|
||||
var preset
|
||||
var notFound = false
|
||||
if(presets && presets[0]){
|
||||
preset = presets[0]
|
||||
s.checkDetails(preset)
|
||||
}else{
|
||||
notFound = true
|
||||
}
|
||||
callback(notFound,preset)
|
||||
})
|
||||
}
|
||||
s.checkUserPurgeLock = function(groupKey){
|
||||
var userGroup = s.group[groupKey]
|
||||
if(s.group[groupKey].usedSpace > s.group[groupKey].sizeLimit){
|
||||
s.group[groupKey].sizePurgeQueue = []
|
||||
s.group[groupKey].sizePurging = false
|
||||
s.systemLog(lang.sizePurgeLockedText + ' : ' + groupKey)
|
||||
s.onStalePurgeLockExtensions.forEach(function(extender){
|
||||
extender(groupKey,s.group[groupKey].usedSpace,s.group[groupKey].sizeLimit)
|
||||
})
|
||||
}
|
||||
}
|
||||
if(config.cron.deleteOverMax === true){
|
||||
s.checkForStalePurgeLocks = function(){
|
||||
var doCheck = function(){
|
||||
Object.keys(s.group).forEach(function(groupKey){
|
||||
s.checkUserPurgeLock(groupKey)
|
||||
})
|
||||
}
|
||||
clearTimeout(s.checkForStalePurgeLocksInterval)
|
||||
s.checkForStalePurgeLocksInterval = setInterval(function(){
|
||||
doCheck()
|
||||
},1000 * 60 * 60)
|
||||
doCheck()
|
||||
}
|
||||
}else{
|
||||
s.checkForStalePurgeLocks = function(){}
|
||||
}
|
||||
}
|
||||
|
|
138
libs/videos.js
138
libs/videos.js
|
@ -230,11 +230,6 @@ module.exports = function(s,config,lang){
|
|||
})
|
||||
})
|
||||
})
|
||||
fs.chmod(videoSnap,0o777,function(err){
|
||||
if(!err){
|
||||
fs.unlink(videoSnap,function(err){})
|
||||
}
|
||||
})
|
||||
}else{
|
||||
console.log(new Error())
|
||||
console.log(lang['Database row does not exist'],queryValues)
|
||||
|
@ -242,55 +237,60 @@ module.exports = function(s,config,lang){
|
|||
})
|
||||
}
|
||||
s.deleteListOfVideos = function(videos){
|
||||
var query = 'DELETE FROM Videos WHERE '
|
||||
var videoQuery = []
|
||||
var queryValues = []
|
||||
videos.forEach(function(video){
|
||||
s.checkDetails(video)
|
||||
//e = video object
|
||||
video.dir = s.getVideoDirectory(video)
|
||||
if(!video.filename && video.time){
|
||||
video.filename = s.formattedTime(video.time)
|
||||
}
|
||||
var filename,
|
||||
time
|
||||
if(video.filename.indexOf('.')>-1){
|
||||
filename = video.filename
|
||||
}else{
|
||||
filename = video.filename+'.'+video.ext
|
||||
}
|
||||
if(video.filename && !video.time){
|
||||
time = s.nameToTime(filename)
|
||||
}else{
|
||||
time = video.time
|
||||
}
|
||||
time = new Date(time)
|
||||
fs.chmod(video.dir+filename,0o777,function(err){
|
||||
s.tx({
|
||||
f: 'video_delete',
|
||||
filename: filename,
|
||||
mid: video.id,
|
||||
ke: video.ke,
|
||||
time: s.nameToTime(filename),
|
||||
end: s.formattedTime(new Date,'YYYY-MM-DD HH:mm:ss')
|
||||
},'GRP_'+video.ke);
|
||||
s.setDiskUsedForGroup(video,-(video.size / 1000000))
|
||||
fs.unlink(video.dir+filename,function(err){
|
||||
fs.stat(video.dir+filename,function(err){
|
||||
if(!err){
|
||||
s.file('delete',video.dir+filename)
|
||||
}
|
||||
var deleteSetOfVideos = function(videos){
|
||||
var query = 'DELETE FROM Videos WHERE '
|
||||
var videoQuery = []
|
||||
var queryValues = []
|
||||
videos.forEach(function(video){
|
||||
s.checkDetails(video)
|
||||
//e = video object
|
||||
video.dir = s.getVideoDirectory(video)
|
||||
if(!video.filename && video.time){
|
||||
video.filename = s.formattedTime(video.time)
|
||||
}
|
||||
var filename,
|
||||
time
|
||||
if(video.filename.indexOf('.')>-1){
|
||||
filename = video.filename
|
||||
}else{
|
||||
filename = video.filename+'.'+video.ext
|
||||
}
|
||||
if(video.filename && !video.time){
|
||||
time = s.nameToTime(filename)
|
||||
}else{
|
||||
time = video.time
|
||||
}
|
||||
time = new Date(time)
|
||||
fs.chmod(video.dir+filename,0o777,function(err){
|
||||
s.tx({
|
||||
f: 'video_delete',
|
||||
filename: filename,
|
||||
mid: video.id,
|
||||
ke: video.ke,
|
||||
time: s.nameToTime(filename),
|
||||
end: s.formattedTime(new Date,'YYYY-MM-DD HH:mm:ss')
|
||||
},'GRP_'+video.ke);
|
||||
s.setDiskUsedForGroup(video,-(video.size / 1000000))
|
||||
fs.unlink(video.dir+filename,function(err){
|
||||
fs.stat(video.dir+filename,function(err){
|
||||
if(!err){
|
||||
s.file('delete',video.dir+filename)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
videoQuery.push('(`mid`=? AND `ke`=? AND `time`=?)')
|
||||
queryValues = queryValues.concat([video.id,video.ke,time])
|
||||
})
|
||||
videoQuery.push('(`mid`=? AND `ke`=? AND `time`=?)')
|
||||
queryValues = queryValues.concat([video.id,video.ke,time])
|
||||
})
|
||||
query += videoQuery.join(' OR ')
|
||||
s.sqlQuery(query,queryValues,function(err){
|
||||
if(err){
|
||||
s.systemLog(lang['List of Videos Delete Error'],err)
|
||||
}
|
||||
query += videoQuery.join(' OR ')
|
||||
s.sqlQuery(query,queryValues,function(err){
|
||||
if(err){
|
||||
s.systemLog(lang['List of Videos Delete Error'],err)
|
||||
}
|
||||
})
|
||||
}
|
||||
videos.chunk(100).forEach(function(videosChunk){
|
||||
deleteSetOfVideos(videosChunk)
|
||||
})
|
||||
}
|
||||
s.deleteVideoFromCloudExtensions = {}
|
||||
|
@ -378,4 +378,40 @@ module.exports = function(s,config,lang){
|
|||
finish()
|
||||
}
|
||||
}
|
||||
s.streamMp4FileOverHttp = function(filePath,req,res){
|
||||
var ext = filePath.split('.')
|
||||
ext = filePath[filePath.length - 1]
|
||||
var total = fs.statSync(filePath).size;
|
||||
if (req.headers['range']) {
|
||||
try{
|
||||
var range = req.headers.range;
|
||||
var parts = range.replace(/bytes=/, "").split("-");
|
||||
var partialstart = parts[0];
|
||||
var partialend = parts[1];
|
||||
var start = parseInt(partialstart, 10);
|
||||
var end = partialend ? parseInt(partialend, 10) : total-1;
|
||||
var chunksize = (end-start)+1;
|
||||
var file = fs.createReadStream(filePath, {start: start, end: end});
|
||||
req.headerWrite={ 'Content-Range': 'bytes ' + start + '-' + end + '/' + total, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': 'video/'+req.ext }
|
||||
req.writeCode=206
|
||||
}catch(err){
|
||||
req.headerWrite={ 'Content-Length': total, 'Content-Type': 'video/'+req.ext};
|
||||
var file = fs.createReadStream(filePath)
|
||||
req.writeCode=200
|
||||
}
|
||||
} else {
|
||||
req.headerWrite={ 'Content-Length': total, 'Content-Type': 'video/'+req.ext};
|
||||
var file = fs.createReadStream(filePath)
|
||||
req.writeCode=200
|
||||
}
|
||||
if(req.query.downloadName){
|
||||
req.headerWrite['content-disposition']='attachment; filename="'+req.query.downloadName+'"';
|
||||
}
|
||||
res.writeHead(req.writeCode,req.headerWrite);
|
||||
file.on('close',function(){
|
||||
res.end()
|
||||
})
|
||||
file.pipe(res)
|
||||
return file
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,8 @@ module.exports = function(s,config,lang,io){
|
|||
if(config.renderPaths.grid === undefined){config.renderPaths.grid='pages/grid'}
|
||||
//slick.js (cycle) page
|
||||
if(config.renderPaths.cycle === undefined){config.renderPaths.cycle='pages/cycle'}
|
||||
// Use uws/cws
|
||||
if(config.useUWebsocketJs === undefined){config.useUWebsocketJs=true}
|
||||
//SSL options
|
||||
if(config.ssl&&config.ssl.key&&config.ssl.cert){
|
||||
config.ssl.key=fs.readFileSync(s.checkRelativePath(config.ssl.key),'utf8')
|
||||
|
@ -111,5 +113,11 @@ module.exports = function(s,config,lang,io){
|
|||
path:s.checkCorrectPathEnding(config.webPaths.super)+'socket.io',
|
||||
transports: ['websocket']
|
||||
})
|
||||
if(config.useUWebsocketJs === true){
|
||||
io.engine.ws = new (require('cws').Server)({
|
||||
noServer: true,
|
||||
perMessageDeflate: false
|
||||
})
|
||||
}
|
||||
return app
|
||||
}
|
||||
|
|
|
@ -21,10 +21,10 @@ module.exports = function(s,config,lang,app){
|
|||
return
|
||||
}
|
||||
var form = s.getPostData(req)
|
||||
var uid = s.getPostData(req,'uid',false)
|
||||
var mail = s.getPostData(req,'mail',false)
|
||||
var uid = form.uid || s.getPostData(req,'uid',false)
|
||||
var mail = form.mail || s.getPostData(req,'mail',false)
|
||||
if(form){
|
||||
var keys = Object.keys(form)
|
||||
var keys = ['details']
|
||||
var condition = []
|
||||
var value = []
|
||||
keys.forEach(function(v){
|
||||
|
@ -68,8 +68,9 @@ module.exports = function(s,config,lang,app){
|
|||
s.closeJsonResponse(res,endData)
|
||||
return
|
||||
}
|
||||
var uid = s.getPostData(req,'uid',false)
|
||||
var mail = s.getPostData(req,'mail',false)
|
||||
var form = s.getPostData(req)
|
||||
var uid = form.uid || s.getPostData(req,'uid',false)
|
||||
var mail = form.mail || s.getPostData(req,'mail',false)
|
||||
s.sqlQuery('DELETE FROM Users WHERE uid=? AND ke=? AND mail=?',[uid,req.params.ke,mail])
|
||||
s.sqlQuery("SELECT * FROM API WHERE ke=? AND uid=?",[req.params.ke,uid],function(err,rows){
|
||||
if(rows && rows[0]){
|
||||
|
@ -132,6 +133,12 @@ module.exports = function(s,config,lang,app){
|
|||
uid: newId,
|
||||
mail: form.mail
|
||||
},'ADM_'+req.params.ke)
|
||||
endData.user = {
|
||||
details: s.parseJSON(details),
|
||||
ke: req.params.ke,
|
||||
uid: newId,
|
||||
mail: form.mail
|
||||
}
|
||||
}
|
||||
res.end(s.prettyPrint(endData))
|
||||
})
|
||||
|
@ -159,7 +166,6 @@ module.exports = function(s,config,lang,app){
|
|||
ok: false
|
||||
}
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
s.auth(req.params,function(user){
|
||||
var hasRestrictions = user.details.sub && user.details.allmonitors !== '1'
|
||||
if(req.params.f !== 'delete'){
|
||||
|
@ -238,7 +244,6 @@ module.exports = function(s,config,lang,app){
|
|||
],function (req,res){
|
||||
var endData = {ok:false}
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
s.auth(req.params,function(user){
|
||||
var endData = {
|
||||
ok : false
|
||||
|
@ -266,6 +271,7 @@ module.exports = function(s,config,lang,app){
|
|||
},'GRP_' + req.params.ke)
|
||||
endData.ok = true
|
||||
}
|
||||
endData.api = insert
|
||||
s.closeJsonResponse(res,endData)
|
||||
})
|
||||
}else{
|
||||
|
@ -283,7 +289,6 @@ module.exports = function(s,config,lang,app){
|
|||
],function (req,res){
|
||||
var endData = {ok:false}
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
s.auth(req.params,function(user){
|
||||
var endData = {
|
||||
ok : false
|
||||
|
@ -336,7 +341,6 @@ module.exports = function(s,config,lang,app){
|
|||
],function (req,res){
|
||||
var endData = {ok:false}
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
s.auth(req.params,function(user){
|
||||
var endData = {
|
||||
ok : false
|
||||
|
@ -366,7 +370,7 @@ module.exports = function(s,config,lang,app){
|
|||
/**
|
||||
* API : Administrator : Get Monitor State Presets List
|
||||
*/
|
||||
app.get([
|
||||
app.all([
|
||||
config.webPaths.apiPrefix+':auth/monitorStates/:ke',
|
||||
config.webPaths.adminApiPrefix+':auth/monitorStates/:ke'
|
||||
],function (req,res){
|
||||
|
@ -411,19 +415,7 @@ module.exports = function(s,config,lang,app){
|
|||
s.closeJsonResponse(res,endData)
|
||||
return
|
||||
}
|
||||
var findPreset = function(callback){
|
||||
s.sqlQuery("SELECT * FROM Presets WHERE ke=? AND type=? AND name=? LIMIT 1",[req.params.ke,'monitorStates',req.params.stateName],function(err,presets){
|
||||
var preset
|
||||
var notFound = false
|
||||
if(presets && presets[0]){
|
||||
preset = presets[0]
|
||||
s.checkDetails(preset)
|
||||
}else{
|
||||
notFound = true
|
||||
}
|
||||
callback(notFound,preset)
|
||||
})
|
||||
}
|
||||
var presetQueryVals = [req.params.ke,'monitorStates',req.params.stateName]
|
||||
switch(req.params.action){
|
||||
case'insert':case'edit':
|
||||
var form = s.getPostData(req)
|
||||
|
@ -433,7 +425,7 @@ module.exports = function(s,config,lang,app){
|
|||
s.closeJsonResponse(res,endData)
|
||||
return
|
||||
}
|
||||
findPreset(function(notFound,preset){
|
||||
s.findPreset(presetQueryVals,function(notFound,preset){
|
||||
if(notFound === true){
|
||||
endData.msg = lang["Inserted State Configuration"]
|
||||
var details = {
|
||||
|
@ -470,7 +462,7 @@ module.exports = function(s,config,lang,app){
|
|||
})
|
||||
break;
|
||||
case'delete':
|
||||
findPreset(function(notFound,preset){
|
||||
s.findPreset(presetQueryVals,function(notFound,preset){
|
||||
if(notFound === true){
|
||||
endData.msg = user.lang['State Configuration Not Found']
|
||||
s.closeJsonResponse(res,endData)
|
||||
|
@ -486,43 +478,8 @@ module.exports = function(s,config,lang,app){
|
|||
})
|
||||
break;
|
||||
default://change monitors according to state
|
||||
findPreset(function(notFound,preset){
|
||||
if(notFound === false){
|
||||
var sqlQuery = 'SELECT * FROM Monitors WHERE ke=? AND '
|
||||
var monitorQuery = []
|
||||
var sqlQueryValues = [req.params.ke]
|
||||
var monitorPresets = {}
|
||||
preset.details.monitors.forEach(function(monitor){
|
||||
monitorQuery.push('mid=?')
|
||||
sqlQueryValues.push(monitor.mid)
|
||||
monitorPresets[monitor.mid] = monitor
|
||||
})
|
||||
sqlQuery += '('+monitorQuery.join(' OR ')+')'
|
||||
s.sqlQuery(sqlQuery,sqlQueryValues,function(err,monitors){
|
||||
if(monitors && monitors[0]){
|
||||
monitors.forEach(function(monitor){
|
||||
s.checkDetails(monitor)
|
||||
s.checkDetails(monitorPresets[monitor.mid])
|
||||
var monitorPreset = monitorPresets[monitor.mid]
|
||||
monitorPreset.details = Object.assign(monitor.details,monitorPreset.details)
|
||||
monitor = s.cleanMonitorObjectForDatabase(Object.assign(monitor,monitorPreset))
|
||||
monitor.details = JSON.stringify(monitor.details)
|
||||
s.addOrEditMonitor(Object.assign(monitor,{}),function(err,endData){
|
||||
|
||||
},user)
|
||||
})
|
||||
endData.ok = true
|
||||
s.tx({f:'change_group_state',ke:req.params.ke,name:req.params.stateName},'GRP_'+req.params.ke)
|
||||
s.closeJsonResponse(res,endData)
|
||||
}else{
|
||||
endData.msg = user.lang['State Configuration has no monitors associated']
|
||||
s.closeJsonResponse(res,endData)
|
||||
}
|
||||
})
|
||||
}else{
|
||||
endData.msg = user.lang['State Configuration Not Found']
|
||||
s.closeJsonResponse(res,endData)
|
||||
}
|
||||
s.activateMonitorStates(req.params.ke,req.params.stateName,user,function(endData){
|
||||
s.closeJsonResponse(res,endData)
|
||||
})
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ var execSync = require('child_process').execSync;
|
|||
var exec = require('child_process').exec;
|
||||
var spawn = require('child_process').spawn;
|
||||
var httpProxy = require('http-proxy');
|
||||
var onvif = require('node-onvif');
|
||||
var proxy = httpProxy.createProxyServer({})
|
||||
var ejs = require('ejs');
|
||||
var CircularJSON = require('circular-json');
|
||||
|
@ -67,6 +68,10 @@ module.exports = function(s,config,lang,app,io){
|
|||
app.use(s.checkCorrectPathEnding(config.webPaths.super)+'libs',express.static(s.mainDirectory + '/web/libs'))
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({extended: true}));
|
||||
app.use(function (req,res,next){
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
next()
|
||||
})
|
||||
app.set('views', s.mainDirectory + '/web');
|
||||
app.set('view engine','ejs');
|
||||
//add template handler
|
||||
|
@ -125,7 +130,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
app.get(config.webPaths.apiPrefix+':auth/userInfo/:ke',function (req,res){
|
||||
req.ret={ok:false};
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
s.auth(req.params,function(user){
|
||||
req.ret.ok=true
|
||||
req.ret.user=user
|
||||
|
@ -151,9 +155,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
s.checkCorrectPathEnding(config.webPaths.super)+':screen',
|
||||
],function (req,res){
|
||||
req.ip = s.getClientIp(req)
|
||||
if(req.query.json === 'true'){
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
}
|
||||
var screenChooser = function(screen){
|
||||
var search = function(screen){
|
||||
if(req.url.indexOf(screen) > -1){
|
||||
|
@ -181,7 +182,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
s.renderPage(req,res,config.renderPaths.index,{
|
||||
failedLogin: true,
|
||||
message: lang.failedLoginText1,
|
||||
lang: lang,
|
||||
lang: s.copySystemDefaultLanguage(),
|
||||
config: config,
|
||||
screen: screenChooser(req.params.screen)
|
||||
},function(err,html){
|
||||
|
@ -239,7 +240,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
s.renderPage(req,res,config.renderPaths.index,{
|
||||
failedLogin: true,
|
||||
message: lang.failedLoginText2,
|
||||
lang: lang,
|
||||
lang: s.copySystemDefaultLanguage(),
|
||||
config: config,
|
||||
screen: screenChooser(req.params.screen)
|
||||
},function(err,html){
|
||||
|
@ -286,7 +287,8 @@ module.exports = function(s,config,lang,app,io){
|
|||
// config: config,
|
||||
$user: req.resp,
|
||||
lang: r.lang,
|
||||
define: s.getDefinitonFile(r.details.lang)
|
||||
define: s.getDefinitonFile(r.details.lang),
|
||||
customAutoLoad: s.customAutoLoadTree
|
||||
})
|
||||
})
|
||||
break;
|
||||
|
@ -297,7 +299,8 @@ module.exports = function(s,config,lang,app,io){
|
|||
// config: config,
|
||||
$user: req.resp,
|
||||
lang: r.lang,
|
||||
define: s.getDefinitonFile(r.details.lang)
|
||||
define: s.getDefinitonFile(r.details.lang),
|
||||
customAutoLoad: s.customAutoLoadTree
|
||||
})
|
||||
})
|
||||
break;
|
||||
|
@ -311,17 +314,36 @@ module.exports = function(s,config,lang,app,io){
|
|||
$subs: rr,
|
||||
$mons: rrr,
|
||||
lang: r.lang,
|
||||
define: s.getDefinitonFile(r.details.lang)
|
||||
define: s.getDefinitonFile(r.details.lang),
|
||||
customAutoLoad: s.customAutoLoadTree
|
||||
})
|
||||
})
|
||||
})
|
||||
}else{
|
||||
//not admin user
|
||||
renderPage(config.renderPaths.home,{$user:req.resp,config:config,lang:r.lang,define:s.getDefinitonFile(r.details.lang),addStorage:s.dir.addStorage,fs:fs,__dirname:s.mainDirectory});
|
||||
renderPage(config.renderPaths.home,{
|
||||
$user:req.resp,
|
||||
config:config,
|
||||
lang:r.lang,
|
||||
define:s.getDefinitonFile(r.details.lang),
|
||||
addStorage:s.dir.addStorage,
|
||||
fs:fs,
|
||||
__dirname:s.mainDirectory,
|
||||
customAutoLoad: s.customAutoLoadTree
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
renderPage(config.renderPaths.home,{$user:req.resp,config:config,lang:r.lang,define:s.getDefinitonFile(r.details.lang),addStorage:s.dir.addStorage,fs:fs,__dirname:s.mainDirectory});
|
||||
renderPage(config.renderPaths.home,{
|
||||
$user:req.resp,
|
||||
config:config,
|
||||
lang:r.lang,
|
||||
define:s.getDefinitonFile(r.details.lang),
|
||||
addStorage:s.dir.addStorage,
|
||||
fs:fs,
|
||||
__dirname:s.mainDirectory,
|
||||
customAutoLoad: s.customAutoLoadTree
|
||||
});
|
||||
break;
|
||||
}
|
||||
s.userLog({ke:r.ke,mid:'$USER'},{type:r.lang['New Authentication Token'],msg:{for:req.body.function,mail:r.mail,id:r.uid,ip:req.ip}})
|
||||
|
@ -511,6 +533,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
r=[]
|
||||
}
|
||||
data.Logs = r
|
||||
data.customAutoLoad = s.customAutoLoadTree
|
||||
fs.readFile(s.location.config,'utf8',function(err,file){
|
||||
data.plainConfig = JSON.parse(file)
|
||||
renderPage(config.renderPaths.super,data)
|
||||
|
@ -558,7 +581,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
* API : Brute Protection Lock Reset by API
|
||||
*/
|
||||
app.get([config.webPaths.apiPrefix+':auth/resetBruteProtection/:ke'], function (req,res){
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
s.auth(req.params,function(user){
|
||||
if(s.failedLoginAttempts[user.mail]){
|
||||
clearTimeout(s.failedLoginAttempts[user.mail].timeout)
|
||||
|
@ -576,7 +598,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
config.webPaths.apiPrefix+':auth/cycle/:ke',
|
||||
config.webPaths.apiPrefix+':auth/cycle/:ke/:group'
|
||||
], function(req,res) {
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
s.auth(req.params,function(user){
|
||||
if(user.permissions.get_monitors==="0"){
|
||||
res.end(user.lang['Not Permitted'])
|
||||
|
@ -705,7 +726,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
}else{
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
}
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
req.fn=function(user){
|
||||
if(user.permissions.get_monitors==="0"){
|
||||
res.end(s.prettyPrint([]))
|
||||
|
@ -821,7 +841,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
app.get([config.webPaths.apiPrefix+':auth/monitor/:ke',config.webPaths.apiPrefix+':auth/monitor/:ke/:id'], function (req,res){
|
||||
req.ret={ok:false};
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
req.fn=function(user){
|
||||
if(user.permissions.get_monitors==="0"){
|
||||
res.end(s.prettyPrint([]))
|
||||
|
@ -903,6 +922,54 @@ module.exports = function(s,config,lang,app,io){
|
|||
s.auth(req.params,req.fn,res,req);
|
||||
});
|
||||
/**
|
||||
* API : Merge Recorded Videos into one file
|
||||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/videosMerge/:ke', function (req,res){
|
||||
var failed = function(resp){
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(s.prettyPrint(resp))
|
||||
}
|
||||
if(req.query.videos && req.query.videos !== ''){
|
||||
s.auth(req.params,function(user){
|
||||
var videosSelected = JSON.parse(req.query.videos)
|
||||
var where = []
|
||||
var values = []
|
||||
videosSelected.forEach(function(video){
|
||||
where.push("(ke=? AND mid=? AND `time`=?)")
|
||||
if(!video.ke)video.ke = req.params.ke
|
||||
values.push(video.ke)
|
||||
values.push(video.mid)
|
||||
var time = s.nameToTime(video.filename)
|
||||
if(req.query.isUTC === 'true'){
|
||||
time = s.utcToLocal(time)
|
||||
}
|
||||
time = new Date(time)
|
||||
values.push(time)
|
||||
})
|
||||
s.sqlQuery('SELECT * FROM Videos WHERE '+where.join(' OR '),values,function(err,r){
|
||||
var resp = {ok: false}
|
||||
if(r && r[0]){
|
||||
s.mergeRecordedVideos(r,req.params.ke,function(fullPath,filename){
|
||||
res.setHeader('Content-Disposition', 'attachment; filename="'+filename+'"')
|
||||
var file = fs.createReadStream(fullPath)
|
||||
file.on('close',function(){
|
||||
setTimeout(function(){
|
||||
s.file('delete',fullPath)
|
||||
},1000 * 60 * 3)
|
||||
res.end()
|
||||
})
|
||||
file.pipe(res)
|
||||
})
|
||||
}else{
|
||||
failed({ok:false,msg:'No Videos Found'})
|
||||
}
|
||||
})
|
||||
},res,req);
|
||||
}else{
|
||||
failed({ok:false,msg:'"videos" query variable is missing from request.'})
|
||||
}
|
||||
})
|
||||
/**
|
||||
* API : Get Videos
|
||||
*/
|
||||
app.get([
|
||||
|
@ -912,7 +979,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
config.webPaths.apiPrefix+':auth/cloudVideos/:ke/:id'
|
||||
], function (req,res){
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
s.auth(req.params,function(user){
|
||||
var hasRestrictions = user.details.sub && user.details.allmonitors !== '1'
|
||||
if(
|
||||
|
@ -1033,7 +1099,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
app.get([config.webPaths.apiPrefix+':auth/events/:ke',config.webPaths.apiPrefix+':auth/events/:ke/:id',config.webPaths.apiPrefix+':auth/events/:ke/:id/:limit',config.webPaths.apiPrefix+':auth/events/:ke/:id/:limit/:start',config.webPaths.apiPrefix+':auth/events/:ke/:id/:limit/:start/:end'], function (req,res){
|
||||
req.ret={ok:false};
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
s.auth(req.params,function(user){
|
||||
if(user.permissions.watch_videos==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.video_view.indexOf(req.params.id)===-1){
|
||||
res.end(s.prettyPrint([]))
|
||||
|
@ -1091,7 +1156,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
app.get([config.webPaths.apiPrefix+':auth/logs/:ke',config.webPaths.apiPrefix+':auth/logs/:ke/:id'], function (req,res){
|
||||
req.ret={ok:false};
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
s.auth(req.params,function(user){
|
||||
if(user.permissions.get_logs==="0" || user.details.sub && user.details.view_logs !== '1'){
|
||||
res.end(s.prettyPrint([]))
|
||||
|
@ -1156,7 +1220,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
app.get(config.webPaths.apiPrefix+':auth/smonitor/:ke', function (req,res){
|
||||
req.ret={ok:false};
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
req.fn=function(user){
|
||||
if(user.permissions.get_monitors==="0"){
|
||||
res.end(s.prettyPrint([]))
|
||||
|
@ -1193,7 +1256,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
app.get([config.webPaths.apiPrefix+':auth/monitor/:ke/:id/:f',config.webPaths.apiPrefix+':auth/monitor/:ke/:id/:f/:ff',config.webPaths.apiPrefix+':auth/monitor/:ke/:id/:f/:ff/:fff'], function (req,res){
|
||||
req.ret={ok:false};
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
s.auth(req.params,function(user){
|
||||
if(user.permissions.control_monitors==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitor_edit.indexOf(req.params.id)===-1){
|
||||
res.end(user.lang['Not Permitted'])
|
||||
|
@ -1288,7 +1350,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
*/
|
||||
app.get([config.webPaths.apiPrefix+':auth/fileBin/:ke',config.webPaths.apiPrefix+':auth/fileBin/:ke/:id'],function (req,res){
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
req.fn=function(user){
|
||||
req.sql='SELECT * FROM Files WHERE ke=?';req.ar=[req.params.ke];
|
||||
if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
|
||||
|
@ -1321,7 +1382,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
* API : Get fileBin file
|
||||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/fileBin/:ke/:id/:year/:month/:day/:file', function (req,res){
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
req.fn=function(user){
|
||||
req.failed=function(){
|
||||
res.end(user.lang['File Not Found'])
|
||||
|
@ -1352,7 +1412,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
* API : Zip Videos and Get Link from fileBin
|
||||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/zipVideos/:ke', function (req,res){
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
var failed = function(resp){
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(s.prettyPrint(resp))
|
||||
|
@ -1375,7 +1434,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
values.push(time)
|
||||
})
|
||||
s.sqlQuery('SELECT * FROM Videos WHERE '+where.join(' OR '),values,function(err,r){
|
||||
var resp = {ok:false}
|
||||
var resp = {ok: false}
|
||||
if(r && r[0]){
|
||||
resp.ok = true
|
||||
var zipDownload = null
|
||||
|
@ -1396,7 +1455,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
fs.mkdirSync(fileBinDir);
|
||||
}
|
||||
r.forEach(function(video){
|
||||
timeFormatted = s.formattedTime(video.time)
|
||||
var timeFormatted = s.formattedTime(video.time)
|
||||
video.filename = timeFormatted+'.'+video.ext
|
||||
var dir = s.getVideoDirectory(video)+video.filename
|
||||
var tempVideoFile = timeFormatted+' - '+video.mid+'.'+video.ext
|
||||
|
@ -1418,16 +1477,27 @@ module.exports = function(s,config,lang,app,io){
|
|||
var zipDownload = fs.createReadStream(zippedFile)
|
||||
zipDownload.pipe(res)
|
||||
zipDownload.on('error', function (error) {
|
||||
s.userLog({ke:req.params.ke,mid:'$USER'},{title:'Zip Download Error',msg:error.toString()})
|
||||
var errorString = error.toString()
|
||||
s.userLog({
|
||||
ke: req.params.ke,
|
||||
mid: '$USER'
|
||||
},{
|
||||
title: 'Zip Download Error',
|
||||
msg: errorString
|
||||
})
|
||||
if(zipDownload && zipDownload.destroy){
|
||||
zipDownload.destroy()
|
||||
}
|
||||
});
|
||||
res.end(s.prettyPrint({
|
||||
ok: false,
|
||||
msg: errorString
|
||||
}))
|
||||
})
|
||||
zipDownload.on('close', function () {
|
||||
res.end()
|
||||
zipDownload.destroy();
|
||||
fs.unlinkSync(zippedFile);
|
||||
});
|
||||
zipDownload.destroy()
|
||||
fs.unlinkSync(zippedFile)
|
||||
})
|
||||
})
|
||||
}else{
|
||||
failed({ok:false,msg:'No Videos Found'})
|
||||
|
@ -1437,7 +1507,120 @@ module.exports = function(s,config,lang,app,io){
|
|||
}else{
|
||||
failed({ok:false,msg:'"videos" query variable is missing from request.'})
|
||||
}
|
||||
});
|
||||
})
|
||||
/**
|
||||
* API : Zip Cloud Videos and Get Link from fileBin
|
||||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/zipCloudVideos/:ke', function (req,res){
|
||||
var failed = function(resp){
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(s.prettyPrint(resp))
|
||||
}
|
||||
if(req.query.videos && req.query.videos !== ''){
|
||||
s.auth(req.params,function(user){
|
||||
var videosSelected = JSON.parse(req.query.videos)
|
||||
var where = []
|
||||
var values = []
|
||||
videosSelected.forEach(function(video){
|
||||
where.push("(ke=? AND mid=? AND `time`=?)")
|
||||
if(!video.ke)video.ke = req.params.ke
|
||||
values.push(video.ke)
|
||||
values.push(video.mid)
|
||||
var time = s.nameToTime(video.filename)
|
||||
if(req.query.isUTC === 'true'){
|
||||
time = s.utcToLocal(time)
|
||||
}
|
||||
time = new Date(time)
|
||||
values.push(time)
|
||||
})
|
||||
s.sqlQuery('SELECT * FROM `Cloud Videos` WHERE '+where.join(' OR '),values,function(err,r){
|
||||
var resp = {ok: false}
|
||||
if(r && r[0]){
|
||||
resp.ok = true
|
||||
var zipDownload = null
|
||||
var tempFiles = []
|
||||
var fileId = s.gid()
|
||||
var fileBinDir = s.dir.fileBin+req.params.ke+'/'
|
||||
var tempScript = s.dir.streams+req.params.ke+'/'+fileId+'.sh'
|
||||
var zippedFilename = s.formattedTime()+'-'+fileId+'-Shinobi_Cloud_Backed_Recordings.zip'
|
||||
var zippedFile = fileBinDir+zippedFilename
|
||||
var script = 'cd '+fileBinDir+' && zip -9 -r '+zippedFile
|
||||
res.on('close', () => {
|
||||
if(zipDownload && zipDownload.destroy){
|
||||
zipDownload.destroy()
|
||||
}
|
||||
fs.unlink(zippedFile);
|
||||
})
|
||||
if(!fs.existsSync(fileBinDir)){
|
||||
fs.mkdirSync(fileBinDir);
|
||||
}
|
||||
var cloudDownloadCount = 0
|
||||
var getFile = function(video,completed){
|
||||
if(!video)completed();
|
||||
s.checkDetails(video)
|
||||
var filename = video.href.split('/')
|
||||
filename = filename[filename.length - 1]
|
||||
var timeFormatted = s.formattedTime(video.time)
|
||||
var tempVideoFile = video.details.type + '-' + video.mid + '-' + filename
|
||||
var tempFileWriteStream = fs.createWriteStream(fileBinDir+tempVideoFile)
|
||||
tempFileWriteStream.on('finish', function() {
|
||||
++cloudDownloadCount
|
||||
getFile(r[cloudDownloadCount],completed)
|
||||
})
|
||||
var cloudVideoDownload = request(video.href)
|
||||
cloudVideoDownload.on('response', function (res) {
|
||||
res.pipe(tempFileWriteStream)
|
||||
})
|
||||
tempFiles.push(fileBinDir+tempVideoFile)
|
||||
script += ' "'+tempVideoFile+'"'
|
||||
}
|
||||
getFile(r[cloudDownloadCount],function(){
|
||||
fs.writeFileSync(tempScript,script,'utf8')
|
||||
var zipCreate = spawn('sh',(tempScript).split(' '),{detached: true})
|
||||
zipCreate.stderr.on('data',function(data){
|
||||
s.userLog({ke:req.params.ke,mid:'$USER'},{title:'Zip Create Error',msg:data.toString()})
|
||||
})
|
||||
zipCreate.on('exit',function(data){
|
||||
fs.unlinkSync(tempScript)
|
||||
tempFiles.forEach(function(file){
|
||||
fs.unlink(file,function(){})
|
||||
})
|
||||
res.setHeader('Content-Disposition', 'attachment; filename="' + zippedFilename + '"')
|
||||
var zipDownload = fs.createReadStream(zippedFile)
|
||||
zipDownload.pipe(res)
|
||||
zipDownload.on('error', function (error) {
|
||||
var errorString = error.toString()
|
||||
s.userLog({
|
||||
ke: req.params.ke,
|
||||
mid: '$USER'
|
||||
},{
|
||||
title: 'Zip Download Error',
|
||||
msg: errorString
|
||||
})
|
||||
if(zipDownload && zipDownload.destroy){
|
||||
zipDownload.destroy()
|
||||
}
|
||||
res.end(s.prettyPrint({
|
||||
ok: false,
|
||||
msg: errorString
|
||||
}))
|
||||
})
|
||||
zipDownload.on('close', function () {
|
||||
res.end()
|
||||
zipDownload.destroy()
|
||||
fs.unlinkSync(zippedFile)
|
||||
})
|
||||
})
|
||||
})
|
||||
}else{
|
||||
failed({ok:false,msg:'No Videos Found'})
|
||||
}
|
||||
})
|
||||
},res,req);
|
||||
}else{
|
||||
failed({ok:false,msg:'"videos" query variable is missing from request.'})
|
||||
}
|
||||
})
|
||||
/**
|
||||
* API : Get Cloud Video File (proxy)
|
||||
*/
|
||||
|
@ -1480,38 +1663,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
if(r&&r[0]){
|
||||
req.dir=s.getVideoDirectory(r[0])+req.params.file
|
||||
if (fs.existsSync(req.dir)){
|
||||
req.ext=req.params.file.split('.')[1];
|
||||
var total = fs.statSync(req.dir).size;
|
||||
if (req.headers['range']) {
|
||||
try{
|
||||
var range = req.headers.range;
|
||||
var parts = range.replace(/bytes=/, "").split("-");
|
||||
var partialstart = parts[0];
|
||||
var partialend = parts[1];
|
||||
var start = parseInt(partialstart, 10);
|
||||
var end = partialend ? parseInt(partialend, 10) : total-1;
|
||||
var chunksize = (end-start)+1;
|
||||
var file = fs.createReadStream(req.dir, {start: start, end: end});
|
||||
req.headerWrite={ 'Content-Range': 'bytes ' + start + '-' + end + '/' + total, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': 'video/'+req.ext }
|
||||
req.writeCode=206
|
||||
}catch(err){
|
||||
req.headerWrite={ 'Content-Length': total, 'Content-Type': 'video/'+req.ext};
|
||||
var file = fs.createReadStream(req.dir)
|
||||
req.writeCode=200
|
||||
}
|
||||
} else {
|
||||
req.headerWrite={ 'Content-Length': total, 'Content-Type': 'video/'+req.ext};
|
||||
var file=fs.createReadStream(req.dir)
|
||||
req.writeCode=200
|
||||
}
|
||||
if(req.query.downloadName){
|
||||
req.headerWrite['content-disposition']='attachment; filename="'+req.query.downloadName+'"';
|
||||
}
|
||||
res.writeHead(req.writeCode,req.headerWrite);
|
||||
file.on('close',function(){
|
||||
res.end();
|
||||
})
|
||||
file.pipe(res);
|
||||
s.streamMp4FileOverHttp(req.dir,req,res)
|
||||
}else{
|
||||
res.end(user.lang['File Not Found in Filesystem'])
|
||||
}
|
||||
|
@ -1524,27 +1676,34 @@ module.exports = function(s,config,lang,app,io){
|
|||
/**
|
||||
* API : Motion Trigger via GET request
|
||||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/motion/:ke/:id', function (req,res){
|
||||
s.auth(req.params,function(user){
|
||||
if(req.query.data){
|
||||
try{
|
||||
var d={id:req.params.id,ke:req.params.ke,details:JSON.parse(req.query.data)};
|
||||
}catch(err){
|
||||
res.end('Data Broken',err);
|
||||
return;
|
||||
}
|
||||
}else{
|
||||
res.end('No Data');
|
||||
return;
|
||||
}
|
||||
if(!d.ke||!d.id||!s.group[d.ke]){
|
||||
res.end(user.lang['No Group with this key exists']);
|
||||
return;
|
||||
}
|
||||
s.triggerEvent(d)
|
||||
res.end(user.lang['Trigger Successful'])
|
||||
},res,req);
|
||||
})
|
||||
app.get(config.webPaths.apiPrefix+':auth/motion/:ke/:id', function (req,res){
|
||||
s.auth(req.params,function(user){
|
||||
var endData = {
|
||||
|
||||
}
|
||||
if(req.query.data){
|
||||
try{
|
||||
var d = {
|
||||
id: req.params.id,
|
||||
ke: req.params.ke,
|
||||
details: JSON.parse(req.query.data)
|
||||
}
|
||||
}catch(err){
|
||||
res.end('Data Broken',err)
|
||||
return
|
||||
}
|
||||
}else{
|
||||
res.end('No Data')
|
||||
return
|
||||
}
|
||||
if(!d.ke||!d.id||!s.group[d.ke]){
|
||||
res.end(user.lang['No Group with this key exists'])
|
||||
return
|
||||
}
|
||||
s.triggerEvent(d)
|
||||
res.end(user.lang['Trigger Successful'])
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : WebHook Tester
|
||||
*/
|
||||
|
@ -1560,7 +1719,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/control/:ke/:id/:direction', function (req,res){
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
s.auth(req.params,function(user){
|
||||
s.cameraControl(req.params,function(resp){
|
||||
res.end(s.prettyPrint(resp))
|
||||
|
@ -1578,7 +1736,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
], function (req,res){
|
||||
req.ret={ok:false};
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
s.auth(req.params,function(user){
|
||||
if(user.permissions.watch_videos==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.video_delete.indexOf(req.params.id)===-1){
|
||||
res.end(user.lang['Not Permitted'])
|
||||
|
@ -1673,7 +1830,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
app.get(config.webPaths.apiPrefix+':auth/probe/:ke',function (req,res){
|
||||
req.ret={ok:false};
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
s.auth(req.params,function(user){
|
||||
switch(req.query.action){
|
||||
// case'stop':
|
||||
|
@ -1720,7 +1876,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
app.all([config.webPaths.apiPrefix+':auth/onvif/:ke/:id/:action',config.webPaths.apiPrefix+':auth/onvif/:ke/:id/:service/:action'],function (req,res){
|
||||
var response = {ok:false};
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
s.auth(req.params,function(user){
|
||||
var errorMessage = function(msg,error){
|
||||
response.ok = false
|
||||
|
@ -1865,4 +2020,13 @@ module.exports = function(s,config,lang,app,io){
|
|||
s.closeJsonResponse(res,endData)
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* Robots.txt
|
||||
*/
|
||||
app.get('/robots.txt', function (req,res){
|
||||
res.on('finish',function(){
|
||||
res.end()
|
||||
})
|
||||
fs.createReadStream(s.mainDirectory + '/web/pages/robots.txt').pipe(res)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ module.exports = function(s,config,lang,app){
|
|||
* Page : Get Embed Stream
|
||||
*/
|
||||
app.get([config.webPaths.apiPrefix+':auth/embed/:ke/:id',config.webPaths.apiPrefix+':auth/embed/:ke/:id/:addon'], function (req,res){
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
req.params.protocol=req.protocol;
|
||||
s.auth(req.params,function(user){
|
||||
if(user.permissions.watch_stream==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitors.indexOf(req.params.id)===-1){
|
||||
|
@ -100,7 +99,6 @@ module.exports = function(s,config,lang,app){
|
|||
* @param {string} full - if `true` page will load the MJPEG iframe page
|
||||
*/
|
||||
app.get([config.webPaths.apiPrefix+':auth/mjpeg/:ke/:id',config.webPaths.apiPrefix+':auth/mjpeg/:ke/:id/:channel'], function(req,res) {
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
if(req.query.full=='true'){
|
||||
s.renderPage(req,res,config.renderPaths.mjpeg,{url:config.webPaths.apiPrefix + req.params.auth+'/mjpeg/'+req.params.ke+'/'+req.params.id,originalURL:s.getOriginalUrl(req)});
|
||||
res.end()
|
||||
|
@ -163,7 +161,6 @@ module.exports = function(s,config,lang,app){
|
|||
* API : Get HLS Stream
|
||||
*/
|
||||
app.get([config.webPaths.apiPrefix+':auth/hls/:ke/:id/:file',config.webPaths.apiPrefix+':auth/hls/:ke/:id/:channel/:file'], function (req,res){
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
req.fn=function(user){
|
||||
s.checkChildProxy(req.params,function(){
|
||||
req.dir=s.dir.streams+req.params.ke+'/'+req.params.id+'/'
|
||||
|
@ -186,7 +183,6 @@ module.exports = function(s,config,lang,app){
|
|||
* API : Get JPEG Snapshot
|
||||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/jpeg/:ke/:id/s.jpg', function(req,res){
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
s.auth(req.params,function(user){
|
||||
s.checkChildProxy(req.params,function(){
|
||||
if(user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitors&&user.details.monitors.indexOf(req.params.id)===-1){
|
||||
|
@ -212,7 +208,6 @@ module.exports = function(s,config,lang,app){
|
|||
* API : Get FLV Stream
|
||||
*/
|
||||
app.get([config.webPaths.apiPrefix+':auth/flv/:ke/:id/s.flv',config.webPaths.apiPrefix+':auth/flv/:ke/:id/:channel/s.flv'], function(req,res) {
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
s.auth(req.params,function(user){
|
||||
s.checkChildProxy(req.params,function(){
|
||||
var Emitter,chunkChannel
|
||||
|
@ -263,7 +258,6 @@ module.exports = function(s,config,lang,app){
|
|||
* API : Get H.265/h265 HEVC stream
|
||||
*/
|
||||
app.get([config.webPaths.apiPrefix+':auth/h265/:ke/:id/s.hevc',config.webPaths.apiPrefix+':auth/h265/:ke/:id/:channel/s.hevc'], function(req,res) {
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
s.auth(req.params,function(user){
|
||||
s.checkChildProxy(req.params,function(){
|
||||
var Emitter,chunkChannel
|
||||
|
@ -313,7 +307,6 @@ module.exports = function(s,config,lang,app){
|
|||
config.webPaths.apiPrefix+':auth/h264/:ke/:id/:feed',
|
||||
config.webPaths.apiPrefix+':auth/h264/:ke/:id'
|
||||
], function (req, res) {
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
s.auth(req.params,function(user){
|
||||
s.checkChildProxy(req.params,function(){
|
||||
if(!req.query.feed){req.query.feed='1'}
|
||||
|
|
|
@ -288,6 +288,7 @@ module.exports = function(s,config,lang,app){
|
|||
]
|
||||
)
|
||||
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)
|
||||
}
|
||||
|
@ -324,7 +325,7 @@ module.exports = function(s,config,lang,app){
|
|||
r = r[0]
|
||||
var details = JSON.parse(r.details)
|
||||
if(form.pass && form.pass !== ''){
|
||||
if(form.pass === form.password_again){
|
||||
if(form.pass === form.password_again || form.pass_again){
|
||||
form.pass = s.createHash(form.pass);
|
||||
}else{
|
||||
endData.msg = lang["Passwords Don't Match"]
|
||||
|
@ -335,11 +336,18 @@ module.exports = function(s,config,lang,app){
|
|||
delete(form.pass);
|
||||
}
|
||||
delete(form.password_again);
|
||||
delete(form.pass_again);
|
||||
var keys = Object.keys(form)
|
||||
var set = []
|
||||
var values = []
|
||||
keys.forEach(function(v,n){
|
||||
if(set==='ke'||set==='password_again'||!form[v]){return}
|
||||
if(
|
||||
set === 'ke' ||
|
||||
!form[v]
|
||||
){
|
||||
//skip
|
||||
return
|
||||
}
|
||||
set.push(v+'=?')
|
||||
if(v === 'details'){
|
||||
form[v] = s.stringJSON(Object.assign(details,s.parseJSON(form[v])))
|
||||
|
@ -360,6 +368,9 @@ module.exports = function(s,config,lang,app){
|
|||
}
|
||||
close()
|
||||
})
|
||||
}else{
|
||||
endData.msg = lang['User Not Found']
|
||||
close()
|
||||
}
|
||||
})
|
||||
}else{
|
||||
|
@ -409,53 +420,218 @@ module.exports = function(s,config,lang,app){
|
|||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Superuser : Export Entire System
|
||||
* API : Superuser : Get Entire System
|
||||
*/
|
||||
app.all(config.webPaths.superApiPrefix+':auth/export/system', function (req,res){
|
||||
s.superAuth(req.params,function(resp){
|
||||
s.systemLog('Copy of the Database Exported',{
|
||||
by: resp.$user.mail,
|
||||
ip: resp.ip
|
||||
})
|
||||
var endData = {
|
||||
ok : true
|
||||
}
|
||||
// var database = s.getPostData(req,'database')
|
||||
endData.database = {}
|
||||
var tableNames = [
|
||||
'Users',
|
||||
'Monitors',
|
||||
'API',
|
||||
'Videos',
|
||||
'Cloud Videos',
|
||||
'Logs',
|
||||
'Files',
|
||||
'Presets',
|
||||
]
|
||||
var completedTables = 0
|
||||
var tableExportLoop = function(callback){
|
||||
var tableName = tableNames[completedTables]
|
||||
if(tableName){
|
||||
var tableIsSelected = s.getPostData(req,tableName) == 1
|
||||
if(tableIsSelected){
|
||||
s.sqlQuery('SELECT * FROM `' + tableName +'`',[],function(err,dataRows){
|
||||
endData.database[tableName] = dataRows
|
||||
++completedTables
|
||||
tableExportLoop(callback)
|
||||
})
|
||||
}else{
|
||||
++completedTables
|
||||
tableExportLoop(callback)
|
||||
}
|
||||
}else{
|
||||
callback()
|
||||
}
|
||||
}
|
||||
tableExportLoop(function(){
|
||||
s.closeJsonResponse(res,endData)
|
||||
})
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Superuser : Import Entire System
|
||||
*/
|
||||
app.all(config.webPaths.superApiPrefix+':auth/import/system', function (req,res){
|
||||
s.superAuth(req.params,function(resp){
|
||||
var endData = {
|
||||
ok : false
|
||||
}
|
||||
console.log(req.files)
|
||||
// insert data
|
||||
var data = s.getPostData(req)
|
||||
var database = s.getPostData(req,'database')
|
||||
if(data && data.database)database = data.database
|
||||
if(database){
|
||||
var rowsExistingAlready = {}
|
||||
var countOfRowsInserted = {}
|
||||
var countOfRowsExistingAlready = {}
|
||||
var insertRow = function(tableName,row,callback){
|
||||
if(!rowsExistingAlready[tableName])rowsExistingAlready[tableName] = []
|
||||
if(!countOfRowsExistingAlready[tableName])countOfRowsExistingAlready[tableName] = 0
|
||||
if(!countOfRowsInserted[tableName])countOfRowsInserted[tableName] = 0
|
||||
var fieldsToCheck = ['ke']
|
||||
switch(tableName){
|
||||
case'API':
|
||||
fieldsToCheck = fieldsToCheck.concat([
|
||||
'code',
|
||||
'uid'
|
||||
])
|
||||
break;
|
||||
case'Cloud Videos':
|
||||
fieldsToCheck = fieldsToCheck.concat([
|
||||
'href',
|
||||
'mid'
|
||||
])
|
||||
break;
|
||||
case'Videos':
|
||||
fieldsToCheck = fieldsToCheck.concat([
|
||||
'time',
|
||||
'mid'
|
||||
])
|
||||
break;
|
||||
case'Users':
|
||||
fieldsToCheck = fieldsToCheck.concat([
|
||||
'uid',
|
||||
'mail'
|
||||
])
|
||||
break;
|
||||
case'Presets':
|
||||
fieldsToCheck = fieldsToCheck.concat([
|
||||
'name',
|
||||
'type'
|
||||
])
|
||||
break;
|
||||
case'Logs':
|
||||
fieldsToCheck = fieldsToCheck.concat([
|
||||
'time',
|
||||
'info',
|
||||
'mid'
|
||||
])
|
||||
break;
|
||||
case'Events':
|
||||
fieldsToCheck = fieldsToCheck.concat([
|
||||
'time',
|
||||
'details',
|
||||
'mid'
|
||||
])
|
||||
break;
|
||||
case'Files':
|
||||
fieldsToCheck = fieldsToCheck.concat([
|
||||
'details',
|
||||
'name',
|
||||
'mid'
|
||||
])
|
||||
break;
|
||||
case'Monitors':
|
||||
fieldsToCheck = fieldsToCheck.concat([
|
||||
'host',
|
||||
'protocol',
|
||||
'port',
|
||||
'path',
|
||||
'mid'
|
||||
])
|
||||
break;
|
||||
}
|
||||
var keysToCheck = []
|
||||
var valuesToCheck = []
|
||||
fieldsToCheck.forEach(function(key){
|
||||
keysToCheck.push(key + '= ?')
|
||||
valuesToCheck.push(row[key])
|
||||
})
|
||||
s.sqlQuery('SELECT * FROM ' + tableName + ' WHERE ' + keysToCheck.join(' AND '),valuesToCheck,function(err,selected){
|
||||
if(selected && selected[0]){
|
||||
selected = selected[0]
|
||||
rowsExistingAlready[tableName].push(selected)
|
||||
callback()
|
||||
}else{
|
||||
var rowKeys = Object.keys(row)
|
||||
var insertEscapes = []
|
||||
var insertValues = []
|
||||
rowKeys.forEach(function(key){
|
||||
insertEscapes.push('?')
|
||||
insertValues.push(row[key])
|
||||
})
|
||||
s.sqlQuery('INSERT INTO ' + tableName + ' (' + rowKeys.join(',') +') VALUES (' + insertEscapes.join(',') + ')',insertValues,function(){
|
||||
if(!err){
|
||||
++countOfRowsInserted[tableName]
|
||||
}
|
||||
callback()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
var actionCount = {}
|
||||
var insertTableRows = function(tableName,rows,callback){
|
||||
if(!actionCount[tableName])actionCount[tableName] = 0
|
||||
var insertLoop = function(){
|
||||
var row = rows[actionCount[tableName]]
|
||||
if(row){
|
||||
insertRow(tableName,row,function(){
|
||||
++actionCount[tableName]
|
||||
insertLoop()
|
||||
})
|
||||
}else{
|
||||
callback()
|
||||
}
|
||||
}
|
||||
insertLoop()
|
||||
}
|
||||
var databaseTableKeys = Object.keys(database)
|
||||
var completedTables = 0
|
||||
var tableInsertLoop = function(callback){
|
||||
var tableName = databaseTableKeys[completedTables]
|
||||
var rows = database[databaseTableKeys[completedTables]]
|
||||
if(tableName){
|
||||
insertTableRows(tableName,rows,function(){
|
||||
++completedTables
|
||||
tableInsertLoop(callback)
|
||||
})
|
||||
}else{
|
||||
callback()
|
||||
}
|
||||
}
|
||||
tableInsertLoop(function(){
|
||||
endData.ok = true
|
||||
endData.tablesInsertedTo = databaseTableKeys
|
||||
endData.countOfRowsInserted = countOfRowsInserted
|
||||
endData.rowsExistingAlready = rowsExistingAlready
|
||||
s.closeJsonResponse(res,endData)
|
||||
})
|
||||
}else{
|
||||
endData.msg = lang['Database Not Found']
|
||||
s.closeJsonResponse(res,endData)
|
||||
}
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Superuser : Force Check for Stale Purge Locks
|
||||
*/
|
||||
app.all(config.webPaths.superApiPrefix+':auth/system/checkForStalePurgeLocks', function (req,res){
|
||||
s.superAuth(req.params,function(resp){
|
||||
var endData = {
|
||||
ok : true
|
||||
}
|
||||
var close = function(){
|
||||
res.end(s.prettyPrint(endData))
|
||||
}
|
||||
var account = s.getPostData(req,'account')
|
||||
s.sqlQuery('SELECT FROM Users',[],function(err,users){
|
||||
s.sqlQuery('SELECT FROM Monitors',[],function(err,monitors){
|
||||
s.sqlQuery('SELECT FROM API',[],function(err,monitors){
|
||||
s.sqlQuery('SELECT FROM Videos',[],function(err,monitors){
|
||||
s.sqlQuery('SELECT FROM Logs',[],function(err,monitors){
|
||||
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
s.sqlQuery('DELETE FROM API WHERE uid=? AND ke=?',[account.uid,account.ke])
|
||||
if(s.getPostData(req,'deleteSubAccounts',false) === '1'){
|
||||
s.sqlQuery('DELETE FROM Users WHERE ke=?',[account.ke])
|
||||
}
|
||||
if(s.getPostData(req,'deleteMonitors',false) == '1'){
|
||||
s.sqlQuery('SELECT * FROM Monitors WHERE ke=?',[account.ke],function(err,monitors){
|
||||
if(monitors && monitors[0]){
|
||||
monitors.forEach(function(monitor){
|
||||
s.camera('stop',monitor)
|
||||
})
|
||||
s.sqlQuery('DELETE FROM Monitors WHERE ke=?',[account.ke])
|
||||
}
|
||||
})
|
||||
}
|
||||
if(s.getPostData(req,'deleteVideos',false) == '1'){
|
||||
s.sqlQuery('DELETE FROM Videos WHERE ke=?',[account.ke])
|
||||
fs.chmod(s.dir.videos+account.ke,0o777,function(err){
|
||||
fs.unlink(s.dir.videos+account.ke,function(err){})
|
||||
})
|
||||
}
|
||||
if(s.getPostData(req,'deleteEvents',false) == '1'){
|
||||
s.sqlQuery('DELETE FROM Events WHERE ke=?',[account.ke])
|
||||
}
|
||||
s.tx({f:'delete_account',ke:account.ke,uid:account.uid,mail:account.mail},'$')
|
||||
close()
|
||||
s.checkForStalePurgeLocks()
|
||||
res.end(s.prettyPrint(endData))
|
||||
},res,req)
|
||||
})
|
||||
}
|
||||
|
|
3
plugins/python-contour/.gitignore
vendored
Normal file
3
plugins/python-contour/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
conf.json
|
||||
faces
|
||||
data
|
59
plugins/python-contour/INSTALL.sh
Normal file
59
plugins/python-contour/INSTALL.sh
Normal file
|
@ -0,0 +1,59 @@
|
|||
#!/bin/bash
|
||||
echo "-----------------------------------------------"
|
||||
echo "-- Installing Python Dlib Plugin for Shinobi --"
|
||||
echo "-----------------------------------------------"
|
||||
echo "-----------------------------------"
|
||||
if [ ! -e "./conf.json" ]; then
|
||||
echo "Creating conf.json"
|
||||
sudo cp conf.sample.json conf.json
|
||||
else
|
||||
echo "conf.json already exists..."
|
||||
fi
|
||||
echo "-----------------------------------"
|
||||
sudo apt update -y
|
||||
echo "Installing python3"
|
||||
sudo apt install python3 python3-dev python3-pip -y
|
||||
echo "-----------------------------------"
|
||||
sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
|
||||
sudo apt update
|
||||
sudo apt-get install gcc-6 g++-6 -y && sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-6 60 --slave /usr/bin/g++ g++ /usr/bin/g++-6
|
||||
echo "-----------------------------------"
|
||||
if ! [ -x "$(command -v nvidia-smi)" ]; then
|
||||
echo "You need to install NVIDIA Drivers to use this."
|
||||
echo "inside the Shinobi directory run the following :"
|
||||
echo "sh INSTALL/cuda9-part1.sh"
|
||||
exit 1
|
||||
else
|
||||
echo "NVIDIA Drivers found..."
|
||||
echo "$(nvidia-smi |grep 'Driver Version')"
|
||||
fi
|
||||
echo "-----------------------------------"
|
||||
if [ ! -d "/usr/local/cuda" ]; then
|
||||
echo "You need to install CUDA Toolkit to use this."
|
||||
echo "inside the Shinobi directory run the following :"
|
||||
echo "sh INSTALL/cuda9-part2-after-reboot.sh"
|
||||
exit 1
|
||||
else
|
||||
echo "CUDA Toolkit found..."
|
||||
fi
|
||||
echo "-----------------------------------"
|
||||
if ! [ -x "$(command -v opencv_version)" ]; then
|
||||
echo "You need to install OpenCV with CUDA first."
|
||||
echo "inside the Shinobi directory run the following :"
|
||||
echo "sh INSTALL/opencv-cuda.sh"
|
||||
exit 1
|
||||
else
|
||||
echo "OpenCV found... : $(opencv_version)"
|
||||
fi
|
||||
echo "-----------------------------------"
|
||||
echo "Getting new pip..."
|
||||
pip3 install --upgrade pip
|
||||
pip install --user --upgrade pip
|
||||
export PATH=/usr/local/cuda/bin:$PATH
|
||||
echo "Smoking pips..."
|
||||
pip3 install flask_socketio
|
||||
pip3 install flask
|
||||
pip3 install numpy
|
||||
pip3 install gevent gevent-websocket
|
||||
echo "Start the plugin with pm2 like so :"
|
||||
echo "pm2 start shinobi-python-dlib.js"
|
72
plugins/python-contour/README.md
Normal file
72
plugins/python-contour/README.md
Normal file
|
@ -0,0 +1,72 @@
|
|||
# Python Contour Detection with OpenCV
|
||||
|
||||
> This plugin requires the use of port `7990` by default. You can specify a different port by adding `pythonPort` to your plugin's conf.json.
|
||||
|
||||
**Ubuntu and Debian only**
|
||||
|
||||
Go to the Shinobi directory. **/home/Shinobi** is the default directory.
|
||||
|
||||
```
|
||||
cd /home/Shinobi/plugins/python-contour
|
||||
```
|
||||
|
||||
Copy the config file.
|
||||
|
||||
```
|
||||
sh INSTALL.sh
|
||||
```
|
||||
|
||||
Start the plugin.
|
||||
|
||||
```
|
||||
pm2 start shinobi-python-contour.js
|
||||
```
|
||||
|
||||
Doing this will reveal options in the monitor configuration. Shinobi does not need to be restarted when a plugin is initiated or stopped.
|
||||
|
||||
## Run the plugin as a Host
|
||||
> The main app (Shinobi) will be the client and the plugin will be the host. The purpose of allowing this method is so that you can use one plugin for multiple Shinobi instances. Allowing you to easily manage connections without starting multiple processes.
|
||||
|
||||
Edit your plugins configuration file. Set the `hostPort` **to be different** than the `listening port for camera.js`.
|
||||
|
||||
```
|
||||
nano conf.json
|
||||
```
|
||||
|
||||
Here is a sample of a Host configuration for the plugin.
|
||||
- `plug` is the name of the plugin corresponding in the main configuration file.
|
||||
- `https` choose if you want to use SSL or not. Default is `false`.
|
||||
- `hostPort` can be any available port number. **Don't make this the same port number as Shinobi.** Default is `8082`.
|
||||
- `type` tells the main application (Shinobi) what kind of plugin it is. In this case it is a detector.
|
||||
|
||||
```
|
||||
{
|
||||
"plug":"PythonContour",
|
||||
"hostPort":8082,
|
||||
"key":"SomeOpenALPRkeySoPeopleDontMessWithYourShinobi",
|
||||
"mode":"host",
|
||||
"type":"detector"
|
||||
}
|
||||
```
|
||||
|
||||
Now modify the **main configuration file** located in the main directory of Shinobi. *Where you currently should be.*
|
||||
|
||||
```
|
||||
nano conf.json
|
||||
```
|
||||
|
||||
Add the `plugins` array if you don't already have it. Add the following *object inside the array*.
|
||||
|
||||
```
|
||||
"plugins":[
|
||||
{
|
||||
"id" : "PythonContour",
|
||||
"https" : false,
|
||||
"host" : "localhost",
|
||||
"port" : 8082,
|
||||
"key" : "SomeOpenALPRkeySoPeopleDontMessWithYourShinobi",
|
||||
"mode" : "host",
|
||||
"type" : "detector"
|
||||
}
|
||||
],
|
||||
```
|
1
plugins/python-contour/bootPy.sh
Normal file
1
plugins/python-contour/bootPy.sh
Normal file
|
@ -0,0 +1 @@
|
|||
python3 -u $@
|
10
plugins/python-contour/conf.sample.json
Normal file
10
plugins/python-contour/conf.sample.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"plug":"PythonContour",
|
||||
"host":"localhost",
|
||||
"port":8080,
|
||||
"pythonPort":7990,
|
||||
"hostPort":8082,
|
||||
"key":"YOUR_CONTOUR_PLUGIN_KEY",
|
||||
"mode":"client",
|
||||
"type":"detector"
|
||||
}
|
18
plugins/python-contour/package.json
Normal file
18
plugins/python-contour/package.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "shinobi-python-contour",
|
||||
"version": "1.0.0",
|
||||
"description": "Contour plugin for Shinobi that uses Python functions for detection.",
|
||||
"main": "shinobi-python-contour.js",
|
||||
"dependencies": {
|
||||
"socket.io-client": "^1.7.4",
|
||||
"express": "^4.16.2",
|
||||
"moment": "^2.19.2",
|
||||
"socket.io": "^2.0.4"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Moe Alam",
|
||||
"license": "ISC"
|
||||
}
|
116
plugins/python-contour/pumpkin.py
Normal file
116
plugins/python-contour/pumpkin.py
Normal file
|
@ -0,0 +1,116 @@
|
|||
from flask import Flask, request, jsonify, render_template
|
||||
from flask_socketio import SocketIO, emit
|
||||
import cv2
|
||||
import os
|
||||
import json
|
||||
import numpy as np
|
||||
import sys
|
||||
|
||||
dirname = sys.argv[1]
|
||||
try:
|
||||
with open("{}/conf.json".format(dirname)) as json_file:
|
||||
config = json.load(json_file)
|
||||
httpPort = config['pythonPort']
|
||||
try:
|
||||
httpPort
|
||||
except NameError:
|
||||
httpPort = 7990
|
||||
except Exception as e:
|
||||
print("conf.json not found.")
|
||||
httpPort = 7990
|
||||
|
||||
# Load Flask
|
||||
app = Flask("Contour Detection for Shinobi (Pumpkin Pie)")
|
||||
socketio = SocketIO(app)
|
||||
# Silence Flask
|
||||
# import logging
|
||||
# log = logging.getLogger('werkzeug')
|
||||
# log.setLevel(logging.ERROR)
|
||||
|
||||
#load car detector
|
||||
oldFrames = {}
|
||||
|
||||
fgbg = cv2.createBackgroundSubtractorMOG2()
|
||||
|
||||
# detection function
|
||||
def spark(filepath,trackerId):
|
||||
try:
|
||||
filepath
|
||||
except NameError:
|
||||
return "File path not found."
|
||||
frame = cv2.imread(filepath)
|
||||
returnData = []
|
||||
# resize the frame, convert it to grayscale, and blur it
|
||||
# frame = imutils.resize(frame, width=500)
|
||||
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||||
gray = cv2.GaussianBlur(gray, (21, 21), 0)
|
||||
|
||||
# if the first frame is None, initialize it
|
||||
global oldFrames
|
||||
try:
|
||||
oldFrames[trackerId]
|
||||
except KeyError:
|
||||
oldFrames[trackerId] = None
|
||||
|
||||
if oldFrames[trackerId] is None:
|
||||
oldFrames[trackerId] = gray
|
||||
|
||||
# compute the absolute difference between the current frame and
|
||||
# first frame
|
||||
frameDelta = cv2.absdiff(oldFrames[trackerId], gray)
|
||||
thresh = cv2.threshold(frameDelta, 55, 255, cv2.THRESH_BINARY)[1]
|
||||
|
||||
# dilate the thresholded image to fill in holes, then find contours
|
||||
# on thresholded image
|
||||
thresh = cv2.dilate(thresh, None, iterations=2)
|
||||
image = thresh.copy()
|
||||
image,cnts,hierarchy = cv2.findContours(image, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
|
||||
|
||||
# loop over the contours
|
||||
for c in cnts:
|
||||
# if the contour is too small, ignore it
|
||||
#if cv2.contourArea(c) > args["max_area"] or cv2.contourArea < args["min_area"]:
|
||||
# continue
|
||||
d = max(cnts, key = cv2.contourArea)
|
||||
# compute the bounding box for the contour, draw it on the frame,
|
||||
# and update the text
|
||||
(x, y, w, h) = cv2.boundingRect(d)
|
||||
matrix = {}
|
||||
matrix["tag"] = "Contour"
|
||||
matrix["x"] = int(x)
|
||||
matrix["y"] = int(y)
|
||||
matrix["w"] = int(w)
|
||||
matrix["h"] = int(h)
|
||||
returnData.append(matrix)
|
||||
return returnData
|
||||
|
||||
# bake the image data by a file path
|
||||
# POST body contains the "img" variable. The value should be to a local image path.
|
||||
# Example : /dev/shm/streams/[GROUP_KEY]/[MONITOR_ID]/s.jpg
|
||||
@app.route('/', methods=['GET'])
|
||||
def index():
|
||||
return "Pumpkin.py is running. This web interface should NEVER be accessible remotely. The Node.js plugin that runs this script should only be allowed accessible remotely."
|
||||
|
||||
# bake the image data by a file path
|
||||
# POST body contains the "img" variable. The value should be to a local image path.
|
||||
# Example : /dev/shm/streams/[GROUP_KEY]/[MONITOR_ID]/s.jpg
|
||||
@app.route('/post', methods=['POST'])
|
||||
def post():
|
||||
filepath = request.form['img']
|
||||
return jsonify(spark(filepath))
|
||||
|
||||
# bake the image data by a file path
|
||||
# GET string contains the "img" variable. The value should be to a local image path.
|
||||
# Example : /dev/shm/streams/[GROUP_KEY]/[MONITOR_ID]/s.jpg
|
||||
@app.route('/get', methods=['GET'])
|
||||
def get():
|
||||
filepath = request.args.get('img')
|
||||
return jsonify(spark(filepath))
|
||||
|
||||
@socketio.on('f')
|
||||
def receiveMessage(message):
|
||||
emit('f',{'id':message.get("id"),'data':spark(message.get("path"),message.get("trackerId"))})
|
||||
|
||||
# quick-and-dirty start
|
||||
if __name__ == '__main__':
|
||||
socketio.run(app, port=httpPort)
|
298
plugins/python-contour/shinobi-python-contour.js
Normal file
298
plugins/python-contour/shinobi-python-contour.js
Normal file
|
@ -0,0 +1,298 @@
|
|||
//
|
||||
// Shinobi - Python DLIB Plugin
|
||||
// Copyright (C) 2016-2025 Moe Alam, moeiscool
|
||||
//
|
||||
// # Donate
|
||||
//
|
||||
// If you like what I am doing here and want me to continue please consider donating :)
|
||||
// PayPal : paypal@m03.ca
|
||||
//
|
||||
process.on('uncaughtException', function (err) {
|
||||
console.error('uncaughtException',err);
|
||||
});
|
||||
//main vars
|
||||
var fs=require('fs');
|
||||
var exec = require('child_process').exec;
|
||||
var spawn = require('child_process').spawn;
|
||||
var moment = require('moment');
|
||||
var http = require('http');
|
||||
var express = require('express');
|
||||
var socketIoClient = require('socket.io-client');
|
||||
var config = require('./conf.json');
|
||||
var http = require('http'),
|
||||
app = express(),
|
||||
server = http.createServer(app);
|
||||
|
||||
exec("kill $(ps aux | grep '[p]ython3 pumpkin.py' | awk '{print $2}')")
|
||||
|
||||
s={
|
||||
group:{},
|
||||
dir:{},
|
||||
isWin:(process.platform==='win32'),
|
||||
s:function(json){return JSON.stringify(json,null,3)}
|
||||
}
|
||||
s.checkCorrectPathEnding=function(x){
|
||||
var length=x.length
|
||||
if(x.charAt(length-1)!=='/'){
|
||||
x=x+'/'
|
||||
}
|
||||
return x.replace('__DIR__',__dirname)
|
||||
}
|
||||
s.debugLog = function(){
|
||||
if(config.debugLog === true){
|
||||
console.log(new Date(),arguments)
|
||||
if(config.debugLogVerbose === true){
|
||||
console.log(new Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!config.port){config.port=8080}
|
||||
if(!config.pythonScript){config.pythonScript=__dirname+'/pumpkin.py'}
|
||||
if(!config.pythonPort){config.pythonPort=7990}
|
||||
if(!config.hostPort){config.hostPort=8082}
|
||||
if(config.systemLog===undefined){config.systemLog=true}
|
||||
//default stream folder check
|
||||
if(!config.streamDir){
|
||||
if(s.isWin===false){
|
||||
config.streamDir='/dev/shm'
|
||||
}else{
|
||||
config.streamDir=config.windowsTempDir
|
||||
}
|
||||
if(!fs.existsSync(config.streamDir)){
|
||||
config.streamDir=__dirname+'/streams/'
|
||||
}else{
|
||||
config.streamDir+='/streams/'
|
||||
}
|
||||
}
|
||||
s.dir.streams=config.streamDir;
|
||||
//streams dir
|
||||
if(!fs.existsSync(s.dir.streams)){
|
||||
fs.mkdirSync(s.dir.streams);
|
||||
}
|
||||
s.gid=function(x){
|
||||
if(!x){x=10};var t = "";var p = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
for( var i=0; i < x; i++ )
|
||||
t += p.charAt(Math.floor(Math.random() * p.length));
|
||||
return t;
|
||||
};
|
||||
s.getRequest = function(url,callback){
|
||||
return http.get(url, function(res){
|
||||
var body = '';
|
||||
res.on('data', function(chunk){
|
||||
body += chunk;
|
||||
});
|
||||
res.on('end',function(){
|
||||
try{body = JSON.parse(body)}catch(err){}
|
||||
callback(body)
|
||||
});
|
||||
}).on('error', function(e){
|
||||
// s.systemLog("Get Snapshot Error", e);
|
||||
});
|
||||
}
|
||||
s.multiplerHeight = 1
|
||||
s.multiplerWidth = 1
|
||||
s.detectObject=function(buffer,d,tx){
|
||||
d.tmpFile=s.gid(5)+'.jpg'
|
||||
if(!fs.existsSync(s.dir.streams)){
|
||||
fs.mkdirSync(s.dir.streams);
|
||||
}
|
||||
d.dir=s.dir.streams+d.ke+'/'
|
||||
if(!fs.existsSync(d.dir)){
|
||||
fs.mkdirSync(d.dir);
|
||||
}
|
||||
d.dir=s.dir.streams+d.ke+'/'+d.id+'/'
|
||||
if(!fs.existsSync(d.dir)){
|
||||
fs.mkdirSync(d.dir);
|
||||
}
|
||||
fs.writeFile(d.dir+d.tmpFile,buffer,function(err){
|
||||
if(err) return s.systemLog(err);
|
||||
if(s.isPythonRunning === false){
|
||||
return console.log('Python Script is not Running.')
|
||||
}
|
||||
var callbackId = s.gid(10)
|
||||
s.group[d.ke][d.id].sendToPython({path:d.dir+d.tmpFile,id:callbackId,trackerId:d.ke+d.id},function(data){
|
||||
if(data.length > 0){
|
||||
var mats=[]
|
||||
data.forEach(function(v){
|
||||
mats.push({
|
||||
x:v.x,
|
||||
y:v.y,
|
||||
width: v.w,
|
||||
height: v.h,
|
||||
confidence:v.confidence,
|
||||
tag:v.tag
|
||||
})
|
||||
})
|
||||
tx({
|
||||
f:'trigger',
|
||||
id:d.id,
|
||||
ke:d.ke,
|
||||
details:{
|
||||
plug:config.plug,
|
||||
name:'dlib',
|
||||
reason:'object',
|
||||
matrices:mats,
|
||||
imgHeight:parseFloat(d.mon.detector_scale_y),
|
||||
imgWidth:parseFloat(d.mon.detector_scale_x)
|
||||
}
|
||||
})
|
||||
}
|
||||
delete(s.callbacks[callbackId])
|
||||
exec('rm -rf '+d.dir+d.tmpFile,{encoding:'utf8'})
|
||||
})
|
||||
})
|
||||
}
|
||||
s.systemLog=function(q,w,e){
|
||||
if(w===undefined){return}
|
||||
if(!w){w=''}
|
||||
if(!e){e=''}
|
||||
if(config.systemLog===true){
|
||||
return console.log(moment().format(),q,w,e)
|
||||
}
|
||||
}
|
||||
s.MainEventController=function(d,cn,tx){
|
||||
switch(d.f){
|
||||
case'init_plugin_as_host':
|
||||
if(!cn){
|
||||
console.log('No CN',d)
|
||||
return
|
||||
}
|
||||
if(d.key!==config.key){
|
||||
console.log(new Date(),'Plugin Key Mismatch',cn.request.connection.remoteAddress,d)
|
||||
cn.emit('init',{ok:false})
|
||||
cn.disconnect()
|
||||
}else{
|
||||
console.log(new Date(),'Plugin Connected to Client',cn.request.connection.remoteAddress)
|
||||
cn.emit('init',{ok:true,plug:config.plug,notice:config.notice,type:config.type})
|
||||
}
|
||||
break;
|
||||
case'init_monitor':
|
||||
if(s.group[d.ke]&&s.group[d.ke][d.id]){
|
||||
delete(s.group[d.ke][d.id].buffer)
|
||||
s.group[d.ke][d.id].refreshTracker(d.ke+d.id)
|
||||
}
|
||||
break;
|
||||
case'frame':
|
||||
try{
|
||||
if(!s.group[d.ke]){
|
||||
s.group[d.ke]={}
|
||||
}
|
||||
if(!s.group[d.ke][d.id]){
|
||||
var engine = s.createCameraBridgeToPython(d.ke+d.id)
|
||||
s.group[d.ke][d.id]={
|
||||
sendToPython : engine.sendToPython,
|
||||
refreshTracker : engine.refreshTracker
|
||||
}
|
||||
}
|
||||
if(!s.group[d.ke][d.id].buffer){
|
||||
s.group[d.ke][d.id].buffer=[d.frame];
|
||||
}else{
|
||||
s.group[d.ke][d.id].buffer.push(d.frame)
|
||||
}
|
||||
if(d.frame[d.frame.length-2] === 0xFF && d.frame[d.frame.length-1] === 0xD9){
|
||||
s.detectObject(Buffer.concat(s.group[d.ke][d.id].buffer),d,tx)
|
||||
s.group[d.ke][d.id].buffer=null;
|
||||
}
|
||||
}catch(err){
|
||||
if(err){
|
||||
s.systemLog(err)
|
||||
delete(s.group[d.ke][d.id].buffer)
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
server.listen(config.hostPort);
|
||||
//web pages and plugin api
|
||||
app.get('/', function (req, res) {
|
||||
res.end('<b>'+config.plug+'</b> for Shinobi is running')
|
||||
});
|
||||
//Conector to Shinobi
|
||||
if(config.mode==='host'){
|
||||
//start plugin as host
|
||||
var io = require('socket.io')(server);
|
||||
io.attach(server);
|
||||
s.connectedClients={};
|
||||
io.on('connection', function (cn) {
|
||||
s.connectedClients[cn.id]={id:cn.id}
|
||||
s.connectedClients[cn.id].tx = function(data){
|
||||
data.pluginKey=config.key;data.plug=config.plug;
|
||||
return io.to(cn.id).emit('ocv',data);
|
||||
}
|
||||
cn.on('f',function(d){
|
||||
s.MainEventController(d,cn,s.connectedClients[cn.id].tx)
|
||||
});
|
||||
cn.on('disconnect',function(d){
|
||||
delete(s.connectedClients[cn.id])
|
||||
})
|
||||
});
|
||||
}else{
|
||||
//start plugin as client
|
||||
if(!config.host){config.host='localhost'}
|
||||
var io = socketIoClient('ws://'+config.host+':'+config.port);//connect to master
|
||||
s.cx=function(x){x.pluginKey=config.key;x.plug=config.plug;return io.emit('ocv',x)}
|
||||
io.on('connect',function(d){
|
||||
s.cx({f:'init',plug:config.plug,notice:config.notice,type:config.type});
|
||||
})
|
||||
io.on('disconnect',function(d){
|
||||
io.connect();
|
||||
})
|
||||
io.on('f',function(d){
|
||||
s.MainEventController(d,null,s.cx)
|
||||
})
|
||||
}
|
||||
|
||||
//Start Python Controller
|
||||
s.callbacks = {}
|
||||
s.createCameraBridgeToPython = function(uniqueId){
|
||||
var pythonIo = socketIoClient('ws://localhost:'+config.pythonPort,{transports : ['websocket']});
|
||||
var sendToPython = function(data,callback){
|
||||
s.callbacks[data.id] = callback
|
||||
pythonIo.emit('f',data)
|
||||
}
|
||||
var refreshTracker = function(data){
|
||||
pythonIo.emit('refreshTracker',{trackerId : data})
|
||||
}
|
||||
pythonIo.on('connect',function(d){
|
||||
s.debugLog(uniqueId+' is Connected from Python')
|
||||
})
|
||||
pythonIo.on('disconnect',function(d){
|
||||
s.debugLog(uniqueId+' is Disconnected from Python')
|
||||
setTimeout(function(){
|
||||
pythonIo.connect();
|
||||
s.debugLog(uniqueId+' is Attempting to Reconect to Python')
|
||||
},3000)
|
||||
})
|
||||
pythonIo.on('f',function(d){
|
||||
if(s.callbacks[d.id]){
|
||||
s.callbacks[d.id](d.data)
|
||||
delete(s.callbacks[d.id])
|
||||
}
|
||||
})
|
||||
return {refreshTracker : refreshTracker, sendToPython : sendToPython}
|
||||
}
|
||||
|
||||
|
||||
//Start Python Daemon
|
||||
process.env.PYTHONUNBUFFERED = 1;
|
||||
s.createPythonProcess = function(){
|
||||
s.isPythonRunning = false
|
||||
s.pythonScript = spawn('sh',[__dirname+'/bootPy.sh',config.pythonScript,__dirname]);
|
||||
var onStdErr = function(data){
|
||||
s.debugLog(data.toString())
|
||||
}
|
||||
var onStdOut = function(data){
|
||||
s.debugLog(data.toString())
|
||||
}
|
||||
setTimeout(function(){
|
||||
s.isPythonRunning = true
|
||||
},5000)
|
||||
s.pythonScript.stderr.on('data',onStdErr);
|
||||
|
||||
s.pythonScript.stdout.on('data',onStdOut);
|
||||
|
||||
s.pythonScript.on('close', function () {
|
||||
s.debugLog('Python CLOSED')
|
||||
});
|
||||
}
|
||||
s.createPythonProcess()
|
|
@ -1,113 +0,0 @@
|
|||
-- --------------------------------------------------------
|
||||
-- Host: 192.168.88.58
|
||||
-- Server version: 5.7.17-0ubuntu0.16.04.1 - (Ubuntu)
|
||||
-- Server OS: Linux
|
||||
-- HeidiSQL Version: 9.3.0.4984
|
||||
-- --------------------------------------------------------
|
||||
|
||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET NAMES utf8mb4 */;
|
||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||
|
||||
-- Dumping structure for table ccio.API
|
||||
CREATE TABLE IF NOT EXISTS `API` (
|
||||
`ke` varchar(50) DEFAULT NULL,
|
||||
`uid` varchar(50) DEFAULT NULL,
|
||||
`ip` tinytext,
|
||||
`code` varchar(100) DEFAULT NULL,
|
||||
`details` text,
|
||||
`time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
|
||||
|
||||
-- Dumping structure for table ccio.Events
|
||||
CREATE TABLE IF NOT EXISTS `Events` (
|
||||
`ke` varchar(50) DEFAULT NULL,
|
||||
`mid` varchar(50) DEFAULT NULL,
|
||||
`details` text,
|
||||
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
|
||||
|
||||
-- Dumping structure for table ccio.Logs
|
||||
CREATE TABLE IF NOT EXISTS `Logs` (
|
||||
`ke` varchar(50) DEFAULT NULL,
|
||||
`mid` varchar(50) DEFAULT NULL,
|
||||
`info` text,
|
||||
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
|
||||
|
||||
-- Dumping structure for table ccio.Monitors
|
||||
CREATE TABLE IF NOT EXISTS `Monitors` (
|
||||
`mid` varchar(50) DEFAULT NULL,
|
||||
`ke` varchar(50) DEFAULT NULL,
|
||||
`name` varchar(50) DEFAULT NULL,
|
||||
`shto` text,
|
||||
`shfr` text,
|
||||
`details` longtext,
|
||||
`type` varchar(50) DEFAULT 'jpeg',
|
||||
`ext` varchar(50) DEFAULT 'webm',
|
||||
`protocol` varchar(50) DEFAULT 'http',
|
||||
`host` varchar(100) DEFAULT '0.0.0.0',
|
||||
`path` varchar(100) DEFAULT '/',
|
||||
`port` int(8) DEFAULT '80',
|
||||
`fps` int(8) DEFAULT '1',
|
||||
`mode` varchar(15) DEFAULT NULL,
|
||||
`width` int(11) DEFAULT '640',
|
||||
`height` int(11) DEFAULT '360'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
|
||||
|
||||
-- Dumping structure for table ccio.Presets
|
||||
CREATE TABLE IF NOT EXISTS `Presets` (
|
||||
`ke` varchar(50) DEFAULT NULL,
|
||||
`name` text,
|
||||
`details` text,
|
||||
`type` enum('monitor','event','user') DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
|
||||
|
||||
-- Dumping structure for table ccio.Users
|
||||
CREATE TABLE IF NOT EXISTS `Users` (
|
||||
`ke` varchar(50) DEFAULT NULL,
|
||||
`uid` varchar(50) DEFAULT NULL,
|
||||
`auth` varchar(50) DEFAULT NULL,
|
||||
`mail` varchar(100) DEFAULT NULL,
|
||||
`pass` varchar(100) DEFAULT NULL,
|
||||
`details` longtext,
|
||||
UNIQUE KEY `mail` (`mail`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
|
||||
|
||||
-- Dumping structure for table ccio.Videos
|
||||
CREATE TABLE IF NOT EXISTS `Videos` (
|
||||
`mid` varchar(50) DEFAULT NULL,
|
||||
`ke` varchar(50) DEFAULT NULL,
|
||||
`ext` enum('webm','mp4') DEFAULT NULL,
|
||||
`time` timestamp NULL DEFAULT NULL,
|
||||
`duration` float DEFAULT NULL,
|
||||
`size` float DEFAULT NULL,
|
||||
`frames` int(11) DEFAULT NULL,
|
||||
`end` timestamp NULL DEFAULT NULL,
|
||||
`status` int(1) DEFAULT '0' COMMENT '0:Building,1:Complete,2:Read,3:Archive',
|
||||
`details` text
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
|
||||
/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */;
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
|
@ -1,6 +1,6 @@
|
|||
-- --------------------------------------------------------
|
||||
-- Host: 192.168.88.37
|
||||
-- Server version: 10.1.25-MariaDB- - Ubuntu 17.04
|
||||
-- Host: 192.168.1.31
|
||||
-- Server version: 10.1.30-MariaDB-0ubuntu0.17.10.1 - Ubuntu 17.10
|
||||
-- Server OS: debian-linux-gnu
|
||||
-- HeidiSQL Version: 9.4.0.5125
|
||||
-- --------------------------------------------------------
|
||||
|
@ -26,6 +26,19 @@ CREATE TABLE IF NOT EXISTS `API` (
|
|||
`time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
-- Dumping structure for table ccio.Cloud Videos
|
||||
CREATE TABLE IF NOT EXISTS `Cloud Videos` (
|
||||
`mid` varchar(50) NOT NULL,
|
||||
`ke` varchar(50) DEFAULT NULL,
|
||||
`href` text NOT NULL,
|
||||
`size` float DEFAULT NULL,
|
||||
`time` timestamp NULL DEFAULT NULL,
|
||||
`end` timestamp NULL DEFAULT NULL,
|
||||
`status` int(1) DEFAULT '0' COMMENT '0:Complete,1:Read,2:Archive',
|
||||
`details` text
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
-- Dumping structure for table ccio.Events
|
||||
CREATE TABLE IF NOT EXISTS `Events` (
|
||||
|
@ -101,6 +114,29 @@ CREATE TABLE IF NOT EXISTS `Videos` (
|
|||
`details` text
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
-- Dumping structure for table ccio.Files
|
||||
CREATE TABLE IF NOT EXISTS `Files` (
|
||||
`ke` varchar(50) NOT NULL,
|
||||
`mid` varchar(50) NOT NULL,
|
||||
`name` tinytext NOT NULL,
|
||||
`size` float NOT NULL DEFAULT '0',
|
||||
`details` text NOT NULL,
|
||||
`status` int(1) NOT NULL DEFAULT '0'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
ALTER TABLE `Files` ADD COLUMN `time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `status`;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
-- Dumping structure for table ccio.Schedules
|
||||
CREATE TABLE IF NOT EXISTS `Schedules` (
|
||||
`ke` varchar(50) DEFAULT NULL,
|
||||
`name` text,
|
||||
`details` text,
|
||||
`start` varchar(10) DEFAULT NULL,
|
||||
`end` varchar(10) DEFAULT NULL,
|
||||
`enabled` int(1) NOT NULL DEFAULT '1'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
|
||||
/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */;
|
||||
|
|
606
test/run.js
606
test/run.js
|
@ -1,94 +1,151 @@
|
|||
module.exports = function(s,config,lang,app,io){
|
||||
var fs = require('fs')
|
||||
var request = require('request')
|
||||
var execSync = require('child_process').execSync
|
||||
module.exports = function(s,config,lang,io){
|
||||
var temp = {}
|
||||
var superUsers = require(s.location.super)
|
||||
var requestURL = 'http://'+config.bindip + ':' + config.port +'/'
|
||||
var requestSuperURL = 'http://localhost:' + config.port +'/super/' + superUsers[0].tokens[0] + '/'
|
||||
var getBaseURL = function(){
|
||||
return 'http://localhost:' + config.port +'/'
|
||||
}
|
||||
var buildRegularApiRequestURL = function(auth,path,groupKey){
|
||||
return getBaseURL() + auth + '/' + path + '/' + groupKey + '/'
|
||||
}
|
||||
var buildAdminRequestURL = function(auth,path,groupKey){
|
||||
return getBaseURL() + 'admin/' + auth + '/' + path + '/' + groupKey + '/'
|
||||
}
|
||||
var checkResult = function(functionName,expectedResult,testResult){
|
||||
if(expectedResult !== testResult){
|
||||
console.log(expectedResult,testResult)
|
||||
throw new Error('x ' + functionName + ' : Failed!')
|
||||
console.log('x ' + functionName + ' : Failed!')
|
||||
return false
|
||||
}else{
|
||||
console.log('- ' + functionName + ' : Success')
|
||||
console.log('✓ ' + functionName + ' : Success')
|
||||
return true
|
||||
}
|
||||
}
|
||||
var administratorAccountData = {
|
||||
"mail":"test@test1.com",
|
||||
"pass":"test1",
|
||||
"pass_again":"test1",
|
||||
"ke":"GroupKey123456",
|
||||
"details":{
|
||||
"factorAuth": "0",
|
||||
"size": "10000",
|
||||
"days": "5",
|
||||
"event_days": "10",
|
||||
"log_days": "10",
|
||||
"max_camera": "",
|
||||
"permissions": "all",
|
||||
"edit_size": "1",
|
||||
"edit_days": "1",
|
||||
"edit_event_days": "1",
|
||||
"edit_log_days": "1",
|
||||
"use_admin": "1",
|
||||
"use_aws_s3": "1",
|
||||
"use_webdav": "1",
|
||||
"use_discordbot": "1",
|
||||
"use_ldap": "1"
|
||||
}
|
||||
}
|
||||
var getAdministratorAccountData = function(){
|
||||
return Object.assign(administratorAccountData,{})
|
||||
}
|
||||
var sampleMonitorObject = require('./testMonitor-WatchOnly.json')
|
||||
var test = {
|
||||
"basic.js" : {
|
||||
checkRelativePath : function(){
|
||||
checkRelativePath : function(next){
|
||||
var expectedResult = s.mainDirectory + '/'
|
||||
var testResult = s.checkRelativePath('')
|
||||
checkResult('checkRelativePath',expectedResult,testResult)
|
||||
checkResult('Internal Function : checkRelativePath',expectedResult,testResult)
|
||||
next()
|
||||
},
|
||||
parseJSON : function(){
|
||||
parseJSON : function(next){
|
||||
var expectedResult = {}
|
||||
var testResult = s.parseJSON('{}')
|
||||
checkResult('parseJSON',JSON.stringify(expectedResult),JSON.stringify(testResult))
|
||||
checkResult('Internal Function : parseJSON',JSON.stringify(expectedResult),JSON.stringify(testResult))
|
||||
next()
|
||||
},
|
||||
stringJSON : function(){
|
||||
stringJSON : function(next){
|
||||
var expectedResult = '{}'
|
||||
var testResult = s.stringJSON({})
|
||||
checkResult('stringJSON',expectedResult,testResult)
|
||||
checkResult('Internal Function : stringJSON',expectedResult,testResult)
|
||||
next()
|
||||
},
|
||||
addUserPassToUrl : function(){
|
||||
addUserPassToUrl : function(next){
|
||||
var expectedResult = 'http://user:pass@url.com'
|
||||
var testResult = s.addUserPassToUrl('http://url.com','user','pass')
|
||||
checkResult('addUserPassToUrl',expectedResult,testResult)
|
||||
checkResult('Internal Function : addUserPassToUrl',expectedResult,testResult)
|
||||
next()
|
||||
},
|
||||
checkCorrectPathEnding : function(){
|
||||
checkCorrectPathEnding : function(next){
|
||||
var expectedResult = '/'
|
||||
var testResult = s.checkCorrectPathEnding('')
|
||||
checkResult('checkCorrectPathEnding',expectedResult,testResult)
|
||||
checkResult('Internal Function : checkCorrectPathEnding',expectedResult,testResult)
|
||||
next()
|
||||
},
|
||||
md5 : function(){
|
||||
md5 : function(next){
|
||||
var expectedResult = '5f4dcc3b5aa765d61d8327deb882cf99'
|
||||
var testResult = s.md5('password')
|
||||
checkResult('md5',expectedResult,testResult)
|
||||
checkResult('Internal Function : md5',expectedResult,testResult)
|
||||
next()
|
||||
},
|
||||
sha256 : function(){
|
||||
sha256 : function(next){
|
||||
var expectedResult = '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08'
|
||||
var testResult = require('crypto').createHash('sha256').update('test').digest("hex")
|
||||
checkResult('createHash/sha256',expectedResult,testResult)
|
||||
checkResult('Internal Function : createHash/sha256',expectedResult,testResult)
|
||||
next()
|
||||
},
|
||||
nameToTime : function(){
|
||||
nameToTime : function(next){
|
||||
var expectedResult = '2018-10-22 23:00:00'
|
||||
var testResult = s.nameToTime('2018-10-22T23-00-00.mp4')
|
||||
checkResult('nameToTime',expectedResult,testResult)
|
||||
checkResult('Internal Function : nameToTime',expectedResult,testResult)
|
||||
next()
|
||||
},
|
||||
ipRange : function(){
|
||||
ipRange : function(next){
|
||||
var expectedResult = [
|
||||
'192.168.1.1',
|
||||
'192.168.1.2',
|
||||
'192.168.1.3'
|
||||
]
|
||||
var testResult = s.ipRange('192.168.1.1','192.168.1.3')
|
||||
checkResult('ipRange',JSON.stringify(expectedResult),JSON.stringify(testResult))
|
||||
checkResult('Internal Function : ipRange',JSON.stringify(expectedResult),JSON.stringify(testResult))
|
||||
next()
|
||||
},
|
||||
portRange : function(){
|
||||
portRange : function(next){
|
||||
var expectedResult = [
|
||||
8000,
|
||||
8001,
|
||||
8002,
|
||||
]
|
||||
var testResult = s.portRange(8000,8002)
|
||||
checkResult('portRange',JSON.stringify(expectedResult),JSON.stringify(testResult))
|
||||
checkResult('Internal Function : portRange',JSON.stringify(expectedResult),JSON.stringify(testResult))
|
||||
next()
|
||||
},
|
||||
getFunctionParamNames : function(){
|
||||
getFunctionParamNames : function(next){
|
||||
var testing = function(arg1,arg2){}
|
||||
var expectedResult = [
|
||||
'arg1',
|
||||
'arg2',
|
||||
]
|
||||
var testResult = s.getFunctionParamNames(testing)
|
||||
checkResult('getFunctionParamNames',JSON.stringify(expectedResult),JSON.stringify(testResult))
|
||||
checkResult('Internal Function : getFunctionParamNames',JSON.stringify(expectedResult),JSON.stringify(testResult))
|
||||
next()
|
||||
}
|
||||
},
|
||||
"ffmpeg.js" : {
|
||||
splitForFFPMEG : function(){
|
||||
splitForFFPMEG : function(next){
|
||||
var expectedResult = [
|
||||
'flag1',
|
||||
'flag2',
|
||||
'fl ag3',
|
||||
]
|
||||
var testResult = s.splitForFFPMEG('flag1 flag2 "fl ag3"')
|
||||
checkResult('splitForFFPMEG',JSON.stringify(expectedResult),JSON.stringify(testResult))
|
||||
checkResult('Internal Function : splitForFFPMEG',JSON.stringify(expectedResult),JSON.stringify(testResult))
|
||||
next()
|
||||
},
|
||||
"ffmpeg" : function(){
|
||||
"ffmpeg" : function(next){
|
||||
//command string builder
|
||||
var x = {tmp : ''}
|
||||
s.checkDetails(sampleMonitorObject)
|
||||
|
@ -100,7 +157,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
s.ffmpegFunctions.buildMainDetector(sampleMonitorObject,x)
|
||||
s.ffmpegFunctions.assembleMainPieces(sampleMonitorObject,x)
|
||||
var testResult = x.ffmpegCommandString
|
||||
checkResult('ffmpeg',expectedResult,testResult)
|
||||
checkResult('Internal Function : ffmpeg',expectedResult,testResult)
|
||||
//check pipe builder
|
||||
var expectedResult = []
|
||||
var times = config.pipeAddition
|
||||
|
@ -112,19 +169,490 @@ module.exports = function(s,config,lang,app,io){
|
|||
}
|
||||
s.ffmpegFunctions.createPipeArray(sampleMonitorObject,x)
|
||||
var testResult = x.stdioPipes
|
||||
checkResult('ffmpeg.createPipeArray',JSON.stringify(expectedResult),JSON.stringify(testResult))
|
||||
checkResult('Internal Function : ffmpeg.createPipeArray',JSON.stringify(expectedResult),JSON.stringify(testResult))
|
||||
next()
|
||||
}
|
||||
},
|
||||
"webServer" : {
|
||||
"super/accounts/saveSettings" : function(next){
|
||||
console.log(requestSuperURL)
|
||||
var userData = {
|
||||
"mail": "admin@shinobi.video1",
|
||||
"pass": "password",
|
||||
"pass_again": "password"
|
||||
}
|
||||
var builtURL = requestSuperURL + 'accounts/saveSettings?data=' + encodeURIComponent(s.s(userData))
|
||||
request(builtURL,function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
checkResult('API : /accounts/saveSettings',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"super/accounts/registerAdmin" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = requestSuperURL + 'accounts/registerAdmin?data=' + encodeURIComponent(s.s(userData))
|
||||
request(builtURL,function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
administratorAccountData.uid = response.user.uid
|
||||
checkResult('API : /accounts/registerAdmin',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"super/accounts/deleteAdmin" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = requestSuperURL + 'accounts/deleteAdmin?account=' + encodeURIComponent(s.s({
|
||||
"mail":"test@test1.com",
|
||||
"ke":"GroupKey123456",
|
||||
"uid":administratorAccountData.uid
|
||||
|
||||
}))
|
||||
request(builtURL,function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
checkResult('API : /accounts/deleteAdmin',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"super/accounts/registerAdmin (Recreate)" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = requestSuperURL + 'accounts/registerAdmin?data=' + encodeURIComponent(s.s(userData))
|
||||
request(builtURL,function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
administratorAccountData.uid = response.user.uid
|
||||
checkResult('API : /accounts/registerAdmin',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"super/accounts/list" : function(next){
|
||||
var builtURL = requestSuperURL + 'accounts/list'
|
||||
request(builtURL,function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
if(response.ok === true){
|
||||
// administratorAccountData = response.users[0]
|
||||
}
|
||||
checkResult('API : /accounts/list',1,response.users.length)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"super/accounts/list/admin" : function(next){
|
||||
var builtURL = requestSuperURL + 'accounts/list/admin'
|
||||
request(builtURL,function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
checkResult('API : /accounts/list/admin',1,response.users.length)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"super/accounts/list/sub" : function(next){
|
||||
var builtURL = requestSuperURL + 'accounts/list/sub'
|
||||
request(builtURL,function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
checkResult('API : /accounts/list/sub',0,response.users.length)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"super/accounts/editAdmin" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
delete(userData.uid)
|
||||
var builtURL = requestSuperURL + 'accounts/editAdmin?data=' + encodeURIComponent(s.s(userData)) + "&account=" + encodeURIComponent(s.s({
|
||||
"mail":"test@test1.com",
|
||||
"ke":"GroupKey123456"
|
||||
}))
|
||||
request(builtURL,function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
if(response.ok !== true)console.log(response.msg)
|
||||
checkResult('API : /accounts/editAdmin',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"/ (Login via API)" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = getBaseURL() + '?json=true'
|
||||
request.post(builtURL,{
|
||||
form : {machineID: "testMachineId", mail: "test@test1.com", pass: "test1", function: "dash"}
|
||||
},function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
if(response.ok !== true)console.log(response)
|
||||
administratorAccountData.auth = response.$user.auth_token
|
||||
checkResult('API : / (Login via API)',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"/api/add" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = buildRegularApiRequestURL(administratorAccountData.auth,'api',administratorAccountData.ke) + 'add'
|
||||
request.post(builtURL,{
|
||||
form : {
|
||||
"data": {
|
||||
"ip": "0.0.0.0",
|
||||
"details": {
|
||||
"auth_socket": "1",
|
||||
"get_monitors": "1",
|
||||
"control_monitors": "1",
|
||||
"get_logs": "1",
|
||||
"watch_stream": "1",
|
||||
"watch_snapshot": "1",
|
||||
"watch_videos": "1",
|
||||
"delete_videos": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
if(response.ok !== true)console.log(builtURL,response)
|
||||
temp.newApiKey = response.api.code
|
||||
checkResult('API : /api/add',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"Delete API Key" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = buildRegularApiRequestURL(administratorAccountData.auth,'api',administratorAccountData.ke) + 'delete'
|
||||
request.post(builtURL,{
|
||||
form : {
|
||||
"data": {
|
||||
"code": temp.newApiKey
|
||||
}
|
||||
}
|
||||
},function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
if(response.ok !== true)console.log(builtURL,response)
|
||||
checkResult('API : /api/delete',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"/admin/accounts/register" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = buildAdminRequestURL(administratorAccountData.auth,'accounts',administratorAccountData.ke) + 'register'
|
||||
request.post(builtURL,{
|
||||
form : {
|
||||
"data": {
|
||||
"mail": "test@test2.com",
|
||||
"pass": "test1",
|
||||
"password_again": "test1"
|
||||
}
|
||||
}
|
||||
},function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
if(response.ok !== true)console.log(builtURL,response)
|
||||
temp.subAccount = response.user
|
||||
checkResult('API : /admin/accounts/register',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"/admin/accounts/edit" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = buildAdminRequestURL(administratorAccountData.auth,'accounts',administratorAccountData.ke) + 'edit'
|
||||
request.post(builtURL,{
|
||||
form : {
|
||||
"data": {
|
||||
"uid": temp.subAccount.uid,
|
||||
"mail": temp.subAccount.mail,
|
||||
"details": temp.subAccount.details
|
||||
}
|
||||
}
|
||||
},function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
if(response.ok !== true)console.log(builtURL,response)
|
||||
checkResult('API : /admin/accounts/edit',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"/admin/accounts/delete" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = buildAdminRequestURL(administratorAccountData.auth,'accounts',administratorAccountData.ke) + 'delete'
|
||||
request.post(builtURL,{
|
||||
form : {
|
||||
"data": {
|
||||
"uid": temp.subAccount.uid,
|
||||
"mail": temp.subAccount.mail,
|
||||
}
|
||||
}
|
||||
},function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
if(response.ok !== true)console.log(builtURL,response)
|
||||
temp.subAccount = null
|
||||
checkResult('API : /admin/accounts/delete',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"/configureMonitor (Add)" : function(next){
|
||||
temp.monitorId = "10998"
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = buildRegularApiRequestURL(administratorAccountData.auth,'configureMonitor',administratorAccountData.ke) + temp.monitorId
|
||||
request.post(builtURL,{
|
||||
form : {
|
||||
"data": {"mode":"start","mid":temp.monitorId,"name":"ReoLinkWireless","type":"mp4","protocol":"https","host":"cdn.shinobi.video","port":"443","path":"/videos/faces.mp4","ext":"mp4","fps":"3","width":"2048","height":"1536","details":"{\"notes\":\"\",\"dir\":\"\",\"auto_host_enable\":\"1\",\"auto_host\":\"rtsp://user:pass@192.168.1.40:554/\",\"rtsp_transport\":\"tcp\",\"muser\":\"user\",\"mpass\":\"pass\",\"port_force\":null,\"fatal_max\":\"0\",\"aduration\":\"1000000\",\"probesize\":\"1000000\",\"stream_loop\":\"1\",\"sfps\":\"\",\"accelerator\":\"0\",\"hwaccel\":\"cuvid\",\"hwaccel_vcodec\":\"h264_cuvid\",\"hwaccel_device\":\"\",\"stream_type\":\"mp4\",\"stream_flv_type\":\"http\",\"stream_flv_maxLatency\":\"\",\"stream_mjpeg_clients\":\"0\",\"stream_vcodec\":\"copy\",\"stream_acodec\":\"no\",\"hls_time\":\"2\",\"hls_list_size\":\"2\",\"preset_stream\":\"\",\"signal_check\":\"\",\"signal_check_log\":\"0\",\"stream_quality\":\"1\",\"stream_fps\":\"10\",\"stream_scale_x\":\"3072\",\"stream_scale_y\":\"1728\",\"rotate_stream\":null,\"svf\":\"\",\"tv_channel\":null,\"tv_channel_id\":\"\",\"tv_channel_group_title\":\"\",\"stream_timestamp\":null,\"stream_timestamp_font\":\"\",\"stream_timestamp_font_size\":\"\",\"stream_timestamp_color\":\"\",\"stream_timestamp_box_color\":\"\",\"stream_timestamp_x\":\"\",\"stream_timestamp_y\":\"\",\"stream_watermark\":\"0\",\"stream_watermark_location\":\"\",\"stream_watermark_position\":null,\"snap\":\"0\",\"snap_fps\":\"1\",\"snap_scale_x\":\"1920\",\"snap_scale_y\":\"1072\",\"snap_vf\":\"\",\"vcodec\":\"copy\",\"crf\":\"1\",\"preset_record\":\"\",\"acodec\":\"no\",\"dqf\":\"0\",\"cutoff\":\"\",\"rotate_record\":null,\"vf\":\"\",\"timestamp\":\"0\",\"timestamp_font\":\"\",\"timestamp_font_size\":\"\",\"timestamp_color\":\"\",\"timestamp_box_color\":\"\",\"timestamp_x\":\"\",\"timestamp_y\":\"\",\"watermark\":null,\"watermark_location\":\"\",\"watermark_position\":null,\"cust_input\":\"\",\"cust_snap\":\"\",\"cust_rtmp\":\"\",\"cust_rawh264\":\"\",\"cust_detect\":\"\",\"cust_stream\":\"\",\"cust_stream_server\":\"\",\"cust_record\":\"\",\"custom_output\":\"\",\"detector\":\"0\",\"detector_pam\":\"0\",\"detector_noise_filter\":null,\"detector_webhook\":\"0\",\"detector_webhook_url\":\"\",\"detector_command_enable\":\"0\",\"detector_command\":\"\",\"detector_command_timeout\":\"\",\"detector_lock_timeout\":\"\",\"detector_save\":\"0\",\"detector_frame_save\":\"0\",\"detector_mail\":\"0\",\"detector_mail_timeout\":\"\",\"detector_record_method\":\"sip\",\"detector_trigger\":\"1\",\"detector_trigger_record_fps\":\"\",\"detector_timeout\":\"10\",\"watchdog_reset\":\"0\",\"detector_delete_motionless_videos\":\"0\",\"detector_send_frames\":\"1\",\"detector_region_of_interest\":\"0\",\"detector_fps\":\"\",\"detector_scale_x\":\"640\",\"detector_scale_y\":\"480\",\"detector_use_motion\":\"1\",\"detector_use_detect_object\":\"0\",\"detector_frame\":\"0\",\"detector_sensitivity\":\"\",\"cords\":\"[]\",\"detector_buffer_vcodec\":\"auto\",\"detector_buffer_fps\":\"\",\"detector_buffer_hls_time\":\"\",\"detector_buffer_hls_list_size\":\"\",\"detector_buffer_start_number\":\"\",\"detector_buffer_live_start_index\":\"\",\"detector_lisence_plate\":\"0\",\"detector_lisence_plate_country\":\"us\",\"detector_notrigger\":\"0\",\"detector_notrigger_mail\":\"0\",\"detector_notrigger_timeout\":\"\",\"control\":\"0\",\"control_base_url\":\"\",\"control_url_method\":null,\"control_stop\":null,\"control_url_stop_timeout\":\"\",\"control_url_center\":\"\",\"control_url_left\":\"\",\"control_url_left_stop\":\"\",\"control_url_right\":\"\",\"control_url_right_stop\":\"\",\"control_url_up\":\"\",\"control_url_up_stop\":\"\",\"control_url_down\":\"\",\"control_url_down_stop\":\"\",\"control_url_enable_nv\":\"\",\"control_url_disable_nv\":\"\",\"control_url_zoom_out\":\"\",\"control_url_zoom_out_stop\":\"\",\"control_url_zoom_in\":\"\",\"control_url_zoom_in_stop\":\"\",\"groups\":\"\",\"loglevel\":\"quiet\",\"sqllog\":\"0\",\"detector_cascades\":\"\",\"stream_channels\":\"\",\"input_maps\":\"\",\"input_map_choices\":\"\"}","shto":"[]","shfr":"[]"}
|
||||
}
|
||||
},function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
if(response.ok !== true)console.log(builtURL,response)
|
||||
checkResult('API : /configureMonitor (Add)',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"/configureMonitor (Add Second)" : function(next){
|
||||
temp.monitorId2 = "10999"
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = buildRegularApiRequestURL(administratorAccountData.auth,'configureMonitor',administratorAccountData.ke) + temp.monitorId2
|
||||
request.post(builtURL,{
|
||||
form : {
|
||||
"data": {"mode":"start","mid":temp.monitorId2,"name":"ReoLinkWireless","type":"mp4","protocol":"https","host":"cdn.shinobi.video","port":"443","path":"/videos/faces.mp4","ext":"mp4","fps":"3","width":"2048","height":"1536","details":"{\"notes\":\"\",\"dir\":\"\",\"auto_host_enable\":\"1\",\"auto_host\":\"rtsp://user:pass@192.168.1.40:554/\",\"rtsp_transport\":\"tcp\",\"muser\":\"user\",\"mpass\":\"pass\",\"port_force\":null,\"fatal_max\":\"0\",\"aduration\":\"1000000\",\"probesize\":\"1000000\",\"stream_loop\":\"1\",\"sfps\":\"\",\"accelerator\":\"0\",\"hwaccel\":\"cuvid\",\"hwaccel_vcodec\":\"h264_cuvid\",\"hwaccel_device\":\"\",\"stream_type\":\"hls\",\"stream_flv_type\":\"http\",\"stream_flv_maxLatency\":\"\",\"stream_mjpeg_clients\":\"0\",\"stream_vcodec\":\"copy\",\"stream_acodec\":\"no\",\"hls_time\":\"2\",\"hls_list_size\":\"2\",\"preset_stream\":\"\",\"signal_check\":\"\",\"signal_check_log\":\"0\",\"stream_quality\":\"1\",\"stream_fps\":\"10\",\"stream_scale_x\":\"3072\",\"stream_scale_y\":\"1728\",\"rotate_stream\":null,\"svf\":\"\",\"tv_channel\":null,\"tv_channel_id\":\"\",\"tv_channel_group_title\":\"\",\"stream_timestamp\":null,\"stream_timestamp_font\":\"\",\"stream_timestamp_font_size\":\"\",\"stream_timestamp_color\":\"\",\"stream_timestamp_box_color\":\"\",\"stream_timestamp_x\":\"\",\"stream_timestamp_y\":\"\",\"stream_watermark\":\"0\",\"stream_watermark_location\":\"\",\"stream_watermark_position\":null,\"snap\":\"0\",\"snap_fps\":\"1\",\"snap_scale_x\":\"1920\",\"snap_scale_y\":\"1072\",\"snap_vf\":\"\",\"vcodec\":\"copy\",\"crf\":\"1\",\"preset_record\":\"\",\"acodec\":\"no\",\"dqf\":\"0\",\"cutoff\":\"\",\"rotate_record\":null,\"vf\":\"\",\"timestamp\":\"0\",\"timestamp_font\":\"\",\"timestamp_font_size\":\"\",\"timestamp_color\":\"\",\"timestamp_box_color\":\"\",\"timestamp_x\":\"\",\"timestamp_y\":\"\",\"watermark\":null,\"watermark_location\":\"\",\"watermark_position\":null,\"cust_input\":\"\",\"cust_snap\":\"\",\"cust_rtmp\":\"\",\"cust_rawh264\":\"\",\"cust_detect\":\"\",\"cust_stream\":\"\",\"cust_stream_server\":\"\",\"cust_record\":\"\",\"custom_output\":\"\",\"detector\":\"0\",\"detector_pam\":\"0\",\"detector_noise_filter\":null,\"detector_webhook\":\"0\",\"detector_webhook_url\":\"\",\"detector_command_enable\":\"0\",\"detector_command\":\"\",\"detector_command_timeout\":\"\",\"detector_lock_timeout\":\"\",\"detector_save\":\"0\",\"detector_frame_save\":\"0\",\"detector_mail\":\"0\",\"detector_mail_timeout\":\"\",\"detector_record_method\":\"sip\",\"detector_trigger\":\"1\",\"detector_trigger_record_fps\":\"\",\"detector_timeout\":\"10\",\"watchdog_reset\":\"0\",\"detector_delete_motionless_videos\":\"0\",\"detector_send_frames\":\"1\",\"detector_region_of_interest\":\"0\",\"detector_fps\":\"\",\"detector_scale_x\":\"640\",\"detector_scale_y\":\"480\",\"detector_use_motion\":\"1\",\"detector_use_detect_object\":\"0\",\"detector_frame\":\"0\",\"detector_sensitivity\":\"\",\"cords\":\"[]\",\"detector_buffer_vcodec\":\"auto\",\"detector_buffer_fps\":\"\",\"detector_buffer_hls_time\":\"\",\"detector_buffer_hls_list_size\":\"\",\"detector_buffer_start_number\":\"\",\"detector_buffer_live_start_index\":\"\",\"detector_lisence_plate\":\"0\",\"detector_lisence_plate_country\":\"us\",\"detector_notrigger\":\"0\",\"detector_notrigger_mail\":\"0\",\"detector_notrigger_timeout\":\"\",\"control\":\"0\",\"control_base_url\":\"\",\"control_url_method\":null,\"control_stop\":null,\"control_url_stop_timeout\":\"\",\"control_url_center\":\"\",\"control_url_left\":\"\",\"control_url_left_stop\":\"\",\"control_url_right\":\"\",\"control_url_right_stop\":\"\",\"control_url_up\":\"\",\"control_url_up_stop\":\"\",\"control_url_down\":\"\",\"control_url_down_stop\":\"\",\"control_url_enable_nv\":\"\",\"control_url_disable_nv\":\"\",\"control_url_zoom_out\":\"\",\"control_url_zoom_out_stop\":\"\",\"control_url_zoom_in\":\"\",\"control_url_zoom_in_stop\":\"\",\"groups\":\"\",\"loglevel\":\"quiet\",\"sqllog\":\"0\",\"detector_cascades\":\"\",\"stream_channels\":\"\",\"input_maps\":\"\",\"input_map_choices\":\"\"}","shto":"[]","shfr":"[]"}
|
||||
}
|
||||
},function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
if(response.ok !== true)console.log(builtURL,response)
|
||||
checkResult('API : /configureMonitor (Add Second)',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"/configureMonitor (Edit)" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = buildRegularApiRequestURL(administratorAccountData.auth,'configureMonitor',administratorAccountData.ke) + temp.monitorId
|
||||
request.post(builtURL,{
|
||||
form : {
|
||||
"data": {"mode":"start","mid":temp.monitorId,"name":"ReoLinkWireless","type":"mp4","protocol":"https","host":"cdn.shinobi.video","port":"443","path":"/videos/faces.mp4","ext":"mp4","fps":"3","width":"2048","height":"1536","details":"{\"notes\":\"\",\"dir\":\"\",\"auto_host_enable\":\"1\",\"auto_host\":\"rtsp://user:pass@192.168.1.40:554/\",\"rtsp_transport\":\"tcp\",\"muser\":\"user\",\"mpass\":\"pass\",\"port_force\":null,\"fatal_max\":\"0\",\"aduration\":\"1000000\",\"probesize\":\"1000000\",\"stream_loop\":\"1\",\"sfps\":\"\",\"accelerator\":\"0\",\"hwaccel\":\"cuvid\",\"hwaccel_vcodec\":\"h264_cuvid\",\"hwaccel_device\":\"\",\"stream_type\":\"mp4\",\"stream_flv_type\":\"http\",\"stream_flv_maxLatency\":\"\",\"stream_mjpeg_clients\":\"0\",\"stream_vcodec\":\"copy\",\"stream_acodec\":\"no\",\"hls_time\":\"2\",\"hls_list_size\":\"2\",\"preset_stream\":\"\",\"signal_check\":\"\",\"signal_check_log\":\"0\",\"stream_quality\":\"1\",\"stream_fps\":\"10\",\"stream_scale_x\":\"3072\",\"stream_scale_y\":\"1728\",\"rotate_stream\":null,\"svf\":\"\",\"tv_channel\":null,\"tv_channel_id\":\"\",\"tv_channel_group_title\":\"\",\"stream_timestamp\":null,\"stream_timestamp_font\":\"\",\"stream_timestamp_font_size\":\"\",\"stream_timestamp_color\":\"\",\"stream_timestamp_box_color\":\"\",\"stream_timestamp_x\":\"\",\"stream_timestamp_y\":\"\",\"stream_watermark\":\"0\",\"stream_watermark_location\":\"\",\"stream_watermark_position\":null,\"snap\":\"0\",\"snap_fps\":\"1\",\"snap_scale_x\":\"1920\",\"snap_scale_y\":\"1072\",\"snap_vf\":\"\",\"vcodec\":\"copy\",\"crf\":\"1\",\"preset_record\":\"\",\"acodec\":\"no\",\"dqf\":\"0\",\"cutoff\":\"\",\"rotate_record\":null,\"vf\":\"\",\"timestamp\":\"0\",\"timestamp_font\":\"\",\"timestamp_font_size\":\"\",\"timestamp_color\":\"\",\"timestamp_box_color\":\"\",\"timestamp_x\":\"\",\"timestamp_y\":\"\",\"watermark\":null,\"watermark_location\":\"\",\"watermark_position\":null,\"cust_input\":\"\",\"cust_snap\":\"\",\"cust_rtmp\":\"\",\"cust_rawh264\":\"\",\"cust_detect\":\"\",\"cust_stream\":\"\",\"cust_stream_server\":\"\",\"cust_record\":\"\",\"custom_output\":\"\",\"detector\":\"0\",\"detector_pam\":\"0\",\"detector_noise_filter\":null,\"detector_webhook\":\"0\",\"detector_webhook_url\":\"\",\"detector_command_enable\":\"0\",\"detector_command\":\"\",\"detector_command_timeout\":\"\",\"detector_lock_timeout\":\"\",\"detector_save\":\"0\",\"detector_frame_save\":\"0\",\"detector_mail\":\"0\",\"detector_mail_timeout\":\"\",\"detector_record_method\":\"sip\",\"detector_trigger\":\"1\",\"detector_trigger_record_fps\":\"\",\"detector_timeout\":\"10\",\"watchdog_reset\":\"0\",\"detector_delete_motionless_videos\":\"0\",\"detector_send_frames\":\"1\",\"detector_region_of_interest\":\"0\",\"detector_fps\":\"\",\"detector_scale_x\":\"640\",\"detector_scale_y\":\"480\",\"detector_use_motion\":\"1\",\"detector_use_detect_object\":\"0\",\"detector_frame\":\"0\",\"detector_sensitivity\":\"\",\"cords\":\"[]\",\"detector_buffer_vcodec\":\"auto\",\"detector_buffer_fps\":\"\",\"detector_buffer_hls_time\":\"\",\"detector_buffer_hls_list_size\":\"\",\"detector_buffer_start_number\":\"\",\"detector_buffer_live_start_index\":\"\",\"detector_lisence_plate\":\"0\",\"detector_lisence_plate_country\":\"us\",\"detector_notrigger\":\"0\",\"detector_notrigger_mail\":\"0\",\"detector_notrigger_timeout\":\"\",\"control\":\"0\",\"control_base_url\":\"\",\"control_url_method\":null,\"control_stop\":null,\"control_url_stop_timeout\":\"\",\"control_url_center\":\"\",\"control_url_left\":\"\",\"control_url_left_stop\":\"\",\"control_url_right\":\"\",\"control_url_right_stop\":\"\",\"control_url_up\":\"\",\"control_url_up_stop\":\"\",\"control_url_down\":\"\",\"control_url_down_stop\":\"\",\"control_url_enable_nv\":\"\",\"control_url_disable_nv\":\"\",\"control_url_zoom_out\":\"\",\"control_url_zoom_out_stop\":\"\",\"control_url_zoom_in\":\"\",\"control_url_zoom_in_stop\":\"\",\"groups\":\"\",\"loglevel\":\"quiet\",\"sqllog\":\"0\",\"detector_cascades\":\"\",\"stream_channels\":\"\",\"input_maps\":\"\",\"input_map_choices\":\"\"}","shto":"[]","shfr":"[]"}
|
||||
}
|
||||
},function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
if(response.ok !== true)console.log(builtURL,response)
|
||||
checkResult('API : /configureMonitor (Edit)',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"/monitor/[MONITOR_ID] (Get)" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = buildRegularApiRequestURL(administratorAccountData.auth,'monitor',administratorAccountData.ke) + temp.monitorId
|
||||
request.get(builtURL,function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
checkResult('API : /monitor/[MONITOR_ID] (Get)',temp.monitorId,response.mid)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"/monitor/[MONITOR_ID]/[MODE] (Mode Switch to Disabled)" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = buildRegularApiRequestURL(administratorAccountData.auth,'monitor',administratorAccountData.ke) + temp.monitorId + '/stop'
|
||||
request.get(builtURL,function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
checkResult('API : /monitor/[MONITOR_ID] (Mode Switch to Disabled)',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"/monitor/[MONITOR_ID]/[MODE] (Mode Switch to Watch-Only)" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = buildRegularApiRequestURL(administratorAccountData.auth,'monitor',administratorAccountData.ke) + temp.monitorId + '/start'
|
||||
request.get(builtURL,function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
checkResult('API : /monitor/[MONITOR_ID] (Mode Switch to Watch-Only)',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"/monitor/[MONITOR_ID]/[MODE] (Mode Switch to Record)" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = buildRegularApiRequestURL(administratorAccountData.auth,'monitor',administratorAccountData.ke) + temp.monitorId + '/record'
|
||||
request.get(builtURL,function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
checkResult('API : /monitor/[MONITOR_ID] (Mode Switch to Record)',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"/monitor (Get All)" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = buildRegularApiRequestURL(administratorAccountData.auth,'monitor',administratorAccountData.ke)
|
||||
request.get(builtURL,function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
if(!checkResult('API : /monitor (Get All)',2,response.length)){
|
||||
console.log(Object.keys(response))
|
||||
}
|
||||
next()
|
||||
})
|
||||
},
|
||||
"/configureMonitor (Delete)" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = buildRegularApiRequestURL(administratorAccountData.auth,'configureMonitor',administratorAccountData.ke) + temp.monitorId2 + '/delete'
|
||||
request.post(builtURL,function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
if(response.ok !== true)console.log(builtURL,response)
|
||||
checkResult('API : /configureMonitor (Delete)',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"/monitorStates Insert (Disable + Detector Off)" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = buildRegularApiRequestURL(administratorAccountData.auth,'monitorStates',administratorAccountData.ke) + 'DisableWithDetectorOff/insert'
|
||||
request.post(builtURL,{
|
||||
form: {
|
||||
data: {
|
||||
"monitors": [
|
||||
{
|
||||
"mode":"stop",
|
||||
"mid":temp.monitorId,
|
||||
"details": {
|
||||
"detector": "0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
if(response.ok !== true)console.log(builtURL,response)
|
||||
checkResult('API : /monitorStates Insert (Disable + Detector Off)',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"/monitorStates Insert (Enable + Detector On)" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = buildRegularApiRequestURL(administratorAccountData.auth,'monitorStates',administratorAccountData.ke) + 'EnableWithDetectorOn/insert'
|
||||
request.post(builtURL,{
|
||||
form: {
|
||||
data: {
|
||||
"monitors": [
|
||||
{
|
||||
"mode":"start",
|
||||
"mid":temp.monitorId,
|
||||
"details": {
|
||||
"detector": "1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
if(response.ok !== true)console.log(builtURL,response)
|
||||
checkResult('API : /monitorStates Insert (Enable + Detector On)',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"/monitorStates Insert (Continuous Recording)" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = buildRegularApiRequestURL(administratorAccountData.auth,'monitorStates',administratorAccountData.ke) + 'RecordOnly/insert'
|
||||
request.post(builtURL,{
|
||||
form: {
|
||||
data: {
|
||||
"monitors": [
|
||||
{
|
||||
"mode":"record",
|
||||
"mid":temp.monitorId,
|
||||
"details": {
|
||||
"detector": "0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
if(response.ok !== true)console.log(builtURL,response)
|
||||
checkResult('API : /monitorStates Insert (Continuous Recording)',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"/monitorStates List" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = buildRegularApiRequestURL(administratorAccountData.auth,'monitorStates',administratorAccountData.ke)
|
||||
request.get(builtURL,function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
if(response.ok !== true)console.log(builtURL,response)
|
||||
checkResult('API : /monitorStates List',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"/monitorStates Run Action (Disable + Detector Off)" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = buildRegularApiRequestURL(administratorAccountData.auth,'monitorStates',administratorAccountData.ke) + 'DisableWithDetectorOff'
|
||||
request.get(builtURL,function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
if(response.ok !== true)console.log(builtURL,response)
|
||||
checkResult('API : /monitorStates Run Action (Disable + Detector Off)',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"/monitorStates Run Action (Enable + Detector On)" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = buildRegularApiRequestURL(administratorAccountData.auth,'monitorStates',administratorAccountData.ke) + 'EnableWithDetectorOn'
|
||||
request.get(builtURL,function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
if(response.ok !== true)console.log(builtURL,response)
|
||||
checkResult('API : /monitorStates Run Action (Enable + Detector On)',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"/monitorStates Run Action (Continuous Recording)" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = buildRegularApiRequestURL(administratorAccountData.auth,'monitorStates',administratorAccountData.ke) + 'RecordOnly'
|
||||
request.get(builtURL,function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
if(response.ok !== true)console.log(builtURL,response)
|
||||
checkResult('API : /monitorStates Run Action (Continuous Recording)',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"/monitorStates Delete (Disable + Detector Off)" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = buildRegularApiRequestURL(administratorAccountData.auth,'monitorStates',administratorAccountData.ke) + 'DisableWithDetectorOff/delete'
|
||||
request.get(builtURL,function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
if(response.ok !== true)console.log(builtURL,response)
|
||||
checkResult('API : /monitorStates Delete (Disable + Detector Off)',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"/monitorStates Delete (Enable + Detector On)" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = buildRegularApiRequestURL(administratorAccountData.auth,'monitorStates',administratorAccountData.ke) + 'EnableWithDetectorOn/delete'
|
||||
request.get(builtURL,function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
if(response.ok !== true)console.log(builtURL,response)
|
||||
checkResult('API : /monitorStates Delete (Enable + Detector On)',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
"/monitorStates Delete (Continuous Recording)" : function(next){
|
||||
var userData = getAdministratorAccountData()
|
||||
var builtURL = buildRegularApiRequestURL(administratorAccountData.auth,'monitorStates',administratorAccountData.ke) + 'RecordOnly/delete'
|
||||
request.get(builtURL,function(err, httpResponse, body){
|
||||
var response = s.parseJSON(body)
|
||||
if(response.ok !== true)console.log(builtURL,response)
|
||||
checkResult('API : /monitorStates Delete (Continuous Recording)',true,response.ok)
|
||||
next()
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
console.log('----- Function Test Starting')
|
||||
Object.keys(test).forEach(function(libkey){
|
||||
var library = test[libkey]
|
||||
console.log('--- Testing ' + libkey + '...')
|
||||
Object.keys(library).forEach(function(key){
|
||||
var functionTest = library[key]
|
||||
functionTest()
|
||||
})
|
||||
console.log('-- Completed ' + libkey + '...')
|
||||
var completedGroups = 0
|
||||
var testGroupKeys = Object.keys(test)
|
||||
var testGroupRunLoop = function(callback){
|
||||
var tableName = testGroupKeys[completedGroups]
|
||||
var testers = test[testGroupKeys[completedGroups]]
|
||||
if(tableName){
|
||||
console.log('--- Testing ' + tableName + '...')
|
||||
// test functions >
|
||||
var completedFunctions = 0
|
||||
var testFunctionsKeys = Object.keys(testers)
|
||||
var testFunctionRunLoop = function(innerCallback){
|
||||
var functioName = testFunctionsKeys[completedFunctions]
|
||||
var theFunction = testers[testFunctionsKeys[completedFunctions]]
|
||||
if(functioName){
|
||||
theFunction(function(){
|
||||
++completedFunctions
|
||||
testFunctionRunLoop(innerCallback)
|
||||
})
|
||||
}else{
|
||||
innerCallback()
|
||||
}
|
||||
}
|
||||
testFunctionRunLoop(function(){
|
||||
console.log('-- Completed ' + tableName + '...')
|
||||
++completedGroups
|
||||
testGroupRunLoop(callback)
|
||||
})
|
||||
// test functions />
|
||||
}else{
|
||||
callback()
|
||||
}
|
||||
}
|
||||
testGroupRunLoop(function(){
|
||||
console.log('---- Function Test Ended')
|
||||
})
|
||||
console.log('---- Function Test Ended')
|
||||
}
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"mail": "user@shinobi.video",
|
||||
"ke": "",
|
||||
"pass": "password",
|
||||
"password_again": "password",
|
||||
"details": "{\"factorAuth\":\"0\",\"size\":\"\",\"days\":\"\",\"event_days\":\"\",\"log_days\":\"\",\"max_camera\":\"\",\"permissions\":\"all\",\"edit_size\":\"1\",\"edit_days\":\"1\",\"edit_event_days\":\"1\",\"edit_log_days\":\"1\",\"use_admin\":\"1\",\"use_aws_s3\":\"1\",\"use_webdav\":\"1\",\"use_discordbot\":\"1\",\"use_ldap\":\"1\"}"
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"data": {
|
||||
"ip": "[IP ADDRESS]",
|
||||
"details": {
|
||||
"auth_socket": "1",
|
||||
"get_monitors": "1",
|
||||
"control_monitors": "1",
|
||||
"get_logs": "1",
|
||||
"watch_stream": "1",
|
||||
"watch_snapshot": "1",
|
||||
"watch_videos": "1",
|
||||
"delete_videos": "1"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"data": {
|
||||
"code": "[API KEY]"
|
||||
}
|
||||
}
|
89
web/libs/css/dash2.basic.css
Normal file
89
web/libs/css/dash2.basic.css
Normal file
|
@ -0,0 +1,89 @@
|
|||
/*Cusotm Bootstrap*/
|
||||
.col-5ths,
|
||||
.col-sm-5ths,
|
||||
.col-md-5ths,
|
||||
.col-lg-5ths {
|
||||
position: relative;
|
||||
min-height: 1px;
|
||||
padding-right: 15px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.col-5ths {
|
||||
-webkit-box-flex: 0;
|
||||
-webkit-flex: 0 0 20%;
|
||||
-ms-flex: 0 0 20%;
|
||||
flex: 0 0 20%;
|
||||
max-width: 20%;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.col-sm-5ths {
|
||||
-webkit-box-flex: 0;
|
||||
-webkit-flex: 0 0 20%;
|
||||
-ms-flex: 0 0 20%;
|
||||
flex: 0 0 20%;
|
||||
max-width: 20%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.col-md-5ths {
|
||||
-webkit-box-flex: 0;
|
||||
-webkit-flex: 0 0 20%;
|
||||
-ms-flex: 0 0 20%;
|
||||
flex: 0 0 20%;
|
||||
max-width: 20%;
|
||||
}
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color:#bd9565;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color:#bd9565;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 10px;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
/**/
|
||||
.flex{display:flex}
|
||||
.flex>div{flex:1}
|
||||
.flex-block{display:inline-flex;width:100%;flex-flow: row wrap;}
|
||||
.flex-unit-3{flex:3}
|
||||
.flex-inline{display: inline-flex;position:relative}
|
||||
@import (less) "../less/pie.less";
|
||||
ul{list-style:none}
|
||||
*{transition:0.2s;box-sizing:border-box}
|
||||
.affix-top{position:fixed}
|
||||
.no-padding{padding:0!important}
|
||||
.no-margin{margin:0!important}
|
||||
.pre-inline{white-space: normal;word-break: normal}
|
||||
.pre-inline>ul{margin:0;padding:0}
|
||||
a{cursor:pointer}
|
||||
nav h4{cursor:default;font-size:95%;padding:16px 40px;font-weight:100;text-transform:uppercase;letter-spacing:2px}
|
||||
.m-r{margin-right:10px}
|
||||
.m-b{margin-bottom:10px}
|
||||
.m-t{margin-top:10px}
|
||||
.m-l{margin-left:10px}
|
||||
.overflow-hidden{overflow: hidden!important}
|
||||
.list-inline{list-style:none}
|
||||
.list-inline li{display:inline-block;vertical-align: top;margin:0;}
|
||||
.truncate{width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
||||
img{max-width:100%}
|
||||
.display-table{display:table;width:100%}
|
||||
.display-inline{display:inline-block}
|
||||
.display-table-cell{display:table-cell}
|
||||
.small{font-size:80%}
|
||||
.super-center{position:absolute;left:0;top:0;right:0;bottom:0;margin:auto;width: 4em;height: 1em;}
|
||||
.permission_monitor_edit{display:none}
|
||||
.permission_video_delete{display:none}
|
||||
.nodata .divider{margin:5px 0}
|
||||
.loading .divider{margin:5px 0}
|
||||
/* Video Grid */
|
||||
.video_grid{overflow: auto;height: 100%;display: block;}
|
||||
.video_grid .col-md-2{padding-left:5px;padding-right:5px;padding-bottom:10px}
|
||||
.video_grid .thumb{width:100%;height:150px;display:inline-block;background-size:cover;position:relative;overflow:hidden;border-radius:4px;border:1px solid #000;box-shadow:0 0 10px #151515}
|
||||
.video_grid .thumb .title-strip, .video_grid .thumb .button-strip{width:100%;position:absolute;left:0;background:rgba(0,0,0,0.7);color:#fff;padding:4px}
|
||||
.video_grid .thumb .title-strip{top:0;opacity:0.5}
|
||||
.video_grid .thumb .button-strip{bottom:0;opacity:0}
|
||||
.video_grid .thumb:hover .title-strip, .video_grid .thumb:hover .button-strip{opacity:1}
|
44
web/libs/css/dash2.forms.css
Normal file
44
web/libs/css/dash2.forms.css
Normal file
|
@ -0,0 +1,44 @@
|
|||
form.modal-body{margin:0}
|
||||
.form-group label span{padding:5px;font-weight: 400;color: #2d2d2d;display:block;border-bottom: 1px dotted #ddd;font-size: 10pt;}
|
||||
.form-group label{display:table}
|
||||
.form-group label>div{display:table-cell}
|
||||
.form-group label>div:nth-child(2n-1){width:30%}
|
||||
.form-group label>div:nth-child(2){width:70%;padding:5px;border:1px solid #dedede;border-radius:5px}
|
||||
.dark .form-group label>div,.dark .form-group label>div>span{border-color:#454545;color:#fff}
|
||||
.important.form-group label>div:nth-child(2),.important.form-group label>div>span{border-color:red}
|
||||
.form-group label span small{margin-left: 2px;display:block;font-weight: 600;}
|
||||
.form-group-group .round-left{border-radius: 50px 0 0 50px;margin-left:10px}
|
||||
.form-group-group blockquote:before,.form-group-group blockquote:after{display:none!important}
|
||||
.form-group-group blockquote{letter-spacing:normal;font-style:normal}
|
||||
.form-group-group blockquote p:empty{display:none}
|
||||
.form-group-group blockquote p{font-size:inherit}
|
||||
.form-group-group blockquote p:last-child{margin-bottom:0}
|
||||
.form-group-group-group>div,.form-group-group-group .h_us_advanced>div{margin-bottom:15px;}
|
||||
.form-group-group{padding:0 10px 10px 10px;overflow:hidden;margin-bottom:15px;border-radius:5px;border:1px solid #ddd;background:#fff}
|
||||
.form-group-group table{width:100%}
|
||||
.form-group-group table tr td{padding:10px 5px}
|
||||
.form-group-group table tr:not(:last-child) td{border-bottom:1px dotted #eee}
|
||||
.form-group-group .mdl-list__item{border-bottom:1px solid #eee;}
|
||||
.form-group-group .mdl-list__item:hover{background:#e6e6e6;border-radius:4px;}
|
||||
.dark .form-group-group .mdl-list__item{color:#fff;border-bottom:1px solid #444;}
|
||||
.dark .form-group-group .mdl-list__item:hover{background:#555;}
|
||||
.form-group-group:last-child,.form-group-group > .form-group:last-child{margin-bottom:0}
|
||||
.form-group-group h4{margin:0 -10px 15px -10px;padding:15px;background:#ddd;}
|
||||
.form-group-group h4 small{color:#fff;}
|
||||
.form-group-group.red{border-color:#d9534f}
|
||||
.form-group-group.red h4{background:#d9534f;color:#fff}
|
||||
.form-group-group.purple{border-color:#3f51b5}
|
||||
.form-group-group.purple h4{background:#3f51b5;color:#fff}
|
||||
.form-group-group.blue{border-color:#337ab7}
|
||||
.form-group-group.blue h4{background:#337ab7;color:#fff}
|
||||
.form-group-group.navy{border-color:#31708f}
|
||||
.form-group-group.navy h4{background:#31708f;color:#fff}
|
||||
.form-group-group.green{border-color:#449d44}
|
||||
.form-group-group.green h4{background:#449d44;color:#fff}
|
||||
.form-group-group.forestgreen{border-color:#1e4046}
|
||||
.form-group-group.forestgreen h4{background:#1e4046;color:#fff}
|
||||
.form-group-group.orange{border-color:#c49a68}
|
||||
.form-group-group.orange h4{background:#c49a68;color:#fff}
|
||||
.form-group-group.grey{border-color:#777}
|
||||
.form-group-group.grey h4{background:#777;color:#fff}
|
||||
.dark .form-group-group{background:#222}
|
6
web/libs/css/dash2.modal.css
Normal file
6
web/libs/css/dash2.modal.css
Normal file
|
@ -0,0 +1,6 @@
|
|||
.dark.modal .modal-header,.dark.modal .modal-footer{background:#333;border-color:#444;}
|
||||
.dark.modal .modal-header{color:#fff;}
|
||||
.dark.modal .modal-footer>*:not(.btn){color:#fff;}
|
||||
.dark.modal .modal-body{background:#333;}
|
||||
.dark.modal .close{color:#fff;}
|
||||
.dark.modal{color:#fff;}
|
86
web/libs/css/dash2.monitors.css
Normal file
86
web/libs/css/dash2.monitors.css
Normal file
|
@ -0,0 +1,86 @@
|
|||
|
||||
.jpegMode .cpu_load .progress-bar,.jpegMode .ram_load .progress-bar{background-color:#5cb85c}
|
||||
.jpegMode [system="jpegToggle"],[system].text-success{color:#5cb85c!important}
|
||||
|
||||
.monitor_item .stream-hud{opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;z-index:10}
|
||||
.monitor_item .stream-hud .camera_cpu_usage{position:absolute;top:0;left:0;width: 100%;}
|
||||
.monitor_item .stream-hud .camera_cpu_usage .progress{width: 100%;}
|
||||
.monitor_item .stream-hud .camera_cpu_usage:hover .progress{height:20px;transition:0.2s}
|
||||
.monitor_item .stream-hud .controls{position:absolute;top:10px;left:10px;}
|
||||
.monitor_item .stream-hud:hover{opacity:1}
|
||||
.monitor_item .stream-hud .bottom-text{position:absolute;bottom:0;left:0;width:70%;padding:5px;text-shadow: 0 0 10px #333;}
|
||||
.monitor_item .stream-hud:hover .bottom-text{top:0;}
|
||||
.monitor_item .stream-hud .bottom-text .detector-fade{background: rgba(0,0,0,0.4);padding:10px 20px;border-radius:10px}
|
||||
.monitor_item .stream-hud .lamp{position:absolute;top:5px;right:5px;z-index:1;text-shadow: 0 0 15px #333;}
|
||||
.monitor_item[mode="Disabled"] .stream-hud .lamp{color:#5d5d5d}
|
||||
.monitor_item[mode="Watch Only"] .stream-hud .lamp{color:#5da8e8}
|
||||
.monitor_item[mode="Idle"] .stream-hud .lamp{color:#fff}
|
||||
.monitor_item[mode="Record"] .stream-hud .lamp{color:#d9534f}
|
||||
/*.data-menu{max-height:700px}*/
|
||||
.data-menu:not(:last-child){border-right:1px solid #fff;}
|
||||
.data-menu.logs{list-style:none;}
|
||||
.monitor_item .motionVision{display:none}
|
||||
|
||||
.monitor_item .grid-stack-item-content{width:100%!important;left:0!important;right:0!important}
|
||||
.monitor_item .ui-resizable-se {bottom: 10px!important;}
|
||||
.monitor_item .stream-block{position: relative;text-align: center}
|
||||
.monitor_item .mdl-data_window{overflow-x: auto;background:rgba(0,0,0,0.7);color:#fff;height:100%}
|
||||
.monitor_item .mdl-data_window:not(.col-md-6){width:0;min-width:0;height:0px;min-height:0}
|
||||
|
||||
|
||||
.monitor_item.fullscreen img.stream-element{height:100%;width:auto}
|
||||
.monitor_item.fullscreen canvas.stream-element{height:auto;width:auto;background-color:black;}
|
||||
.monitor_item .stream-element{border: 0;object-fit: fill;height: 100%;width:100%}
|
||||
.monitor_item{position:relative;padding:0;transition:none;background:#000}
|
||||
.monitor_item .mdl-card{min-height:auto;border:1px solid #272727;border-radius:0px;overflow:hidden}
|
||||
.monitor_item .mdl-card__media{position:relative;padding:0!important;display:block!important;background:#000;}
|
||||
.monitor_item.selected .stream-element{height:600px}
|
||||
.monitor_item.selected .fa-expand:before{content:"\f066"}
|
||||
.monitor_item .mdl-card__supporting-text{background:#222;color:#fff!important;display:block;min-height:auto!important}
|
||||
.monitor_item.detector_triggered .detector-fade{opacity:1}
|
||||
.monitor_item .detector-fade{opacity:0}
|
||||
.monitor_item .indifference{position:absolute;width:100%;left:0;top:0;transition:0.2s;}
|
||||
.monitor_item .progress{width:100%;background:#333;box-shadow:0;}
|
||||
.monitor_item .indifference:hover .progress{height:20px;transition:0.2s}
|
||||
.hide_indifference .indifference{display:none!important}
|
||||
.hide_indifference [class_toggle="hide_indifference"]{color:#d9534f!important}
|
||||
.monitor_item .mdl-card:not(.mdl-cell--4-col-desktop) .mdl-card__supporting-text .monitor_details{display:none;font-size:90%;margin-bottom:10px}
|
||||
.monitor_item[mode="Record"] [mode="record"]{display:none}
|
||||
.monitor_item[mode="Watch Only"] [mode="start"]{display:none}
|
||||
.monitor_item .stream-hud .controls .btn{opacity:0.7}
|
||||
.monitor_item.doObjectDetection .progress-bar{background-color: #57d94f}
|
||||
|
||||
.data-menu{text-align:left}
|
||||
.data-menu ul,.side-menu ul{list-style:none;margin:0;padding:0;}
|
||||
.data-menu li,.side-menu li:not(.mdl-menu__item){
|
||||
border-bottom:1px solid #54502d;padding:10px;
|
||||
}
|
||||
.data-menu .progress-circle{margin:0 10px 0 0;position:relative;height:40px;width:40px;float:left}
|
||||
.data-menu .progress-circle span:after{content:''}
|
||||
img.circle-img,div.circle-img{border-radius:50%;height:50px;width:50px}
|
||||
.circle-img.sm{height:25px;width:25px}
|
||||
|
||||
@media screen and (max-width:1500px){
|
||||
.monitor_item .mdl-card__supporting-text .btn{
|
||||
padding: 5px 10px;
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
#monitors_live .monitor_item [class_toggle="show_logs"]{display:none}
|
||||
#monitors_live .monitor_item .indifference{top:-5px}
|
||||
#monitors_live .monitor_item .mdl-cell--8-col{width:100%;border:0;border-radius:0;margin:0;position:relative}
|
||||
#monitors_live .monitor_item .mdl-cell--4-col-desktop,.monitor_item .mdl-card__supporting-text{display:none}
|
||||
#monitors_live .monitor_item .mdl-card__supporting-text .monitor_details,#monitors_live .monitor_item .mdl-card__supporting-text .btn-group{display:none;text-align:center}
|
||||
#monitors_live .monitor_item .mdl-card__supporting-text:not(.meta){display:block;position:absolute;bottom:0;left:0;height:0;padding:0;overflow:visible}
|
||||
#monitors_live .monitor_item.show_data .mdl-card__supporting-text:not(.meta){width:50%}
|
||||
#monitors_live .monitor_item.detector_triggered .mdl-card__supporting-text:not(.meta) .indifference{opacity:0.5;}
|
||||
#monitors_live .monitor_item:hover .mdl-card__supporting-text:not(.meta){padding:15px;z-index:15;height:auto;}
|
||||
#monitors_live .monitor_item:hover .mdl-card__supporting-text .monitor_details{display:block}
|
||||
#monitors_live .monitor_item:hover .mdl-card__supporting-text .btn-group{display:inline-block}
|
||||
|
||||
.signal.green{background:#5cb85c}
|
||||
[status="1"] .btn[video="launch"],[data-status="1"] .btn[video="launch"]{background:#337ab7;border-color:#337ab7}
|
||||
[status="2"] .btn[launch="video"],[status="2"] .btn[video="launch"],[data-status="2"] .btn[video="launch"]{background:#a59100;border-color:#a59100}
|
||||
.signal.red{background:#c9302c}
|
8
web/libs/css/dash2.powervideo.css
Normal file
8
web/libs/css/dash2.powervideo.css
Normal file
|
@ -0,0 +1,8 @@
|
|||
#pvideo_viewer iframe{border:0;width:100%;height:350px;margin-bottom:10px;overflow:hidden}
|
||||
#pvideo_viewer video{max-height:300px;max-width:100%;}
|
||||
#pvideo_viewer .holder{height:300px;}
|
||||
#pvideo_viewer h3{margin-top:0}
|
||||
#pvideo_viewer .progressBar{position:relative;}
|
||||
#pvideo_viewer .bufferBar{position:absolute;left:0;top:0;opacity:0.4}
|
||||
#pvideo_viewer .timeBar{position:relative;z-index: 222;background:transparent}
|
||||
#pvideo_viewer h3{font-family:monospace}
|
89
web/libs/css/dash2.ptzcontrols.css
Normal file
89
web/libs/css/dash2.ptzcontrols.css
Normal file
|
@ -0,0 +1,89 @@
|
|||
/*Control Pad*/
|
||||
.PTZ_controls {
|
||||
z-index: 111;
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
top: 20px;
|
||||
margin:0;
|
||||
display: inline-block;
|
||||
width: 120px;
|
||||
}
|
||||
.PTZ_controls .btn-group{margin-top:10px}
|
||||
.PTZ_controls .pad {
|
||||
position: relative;
|
||||
height: 120px;
|
||||
width: 120px;
|
||||
background: #b7b7b7;
|
||||
border-radius: 50%;
|
||||
box-shadow: inset 0 0 1px rgba(120, 120, 120, 0.6), inset 0 2px 2px rgba(0, 0, 0, 0.1), 0 2px 2px rgba(240, 240, 240, 0.4);
|
||||
}
|
||||
.PTZ_controls .control {
|
||||
position: absolute;
|
||||
}
|
||||
.PTZ_controls .pad .control {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
background: #636363;
|
||||
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.6), 0 0 0 3px rgba(60, 60, 60, 0.2), 0 0 0 4px rgba(60, 60, 60, 0.2);
|
||||
border-radius: 2px;
|
||||
}
|
||||
.PTZ_controls .zoom_in{
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
.PTZ_controls .zoom_out{
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
.PTZ_controls .nv_enabled{
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
.PTZ_controls .nv_disable{
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
.PTZ_controls .pad .top {
|
||||
top: 15px;
|
||||
left: 50%;
|
||||
margin: 0 0 0 -15px;
|
||||
}
|
||||
.PTZ_controls .pad .left {
|
||||
top: 45px;
|
||||
left: 15px;
|
||||
}
|
||||
.PTZ_controls .pad .right {
|
||||
top: 45px;
|
||||
right: 15px;
|
||||
}
|
||||
.PTZ_controls .pad .control.right:before {
|
||||
transform: rotate(90deg) translate(-3px, -5px);
|
||||
}
|
||||
.PTZ_controls .pad .bottom {
|
||||
bottom: 15px;
|
||||
left: 50%;
|
||||
margin: 0 0 0 -15px;
|
||||
}
|
||||
/* Overlap the other controls to hide box-shadow */
|
||||
.PTZ_controls .pad .middle {
|
||||
height: 34px;
|
||||
width: 34px;
|
||||
z-index: 5;
|
||||
top: 43px;
|
||||
left: 50%;
|
||||
margin: 0 0 0 -17px;
|
||||
box-shadow: none;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.PTZ_controls .pad .middle:after {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -35% 0 0 -35%;
|
||||
content: '';
|
||||
background: #636363;
|
||||
height: 70%;
|
||||
width: 70%;
|
||||
border-radius: 100%;
|
||||
box-shadow: inset 0 0 2px rgba(120, 120, 120, 0.6), inset 0 2px 8px rgba(0, 0, 0, 0.1), 0 2px 2px rgba(240, 240, 240, 0.2);
|
||||
}
|
5
web/libs/css/dash2.regioneditor.css
Normal file
5
web/libs/css/dash2.regioneditor.css
Normal file
|
@ -0,0 +1,5 @@
|
|||
#region_editor .modal-body{text-align:center;overflow:auto;max-height:800px}
|
||||
#region_editor .canvas_holder{position:relative;display:inline-block;overflow:auto;min-height:450px}
|
||||
#region_editor .cord_element{position:absolute;background:rgba(221, 221, 221, 0.8);z-index:11;padding:5px;}
|
||||
#region_editor .cord_element.selected{z-index:12;}
|
||||
#region_editor .cord_element .controls{margin-bottom:5px;}
|
11
web/libs/css/dash2.righttoleft.css
Normal file
11
web/libs/css/dash2.righttoleft.css
Normal file
|
@ -0,0 +1,11 @@
|
|||
.right-to-left {text-align:right}
|
||||
.right-to-left select{direction: rtl;}
|
||||
.right-to-left input,.right-to-left textarea{direction: rtl;text-align:right}
|
||||
.right-to-left .form-group label span{padding-right:10px}
|
||||
.right-to-left .modal-footer{text-align:left}
|
||||
.right-to-left .mdl-menu__item>div>*{flex:1}
|
||||
.right-to-left .mdl-menu__item>div>i{margin-right:0;margin-left:5px}
|
||||
.right-to-left .mdl-menu__item{text-align:right}
|
||||
.right-to-left .mdl-menu__item i{float:right}
|
||||
.right-to-left .pull-right,.right-to-left .close{float:left!important}
|
||||
.right-to-left .pull-left,.right-to-left .mdl-menu__item span{float:right!important}
|
13
web/libs/css/dash2.timelapse.css
Normal file
13
web/libs/css/dash2.timelapse.css
Normal file
|
@ -0,0 +1,13 @@
|
|||
#timelapse_video_line{overflow-y:scroll;overflow-x:hidden;max-height:400px;margin:0;text-align:left}
|
||||
#timelapse_video_display .videoBefore,#timelapse_video_display .videoAfter{display:none}
|
||||
.timelapse_video:not(:last-child){border-bottom:1px solid #444;}
|
||||
.timelapse_video .frame{width:50px;height:50px;background-size:cover;background-position:center;border-radius:5px}
|
||||
.timelapse_video>div>div:not(:last-child){padding-right:10px}
|
||||
.timelapse_video .flex-block:not(:last-child){padding-bottom:10px}
|
||||
.timelapse_video.list-group-item{padding:10px}
|
||||
.timelapse_hud{position: relative;background:#000}
|
||||
.timelapse_hud .timelapse_playRate{position: absolute;font-family: monospace;top:10px;right:0;left:0;margin:auto;font-size:23px}
|
||||
#timelapse .progress-bar{transition:0.5s!important}
|
||||
.timelapse_hud .controlBar{position: absolute;background:rgba(22,22,22,0.8);width:100%;left:0;bottom:0;}
|
||||
.timelapse_hud .hover-hide{opacity:0}
|
||||
.timelapse_hud:hover .hover-hide{opacity:1;z-index:5}
|
|
@ -1,87 +1,3 @@
|
|||
/*Cusotm Bootstrap*/
|
||||
.col-5ths,
|
||||
.col-sm-5ths,
|
||||
.col-md-5ths,
|
||||
.col-lg-5ths {
|
||||
position: relative;
|
||||
min-height: 1px;
|
||||
padding-right: 15px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.col-5ths {
|
||||
-webkit-box-flex: 0;
|
||||
-webkit-flex: 0 0 20%;
|
||||
-ms-flex: 0 0 20%;
|
||||
flex: 0 0 20%;
|
||||
max-width: 20%;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.col-sm-5ths {
|
||||
-webkit-box-flex: 0;
|
||||
-webkit-flex: 0 0 20%;
|
||||
-ms-flex: 0 0 20%;
|
||||
flex: 0 0 20%;
|
||||
max-width: 20%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.col-md-5ths {
|
||||
-webkit-box-flex: 0;
|
||||
-webkit-flex: 0 0 20%;
|
||||
-ms-flex: 0 0 20%;
|
||||
flex: 0 0 20%;
|
||||
max-width: 20%;
|
||||
}
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color:#bd9565;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color:#bd9565;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 10px;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
/**/
|
||||
.flex{display:flex}
|
||||
.flex>div{flex:1}
|
||||
.flex-block{display:inline-flex;width:100%;flex-flow: row wrap;}
|
||||
.flex-unit-3{flex:3}
|
||||
.flex-inline{display: inline-flex;position:relative}
|
||||
@import (less) "../less/pie.less";
|
||||
ul{list-style:none}
|
||||
*{transition:0.2s;box-sizing:border-box}
|
||||
.affix-top{position:fixed}
|
||||
.no-padding{padding:0!important}
|
||||
.no-margin{margin:0!important}
|
||||
.pre-inline{white-space: normal;word-break: normal}
|
||||
.pre-inline>ul{margin:0;padding:0}
|
||||
a{cursor:pointer}
|
||||
nav h4{cursor:default;font-size:95%;padding:16px 40px;font-weight:100;text-transform:uppercase;letter-spacing:2px}
|
||||
.m-r{margin-right:10px}
|
||||
.m-b{margin-bottom:10px}
|
||||
.m-t{margin-top:10px}
|
||||
.m-l{margin-left:10px}
|
||||
.overflow-hidden{overflow: hidden!important}
|
||||
.list-inline{list-style:none}
|
||||
.list-inline li{display:inline-block;vertical-align: top;margin:0;}
|
||||
.truncate{width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
||||
img{max-width:100%}
|
||||
.display-table{display:table;width:100%}
|
||||
.display-inline{display:inline-block}
|
||||
.display-table-cell{display:table-cell}
|
||||
.small{font-size:80%}
|
||||
.super-center{position:absolute;left:0;top:0;right:0;bottom:0;margin:auto;width: 4em;height: 1em;}
|
||||
.jpegMode .cpu_load .progress-bar,.jpegMode .ram_load .progress-bar{background-color:#5cb85c}
|
||||
.jpegMode [system="jpegToggle"],[system].text-success{color:#5cb85c!important}
|
||||
.permission_monitor_edit{display:none}
|
||||
.permission_video_delete{display:none}
|
||||
.nodata .divider{margin:5px 0}
|
||||
.loading .divider{margin:5px 0}
|
||||
|
||||
#accbtn{
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
@ -89,75 +5,6 @@ img{max-width:100%}
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
.monitor_item .stream-hud{opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;z-index:2}
|
||||
.monitor_item .stream-hud .camera_cpu_usage{position:absolute;top:0;left:0;width: 100%;}
|
||||
.monitor_item .stream-hud .camera_cpu_usage .progress{width: 100%;}
|
||||
.monitor_item .stream-hud .camera_cpu_usage:hover .progress{height:20px;transition:0.2s}
|
||||
.monitor_item .stream-hud .controls{position:absolute;top:10px;left:10px;}
|
||||
.monitor_item .stream-hud:hover{opacity:1}
|
||||
.monitor_item .stream-hud .bottom-text{position:absolute;bottom:0;left:0;width:100%;padding:5px;text-shadow: 0 0 10px #333;}
|
||||
.monitor_item .stream-hud .bottom-text .detector-fade{background: rgba(0,0,0,0.4);padding:10px 20px;border-radius:10px}
|
||||
.monitor_item .stream-hud .lamp{position:absolute;top:5px;right:5px;z-index:1;text-shadow: 0 0 15px #333;}
|
||||
.monitor_item[mode="Disabled"] .stream-hud .lamp{color:#5d5d5d}
|
||||
.monitor_item[mode="Watch Only"] .stream-hud .lamp{color:#5da8e8}
|
||||
.monitor_item[mode="Idle"] .stream-hud .lamp{color:#fff}
|
||||
.monitor_item[mode="Record"] .stream-hud .lamp{color:#d9534f}
|
||||
/*.data-menu{max-height:700px}*/
|
||||
.data-menu:not(:last-child){border-right:1px solid #fff;}
|
||||
.data-menu.logs{list-style:none;}
|
||||
.monitor_item .motionVision{display:none}
|
||||
|
||||
.monitor_item .grid-stack-item-content{width:100%!important;left:0!important;right:0!important}
|
||||
.monitor_item .ui-resizable-se {bottom: 10px!important;}
|
||||
.monitor_item .stream-block{position: relative;text-align: center}
|
||||
.monitor_item .mdl-data_window{overflow-x: auto;background:rgba(0,0,0,0.7);color:#fff;height:100%}
|
||||
.monitor_item .mdl-data_window:not(.col-md-6){width:0;min-width:0;height:0px;min-height:0}
|
||||
|
||||
|
||||
.monitor_item.fullscreen img.stream-element{height:100%;width:auto}
|
||||
.monitor_item.fullscreen canvas.stream-element{height:auto;width:auto;background-color:black;}
|
||||
.monitor_item .stream-element{border: 0;object-fit: fill;height: 100%;width:100%}
|
||||
.monitor_item{position:relative;padding:0;transition:none;background:#000}
|
||||
.monitor_item .mdl-card{min-height:auto;border:1px solid #272727;border-radius:0px;overflow:hidden}
|
||||
.monitor_item .mdl-card__media{position:relative;padding:0!important;display:block!important;background:#000;}
|
||||
.monitor_item.selected .stream-element{height:600px}
|
||||
.monitor_item.selected .fa-expand:before{content:"\f066"}
|
||||
.monitor_item .mdl-card__supporting-text{background:#222;color:#fff!important;display:block;min-height:auto!important}
|
||||
.monitor_item.detector_triggered .detector-fade{opacity:1}
|
||||
.monitor_item .detector-fade{opacity:0}
|
||||
.monitor_item .indifference{position:absolute;width:100%;left:0;top:0;transition:0.2s;}
|
||||
.monitor_item .progress{width:100%;background:#333;box-shadow:0;}
|
||||
.monitor_item .indifference:hover .progress{height:20px;transition:0.2s}
|
||||
.hide_indifference .indifference{display:none!important}
|
||||
.hide_indifference [class_toggle="hide_indifference"]{color:#d9534f!important}
|
||||
.monitor_item .mdl-card:not(.mdl-cell--4-col-desktop) .mdl-card__supporting-text .monitor_details{display:none;font-size:90%;margin-bottom:10px}
|
||||
.monitor_item[mode="Record"] [mode="record"]{display:none}
|
||||
.monitor_item[mode="Watch Only"] [mode="start"]{display:none}
|
||||
.monitor_item .stream-hud .controls .btn{opacity:0.7}
|
||||
.monitor_item.doObjectDetection .progress-bar{background-color: #57d94f}
|
||||
|
||||
@media screen and (max-width:1500px){
|
||||
.monitor_item .mdl-card__supporting-text .btn{
|
||||
padding: 5px 10px;
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
#monitors_live .monitor_item [class_toggle="show_logs"]{display:none}
|
||||
#monitors_live .monitor_item .indifference{top:-5px}
|
||||
#monitors_live .monitor_item .mdl-cell--8-col{width:100%;border:0;border-radius:0;margin:0;position:relative}
|
||||
#monitors_live .monitor_item .mdl-cell--4-col-desktop,.monitor_item .mdl-card__supporting-text{display:none}
|
||||
#monitors_live .monitor_item .mdl-card__supporting-text .monitor_details,#monitors_live .monitor_item .mdl-card__supporting-text .btn-group{display:none;text-align:center}
|
||||
#monitors_live .monitor_item .mdl-card__supporting-text:not(.meta){display:block;position:absolute;bottom:0;left:0;height:0;padding:0;}
|
||||
#monitors_live .monitor_item.show_data .mdl-card__supporting-text:not(.meta){width:50%}
|
||||
#monitors_live .monitor_item.detector_triggered .mdl-card__supporting-text:not(.meta) .indifference{opacity:0.5;}
|
||||
#monitors_live .monitor_item:hover .mdl-card__supporting-text:not(.meta){padding:15px;z-index:15;height:auto;}
|
||||
#monitors_live .monitor_item:hover .mdl-card__supporting-text .monitor_details{display:block}
|
||||
#monitors_live .monitor_item:hover .mdl-card__supporting-text .btn-group{display:inline-block}
|
||||
|
||||
|
||||
|
||||
#vis_pwrvideo{height:250px}
|
||||
#monSectionStreamChannels,#monSectionInputMaps{margin-bottom: 15px;}
|
||||
#monSectionStreamChannels:empty,#monSectionInputMaps:empty{display:none}
|
||||
|
@ -169,19 +16,12 @@ img{max-width:100%}
|
|||
|
||||
.demo-blog .demo-blog__posts.montage{max-width:100%}
|
||||
|
||||
|
||||
.mdl-layout__drawer{overflow-y: visible!important}
|
||||
.hide-side .mdl-layout__drawer{overflow-y: hidden}
|
||||
.mdl-layout__header-row{padding-left:10!important}
|
||||
.mdl-layout__header-row .nav>li>a{border-radius:50%;}
|
||||
.mdl-layout__drawer-button i{position:absolute;top:0;right:0;bottom:0;left:0;margin:auto;height:1em;color:#fff}
|
||||
.data-menu{text-align:left}
|
||||
.data-menu ul,.side-menu ul{list-style:none;margin:0;padding:0;}
|
||||
.data-menu li,.side-menu li{
|
||||
border-bottom:1px solid #54502d;padding:10px;
|
||||
}
|
||||
.data-menu .progress-circle{margin:0 10px 0 0;position:relative;height:40px;width:40px;float:left}
|
||||
.data-menu .progress-circle span:after{content:''}
|
||||
img.circle-img,div.circle-img{border-radius:50%;height:50px;width:50px}
|
||||
.circle-img.sm{height:25px;width:25px}
|
||||
|
||||
|
||||
.video_video{margin:auto;max-width:100%;max-height:600px;}
|
||||
#confirm_window .video_video{margin-top:15px}
|
||||
|
@ -200,60 +40,9 @@ img.circle-img,div.circle-img{border-radius:50%;height:50px;width:50px}
|
|||
.flex-container-modal-body .flex-block>div{flex:1;float:none}
|
||||
|
||||
.modal{overflow:auto!important}
|
||||
form.modal-body{margin:0}
|
||||
#region_editor .modal-body{text-align:center;overflow:auto;max-height:800px}
|
||||
#region_editor .canvas_holder{position:relative;display:inline-block;overflow:auto;min-height:450px}
|
||||
#region_editor .cord_element{position:absolute;background:rgba(221, 221, 221, 0.8);z-index:11;padding:5px;}
|
||||
#region_editor .cord_element.selected{z-index:12;}
|
||||
#region_editor .cord_element .controls{margin-bottom:5px;}
|
||||
.form-group label span{padding:5px;font-weight: 400;color: #2d2d2d;display:block;border-bottom: 1px dotted #ddd;font-size: 10pt;}
|
||||
.form-group label{display:table}
|
||||
.form-group label>div{display:table-cell}
|
||||
.form-group label>div:nth-child(2n-1){width:30%}
|
||||
.form-group label>div:nth-child(2){width:70%;padding:5px;border:1px solid #dedede;border-radius:5px}
|
||||
.dark .form-group label>div,.dark .form-group label>div>span{border-color:#454545;color:#fff}
|
||||
.important.form-group label>div:nth-child(2),.important.form-group label>div>span{border-color:red}
|
||||
.form-group label span small{margin-left: 2px;display:block;font-weight: 600;}
|
||||
.form-group-group .round-left{border-radius: 50px 0 0 50px;margin-left:10px}
|
||||
.form-group-group blockquote:before,.form-group-group blockquote:after{display:none!important}
|
||||
.form-group-group blockquote{letter-spacing:normal;font-style:normal}
|
||||
.form-group-group blockquote p:empty{display:none}
|
||||
.form-group-group blockquote p{font-size:inherit}
|
||||
.form-group-group blockquote p:last-child{margin-bottom:0}
|
||||
.form-group-group-group>div,.form-group-group-group .h_us_advanced>div{margin-bottom:15px;}
|
||||
.form-group-group{padding:0 10px 10px 10px;overflow:hidden;margin-bottom:15px;border-radius:5px;border:1px solid #ddd;background:#fff}
|
||||
.form-group-group table{width:100%}
|
||||
.form-group-group table tr td{padding:10px 5px}
|
||||
.form-group-group table tr:not(:last-child) td{border-bottom:1px dotted #eee}
|
||||
.form-group-group .mdl-list__item{border-bottom:1px solid #eee;}
|
||||
.form-group-group .mdl-list__item:hover{background:#e6e6e6;border-radius:4px;}
|
||||
.dark .form-group-group .mdl-list__item{color:#fff;border-bottom:1px solid #444;}
|
||||
.dark .form-group-group .mdl-list__item:hover{background:#555;}
|
||||
.form-group-group:last-child,.form-group-group > .form-group:last-child{margin-bottom:0}
|
||||
.form-group-group h4{margin:0 -10px 15px -10px;padding:15px;background:#ddd;}
|
||||
.form-group-group h4 small{color:#fff;}
|
||||
.form-group-group.red{border-color:#d9534f}
|
||||
.form-group-group.red h4{background:#d9534f;color:#fff}
|
||||
.form-group-group.purple{border-color:#3f51b5}
|
||||
.form-group-group.purple h4{background:#3f51b5;color:#fff}
|
||||
.form-group-group.blue{border-color:#337ab7}
|
||||
.form-group-group.blue h4{background:#337ab7;color:#fff}
|
||||
.form-group-group.navy{border-color:#31708f}
|
||||
.form-group-group.navy h4{background:#31708f;color:#fff}
|
||||
.form-group-group.green{border-color:#449d44}
|
||||
.form-group-group.green h4{background:#449d44;color:#fff}
|
||||
.form-group-group.forestgreen{border-color:#1e4046}
|
||||
.form-group-group.forestgreen h4{background:#1e4046;color:#fff}
|
||||
.form-group-group.orange{border-color:#c49a68}
|
||||
.form-group-group.orange h4{background:#c49a68;color:#fff}
|
||||
.form-group-group.grey{border-color:#777}
|
||||
.form-group-group.grey h4{background:#777;color:#fff}
|
||||
.dark .form-group-group{background:#222}
|
||||
|
||||
.videos_list .title{font-size:12pt;padding:0 10px}
|
||||
[status="1"] .btn[video="launch"],[data-status="1"] .btn[video="launch"]{background:#337ab7;border-color:#337ab7}
|
||||
[status="2"] .btn[launch="video"],[status="2"] .btn[video="launch"],[data-status="2"] .btn[video="launch"]{background:#a59100;border-color:#a59100}
|
||||
.signal.red{background:#c9302c}
|
||||
.signal.green{background:#5cb85c}
|
||||
|
||||
.demo-drawer{background:#2b2a2a;color:#fff;}
|
||||
.demo-drawer.mdl-layout__drawer .mdl-navigation{padding-top:0;}
|
||||
.demo-drawer::-webkit-scrollbar{display:none;}
|
||||
|
@ -285,12 +74,14 @@ form.modal-body{margin:0}
|
|||
.nav-xs.side-menu.list-blocks .monitor_block img{width:40px;height:40px;}
|
||||
.side-menu.list-blocks .monitor_block .box{width:calc(100% - 70px);display:inline-block}
|
||||
.nav-xs.side-menu.list-blocks .monitor_block .list-data{display:none}
|
||||
.side-menu .mdl-menu{z-index: 12}
|
||||
|
||||
#monitors_list .monitor_block{transition:none}
|
||||
.dropdown-menu.scrollable{max-height:300px}
|
||||
.upload_file input{display:none}
|
||||
#video_preview .stream-objects{right:0;margin:auto;display:inline-block;position:relative;width:auto}
|
||||
.stream-block,.stream-objects{overflow: hidden!important}
|
||||
.stream-objects{position:absolute;top:0;left:0;width:100%;height:100%;z-index:1}
|
||||
.stream-objects{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10}
|
||||
.stream-objects .tag{position:absolute;bottom:100%;left:0;background:red;color:#fff;font-family:monospace;font-size:80%;border-radius:5px 5px 0 0 ;padding:3px 5px;}
|
||||
.stream-objects .stream-detected-object{position:absolute;top:0;left:0;border:3px solid red;background:transparent;border-radius:5px}
|
||||
.stream-objects .stream-detected-point{position:absolute;top:0;left:0;border:3px solid yellow;background:transparent;border-radius:5px}
|
||||
|
@ -306,39 +97,11 @@ form.modal-body{margin:0}
|
|||
|
||||
.form-group label{width:100%}
|
||||
|
||||
#pvideo_viewer iframe{border:0;width:100%;height:350px;margin-bottom:10px;overflow:hidden}
|
||||
#pvideo_viewer video{max-height:300px;max-width:100%;}
|
||||
#pvideo_viewer .holder{height:300px;}
|
||||
#pvideo_viewer h3{margin-top:0}
|
||||
#pvideo_viewer .progressBar{position:relative;}
|
||||
#pvideo_viewer .bufferBar{position:absolute;left:0;top:0;opacity:0.4}
|
||||
#pvideo_viewer .timeBar{position:relative;z-index: 222;background:transparent}
|
||||
#pvideo_viewer h3{font-family:monospace}
|
||||
|
||||
|
||||
#vis_monitors{overflow:auto;max-height:400px}
|
||||
#vis_monitors .btn-group-vertical{width:100%}
|
||||
|
||||
/*timeline*/
|
||||
#timelapse_video_line{overflow-y:scroll;overflow-x:hidden;max-height:400px;margin:0;text-align:left}
|
||||
#timelapse_video_display .videoBefore,#timelapse_video_display .videoAfter{display:none}
|
||||
.timelapse_video:not(:last-child){border-bottom:1px solid #444;}
|
||||
.timelapse_video .frame{width:50px;height:50px;background-size:cover;background-position:center;border-radius:5px}
|
||||
.timelapse_video>div>div:not(:last-child){padding-right:10px}
|
||||
.timelapse_video .flex-block:not(:last-child){padding-bottom:10px}
|
||||
.timelapse_video.list-group-item{padding:10px}
|
||||
.timelapse_hud{position: relative;background:#000}
|
||||
.timelapse_hud .timelapse_playRate{position: absolute;font-family: monospace;top:10px;right:0;left:0;margin:auto;font-size:23px}
|
||||
#timelapse .progress-bar{transition:0.5s!important}
|
||||
.timelapse_hud .controlBar{position: absolute;background:rgba(22,22,22,0.8);width:100%;left:0;bottom:0;}
|
||||
.timelapse_hud .hover-hide{opacity:0}
|
||||
.timelapse_hud:hover .hover-hide{opacity:1;z-index:5}
|
||||
.video_grid{overflow: auto;height: 100%;display: block;}
|
||||
.video_grid .col-md-2{padding-left:5px;padding-right:5px;padding-bottom:10px}
|
||||
.video_grid .thumb{width:100%;height:150px;display:inline-block;background-size:cover;position:relative;overflow:hidden;border-radius:4px;border:1px solid #000;box-shadow:0 0 10px #151515}
|
||||
.video_grid .thumb .title-strip, .video_grid .thumb .button-strip{width:100%;position:absolute;left:0;background:rgba(0,0,0,0.7);color:#fff;padding:4px}
|
||||
.video_grid .thumb .title-strip{top:0;opacity:0.5}
|
||||
.video_grid .thumb .button-strip{bottom:0;opacity:0}
|
||||
.video_grid .thumb:hover .title-strip, .video_grid .thumb:hover .button-strip{opacity:1}
|
||||
|
||||
.table-striped>tbody>tr>td{vertical-align:middle}
|
||||
.table-striped .thumbnail{width:100px;height:80px;border-radius:5px;margin:0;display:inline-block;}
|
||||
|
@ -352,13 +115,6 @@ form.modal-body{margin:0}
|
|||
background-color: #c49a68;
|
||||
border-color: #c49a68;
|
||||
}
|
||||
|
||||
.dark.modal .modal-header,.dark.modal .modal-footer{background:#333;border-color:#444;}
|
||||
.dark.modal .modal-header{color:#fff;}
|
||||
.dark.modal .modal-footer>*:not(.btn){color:#fff;}
|
||||
.dark.modal .modal-body{background:#333;}
|
||||
.dark.modal .close{color:#fff;}
|
||||
.dark.modal{color:#fff;}
|
||||
.dark .table-striped>tbody>tr:nth-of-type(even){background:#616161}
|
||||
.dark .table-striped>tbody>tr>td{border-color:#222;color:#fff}
|
||||
.dark .table-striped>thead>tr>th{border-color:#222;color:#fff;background:#616161;vertical-align:middle;}
|
||||
|
@ -481,95 +237,10 @@ ul.msg_list li .message {
|
|||
.mdl-js-layout.hide-side:not(.is-small-screen)>.mdl-layout__header{
|
||||
margin-left: 0px;width:100%;transition:0.2s
|
||||
}
|
||||
/*Control Pad*/
|
||||
.PTZ_controls {
|
||||
z-index: 111;
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
top: 20px;
|
||||
margin:0;
|
||||
display: inline-block;
|
||||
width: 120px;
|
||||
}
|
||||
.PTZ_controls .btn-group{margin-top:10px}
|
||||
.PTZ_controls .pad {
|
||||
position: relative;
|
||||
height: 120px;
|
||||
width: 120px;
|
||||
background: #b7b7b7;
|
||||
border-radius: 50%;
|
||||
box-shadow: inset 0 0 1px rgba(120, 120, 120, 0.6), inset 0 2px 2px rgba(0, 0, 0, 0.1), 0 2px 2px rgba(240, 240, 240, 0.4);
|
||||
}
|
||||
.PTZ_controls .control {
|
||||
position: absolute;
|
||||
}
|
||||
.PTZ_controls .pad .control {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
background: #636363;
|
||||
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.6), 0 0 0 3px rgba(60, 60, 60, 0.2), 0 0 0 4px rgba(60, 60, 60, 0.2);
|
||||
border-radius: 2px;
|
||||
}
|
||||
.PTZ_controls .zoom_in{
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
.PTZ_controls .zoom_out{
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
.PTZ_controls .nv_enabled{
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
.PTZ_controls .nv_disable{
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
.PTZ_controls .pad .top {
|
||||
top: 15px;
|
||||
left: 50%;
|
||||
margin: 0 0 0 -15px;
|
||||
}
|
||||
.PTZ_controls .pad .left {
|
||||
top: 45px;
|
||||
left: 15px;
|
||||
}
|
||||
.PTZ_controls .pad .right {
|
||||
top: 45px;
|
||||
right: 15px;
|
||||
}
|
||||
.PTZ_controls .pad .control.right:before {
|
||||
transform: rotate(90deg) translate(-3px, -5px);
|
||||
}
|
||||
.PTZ_controls .pad .bottom {
|
||||
bottom: 15px;
|
||||
left: 50%;
|
||||
margin: 0 0 0 -15px;
|
||||
}
|
||||
/* Overlap the other controls to hide box-shadow */
|
||||
.PTZ_controls .pad .middle {
|
||||
height: 34px;
|
||||
width: 34px;
|
||||
z-index: 5;
|
||||
top: 43px;
|
||||
left: 50%;
|
||||
margin: 0 0 0 -17px;
|
||||
box-shadow: none;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.PTZ_controls .pad .middle:after {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -35% 0 0 -35%;
|
||||
content: '';
|
||||
background: #636363;
|
||||
height: 70%;
|
||||
width: 70%;
|
||||
border-radius: 100%;
|
||||
box-shadow: inset 0 0 2px rgba(120, 120, 120, 0.6), inset 0 2px 8px rgba(0, 0, 0, 0.1), 0 2px 2px rgba(240, 240, 240, 0.2);
|
||||
}
|
||||
.mdl-menu__item>div{display:flex;align-items: center;width:100%}
|
||||
.mdl-menu__item>div>i{margin-right:5px}
|
||||
|
||||
|
||||
/*Digital Zoom*/
|
||||
.stream-block{
|
||||
position: relative;
|
||||
|
@ -663,20 +334,7 @@ ul.msg_list li .message {
|
|||
-moz-animation: blink 1s linear infinite;
|
||||
animation: blink 1s linear infinite;
|
||||
}
|
||||
.mdl-menu__item>div{display:flex;align-items: center;width:100%}
|
||||
.mdl-menu__item>div>i{margin-right:5px}
|
||||
/*For languages that are right to left*/
|
||||
.right-to-left {text-align:right}
|
||||
.right-to-left select{direction: rtl;}
|
||||
.right-to-left input,.right-to-left textarea{direction: rtl;text-align:right}
|
||||
.right-to-left .form-group label span{padding-right:10px}
|
||||
.right-to-left .modal-footer{text-align:left}
|
||||
.right-to-left .mdl-menu__item>div>*{flex:1}
|
||||
.right-to-left .mdl-menu__item>div>i{margin-right:0;margin-left:5px}
|
||||
.right-to-left .mdl-menu__item{text-align:right}
|
||||
.right-to-left .mdl-menu__item i{float:right}
|
||||
.right-to-left .pull-right,.right-to-left .close{float:left!important}
|
||||
.right-to-left .pull-left,.right-to-left .mdl-menu__item span{float:right!important}
|
||||
|
||||
/* All-CSS Toggle Switch (Checkbox Hack) by Marcus Burnette - https://codepen.io/mburnette/pen/LxNxNg */
|
||||
.marc-toggle {
|
||||
width: 50px;
|
||||
|
|
742
web/libs/css/main.dash2.old.css
Normal file
742
web/libs/css/main.dash2.old.css
Normal file
|
@ -0,0 +1,742 @@
|
|||
/*Cusotm Bootstrap*/
|
||||
.col-5ths,
|
||||
.col-sm-5ths,
|
||||
.col-md-5ths,
|
||||
.col-lg-5ths {
|
||||
position: relative;
|
||||
min-height: 1px;
|
||||
padding-right: 15px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.col-5ths {
|
||||
-webkit-box-flex: 0;
|
||||
-webkit-flex: 0 0 20%;
|
||||
-ms-flex: 0 0 20%;
|
||||
flex: 0 0 20%;
|
||||
max-width: 20%;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.col-sm-5ths {
|
||||
-webkit-box-flex: 0;
|
||||
-webkit-flex: 0 0 20%;
|
||||
-ms-flex: 0 0 20%;
|
||||
flex: 0 0 20%;
|
||||
max-width: 20%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.col-md-5ths {
|
||||
-webkit-box-flex: 0;
|
||||
-webkit-flex: 0 0 20%;
|
||||
-ms-flex: 0 0 20%;
|
||||
flex: 0 0 20%;
|
||||
max-width: 20%;
|
||||
}
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color:#bd9565;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color:#bd9565;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 10px;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
/**/
|
||||
.flex{display:flex}
|
||||
.flex>div{flex:1}
|
||||
.flex-block{display:inline-flex;width:100%;flex-flow: row wrap;}
|
||||
.flex-unit-3{flex:3}
|
||||
.flex-inline{display: inline-flex;position:relative}
|
||||
@import (less) "../less/pie.less";
|
||||
ul{list-style:none}
|
||||
*{transition:0.2s;box-sizing:border-box}
|
||||
.affix-top{position:fixed}
|
||||
.no-padding{padding:0!important}
|
||||
.no-margin{margin:0!important}
|
||||
.pre-inline{white-space: normal;word-break: normal}
|
||||
.pre-inline>ul{margin:0;padding:0}
|
||||
a{cursor:pointer}
|
||||
nav h4{cursor:default;font-size:95%;padding:16px 40px;font-weight:100;text-transform:uppercase;letter-spacing:2px}
|
||||
.m-r{margin-right:10px}
|
||||
.m-b{margin-bottom:10px}
|
||||
.m-t{margin-top:10px}
|
||||
.m-l{margin-left:10px}
|
||||
.overflow-hidden{overflow: hidden!important}
|
||||
.list-inline{list-style:none}
|
||||
.list-inline li{display:inline-block;vertical-align: top;margin:0;}
|
||||
.truncate{width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
||||
img{max-width:100%}
|
||||
.display-table{display:table;width:100%}
|
||||
.display-inline{display:inline-block}
|
||||
.display-table-cell{display:table-cell}
|
||||
.small{font-size:80%}
|
||||
.super-center{position:absolute;left:0;top:0;right:0;bottom:0;margin:auto;width: 4em;height: 1em;}
|
||||
.jpegMode .cpu_load .progress-bar,.jpegMode .ram_load .progress-bar{background-color:#5cb85c}
|
||||
.jpegMode [system="jpegToggle"],[system].text-success{color:#5cb85c!important}
|
||||
.permission_monitor_edit{display:none}
|
||||
.permission_video_delete{display:none}
|
||||
.nodata .divider{margin:5px 0}
|
||||
.loading .divider{margin:5px 0}
|
||||
|
||||
#accbtn{
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.monitor_item .stream-hud{opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;z-index:10}
|
||||
.monitor_item .stream-hud .camera_cpu_usage{position:absolute;top:0;left:0;width: 100%;}
|
||||
.monitor_item .stream-hud .camera_cpu_usage .progress{width: 100%;}
|
||||
.monitor_item .stream-hud .camera_cpu_usage:hover .progress{height:20px;transition:0.2s}
|
||||
.monitor_item .stream-hud .controls{position:absolute;top:10px;left:10px;}
|
||||
.monitor_item .stream-hud:hover{opacity:1}
|
||||
.monitor_item .stream-hud .bottom-text{position:absolute;bottom:0;left:0;width:100%;padding:5px;text-shadow: 0 0 10px #333;}
|
||||
.monitor_item .stream-hud .bottom-text .detector-fade{background: rgba(0,0,0,0.4);padding:10px 20px;border-radius:10px}
|
||||
.monitor_item .stream-hud .lamp{position:absolute;top:5px;right:5px;z-index:1;text-shadow: 0 0 15px #333;}
|
||||
.monitor_item[mode="Disabled"] .stream-hud .lamp{color:#5d5d5d}
|
||||
.monitor_item[mode="Watch Only"] .stream-hud .lamp{color:#5da8e8}
|
||||
.monitor_item[mode="Idle"] .stream-hud .lamp{color:#fff}
|
||||
.monitor_item[mode="Record"] .stream-hud .lamp{color:#d9534f}
|
||||
/*.data-menu{max-height:700px}*/
|
||||
.data-menu:not(:last-child){border-right:1px solid #fff;}
|
||||
.data-menu.logs{list-style:none;}
|
||||
.monitor_item .motionVision{display:none}
|
||||
|
||||
.monitor_item .grid-stack-item-content{width:100%!important;left:0!important;right:0!important}
|
||||
.monitor_item .ui-resizable-se {bottom: 10px!important;}
|
||||
.monitor_item .stream-block{position: relative;text-align: center}
|
||||
.monitor_item .mdl-data_window{overflow-x: auto;background:rgba(0,0,0,0.7);color:#fff;height:100%}
|
||||
.monitor_item .mdl-data_window:not(.col-md-6){width:0;min-width:0;height:0px;min-height:0}
|
||||
|
||||
|
||||
.monitor_item.fullscreen img.stream-element{height:100%;width:auto}
|
||||
.monitor_item.fullscreen canvas.stream-element{height:auto;width:auto;background-color:black;}
|
||||
.monitor_item .stream-element{border: 0;object-fit: fill;height: 100%;width:100%}
|
||||
.monitor_item{position:relative;padding:0;transition:none;background:#000}
|
||||
.monitor_item .mdl-card{min-height:auto;border:1px solid #272727;border-radius:0px;overflow:hidden}
|
||||
.monitor_item .mdl-card__media{position:relative;padding:0!important;display:block!important;background:#000;height:100%}
|
||||
.monitor_item.selected .stream-element{height:600px}
|
||||
.monitor_item.selected .fa-expand:before{content:"\f066"}
|
||||
.monitor_item .mdl-card__supporting-text{background:#222;color:#fff!important;display:block;min-height:auto!important}
|
||||
.monitor_item.detector_triggered .detector-fade{opacity:1}
|
||||
.monitor_item .detector-fade{opacity:0}
|
||||
.monitor_item .indifference{position:absolute;width:100%;left:0;top:0;transition:0.2s;}
|
||||
.monitor_item .progress{width:100%;background:#333;box-shadow:0;}
|
||||
.monitor_item .indifference:hover .progress{height:20px;transition:0.2s}
|
||||
.hide_indifference .indifference{display:none!important}
|
||||
.hide_indifference [class_toggle="hide_indifference"]{color:#d9534f!important}
|
||||
.monitor_item .mdl-card:not(.mdl-cell--4-col-desktop) .mdl-card__supporting-text .monitor_details{display:none;font-size:90%;margin-bottom:10px}
|
||||
.monitor_item[mode="Record"] [mode="record"]{display:none}
|
||||
.monitor_item[mode="Watch Only"] [mode="start"]{display:none}
|
||||
.monitor_item .stream-hud .controls .btn{opacity:0.7}
|
||||
.monitor_item.doObjectDetection .progress-bar{background-color: #57d94f}
|
||||
|
||||
@media screen and (max-width:1500px){
|
||||
.monitor_item .mdl-card__supporting-text .btn{
|
||||
padding: 5px 10px;
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
#monitors_live .monitor_item [class_toggle="show_logs"]{display:none}
|
||||
#monitors_live .monitor_item .indifference{top:-5px}
|
||||
#monitors_live .monitor_item .mdl-cell--8-col{width:100%;border:0;border-radius:0;margin:0;position:relative}
|
||||
#monitors_live .monitor_item .mdl-cell--4-col-desktop,.monitor_item .mdl-card__supporting-text{display:none}
|
||||
#monitors_live .monitor_item .mdl-card__supporting-text .monitor_details,#monitors_live .monitor_item .mdl-card__supporting-text .btn-group{display:none;text-align:center}
|
||||
#monitors_live .monitor_item .mdl-card__supporting-text:not(.meta){display:block;position:absolute;bottom:0;left:0;height:0;padding:0;overflow:visible;}
|
||||
#monitors_live .monitor_item.show_data .mdl-card__supporting-text:not(.meta){width:50%}
|
||||
#monitors_live .monitor_item.detector_triggered .mdl-card__supporting-text:not(.meta) .indifference{opacity:0.5;}
|
||||
#monitors_live .monitor_item:hover .mdl-card__supporting-text:not(.meta){padding:15px;z-index:15;height:auto;}
|
||||
#monitors_live .monitor_item:hover .mdl-card__supporting-text .monitor_details{display:block}
|
||||
#monitors_live .monitor_item:hover .mdl-card__supporting-text .btn-group{display:inline-block}
|
||||
|
||||
|
||||
|
||||
#vis_pwrvideo{height:250px}
|
||||
#monSectionStreamChannels,#monSectionInputMaps{margin-bottom: 15px;}
|
||||
#monSectionStreamChannels:empty,#monSectionInputMaps:empty{display:none}
|
||||
#region_editor_live iframe,.canvas_holder canvas{border:0;position:absolute;left:0;top:0}
|
||||
.canvas_holder canvas{z-index:11}
|
||||
|
||||
.demo-blog .mdl-card__media ~ .mdl-card__supporting-text{position:relative;overflow:initial;cursor:move}
|
||||
.demo-blog .mdl-card__media ~ .mdl-card__supporting-text .btn-group{cursor: default}
|
||||
|
||||
.demo-blog .demo-blog__posts.montage{max-width:100%}
|
||||
|
||||
|
||||
.mdl-layout__header-row{padding-left:10!important}
|
||||
.mdl-layout__header-row .nav>li>a{border-radius:50%;}
|
||||
.mdl-layout__drawer-button i{position:absolute;top:0;right:0;bottom:0;left:0;margin:auto;height:1em;color:#fff}
|
||||
.data-menu{text-align:left}
|
||||
.data-menu ul,.side-menu ul{list-style:none;margin:0;padding:0;}
|
||||
.data-menu li,.side-menu li{
|
||||
border-bottom:1px solid #54502d;padding:10px;
|
||||
}
|
||||
.data-menu .progress-circle{margin:0 10px 0 0;position:relative;height:40px;width:40px;float:left}
|
||||
.data-menu .progress-circle span:after{content:''}
|
||||
img.circle-img,div.circle-img{border-radius:50%;height:50px;width:50px}
|
||||
.circle-img.sm{height:25px;width:25px}
|
||||
|
||||
.video_video{margin:auto;max-width:100%;max-height:600px;}
|
||||
#confirm_window .video_video{margin-top:15px}
|
||||
#confirm_window .info-table{margin-top:15px}
|
||||
@media (max-width: 768px){
|
||||
.full.modal .modal-body,.medium.modal .modal-body{max-height:400px;overflow:auto}
|
||||
}
|
||||
@media (min-width: 768px){
|
||||
.modal.full,.modal.medium{padding-left:0!important;}
|
||||
.modal.full .modal-dialog{width:calc(100% - 10px)!important;margin: 30px auto;}
|
||||
.modal.medium .modal-dialog{width:calc(70% - 10px)!important;margin: 30px auto;}
|
||||
.full.modal .modal-body,.medium.modal .modal-body{height:calc(100% - 200px);overflow:auto}
|
||||
}
|
||||
|
||||
.flex-container-modal-body{overflow: auto}
|
||||
.flex-container-modal-body .flex-block>div{flex:1;float:none}
|
||||
|
||||
.modal{overflow:auto!important}
|
||||
form.modal-body{margin:0}
|
||||
#region_editor .modal-body{text-align:center;overflow:auto;max-height:800px}
|
||||
#region_editor .canvas_holder{position:relative;display:inline-block;overflow:auto;min-height:450px}
|
||||
#region_editor .cord_element{position:absolute;background:rgba(221, 221, 221, 0.8);z-index:11;padding:5px;}
|
||||
#region_editor .cord_element.selected{z-index:12;}
|
||||
#region_editor .cord_element .controls{margin-bottom:5px;}
|
||||
.form-group label span{padding:5px;font-weight: 400;color: #2d2d2d;display:block;border-bottom: 1px dotted #ddd;font-size: 10pt;}
|
||||
.form-group label{display:table}
|
||||
.form-group label>div{display:table-cell}
|
||||
.form-group label>div:nth-child(2n-1){width:30%}
|
||||
.form-group label>div:nth-child(2){width:70%;padding:5px;border:1px solid #dedede;border-radius:5px}
|
||||
.dark .form-group label>div,.dark .form-group label>div>span{border-color:#454545;color:#fff}
|
||||
.important.form-group label>div:nth-child(2),.important.form-group label>div>span{border-color:red}
|
||||
.form-group label span small{margin-left: 2px;display:block;font-weight: 600;}
|
||||
.form-group-group .round-left{border-radius: 50px 0 0 50px;margin-left:10px}
|
||||
.form-group-group blockquote:before,.form-group-group blockquote:after{display:none!important}
|
||||
.form-group-group blockquote{letter-spacing:normal;font-style:normal}
|
||||
.form-group-group blockquote p:empty{display:none}
|
||||
.form-group-group blockquote p{font-size:inherit}
|
||||
.form-group-group blockquote p:last-child{margin-bottom:0}
|
||||
.form-group-group-group>div,.form-group-group-group .h_us_advanced>div{margin-bottom:15px;}
|
||||
.form-group-group{padding:0 10px 10px 10px;overflow:hidden;margin-bottom:15px;border-radius:5px;border:1px solid #ddd;background:#fff}
|
||||
.form-group-group table{width:100%}
|
||||
.form-group-group table tr td{padding:10px 5px}
|
||||
.form-group-group table tr:not(:last-child) td{border-bottom:1px dotted #eee}
|
||||
.form-group-group .mdl-list__item{border-bottom:1px solid #eee;}
|
||||
.form-group-group .mdl-list__item:hover{background:#e6e6e6;border-radius:4px;}
|
||||
.dark .form-group-group .mdl-list__item{color:#fff;border-bottom:1px solid #444;}
|
||||
.dark .form-group-group .mdl-list__item:hover{background:#555;}
|
||||
.form-group-group:last-child,.form-group-group > .form-group:last-child{margin-bottom:0}
|
||||
.form-group-group h4{margin:0 -10px 15px -10px;padding:15px;background:#ddd;}
|
||||
.form-group-group h4 small{color:#fff;}
|
||||
.form-group-group.red{border-color:#d9534f}
|
||||
.form-group-group.red h4{background:#d9534f;color:#fff}
|
||||
.form-group-group.purple{border-color:#3f51b5}
|
||||
.form-group-group.purple h4{background:#3f51b5;color:#fff}
|
||||
.form-group-group.blue{border-color:#337ab7}
|
||||
.form-group-group.blue h4{background:#337ab7;color:#fff}
|
||||
.form-group-group.navy{border-color:#31708f}
|
||||
.form-group-group.navy h4{background:#31708f;color:#fff}
|
||||
.form-group-group.green{border-color:#449d44}
|
||||
.form-group-group.green h4{background:#449d44;color:#fff}
|
||||
.form-group-group.forestgreen{border-color:#1e4046}
|
||||
.form-group-group.forestgreen h4{background:#1e4046;color:#fff}
|
||||
.form-group-group.orange{border-color:#c49a68}
|
||||
.form-group-group.orange h4{background:#c49a68;color:#fff}
|
||||
.form-group-group.grey{border-color:#777}
|
||||
.form-group-group.grey h4{background:#777;color:#fff}
|
||||
.dark .form-group-group{background:#222}
|
||||
.videos_list .title{font-size:12pt;padding:0 10px}
|
||||
[status="1"] .btn[video="launch"],[data-status="1"] .btn[video="launch"]{background:#337ab7;border-color:#337ab7}
|
||||
[status="2"] .btn[launch="video"],[status="2"] .btn[video="launch"],[data-status="2"] .btn[video="launch"]{background:#a59100;border-color:#a59100}
|
||||
.signal.red{background:#c9302c}
|
||||
.signal.green{background:#5cb85c}
|
||||
.demo-drawer{background:#2b2a2a;color:#fff;}
|
||||
.demo-drawer.mdl-layout__drawer .mdl-navigation{padding-top:0;}
|
||||
.demo-drawer::-webkit-scrollbar{display:none;}
|
||||
.small-square-img{height:40px;width:40px;border-radius:5px}
|
||||
|
||||
.side-menu .monitor_block{padding:0;position:relative}
|
||||
.side-menu .monitor_block img{width:100%;height:75px;cursor:pointer;border: 0.5px inset #263238;}
|
||||
@media screen and (max-width:1025px){
|
||||
.side-menu .monitor_block img{height:175px;}
|
||||
}
|
||||
.side-menu .monitor_block:hover .icons{opacity:1}
|
||||
.side-menu .monitor_block:hover .title{opacity:1}
|
||||
.side-menu .monitor_block .icons,.side-menu .monitor_block .title{opacity:0;width:100%;bottom:0;left:0;background:rgba(0,0,0,0.6);position:absolute;padding:2.5px;z-index:11;cursor:move}
|
||||
.side-menu .monitor_block .title{bottom:auto;top:0;color:#fff}
|
||||
.nav-xs.side-menu .monitor_block{width:100%}
|
||||
.side-menu .monitor_block .list-data{display:none}
|
||||
.output_data:empty{display:none}
|
||||
.output_data{max-height:500px;font-family:monospace;padding:10px;border-radius:5px;background:#f3f3f3;overflow:auto}
|
||||
.dark .output_data{background:#222;}
|
||||
#probe .output_data div>div{margin-left:10px}
|
||||
.side-menu.list-blocks .monitor_block .icons,.side-menu.list-blocks .monitor_block .title{position:inherit;opacity:1;background:none}
|
||||
.side-menu.list-blocks .monitor_block .title{padding:5px;border-radius:5px;background:#222;}
|
||||
.side-menu.list-blocks .monitor_block:not(:last-child){border-bottom: 1px solid #54502d;}
|
||||
.side-menu.list-blocks .monitor_block:first-child{border-top: 1px solid #54502d;}
|
||||
.side-menu.list-blocks .monitor_block{float:none;width:100%;padding: 10px}
|
||||
.side-menu.list-blocks .monitor_block.ui-sortable-helper{background:rgba(0,0,0,0.6);border-radius:5px;padding:5px;border:0}
|
||||
.side-menu.list-blocks .monitor_block .list-data{display:block}
|
||||
.side-menu.list-blocks .monitor_block img{width:60px;height:60px;cursor:pointer;display:inline-block;margin-right:10px;border-radius:50%;vertical-align:top;border:0}
|
||||
.nav-xs.side-menu.list-blocks .monitor_block img{width:40px;height:40px;}
|
||||
.side-menu.list-blocks .monitor_block .box{width:calc(100% - 70px);display:inline-block}
|
||||
.nav-xs.side-menu.list-blocks .monitor_block .list-data{display:none}
|
||||
#monitors_list .monitor_block{transition:none}
|
||||
.dropdown-menu.scrollable{max-height:300px}
|
||||
.upload_file input{display:none}
|
||||
#video_preview .stream-objects{right:0;margin:auto;display:inline-block;position:relative;width:auto}
|
||||
.stream-block,.stream-objects{overflow: hidden!important}
|
||||
.stream-objects{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10}
|
||||
.stream-objects .tag{position:absolute;bottom:100%;left:0;background:red;color:#fff;font-family:monospace;font-size:80%;border-radius:5px 5px 0 0 ;padding:3px 5px;}
|
||||
.stream-objects .stream-detected-object{position:absolute;top:0;left:0;border:3px solid red;background:transparent;border-radius:5px}
|
||||
.stream-objects .stream-detected-point{position:absolute;top:0;left:0;border:3px solid yellow;background:transparent;border-radius:5px}
|
||||
.stream-objects .point{position:absolute;top:0;left:0;border:3px solid red;border-radius:50%}
|
||||
|
||||
|
||||
#side_menu_right.nav-xs{width:0!important;overflow:hidden}
|
||||
.side-menu table{color:#fff;}
|
||||
#main_canvas{background:#333;color:#fff;padding-top:0}
|
||||
#main_header{background:#222;color:#fff;}
|
||||
#logs_modal table tr td:first-child{width:10%}
|
||||
[class_toggle]{cursor:pointer}
|
||||
|
||||
.form-group label{width:100%}
|
||||
|
||||
#pvideo_viewer iframe{border:0;width:100%;height:350px;margin-bottom:10px;overflow:hidden}
|
||||
#pvideo_viewer video{max-height:300px;max-width:100%;}
|
||||
#pvideo_viewer .holder{height:300px;}
|
||||
#pvideo_viewer h3{margin-top:0}
|
||||
#pvideo_viewer .progressBar{position:relative;}
|
||||
#pvideo_viewer .bufferBar{position:absolute;left:0;top:0;opacity:0.4}
|
||||
#pvideo_viewer .timeBar{position:relative;z-index: 222;background:transparent}
|
||||
#pvideo_viewer h3{font-family:monospace}
|
||||
|
||||
#vis_monitors{overflow:auto;max-height:400px}
|
||||
#vis_monitors .btn-group-vertical{width:100%}
|
||||
|
||||
/*timeline*/
|
||||
#timelapse_video_line{overflow-y:scroll;overflow-x:hidden;max-height:400px;margin:0;text-align:left}
|
||||
#timelapse_video_display .videoBefore,#timelapse_video_display .videoAfter{display:none}
|
||||
.timelapse_video:not(:last-child){border-bottom:1px solid #444;}
|
||||
.timelapse_video .frame{width:50px;height:50px;background-size:cover;background-position:center;border-radius:5px}
|
||||
.timelapse_video>div>div:not(:last-child){padding-right:10px}
|
||||
.timelapse_video .flex-block:not(:last-child){padding-bottom:10px}
|
||||
.timelapse_video.list-group-item{padding:10px}
|
||||
.timelapse_hud{position: relative;background:#000}
|
||||
.timelapse_hud .timelapse_playRate{position: absolute;font-family: monospace;top:10px;right:0;left:0;margin:auto;font-size:23px}
|
||||
#timelapse .progress-bar{transition:0.5s!important}
|
||||
.timelapse_hud .controlBar{position: absolute;background:rgba(22,22,22,0.8);width:100%;left:0;bottom:0;}
|
||||
.timelapse_hud .hover-hide{opacity:0}
|
||||
.timelapse_hud:hover .hover-hide{opacity:1;z-index:5}
|
||||
.video_grid{overflow: auto;height: 100%;display: block;}
|
||||
.video_grid .col-md-2{padding-left:5px;padding-right:5px;padding-bottom:10px}
|
||||
.video_grid .thumb{width:100%;height:150px;display:inline-block;background-size:cover;position:relative;overflow:hidden;border-radius:4px;border:1px solid #000;box-shadow:0 0 10px #151515}
|
||||
.video_grid .thumb .title-strip, .video_grid .thumb .button-strip{width:100%;position:absolute;left:0;background:rgba(0,0,0,0.7);color:#fff;padding:4px}
|
||||
.video_grid .thumb .title-strip{top:0;opacity:0.5}
|
||||
.video_grid .thumb .button-strip{bottom:0;opacity:0}
|
||||
.video_grid .thumb:hover .title-strip, .video_grid .thumb:hover .button-strip{opacity:1}
|
||||
|
||||
.table-striped>tbody>tr>td{vertical-align:middle}
|
||||
.table-striped .thumbnail{width:100px;height:80px;border-radius:5px;margin:0;display:inline-block;}
|
||||
#motion_list{height:155px;overflow:auto;border-radius:5px;border:1px solid #444;position:relative;background: #222;margin:0}
|
||||
.dark .list-group-item{border-color: #444;background:#222}
|
||||
.dark .list-group-item.active{background:#c49a68;border-color:#a7865f}
|
||||
.novideos{text-transform: uppercase;text-align: center;border-bottom:0!important;padding-top: 55%!important;letter-spacing:2px}
|
||||
|
||||
.btn-warning {
|
||||
color: #fff;
|
||||
background-color: #c49a68;
|
||||
border-color: #c49a68;
|
||||
}
|
||||
|
||||
.dark.modal .modal-header,.dark.modal .modal-footer{background:#333;border-color:#444;}
|
||||
.dark.modal .modal-header{color:#fff;}
|
||||
.dark.modal .modal-footer>*:not(.btn){color:#fff;}
|
||||
.dark.modal .modal-body{background:#333;}
|
||||
.dark.modal .close{color:#fff;}
|
||||
.dark.modal{color:#fff;}
|
||||
.dark .table-striped>tbody>tr:nth-of-type(even){background:#616161}
|
||||
.dark .table-striped>tbody>tr>td{border-color:#222;color:#fff}
|
||||
.dark .table-striped>thead>tr>th{border-color:#222;color:#fff;background:#616161;vertical-align:middle;}
|
||||
.dark .table-striped>tbody>tr:nth-of-type(odd){background-color: #4c4747;}
|
||||
.dark .table>tbody>tr.active>td{background:inherit;border:0}
|
||||
.dark code{color: #c49a68;background-color: #36333d;}
|
||||
.dark a:not(.btn){color: #c49a68;}
|
||||
.follow-list ul{padding:0;margin:0;font-family:"Roboto","Helvetica","Arial",sans-serif;}
|
||||
.follow-list ul a:not(.btn){color:#fff}
|
||||
.os_bars{width:600px;display:inline-block;padding:5px 0 0 10px}
|
||||
@media screen and (max-width: 600px){
|
||||
.os_bars{width:200px;}
|
||||
.os_bars label{padding:2.5px 0;margin:0;font-size:8pt}
|
||||
}
|
||||
.os_bars .display-table .display-table-cell{padding:5px;vertical-align:center;width:33%}
|
||||
.progress{height:5px;margin:0;}
|
||||
.os_bars label,.os_bars .percent{padding:2.5px 0;margin:0;font-size:7.5pt}
|
||||
.ui-pnotify-hide .ui-pnotify{display:none!important}
|
||||
/*cool dropdown thing*/
|
||||
ul.msg_list li {
|
||||
background: #f7f7f7;color:#333;
|
||||
padding: 5px;
|
||||
display: list-item;
|
||||
margin: 6px 6px 0;
|
||||
width: 96% !important
|
||||
}
|
||||
|
||||
ul.msg_list li div{display:block}
|
||||
|
||||
ul.msg_list li:last-child {
|
||||
margin-bottom: 6px;
|
||||
padding: 10px
|
||||
}
|
||||
|
||||
ul.msg_list li a {
|
||||
padding: 3px 5px !important
|
||||
}
|
||||
ul.msg_list li .progress {
|
||||
height:5px;margin:10px 0 0 0;
|
||||
}
|
||||
|
||||
ul.msg_list li .image img {
|
||||
border-radius: 2px 2px 2px 2px;
|
||||
-webkit-border-radius: 2px 2px 2px 2px;
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
width: 11%
|
||||
}
|
||||
|
||||
ul.msg_list li .time {
|
||||
font-size: 11px;
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
ul.msg_list li .message {
|
||||
display: block !important;
|
||||
font-size: 11px
|
||||
}
|
||||
|
||||
.dropdown-menu.msg_list span {
|
||||
white-space: normal
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
border: medium none;
|
||||
box-shadow: none;
|
||||
display: none;
|
||||
float: left;
|
||||
font-size: 12px;
|
||||
left: 0;
|
||||
list-style: none outside none;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
text-shadow: none;
|
||||
top: 100%;
|
||||
z-index: 9998;
|
||||
border: 1px solid #D9DEE4;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0
|
||||
}
|
||||
|
||||
.dropdown-menu>li>a {
|
||||
color: #5A738E
|
||||
}
|
||||
|
||||
.navbar-nav .open .dropdown-menu {
|
||||
position: absolute;
|
||||
background: #fff;
|
||||
margin-top: 0;
|
||||
border: 1px solid #D9DEE4;
|
||||
-webkit-box-shadow: none;
|
||||
right: 0;
|
||||
left: auto;
|
||||
width: 220px
|
||||
}
|
||||
.is-small-screen .nav>li{display:inline-block}
|
||||
.navbar-nav .open .dropdown-menu li a{padding:7px 15px}
|
||||
.navbar-nav .open .dropdown-menu.msg_list {
|
||||
width: 300px
|
||||
}
|
||||
.nav>li>a{color:#fff}
|
||||
.nav>li>a:focus, .nav>li>a:hover,.nav .open>a, .nav .open>a:focus, .nav .open>a:hover{background:#867560}
|
||||
|
||||
.mdl-js-layout.hide-side:not(.is-small-screen){
|
||||
|
||||
}
|
||||
@media screen and (min-width: 1025px){
|
||||
.mdl-js-layout.hide-side:not(.is-small-screen)>.mdl-layout__drawer {
|
||||
width: 0px;transition:0.2s
|
||||
}
|
||||
}
|
||||
.mdl-js-layout.hide-side:not(.is-small-screen) .mdl-layout__header .mdl-layout__drawer-button{
|
||||
display:none;
|
||||
}
|
||||
.mdl-js-layout.hide-side:not(.is-small-screen)>.mdl-layout__content{
|
||||
margin-left: 0px;transition:0.2s
|
||||
}
|
||||
.mdl-js-layout.hide-side:not(.is-small-screen)>.mdl-layout__header{
|
||||
margin-left: 0px;width:100%;transition:0.2s
|
||||
}
|
||||
/*Control Pad*/
|
||||
.PTZ_controls {
|
||||
z-index: 111;
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
top: 20px;
|
||||
margin:0;
|
||||
display: inline-block;
|
||||
width: 120px;
|
||||
}
|
||||
.PTZ_controls .btn-group{margin-top:10px}
|
||||
.PTZ_controls .pad {
|
||||
position: relative;
|
||||
height: 120px;
|
||||
width: 120px;
|
||||
background: #b7b7b7;
|
||||
border-radius: 50%;
|
||||
box-shadow: inset 0 0 1px rgba(120, 120, 120, 0.6), inset 0 2px 2px rgba(0, 0, 0, 0.1), 0 2px 2px rgba(240, 240, 240, 0.4);
|
||||
}
|
||||
.PTZ_controls .control {
|
||||
position: absolute;
|
||||
}
|
||||
.PTZ_controls .pad .control {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
background: #636363;
|
||||
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.6), 0 0 0 3px rgba(60, 60, 60, 0.2), 0 0 0 4px rgba(60, 60, 60, 0.2);
|
||||
border-radius: 2px;
|
||||
}
|
||||
.PTZ_controls .zoom_in{
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
.PTZ_controls .zoom_out{
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
.PTZ_controls .nv_enabled{
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
.PTZ_controls .nv_disable{
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
.PTZ_controls .pad .top {
|
||||
top: 15px;
|
||||
left: 50%;
|
||||
margin: 0 0 0 -15px;
|
||||
}
|
||||
.PTZ_controls .pad .left {
|
||||
top: 45px;
|
||||
left: 15px;
|
||||
}
|
||||
.PTZ_controls .pad .right {
|
||||
top: 45px;
|
||||
right: 15px;
|
||||
}
|
||||
.PTZ_controls .pad .control.right:before {
|
||||
transform: rotate(90deg) translate(-3px, -5px);
|
||||
}
|
||||
.PTZ_controls .pad .bottom {
|
||||
bottom: 15px;
|
||||
left: 50%;
|
||||
margin: 0 0 0 -15px;
|
||||
}
|
||||
/* Overlap the other controls to hide box-shadow */
|
||||
.PTZ_controls .pad .middle {
|
||||
height: 34px;
|
||||
width: 34px;
|
||||
z-index: 5;
|
||||
top: 43px;
|
||||
left: 50%;
|
||||
margin: 0 0 0 -17px;
|
||||
box-shadow: none;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.PTZ_controls .pad .middle:after {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -35% 0 0 -35%;
|
||||
content: '';
|
||||
background: #636363;
|
||||
height: 70%;
|
||||
width: 70%;
|
||||
border-radius: 100%;
|
||||
box-shadow: inset 0 0 2px rgba(120, 120, 120, 0.6), inset 0 2px 8px rgba(0, 0, 0, 0.1), 0 2px 2px rgba(240, 240, 240, 0.2);
|
||||
}
|
||||
/*Digital Zoom*/
|
||||
.stream-block{
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
}
|
||||
.zoomGlass {
|
||||
overflow: hidden;
|
||||
transition: none;
|
||||
width: 175px; height: 175px;
|
||||
position: absolute;
|
||||
border-radius: 15px;
|
||||
border: 3px solid #ddd;
|
||||
z-index:9999;
|
||||
}
|
||||
.zoomGlass iframe,.zoomGlass canvas{position:absolute;transition: none;}
|
||||
.zoomGlass .hoverShade{position:absolute;width:100%;height:100%}
|
||||
|
||||
.dark.form-control,.dark .form-control {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 35px;
|
||||
padding: 6px 12px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: #eee;
|
||||
background-color: #36333d;
|
||||
background-image: none;
|
||||
border: 1px solid #444;
|
||||
border-radius: 4px;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
|
||||
}
|
||||
|
||||
.dark.form-control:focus,.dark .form-control:focus {
|
||||
color: #ddd;
|
||||
background-color: #333;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
||||
/*** custom checkboxes ***/
|
||||
|
||||
.checkbox input[type=checkbox] { display:none; } /* to hide the checkbox itself */
|
||||
.checkbox input[type=checkbox] + label:before {
|
||||
font-family: FontAwesome;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.checkbox input[type=checkbox] + label:before { content: "\f096"; } /* unchecked icon */
|
||||
.checkbox input[type=checkbox] + label:before { letter-spacing: 10px; } /* space between checkbox and label */
|
||||
|
||||
.checkbox input[type=checkbox]:checked + label:before { content: "\f046"; } /* checked icon */
|
||||
.checkbox input[type=checkbox]:checked + label:before { letter-spacing: 5px; } /* allow space for check mark */
|
||||
|
||||
/*Clock*/
|
||||
#time-date {font-size:12px; text-align:center;}
|
||||
@media screen and (min-width:1025px){
|
||||
#clock {padding-right:35px}
|
||||
}
|
||||
#clock ul { width:150px; margin:0 auto; padding:0px; list-style:none; text-align:center; }
|
||||
#clock ul li { display:inline; font-size:1.6em; text-align:center;font-family:monospace;}
|
||||
|
||||
#clock .point { position:relative; -moz-animation:mymove 1s ease infinite; -webkit-animation:mymove 1s ease infinite; }
|
||||
|
||||
/*custom vis.js css*/
|
||||
.vis-timeline{background:#212121;color:#fff;border-color:#444}
|
||||
.vis-time-axis .vis-text{color: #dedede}
|
||||
.vis-item.vis-range .vis-item-content{background:#333;color:#fff}
|
||||
.vis-time-axis .vis-grid.vis-minor{border-color:#444}
|
||||
|
||||
|
||||
@-moz-document url-prefix() {
|
||||
.full.modal .modal-body, .medium.modal .modal-body {
|
||||
height:70%
|
||||
}
|
||||
}
|
||||
|
||||
/*animations*/
|
||||
@keyframes blink {
|
||||
0% { opacity:1 }
|
||||
50% { opacity:0 }
|
||||
100% { opacity:1 }
|
||||
}
|
||||
@-webkit-keyframes blink {
|
||||
0% { opacity:1 }
|
||||
50% { opacity:0 }
|
||||
100% { opacity:1 }
|
||||
}
|
||||
.blink,[mode="Record"] .lamp {
|
||||
-webkit-animation: blink 1s linear infinite;
|
||||
-moz-animation: blink 1s linear infinite;
|
||||
animation: blink 1s linear infinite;
|
||||
}
|
||||
.mdl-menu__item>div{display:flex;align-items: center;width:100%}
|
||||
.mdl-menu__item>div>i{margin-right:5px}
|
||||
/*For languages that are right to left*/
|
||||
.right-to-left {text-align:right}
|
||||
.right-to-left select{direction: rtl;}
|
||||
.right-to-left input,.right-to-left textarea{direction: rtl;text-align:right}
|
||||
.right-to-left .form-group label span{padding-right:10px}
|
||||
.right-to-left .modal-footer{text-align:left}
|
||||
.right-to-left .mdl-menu__item>div>*{flex:1}
|
||||
.right-to-left .mdl-menu__item>div>i{margin-right:0;margin-left:5px}
|
||||
.right-to-left .mdl-menu__item{text-align:right}
|
||||
.right-to-left .mdl-menu__item i{float:right}
|
||||
.right-to-left .pull-right,.right-to-left .close{float:left!important}
|
||||
.right-to-left .pull-left,.right-to-left .mdl-menu__item span{float:right!important}
|
||||
/* All-CSS Toggle Switch (Checkbox Hack) by Marcus Burnette - https://codepen.io/mburnette/pen/LxNxNg */
|
||||
.marc-toggle {
|
||||
width: 50px;
|
||||
height: 25px;
|
||||
}
|
||||
.marc-toggle.abs-bot-left {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
}
|
||||
.marc-toggle.abs-bot-right {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
.marc-toggle input[type=checkbox]{
|
||||
height: 0;
|
||||
width: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.marc-toggle label {
|
||||
cursor: pointer;
|
||||
text-indent: -9999px;
|
||||
width: 100px;
|
||||
height: 20px;
|
||||
background: grey;
|
||||
display: block;
|
||||
border-radius: 100px;
|
||||
margin-bottom: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.marc-toggle label:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: #fff;
|
||||
border-radius: 90px;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.marc-toggle input:checked + label {
|
||||
background: #00118c;
|
||||
}
|
||||
|
||||
.marc-toggle input:checked + label:after {
|
||||
left: calc(100% - 5px);
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
.marc-toggle label:active:after {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
/*hexagon pattern*/
|
||||
.bg-hexagon {
|
||||
background-color: #054e9f;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='49' viewBox='0 0 28 49'%3E%3Cg fill-rule='evenodd'%3E%3Cg id='hexagons' fill='%23fdfdfd' fill-opacity='0.4' fill-rule='nonzero'%3E%3Cpath d='M13.99 9.25l13 7.5v15l-13 7.5L1 31.75v-15l12.99-7.5zM3 17.9v12.7l10.99 6.34 11-6.35V17.9l-11-6.34L3 17.9zM0 15l12.98-7.5V0h-2v6.35L0 12.69v2.3zm0 18.5L12.98 41v8h-2v-6.85L0 35.81v-2.3zM15 0v7.5L27.99 15H28v-2.31h-.01L17 6.35V0h-2zm0 49v-8l12.99-7.5H28v2.31h-.01L17 42.15V49h-2z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
|
||||
}
|
1
web/libs/css/material-design-iconic-font.min.css
vendored
Normal file
1
web/libs/css/material-design-iconic-font.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -608,15 +608,6 @@ _:-ms-input-placeholder, :root .demo-graph {
|
|||
opacity: 0.46;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.social-btn__twitter {
|
||||
background-image: url('https://www.gstatic.com/images/icons/material/system/2x/post_twitter_black_24dp.png');
|
||||
}
|
||||
.social-btn__blogger {
|
||||
background-image: url('https://www.gstatic.com/images/icons/material/system/2x/post_facebook_black_24dp.png');
|
||||
}
|
||||
.social-btn__gplus {
|
||||
background-image: url('https://www.gstatic.com/images/icons/material/system/2x/post_gplus_black_24dp.png');
|
||||
}
|
||||
.social-btn__share {
|
||||
color: rgba(0, 0, 0, 0.54);
|
||||
background: transparent;
|
||||
|
|
48
web/libs/css/montserrat.css
Normal file
48
web/libs/css/montserrat.css
Normal file
|
@ -0,0 +1,48 @@
|
|||
/* Generated by Font Squirrel (http://www.fontsquirrel.com) on February 2, 2016 */
|
||||
/* Downloaded from https://github.com/Antpolis/Montserrat-Font */
|
||||
|
||||
|
||||
@font-face {
|
||||
font-family: 'montserratbold';
|
||||
src: url('../fonts/montserrat-bold.eot');
|
||||
src: url('../fonts/montserrat-bold.eot?#iefix') format('embedded-opentype'),
|
||||
url('../fonts/montserrat-bold.woff2') format('woff2'),
|
||||
url('../fonts/montserrat-bold.woff') format('woff'),
|
||||
url('../fonts/montserrat-bold.ttf') format('truetype'),
|
||||
url('../fonts/montserrat-bold.svg#montserratbold') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@font-face {
|
||||
font-family: 'montserratlight';
|
||||
src: url('../fonts/montserrat-light.eot');
|
||||
src: url('../fonts/montserrat-light.eot?#iefix') format('embedded-opentype'),
|
||||
url('../fonts/montserrat-light.woff2') format('woff2'),
|
||||
url('../fonts/montserrat-light.woff') format('woff'),
|
||||
url('../fonts/montserrat-light.ttf') format('truetype'),
|
||||
url('../fonts/montserrat-light.svg#montserratlight') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@font-face {
|
||||
font-family: 'montserratregular';
|
||||
src: url('../fonts/montserrat-regular.eot');
|
||||
src: url('../fonts/montserrat-regular.eot?#iefix') format('embedded-opentype'),
|
||||
url('../fonts/montserrat-regular.woff2') format('woff2'),
|
||||
url('../fonts/montserrat-regular.woff') format('woff'),
|
||||
url('../fonts/montserrat-regular.ttf') format('truetype'),
|
||||
url('../fonts/montserrat-regular.svg#montserratregular') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
|
||||
}
|
|
@ -1,246 +0,0 @@
|
|||
/* bug: pseudo selectors for :fullscreen, :-moz-full-screen, and :-webkit-full-screen cannot be grouped together */
|
||||
|
||||
/* styles for container div */
|
||||
|
||||
div.mse-container {
|
||||
position: relative;
|
||||
float: left;
|
||||
display: inline-block;
|
||||
background: linear-gradient(black, grey, black);
|
||||
border: 1px solid white;
|
||||
}
|
||||
|
||||
div.mse-container:fullscreen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
div.mse-container:-ms-fullscreen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
div.mse-container:-moz-full-screen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
div.mse-container:-webkit-full-screen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
div.mse-container.disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
/* styles for video */
|
||||
|
||||
video.mse-video {
|
||||
pointer-events: none;
|
||||
cursor: none;
|
||||
}
|
||||
|
||||
div.mse-container:fullscreen video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
div.mse-container:-ms-fullscreen video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
div.mse-container:-moz-full-screen video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
div.mse-container:-webkit-full-screen video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* styles for controls div */
|
||||
|
||||
div.mse-controls {
|
||||
position: absolute;
|
||||
background: black;
|
||||
border: 1px solid white;
|
||||
left: 5px;
|
||||
right: 5px;
|
||||
bottom: 5px;
|
||||
padding: 3px;
|
||||
border-radius: 5px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
div.mse-controls:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
div.mse-container:fullscreen div {
|
||||
border: 2px solid white;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
padding: 6px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
div.mse-container:-ms-fullscreen div {
|
||||
border: 2px solid white;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
padding: 6px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
div.mse-container:-moz-full-screen div {
|
||||
border: 2px solid white;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
padding: 6px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
div.mse-container:-webkit-full-screen div {
|
||||
border: 2px solid white;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
padding: 6px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* styles for buttons */
|
||||
|
||||
div.mse-container button {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
opacity: 0.8;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
div.mse-container button:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
div.mse-container button:active {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
div.mse-container:fullscreen button {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
div.mse-container:-ms-fullscreen button {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
div.mse-container:-moz-full-screen button {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
div.mse-container:-webkit-full-screen button {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
button.mse-start, button.mse-stop, button.mse-snapshot {
|
||||
float: left;
|
||||
}
|
||||
|
||||
button.mse-fullscreen, button.mse-cycle {
|
||||
float: right;
|
||||
}
|
||||
|
||||
button.mse-start:before {
|
||||
font-family: FontAwesome;
|
||||
content: "\f04b";
|
||||
}
|
||||
|
||||
button.mse-stop:before {
|
||||
font-family: FontAwesome;
|
||||
content: "\f04d";
|
||||
}
|
||||
|
||||
button.mse-snapshot:before {
|
||||
font-family: FontAwesome;
|
||||
content: "\f030";
|
||||
}
|
||||
|
||||
button.mse-fullscreen:before {
|
||||
font-family: FontAwesome;
|
||||
content: "\f065";
|
||||
}
|
||||
|
||||
button.mse-stop.cycling {
|
||||
pointer-events: none;
|
||||
cursor: none;
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
div.mse-container:fullscreen button.mse-fullscreen:before {
|
||||
font-family: FontAwesome;
|
||||
content: "\f066";
|
||||
}
|
||||
|
||||
div.mse-container:-ms-fullscreen button.mse-fullscreen:before {
|
||||
font-family: FontAwesome;
|
||||
content: "\f066";
|
||||
}
|
||||
|
||||
div.mse-container:-moz-full-screen button.mse-fullscreen:before {
|
||||
font-family: FontAwesome;
|
||||
content: "\f066";
|
||||
}
|
||||
|
||||
div.mse-container:-webkit-full-screen button.mse-fullscreen:before {
|
||||
font-family: FontAwesome;
|
||||
content: "\f066";
|
||||
}
|
||||
|
||||
button.mse-cycle:before {
|
||||
font-family: FontAwesome;
|
||||
content: "\f021";/* other cycle icon options : f021 f110 f01e f1ce */
|
||||
}
|
||||
|
||||
button.mse-cycle.animated {
|
||||
-webkit-animation: spin 5s linear infinite;
|
||||
animation: spin 5s linear infinite;
|
||||
}
|
||||
|
||||
/* SPIN animation for cycle button */
|
||||
|
||||
@-webkit-keyframes spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: rotate(359deg);
|
||||
transform: rotate(359deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
-ms-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: rotate(359deg);
|
||||
-ms-transform: rotate(359deg);
|
||||
transform: rotate(359deg);
|
||||
}
|
||||
}
|
106
web/libs/css/roboto.css
Normal file
106
web/libs/css/roboto.css
Normal file
|
@ -0,0 +1,106 @@
|
|||
/* BEGIN Thin */
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url("../fonts/Thin/Roboto-Thin.woff2?v=2.137") format("woff2"), url("../fonts/Thin/Roboto-Thin.woff?v=2.137") format("woff");
|
||||
font-weight: 100;
|
||||
font-style: normal; }
|
||||
/* END Thin */
|
||||
/* BEGIN Thin Italic */
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url("../fonts/ThinItalic/Roboto-ThinItalic.woff2?v=2.137") format("woff2"), url("../fonts/ThinItalic/Roboto-ThinItalic.woff?v=2.137") format("woff");
|
||||
font-weight: 100;
|
||||
font-style: italic; }
|
||||
/* END Thin Italic */
|
||||
/* BEGIN Light */
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url("../fonts/Light/Roboto-Light.woff2?v=2.137") format("woff2"), url("../fonts/Light/Roboto-Light.woff?v=2.137") format("woff");
|
||||
font-weight: 300;
|
||||
font-style: normal; }
|
||||
/* END Light */
|
||||
/* BEGIN Light Italic */
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url("../fonts/LightItalic/Roboto-LightItalic.woff2?v=2.137") format("woff2"), url("../fonts/LightItalic/Roboto-LightItalic.woff?v=2.137") format("woff");
|
||||
font-weight: 300;
|
||||
font-style: italic; }
|
||||
/* END Light Italic */
|
||||
/* BEGIN Regular */
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url("../fonts/Regular/Roboto-Regular.woff2?v=2.137") format("woff2"), url("../fonts/Regular/Roboto-Regular.woff?v=2.137") format("woff");
|
||||
font-weight: 400;
|
||||
font-style: normal; }
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url("../fonts/Regular/Roboto-Regular.woff2?v=2.137") format("woff2"), url("../fonts/Regular/Roboto-Regular.woff?v=2.137") format("woff");
|
||||
font-weight: normal;
|
||||
font-style: normal; }
|
||||
/* END Regular */
|
||||
/* BEGIN Italic */
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url("../fonts/Italic/Roboto-Italic.woff2?v=2.137") format("woff2"), url("../fonts/Italic/Roboto-Italic.woff?v=2.137") format("woff");
|
||||
font-weight: 400;
|
||||
font-style: italic; }
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url("../fonts/Italic/Roboto-Italic.woff2?v=2.137") format("woff2"), url("../fonts/Italic/Roboto-Italic.woff?v=2.137") format("woff");
|
||||
font-weight: normal;
|
||||
font-style: italic; }
|
||||
/* END Italic */
|
||||
/* BEGIN Medium */
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url("../fonts/Medium/Roboto-Medium.woff2?v=2.137") format("woff2"), url("../fonts/Medium/Roboto-Medium.woff?v=2.137") format("woff");
|
||||
font-weight: 500;
|
||||
font-style: normal; }
|
||||
/* END Medium */
|
||||
/* BEGIN Medium Italic */
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url("../fonts/MediumItalic/Roboto-MediumItalic.woff2?v=2.137") format("woff2"), url("../fonts/MediumItalic/Roboto-MediumItalic.woff?v=2.137") format("woff");
|
||||
font-weight: 500;
|
||||
font-style: italic; }
|
||||
/* END Medium Italic */
|
||||
/* BEGIN Bold */
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url("../fonts/Bold/Roboto-Bold.woff2?v=2.137") format("woff2"), url("../fonts/Bold/Roboto-Bold.woff?v=2.137") format("woff");
|
||||
font-weight: 700;
|
||||
font-style: normal; }
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url("../fonts/Bold/Roboto-Bold.woff2?v=2.137") format("woff2"), url("../fonts/Bold/Roboto-Bold.woff?v=2.137") format("woff");
|
||||
font-weight: bold;
|
||||
font-style: normal; }
|
||||
/* END Bold */
|
||||
/* BEGIN Bold Italic */
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url("../fonts/BoldItalic/Roboto-BoldItalic.woff2?v=2.137") format("woff2"), url("../fonts/BoldItalic/Roboto-BoldItalic.woff?v=2.137") format("woff");
|
||||
font-weight: 700;
|
||||
font-style: italic; }
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url("../fonts/BoldItalic/Roboto-BoldItalic.woff2?v=2.137") format("woff2"), url("../fonts/BoldItalic/Roboto-BoldItalic.woff?v=2.137") format("woff");
|
||||
font-weight: bold;
|
||||
font-style: italic; }
|
||||
/* END Bold Italic */
|
||||
/* BEGIN Black */
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url("../fonts/Black/Roboto-Black.woff2?v=2.137") format("woff2"), url("../fonts/Black/Roboto-Black.woff?v=2.137") format("woff");
|
||||
font-weight: 900;
|
||||
font-style: normal; }
|
||||
/* END Black */
|
||||
/* BEGIN Black Italic */
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url("../fonts/BlackItalic/Roboto-BlackItalic.woff2?v=2.137") format("woff2"), url("../fonts/BlackItalic/Roboto-BlackItalic.woff?v=2.137") format("woff");
|
||||
font-weight: 900;
|
||||
font-style: italic; }
|
||||
/* END Black Italic */
|
||||
|
||||
/*# sourceMappingURL=roboto.css.map */
|
7
web/libs/css/roboto.css.map
Normal file
7
web/libs/css/roboto.css.map
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"version": 3,
|
||||
"mappings": "AAAA,gBAAgB;AAChB,UAKC;EAJA,WAAW,EAAE,MAAM;ECDnB,GAAG,EAAE,0HAC6F;EDElG,WAAW,EAAE,GAAG;EAChB,UAAU,EAAE,MAAM;AAEnB,cAAc;AEPd,uBAAuB;AACvB,UAKC;EAJA,WAAW,EAAE,MAAM;EDDnB,GAAG,EAAE,kJAC6F;ECElG,WAAW,EAAE,GAAG;EAChB,UAAU,EAAE,MAAM;AAEnB,qBAAqB;ACPrB,iBAAiB;AACjB,UAKC;EAJA,WAAW,EAAE,MAAM;EFDnB,GAAG,EAAE,8HAC6F;EEElG,WAAW,EAAE,GAAG;EAChB,UAAU,EAAE,MAAM;AAEnB,eAAe;ACPf,wBAAwB;AACxB,UAKC;EAJA,WAAW,EAAE,MAAM;EHDnB,GAAG,EAAE,sJAC6F;EGElG,WAAW,EAAE,GAAG;EAChB,UAAU,EAAE,MAAM;AAEnB,sBAAsB;ACPtB,mBAAmB;AACnB,UAKC;EAJA,WAAW,EAAE,MAAM;EJDnB,GAAG,EAAE,sIAC6F;EIElG,WAAW,EAAE,GAAG;EAChB,UAAU,EAAE,MAAM;AAEnB,UAKC;EAJA,WAAW,EAAE,MAAM;EJPnB,GAAG,EAAE,sIAC6F;EIQlG,WAAW,EAAE,MAAM;EACnB,UAAU,EAAE,MAAM;AAEnB,iBAAiB;ACbjB,kBAAkB;AAClB,UAKC;EAJA,WAAW,EAAE,MAAM;ELDnB,GAAG,EAAE,kIAC6F;EKElG,WAAW,EAAE,GAAG;EAChB,UAAU,EAAE,MAAM;AAEnB,UAKC;EAJA,WAAW,EAAE,MAAM;ELPnB,GAAG,EAAE,kIAC6F;EKQlG,WAAW,EAAE,MAAM;EACnB,UAAU,EAAE,MAAM;AAEnB,gBAAgB;ACbhB,kBAAkB;AAClB,UAKC;EAJA,WAAW,EAAE,MAAM;ENDnB,GAAG,EAAE,kIAC6F;EMElG,WAAW,EAAE,GAAG;EAChB,UAAU,EAAE,MAAM;AAEnB,gBAAgB;ACPhB,yBAAyB;AACzB,UAKC;EAJA,WAAW,EAAE,MAAM;EPDnB,GAAG,EAAE,0JAC6F;EOElG,WAAW,EAAE,GAAG;EAChB,UAAU,EAAE,MAAM;AAEnB,uBAAuB;ACPvB,gBAAgB;AAChB,UAKC;EAJA,WAAW,EAAE,MAAM;ERDnB,GAAG,EAAE,0HAC6F;EQElG,WAAW,EAAE,GAAG;EAChB,UAAU,EAAE,MAAM;AAEnB,UAKC;EAJA,WAAW,EAAE,MAAM;ERPnB,GAAG,EAAE,0HAC6F;EQQlG,WAAW,EAAE,IAAI;EACjB,UAAU,EAAE,MAAM;AAEnB,cAAc;ACbd,uBAAuB;AACvB,UAKC;EAJA,WAAW,EAAE,MAAM;ETDnB,GAAG,EAAE,kJAC6F;ESElG,WAAW,EAAE,GAAG;EAChB,UAAU,EAAE,MAAM;AAEnB,UAKC;EAJA,WAAW,EAAE,MAAM;ETPnB,GAAG,EAAE,kJAC6F;ESQlG,WAAW,EAAE,IAAI;EACjB,UAAU,EAAE,MAAM;AAEnB,qBAAqB;ACbrB,iBAAiB;AACjB,UAKC;EAJA,WAAW,EAAE,MAAM;EVDnB,GAAG,EAAE,8HAC6F;EUElG,WAAW,EAAE,GAAG;EAChB,UAAU,EAAE,MAAM;AAEnB,eAAe;ACPf,wBAAwB;AACxB,UAKC;EAJA,WAAW,EAAE,MAAM;EXDnB,GAAG,EAAE,sJAC6F;EWElG,WAAW,EAAE,GAAG;EAChB,UAAU,EAAE,MAAM;AAEnB,sBAAsB",
|
||||
"sources": ["../sass/_Thin.scss","../sass/_mixins.scss","../sass/_ThinItalic.scss","../sass/_Light.scss","../sass/_LightItalic.scss","../sass/_Regular.scss","../sass/_Italic.scss","../sass/_Medium.scss","../sass/_MediumItalic.scss","../sass/_Bold.scss","../sass/_BoldItalic.scss","../sass/_Black.scss","../sass/_BlackItalic.scss"],
|
||||
"names": [],
|
||||
"file": "roboto.css"
|
||||
}
|
15
web/libs/css/roboto.less
Normal file
15
web/libs/css/roboto.less
Normal file
|
@ -0,0 +1,15 @@
|
|||
@import (reference) "../less/_variables";
|
||||
@import (reference) "../less/_mixins";
|
||||
|
||||
@import "../less/_Thin";
|
||||
@import "../less/_ThinItalic";
|
||||
@import "../less/_Light";
|
||||
@import "../less/_LightItalic";
|
||||
@import "../less/_Regular";
|
||||
@import "../less/_Italic";
|
||||
@import "../less/_Medium";
|
||||
@import "../less/_MediumItalic";
|
||||
@import "../less/_Bold";
|
||||
@import "../less/_BoldItalic";
|
||||
@import "../less/_Black";
|
||||
@import "../less/_BlackItalic";
|
15
web/libs/css/roboto.scss
Normal file
15
web/libs/css/roboto.scss
Normal file
|
@ -0,0 +1,15 @@
|
|||
@import "../sass/variables";
|
||||
@import "../sass/mixins";
|
||||
|
||||
@import "../sass/Thin";
|
||||
@import "../sass/ThinItalic";
|
||||
@import "../sass/Light";
|
||||
@import "../sass/LightItalic";
|
||||
@import "../sass/Regular";
|
||||
@import "../sass/Italic";
|
||||
@import "../sass/Medium";
|
||||
@import "../sass/MediumItalic";
|
||||
@import "../sass/Bold";
|
||||
@import "../sass/BoldItalic";
|
||||
@import "../sass/Black";
|
||||
@import "../sass/BlackItalic";
|
BIN
web/libs/fonts/Black/Roboto-Black.ttf
Normal file
BIN
web/libs/fonts/Black/Roboto-Black.ttf
Normal file
Binary file not shown.
BIN
web/libs/fonts/Black/Roboto-Black.woff
Normal file
BIN
web/libs/fonts/Black/Roboto-Black.woff
Normal file
Binary file not shown.
BIN
web/libs/fonts/Black/Roboto-Black.woff2
Normal file
BIN
web/libs/fonts/Black/Roboto-Black.woff2
Normal file
Binary file not shown.
BIN
web/libs/fonts/BlackItalic/Roboto-BlackItalic.ttf
Normal file
BIN
web/libs/fonts/BlackItalic/Roboto-BlackItalic.ttf
Normal file
Binary file not shown.
BIN
web/libs/fonts/BlackItalic/Roboto-BlackItalic.woff
Normal file
BIN
web/libs/fonts/BlackItalic/Roboto-BlackItalic.woff
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue