From 660266c7632f5b10c4c5f39f64f2b5ed4e2ad8ec Mon Sep 17 00:00:00 2001 From: Moe Date: Thu, 15 Mar 2018 14:48:54 -0700 Subject: [PATCH] Vah Naboris --- .dockerignore | 9 - Dockerfile | 19 - INSTALL/centos.sh | 191 +++-- INSTALL/cuda9-part1.sh | 18 + INSTALL/cuda9-part2-after-reboot.sh | 6 + INSTALL/installDatabase.js | 37 + INSTALL/macos-part2.sh | 92 +++ INSTALL/macos.sh | 101 +-- INSTALL/openalpr-gpu-easy.sh | 30 + INSTALL/opencv-cuda.sh | 29 + INSTALL/shinobi.service | 11 + INSTALL/start.sh | 1 - INSTALL/ubuntu.sh | 226 +++--- README-Docker.md | 17 - camera.js | 1013 +++++++++++++++++-------- conf.sample.json | 3 +- cron.js | 117 ++- docker-compose-nodb.yml | 5 - docker-compose.yml | 67 -- docker-entrypoint.sh | 20 - docker.env | 2 - languages/en_CA.json | 33 + package.json | 3 + sql/.gitignore | 3 +- sql/shinobi.sample.sqlite | Bin 0 -> 40960 bytes stylesheets/github-light.css | 124 --- stylesheets/normalize.css | 424 ----------- stylesheets/stylesheet.css | 245 ------ web/libs/css/main.dash2.css | 4 +- web/libs/css/poseidon.css | 246 ++++++ web/libs/js/main.dash2.js | 1083 ++++++++++++++++++++------- web/libs/js/poseidon.js | 608 +++++++++++++++ web/pages/blocks/monitoredit.ejs | 295 +++++--- web/pages/blocks/multimon.ejs | 112 ++- web/pages/embed.ejs | 76 +- web/pages/home.ejs | 7 +- 36 files changed, 3370 insertions(+), 1907 deletions(-) delete mode 100644 .dockerignore delete mode 100644 Dockerfile create mode 100644 INSTALL/cuda9-part1.sh create mode 100644 INSTALL/cuda9-part2-after-reboot.sh create mode 100644 INSTALL/installDatabase.js create mode 100644 INSTALL/macos-part2.sh create mode 100644 INSTALL/openalpr-gpu-easy.sh create mode 100644 INSTALL/opencv-cuda.sh create mode 100644 INSTALL/shinobi.service delete mode 100644 README-Docker.md delete mode 100644 docker-compose-nodb.yml delete mode 100644 docker-compose.yml delete mode 100755 docker-entrypoint.sh delete mode 100644 docker.env create mode 100644 sql/shinobi.sample.sqlite delete mode 100644 stylesheets/github-light.css delete mode 100644 stylesheets/normalize.css delete mode 100644 stylesheets/stylesheet.css create mode 100644 web/libs/css/poseidon.css create mode 100644 web/libs/js/poseidon.js diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 9173659c..00000000 --- a/.dockerignore +++ /dev/null @@ -1,9 +0,0 @@ -.git -.gitignore -.env -README.md -Dockerfile -docker-compose*.yml -node_modules -videos -dbdata diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index f176e069..00000000 --- a/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM mhart/alpine-node:8 - -WORKDIR /opt/shinobi - -# Install package dependencies -RUN apk add --update --no-cache ffmpeg python pkgconfig cairo-dev make g++ jpeg-dev - -# Install NodeJS dependencies -COPY package.json /opt/shinobi -RUN npm install && \ - npm install canvas - -# Copy code -COPY . /opt/shinobi - -VOLUME ["/opt/shinobi/videos"] - -EXPOSE 8080 -ENTRYPOINT ["/opt/shinobi/docker-entrypoint.sh" ] diff --git a/INSTALL/centos.sh b/INSTALL/centos.sh index 1acc9f12..a89f67bb 100644 --- a/INSTALL/centos.sh +++ b/INSTALL/centos.sh @@ -5,6 +5,14 @@ echo "=========================================================" echo "To answer yes type the letter (y) in lowercase and press ENTER." echo "Default is no (N). Skip any components you already have or don't need." echo "=============" +if [ ! -e "./conf.json" ]; then + cp conf.sample.json conf.json +fi +if [ ! -e "./super.json" ]; then + echo "Default Superuser : admin@shinobi.video" + echo "Default Password : admin" + cp super.sample.json super.json +fi echo "Shinobi - Run yum update" sudo yum update -y echo "Shinobi - Get dependencies" @@ -17,64 +25,83 @@ sudo yum install ffmpeg ffmpeg-devel -y echo "Shinobi - Do you want to Install Node.js?" echo "(y)es or (N)o" read nodejsinstall -if [ "$nodejsinstall" = "y" ]; then +if [ "$nodejsinstall" = "y" ] || [ "$nodejsinstall" = "Y" ]; then sudo wget https://rpm.nodesource.com/setup_8.x sudo chmod +x setup_8.x ./setup_8.x sudo yum install nodejs -y fi echo "=============" -echo "Shinobi - Do you want to Install MariaDB?" -echo "(y)es or (N)o" -read mysqlagree -if [ "$mysqlagree" = "y" ]; then - sudo yum install mariadb mariadb-server -y - #Start mysql and enable on boot - sudo systemctl start mariadb - sudo systemctl enable mariadb - #Run mysql install - sudo mysql_secure_installation -fi -echo "=============" -echo "Shinobi - Database Installation" -echo "(y)es or (N)o" -read mysqlagreeData -if [ "$mysqlagreeData" = "y" ]; then - echo "What is your SQL Username?" - read sqluser - echo "What is your SQL Password?" - read sqlpass - sudo mysql -u $sqluser -p$sqlpass -e "source sql/user.sql" || true - sudo mysql -u $sqluser -p$sqlpass -e "source sql/framework.sql" || true - echo "Shinobi - Do you want to create a new user for viewing and managing cameras in Shinobi? You can do this later in the Superuser panel." +echo "Shinobi - Do you want to use MariaDB or SQLite3?" +echo "SQLite3 is better for small installs" +echo "MariaDB (MySQL) is better for large installs" +echo "(S)QLite3 or (M)ariaDB?" +echo "Press [ENTER] for default (MariaDB)" +read sqliteormariadb +if [ "$sqliteormariadb" = "S" ] || [ "$sqliteormariadb" = "s" ]; then + sudo npm install jsonfile + sudo yum install -y sqlite sqlite-devel -y + node ./tools/modifyConfiguration.js databaseType=sqlite3 + if [ ! -e "./shinobi.sqlite" ]; then + echo "Creating shinobi.sqlite for SQLite3..." + sudo cp sql/shinobi.sample.sqlite shinobi.sqlite + else + echo "shinobi.sqlite already exists. Continuing..." + fi +else + echo "=============" + echo "Shinobi - Do you want to Install MariaDB?" echo "(y)es or (N)o" - read mysqlDefaultData - if [ "$mysqlDefaultData" = "y" ]; then - escapeReplaceQuote='\\"' - groupKey=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 7 | head -n 1) - userID=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1) - userEmail=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1)"@"$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1)".com" - userPasswordPlain=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1) - userPasswordMD5=$(echo -n "$userPasswordPlain" | md5sum | awk '{print $1}') - userDetails='{"days":"10"}' - userDetails=$(echo "$userDetails" | sed -e 's/"/'$escapeReplaceQuote'/g') - echo $userDetailsNew - apiIP='0.0.0.0' - apiKey=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1) - apiDetails='{"auth_socket":"1","get_monitors":"1","control_monitors":"1","get_logs":"1","watch_stream":"1","watch_snapshot":"1","watch_videos":"1","delete_videos":"1"}' - apiDetails=$(echo "$apiDetails" | sed -e 's/"/'$escapeReplaceQuote'/g') - rm sql/default_user.sql || true - echo "USE ccio;INSERT INTO Users (\`ke\`,\`uid\`,\`auth\`,\`mail\`,\`pass\`,\`details\`) VALUES (\"$groupKey\",\"$userID\",\"$apiKey\",\"$userEmail\",\"$userPasswordMD5\",\"$userDetails\");INSERT INTO API (\`code\`,\`ke\`,\`uid\`,\`ip\`,\`details\`) VALUES (\"$apiKey\",\"$groupKey\",\"$userID\",\"$apiIP\",\"$apiDetails\");" > "sql/default_user.sql" - sudo mysql -u $sqluser -p$sqlpass --database ccio -e "source sql/default_user.sql" > "INSTALL/log.txt" - echo "The following details will be shown again at the end of the installation." - echo "=====================================" - echo "======= Login Credentials =======" - echo "|| Username : $userEmail" - echo "|| Password : $userPasswordPlain" - echo "|| API Key : $apiKey" - echo "=====================================" - echo "=====================================" - echo "** To change these settings login to either to the Superuser panel or login to the dashboard as the user that was just created and open the Settings window. **" + read mysqlagree + if [ "$mysqlagree" = "y" ] || [ "$mysqlagree" = "Y" ]; then + sudo yum install mariadb mariadb-server -y + #Start mysql and enable on boot + sudo systemctl start mariadb + sudo systemctl enable mariadb + #Run mysql install + sudo mysql_secure_installation + fi + echo "=============" + echo "Shinobi - Database Installation" + echo "(y)es or (N)o" + read mysqlagreeData + if [ "$mysqlagreeData" = "y" ] || [ "$mysqlagreeData" = "Y" ]; then + echo "What is your SQL Username?" + read sqluser + echo "What is your SQL Password?" + read sqlpass + sudo mysql -u $sqluser -p$sqlpass -e "source sql/user.sql" || true + sudo mysql -u $sqluser -p$sqlpass -e "source sql/framework.sql" || true + echo "Shinobi - Do you want to create a new user for viewing and managing cameras in Shinobi? You can do this later in the Superuser panel." + echo "(y)es or (N)o" + read mysqlDefaultData + if [ "$mysqlDefaultData" = "y" ] || [ "$mysqlDefaultData" = "Y" ]; then + escapeReplaceQuote='\\"' + groupKey=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 7 | head -n 1) + userID=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1) + userEmail=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1)"@"$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1)".com" + userPasswordPlain=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1) + userPasswordMD5=$(echo -n "$userPasswordPlain" | md5sum | awk '{print $1}') + userDetails='{"days":"10"}' + userDetails=$(echo "$userDetails" | sed -e 's/"/'$escapeReplaceQuote'/g') + echo $userDetailsNew + apiIP='0.0.0.0' + apiKey=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1) + apiDetails='{"auth_socket":"1","get_monitors":"1","control_monitors":"1","get_logs":"1","watch_stream":"1","watch_snapshot":"1","watch_videos":"1","delete_videos":"1"}' + apiDetails=$(echo "$apiDetails" | sed -e 's/"/'$escapeReplaceQuote'/g') + rm sql/default_user.sql || true + echo "USE ccio;INSERT INTO Users (\`ke\`,\`uid\`,\`auth\`,\`mail\`,\`pass\`,\`details\`) VALUES (\"$groupKey\",\"$userID\",\"$apiKey\",\"$userEmail\",\"$userPasswordMD5\",\"$userDetails\");INSERT INTO API (\`code\`,\`ke\`,\`uid\`,\`ip\`,\`details\`) VALUES (\"$apiKey\",\"$groupKey\",\"$userID\",\"$apiIP\",\"$apiDetails\");" > "sql/default_user.sql" + sudo mysql -u $sqluser -p$sqlpass --database ccio -e "source sql/default_user.sql" > "INSTALL/log.txt" + echo "The following details will be shown again at the end of the installation." + echo "=====================================" + echo "======= Login Credentials =======" + echo "|| Username : $userEmail" + echo "|| Password : $userPasswordPlain" + echo "|| API Key : $apiKey" + echo "=====================================" + echo "=====================================" + echo "** To change these settings login to either to the Superuser panel or login to the dashboard as the user that was just created and open the Settings window. **" + fi fi fi echo "=============" @@ -83,39 +110,53 @@ sudo npm install echo "=============" echo "Shinobi - Install PM2" sudo npm install pm2 -g -if [ ! -e "./conf.json" ]; then - cp conf.sample.json conf.json -fi -if [ ! -e "./super.json" ]; then - echo "Default Superuser : admin@shinobi.video" - echo "Default Password : admin" - cp super.sample.json super.json -fi echo "Shinobi - Finished" sudo chmod -R 755 . touch INSTALL/installed.txt -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 +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 -if [ "$startShinobi" = "y" ]; then +if [ "$startShinobi" = "y" ] || [ "$startShinobi" = "Y" ]; then sudo pm2 start camera.js sudo pm2 start cron.js sudo pm2 startup sudo pm2 save sudo pm2 list fi -echo "details written to INSTALL/installed.txt" -echo "=====================================" -echo "======= Login Credentials =======" -echo "|| Username : $userEmail" -echo "|| Password : $userPasswordPlain" -echo "|| API Key : $apiKey" -echo "=====================================" -echo "=====================================" \ No newline at end of file +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://$(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 \ No newline at end of file diff --git a/INSTALL/cuda9-part1.sh b/INSTALL/cuda9-part1.sh new file mode 100644 index 00000000..2468e31c --- /dev/null +++ b/INSTALL/cuda9-part1.sh @@ -0,0 +1,18 @@ +sudo add-apt-repository ppa:graphics-drivers/ppa -y +sudo apt update -y +sudo apt install g++ freeglut3-dev build-essential libx11-dev libxmu-dev libxi-dev libglu1-mesa libglu1-mesa-dev -y +sudo apt install gcc-6 -y +sudo apt install g++-6 -y +wget https://cdn.shinobi.video/installers/cuda9-part2-after-reboot.sh -O cuda9-part2-after-reboot.sh +sudo chmod +x ./cuda9-part2-after-reboot.sh +wget https://developer.nvidia.com/compute/cuda/9.0/Prod/local_installers/cuda_9.0.176_384.81_linux-run -O cuda_9.run +sudo chmod +x cuda_9.run +sudo echo "blacklist amd76x_edac" >> /etc/modprobe.d/blacklist.conf +sudo echo "blacklist vga16fb" >> /etc/modprobe.d/blacklist.conf +sudo echo "blacklist nouveau" >> /etc/modprobe.d/blacklist.conf +sudo echo "blacklist rivafb" >> /etc/modprobe.d/blacklist.conf +sudo echo "blacklist nvidiafb" >> /etc/modprobe.d/blacklist.conf +sudo echo "blacklist rivatv" >> /etc/modprobe.d/blacklist.conf +sudo update-initramfs -u +echo "Now you need to reboot and run the next part." +echo "Do after the reboot inside this directory : ./cuda9-part2-after-reboot.sh" \ No newline at end of file diff --git a/INSTALL/cuda9-part2-after-reboot.sh b/INSTALL/cuda9-part2-after-reboot.sh new file mode 100644 index 00000000..78fc219e --- /dev/null +++ b/INSTALL/cuda9-part2-after-reboot.sh @@ -0,0 +1,6 @@ +sudo service lightdm stop +sudo init 3 +sudo ./cuda_9.run -- override +sudo ln -s /usr/bin/gcc-6 /usr/local/cuda/bin/gcc +sudo ln -s /usr/bin/g++-6 /usr/local/cuda/bin/g++ +nvidia-smi \ No newline at end of file diff --git a/INSTALL/installDatabase.js b/INSTALL/installDatabase.js new file mode 100644 index 00000000..fc6e016c --- /dev/null +++ b/INSTALL/installDatabase.js @@ -0,0 +1,37 @@ +var knex = require('knex'); +if(config.databaseType===undefined){config.databaseType='mysql'} + +var databaseOptions = { + client: config.databaseType, + connection: config.db, +} +if(databaseOptions.client.indexOf('sqlite')>-1){ + databaseOptions.client = 'sqlite3'; + databaseOptions.useNullAsDefault = true; +} +if(databaseOptions.client === 'sqlite3' && databaseOptions.connection.filename === undefined){ + databaseOptions.connection.filename = __dirname+"/shinobi.sqlite" +} +s.databaseEngine = knex(databaseOptions) +s.sqlQuery = function(query,values,onMoveOn,hideLog){ + if(!values){values=[]} + if(typeof values === 'function'){ + var onMoveOn = values; + var values = []; + } + if(!onMoveOn){onMoveOn=function(){}} + return s.databaseEngine.raw(query,values) + .asCallback(function(err,r){ + if(err&&config.databaseLogs){ + s.systemLog('s.sqlQuery QUERY',query) + s.systemLog('s.sqlQuery ERROR',err) + } + if(onMoveOn) + if(typeof onMoveOn === 'function'){ + if(!r)r=[] + onMoveOn(err,r) + }else{ + console.log(onMoveOn) + } + }) +} \ No newline at end of file diff --git a/INSTALL/macos-part2.sh b/INSTALL/macos-part2.sh new file mode 100644 index 00000000..36133dbe --- /dev/null +++ b/INSTALL/macos-part2.sh @@ -0,0 +1,92 @@ + +#!/bin/bash +echo "=========================================================" +echo "==!! Shinobi : The Open Source CCTV and NVR Solution !!==" +echo "=================== Mac OS Install Part 2 ===============" +echo "=========================================================" +echo "Shinobi - Database Installation" +echo "(y)es or (N)o" +read mysqlagreeData +if [ "$mysqlagreeData" = "y" ]; then + echo "Shinobi - Use root for database installation?" + echo "(y)es or (N)o" + echo "What is your SQL Username?" + read sqluser + echo "What is your SQL Password?" + read sqlpass + echo "You may now be asked for your Administator (root for Mac OS, not MySQL) password" + sudo mysql -u $sqluser -p$sqlpass -e "source sql/user.sql" || true + sudo mysql -u $sqluser -p$sqlpass -e "source sql/framework.sql" || true + echo "Shinobi - Do you want to create a new user for viewing and managing cameras in Shinobi? You can do this later in the Superuser panel." + echo "(y)es or (N)o" + read mysqlDefaultData + if [ "$mysqlDefaultData" = "y" ]; then + escapeReplaceQuote='\\"' + groupKey=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 7 | head -n 1) + userID=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1) + userEmail=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1)"@"$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1)".com" + userPasswordPlain=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1) + userPasswordMD5=$(echo -n "$userPasswordPlain" | md5sum | awk '{print $1}') + userDetails='{"days":"10"}' + userDetails=$(echo "$userDetails" | sed -e 's/"/'$escapeReplaceQuote'/g') + echo $userDetailsNew + apiIP='0.0.0.0' + apiKey=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1) + apiDetails='{"auth_socket":"1","get_monitors":"1","control_monitors":"1","get_logs":"1","watch_stream":"1","watch_snapshot":"1","watch_videos":"1","delete_videos":"1"}' + apiDetails=$(echo "$apiDetails" | sed -e 's/"/'$escapeReplaceQuote'/g') + rm sql/default_user.sql || true + echo "USE ccio;INSERT INTO Users (\`ke\`,\`uid\`,\`auth\`,\`mail\`,\`pass\`,\`details\`) VALUES (\"$groupKey\",\"$userID\",\"$apiKey\",\"$userEmail\",\"$userPasswordMD5\",\"$userDetails\");INSERT INTO API (\`code\`,\`ke\`,\`uid\`,\`ip\`,\`details\`) VALUES (\"$apiKey\",\"$groupKey\",\"$userID\",\"$apiIP\",\"$apiDetails\");" > "sql/default_user.sql" + sudo mysql -u $sqluser -p$sqlpass --database ccio -e "source sql/default_user.sql" > "INSTALL/log.txt" + echo "The following details will be shown again at the end of the installation." + echo "=====================================" + echo "======= Login Credentials =======" + echo "|| Username : $userEmail" + echo "|| Password : $userPasswordPlain" + echo "|| API Key : $apiKey" + echo "=====================================" + echo "=====================================" + echo "** To change these settings login to either to the Superuser panel or login to the dashboard as the user that was just created and open the Settings window. **" + fi +fi +echo "=============" +echo "Shinobi - Install NPM Libraries" +sudo npm install +echo "=============" +echo "Shinobi - Install PM2" +sudo npm install pm2 -g +if [ ! -e "./conf.json" ]; then + sudo cp conf.sample.json conf.json +fi +if [ ! -e "./super.json" ]; then + echo "Default Superuser : admin@shinobi.video" + echo "Default Password : admin" + sudo cp super.sample.json super.json +fi +echo "Shinobi - Finished" +touch INSTALL/installed.txt +sudo chmod -R 755 . +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 +echo "Shinobi - Start Shinobi and set to start on boot?" +echo "(y)es or (N)o" +read startShinobi +if [ "$startShinobi" = "y" ]; then + sudo pm2 start camera.js + sudo pm2 start cron.js + sudo pm2 startup + sudo pm2 save + sudo pm2 list +fi +echo "details written to INSTALL/installed.txt" +echo "=====================================" +echo "======= Login Credentials =======" +echo "|| Username : $userEmail" +echo "|| Password : $userPasswordPlain" +echo "|| API Key : $apiKey" +echo "=====================================" +echo "=====================================" \ No newline at end of file diff --git a/INSTALL/macos.sh b/INSTALL/macos.sh index 62cfb3ec..f0a23f0b 100644 --- a/INSTALL/macos.sh +++ b/INSTALL/macos.sh @@ -1,6 +1,7 @@ #!/bin/bash echo "=========================================================" echo "==!! Shinobi : The Open Source CCTV and NVR Solution !!==" +echo "=================== Mac OS Install Part 1 ===============" echo "=========================================================" echo "To answer yes type the letter (y) in lowercase and press ENTER." echo "Default is no (N). Skip any components you already have or don't need." @@ -41,101 +42,9 @@ if [ "$mysqlagree" = "y" ]; then bash <(curl -Ls http://git.io/eUx7rg) fi echo "=============" -echo "Shinobi - Database Installation" -echo "(y)es or (N)o" -read mysqlagreeData -if [ "$mysqlagreeData" = "y" ]; then - if [ "$mysqlagree" = "y" ]; then - sqluser="root" - sqlpass=$(cat ~/Desktop/MYSQL_PASSWORD) - fi - if [ ! "$mysqlagree" = "y" ]; then - echo "Shinobi - Use root for database installation?" - echo "(y)es or (N)o" - read useroot - if [ "$useroot" = "y" ]; then - sqluser="root" - sqlpass=$(cat ~/Desktop/MYSQL_PASSWORD) - else - echo "What is your SQL Username?" - read sqluser - echo "What is your SQL Password?" - read sqlpass - fi - fi - echo "You may now be asked for your Administator (root for Mac OS, not MySQL) password" - sudo mysql -u $sqluser -p$sqlpass -e "source sql/user.sql" || true - sudo mysql -u $sqluser -p$sqlpass -e "source sql/framework.sql" || true - echo "Shinobi - Do you want to create a new user for viewing and managing cameras in Shinobi? You can do this later in the Superuser panel." - echo "(y)es or (N)o" - read mysqlDefaultData - if [ "$mysqlDefaultData" = "y" ]; then - escapeReplaceQuote='\\"' - groupKey=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 7 | head -n 1) - userID=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1) - userEmail=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1)"@"$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1)".com" - userPasswordPlain=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1) - userPasswordMD5=$(echo -n "$userPasswordPlain" | md5sum | awk '{print $1}') - userDetails='{"days":"10"}' - userDetails=$(echo "$userDetails" | sed -e 's/"/'$escapeReplaceQuote'/g') - echo $userDetailsNew - apiIP='0.0.0.0' - apiKey=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1) - apiDetails='{"auth_socket":"1","get_monitors":"1","control_monitors":"1","get_logs":"1","watch_stream":"1","watch_snapshot":"1","watch_videos":"1","delete_videos":"1"}' - apiDetails=$(echo "$apiDetails" | sed -e 's/"/'$escapeReplaceQuote'/g') - rm sql/default_user.sql || true - echo "USE ccio;INSERT INTO Users (\`ke\`,\`uid\`,\`auth\`,\`mail\`,\`pass\`,\`details\`) VALUES (\"$groupKey\",\"$userID\",\"$apiKey\",\"$userEmail\",\"$userPasswordMD5\",\"$userDetails\");INSERT INTO API (\`code\`,\`ke\`,\`uid\`,\`ip\`,\`details\`) VALUES (\"$apiKey\",\"$groupKey\",\"$userID\",\"$apiIP\",\"$apiDetails\");" > "sql/default_user.sql" - sudo mysql -u $sqluser -p$sqlpass --database ccio -e "source sql/default_user.sql" > "INSTALL/log.txt" - echo "The following details will be shown again at the end of the installation." - echo "=====================================" - echo "======= Login Credentials =======" - echo "|| Username : $userEmail" - echo "|| Password : $userPasswordPlain" - echo "|| API Key : $apiKey" - echo "=====================================" - echo "=====================================" - echo "** To change these settings login to either to the Superuser panel or login to the dashboard as the user that was just created and open the Settings window. **" - fi -fi echo "=============" -echo "Shinobi - Install NPM Libraries" -sudo npm install +echo "You must now close this terminal window and reopen it." +echo "Reopen the Shinobi folder and run" +echo "chmod +x INSTALL/macos-part2.sh && INSTALL/macos-part2.sh" echo "=============" -echo "Shinobi - Install PM2" -sudo npm install pm2 -g -if [ ! -e "./conf.json" ]; then - sudo cp conf.sample.json conf.json -fi -if [ ! -e "./super.json" ]; then - echo "Default Superuser : admin@shinobi.video" - echo "Default Password : admin" - sudo cp super.sample.json super.json -fi -echo "Shinobi - Finished" -touch INSTALL/installed.txt -sudo chmod -R 755 . -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 -echo "Shinobi - Start Shinobi and set to start on boot?" -echo "(y)es or (N)o" -read startShinobi -if [ "$startShinobi" = "y" ]; then - sudo pm2 start camera.js - sudo pm2 start cron.js - sudo pm2 startup - sudo pm2 save - sudo pm2 list -fi -echo "details written to INSTALL/installed.txt" -echo "=====================================" -echo "======= Login Credentials =======" -echo "|| Username : $userEmail" -echo "|| Password : $userPasswordPlain" -echo "|| API Key : $apiKey" -echo "=====================================" -echo "=====================================" \ No newline at end of file +echo "=============" \ No newline at end of file diff --git a/INSTALL/openalpr-gpu-easy.sh b/INSTALL/openalpr-gpu-easy.sh new file mode 100644 index 00000000..f0fff672 --- /dev/null +++ b/INSTALL/openalpr-gpu-easy.sh @@ -0,0 +1,30 @@ +# Install prerequisites +# this includes all the ones missing from OpenALPR's guide. +sudo apt install libopencv-dev 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 –DCOMPILE_GPU=1 .. + +# 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 \ No newline at end of file diff --git a/INSTALL/opencv-cuda.sh b/INSTALL/opencv-cuda.sh new file mode 100644 index 00000000..d477c7b0 --- /dev/null +++ b/INSTALL/opencv-cuda.sh @@ -0,0 +1,29 @@ +# OpenCV CUDA + +wget -O opencv.zip https://github.com/opencv/opencv/archive/3.4.0.zip +wget -O opencv_contrib.zip https://github.com/opencv/opencv_contrib/archive/3.4.0.zip +sudo apt-get install unzip -y + +sudo unzip opencv.zip -d tempOpenCV +sudo unzip opencv_contrib.zip -d tempOpenCVContrib + +sudo mv tempOpenCV/opencv-3.4.0 opencv +sudo mv tempOpenCVContrib/opencv_contrib-3.4.0 opencv_contrib +sudo rm -rf tempOpenCV +sudo rm -rf tempOpenCVContrib + +sudo apt install build-essential cmake git pkg-config unzip ffmpeg qtbase5-dev python-dev python3-dev python-numpy python3-numpy libhdf5-dev libgtk-3-dev libdc1394-22 libdc1394-22-dev libjpeg-dev libtiff5-dev libtesseract-dev -y + +sudo add-apt-repository "deb http://security.ubuntu.com/ubuntu zesty-security main" +sudo add-apt-repository "deb http://security.ubuntu.com/ubuntu xenial-security main" +sudo apt update +sudo apt install libjasper1 libjasper-dev libavcodec-dev libavformat-dev libswscale-dev libxine2-dev libgstreamer-plugins-base1.0-0 libgstreamer-plugins-base1.0-dev libpng16-16 libpng-dev libv4l-dev libtbb-dev libfaac-dev libmp3lame-dev libopencore-amrnb-dev libopencore-amrwb-dev libtheora-dev libvorbis-dev libxvidcore-dev v4l-utils -y + +cd opencv +mkdir release +cd release + +cmake -D CMAKE_INSTALL_PREFIX=/usr/local -D WITH_NVCUVID=ON -D FORCE_VTK=ON -D BUILD_DOCS=ON -D WITH_XINE=ON -D WITH_CUDA=ON -D WITH_OPENGL=ON -D WITH_TBB=ON -D BUILD_EXAMPLES=ON -D WITH_OPENCL=ON -D CMAKE_BUILD_TYPE=RELEASE -D CUDA_NVCC_FLAGS="-D_FORCE_INLINES --expt-relaxed-constexpr" -D WITH_GDAL=ON -D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib/modules/ -D ENABLE_FAST_MATH=1 -D CUDA_FAST_MATH=1 -D WITH_CUBLAS=1 .. + +make -j4 +sudo make install \ No newline at end of file diff --git a/INSTALL/shinobi.service b/INSTALL/shinobi.service new file mode 100644 index 00000000..73138e2e --- /dev/null +++ b/INSTALL/shinobi.service @@ -0,0 +1,11 @@ +[Unit] +Description=ShinobiCCTV + +[Service] +WorkingDirectory=/home/Shinobi +Type=forking +ExecStart=/bin/bash INSTALL/start.sh +KillMode=process + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/INSTALL/start.sh b/INSTALL/start.sh index 64df53e7..a2439f75 100644 --- a/INSTALL/start.sh +++ b/INSTALL/start.sh @@ -3,7 +3,6 @@ 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 diff --git a/INSTALL/ubuntu.sh b/INSTALL/ubuntu.sh index eceb07f9..bebed33b 100644 --- a/INSTALL/ubuntu.sh +++ b/INSTALL/ubuntu.sh @@ -5,10 +5,18 @@ echo "=========================================================" echo "To answer yes type the letter (y) in lowercase and press ENTER." echo "Default is no (N). Skip any components you already have or don't need." echo "=============" +if [ ! -e "./conf.json" ]; then + sudo cp conf.sample.json conf.json +fi +if [ ! -e "./super.json" ]; then + echo "Default Superuser : admin@shinobi.video" + echo "Default Password : admin" + sudo cp super.sample.json super.json +fi echo "Shinobi - Do you want to Install Node.js?" echo "(y)es or (N)o" read nodejsinstall -if [ "$nodejsinstall" = "y" ]; then +if [ "$nodejsinstall" = "y" ] || [ "$nodejsinstall" = "Y" ]; then wget https://deb.nodesource.com/setup_8.x chmod +x setup_8.x ./setup_8.x @@ -18,74 +26,104 @@ echo "=============" echo "Shinobi - Do you want to Install FFMPEG?" echo "(y)es or (N)o" read ffmpeginstall -if [ "$ffmpeginstall" = "y" ]; then - echo "Shinobi - Get FFMPEG 3.x from ppa:jonathonf/ffmpeg-3" - sudo apt-get install software-properties-common python-software-properties -y - sudo add-apt-repository ppa:jonathonf/ffmpeg-3 -y - sudo apt update -y - sudo apt install ffmpeg -y - sudo apt install libav-tools -y - sudo apt install x264 -y - sudo apt install x265 -y -fi -echo "=============" -echo "Shinobi - Do you want to Install MariaDB? Choose No if you have MySQL or MariaDB already." -echo "(y)es or (N)o" -read mysqlagree -if [ "$mysqlagree" = "y" ]; then - echo "Shinobi - Installing MariaDB" - echo "Password for root SQL user, If you are installing SQL now then you may put anything:" - read sqlpass - echo "mariadb-server mariadb-server/root_password password $sqlpass" | debconf-set-selections - echo "mariadb-server mariadb-server/root_password_again password $sqlpass" | debconf-set-selections - sudo apt install mariadb-server -y - sudo service mysql start +if [ "$ffmpeginstall" = "y" ] || [ "$ffmpeginstall" = "Y" ]; then + #Detect Ubuntu Version + echo "=============" + echo " Detecting Ubuntu Version" + echo "=============" + declare -i getubuntuversion=$(lsb_release -r | awk '{print $2}' | cut -d . -f1) + echo "=============" + echo " Ubuntu Version: $getubuntuversion" + echo "=============" + if [[ "$getubuntuversion" == "16" || "$getubuntuversion" < "16" ]]; then + echo "=============" + echo "Shinobi - Get FFMPEG 3.x from ppa:jonathonf/ffmpeg-3" + sudo add-apt-repository ppa:jonathonf/ffmpeg-3 -y + sudo apt update -y && sudo apt install ffmpeg libav-tools x264 x265 -y + echo "=============" + else + echo "=============" + echo "Shinobi - Installing FFMPEG" + sudo apt install ffmpeg libav-tools x264 x265 -y + echo "=============" + fi fi echo "=============" -echo "Shinobi - Database Installation" -echo "(y)es or (N)o" -read mysqlagreeData -if [ "$mysqlagreeData" = "y" ]; then - if [ "$mysqlagree" = "y" ]; then - sqluser="root" +echo "Shinobi - Do you want to use MariaDB or SQLite3?" +echo "SQLite3 is better for small installs" +echo "MariaDB (MySQL) is better for large installs" +echo "(S)QLite3 or (M)ariaDB?" +echo "Press [ENTER] for default (MariaDB)" +read sqliteormariadb +if [ "$sqliteormariadb" = "S" ] || [ "$sqliteormariadb" = "s" ]; then + sudo npm install jsonfile + sudo apt-get install sqlite3 libsqlite3-dev -y + node ./tools/modifyConfiguration.js databaseType=sqlite3 + if [ ! -e "./shinobi.sqlite" ]; then + echo "Creating shinobi.sqlite for SQLite3..." + sudo cp sql/shinobi.sample.sqlite shinobi.sqlite + else + echo "shinobi.sqlite already exists. Continuing..." fi - if [ ! "$mysqlagree" = "y" ]; then - echo "What is your SQL Username?" - read sqluser - echo "What is your SQL Password?" +else + echo "Shinobi - Do you want to Install MariaDB? Choose No if you already have it." + echo "(y)es or (N)o" + read mysqlagree + if [ "$mysqlagree" = "y" ] || [ "$mysqlagree" = "Y" ]; then + echo "Shinobi - Installing MariaDB" + echo "Password for root SQL user, If you are installing SQL now then you may put anything:" read sqlpass + echo "mariadb-server mariadb-server/root_password password $sqlpass" | debconf-set-selections + echo "mariadb-server mariadb-server/root_password_again password $sqlpass" | debconf-set-selections + sudo apt install mariadb-server -y + sudo service mysql start fi - sudo mysql -u $sqluser -p$sqlpass -e "source sql/user.sql" || true - sudo mysql -u $sqluser -p$sqlpass -e "source sql/framework.sql" || true - echo "Shinobi - Do you want to create a new user for viewing and managing cameras in Shinobi? You can do this later in the Superuser panel." + echo "=============" + echo "Shinobi - Database Installation" echo "(y)es or (N)o" - read mysqlDefaultData - if [ "$mysqlDefaultData" = "y" ]; then - escapeReplaceQuote='\\"' - groupKey=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 7 | head -n 1) - userID=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1) - userEmail=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1)"@"$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1)".com" - userPasswordPlain=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1) - userPasswordMD5=$(echo -n "$userPasswordPlain" | md5sum | awk '{print $1}') - userDetails='{"days":"10"}' - userDetails=$(echo "$userDetails" | sed -e 's/"/'$escapeReplaceQuote'/g') - echo $userDetailsNew - apiIP='0.0.0.0' - apiKey=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1) - apiDetails='{"auth_socket":"1","get_monitors":"1","control_monitors":"1","get_logs":"1","watch_stream":"1","watch_snapshot":"1","watch_videos":"1","delete_videos":"1"}' - apiDetails=$(echo "$apiDetails" | sed -e 's/"/'$escapeReplaceQuote'/g') - rm sql/default_user.sql || true - echo "USE ccio;INSERT INTO Users (\`ke\`,\`uid\`,\`auth\`,\`mail\`,\`pass\`,\`details\`) VALUES (\"$groupKey\",\"$userID\",\"$apiKey\",\"$userEmail\",\"$userPasswordMD5\",\"$userDetails\");INSERT INTO API (\`code\`,\`ke\`,\`uid\`,\`ip\`,\`details\`) VALUES (\"$apiKey\",\"$groupKey\",\"$userID\",\"$apiIP\",\"$apiDetails\");" > "sql/default_user.sql" - sudo mysql -u $sqluser -p$sqlpass --database ccio -e "source sql/default_user.sql" > "INSTALL/log.txt" - echo "The following details will be shown again at the end of the installation." - echo "=====================================" - echo "======= Login Credentials =======" - echo "|| Username : $userEmail" - echo "|| Password : $userPasswordPlain" - echo "|| API Key : $apiKey" - echo "=====================================" - echo "=====================================" - echo "** To change these settings login to either to the Superuser panel or login to the dashboard as the user that was just created and open the Settings window. **" + read mysqlagreeData + if [ "$mysqlagreeData" = "y" ] || [ "$mysqlagreeData" = "Y" ]; then + if [ "$mysqlagree" = "y" ] || [ "$mysqlagree" = "Y" ]; then + sqluser="root" + fi + if [ ! "$mysqlagree" = "y" ]; then + echo "What is your SQL Username?" + read sqluser + echo "What is your SQL Password?" + read sqlpass + fi + sudo mysql -u $sqluser -p$sqlpass -e "source sql/user.sql" || true + sudo mysql -u $sqluser -p$sqlpass -e "source sql/framework.sql" || true + echo "Shinobi - Do you want to create a new user for viewing and managing cameras in Shinobi? You can do this later in the Superuser panel." + echo "(y)es or (N)o" + read mysqlDefaultData + if [ "$mysqlDefaultData" = "y" ] || [ "$mysqlDefaultData" = "Y" ]; then + escapeReplaceQuote='\\"' + groupKey=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 7 | head -n 1) + userID=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1) + userEmail=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1)"@"$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1)".com" + userPasswordPlain=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1) + userPasswordMD5=$(echo -n "$userPasswordPlain" | md5sum | awk '{print $1}') + userDetails='{"days":"10"}' + userDetails=$(echo "$userDetails" | sed -e 's/"/'$escapeReplaceQuote'/g') + echo $userDetailsNew + apiIP='0.0.0.0' + apiKey=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1) + apiDetails='{"auth_socket":"1","get_monitors":"1","control_monitors":"1","get_logs":"1","watch_stream":"1","watch_snapshot":"1","watch_videos":"1","delete_videos":"1"}' + apiDetails=$(echo "$apiDetails" | sed -e 's/"/'$escapeReplaceQuote'/g') + rm sql/default_user.sql || true + echo "USE ccio;INSERT INTO Users (\`ke\`,\`uid\`,\`auth\`,\`mail\`,\`pass\`,\`details\`) VALUES (\"$groupKey\",\"$userID\",\"$apiKey\",\"$userEmail\",\"$userPasswordMD5\",\"$userDetails\");INSERT INTO API (\`code\`,\`ke\`,\`uid\`,\`ip\`,\`details\`) VALUES (\"$apiKey\",\"$groupKey\",\"$userID\",\"$apiIP\",\"$apiDetails\");" > "sql/default_user.sql" + sudo mysql -u $sqluser -p$sqlpass --database ccio -e "source sql/default_user.sql" > "INSTALL/log.txt" + echo "The following details will be shown again at the end of the installation." + echo "=====================================" + echo "======= Login Credentials =======" + echo "|| Username : $userEmail" + echo "|| Password : $userPasswordPlain" + echo "|| API Key : $apiKey" + echo "=====================================" + echo "=====================================" + echo "** To change these settings login to either to the Superuser panel or login to the dashboard as the user that was just created and open the Settings window. **" + fi fi fi echo "=============" @@ -94,39 +132,53 @@ sudo npm install echo "=============" echo "Shinobi - Install PM2" sudo npm install pm2 -g -if [ ! -e "./conf.json" ]; then - sudo cp conf.sample.json conf.json -fi -if [ ! -e "./super.json" ]; then - echo "Default Superuser : admin@shinobi.video" - echo "Default Password : admin" - sudo cp super.sample.json super.json -fi echo "Shinobi - Finished" sudo chmod -R 755 . touch INSTALL/installed.txt -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 +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 -if [ "$startShinobi" = "y" ]; then +if [ "$startShinobi" = "y" ] || [ "$startShinobi" = "y" ]; then sudo pm2 start camera.js sudo pm2 start cron.js sudo pm2 startup sudo pm2 save sudo pm2 list fi -echo "details written to INSTALL/installed.txt" -echo "=====================================" -echo "======= Login Credentials =======" -echo "|| Username : $userEmail" -echo "|| Password : $userPasswordPlain" -echo "|| API Key : $apiKey" -echo "=====================================" -echo "=====================================" \ No newline at end of file +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://$(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 \ No newline at end of file diff --git a/README-Docker.md b/README-Docker.md deleted file mode 100644 index 950d8a08..00000000 --- a/README-Docker.md +++ /dev/null @@ -1,17 +0,0 @@ -# Running docker - -- modify .env with your appropriate values - - at a minimum you should at least change: - - MYSQL_VOLUME_DIR - - VIDEOS_VOLUME_DIR - - MYSQL_PASSWORD - - MYSQL_ROOT_PASSWORD - - ADMIN_PASSWORD=admin -- modify any other details to suit your needs in `docker-compose.yml` ie volume paths -- make sure you have docker installed and running -- make sure you have docker-compose installed -- run docker-compose - ``` - docker-compose up -d - ``` -- you will see that camera and cron will spew a few errors about connecting to the db while the db container spins up diff --git a/camera.js b/camera.js index 8777bb95..6273c783 100644 --- a/camera.js +++ b/camera.js @@ -12,6 +12,13 @@ process.on('uncaughtException', function (err) { console.error('Uncaught Exception occured!'); console.error(err.stack); }); +var ffmpegPath = false; +try{ + ffmpegPath = require('ffmpeg-static').path; +}catch(err){ + console.log('No Static FFmpeg. Continuing.') + //no static ffmpeg +} var fs = require('fs'); var os = require('os'); var URL = require('url'); @@ -40,6 +47,7 @@ var connectionTester = require('connection-tester'); var events = require('events'); var Cam = require('onvif').Cam; var knex = require('knex'); +var Mp4Frag = require('mp4frag'); const P2P = require('pipe2pam'); const PamDiff = require('pam-diff'); var location = {} @@ -77,6 +85,7 @@ if(config.mail){ } //config defaults if(config.cpuUsageMarker===undefined){config.cpuUsageMarker='%Cpu'} +if(config.customCpuCommand===undefined){config.customCpuCommand=null} if(config.autoDropCache===undefined){config.autoDropCache=true} if(config.doSnapshot===undefined){config.doSnapshot=true} if(config.restart===undefined){config.restart={}} @@ -131,13 +140,18 @@ s.getDefinitonFile=function(rule){ } return file } -if(config.databaseType === 'sqlite3'&&config.db.filename === undefined){ - config.db.filename = __dirname+"/shinobi.sqlite" -} -s.databaseEngine = knex({ +var databaseOptions = { client: config.databaseType, - connection: config.db -}) + connection: config.db, +} +if(databaseOptions.client.indexOf('sqlite')>-1){ + databaseOptions.client = 'sqlite3'; + databaseOptions.useNullAsDefault = true; +} +if(databaseOptions.client === 'sqlite3' && databaseOptions.connection.filename === undefined){ + databaseOptions.connection.filename = __dirname+"/shinobi.sqlite" +} +s.databaseEngine = knex(databaseOptions) s.sqlQuery = function(query,values,onMoveOn,hideLog){ if(!values){values=[]} if(typeof values === 'function'){ @@ -153,8 +167,13 @@ s.sqlQuery = function(query,values,onMoveOn,hideLog){ } if(onMoveOn) if(typeof onMoveOn === 'function'){ - if(r){ - r = r[0]; + switch(databaseOptions.client){ + case'sqlite3': + if(!r)r=[] + break; + default: + if(r)r=r[0] + break; } onMoveOn(err,r) }else{ @@ -162,18 +181,20 @@ s.sqlQuery = function(query,values,onMoveOn,hideLog){ } }) } -s.sqlQuery('ALTER TABLE `Videos` ADD COLUMN `details` TEXT NULL DEFAULT NULL AFTER `status`;',function(err){ - if(err){ - s.systemLog("Critical update 1/2 already applied"); - } - 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(databaseOptions.client === 'mysql'){ + s.sqlQuery('ALTER TABLE `Videos` ADD COLUMN `details` TEXT NULL DEFAULT NULL AFTER `status`;',function(err){ if(err){ - s.systemLog("Critical update 2/2 NOT applied, this could be bad"); - }else{ - s.systemLog("Critical update 2/2 already applied"); + s.systemLog("Critical update 1/2 already applied"); } + 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){ + s.systemLog("Critical update 2/2 NOT applied, this could be bad"); + }else{ + s.systemLog("Critical update 2/2 already applied"); + } + },true); },true); -},true); +} //kill any ffmpeg running s.ffmpegKill=function(){ var cmd='' @@ -442,10 +463,14 @@ io.attach(server); console.log('NODE.JS version : '+execSync("node -v")) //ffmpeg location if(!config.ffmpegDir){ - if(s.isWin===true){ - config.ffmpegDir=__dirname+'/ffmpeg/ffmpeg.exe' + if(ffmpegPath !== false){ + config.ffmpegDir = ffmpegPath }else{ - config.ffmpegDir='ffmpeg' + if(s.isWin===true){ + config.ffmpegDir = __dirname+'/ffmpeg/ffmpeg.exe' + }else{ + config.ffmpegDir = 'ffmpeg' + } } } s.ffmpegVersion=execSync(config.ffmpegDir+" -version").toString().split('Copyright')[0].replace('ffmpeg version','').trim() @@ -515,7 +540,9 @@ s.init=function(x,e,k,fn){ if(!s.group[e.ke].mon[e.mid]){s.group[e.ke].mon[e.mid]={}} if(!s.group[e.ke].mon[e.mid].streamIn){s.group[e.ke].mon[e.mid].streamIn={}}; if(!s.group[e.ke].mon[e.mid].emitterChannel){s.group[e.ke].mon[e.mid].emitterChannel={}}; - if(!s.group[e.ke].mon[e.mid].firstFLVchunk){s.group[e.ke].mon[e.mid].firstFLVchunk={}}; + if(!s.group[e.ke].mon[e.mid].mp4frag){s.group[e.ke].mon[e.mid].mp4frag={}}; + if(!s.group[e.ke].mon[e.mid].firstStreamChunk){s.group[e.ke].mon[e.mid].firstStreamChunk={}}; + if(!s.group[e.ke].mon[e.mid].contentWriter){s.group[e.ke].mon[e.mid].contentWriter={}}; 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={}}; @@ -621,8 +648,14 @@ s.init=function(x,e,k,fn){ //lock this function s.group[e.ke].sizeChanging=true //validate current values - if(!s.group[e.ke].usedSpace){s.group[e.ke].usedSpace=0}else{s.group[e.ke].usedSpace=parseFloat(s.group[e.ke].usedSpace)} - if(s.group[e.ke].usedSpace<0){s.group[e.ke].usedSpace=0} + if(!s.group[e.ke].usedSpace){ + s.group[e.ke].usedSpace=0 + }else{ + s.group[e.ke].usedSpace=parseFloat(s.group[e.ke].usedSpace) + } + if(s.group[e.ke].usedSpace<0||isNaN(s.group[e.ke].usedSpace)){ + s.group[e.ke].usedSpace=0 + } //set queue processor var checkQueue=function(){ //get first in queue @@ -746,21 +779,27 @@ s.video=function(x,e){ break; case'delete': if(!e.filename&&e.time){e.filename=s.moment(e.time)} + var filename + if(e.filename.indexOf('.')>-1){ + filename = e.filename + }else{ + filename = e.filename+'.'+e.ext + } if(!e.status){e.status=0} - e.save=[e.id,e.ke,s.nameToTime(e.filename)]; + e.save=[e.id,e.ke,s.nameToTime(filename)]; s.sqlQuery('SELECT * FROM Videos WHERE `mid`=? AND `ke`=? AND `time`=?',e.save,function(err,r){ if(r&&r[0]){ r=r[0] - e.dir=s.video('getDir',r) + var dir=s.video('getDir',r) s.sqlQuery('DELETE FROM Videos WHERE `mid`=? AND `ke`=? AND `time`=?',e.save,function(){ - fs.stat(e.dir+e.filename+'.'+e.ext,function(err,file){ + fs.stat(dir+filename,function(err,file){ if(err){ s.systemLog('File Delete Error : '+e.ke+' : '+' : '+e.mid,err) } s.init('diskUsedSet',e,-(r.size/1000000)) }) - s.tx({f:'video_delete',filename:e.filename+'.'+e.ext,mid:e.mid,ke:e.ke,time:s.nameToTime(e.filename),end:s.moment(new Date,'YYYY-MM-DD HH:mm:ss')},'GRP_'+e.ke); - s.file('delete',e.dir+e.filename+'.'+e.ext) + s.tx({f:'video_delete',filename:filename,mid:e.mid,ke:e.ke,time:s.nameToTime(filename),end:s.moment(new Date,'YYYY-MM-DD HH:mm:ss')},'GRP_'+e.ke); + s.file('delete',dir+filename) }) } }) @@ -912,6 +951,257 @@ s.splitForFFPMEG = function (ffmpegCommandAsString) { }, {a: ['']}).a }; s.ffmpeg=function(e){ + //create input map + var createFFmpegMap = function(arrayOfMaps){ + //e.details.input_map_choices.stream + var string = ''; + if(arrayOfMaps && arrayOfMaps instanceof Array && arrayOfMaps.length>0){ + arrayOfMaps.forEach(function(v){ + if(v.map==='')v.map='0' + string += ' -map '+v.map + }) + } + return string; + } + var createInputMap = function(number,input){ + //fulladdress - Full Input Path + //`x` is an object used to contain temporary values. + var x = {} + x.cust_input = '' + x.hwaccel = '' + if(input.cust_input&&input.cust_input!==''){x.cust_input+=' '+input.cust_input;} + //input - analyze duration + if(input.aduration&&input.aduration!==''){x.cust_input+=' -analyzeduration '+input.aduration}; + //input - probe size + if(input.probesize&&input.probesize!==''){x.cust_input+=' -probesize '+input.probesize}; + //input - stream loop (good for static files/lists) + if(input.stream_loop==='1'){x.cust_input+=' -stream_loop -1'}; + //input - is h264 has rtsp in address and transport method is chosen + if(input.type==='mjpeg'){ + if(x.cust_input.indexOf('-f ')===-1){ + x.cust_input+=' -f mjpeg' + } + //input - frames per second + if(x.cust_input.indexOf('-r ')===-1&&!input.sfps||input.sfps===''){ + input.sfps=parseFloat(input.sfps); + if(isNaN(input.sfps)){input.sfps=1} + input.sfps + x.cust_input+=' -r '+input.sfps + } + x.cust_input+=' -reconnect 1'; + } + if((input.type==='h264'||input.type==='mp4')&&input.fulladdress.indexOf('rtsp://')>-1&&input.rtsp_transport!==''&&input.rtsp_transport!=='no'){ + x.cust_input += ' -rtsp_transport '+input.rtsp_transport; + } + if((input.type==='mp4'||input.type==='mjpeg')&&x.cust_input.indexOf('-re')===-1){ + x.cust_input += ' -re' + } + //hardware acceleration + if(input.accelerator&&input.accelerator==='1'){ + if(input.hwaccel&&input.hwaccel!==''){ + x.hwaccel+=' -hwaccel '+input.hwaccel; + } + if(input.hwaccel_vcodec&&input.hwaccel_vcodec!==''&&input.hwaccel_vcodec!=='auto'&&input.hwaccel_vcodec!=='no'){ + x.hwaccel+=' -c:v '+input.hwaccel_vcodec; + } + if(input.hwaccel_device&&input.hwaccel_device!==''){ + switch(input.hwaccel){ + case'vaapi': + x.hwaccel+=' -vaapi_device '+input.hwaccel_device+' -hwaccel_output_format vaapi'; + break; + default: + x.hwaccel+=' -hwaccel_device '+input.hwaccel_device; + break; + } + } + } + //custom - input flags + return x.hwaccel+x.cust_input+' -i "'+input.fulladdress+'"'; + } + //create sub stream channel + var createStreamChannel = function(number,channel){ + //`x` is an object used to contain temporary values. + var x = { + pipe:'' + } + if(!number||number==''){ + x.channel_sdir = e.sdir; + }else{ + x.channel_sdir = e.sdir+'channel'+number+'/'; + if (!fs.existsSync(x.channel_sdir)){ + fs.mkdirSync(x.channel_sdir); + } + } + x.stream_video_filters=[] + //stream - frames per second + if(channel.stream_vcodec!=='copy'){ + if(!channel.stream_fps||channel.stream_fps===''){ + switch(channel.stream_type){ + case'rtmp': + channel.stream_fps=30 + break; + default: +// channel.stream_fps=5 + break; + } + } + } + if(channel.stream_fps&&channel.stream_fps!==''){x.stream_fps=' -r '+channel.stream_fps}else{x.stream_fps=''} + + //stream - hls vcodec + if(channel.stream_vcodec&&channel.stream_vcodec!=='no'){ + if(channel.stream_vcodec!==''){x.stream_vcodec=' -c:v '+channel.stream_vcodec}else{x.stream_vcodec=' -c:v libx264'} + }else{ + x.stream_vcodec=''; + } + //stream - hls acodec + if(channel.stream_acodec!=='no'){ + if(channel.stream_acodec&&channel.stream_acodec!==''){x.stream_acodec=' -c:a '+channel.stream_acodec}else{x.stream_acodec=''} + }else{ + x.stream_acodec=' -an'; + } + //stream - resolution + if(channel.stream_scale_x&&channel.stream_scale_x!==''&&channel.stream_scale_y&&channel.stream_scale_y!==''){ + x.ratio=channel.stream_scale_x+'x'+channel.stream_scale_y; + } + //stream - hls segment time + if(channel.hls_time&&channel.hls_time!==''){x.hls_time=channel.hls_time}else{x.hls_time="2"} + //hls list size + if(channel.hls_list_size&&channel.hls_list_size!==''){x.hls_list_size=channel.hls_list_size}else{x.hls_list_size=2} + //stream - custom flags + if(channel.cust_stream&&channel.cust_stream!==''){x.cust_stream=' '+channel.cust_stream}else{x.cust_stream=''} + //stream - preset + if(channel.preset_stream&&channel.preset_stream!==''){x.preset_stream=' -preset '+channel.preset_stream;}else{x.preset_stream=''} + //stream - quality + if(channel.stream_quality&&channel.stream_quality!==''){x.stream_quality=channel.stream_quality}else{x.stream_quality=''} + //hardware acceleration + if(e.details.accelerator&&e.details.accelerator==='1'){ + if(e.details.hwaccel&&e.details.hwaccel!==''){ + x.hwaccel+=' -hwaccel '+e.details.hwaccel; + } + if(e.details.hwaccel_vcodec&&e.details.hwaccel_vcodec!==''){ + x.hwaccel+=' -c:v '+e.details.hwaccel_vcodec; + } + if(e.details.hwaccel_device&&e.details.hwaccel_device!==''){ + switch(e.details.hwaccel){ + case'vaapi': + x.hwaccel+=' -vaapi_device '+e.details.hwaccel_device+' -hwaccel_output_format vaapi'; + break; + default: + x.hwaccel+=' -hwaccel_device '+e.details.hwaccel_device; + break; + } + } + // else{ + // if(e.details.hwaccel==='vaapi'){ + // x.hwaccel+=' -hwaccel_device 0'; + // } + // } + } + + if(channel.rotate_stream&&channel.rotate_stream!==""&&channel.rotate_stream!=="no"){ + x.stream_video_filters.push('transpose='+channel.rotate_stream); + } + //stream - video filter + if(channel.svf&&channel.svf!==''){ + x.stream_video_filters.push(channel.svf) + } + if(x.stream_video_filters.length>0){ + var string = x.stream_video_filters.join(',').trim() + if(string===''){ + x.stream_video_filters='' + }else{ + x.stream_video_filters=' -vf '+string + } + }else{ + x.stream_video_filters='' + } + if(e.details.input_map_choices&&e.details.input_map_choices.record){ + //add input feed map + x.pipe += createFFmpegMap(e.details.input_map_choices['stream_channel-'+(number-config.pipeAddition)]) + } + switch(channel.stream_type){ + case'mp4': + x.cust_stream+=' -movflags +frag_keyframe+empty_moov+default_base_moof -metadata title="Poseidon Stream" -reset_timestamps 1' + if(channel.stream_vcodec!=='copy'){ + if(x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.ratio} + x.cust_stream+=x.stream_fps + if(x.stream_quality&&x.stream_quality!=='')x.stream_quality=' -crf '+x.stream_quality; + x.cust_stream+=x.stream_quality + x.cust_stream+=x.preset_stream + x.cust_stream+=x.stream_video_filters + } + x.pipe+=' -f mp4'+x.stream_acodec+x.stream_vcodec+x.cust_stream+' pipe:'+number; + break; + case'rtmp': + x.rtmp_server_url=s.checkCorrectPathEnding(channel.rtmp_server_url); + if(channel.stream_vcodec!=='copy'){ + if(channel.stream_vcodec==='libx264'){ + channel.stream_vcodec = 'h264' + } + x.cust_stream+=x.stream_fps + if(x.stream_quality&&x.stream_quality!=='')x.stream_quality=' -crf '+x.stream_quality; + x.cust_stream+=x.stream_quality + x.cust_stream+=x.preset_stream + if(channel.stream_v_br&&channel.stream_v_br!==''){x.cust_stream+=' -b:v '+channel.stream_v_br} + } + if(channel.stream_vcodec!=='no'&&channel.stream_vcodec!==''){ + x.cust_stream+=' -vcodec '+channel.stream_vcodec + } + if(channel.stream_acodec!=='copy'){ + if(!channel.stream_acodec||channel.stream_acodec===''||channel.stream_acodec==='no'){ + channel.stream_acodec = 'aac' + } + if(!channel.stream_a_br||channel.stream_a_br===''){channel.stream_a_br='128k'} + x.cust_stream+=' -ab '+channel.stream_a_br + } + if(channel.stream_acodec!==''){ + x.cust_stream+=' -acodec '+channel.stream_acodec + } + x.pipe+=' -f flv'+x.stream_video_filters+x.cust_stream+' "'+x.rtmp_server_url+channel.rtmp_stream_key+'"'; + break; + case'h264': + if(channel.stream_vcodec!=='copy'){ + if(x.cust_stream.indexOf('-s ')===-1&&x.ratio){x.cust_stream+=' -s '+x.ratio} + x.cust_stream+=x.stream_fps + if(x.stream_quality&&x.stream_quality!=='')x.stream_quality=' -crf '+x.stream_quality; + x.cust_stream+=x.stream_quality + x.cust_stream+=x.preset_stream + x.cust_stream+=x.stream_video_filters + } + x.pipe+=' -f mpegts'+x.stream_acodec+x.stream_vcodec+x.cust_stream+' pipe:'+number; + break; + case'flv': + if(channel.stream_vcodec!=='copy'){ + if(x.cust_stream.indexOf('-s ')===-1&&x.ratio){x.cust_stream+=' -s '+x.ratio} + x.cust_stream+=x.stream_fps + if(x.stream_quality&&x.stream_quality!=='')x.stream_quality=' -crf '+x.stream_quality; + x.cust_stream+=x.stream_quality + x.cust_stream+=x.preset_stream + x.cust_stream+=x.stream_video_filters + } + x.pipe+=' -f flv'+x.stream_acodec+x.stream_vcodec+x.cust_stream+' pipe:'+number; + break; + case'hls': + if(channel.stream_vcodec!=='h264_vaapi'&&channel.stream_vcodec!=='copy'){ + if(x.stream_quality&&x.stream_quality!=='')x.stream_quality=' -crf '+x.stream_quality; + if(x.cust_stream.indexOf('-tune')===-1){x.cust_stream+=' -tune zerolatency'} + if(x.cust_stream.indexOf('-g ')===-1){x.cust_stream+=' -g 1'} + if(x.cust_stream.indexOf('-s ')===-1&&x.ratio){x.cust_stream+=' -s '+x.ratio} + x.cust_stream+=x.stream_video_filters + } + x.pipe+=x.preset_stream+x.stream_quality+x.stream_acodec+x.stream_vcodec+x.stream_fps+' -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 "'+x.channel_sdir+'s.m3u8"'; + break; + case'mjpeg': + if(x.stream_quality&&x.stream_quality!=='')x.stream_quality=' -q:v '+x.stream_quality; + x.pipe+=' -c:v mjpeg -f mpjpeg -boundary_tag shinobi'+x.cust_stream+x.stream_video_filters+x.stream_quality+x.stream_fps+' -s '+x.ratio+' pipe:'+number; + break; + default: + x.pipe='' + break; + } + return x.pipe + } //set X for temporary values so we don't break our main monitor object. var x={tmp:''}; //set some placeholding values to avoid "undefined" in ffmpeg string. @@ -921,11 +1211,13 @@ s.ffmpeg=function(e){ x.record_video_filters=[] x.stream_video_filters=[] x.hwaccel='' + x.pipe='' //input - analyze duration if(e.details.aduration&&e.details.aduration!==''){x.cust_input+=' -analyzeduration '+e.details.aduration}; //input - probe size if(e.details.probesize&&e.details.probesize!==''){x.cust_input+=' -probesize '+e.details.probesize}; - //input - check protocol + //input - stream loop (good for static files/lists) + if(e.details.stream_loop==='1'){x.cust_input+=' -stream_loop -1'}; //input switch(e.type){ case'h264': @@ -1142,164 +1434,56 @@ s.ffmpeg=function(e){ x.stream_video_filters='' } //stream - pipe build + if(e.details.input_map_choices&&e.details.input_map_choices.stream){ + //add input feed map + x.pipe += createFFmpegMap(e.details.input_map_choices.stream) + } switch(e.details.stream_type){ - case'flv': + case'mp4': + x.cust_stream+=' -movflags +frag_keyframe+empty_moov+default_base_moof -metadata title="Poseidon Stream" -reset_timestamps 1' if(e.details.stream_vcodec!=='copy'){ if(x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.ratio} x.cust_stream+=x.stream_fps - if(x.stream_quality)x.stream_quality=' -crf '+x.stream_quality; + if(x.stream_quality&&x.stream_quality!=='')x.stream_quality=' -crf '+x.stream_quality; x.cust_stream+=x.stream_quality x.cust_stream+=x.preset_stream + x.cust_stream+=x.stream_video_filters } - x.pipe=' -f flv'+x.stream_acodec+x.stream_vcodec+x.stream_video_filters+x.cust_stream+' pipe:1'; + x.pipe+=' -f mp4'+x.stream_acodec+x.stream_vcodec+x.cust_stream+' pipe:1'; + break; + case'flv': + if(e.details.stream_vcodec!=='copy'){ + if(x.cust_stream.indexOf('-s ')===-1&&x.ratio){x.cust_stream+=' -s '+x.ratio} + x.cust_stream+=x.stream_fps + if(x.stream_quality&&x.stream_quality!=='')x.stream_quality=' -crf '+x.stream_quality; + x.cust_stream+=x.stream_quality + x.cust_stream+=x.preset_stream + x.cust_stream+=x.stream_video_filters + } + x.pipe+=' -f flv'+x.stream_acodec+x.stream_vcodec+x.cust_stream+' pipe:1'; break; case'hls': - if(e.details.stream_vcodec!=='h264_vaapi'){ - if(x.stream_quality)x.stream_quality=' -crf '+x.stream_quality; + if(e.details.stream_vcodec!=='h264_vaapi'&&e.details.stream_vcodec!=='copy'){ + if(x.stream_quality&&x.stream_quality!=='')x.stream_quality=' -crf '+x.stream_quality; if(x.cust_stream.indexOf('-tune')===-1){x.cust_stream+=' -tune zerolatency'} if(x.cust_stream.indexOf('-g ')===-1){x.cust_stream+=' -g 1'} + if(x.cust_stream.indexOf('-s ')===-1&&x.ratio){x.cust_stream+=' -s '+x.ratio} + x.cust_stream+=x.stream_video_filters } - x.pipe=x.preset_stream+x.stream_quality+x.stream_acodec+x.stream_vcodec+x.stream_fps+' -f hls -s '+x.ratio+x.stream_video_filters+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"'; + x.pipe+=x.preset_stream+x.stream_quality+x.stream_acodec+x.stream_vcodec+x.stream_fps+' -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(x.stream_quality)x.stream_quality=' -q:v '+x.stream_quality; - x.pipe=' -c:v mjpeg -f mpjpeg -boundary_tag shinobi'+x.cust_stream+x.stream_video_filters+x.stream_quality+x.stream_fps+' -s '+x.ratio+' pipe:1'; + if(x.stream_quality&&x.stream_quality!=='')x.stream_quality=' -q:v '+x.stream_quality; + x.pipe+=' -c:v mjpeg -f mpjpeg -boundary_tag shinobi'+x.cust_stream+x.stream_video_filters+x.stream_quality+x.stream_fps+' -s '+x.ratio+' pipe:1'; break; case'b64':case'':case undefined:case null://base64 - if(x.stream_quality)x.stream_quality=' -q:v '+x.stream_quality; - x.pipe=' -c:v mjpeg -f image2pipe'+x.cust_stream+x.stream_video_filters+x.stream_quality+x.stream_fps+' -s '+x.ratio+' pipe:1'; + if(x.stream_quality&&x.stream_quality!=='')x.stream_quality=' -q:v '+x.stream_quality; + x.pipe+=' -c:v mjpeg -f image2pipe'+x.cust_stream+x.stream_video_filters+x.stream_quality+x.stream_fps+' -s '+x.ratio+' pipe:1'; break; default: x.pipe='' break; } - var createStreamChannel = function(number,channel){ - var x = {} - if(!number||number==''){ - x.channel_sdir = e.sdir; - }else{ - x.channel_sdir = e.sdir+'channel'+number+'/'; - if (!fs.existsSync(x.channel_sdir)){ - fs.mkdirSync(x.channel_sdir); - } - } - x.stream_video_filters=[] - //stream - frames per second - if(!channel.sfps||channel.sfps===''){ - channel.sfps=parseFloat(channel.sfps); - if(isNaN(channel.sfps)){channel.sfps=1} - } - if(channel.stream_fps&&channel.stream_fps!==''){x.stream_fps=' -r '+channel.stream_fps}else{x.stream_fps=''} - - //stream - hls vcodec - if(channel.stream_vcodec&&channel.stream_vcodec!=='no'){ - if(channel.stream_vcodec!==''){x.stream_vcodec=' -c:v '+channel.stream_vcodec}else{x.stream_vcodec=' -c:v libx264'} - }else{ - x.stream_vcodec=''; - } - //stream - hls acodec - if(channel.stream_acodec!=='no'){ - if(channel.stream_acodec&&channel.stream_acodec!==''){x.stream_acodec=' -c:a '+channel.stream_acodec}else{x.stream_acodec=''} - }else{ - x.stream_acodec=' -an'; - } - //stream - resolution - if(channel.stream_scale_x&&channel.stream_scale_x!==''&&channel.stream_scale_y&&channel.stream_scale_y!==''){ - x.ratio=channel.stream_scale_x+'x'+channel.stream_scale_y; - } - //stream - hls segment time - if(channel.hls_time&&channel.hls_time!==''){x.hls_time=channel.hls_time}else{x.hls_time="2"} - //hls list size - if(channel.hls_list_size&&channel.hls_list_size!==''){x.hls_list_size=channel.hls_list_size}else{x.hls_list_size=2} - //stream - custom flags - if(channel.cust_stream&&channel.cust_stream!==''){x.cust_stream=' '+channel.cust_stream}else{x.cust_stream=''} - //stream - preset - if(channel.preset_stream&&channel.preset_stream!==''){x.preset_stream=' -preset '+channel.preset_stream;}else{x.preset_stream=''} - //stream - quality - if(channel.stream_quality&&channel.stream_quality!==''){x.stream_quality=channel.stream_quality}else{x.stream_quality=''} - //hardware acceleration - if(e.details.accelerator&&e.details.accelerator==='1'){ - if(e.details.hwaccel&&e.details.hwaccel!==''){ - x.hwaccel+=' -hwaccel '+e.details.hwaccel; - } - if(e.details.hwaccel_vcodec&&e.details.hwaccel_vcodec!==''){ - x.hwaccel+=' -c:v '+e.details.hwaccel_vcodec; - } - if(e.details.hwaccel_device&&e.details.hwaccel_device!==''){ - switch(e.details.hwaccel){ - case'vaapi': - x.hwaccel+=' -vaapi_device '+e.details.hwaccel_device+' -hwaccel_output_format vaapi'; - break; - default: - x.hwaccel+=' -hwaccel_device '+e.details.hwaccel_device; - break; - } - } - // else{ - // if(e.details.hwaccel==='vaapi'){ - // x.hwaccel+=' -hwaccel_device 0'; - // } - // } - } - - if(channel.rotate_stream&&channel.rotate_stream!==""&&channel.rotate_stream!=="no"){ - x.stream_video_filters.push('transpose='+channel.rotate_stream); - } - //stream - video filter - if(channel.svf&&channel.svf!==''){ - x.stream_video_filters.push(channel.svf) - } - if(x.stream_video_filters.length>0){ - x.stream_video_filters=' -vf '+x.stream_video_filters.join(',') - }else{ - x.stream_video_filters='' - } -// if(!channel.stream_map||channel.stream_map==''){ -// x.stream_map=" -map 0" -// }else{ -// x.stream_map=" -map "+channel.stream_map -// } - x.stream_map="" -//// - switch(channel.stream_type){ - case'h264': - if(channel.stream_vcodec!=='copy'){ - if(x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.ratio} - x.cust_stream+=x.stream_fps - if(x.stream_quality)x.stream_quality=' -crf '+x.stream_quality; - x.cust_stream+=x.stream_quality - x.cust_stream+=x.preset_stream - } - x.pipe=x.stream_map+' -f mpegts'+x.stream_acodec+x.stream_vcodec+x.stream_video_filters+x.cust_stream+' pipe:'+number; - break; - case'flv': - if(channel.stream_vcodec!=='copy'){ - if(x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.ratio} - x.cust_stream+=x.stream_fps - if(x.stream_quality)x.stream_quality=' -crf '+x.stream_quality; - x.cust_stream+=x.stream_quality - x.cust_stream+=x.preset_stream - } - x.pipe=x.stream_map+' -f flv'+x.stream_acodec+x.stream_vcodec+x.stream_video_filters+x.cust_stream+' pipe:'+number; - break; - case'hls': - if(channel.stream_vcodec!=='h264_vaapi'){ - if(x.stream_quality)x.stream_quality=' -crf '+x.stream_quality; - if(x.cust_stream.indexOf('-tune')===-1){x.cust_stream+=' -tune zerolatency'} - if(x.cust_stream.indexOf('-g ')===-1){x.cust_stream+=' -g 1'} - } - x.pipe=x.stream_map+x.preset_stream+x.stream_quality+x.stream_acodec+x.stream_vcodec+x.stream_fps+' -f hls -s '+x.ratio+x.stream_video_filters+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 "'+x.channel_sdir+'s.m3u8"'; - break; - case'mjpeg': - if(x.stream_quality)x.stream_quality=' -q:v '+x.stream_quality; - x.pipe=x.stream_map+' -c:v mjpeg -f mpjpeg -boundary_tag shinobi'+x.cust_stream+x.stream_video_filters+x.stream_quality+x.stream_fps+' -s '+x.ratio+' pipe:'+number; - break; - default: - x.pipe='' - break; - } - return x.pipe - } if(e.details.stream_channels){ e.details.stream_channels.forEach(function(v,n){ x.pipe+=createStreamChannel(n+config.pipeAddition,v) @@ -1307,6 +1491,10 @@ s.ffmpeg=function(e){ } //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){ + //add input feed map + x.pipe += createFFmpegMap(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.cust_detect&&e.details.cust_detect!==''){x.cust_detect+=e.details.cust_detect;} @@ -1317,15 +1505,23 @@ s.ffmpeg=function(e){ } } //api - snapshot bin/ cgi.bin (JPEG Mode) - if(e.details.snap==='1'||e.details.stream_type==='jpeg'){ + if(e.details.snap==='1'){ + if(e.details.input_map_choices&&e.details.input_map_choices.snap){ + //add input feed map + x.pipe += createFFmpegMap(e.details.input_map_choices.snap) + } 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.sratio=' -s '+e.details.snap_scale_x+'x'+e.details.snap_scale_y}else{x.sratio=''} 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.sratio+x.snap_vf+' "'+e.sdir+'s.jpg" -y'; } - //Raw H.264 stream over HTTP (RTSP simulation) - if(e.details.detector_trigger=='1'&&e.details.detector_record_method==='sip'){ + //Traditional Recording Buffer + if(e.details.detector=='1'&&e.details.detector_trigger=='1'&&e.details.detector_record_method==='sip'){ + if(e.details.input_map_choices&&e.details.input_map_choices.detector_sip_buffer){ + //add input feed map + x.pipe += createFFmpegMap(e.details.input_map_choices.detector_sip_buffer) + } x.detector_buffer_filters=[] if(!e.details.detector_buffer_vcodec||e.details.detector_buffer_vcodec===''||e.details.detector_buffer_vcodec==='auto'){ switch(e.type){ @@ -1340,7 +1536,7 @@ s.ffmpeg=function(e){ if(!e.details.detector_buffer_tune||e.details.detector_buffer_tune===''){e.details.detector_buffer_tune='zerolatency'} if(!e.details.detector_buffer_g||e.details.detector_buffer_g===''){e.details.detector_buffer_g='1'} if(!e.details.detector_buffer_hls_time||e.details.detector_buffer_hls_time===''){e.details.detector_buffer_hls_time='2'} - if(!e.details.detector_buffer_hls_list_size||e.details.detector_buffer_hls_list_size===''){e.details.detector_buffer_hls_list_size='10'} + 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'} @@ -1366,65 +1562,66 @@ s.ffmpeg=function(e){ } x.pipe+=x.detector_buffer_fps+' -an -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' } -// //Stream to YouTube (Stream out to server) -// if(e.details.stream_server==='1'){ -// if(!e.details.stream_server_vbr||e.details.stream_server_vbr===''){e.details.stream_server_vbr='256k'} -// x.stream_server_vbr=' -b:v '+e.details.stream_server_vbr; -// if(e.details.stream_server_fps&&e.details.stream_server_fps!==''){ -// x.stream_server_fps=' -r '+e.details.stream_server_fps -// e.details.stream_server_fps=parseFloat(e.details.stream_server_fps) -// x.stream_server_fps+=' -g '+e.details.stream_server_fps -// }else{x.stream_server_fps=''} -// if(e.details.stream_server_crf&&e.details.stream_server_crf!==''){x.stream_server_crf=' -crf '+e.details.stream_server_crf}else{x.stream_server_crf=''} -// if(e.details.stream_server_vf&&e.details.stream_server_vf!==''){x.stream_server_vf=' -vf '+e.details.stream_server_vf}else{x.stream_server_vf=''} -// if(e.details.stream_server_preset&&e.details.stream_server_preset!==''){x.stream_server_preset=' -preset '+e.details.stream_server_preset}else{x.stream_server_preset=''} -// if(e.details.stream_server_scale_x&&e.details.stream_server_scale_x!==''&&e.details.stream_server_scale_y&&e.details.stream_server_scale_y!==''){x.stream_server_ratio=' -s '+e.details.stream_server_scale_x+'x'+e.details.stream_server_scale_y}else{x.stream_server_ratio=''} -// if(e.details.cust_stream_server&&e.details.cust_stream_server!==''){x.cust_stream_server=' '+e.details.cust_stream_server;}else{x.cust_stream_server=''} -// x.pipe+=' -vcodec libx264 -pix_fmt yuv420p'+x.stream_server_preset+x.stream_server_crf+x.stream_server_fps+x.stream_server_vbr+x.stream_server_ratio+x.stream_server_vf+' -acodec aac -strict 2 -ar 44100 -q:a 3 -b:a 712000'+x.cust_stream_server+' -f flv '+e.details.stream_server_url; -// } //custom - output if(e.details.custom_output&&e.details.custom_output!==''){x.pipe+=' '+e.details.custom_output;} //custom - input flags if(e.details.cust_input&&e.details.cust_input!==''){x.cust_input+=' '+e.details.cust_input;} //logging - level if(e.details.loglevel&&e.details.loglevel!==''){x.loglevel='-loglevel '+e.details.loglevel;}else{x.loglevel='-loglevel error'} - if(e.mode=='record'){ - //custom - record flags + //build record string. + if(e.mode==='record'){ + if(e.details.input_map_choices&&e.details.input_map_choices.record){ + //add input feed map + x.record_string += createFFmpegMap(e.details.input_map_choices.record) + } + //if h264, hls, mp4, or local add the audio codec flag + switch(e.type){ + case'h264':case'hls':case'mp4':case'local': + x.record_string+=x.acodec; + break; + } + //custom flags if(e.details.cust_record&&e.details.cust_record!==''){x.record_string+=' '+e.details.cust_record;} - //record - preset + //preset flag if(e.details.preset_record&&e.details.preset_record!==''){x.record_string+=' -preset '+e.details.preset_record;} + //main string write + x.record_string+=x.vcodec+x.framerate+x.record_video_filters+x.record_dimensions+x.segment; + } + //create executeable FFMPEG command + x.ffmpegCommandString = x.loglevel; + //add main input + if((e.type==='mp4'||e.type==='mjpeg')&&x.cust_input.indexOf('-re')===-1){ + x.cust_input += ' -re' } - //build final string based on the input type. switch(e.type){ case'dashcam': - if(e.mode==='record'){x.record_string+=x.vcodec+x.framerate+x.record_video_filters+x.record_dimensions+x.segment;} - x.tmp=x.loglevel+' -i -'+x.record_string+x.pipe; + x.ffmpegCommandString += ' -i -'; break; case'socket':case'jpeg':case'pipe': - if(e.mode==='record'){x.record_string+=x.vcodec+x.framerate+x.record_video_filters+x.record_dimensions+x.segment;} - x.tmp=x.loglevel+' -pattern_type glob -f image2pipe'+x.framerate+' -vcodec mjpeg'+x.cust_input+' -i -'+x.record_string+x.pipe; + x.ffmpegCommandString += ' -pattern_type glob -f image2pipe'+x.framerate+' -vcodec mjpeg'+x.cust_input+' -i -'; break; case'mjpeg': - if(e.mode=='record'){ - x.record_string+=x.vcodec+x.record_video_filters+x.framerate+x.record_dimensions+x.segment; - } - x.tmp=x.loglevel+' -reconnect 1 -r '+e.details.sfps+' -f mjpeg'+x.cust_input+' -i '+e.url+''+x.record_string+x.pipe; + x.ffmpegCommandString += ' -reconnect 1 -r '+e.details.sfps+' -f mjpeg'+x.cust_input+' -i "'+e.url+'"'; break; case'h264':case'hls':case'mp4': - if(e.mode=='record'){ - x.record_string+=x.vcodec+x.framerate+x.acodec+x.record_dimensions+x.record_video_filters+' '+x.segment; - } - x.tmp=x.loglevel+x.cust_input+x.hwaccel+' -i '+e.url+x.record_string+x.pipe; + x.ffmpegCommandString += x.cust_input+x.hwaccel+' -i "'+e.url+'"'; break; case'local': - if(e.mode=='record'){ - x.record_string+=x.vcodec+x.framerate+x.acodec+x.record_dimensions+x.record_video_filters+' '+x.segment; - } - x.tmp=x.loglevel+x.cust_input+' -i '+e.path+''+x.record_string+x.pipe; + x.ffmpegCommandString += x.cust_input+' -i "'+e.path+'"'; break; } - s.group[e.ke].mon[e.mid].ffmpeg=x.tmp; - x.stdioPipes=[] + //add extra input maps + if(e.details.input_maps){ + e.details.input_maps.forEach(function(v,n){ + x.ffmpegCommandString += createInputMap(n+1,v) + }) + } + //add recording and stream outputs + x.ffmpegCommandString += x.record_string+x.pipe + //hold ffmpeg command for log stream + s.group[e.ke].mon[e.mid].ffmpeg = x.ffmpegCommandString; + //create additional pipes from ffmpeg + x.stdioPipes = []; var times = config.pipeAddition; if(e.details.stream_channels){ times+=e.details.stream_channels.length @@ -1432,8 +1629,8 @@ s.ffmpeg=function(e){ for(var i=0; i < times; i++){ x.stdioPipes.push('pipe') } - x.tmp = s.splitForFFPMEG(x.tmp.replace(/\s+/g,' ').trim()) - return spawn(config.ffmpegDir,x.tmp,{detached: true,stdio:x.stdioPipes}); + x.ffmpegCommandString = s.splitForFFPMEG(x.ffmpegCommandString.replace(/\s+/g,' ').trim()) + return spawn(config.ffmpegDir,x.ffmpegCommandString,{detached: true,stdio:x.stdioPipes}); } s.file=function(x,e){ if(!e){e={}}; @@ -1445,6 +1642,10 @@ s.file=function(x,e){ if(!e){return false;} return exec('rm -f '+e,{detached: true}); break; + case'delete_folder': + if(!e){return false;} + return exec('rm -rf '+e,{detached: true}); + break; case'delete_files': if(!e.age_type){e.age_type='min'};if(!e.age){e.age='1'}; exec('find '+e.path+' -type f -c'+e.age_type+' +'+e.age+' -exec rm -f {} +',{detached: true}); @@ -1461,7 +1662,8 @@ s.camera=function(x,e,cn,tx){ if(e.details&&(e.details instanceof Object)===false){ try{e.details=JSON.parse(e.details)}catch(err){} } - (['detector_cascades','cords']).forEach(function(v){ + //parse Objects + (['detector_cascades','cords','input_map_choices']).forEach(function(v){ if(e.details&&e.details[v]&&(e.details[v] instanceof Object)===false){ try{ e.details[v]=JSON.parse(e.details[v]); @@ -1471,7 +1673,8 @@ s.camera=function(x,e,cn,tx){ } } }); - (['stream_channels']).forEach(function(v){ + //parse Arrays + (['stream_channels','input_maps']).forEach(function(v){ if(e.details&&e.details[v]&&(e.details[v] instanceof Array)===false){ try{ e.details[v]=JSON.parse(e.details[v]); @@ -1649,18 +1852,20 @@ s.camera=function(x,e,cn,tx){ fs.mkdirSync(e.dir); } } - //stream dir - e.sdir=s.dir.streams+e.ke+'/'; - if (!fs.existsSync(e.sdir)){ - fs.mkdirSync(e.sdir); - } - e.sdir=s.dir.streams+e.ke+'/'+e.id+'/'; - if (!fs.existsSync(e.sdir)){ - fs.mkdirSync(e.sdir); - }else{ - s.file('delete',e.sdir+'*') - s.file('delete',e.sdir+'channel*') + var setStreamDir = function(){ + //stream dir + e.sdir=s.dir.streams+e.ke+'/'; + if (!fs.existsSync(e.sdir)){ + fs.mkdirSync(e.sdir); + } + e.sdir=s.dir.streams+e.ke+'/'+e.id+'/'; + if (!fs.existsSync(e.sdir)){ + fs.mkdirSync(e.sdir); + }else{ + s.file('delete_folder',e.sdir+'*') + } } + setStreamDir() //start "no motion" checker if(e.details.detector=='1'&&e.details.detector_notrigger=='1'){ if(!e.details.detector_notrigger_timeout||e.details.detector_notrigger_timeout===''){ @@ -1706,38 +1911,42 @@ s.camera=function(x,e,cn,tx){ },60000*1); } if(x==='record'||(x==='start'&&e.details.detector_record_method==='sip')){ + if(s.group[e.ke].mon[e.id].fswatch && typeof s.group[e.ke].mon[e.id].fswatch.close === 'function'){s.group[e.ke].mon[e.id].fswatch.close()} s.group[e.ke].mon[e.id].fswatch=fs.watch(e.dir,{encoding:'utf8'},function(eventType,filename){ if(s.group[e.ke].mon[e.id].fixingVideos[filename]){return} switch(eventType){ case'change': - clearTimeout(s.group[e.ke].mon[e.id].checker) - clearTimeout(s.group[e.ke].mon[e.id].checkStream) - s.group[e.ke].mon[e.id].checker=setTimeout(function(){ - if(s.group[e.ke].mon[e.id].started===1){ - e.fn(); - s.log(e,{type:lang['Camera is not recording'],msg:{msg:lang['Restarting Process']}}); - } - },60000*2); + if(s.platform!=='darwin'){ + clearTimeout(s.group[e.ke].mon[e.id].checker) + clearTimeout(s.group[e.ke].mon[e.id].checkStream) + s.group[e.ke].mon[e.id].checker=setTimeout(function(){ + if(s.group[e.ke].mon[e.id].started===1){ + e.fn(); + s.log(e,{type:lang['Camera is not recording'],msg:{msg:lang['Restarting Process']}}); + } + },60000*2); + } break; case'rename': fs.exists(e.dir+filename,function(exists){ if(exists){ if(s.group[e.ke].mon[e.id].open){ s.video('close',e); - if(e.details.detector==='1'&&s.ocv&&s.group[e.ke].mon[e.id].started===1&&e.details&&e.details.detector_record_method==='del'&&e.details.detector_delete_motionless_videos==='1'&&s.group[e.ke].mon[e.id].detector_motion_count===0){ - if(e.details.loglevel!=='quiet'){ - s.log(e,{type:lang['Delete Motionless Video'],msg:e.filename+'.'+e.ext}); + var row = Object.assign({},s.init('noReference',e)); + setTimeout(function(){ + if(row.details.detector==='1'&&s.group[row.ke].mon[row.id].started===1&&row.details&&row.details.detector_record_method==='del'&&row.details.detector_delete_motionless_videos==='1'&&s.group[row.ke].mon[row.id].detector_motion_count===0){ + if(row.details.loglevel!=='quiet'){ + s.log(row,{type:lang['Delete Motionless Video'],msg:row.filename+'.'+row.ext}); + } + s.video('delete',row) } - s.video('delete',s.init('noReference',e)) - } + },2000) } - setTimeout(function(){ - e.filename=filename.split('.')[0]; - s.video('open',e); - s.group[e.ke].mon[e.id].open=e.filename; - s.group[e.ke].mon[e.id].open_ext=e.ext; - s.group[e.ke].mon[e.id].detector_motion_count=0; - },2000) + e.filename=filename.split('.')[0]; + s.video('open',e); + s.group[e.ke].mon[e.id].open=e.filename; + s.group[e.ke].mon[e.id].open_ext=e.ext; + s.group[e.ke].mon[e.id].detector_motion_count=0; } }); break; @@ -1751,7 +1960,9 @@ s.camera=function(x,e,cn,tx){ s.group[e.ke].mon[e.id].fswatchStream=fs.watch(e.sdir,{encoding:'utf8'},function(eventType,filename){ switch(eventType){ case'change': - e.resetStreamCheck() + if(s.platform!=='darwin'){ + e.resetStreamCheck() + } break; } }) @@ -1780,6 +1991,7 @@ s.camera=function(x,e,cn,tx){ } e.error_fatal_count=0; e.fn=function(){//this function loops to create new files + setStreamDir() clearTimeout(s.group[e.ke].mon[e.id].checker) if(s.group[e.ke].mon[e.id].started===1){ e.error_count=0; @@ -2000,9 +2212,13 @@ s.camera=function(x,e,cn,tx){ //frames to stream ++e.frames; switch(e.details.stream_type){ + case'mp4': + s.group[e.ke].mon[e.id].mp4frag['MAIN'] = new Mp4Frag(); + s.group[e.ke].mon[e.id].spawn.stdio[1].pipe(s.group[e.ke].mon[e.id].mp4frag['MAIN']) + break; case'flv': e.frame_to_stream=function(d){ - if(!s.group[e.ke].mon[e.id].firstFLVchunk['MAIN'])s.group[e.ke].mon[e.id].firstFLVchunk['MAIN'] = d; + if(!s.group[e.ke].mon[e.id].firstStreamChunk['MAIN'])s.group[e.ke].mon[e.id].firstStreamChunk['MAIN'] = d; e.frame_to_stream=function(d){ e.resetStreamCheck() s.group[e.ke].mon[e.id].emitter.emit('data',d); @@ -2039,33 +2255,38 @@ s.camera=function(x,e,cn,tx){ } if(e.details.stream_channels&&e.details.stream_channels!==''){ var createStreamEmitter = function(channel,number){ - if(!s.group[e.ke].mon[e.id].emitterChannel[number+config.pipeAddition]){ - s.group[e.ke].mon[e.id].emitterChannel[number+config.pipeAddition] = new events.EventEmitter().setMaxListeners(0); + var pipeNumber = number+config.pipeAddition; + if(!s.group[e.ke].mon[e.id].emitterChannel[pipeNumber]){ + s.group[e.ke].mon[e.id].emitterChannel[pipeNumber] = new events.EventEmitter().setMaxListeners(0); } var frame_to_stream switch(channel.stream_type){ + case'mp4': + s.group[e.ke].mon[e.id].mp4frag[pipeNumber] = new Mp4Frag(); + s.group[e.ke].mon[e.id].spawn.stdio[pipeNumber].pipe(s.group[e.ke].mon[e.id].mp4frag[pipeNumber]) + break; case'mjpeg': frame_to_stream=function(d){ - s.group[e.ke].mon[e.id].emitterChannel[number+config.pipeAddition].emit('data',d); + s.group[e.ke].mon[e.id].emitterChannel[pipeNumber].emit('data',d); } break; case'flv': frame_to_stream=function(d){ - if(!s.group[e.ke].mon[e.id].firstFLVchunk[number+config.pipeAddition])s.group[e.ke].mon[e.id].firstFLVchunk[number+config.pipeAddition] = d; + if(!s.group[e.ke].mon[e.id].firstStreamChunk[pipeNumber])s.group[e.ke].mon[e.id].firstStreamChunk[pipeNumber] = d; frame_to_stream=function(d){ - s.group[e.ke].mon[e.id].emitterChannel[number+config.pipeAddition].emit('data',d); + s.group[e.ke].mon[e.id].emitterChannel[pipeNumber].emit('data',d); } frame_to_stream(d) } break; case'h264': frame_to_stream=function(d){ - s.group[e.ke].mon[e.id].emitterChannel[number+config.pipeAddition].emit('data',d); + s.group[e.ke].mon[e.id].emitterChannel[pipeNumber].emit('data',d); } break; } if(frame_to_stream){ - s.group[e.ke].mon[e.id].spawn.stdio[number+config.pipeAddition].on('data',frame_to_stream); + s.group[e.ke].mon[e.id].spawn.stdio[pipeNumber].on('data',frame_to_stream); } } e.details.stream_channels.forEach(createStreamEmitter) @@ -2240,13 +2461,8 @@ s.camera=function(x,e,cn,tx){ }else{ detector_timeout = parseFloat(d.mon.details.detector_timeout) } - if(d.mon.mode=='start'&&d.mon.details.detector_trigger=='1'&&d.mon.details.detector_record_method==='sip'){ + if(d.mon.mode=='start'&&d.mon.details.detector_trigger==='1'&&d.mon.details.detector_record_method==='sip'){ //s.group[d.ke].mon[d.id].eventBasedRecording.timeout - if(s.group[d.ke].mon[d.id].eventBasedRecording.timeout -// &&d.mon.details.watchdog_reset!=='1' - ){ -// return - } // 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; @@ -2264,23 +2480,24 @@ s.camera=function(x,e,cn,tx){ } s.group[d.ke].mon[d.id].eventBasedRecording.allowEnd = false; var runRecord = function(){ - s.log(d,'Spawned Recorder') + s.log(d,{type:"Traditional Recording",msg:"Started"}) //-t 00:'+moment(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 -t 00:'+moment(new Date(detector_timeout * 1000 * 60)).format('mm:ss')+' -c:v copy -an -strftime 1 "'+s.dir.videos+d.ke+'/'+d.id+'/'+s.moment()+'.mp4"').replace(/\s+/g,' ').trim())) + 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 -t 00:'+moment(new Date(detector_timeout * 1000 * 60)).format('mm:ss')+' -c:v copy -c:a copy -strftime 1 "'+s.video('getDir',d.mon)+s.moment()+'.mp4"').replace(/\s+/g,' ').trim())) var ffmpegError=''; var error - s.group[d.ke].mon[d.id].eventBasedRecording.process.stderr.on('data',function(d){ - s.log(e,{type:"Traditional Recording",msg:d.toString()}) + s.group[d.ke].mon[d.id].eventBasedRecording.process.stderr.on('data',function(data){ + s.log(d,{type:"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.log(e,{type:"Traditional Recording",msg:"Detector Recording Complete"}) + s.log(d,{type:"Traditional Recording",msg:"Detector Recording Process Exited Prematurely. Restarting."}) runRecord() return } + s.log(d,{type:"Traditional Recording",msg:"Detector Recording Complete"}) s.group[d.ke].mon[d.id].closeVideo() delete(s.group[d.ke].users[d.auth]) - s.log(e,{type:"Traditional Recording",msg:'Clear Recorder Process'}) + s.log(d,{type:"Traditional Recording",msg:'Clear Recorder Process'}) delete(s.group[d.ke].mon[d.id].eventBasedRecording.process) delete(s.group[d.ke].mon[d.id].eventBasedRecording.timeout) }) @@ -2551,15 +2768,129 @@ var tx; if(!Emitter){ cn.disconnect();return; } + if(!d.channel)d.channel = 'MAIN'; cn.ke=d.ke, cn.uid=d.uid, cn.auth=d.auth; cn.channel=d.channel; - cn.flvStream=d.id; - tx({time:toUTC(),buffer:s.group[d.ke].mon[d.id].firstFLVchunk[chunkChannel]}) - Emitter.on('data',s.group[d.ke].mon[d.id].contentWriter=function(buffer){ + cn.removeListenerOnDisconnect=true; + cn.socketVideoStream=d.id; + tx({time:toUTC(),buffer:s.group[d.ke].mon[d.id].firstStreamChunk[chunkChannel]}) + Emitter.on('data',s.group[d.ke].mon[d.id].contentWriter[chunkChannel]=function(buffer){ tx({time:toUTC(),buffer:buffer}) }) + } + 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]){ + d.success(r) + }else{ + s.sqlQuery('SELECT * FROM API WHERE ke=? AND code=? AND uid=?',[d.ke,d.auth,d.uid],function(err,r) { + if(r&&r[0]){ + r=r[0] + r.details=JSON.parse(r.details) + if(r.details.auth_socket==='1'){ + s.sqlQuery('SELECT ke,uid,auth,mail,details FROM Users WHERE ke=? AND uid=?',[r.ke,r.uid],function(err,r) { + if(r&&r[0]){ + d.success(r) + }else{ + d.failed('User not found') + } + }) + }else{ + d.failed('Permissions for this key do not allow authentication with Websocket') + } + }else{ + d.failed('Not an API key') + } + }) + } + }) + }) + //unique MP4 socket stream + cn.on('MP4',function(d){ + if(!s.group[d.ke]||!s.group[d.ke].mon||!s.group[d.ke].mon[d.id]){ + cn.disconnect();return; + } + cn.ip=cn.request.connection.remoteAddress; + var toUTC = function(){ + return new Date().toISOString(); + } + var tx=function(z){cn.emit('data',z);} + d.failed=function(msg){ + tx({f:'stop_reconnect',msg:msg,token_used:d.auth,ke:d.ke}); + cn.disconnect(); + } + d.success=function(r){ + r=r[0]; + var Emitter,chunkChannel + if(!d.channel){ + Emitter = s.group[d.ke].mon[d.id].emitter + chunkChannel = 'MAIN' + }else{ + Emitter = s.group[d.ke].mon[d.id].emitterChannel[parseInt(d.channel)+config.pipeAddition] + chunkChannel = parseInt(d.channel)+config.pipeAddition + } + if(!Emitter){ + cn.disconnect();return; + } + if(!d.channel)d.channel = 'MAIN'; + cn.ke=d.ke, + cn.uid=d.uid, + cn.auth=d.auth; + cn.channel=d.channel; + cn.socketVideoStream=d.id; + var mp4frag = s.group[d.ke].mon[d.id].mp4frag[d.channel]; + var onInitialized = () => { + cn.emit('mime', mp4frag.mime); + mp4frag.removeListener('initialized', onInitialized); + }; + + //event listener + var onSegment = function(data){ + cn.emit('segment', data); + }; + cn.on('MP4Command',function(msg){ + switch (msg) { + case 'mime' ://client is requesting mime + var mime = mp4frag.mime; + if (mime) { + cn.emit('mime', mime); + } else { + mp4frag.on('initialized', onInitialized); + } + break; + case 'initialization' ://client is requesting initialization segment + cn.emit('initialization', mp4frag.initialization); + break; + case 'segment' ://client is requesting a SINGLE segment + var segment = mp4frag.segment; + if (segment) { + cn.emit('segment', segment); + } else { + mp4frag.once('segment', onSegment); + } + break; + case 'segments' ://client is requesting ALL segments + //send current segment first to start video asap + var segment = mp4frag.segment; + if (segment) { + cn.emit('segment', segment); + } + //add listener for segments being dispatched by mp4frag + mp4frag.on('segment', onSegment); + break; + case 'pause' : + mp4frag.removeListener('segment', onSegment); + break; + case 'resume' : + mp4frag.on('segment', onSegment); + break; + case 'stop' ://client requesting to stop receiving segments + mp4frag.removeListener('segment', onSegment); + mp4frag.removeListener('initialized', onInitialized); + break; + } + }) } 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]){ @@ -2621,7 +2952,7 @@ var tx; s.tx({f:'user_status_change',ke:d.ke,uid:cn.uid,status:1,user:s.group[d.ke].users[d.auth]},'GRP_'+d.ke) s.init('diskUsedEmit',d) s.init('apps',d) - s.sqlQuery('SELECT * FROM API WHERE ke=? && uid=?',[d.ke,d.uid],function(err,rrr) { + s.sqlQuery('SELECT * FROM API WHERE ke=? AND uid=?',[d.ke,d.uid],function(err,rrr) { tx({ f:'init_success', users:s.group[d.ke].vid, @@ -2899,13 +3230,14 @@ var tx; d.base=d.m.details.control_base_url; } if(!d.m.details.control_url_stop_timeout||d.m.details.control_url_stop_timeout===''){d.m.details.control_url_stop_timeout=1000} + if(!d.m.details.control_url_method||d.m.details.control_url_method===''){d.m.details.control_url_method="GET"} d.setURL=function(url){ d.URLobject=URL.parse(url) if(!d.URLobject.port){d.URLobject.port=80} d.options = { host: d.URLobject.hostname, port: d.URLobject.port, - method: "GET", + method: d.m.details.control_url_method, path: d.URLobject.pathname, }; if(d.URLobject.query){ @@ -2919,7 +3251,7 @@ var tx; } } d.setURL(d.base+d.m.details['control_url_'+d.direction]) - http.get(d.options, function(first) { + http.request(d.options, function(first) { var body = ''; first.on('data', function(chunk) { body+=chunk @@ -2930,7 +3262,7 @@ var tx; s.log(d,{type:'Control Triggered Started',msg:body}); d.setURL(d.base+d.m.details['control_url_'+d.direction+'_stop']) setTimeout(function(){ - http.get(d.options, function(data) { + http.request(d.options, function(data) { var body='' data.on('data', function(chunk){ body+=chunk @@ -3003,16 +3335,13 @@ var tx; break; } break; - case'video': - switch(d.ff){ - case'fix': - s.video('fix',d) - break; - case'delete': - s.video('delete',d) - break; - } - break; +// case'video': +// switch(d.ff){ +// case'fix': +// s.video('fix',d) +// break; +// } +// break; case'ffprobe': if(s.group[cn.ke].users[cn.auth]){ switch(d.ff){ @@ -3063,7 +3392,7 @@ var tx; d.ip=d.arr.join(',') } if(d.port===''){ - d.port='80,8080,554' + d.port='80,8080,8000,7575,8081,554' } d.ip.split(',').forEach(function(v){ if(v.indexOf('-')>-1){ @@ -3101,7 +3430,7 @@ var tx; d.cams=[] d.IP_LIST.forEach(function(ip_entry,n) { d.PORT_LIST.forEach(function(port_entry,nn) { - return new Cam({ + new Cam({ hostname: ip_entry, username: d.USERNAME, password: d.PASSWORD, @@ -3130,6 +3459,7 @@ var tx; }); }); // foreach }); // foreach +// tx({f:'onvif_end'}) break; } }catch(er){ @@ -3206,7 +3536,13 @@ var tx; case'update': s.ffmpegKill() s.systemLog('Shinobi ordered to update',{by:cn.mail,ip:cn.ip,distro:d.distro}) - exec('chmod +x '+__dirname+'/UPDATE.sh&&'+__dirname+'/UPDATE.sh '+d.distro,{detached: true}) + var updateProcess = spawn('sh',(__dirname+'/UPDATE.sh '+d.distro).split(' '),{detached: true}) + updateProcess.stderr.on('data',function(data){ + s.systemLog('Update Info',data.toString()) + }) + updateProcess.stdout.on('data',function(data){ + s.systemLog('Update Info',data.toString()) + }) break; case'restart': d.check=function(x){return d.target.indexOf(x)>-1} @@ -3238,18 +3574,22 @@ var tx; if(d.form.mail!==''&&d.form.pass!==''){ if(d.form.pass===d.form.password_again){ s.sqlQuery('SELECT * FROM Users WHERE mail=?',[d.form.mail],function(err,r) { - if(r&&r[0]){//found one exist + if(r&&r[0]){ + //found address already exists d.msg='Email address is in use.'; s.tx({f:'error',ff:'account_register',msg:d.msg},cn.id) - }else{//create new + }else{ + //create new //user id d.form.uid=s.gid(); //check to see if custom key set if(!d.form.ke||d.form.ke===''){ d.form.ke=s.gid() } + //write user to db s.sqlQuery('INSERT INTO Users (ke,uid,mail,pass,details) VALUES (?,?,?,?,?)',[d.form.ke,d.form.uid,d.form.mail,s.md5(d.form.pass),d.form.details]) s.tx({f:'add_account',details:d.form.details,ke:d.form.ke,uid:d.form.uid,mail:d.form.mail},'$'); + //init user s.init('group',d.form) } }) @@ -3308,7 +3648,7 @@ var tx; // admin page socket functions cn.on('a',function(d){ if(!cn.init&&d.f=='init'){ - s.sqlQuery('SELECT * FROM Users WHERE auth=? && uid=?',[d.auth,d.uid],function(err,r){ + s.sqlQuery('SELECT * FROM Users WHERE auth=? AND uid=?',[d.auth,d.uid],function(err,r){ if(r&&r[0]){ r=r[0]; if(!s.group[d.ke]){s.group[d.ke]={users:{}}} @@ -3337,14 +3677,14 @@ var tx; d.condition.push(v+'=?') d.value.push(d.form[v]) }) - d.value=d.value.concat([cn.ke,d.$uid]) + d.value=d.value.concat([d.ke,d.$uid]) s.sqlQuery("UPDATE Users SET "+d.condition.join(',')+" WHERE ke=? AND uid=?",d.value) - s.tx({f:'edit_sub_account',ke:cn.ke,uid:d.$uid,mail:d.mail,form:d.form},'ADM_'+d.ke); + s.tx({f:'edit_sub_account',ke:d.ke,uid:d.$uid,mail:d.mail,form:d.form},'ADM_'+d.ke); break; case'delete': - s.sqlQuery('DELETE FROM Users WHERE uid=? AND ke=? AND mail=?',[d.$uid,cn.ke,d.mail]) - s.sqlQuery('DELETE FROM API WHERE uid=? AND ke=?',[d.$uid,cn.ke]) - s.tx({f:'delete_sub_account',ke:cn.ke,uid:d.$uid,mail:d.mail},'ADM_'+d.ke); + s.sqlQuery('DELETE FROM Users WHERE uid=? AND ke=? AND mail=?',[d.$uid,d.ke,d.mail]) + s.sqlQuery('DELETE FROM API WHERE uid=? AND ke=?',[d.$uid,d.ke]) + s.tx({f:'delete_sub_account',ke:d.ke,uid:d.$uid,mail:d.mail},'ADM_'+d.ke); break; } break; @@ -3457,8 +3797,10 @@ var tx; } }) cn.on('disconnect', function () { - if(cn.flvStream){ - s.group[cn.ke].mon[cn.flvStream].emitter.removeListener('data',s.group[cn.ke].mon[cn.flvStream].contentWriter) + if(cn.removeListenerOnDisconnect){ + s.group[cn.ke].mon[cn.socketVideoStream].emitter.removeListener('data',s.group[cn.ke].mon[cn.socketVideoStream].contentWriter[cn.channel]) + } + if(cn.socketVideoStream){ return } if(cn.ke){ @@ -3469,8 +3811,7 @@ var tx; s.camera('watch_off',{id:v,ke:cn.monitor_watching[v].ke},s.cn(cn)) }) } - } - if(!cn.embedded){ + }else if(!cn.embedded){ if(s.group[cn.ke].users[cn.auth].login_type==='Dashboard'){ s.tx({f:'user_status_change',ke:cn.ke,uid:cn.uid,status:0}) } @@ -4129,14 +4470,14 @@ app.get(['/:auth/flv/:ke/:id/s.flv','/:auth/flv/:ke/:id/:channel/s.flv'], functi Emitter = s.group[req.params.ke].mon[req.params.id].emitterChannel[parseInt(req.params.channel)+config.pipeAddition] chunkChannel = parseInt(req.params.channel)+config.pipeAddition } - if(s.group[req.params.ke].mon[req.params.id].firstFLVchunk[chunkChannel]){ + if(s.group[req.params.ke].mon[req.params.id].firstStreamChunk[chunkChannel]){ //variable name of contentWriter var contentWriter //set headers res.setHeader('Content-Type', 'video/x-flv'); res.setHeader('Access-Control-Allow-Origin','*'); //write first frame on stream - res.write(s.group[req.params.ke].mon[req.params.id].firstFLVchunk[chunkChannel]) + res.write(s.group[req.params.ke].mon[req.params.id].firstStreamChunk[chunkChannel]) //write new frames as they happen Emitter.on('data',contentWriter=function(buffer){ res.write(buffer) @@ -4207,6 +4548,7 @@ app.get(['/:auth/embed/:ke/:id','/:auth/embed/:ke/:id/:addon'], function (req,re } if(s.group[req.params.ke]&&s.group[req.params.ke].mon[req.params.id]){ if(s.group[req.params.ke].mon[req.params.id].started===1){ + req.params.uid=user.uid; res.render("embed",{data:req.params,baseUrl:req.protocol+'://'+req.hostname,config:config,lang:user.lang,mon:CircularJSON.parse(CircularJSON.stringify(s.group[req.params.ke].mon_conf[req.params.id]))}); res.end() }else{ @@ -4224,7 +4566,7 @@ app.get(['/:auth/tvChannels/:ke','/:auth/tvChannels/:ke/:id','/get.php'], functi req.params.username = req.query.username req.params.password = req.query.password } - var output = ['h264','hls'] + var output = ['h264','hls','mp4'] if(req.query.output&&req.query.output!==''){ output = req.query.output.split(',') output.forEach(function(type,n){ @@ -4292,6 +4634,9 @@ app.get(['/:auth/tvChannels/:ke','/:auth/tvChannels/:ke/:id','/get.php'], functi case'flv': streamURL='/'+req.params.auth+'/flv/'+v.ke+'/'+v.mid+channelNumber+'/s.flv' break; + case'mp4': + streamURL='/'+req.params.auth+'/mp4/'+v.ke+'/'+v.mid+channelNumber+'/s.ts' + break; } if(streamURL){ if(!channelRow.streamsSortedByType[type]){ @@ -4406,6 +4751,9 @@ app.get(['/:auth/monitor/:ke','/:auth/monitor/:ke/:id'], function (req,res){ case'flv': streamURL='/'+req.params.auth+'/flv/'+v.ke+'/'+v.mid+'/'+m+'/s.flv' break; + case'mp4': + streamURL='/'+req.params.auth+'/mp4/'+v.ke+'/'+v.mid+'/'+m+'/s.mp4' + break; } r[n].channels.push(streamURL) }) @@ -4570,7 +4918,7 @@ app.get(['/:auth/events/:ke','/:auth/events/:ke/:id','/:auth/events/:ke/:id/:lim },res,req); }); // Get logs json -app.get(['/:auth/logs/:ke','/:auth/logs/:ke/:id','/:auth/logs/:ke/:limit','/:auth/logs/:ke/:id/:limit'], function (req,res){ +app.get(['/:auth/logs/:ke','/: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); @@ -4597,8 +4945,8 @@ app.get(['/:auth/logs/:ke','/:auth/logs/:ke/:id','/:auth/logs/:ke/:limit','/:aut return; } } - if(!req.params.limit||req.params.limit==''){req.params.limit=100} - req.sql+=' ORDER BY `time` DESC LIMIT '+req.params.limit+''; + if(!req.query.limit||req.query.limit==''){req.query.limit=50} + req.sql+=' ORDER BY `time` DESC LIMIT '+req.query.limit+''; s.sqlQuery(req.sql,req.ar,function(err,r){ if(err){ err.sql=req.sql; @@ -5051,16 +5399,51 @@ app.all(['/streamIn/:ke/:id','/streamIn/:ke/:id/:feed'], function (req, res) { res.end('Local connection is only allowed.') } }) +//MP4 Stream +app.get(['/:auth/mp4/:ke/:id/:channel/s.mp4','/:auth/mp4/:ke/:id/s.mp4','/:auth/mp4/:ke/:id/:channel/s.ts','/:auth/mp4/:ke/:id/s.ts'], function (req, res) { + res.header("Access-Control-Allow-Origin",req.headers.origin); + s.auth(req.params,function(user){ + var Channel = 'MAIN' + if(req.params.channel){ + Channel = parseInt(req.params.channel)+config.pipeAddition + } + var mp4frag = s.group[req.params.ke].mon[req.params.id].mp4frag[Channel]; + if(!mp4frag){ + res.status(503); + res.end('MP4 Stream is not enabled'); + }else{ + var init = mp4frag.initialization; + if (!init) { + //browser may have requested init segment before it was ready + res.status(503); + res.end('resource not ready'); + } else { + res.status(200); + res.write(init); + mp4frag.pipe(res); + res.on('close', () => { + mp4frag.unpipe(res); + }); + } + } + }); +}); //simulate RTSP over HTTP -app.get(['/:auth/h264/:ke/:id/:channel','/:auth/h264/:ke/:id'], function (req, res) { +app.get([ + '/:auth/mpegts/:ke/:id/:feed/:file', + '/:auth/mpegts/:ke/:id/:feed/', + '/:auth/h264/:ke/:id/:feed/:file', + '/:auth/h264/:ke/:id/:feed', + '/:auth/h264/:ke/:id' +], function (req, res) { res.header("Access-Control-Allow-Origin",req.headers.origin); s.auth(req.params,function(user){ if(!req.query.feed){req.query.feed='1'} var Emitter - if(!req.params.channel){ + if(!req.params.feed){ Emitter = s.group[req.params.ke].mon[req.params.id].streamIn[req.query.feed] }else{ - Emitter = s.group[req.params.ke].mon[req.params.id].emitterChannel[parseInt(req.params.channel)+config.pipeAddition] + Emitter = s.group[req.params.ke].mon[req.params.id].emitterChannel[parseInt(req.params.feed)+config.pipeAddition] } s.init('streamIn',req.params) var contentWriter @@ -5140,14 +5523,21 @@ s.cpuUsage=function(e){ k.cmd='LANG=C top -b -n 2 | grep "^'+config.cpuUsageMarker+'" | awk \'{print $2}\' | tail -n1'; break; } - if(k.cmd){ + if(config.customCpuCommand){ + exec(config.customCpuCommand,{encoding:'utf8',detached: true},function(err,d){ + if(s.isWin===true) { + d = d.replace(/(\r\n|\n|\r)/gm, "").replace(/%/g, "") + } + e(d) + }); + } else if(k.cmd){ exec(k.cmd,{encoding:'utf8',detached: true},function(err,d){ if(s.isWin===true){ d=d.replace(/(\r\n|\n|\r)/gm,"").replace(/%/g,"") } e(d) }); - }else{ + } else{ e(0) } } @@ -5194,6 +5584,10 @@ s.beat=function(){ io.sockets.emit('ping',{beat:1}); } s.beat(); +s.processReady = function(){ + s.systemLog(lang.startUpText5) + process.send('ready') +} setTimeout(function(){ //get current disk used for each isolated account (admin user) on startup s.sqlQuery('SELECT * FROM Users WHERE details NOT LIKE ?',['%"sub"%'],function(err,r){ @@ -5247,14 +5641,15 @@ setTimeout(function(){ s.camera(v.mode,r.ar); }); } - s.systemLog(lang.startUpText5) - process.send('ready') + s.processReady() }); },3000) }) } }) }) + }else{ + s.processReady() } }) },1500) diff --git a/conf.sample.json b/conf.sample.json index f88a5bda..e24865c8 100644 --- a/conf.sample.json +++ b/conf.sample.json @@ -22,6 +22,7 @@ }, "pluginKeys":{ "Motion":"change_this_to_something_very_random____make_sure_to_match__/plugins/motion/conf.json", - "OpenCV":"change_this_to_something_very_random____make_sure_to_match__/plugins/opencv/conf.json" + "OpenCV":"change_this_to_something_very_random____make_sure_to_match__/plugins/opencv/conf.json", + "OpenALPR":"SomeOpenALPRkeySoPeopleDontMessWithYourShinobi" } } \ No newline at end of file diff --git a/cron.js b/cron.js index 482e5a00..74c674d4 100644 --- a/cron.js +++ b/cron.js @@ -3,12 +3,11 @@ process.on('uncaughtException', function (err) { }); var fs = require('fs'); var path = require('path'); -var mysql = require('mysql'); +var knex = require('knex'); var moment = require('moment'); var exec = require('child_process').exec; var spawn = require('child_process').spawn; var config=require('./conf.json'); -var sql=mysql.createConnection(config.db); //set option defaults s={}; @@ -22,11 +21,75 @@ if(config.cron.deleteLogs===undefined)config.cron.deleteLogs=true; if(config.cron.deleteEvents===undefined)config.cron.deleteEvents=true; if(config.cron.deleteFileBins===undefined)config.cron.deleteFileBins=true; if(config.cron.interval===undefined)config.cron.interval=1; +if(config.databaseType===undefined){config.databaseType='mysql'} +if(config.databaseLogs===undefined){config.databaseLogs=false} if(!config.ip||config.ip===''||config.ip.indexOf('0.0.0.0')>-1)config.ip='localhost'; if(!config.videosDir)config.videosDir=__dirname+'/videos/'; if(!config.binDir){config.binDir=__dirname+'/fileBin/'} if(!config.addStorage){config.addStorage=[]} + +// Database Connection +var databaseOptions = { + client: config.databaseType, + connection: config.db, +} +if(databaseOptions.client.indexOf('sqlite')>-1){ + databaseOptions.client = 'sqlite3'; + databaseOptions.useNullAsDefault = true; +} +if(databaseOptions.client === 'sqlite3' && databaseOptions.connection.filename === undefined){ + databaseOptions.connection.filename = __dirname+"/shinobi.sqlite" +} +s.databaseEngine = knex(databaseOptions) +s.sqlQuery = function(query,values,onMoveOn,hideLog){ + if(!values){values=[]} + var valuesNotFunction = true; + if(typeof values === 'function'){ + var onMoveOn = values; + var values = []; + valuesNotFunction = false; + } + if(!onMoveOn){onMoveOn=function(){}} + if(values&&valuesNotFunction){ + var splitQuery = query.split('?') + var newQuery = '' + splitQuery.forEach(function(v,n){ + newQuery += v + if(values[n]){ + if(isNaN(values[n])){ + newQuery += "'"+values[n]+"'" + }else{ + newQuery += values[n] + } + } + }) + }else{ + newQuery = query + } + return s.databaseEngine.raw(newQuery) + .asCallback(function(err,r){ + if(err&&config.databaseLogs){ + s.systemLog('s.sqlQuery QUERY',query) + s.systemLog('s.sqlQuery ERROR',err) + } + if(onMoveOn) + if(typeof onMoveOn === 'function'){ + switch(databaseOptions.client){ + case'sqlite3': + if(!r)r=[] + break; + default: + if(r)r=r[0] + break; + } + onMoveOn(err,r) + }else{ + console.log(onMoveOn) + } + }) +} + //containers s.overlapLock={}; s.alreadyDeletedRowsWithNoVideosOnStart={}; @@ -93,7 +156,7 @@ s.checkFilterRules=function(v,callback){ "where":[{ "p1":"end", "p2":"<", - "p3":"NOW() - INTERVAL "+(v.maxVideoDays[v.mid]*24)+" HOUR", + "p3":"NOW() - INTERVAL "+(v.d.days*24)+" HOUR", "p3_type":"function", }] }; @@ -124,7 +187,7 @@ s.checkFilterRules=function(v,callback){ if(b.limit&&b.limit!==''){ b.sql+=' LIMIT '+b.limit } - sql.query('SELECT * FROM Videos '+b.sql,b.ar,function(err,r){ + s.sqlQuery('SELECT * FROM Videos '+b.sql,b.ar,function(err,r){ if(r&&r[0]){ b.cx={ f:'filters', @@ -176,7 +239,7 @@ s.deleteRowsWithNoVideo=function(v,callback){ ){ s.alreadyDeletedRowsWithNoVideosOnStart[v.ke]=true; es={}; - sql.query('SELECT * FROM Videos WHERE ke = ? AND status != 0 AND details NOT LIKE \'%"archived":"1"%\' AND time < (NOW() - INTERVAL 10 MINUTE)',[v.ke],function(err,evs){ + s.sqlQuery('SELECT * FROM Videos WHERE ke=? AND status!=0 AND details NOT LIKE \'%"archived":"1"%\' AND time < (NOW() - INTERVAL 10 MINUTE)',[v.ke],function(err,evs){ if(evs&&evs[0]){ es.del=[];es.ar=[v.ke]; evs.forEach(function(ev){ @@ -204,7 +267,7 @@ s.deleteRowsWithNoVideo=function(v,callback){ s.deleteOldLogs=function(v,callback){ if(!v.d.log_days||v.d.log_days==''){v.d.log_days=10}else{v.d.log_days=parseFloat(v.d.log_days)}; if(config.cron.deleteLogs===true&&v.d.log_days!==0){ - sql.query("DELETE FROM Logs WHERE ke=? AND `time` < DATE_SUB(NOW(), INTERVAL ? DAY)",[v.ke,v.d.log_days],function(err,rrr){ + s.sqlQuery("DELETE FROM Logs WHERE ke=? AND `time` < DATE_SUB(NOW(), INTERVAL ? DAY)",[v.ke,v.d.log_days],function(err,rrr){ callback() if(err)return console.error(err); if(rrr.affectedRows.length>0){ @@ -219,7 +282,7 @@ s.deleteOldLogs=function(v,callback){ s.deleteOldEvents=function(v,callback){ if(!v.d.event_days||v.d.event_days==''){v.d.event_days=10}else{v.d.event_days=parseFloat(v.d.event_days)}; if(config.cron.deleteEvents===true&&v.d.event_days!==0){ - sql.query("DELETE FROM Events WHERE ke=? AND `time` < DATE_SUB(NOW(), INTERVAL ? DAY)",[v.ke,v.d.event_days],function(err,rrr){ + s.sqlQuery("DELETE FROM Events WHERE ke=? AND `time` < DATE_SUB(NOW(), INTERVAL ? DAY)",[v.ke,v.d.event_days],function(err,rrr){ callback() if(err)return console.error(err); if(rrr.affectedRows.length>0){ @@ -235,7 +298,7 @@ s.deleteOldFileBins=function(v,callback){ if(!v.d.fileBin_days||v.d.fileBin_days==''){v.d.fileBin_days=10}else{v.d.fileBin_days=parseFloat(v.d.fileBin_days)}; if(config.cron.deleteFileBins===true&&v.d.fileBin_days!==0){ var fileBinQuery = ' FROM Files WHERE ke=? AND `date` < DATE_SUB(NOW(), INTERVAL ? DAY)'; - sql.query("SELECT *"+fileBinQuery,[v.ke,v.d.fileBin_days],function(err,files){ + s.sqlQuery("SELECT *"+fileBinQuery,[v.ke,v.d.fileBin_days],function(err,files){ if(files&&files[0]){ //delete the files files.forEach(function(file){ @@ -244,7 +307,7 @@ s.deleteOldFileBins=function(v,callback){ }) }) //delete the database rows - sql.query("DELETE"+fileBinQuery,[v.ke,v.d.fileBin_days],function(err,rrr){ + s.sqlQuery("DELETE"+fileBinQuery,[v.ke,v.d.fileBin_days],function(err,rrr){ callback() if(err)return console.error(err); if(rrr.affectedRows.length>0){ @@ -270,7 +333,7 @@ s.checkForOrphanedFiles=function(v,callback){ } e={}; var numberOfItems = 0; - sql.query('SELECT * FROM Monitors WHERE ke=?',[v.ke],function(arr,b) { + s.sqlQuery('SELECT * FROM Monitors WHERE ke=?',[v.ke],function(arr,b) { if(b&&b[0]){ b.forEach(function(mon,m){ fs.readdir(s.getVideoDirectory(mon), function(err, items) { @@ -282,7 +345,7 @@ s.checkForOrphanedFiles=function(v,callback){ e.query.push('time=?') e.filesFound.push(s.nameToTime(v)) }) - sql.query('SELECT * FROM Videos WHERE ke=? AND mid=? AND ('+e.query.join(' OR ')+')',e.filesFound,function(arr,r) { + s.sqlQuery('SELECT * FROM Videos WHERE ke=? AND mid=? AND ('+e.query.join(' OR ')+')',e.filesFound,function(arr,r) { if(!r){r=[]}; e.foundSQLrows=[]; r.forEach(function(v,n){ @@ -333,16 +396,34 @@ s.processUser = function(number,rows){ //size if(!v.d.size||v.d.size==''){v.d.size=10000}else{v.d.size=parseFloat(v.d.size)}; //days to keep videos - v.maxVideoDays={} if(!v.d.days||v.d.days==''){v.d.days=5}else{v.d.days=parseFloat(v.d.days)}; - sql.query('SELECT * FROM Monitors WHERE ke=?', [v.ke], function(err,rr) { + s.sqlQuery('SELECT * FROM Monitors WHERE ke=?', [v.ke], function(err,rr) { rr.forEach(function(b,m){ b.details=JSON.parse(b.details); if(b.details.max_keep_days&&b.details.max_keep_days!==''){ - v.maxVideoDays[b.mid]=parseFloat(b.details.max_keep_days) - }else{ - v.maxVideoDays[b.mid]=v.d.days - }; + v.d.filters['deleteOldByCron'+b.mid]={ + "id":'deleteOldByCron'+b.mid, + "name":'deleteOldByCron'+b.mid, + "sort_by":"time", + "sort_by_direction":"ASC", + "limit":"", + "enabled":"1", + "archive":"0", + "email":"0", + "delete":"1", + "execute":"", + "where":[{ + "p1":"ke", + "p2":"=", + "p3":b.mid + },{ + "p1":"end", + "p2":"<", + "p3":"NOW() - INTERVAL "+(parseFloat(b.details.max_keep_days)*24)+" HOUR", + "p3_type":"function", + }] + }; + } }) s.deleteOldLogs(v,function(){ s.deleteOldFileBins(v,function(){ @@ -366,7 +447,7 @@ s.processUser = function(number,rows){ s.cron=function(){ x={}; s.cx({f:'start',time:moment()}) - sql.query('SELECT ke,uid,details,mail FROM Users WHERE details NOT LIKE \'%"sub"%\'', function(err,rows) { + s.sqlQuery('SELECT ke,uid,details,mail FROM Users WHERE details NOT LIKE \'%"sub"%\'', function(err,rows) { if(err){ console.error(err) } diff --git a/docker-compose-nodb.yml b/docker-compose-nodb.yml deleted file mode 100644 index 200ad193..00000000 --- a/docker-compose-nodb.yml +++ /dev/null @@ -1,5 +0,0 @@ -version: "2" -services: - db: - command: "/bin/true" - restart: "no" diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index a49559b7..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,67 +0,0 @@ -version: '2' -services: - db: - image: mysql:5.6 - restart: always - volumes: - - ./sql/docker:/docker-entrypoint-initdb.d:ro - - ./dbdata:/var/lib/mysql - environment: - MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} - MYSQL_USER: ${MYSQL_USER} - MYSQL_PASSWORD: ${MYSQL_PASSWORD} - MYSQL_DATABASE: ${MYSQL_DATABASE} - camera: - depends_on: - - "db" - command: /usr/bin/node /opt/shinobi/camera.js - build: - context: . - restart: always - environment: - ADMIN_PASSWORD: ${ADMIN_PASSWORD} - MYSQL_HOST: ${MYSQL_HOST} - MYSQL_DATABASE: ${MYSQL_DATABASE} - MYSQL_USER: ${MYSQL_USER} - MYSQL_PASSWORD: ${MYSQL_PASSWORD} - TIMEZONE_OFFSET: ${TIMEZONE_OFFSET} - ports: - - 8080:8080 - volumes: - - ./videos:/opt/shinobi/videos - links: - - db:db - cron: - depends_on: - - "db" - command: /usr/bin/node /opt/shinobi/cron.js - build: - context: . - restart: always - environment: - MYSQL_HOST: ${MYSQL_HOST} - MYSQL_DATABASE: ${MYSQL_DATABASE} - MYSQL_USER: ${MYSQL_USER} - MYSQL_PASSWORD: ${MYSQL_PASSWORD} - TIMEZONE_OFFSET: ${TIMEZONE_OFFSET} - links: - - db:db - volumes: - - ./videos:/opt/shinobi/videos - motion: - depends_on: - - "db" - command: /usr/bin/node /opt/shinobi/plugins/motion/shinobi-motion.js - build: - context: . - restart: always - environment: - MYSQL_HOST: ${MYSQL_HOST} - MYSQL_DATABASE: ${MYSQL_DATABASE} - MYSQL_USER: ${MYSQL_USER} - MYSQL_PASSWORD: ${MYSQL_PASSWORD} - TIMEZONE_OFFSET: ${TIMEZONE_OFFSET} - links: - - db:db - volumes: - - ./videos:/opt/shinobi/videos diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh deleted file mode 100755 index cb15d397..00000000 --- a/docker-entrypoint.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh -set -e - -cp /opt/shinobi/conf.sample.json /opt/shinobi/conf.json -cp /opt/shinobi/super.sample.json /opt/shinobi/super.json -cp /opt/shinobi/plugins/motion/conf.sample.json /opt/shinobi/plugins/motion/conf.json - -ADMIN_PASSWORD_MD5=$(echo -n "${ADMIN_PASSWORD}" | md5sum | sed -e 's/ -$//') - -#set config data from variables -sed -i -e 's/"user": "majesticflame"/"user": "'"${MYSQL_USER}"'"/g' \ - -e 's/"password": ""/"password": "'"${MYSQL_PASSWORD}"'"/g' \ - -e 's/"host": "127.0.0.1"/"host": "'"${MYSQL_HOST}"'"/g' \ - -e 's/"database": "ccio"/"database": "'"${MYSQL_DATABASE}"'"/g' \ - "/opt/shinobi/conf.json" -# Set the admin password -sed -i -e "s/21232f297a57a5a743894a0e4a801fc3/${ADMIN_PASSWORD_MD5}/" "/opt/shinobi/super.json" - -# Execute Command -exec "$@" diff --git a/docker.env b/docker.env deleted file mode 100644 index 43203d4e..00000000 --- a/docker.env +++ /dev/null @@ -1,2 +0,0 @@ -MYSQL_USER=root -MYSQL_PASSWORD=night \ No newline at end of file diff --git a/languages/en_CA.json b/languages/en_CA.json index db35e08e..085d3c3b 100644 --- a/languages/en_CA.json +++ b/languages/en_CA.json @@ -24,6 +24,9 @@ "Accounts": "Accounts", "Settings": "Settings", "Recording FPS": "Recording FPS", + "Input Selector": "Input Selector", + "Input Settings": "Input Settings", + "Connection": "Connection", "API": "API", "ONVIF": "ONVIF", "FFprobe": "Probe", @@ -68,6 +71,7 @@ "Event Limit": "Event Limit", "No Data": "No Data", "Live View": "Live View", + "New Monitor": "New Monitor", "Add": "Add", "Save": "Save", "Close": "Close", @@ -249,6 +253,7 @@ "years": "years", "Identity": "Identity", "Input": "Input", + "Input Feed": "Input Feed", "Stream": "Stream", "Stream Timestamp": "Stream Timestamp", "Stream Watermark": "Stream Watermark", @@ -393,6 +398,7 @@ "Shinobi Streamer": "Shinobi Streamer", "Dashcam (Streamer v2)": "Dashcam (Streamer v2)", "Local": "Local", + "Raw": "Raw", "HTTP": "HTTP", "HTTPS": "HTTPS", "RTSP": "RTSP", @@ -421,6 +427,7 @@ "Bottom Right": "Bottom Right", "Bottom Left": "Bottom Left", "WebM (libvpx)": "WebM (libvpx)", + "Poseidon": "Poseidon", "MP4 (copy, libx264, libx265)": "MP4 (copy, libx264, libx265)", "Default": "Default", "libvpx (Default)": "libvpx (Default)", @@ -485,6 +492,8 @@ "Incorrect Settings Chosen": "Incorrect Settings Chosen", "Can't Connect": "Can't Connect", "Video Finished": "Video Finished", + "No Monitors Selected": "No Monitors 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", "Detector Buffer": "Detector Buffer", @@ -528,6 +537,7 @@ "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", @@ -602,6 +612,7 @@ "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", @@ -611,8 +622,17 @@ "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", + "Map":"Map", + "Add Map":"Add Map", + "Add Input Feed":"Add Input Feed", "Add Channel":"Add Channel", "Automatic":"Automatic", "Max Latency":"Max Latency", @@ -626,5 +646,18 @@ "Emotion Average":"Emotion Average", "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 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":"Monitors to Copy to", "Use Built-In":"Use Built-In" } \ No newline at end of file diff --git a/package.json b/package.json index d3982e47..8579b21e 100644 --- a/package.json +++ b/package.json @@ -22,12 +22,15 @@ "circular-json": "0.3.1", "connection-tester": "^0.1.1", "crypto": "^0.0.3", + "mp4frag": "^0.0.15", "ejs": "^2.5.5", "express": "^4.14.0", "jsonfile": "^3.0.1", "moment": "^2.17.0", "mysql": "^2.12.0", + "sqlite3": "^3.1.13", "knex": "^0.14.2", + "ffmpeg-static": "^2.1.0", "pam-diff": "0.10.2", "pipe2pam": "0.6.2", "nodemailer": "^4.0.1", diff --git a/sql/.gitignore b/sql/.gitignore index 87b28442..27e94883 100644 --- a/sql/.gitignore +++ b/sql/.gitignore @@ -1,2 +1,3 @@ monitors.sql -users.sql \ No newline at end of file +users.sql +shinobi.sqlite \ No newline at end of file diff --git a/sql/shinobi.sample.sqlite b/sql/shinobi.sample.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..2796be7b20f18543d31fb234bb1a23f32428034f GIT binary patch literal 40960 zcmeHQ&2QY+5!cF=WJ_Kvv7@T0>w?)hkOkNhxnJ7dqODa&PMt`KZAnoYTP3(8cb5^D zq)G0|ifg1RqbSgOPdW9}Q!fRK0_{K0{tdnK(o;`86ey5OXCA*-)JGFJ-59gBR(vz> zW8QD(4c~j@8?E18(Oi{ln08xoxx&zELz&FbEsh%+8sgz+9Dah&C|sNk67V|{yFToa zADYj6l%4qf(AfFWp~>9ER`!n*FUI#K_900hLVyq;1PB2_fDj-A{*MUkTVrF>Gc%cA ze&3bqy85A}sHWq6pS%5Fd1+&r+gSSX$};ECxa;E_*VYuSE7@{Ovagr;Iqse1J4>4@ z8{FFF%E}Gk@2E$z)h90Jmlje=yINc2@XK+fwsjqHV7&(#Q98EdYNo*{W(Nf4EL)W| z2MPV+INGP`@wpnd1Wj_9;U1_!HIx$w1me1#WCgSQEQf$7sw-)_!@1D)@wxrck+JFN z>CE#T2KlC=+79~~O#seF4DVy`>>Q&GspGbeR7e|y3o{6OicT!aagNRrbWt~rCUz6M zefxv8^^FHhch@#J*2L!8-TRx%9Csa4bK`S!qodP5n9dAohN3=k9_uh27eQE)rA5|% zQDDEuV1%5*QS$t=vt!d&uV#LApJBIZ8k%d`PVjpqfnY#A4Wl){Izj}}jNWOvCQ~r> z+-WuJFp&n0yJw|WH~WdDHfOO8942X_Y<5?zw^?bC#or- z-dTR0{qt&*VrG)ew%}zjZ8tGKW-B~nG^{?e7Z|y1Dp4PW(m_A5t0~ZAe8kO`i(Zv2 zRcp5TS}Tjd<2;#WG$N&@IHnIgrx$vR z+nWy_EU#^d8+TWi*Eg0{?{OgT;xO~D`*)eZ%U#uQ9q;>0LTHb68e#_)7YxKNEBV9B z*!1PgndeuToB&>;>}53Cl@yNGIrF{HBqnmn`~|*YJxz zga9Ex2oM5<03kpK5CVh%AwUQa0)zk|@Twv}>;G3(v(zs_fDj-A2mwNX5Fi8y0YZQf zAOr{jLLfq51THg^Um#(8@{5Z@js8hasIP&|2p^H*o(2Z zM}IRqGV-&te?6N!^XuV%4{vAwlHrGb8`bqvu6h@)j!sVZnuTlEHrJOQJk8-tL%CXR z3Et;|cStnZd;jeYyiBDkxf{8*)6CVL=HNgBWKtxjg*0G7N>bXIam#6GhFRCbs*e(>=3r`(wAc zl~2mFrPdu&-cjM@In9wx!%$^6E|f2aSM=7qyXLC&lX=-}qZA5Aaqr`gc%%8D-sb-& zxhl0{v3KbT%B3A6*X^%Us6ub*JA(*TzISP6(7-AbdsE*YK(Gph-nD6vYd@Q6S!?Pp z*EbS3%pKLhL3j8-JpBOxVqW^zpy@PU?OmH1eC$BX>>KD~wSFHP`Lh(i;d$8gj-}ci zczHMQVPDnBR%-b|FMD}_4HNM)Lm_%Kw z{kGoc#nR^!4q7|c>tzod9Ws-ldC!rAZ#m>sofuH3?!0z3hd78I^pE2QM)( zDDo7mwNkm49UmBZ7HdVmH+~-Fk-yZ)OU~rK(!eX0;1<(7H#&KxHv_V)|8#w0c{N;D z+_p7bROnpCQY1JTonOdvYw(7eaSuG5vN*dackt1dk|FIJo`2D0K9B@>& z0an}`e)&BRsF9o32bxV?uKEyssq<> zsc5)W0~dJeO~i9rqH5qVOrQ#JGNf(Sv1$toLP40XmgWnvS(C3-_yy^S=G<(niY6_v z@A*R0f|8h9bZyCSEI73V#a$W6ZMI%vO}6n&pVvBO5n)jyL%T*t*Fm1(bR`PHvk#Pp z^JaAwPg5qS4(qF^ng6^?4MT`BB;YnRc2yna&b;-Mee6DXi5 zYF9&{5F)=Be-EsocOx1kM4yg_-jWOeeg*DkhP;=kr;X?AL>X>ua2hMY2?dc&YcHxx z3gk0z>#U_aBA%*(l6gO=YoI)Iy&g1AvrGAsWlBS3c01qJHQeU zmpBd3guOgwE`GlscO*C;iG2`CnAro}!x<&;2@c%9RS`|CpXPTTjtl}Cp;W%Ux(%;{ z+Xa~<*u8^ILyr|vM7B`})y7Co+w6dyX)ZI_sLAm7ZcvRcUPuRXGtewkiZE6HWEvk% zJThZSnGQ^)Oe5^QA>rBx z$AbfxqNlbXmDCyXJxU-GQUV3_8>eie=CESl+~5?nY2%J6kdh{=JjSfp9T~h$gC*v% zq;C>INl))VgCRtI*)Sc`V3HQ7ju~~t3beA@f@8kWYQ%}_jDO;J8=k*~qD7fdao2Zm zy1R1=XzmdMQNm7z=m(&Ogg$LW^nNwT9T>ryVRd}VWNB0wGyyHJq%G~X^5tTT0!I;H zvpYucZ2bU1gsq}3tf6Cd#dL+0n{CnTxMA~N#i8E}lA_ZBnW}p{+To^Dr%wZm6IZ`*g z?}Wm!)A(X4%in=W^bu)?RPUL!*jC+^iQ~e7ryenMTw7~48E6SWeX{764hL)~VAM^X z4I;2Axuuv*C;?s{+DXW#z&#MWZren!psS7}vS%>CO){+2flvdY7l{C>NJwO>O(-jx z4X{wqGDJt{ZzQ@IMD>NY12|Y6?{?&2ij@QdCJ4Tm2Mh*TUd^Ie|Ag6mpJgz~`vr$S zp*QS$cP|hE9TfKb>7(3cQFw3V5j?eel*0q|a23*SJj%sNGEV7H4yYEy2XGxAJbd^l z$1@UqZ{G-0Fk$i2f>37sLRs)BCBDcg`3n4IWy<^_qm-a_MqtIEouHBul!YYJw7vc8 zSscWAr_oUDXy$^mxD@_DK&}i^z5uT{T~`}{8}~%O_C%zz z3O(rJ1)$rlloE`5NRdu(FNw7PGbq0wvHoJyin$9@2zKj2E1-!+BXmnFbX$~$d zQhA@6-ks-Qsep4xdyK86$mla-#n?;{NBzTX=p{^Vb+wDLOt^2`hGsN@2oXS{X>VxA zvIM?L306%0Jkp<}`t?1-J9679;-V+MK1y92-9xO0f06lnu>QaJhgWT(O8p@O2mwNX z5Fi8y0YZQfAOr{jLVytXW+Slwe&*cFo2v_}&+mKB8l$(S%cWAWx>#DQS4;ImMHcc> zeX-707nQtHEXnoif!9V#jiMsUg?dSn7Rx22EGU&ieo;~Dl}cW2RIB*hQig|i_;MJm z(s75yf3^izinNDIZ@7X*yy1#b8W!-e6g+zqW!TrVB`-I;Le|zC4AP3lC0)lF6fJyg zh8vzN?N=D46U~IwSYt@T{Ymx?Ce$6IVe?y&a62R*=!*X+<{1m{5Apvd|M|_f3X+}> zAOr{jLVyq;1PB2_fDj-A2mwNX5Fi8?0&kpIIDcRVBzXQ$7!d-503kpK5CVh%AwUQa z0)zk|KnM^5guttd0IvV9LJ+^n-%bAT;y)<!;{Qc~eDQ4SiJU&g{{y6=mv`aa)su%}ructHhN`Cc ze@D1vIpFWv3Hkp5b%R