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 00000000..2796be7b Binary files /dev/null and b/sql/shinobi.sample.sqlite differ diff --git a/stylesheets/github-light.css b/stylesheets/github-light.css deleted file mode 100644 index 0c6b24d8..00000000 --- a/stylesheets/github-light.css +++ /dev/null @@ -1,124 +0,0 @@ -/* -The MIT License (MIT) - -Copyright (c) 2016 GitHub, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -*/ - -.pl-c /* comment */ { - color: #969896; -} - -.pl-c1 /* constant, variable.other.constant, support, meta.property-name, support.constant, support.variable, meta.module-reference, markup.raw, meta.diff.header */, -.pl-s .pl-v /* string variable */ { - color: #0086b3; -} - -.pl-e /* entity */, -.pl-en /* entity.name */ { - color: #795da3; -} - -.pl-smi /* variable.parameter.function, storage.modifier.package, storage.modifier.import, storage.type.java, variable.other */, -.pl-s .pl-s1 /* string source */ { - color: #333; -} - -.pl-ent /* entity.name.tag */ { - color: #63a35c; -} - -.pl-k /* keyword, storage, storage.type */ { - color: #a71d5d; -} - -.pl-s /* string */, -.pl-pds /* punctuation.definition.string, string.regexp.character-class */, -.pl-s .pl-pse .pl-s1 /* string punctuation.section.embedded source */, -.pl-sr /* string.regexp */, -.pl-sr .pl-cce /* string.regexp constant.character.escape */, -.pl-sr .pl-sre /* string.regexp source.ruby.embedded */, -.pl-sr .pl-sra /* string.regexp string.regexp.arbitrary-repitition */ { - color: #183691; -} - -.pl-v /* variable */ { - color: #ed6a43; -} - -.pl-id /* invalid.deprecated */ { - color: #b52a1d; -} - -.pl-ii /* invalid.illegal */ { - color: #f8f8f8; - background-color: #b52a1d; -} - -.pl-sr .pl-cce /* string.regexp constant.character.escape */ { - font-weight: bold; - color: #63a35c; -} - -.pl-ml /* markup.list */ { - color: #693a17; -} - -.pl-mh /* markup.heading */, -.pl-mh .pl-en /* markup.heading entity.name */, -.pl-ms /* meta.separator */ { - font-weight: bold; - color: #1d3e81; -} - -.pl-mq /* markup.quote */ { - color: #008080; -} - -.pl-mi /* markup.italic */ { - font-style: italic; - color: #333; -} - -.pl-mb /* markup.bold */ { - font-weight: bold; - color: #333; -} - -.pl-md /* markup.deleted, meta.diff.header.from-file */ { - color: #bd2c00; - background-color: #ffecec; -} - -.pl-mi1 /* markup.inserted, meta.diff.header.to-file */ { - color: #55a532; - background-color: #eaffea; -} - -.pl-mdr /* meta.diff.range */ { - font-weight: bold; - color: #795da3; -} - -.pl-mo /* meta.output */ { - color: #1d3e81; -} - diff --git a/stylesheets/normalize.css b/stylesheets/normalize.css deleted file mode 100644 index 30366a6e..00000000 --- a/stylesheets/normalize.css +++ /dev/null @@ -1,424 +0,0 @@ -/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ - -/** - * 1. Set default font family to sans-serif. - * 2. Prevent iOS text size adjust after orientation change, without disabling - * user zoom. - */ - -html { - font-family: sans-serif; /* 1 */ - -ms-text-size-adjust: 100%; /* 2 */ - -webkit-text-size-adjust: 100%; /* 2 */ -} - -/** - * Remove default margin. - */ - -body { - margin: 0; -} - -/* HTML5 display definitions - ========================================================================== */ - -/** - * Correct `block` display not defined for any HTML5 element in IE 8/9. - * Correct `block` display not defined for `details` or `summary` in IE 10/11 - * and Firefox. - * Correct `block` display not defined for `main` in IE 11. - */ - -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -main, -menu, -nav, -section, -summary { - display: block; -} - -/** - * 1. Correct `inline-block` display not defined in IE 8/9. - * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. - */ - -audio, -canvas, -progress, -video { - display: inline-block; /* 1 */ - vertical-align: baseline; /* 2 */ -} - -/** - * Prevent modern browsers from displaying `audio` without controls. - * Remove excess height in iOS 5 devices. - */ - -audio:not([controls]) { - display: none; - height: 0; -} - -/** - * Address `[hidden]` styling not present in IE 8/9/10. - * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. - */ - -[hidden], -template { - display: none; -} - -/* Links - ========================================================================== */ - -/** - * Remove the gray background color from active links in IE 10. - */ - -a { - background-color: transparent; -} - -/** - * Improve readability when focused and also mouse hovered in all browsers. - */ - -a:active, -a:hover { - outline: 0; -} - -/* Text-level semantics - ========================================================================== */ - -/** - * Address styling not present in IE 8/9/10/11, Safari, and Chrome. - */ - -abbr[title] { - border-bottom: 1px dotted; -} - -/** - * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. - */ - -b, -strong { - font-weight: bold; -} - -/** - * Address styling not present in Safari and Chrome. - */ - -dfn { - font-style: italic; -} - -/** - * Address variable `h1` font-size and margin within `section` and `article` - * contexts in Firefox 4+, Safari, and Chrome. - */ - -h1 { - font-size: 2em; - margin: 0.67em 0; -} - -/** - * Address styling not present in IE 8/9. - */ - -mark { - background: #ff0; - color: #000; -} - -/** - * Address inconsistent and variable font size in all browsers. - */ - -small { - font-size: 80%; -} - -/** - * Prevent `sub` and `sup` affecting `line-height` in all browsers. - */ - -sub, -sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; -} - -sup { - top: -0.5em; -} - -sub { - bottom: -0.25em; -} - -/* Embedded content - ========================================================================== */ - -/** - * Remove border when inside `a` element in IE 8/9/10. - */ - -img { - border: 0; -} - -/** - * Correct overflow not hidden in IE 9/10/11. - */ - -svg:not(:root) { - overflow: hidden; -} - -/* Grouping content - ========================================================================== */ - -/** - * Address margin not present in IE 8/9 and Safari. - */ - -figure { - margin: 1em 40px; -} - -/** - * Address differences between Firefox and other browsers. - */ - -hr { - box-sizing: content-box; - height: 0; -} - -/** - * Contain overflow in all browsers. - */ - -pre { - overflow: auto; -} - -/** - * Address odd `em`-unit font size rendering in all browsers. - */ - -code, -kbd, -pre, -samp { - font-family: monospace, monospace; - font-size: 1em; -} - -/* Forms - ========================================================================== */ - -/** - * Known limitation: by default, Chrome and Safari on OS X allow very limited - * styling of `select`, unless a `border` property is set. - */ - -/** - * 1. Correct color not being inherited. - * Known issue: affects color of disabled elements. - * 2. Correct font properties not being inherited. - * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. - */ - -button, -input, -optgroup, -select, -textarea { - color: inherit; /* 1 */ - font: inherit; /* 2 */ - margin: 0; /* 3 */ -} - -/** - * Address `overflow` set to `hidden` in IE 8/9/10/11. - */ - -button { - overflow: visible; -} - -/** - * Address inconsistent `text-transform` inheritance for `button` and `select`. - * All other form control elements do not inherit `text-transform` values. - * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. - * Correct `select` style inheritance in Firefox. - */ - -button, -select { - text-transform: none; -} - -/** - * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` - * and `video` controls. - * 2. Correct inability to style clickable `input` types in iOS. - * 3. Improve usability and consistency of cursor style between image-type - * `input` and others. - */ - -button, -html input[type="button"], /* 1 */ -input[type="reset"], -input[type="submit"] { - -webkit-appearance: button; /* 2 */ - cursor: pointer; /* 3 */ -} - -/** - * Re-set default cursor for disabled elements. - */ - -button[disabled], -html input[disabled] { - cursor: default; -} - -/** - * Remove inner padding and border in Firefox 4+. - */ - -button::-moz-focus-inner, -input::-moz-focus-inner { - border: 0; - padding: 0; -} - -/** - * Address Firefox 4+ setting `line-height` on `input` using `!important` in - * the UA stylesheet. - */ - -input { - line-height: normal; -} - -/** - * It's recommended that you don't attempt to style these elements. - * Firefox's implementation doesn't respect box-sizing, padding, or width. - * - * 1. Address box sizing set to `content-box` in IE 8/9/10. - * 2. Remove excess padding in IE 8/9/10. - */ - -input[type="checkbox"], -input[type="radio"] { - box-sizing: border-box; /* 1 */ - padding: 0; /* 2 */ -} - -/** - * Fix the cursor style for Chrome's increment/decrement buttons. For certain - * `font-size` values of the `input`, it causes the cursor style of the - * decrement button to change from `default` to `text`. - */ - -input[type="number"]::-webkit-inner-spin-button, -input[type="number"]::-webkit-outer-spin-button { - height: auto; -} - -/** - * 1. Address `appearance` set to `searchfield` in Safari and Chrome. - * 2. Address `box-sizing` set to `border-box` in Safari and Chrome - * (include `-moz` to future-proof). - */ - -input[type="search"] { - -webkit-appearance: textfield; /* 1 */ /* 2 */ - box-sizing: content-box; -} - -/** - * Remove inner padding and search cancel button in Safari and Chrome on OS X. - * Safari (but not Chrome) clips the cancel button when the search input has - * padding (and `textfield` appearance). - */ - -input[type="search"]::-webkit-search-cancel-button, -input[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} - -/** - * Define consistent border, margin, and padding. - */ - -fieldset { - border: 1px solid #c0c0c0; - margin: 0 2px; - padding: 0.35em 0.625em 0.75em; -} - -/** - * 1. Correct `color` not being inherited in IE 8/9/10/11. - * 2. Remove padding so people aren't caught out if they zero out fieldsets. - */ - -legend { - border: 0; /* 1 */ - padding: 0; /* 2 */ -} - -/** - * Remove default vertical scrollbar in IE 8/9/10/11. - */ - -textarea { - overflow: auto; -} - -/** - * Don't inherit the `font-weight` (applied by a rule above). - * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. - */ - -optgroup { - font-weight: bold; -} - -/* Tables - ========================================================================== */ - -/** - * Remove most spacing between table cells. - */ - -table { - border-collapse: collapse; - border-spacing: 0; -} - -td, -th { - padding: 0; -} diff --git a/stylesheets/stylesheet.css b/stylesheets/stylesheet.css deleted file mode 100644 index b5f20c23..00000000 --- a/stylesheets/stylesheet.css +++ /dev/null @@ -1,245 +0,0 @@ -* { - box-sizing: border-box; } - -body { - padding: 0; - margin: 0; - font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 16px; - line-height: 1.5; - color: #606c71; } - -a { - color: #1e6bb8; - text-decoration: none; } - a:hover { - text-decoration: underline; } - -.btn { - display: inline-block; - margin-bottom: 1rem; - color: rgba(255, 255, 255, 0.7); - background-color: rgba(255, 255, 255, 0.08); - border-color: rgba(255, 255, 255, 0.2); - border-style: solid; - border-width: 1px; - border-radius: 0.3rem; - transition: color 0.2s, background-color 0.2s, border-color 0.2s; } - .btn + .btn { - margin-left: 1rem; } - -.btn:hover { - color: rgba(255, 255, 255, 0.8); - text-decoration: none; - background-color: rgba(255, 255, 255, 0.2); - border-color: rgba(255, 255, 255, 0.3); } - -@media screen and (min-width: 64em) { - .btn { - padding: 0.75rem 1rem; } } - -@media screen and (min-width: 42em) and (max-width: 64em) { - .btn { - padding: 0.6rem 0.9rem; - font-size: 0.9rem; } } - -@media screen and (max-width: 42em) { - .btn { - display: block; - width: 100%; - padding: 0.75rem; - font-size: 0.9rem; } - .btn + .btn { - margin-top: 1rem; - margin-left: 0; } } - -.page-header { - color: #fff; - text-align: center; - background-color: #159957; - background-image: linear-gradient(120deg, #155799, #159957); } - -@media screen and (min-width: 64em) { - .page-header { - padding: 5rem 6rem; } } - -@media screen and (min-width: 42em) and (max-width: 64em) { - .page-header { - padding: 3rem 4rem; } } - -@media screen and (max-width: 42em) { - .page-header { - padding: 2rem 1rem; } } - -.project-name { - margin-top: 0; - margin-bottom: 0.1rem; } - -@media screen and (min-width: 64em) { - .project-name { - font-size: 3.25rem; } } - -@media screen and (min-width: 42em) and (max-width: 64em) { - .project-name { - font-size: 2.25rem; } } - -@media screen and (max-width: 42em) { - .project-name { - font-size: 1.75rem; } } - -.project-tagline { - margin-bottom: 2rem; - font-weight: normal; - opacity: 0.7; } - -@media screen and (min-width: 64em) { - .project-tagline { - font-size: 1.25rem; } } - -@media screen and (min-width: 42em) and (max-width: 64em) { - .project-tagline { - font-size: 1.15rem; } } - -@media screen and (max-width: 42em) { - .project-tagline { - font-size: 1rem; } } - -.main-content :first-child { - margin-top: 0; } -.main-content img { - max-width: 100%; } -.main-content h1, .main-content h2, .main-content h3, .main-content h4, .main-content h5, .main-content h6 { - margin-top: 2rem; - margin-bottom: 1rem; - font-weight: normal; - color: #159957; } -.main-content p { - margin-bottom: 1em; } -.main-content code { - padding: 2px 4px; - font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; - font-size: 0.9rem; - color: #383e41; - background-color: #f3f6fa; - border-radius: 0.3rem; } -.main-content pre { - padding: 0.8rem; - margin-top: 0; - margin-bottom: 1rem; - font: 1rem Consolas, "Liberation Mono", Menlo, Courier, monospace; - color: #567482; - word-wrap: normal; - background-color: #f3f6fa; - border: solid 1px #dce6f0; - border-radius: 0.3rem; } - .main-content pre > code { - padding: 0; - margin: 0; - font-size: 0.9rem; - color: #567482; - word-break: normal; - white-space: pre; - background: transparent; - border: 0; } -.main-content .highlight { - margin-bottom: 1rem; } - .main-content .highlight pre { - margin-bottom: 0; - word-break: normal; } -.main-content .highlight pre, .main-content pre { - padding: 0.8rem; - overflow: auto; - font-size: 0.9rem; - line-height: 1.45; - border-radius: 0.3rem; } -.main-content pre code, .main-content pre tt { - display: inline; - max-width: initial; - padding: 0; - margin: 0; - overflow: initial; - line-height: inherit; - word-wrap: normal; - background-color: transparent; - border: 0; } - .main-content pre code:before, .main-content pre code:after, .main-content pre tt:before, .main-content pre tt:after { - content: normal; } -.main-content ul, .main-content ol { - margin-top: 0; } -.main-content blockquote { - padding: 0 1rem; - margin-left: 0; - color: #819198; - border-left: 0.3rem solid #dce6f0; } - .main-content blockquote > :first-child { - margin-top: 0; } - .main-content blockquote > :last-child { - margin-bottom: 0; } -.main-content table { - display: block; - width: 100%; - overflow: auto; - word-break: normal; - word-break: keep-all; } - .main-content table th { - font-weight: bold; } - .main-content table th, .main-content table td { - padding: 0.5rem 1rem; - border: 1px solid #e9ebec; } -.main-content dl { - padding: 0; } - .main-content dl dt { - padding: 0; - margin-top: 1rem; - font-size: 1rem; - font-weight: bold; } - .main-content dl dd { - padding: 0; - margin-bottom: 1rem; } -.main-content hr { - height: 2px; - padding: 0; - margin: 1rem 0; - background-color: #eff0f1; - border: 0; } - -@media screen and (min-width: 64em) { - .main-content { - max-width: 64rem; - padding: 2rem 6rem; - margin: 0 auto; - font-size: 1.1rem; } } - -@media screen and (min-width: 42em) and (max-width: 64em) { - .main-content { - padding: 2rem 4rem; - font-size: 1.1rem; } } - -@media screen and (max-width: 42em) { - .main-content { - padding: 2rem 1rem; - font-size: 1rem; } } - -.site-footer { - padding-top: 2rem; - margin-top: 2rem; - border-top: solid 1px #eff0f1; } - -.site-footer-owner { - display: block; - font-weight: bold; } - -.site-footer-credits { - color: #819198; } - -@media screen and (min-width: 64em) { - .site-footer { - font-size: 1rem; } } - -@media screen and (min-width: 42em) and (max-width: 64em) { - .site-footer { - font-size: 1rem; } } - -@media screen and (max-width: 42em) { - .site-footer { - font-size: 0.9rem; } } diff --git a/web/libs/css/main.dash2.css b/web/libs/css/main.dash2.css index 2822686c..953885ae 100644 --- a/web/libs/css/main.dash2.css +++ b/web/libs/css/main.dash2.css @@ -104,8 +104,8 @@ img{max-width:100%} #vis_pwrvideo{height:250px} -#monSectionStreamChannels{margin-bottom: 15px;} -#monSectionStreamChannels:empty{display:none} +#monSectionStreamChannels,#monSectionInputMaps{margin-bottom: 15px;} +#monSectionStreamChannels:empty,#monSectionInputMaps:empty{display:none} #region_editor_live iframe,.canvas_holder canvas{border:0;position:absolute;left:0;top:0} .canvas_holder canvas{z-index:11} diff --git a/web/libs/css/poseidon.css b/web/libs/css/poseidon.css new file mode 100644 index 00000000..e3285fee --- /dev/null +++ b/web/libs/css/poseidon.css @@ -0,0 +1,246 @@ +/* bug: pseudo selectors for :fullscreen, :-moz-full-screen, and :-webkit-full-screen cannot be grouped together */ + +/* styles for container div */ + +div.mse-container { + position: relative; + float: left; + display: inline-block; + background: linear-gradient(black, grey, black); + border: 1px solid white; +} + +div.mse-container:fullscreen { + width: 100%; + height: 100%; +} + +div.mse-container:-ms-fullscreen { + width: 100%; + height: 100%; +} + +div.mse-container:-moz-full-screen { + width: 100%; + height: 100%; +} + +div.mse-container:-webkit-full-screen { + width: 100%; + height: 100%; +} + +div.mse-container.disabled { + pointer-events: none; + opacity: 0.4; +} + +/* styles for video */ + +video.mse-video { + pointer-events: none; + cursor: none; +} + +div.mse-container:fullscreen video { + width: 100%; + height: 100%; +} + +div.mse-container:-ms-fullscreen video { + width: 100%; + height: 100%; +} + +div.mse-container:-moz-full-screen video { + width: 100%; + height: 100%; +} + +div.mse-container:-webkit-full-screen video { + width: 100%; + height: 100%; +} + +/* styles for controls div */ + +div.mse-controls { + position: absolute; + background: black; + border: 1px solid white; + left: 5px; + right: 5px; + bottom: 5px; + padding: 3px; + border-radius: 5px; + opacity: 0; +} + +div.mse-controls:hover { + opacity: 0.8; +} + +div.mse-container:fullscreen div { + border: 2px solid white; + left: 10px; + right: 10px; + bottom: 10px; + padding: 6px; + border-radius: 10px; +} + +div.mse-container:-ms-fullscreen div { + border: 2px solid white; + left: 10px; + right: 10px; + bottom: 10px; + padding: 6px; + border-radius: 10px; +} + +div.mse-container:-moz-full-screen div { + border: 2px solid white; + left: 10px; + right: 10px; + bottom: 10px; + padding: 6px; + border-radius: 10px; +} + +div.mse-container:-webkit-full-screen div { + border: 2px solid white; + left: 10px; + right: 10px; + bottom: 10px; + padding: 6px; + border-radius: 10px; +} + +/* styles for buttons */ + +div.mse-container button { + background-color: transparent; + border: none; + outline: none; + opacity: 0.8; + cursor: pointer; + color: white; + font-size: 16px; + margin: 1px; +} + +div.mse-container button:hover { + opacity: 1; +} + +div.mse-container button:active { + opacity: 0.6; +} + +div.mse-container:fullscreen button { + font-size: 32px; +} + +div.mse-container:-ms-fullscreen button { + font-size: 32px; +} + +div.mse-container:-moz-full-screen button { + font-size: 32px; +} + +div.mse-container:-webkit-full-screen button { + font-size: 32px; +} + +button.mse-start, button.mse-stop, button.mse-snapshot { + float: left; +} + +button.mse-fullscreen, button.mse-cycle { + float: right; +} + +button.mse-start:before { + font-family: FontAwesome; + content: "\f04b"; +} + +button.mse-stop:before { + font-family: FontAwesome; + content: "\f04d"; +} + +button.mse-snapshot:before { + font-family: FontAwesome; + content: "\f030"; +} + +button.mse-fullscreen:before { + font-family: FontAwesome; + content: "\f065"; +} + +button.mse-stop.cycling { + pointer-events: none; + cursor: none; + opacity: 0.2; +} + +div.mse-container:fullscreen button.mse-fullscreen:before { + font-family: FontAwesome; + content: "\f066"; +} + +div.mse-container:-ms-fullscreen button.mse-fullscreen:before { + font-family: FontAwesome; + content: "\f066"; +} + +div.mse-container:-moz-full-screen button.mse-fullscreen:before { + font-family: FontAwesome; + content: "\f066"; +} + +div.mse-container:-webkit-full-screen button.mse-fullscreen:before { + font-family: FontAwesome; + content: "\f066"; +} + +button.mse-cycle:before { + font-family: FontAwesome; + content: "\f021";/* other cycle icon options : f021 f110 f01e f1ce */ +} + +button.mse-cycle.animated { + -webkit-animation: spin 5s linear infinite; + animation: spin 5s linear infinite; +} + +/* SPIN animation for cycle button */ + +@-webkit-keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} + +@keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); + } + + 100% { + -webkit-transform: rotate(359deg); + -ms-transform: rotate(359deg); + transform: rotate(359deg); + } +} \ No newline at end of file diff --git a/web/libs/js/main.dash2.js b/web/libs/js/main.dash2.js index c270a21c..58dfdb32 100644 --- a/web/libs/js/main.dash2.js +++ b/web/libs/js/main.dash2.js @@ -44,7 +44,7 @@ switch($user.details.lang){ user=$user } switch(x){ - case'cleanMons': + case'cleanMon': var acceptedFields = [ 'mid', 'ke', @@ -63,18 +63,22 @@ switch($user.details.lang){ 'width', 'height' ] + var row = {}; + $.each(d,function(m,b){ + if(acceptedFields.indexOf(m)>-1){ + row[m]=b; + } + }) + return row + break; + case'cleanMons': if(d==='object'){ var arr={} }else{ var arr=[] } $.each($.ccio.mon,function(n,v){ - var row = {}; - $.each(v,function(m,b){ - if(acceptedFields.indexOf(m)>-1){ - row[m]=b; - } - }) + var row = $.ccio.init('cleanMon',v) if(d==='object'){ arr[n]=row }else{ @@ -109,7 +113,7 @@ switch($user.details.lang){ break; case'streamMotionDetectOn': switch(JSON.parse(d.mon.details).stream_type){ - case'hls':case'flv': + case'hls':case'flv':case'mp4': //pass break; default: @@ -261,6 +265,9 @@ switch($user.details.lang){ case'flv': streamURL=$.ccio.init('location',user)+user.auth_token+'/flv/'+d.ke+'/'+d.mid+'/s.flv' break; + case'mp4': + streamURL=$.ccio.init('location',user)+user.auth_token+'/mp4/'+d.ke+'/'+d.mid+'/s.mp4' + break; case'b64': streamURL='Websocket' break; @@ -587,7 +594,7 @@ switch($user.details.lang){ case'b64': d.p.resize() break; - case'hls':case'flv': + case'hls':case'flv':case'mp4': if(d.p.find('video')[0].paused){ if(d.d.signal_check_log==1){ d.log={type:'Stream Check',msg:'<%-cleanLang(lang.clientStreamFailedattemptingReconnect)%>'} @@ -684,7 +691,7 @@ switch($user.details.lang){ cb(url,image_data,width,height); } switch(JSON.parse(e.mon.details).stream_type){ - case'hls':case'flv': + case'hls':case'flv':case'mp4': $.ccio.snapshotVideo($('[mid='+e.mon.mid+'].monitor_item video')[0],function(base64,video_data,width,height){ extend(video_data,width,height) }) @@ -942,7 +949,7 @@ switch($user.details.lang){ tmp+=''; }else{ switch(k.d.stream_type){ - case'hls':case'flv': + case'hls':case'flv':case'mp4': tmp+=''; break; case'mjpeg': @@ -1024,9 +1031,220 @@ switch($user.details.lang){ tmp+='
' tmp+='
' tmp+='
' + tmp+=''; + break; + case 'form-group'://Input Map Selector + var fields = [] + if(d.fields){ + if(d.fields instanceof Object){ + fields = [d] + }else{ + fields = d + } + } + $.each(fields,function(n,v){ + var value,hidden + if(!v.attribute)v.attribute=''; + if(!v.placeholder)v.placeholder=''; + if(!v.class)v.class=''; + if(!v.inputType)v.inputType='value'; + if(v.hidden){hidden='style="display:none"'}else{hidden=''}; + if(v.value){value='value=""'}else{value=''}; + tmp+='
' + tmp+=' ' + tmp+='
' + }) + break; + case 'input-map-selector'://Input Map Selector + if(!d.map){d.map=''} + tmp+='
' + tmp+=' ' + tmp+='
' + break; + case 'input-map'://Input Map Options + var tempID = $.ccio.gid(); + if(!d.channel){ + var numberOfChannelsDrawn = $('#monSectionInputMaps .input-map').length + d.channel=numberOfChannelsDrawn+1 + } + var fields = [ +// { +// name:'', +// class:'', +// placeholder:'', +// default:'', +// attribute:'', +// type:'text', +// }, + { + name:'type', + label:'<%-cleanLang(lang['Input Type'])%>', + default:'h264', + attribute:'selector="h_i_'+tempID+'"', + type:'selector', + choices:[ + {label:'<%-cleanLang(lang['H.264 / H.265 / H.265+'])%>',value:'h264'}, + {label:'<%-cleanLang(lang['JPEG'])%>',value:'jpeg'}, + {label:'<%-cleanLang(lang['MJPEG'])%>',value:'mjpeg'}, + {label:'<%-cleanLang(lang['HLS (.m3u8)'])%>',value:'hls'}, + {label:'<%-cleanLang(lang['MPEG-4 (.mp4 / .ts)'])%>',value:'mp4'}, + {label:'<%-cleanLang(lang['Local'])%>',value:'local'}, + {label:'<%-cleanLang(lang['Raw'])%>',value:'raw'}, + ] + }, + { + name:'fulladdress', + label:'<%-cleanLang(lang['Full URL Path'])%>', + placeholder:'Example : rtsp://admin:password@123.123.123.123/stream/1', + type:'text', + }, + { + name:'aduration', + label:'<%-cleanLang(lang['Analyzation Duration'])%>', + placeholder:'Example : 1000000', + type:'text', + }, + { + name:'probesize', + label:'<%-cleanLang(lang['Probe Size'])%>', + placeholder:'Example : 1000000', + type:'text', + }, + { + name:'stream_loop', + label:'<%-cleanLang(lang['Loop Stream'])%>', + class:'h_i_'+tempID+'_input h_i_'+tempID+'_mp4 h_i_'+tempID+'_raw', + hidden:true, + default:'0', + type:'selector', + choices:[ + {label:'No',value:'0'}, + {label:'Yes',value:'1'} + ] + }, + { + name:'rtsp_transport', + label:'<%-cleanLang(lang['RTSP Transport'])%>', + class:'h_i_'+tempID+'_input h_i_'+tempID+'_h264', + default:'0', + type:'selector', + choices:[ + {label:'Auto',value:''}, + {label:'TCP',value:'tcp'}, + {label:'UDP',value:'udp'} + ] + }, + { + name:'accelerator', + label:'<%-cleanLang(lang['Accelerator'])%>', + attribute:'selector="h_accel_'+tempID+'"', + default:'0', + type:'selector', + choices:[ + {label:'No',value:'0'}, + {label:'Yes',value:'1'}, + ] + }, + { + name:'hwaccel', + label:'<%-cleanLang(lang['hwaccel'])%>', + class:'h_accel_'+tempID+'_input h_accel_'+tempID+'_1', + hidden:true, + default:'', + type:'selector', + choices:[ + {label:'<%-cleanLang(lang['Auto'])%>',value:''}, + {label:'<%-cleanLang(lang['cuvid'])%>',value:'cuvid'}, + {label:'<%-cleanLang(lang['vaapi'])%>',value:'vaapi'}, + {label:'<%-cleanLang(lang['qsv'])%>',value:'qsv'}, + {label:'<%-cleanLang(lang['vdpau'])%>',value:'vdpau'}, + {label:'<%-cleanLang(lang['dxva2'])%>',value:'dxva2'}, + {label:'<%-cleanLang(lang['vdpau'])%>',value:'vdpau'}, + {label:'<%-cleanLang(lang['videotoolbox'])%>',value:'videotoolbox'}, + ] + }, + { + name:'hwaccel_vcodec', + label:'<%-cleanLang(lang['hwaccel_vcodec'])%>', + class:'h_accel_'+tempID+'_input h_accel_'+tempID+'_1', + hidden:true, + default:'auto', + type:'selector', + choices:[ + {label:'<%-cleanLang(lang['Auto'])%>',value:'auto'}, + {label:'<%-cleanLang(lang['h264_cuvid'])%>',value:'h264_cuvid',group:'NVIDIA'}, + {label:'<%-cleanLang(lang['hevc_cuvid'])%>',value:'hevc_cuvid',group:'NVIDIA'}, + {label:'<%-cleanLang(lang['mjpeg_cuvid'])%>',value:'mjpeg_cuvid',group:'NVIDIA'}, + {label:'<%-cleanLang(lang['mpeg4_cuvid'])%>',value:'mpeg4_cuvid',group:'NVIDIA'}, + {label:'<%-cleanLang(lang['h264_qsv'])%>',value:'h264_qsv',group:'QuickSync Video'}, + {label:'<%-cleanLang(lang['hevc_qsv'])%>',value:'hevc_qsv',group:'QuickSync Video'}, + {label:'<%-cleanLang(lang['mpeg2_qsv'])%>',value:'mpeg2_qsv',group:'QuickSync Video'}, + ] + }, + { + name:'hwaccel_device', + label:'<%-cleanLang(lang['hwaccel_device'])%>', + class:'h_accel_'+tempID+'_input h_accel_'+tempID+'_1', + hidden:true, + placeholder:'Example : /dev/dri/video0', + type:'text', + }, + ]; + tmp+='
' + tmp+='

<%-lang["Input"]%> <%-lang["Map"]%> : '+d.channel+'' + tmp+='
' + tmp+='

' + $.each(fields,function(n,v){ + if(!v.attribute)v.attribute=''; + if(!v.placeholder)v.placeholder=''; + if(!v.class)v.class=''; + if(v.hidden){v.hidden='style="display:none"'}else{v.hidden=''}; + tmp+='
' + tmp+=' ' + tmp+='
' + }) tmp+='
' break; - case 'stream-channel'://Link Shinobi - 1 set + case 'stream-channel'://Stream Channel var tempID = $.ccio.gid(); if(!d.channel){ var numberOfChannelsDrawn = $('#monSectionStreamChannels .stream-channel').length @@ -1041,9 +1259,19 @@ switch($user.details.lang){ // tmp+='
' // tmp+=' ' // tmp+=' ' + tmp+='
' + tmp+='

<%-cleanLang(lang['Input Feed'])%>' + tmp+='
' + tmp+=' ' + tmp+='
' + tmp+='

' + tmp+='
' + tmp+='
' tmp+='
' tmp+=' ' tmp+='
' + tmp+='
' + tmp+='
' + tmp+=' ' + tmp+='
' + tmp+='
' + tmp+=' ' + tmp+='
' + tmp+='
' tmp+=' ' - tmp+='
' + tmp+='
' tmp+='
' tmp+=' ' tmp+='
' - tmp+='
' - tmp+='
+ +
+ + + diff --git a/web/pages/embed.ejs b/web/pages/embed.ejs index 91fd7cc4..029077f5 100644 --- a/web/pages/embed.ejs +++ b/web/pages/embed.ejs @@ -12,6 +12,7 @@ if(data.addon.indexOf('relative')>-1){ } %> + <% if(data.addon){ @@ -80,10 +81,7 @@ $(document).ready(function(){ case'jpeg': %><% break; - case'flv': - %><% - break; - case'hls': + case'flv':case'hls':case'mp4': %><% break; case'mjpeg': @@ -113,16 +111,72 @@ $(document).ready(function(){ }; $.shinobi.init=function(d){ switch($.shinobi.mon[d.id].details.stream_type){ - case'flv': + case'mp4': + var stream = $('#SHINOBI_'+d.ke+'_'+d.id+' .stream-element'); + if($.shinobi.mon[d.id].details.stream_flv_type==='ws'){ + if($.shinobi.mon[d.id].Poseidon){ + $.shinobi.mon[d.id].Poseidon.destroy() + } + var createPoseidon = function(){ + $.shinobi.mon[d.id].Poseidon = new Poseidon({ + video: stream[0], + auth_token:'<%=data.auth%>', + ke:d.ke, + uid:'<%=data.uid%>', + id:d.id, + url: '<%=data.url%>' + }); + $.shinobi.mon[d.id].Poseidon.start(); + $.shinobi.mon[d.id].Poseidon._socket.on('data',function(res){ + console.log(res) + }) + } + try{ + createPoseidon() + }catch(err){ + console.log(err) + setTimeout(function(){ + createPoseidon() + },3000) + } + }else{ + stream.attr('src','<%=data.url%>/<%=data.auth%>/mp4/'+d.ke+'/'+d.id+'/s.mp4') + setTimeout(function(){ + $.ccio.init('signal-check',{id:d.id,ke:d.ke}) + },3000) + } + break; + case'flv': if (flvjs.isSupported()) { if($.shinobi.mon[d.id].flv){ $.shinobi.mon[d.id].flv.destroy() } - $.shinobi.mon[d.id].flv = flvjs.createPlayer({ - type: 'flv', - isLive: true, - url: '<%=data.url%>/<%=data.auth%>/flv/'+d.ke+'/'+d.id+'/s.flv' - }); + var options = {}; +// if($.shinobi.mon[d.id].details.stream_flv_type==='ws'){ +// if($.shinobi.mon[d.id].details.stream_flv_maxLatency&&$.shinobi.mon[d.id].details.stream_flv_maxLatency!==''){ +// $.shinobi.mon[d.id].details.stream_flv_maxLatency = parseInt($.shinobi.mon[d.id].details.stream_flv_maxLatency) +// }else{ +// $.shinobi.mon[d.id].details.stream_flv_maxLatency = 20000; +// } +// options = { +// type: 'flv', +// isLive: true, +// auth_token:'<%=data.auth%>', +// ke:d.ke, +// uid:'<%=data.uid%>', +// id:d.id, +// maxLatency:$.shinobi.mon[d.id].details.stream_flv_maxLatency, +// hasAudio:false, +// url: '<%=data.url%>' +// } +// }else{ + options = { + type: 'flv', + isLive: true, + url: '<%=data.url%>/<%=data.auth%>/flv/'+d.ke+'/'+d.id+'/s.flv' + } +// } + $.shinobi.mon[d.id].flv = flvjs.createPlayer(options); $.shinobi.mon[d.id].flv.attachMediaElement($('#SHINOBI_'+d.ke+'_'+d.id+' .stream-element')[0]); $.shinobi.mon[d.id].flv.on('error',function(err){ console.log(err) @@ -164,7 +218,7 @@ $(document).ready(function(){ } break; case'mjpeg': - $('#SHINOBI_'+d.ke+'_'+d.id+' .stream-element').attr('src','<%=data.url%>/<%=data.auth%>/mjpeg/'+d.ke+'/'+d.id+'/full') + $('#SHINOBI_'+d.ke+'_'+d.id+' .stream-element').attr('src','<%=data.url%>/<%=data.auth%>/mjpeg/'+d.ke+'/'+d.id+'?full=true') break; } } diff --git a/web/pages/home.ejs b/web/pages/home.ejs index a2468666..a53d86af 100644 --- a/web/pages/home.ejs +++ b/web/pages/home.ejs @@ -5,6 +5,7 @@ + @@ -145,8 +146,11 @@
@@ -187,6 +191,7 @@ +