Merge branch 'dev' into 'master'
Blue Rhydon See merge request Shinobi-Systems/Shinobi!21merge-requests/23/head
commit
5bb19c7d5e
76
COPYING
76
COPYING
|
@ -1,76 +0,0 @@
|
|||
SHINOBI OPEN SOURCE SOFTWARE LICENSE AGREEMENT
|
||||
Version 1, 04 June 2018
|
||||
|
||||
Copyright (C) 2018 Shinobi Systems <https://shinobi.systems>
|
||||
|
||||
We'll try to keep it simple. Thanks for using Shinobi Software!
|
||||
|
||||
Defintions.
|
||||
|
||||
In this End User Licence Agreement,
|
||||
"EULA" shall mean this End User Licence Agreement
|
||||
"Licenser" shall mean SHINOBI SYSTEMS
|
||||
"Licensee" shall mean YOU, or the organisation (if any) on whose behalf YOU are taking the EULA.
|
||||
|
||||
"SOFTWARE PRODUCTS" or "SOFTWARE" or "PRODUCTS" shall mean the Software Product this License is included with and any additional modules or add-ons delivered by Shinobi Systems. The term "SOFTWARE" includes, to the extent provided by SHINOBI SYSTEMS: 1) any revisions, updates and/or upgrades thereto; 2) any data, image or executable files, databases, data engines, computer software, or similar items customarily used or distributed with computer software products; 3) anything in any form whatsoever intended to be used with or in conjunction with the SOFTWARE; and 4) any associated media, documentation (including physical, electronic and on-line) and printed materials (the "Documentation").
|
||||
|
||||
Purpose of the Agreement.
|
||||
|
||||
The Short : Protect the rights of this Software Product and the Licenser.
|
||||
|
||||
The Long : The LICENSER grants the LICENSEE a non-exclusive, non-transferable and perpetual licence to use the SOFTWARE PRODUCTS listed therein and under the terms thereof. By accepting the terms and conditions established in this agreement, the LICENSEE does not acquire any ownership of copyright or other intellectual property rights in any part of the SOFTWARE PRODUCTS. The LICENSEE is only entitled to use the SOFTWARE PRODUCTS in accordance with the terms and conditions set forth by Shinobi Systems. By using the SOFTWARE PRODUCTS, the LICENSEE agrees to accept the terms and conditions presented.
|
||||
|
||||
LICENSEE must purchase the applicable subscription in any other use case unless otherwise granted. If the use case does not have a subscription applicable please contact a representative at support@shinobi.systems.
|
||||
|
||||
Conditions for Free (Unpaid) use.
|
||||
|
||||
- Use in a non commercial area for non commercial purposes
|
||||
- Educational use
|
||||
- Testing Purposes
|
||||
- Use in a School like a elementary school, college, or university
|
||||
|
||||
Support Services.
|
||||
|
||||
The Maintenance and Support Service shall be contracted and provided as per selected plan agreement, taxes will be included in all prices for Support Services.
|
||||
|
||||
Support Services will only provide support services as per the selected agreement.
|
||||
|
||||
This is not the entire agreement on support services. You must also review all agreements provided with subscription plans provided.
|
||||
|
||||
Software Product Ownership.
|
||||
|
||||
This software is property of Shinobi Systems. LICENSEE must keep all copyright notices unchanged.
|
||||
|
||||
Modification of this Software Product.
|
||||
|
||||
LICENSEE may modify the code but LICENSEE must provide the source code if asked by law enforcement or an authorized Shinobi representative. LICENSEE must keep all copyright notices unchanged.
|
||||
|
||||
Software Product Rebranding or "White-Labelling".
|
||||
|
||||
LICENSEE can remove the Shinobi branding from the front end but all copyright notices must remain unchanged.
|
||||
|
||||
Software Product Contributions.
|
||||
|
||||
All contributed code becomes the property of Shinobi Systems. All contributors give permission to Shinobi and Shinobi developers to use the code however it is seen fit.
|
||||
|
||||
Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
Limitation of Liability.
|
||||
|
||||
LIMITATION ON AND EXCLUSION OF REMEDIES AND DAMAGES. YOU CAN RECOVER FROM SHINOBI SYSTEMS AND ITS SUPPLIERS ONLY DIRECT DAMAGES UP TO $5.00 CAD. YOU CANNOT RECOVER ANY OTHER DAMAGES, INCLUDING CONSEQUENTIAL, LOST PROFITS, SPECIAL, INDIRECT OR INCIDENTAL DAMAGES.
|
||||
This limitation applies to:
|
||||
|
||||
- anything related to the software, services, content (including code) on third-party Internet sites, or third-party programs; and
|
||||
- claims for breach of contract, breach of warranty, guarantee or condition, strict liability, negligence, or other tort to the extent permitted by applicable law.
|
||||
|
||||
It also applies even if Shinobi Systems knew or should have known about the possibility of the damages. The above limitation or exclusion may not apply to you because your country may not allow the exclusion or limitation of incidental, consequential or other damages.
|
||||
|
||||
Changes to the Agreement.
|
||||
|
||||
Shinobi Systems reserves the right to change the license and set of terms at any time. Continued use is agreement to those possible changes. Changes to this license will be provided in the commit history of the repository it is located in.
|
||||
|
||||
Legal Proceedings
|
||||
|
||||
All lawsuits must be filed in the city in which the current headquarters is based at the time of the filing.
|
|
@ -0,0 +1,144 @@
|
|||
SHINOBI OPEN SOURCE SOFTWARE LICENSE AGREEMENT
|
||||
==============================================
|
||||
Version 1, 04 June 2018
|
||||
|
||||
Copyright (C) 2018 [Shinobi Systems](https://shinobi.systems)
|
||||
|
||||
*We'll try to keep it simple. Thanks for using Shinobi Software!*
|
||||
|
||||
Defintions.
|
||||
-----------
|
||||
In this license, which also serves as a general End User License Agreement [EULA], the following
|
||||
terms shall be interpreted by these definitions:
|
||||
* "EULA" shall mean this End User Licence Agreement
|
||||
* "Licensor" shall mean SHINOBI SYSTEMS
|
||||
* "Licensee" shall mean YOU, or the organisation (if any) on whose behalf YOU are taking the EULA.
|
||||
|
||||
"SOFTWARE PRODUCTS" or "SOFTWARE" or "PRODUCTS" shall mean the Software Product this License is
|
||||
included with and any additional modules or add-ons delivered by Shinobi Systems. The term
|
||||
"SOFTWARE" includes, to the extent provided by SHINOBI SYSTEMS:
|
||||
1) any revisions, updates and/or upgrades thereto;
|
||||
2) any data, image or executable files, databases, data engines, computer software, or similar
|
||||
items customarily used or distributed with computer software products;
|
||||
3) anything in any form whatsoever intended to be used with or in conjunction with the SOFTWARE;
|
||||
4) any associated media, documentation (including physical, electronic and on-line) and printed
|
||||
materials (the "Documentation").
|
||||
|
||||
Purpose of the Agreement.
|
||||
-------------------------
|
||||
**The Short**: Protect the rights of this Software Product and the LICENSOR.
|
||||
|
||||
**The Long**: The LICENSOR grants the LICENSEE a non-exclusive, non-transferable and perpetual
|
||||
licence to use the SOFTWARE PRODUCTS listed therein and under the terms thereof. By accepting
|
||||
the terms and conditions established in this agreement, the LICENSEE does not acquire any
|
||||
ownership of copyright or other intellectual property rights in any part of the SOFTWARE
|
||||
PRODUCTS. The LICENSEE is only entitled to use the SOFTWARE PRODUCTS in accordance with the
|
||||
terms and conditions set forth by Shinobi Systems. By using the SOFTWARE PRODUCTS, the
|
||||
LICENSEE agrees to accept the terms and conditions presented.
|
||||
|
||||
LICENSEE must purchase the applicable subscription in any other use case unless otherwise
|
||||
granted. If the use case does not have a subscription applicable please contact a
|
||||
representative at support@shinobi.systems.
|
||||
|
||||
#### Commercial Uses
|
||||
- Selling usage of the software
|
||||
- Using the software in locations that engage in the buying and selling of goods and services
|
||||
|
||||
#### Conditions for Free (Unpaid) use.
|
||||
- Use in a non commercial area
|
||||
- Used for non commercial purposes
|
||||
- When used for research or educational purposes
|
||||
- Testing Purposes
|
||||
- Usage by Educational institutions
|
||||
- Use for Emergency Services and facilties associated like Search and Rescue Services or
|
||||
Ambulance Services
|
||||
- Use in Health Care facility like a hospital or walk-in clinic
|
||||
|
||||
#### Support Services.
|
||||
The Maintenance and Support Service shall be contracted and provided as per selected plan
|
||||
agreement, taxes will be included in all prices for Support Services.
|
||||
|
||||
Support Services will only provide support services as per the selected agreement.
|
||||
|
||||
This is not the entire agreement on support services. You must also review all agreements
|
||||
provided with subscription plans provided.
|
||||
|
||||
#### Software Product Ownership.
|
||||
This software is property of Shinobi Systems. LICENSEE must keep all copyright notices
|
||||
unchanged.
|
||||
|
||||
#### Modification of this Software Product.
|
||||
LICENSEE may modify code for personal use but must provide these changes upon request from
|
||||
Shinobi Systems or an authorized Shinobi representative. LICENSEE may not alter or change
|
||||
copyright notices. All code changes by LICENSEE shall fall under the copyright of Shinobi
|
||||
Systems in the case code modified by LICENSEE is integrated into the official Shinobi code base.
|
||||
|
||||
#### Software Product Rebranding or "White-Labelling".
|
||||
LICENSEE can remove the Shinobi branding from the front end but all copyright notices must
|
||||
remain unchanged.
|
||||
|
||||
#### Software Product Contributions.
|
||||
All contributed code becomes the property of Shinobi Systems. All contributors give permission
|
||||
to Shinobi and Shinobi developers to use the code however it is seen fit.
|
||||
|
||||
#### Disclaimer of Warranty.
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM
|
||||
"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK
|
||||
AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE,
|
||||
YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
#### Changes to the Agreement.
|
||||
Shinobi Systems reserves the right to change the license and set of terms at any time.
|
||||
Continued use is agreement to those possible changes. Changes to this license will be provided
|
||||
in the commit history of the repository it is located in.
|
||||
|
||||
#### Legal Proceedings.
|
||||
All lawsuits must be filed at the Vancouver Court House.
|
||||
|
||||
Courthouse Vancouver Robson Square
|
||||
800 Hornby St, Vancouver, BC V6Z 2C5
|
||||
|
||||
#### List of Included Software
|
||||
|
||||
This list is completed to best of our knowledge.
|
||||
|
||||
Node.js - https://nodejs.org/en/
|
||||
MariaDB - https://mariadb.org/
|
||||
FFmpeg - https://www.ffmpeg.org/
|
||||
request - https://www.npmjs.com/package/request
|
||||
Express (npm) - https://expressjs.com/ https://www.npmjs.com/package/express
|
||||
EJS (npm) - http://ejs.co/ https://www.npmjs.com/package/ejs
|
||||
pam-diff (npm) (Motion Detector) - https://github.com/kevinGodell/pam-diff
|
||||
pipe2pam (npm) (for pam-diff) - https://github.com/kevinGodell/pipe2pam
|
||||
mp4frag (npm) (Poseidon's main engine) - https://github.com/kevinGodell/mp4frag
|
||||
mse-live-player (for mp4frag) - https://github.com/kevinGodell/mse-live-player
|
||||
pipe2jpeg (npm) - https://github.com/kevinGodell/pipe2jpeg
|
||||
webdav (npm) - https://www.npmjs.com/package/webdav
|
||||
jsonfile (npm) - https://www.npmjs.com/package/jsonfile
|
||||
connectionTester (npm) - https://www.npmjs.com/package/connectionTester
|
||||
node-onvif (npm) - https://www.npmjs.com/package/node-onvif
|
||||
knex (npm) - https://www.npmjs.com/package/knex
|
||||
nodemailer (npm) - https://www.npmjs.com/package/nodemailer
|
||||
mysql (npm) - https://www.npmjs.com/package/mysql
|
||||
sqlite3 (npm) - https://www.npmjs.com/package/sqlite3
|
||||
ldapauth-fork (npm) - https://www.npmjs.com/package/ldapauth-fork
|
||||
http-proxy (npm) - https://www.npmjs.com/package/http-proxy
|
||||
hls.js - https://github.com/video-dev/hls.js/
|
||||
flv.js - https://github.com/Bilibili/flv.js/
|
||||
Material Design Lite - https://getmdl.io/
|
||||
socket.io - https://socket.io/
|
||||
fullcalendar - https://fullcalendar.io/
|
||||
jQuery & jQuery UI - http://jquery.com
|
||||
Bootstrap - https://getbootstrap.com/
|
||||
PNotify - https://sciactive.com/pnotify/
|
||||
Font Awesome - https://fontawesome.com/
|
||||
Gridstack.js - http://gridstackjs.com/
|
||||
Date Range Picker (daterangepicker.js) - http://www.daterangepicker.com/
|
||||
Hero Pattern CSS Backgrounds - http://www.heropatterns.com/
|
||||
Bootstrap Table - https://github.com/wenzhixin/bootstrap-table
|
||||
placeholder.js - https://jamesallardice.github.io/Placeholders.js/
|
||||
Moment.js - https://momentjs.com/
|
||||
Livestamp.js - https://mattbradley.github.io/livestampjs/
|
||||
Lodash - https://lodash.com/
|
|
@ -133,7 +133,9 @@ else
|
|||
fi
|
||||
echo "============="
|
||||
echo "Shinobi - Install NPM Libraries"
|
||||
sudo npm install
|
||||
sudo npm i npm -g
|
||||
sudo npm install --unsafe-perm
|
||||
sudo npm audit fix --unsafe-perm
|
||||
echo "============="
|
||||
echo "Shinobi - Install PM2"
|
||||
sudo npm install pm2 -g
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
#!/bin/sh
|
||||
echo "------------------------------------------"
|
||||
echo "-- Installing CUDA Toolkit and CUDA DNN --"
|
||||
echo "------------------------------------------"
|
||||
# Install CUDA Drivers and Toolkit
|
||||
wget https://cdn.shinobi.video/installers/cuda-repo-ubuntu1710_9.2.148-1_amd64.deb -O cuda.deb
|
||||
sudo dpkg -i cuda.deb
|
||||
sudo apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1710/x86_64/7fa2af80.pub
|
||||
sudo apt-get update -y
|
||||
sudo apt-get -o Dpkg::Options::="--force-overwrite" install cuda -y
|
||||
sudo apt-get -o Dpkg::Options::="--force-overwrite" install --fix-broken -y
|
||||
# Install CUDA DNN
|
||||
wget https://cdn.shinobi.video/installers/libcudnn7_7.2.1.38-1+cuda9.2_amd64.deb -O cuda-dnn.deb
|
||||
sudo dpkg -i cuda-dnn.deb
|
||||
wget https://cdn.shinobi.video/installers/libcudnn7-dev_7.2.1.38-1+cuda9.2_amd64.deb -O cuda-dnn-dev.deb
|
||||
sudo dpkg -i cuda-dnn-dev.deb
|
||||
echo "-- Cleaning Up --"
|
||||
# Cleanup
|
||||
sudo rm cuda.deb
|
||||
sudo rm cuda-dnn.deb
|
||||
sudo rm cuda-dnn-dev.deb
|
||||
echo "------------------------------"
|
||||
echo "Reboot is required. Do it now?"
|
||||
echo "------------------------------"
|
||||
echo "(y)es or (N)o. Default is No."
|
||||
read rebootTheMachineHomie
|
||||
if [ "$rebootTheMachineHomie" = "y" ] || [ "$rebootTheMachineHomie" = "Y" ]; then
|
||||
sudo reboot
|
||||
fi
|
|
@ -41,7 +41,9 @@ if ( $mysqlagreeData == "y" ) then
|
|||
endif
|
||||
echo "============="
|
||||
echo "Shinobi - Install NPM Libraries"
|
||||
npm install
|
||||
npm i npm -g
|
||||
npm install --unsafe-perm
|
||||
npm audit fix --unsafe-perm
|
||||
echo "============="
|
||||
echo "Shinobi - Install PM2"
|
||||
npm install pm2 -g
|
||||
|
|
|
@ -49,7 +49,9 @@ if [ "$mysqlagreeData" = "y" ]; then
|
|||
fi
|
||||
echo "============="
|
||||
echo "Shinobi - Install NPM Libraries"
|
||||
sudo npm install
|
||||
sudo npm i npm -g
|
||||
sudo npm install --unsafe-perm
|
||||
sudo npm audit fix --unsafe-perm
|
||||
echo "============="
|
||||
echo "Shinobi - Install PM2"
|
||||
sudo npm install pm2 -g
|
||||
|
|
|
@ -8,6 +8,7 @@ echo "1. Ubuntu"
|
|||
echo "2. CentOS"
|
||||
echo "3. MacOS"
|
||||
echo "4. FreeBSD"
|
||||
echo "5. OpenSUSE"
|
||||
echo "========"
|
||||
read oschoicee
|
||||
case $oschoicee in
|
||||
|
@ -27,6 +28,11 @@ INSTALL/macos.sh
|
|||
chmod +x INSTALL/freebsd.sh
|
||||
INSTALL/freebsd.sh
|
||||
;;
|
||||
"5")
|
||||
chmod +x INSTALL/opensuse.sh
|
||||
INSTALL/opensuse.sh
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Choice not found."
|
||||
;;
|
||||
|
|
|
@ -0,0 +1,190 @@
|
|||
#!/bin/bash
|
||||
echo "========================================================="
|
||||
echo "==!! Shinobi : The Open Source CCTV and NVR Solution !!=="
|
||||
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"
|
||||
sudo cp super.sample.json super.json
|
||||
echo "Shinobi - Do you want to enable superuser access?"
|
||||
echo "This may be useful if passwords are forgotten or"
|
||||
echo "if you would like to limit accessibility of an"
|
||||
echo "account for business scenarios."
|
||||
echo "(y)es or (N)o"
|
||||
read createSuperJson
|
||||
if [ "$createSuperJson" = "y" ] || [ "$createSuperJson" = "Y" ]; then
|
||||
echo "Default Superuser : admin@shinobi.video"
|
||||
echo "Default Password : admin"
|
||||
echo "* You can edit these settings in \"super.json\" located in the Shinobi directory."
|
||||
sudo cp super.sample.json super.json
|
||||
fi
|
||||
fi
|
||||
echo "Shinobi - Run zypper refresh"
|
||||
sudo zypper refresh
|
||||
sudo zypper install -y make
|
||||
echo "============="
|
||||
echo "Shinobi - Do you want to Install Node.js?"
|
||||
echo "(y)es or (N)o"
|
||||
NODEJSINSTALL=0
|
||||
read nodejsinstall
|
||||
if [ "$nodejsinstall" = "y" ] || [ "$nodejsinstall" = "Y" ]; then
|
||||
sudo zypper install -y nodejs8
|
||||
NODEJSINSTALL=1
|
||||
fi
|
||||
echo "============="
|
||||
echo "Shinobi - Do you want to Install FFMPEG?"
|
||||
echo "(y)es or (N)o"
|
||||
read ffmpeginstall
|
||||
if [ "$ffmpeginstall" = "y" ] || [ "$ffmpeginstall" = "Y" ]; then
|
||||
# Without nodejs8 package we can't use npm command
|
||||
if [ "$NODEJSINSTALL" -eq "1" ]; then
|
||||
echo "Shinobi - Do you want to Install FFMPEG with `zypper --version` or download a static version provided with npm `npm --version`?"
|
||||
echo "(z)ypper or (N)pm"
|
||||
echo "Press [ENTER] for default (npm)"
|
||||
read ffmpegstaticinstall
|
||||
if [ "$ffmpegstaticinstall" = "z" ] || [ "$ffmpegstaticinstall" = "Z" ]; then
|
||||
# Install ffmpeg and ffmpeg-devel
|
||||
sudo zypper install -y ffmpeg ffmpeg-devel
|
||||
else
|
||||
sudo npm install ffmpeg-static@2.2.1
|
||||
fi
|
||||
else
|
||||
sudo zypper install -y ffmpeg ffmpeg-devel
|
||||
fi
|
||||
fi
|
||||
echo "============="
|
||||
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 zypper install -y sqlite3 sqlite3-devel
|
||||
sudo npm install sqlite3
|
||||
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 mysqlagree
|
||||
if [ "$mysqlagree" = "y" ] || [ "$mysqlagree" = "Y" ]; then
|
||||
sudo zypper install -y mariadb
|
||||
#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 "============="
|
||||
echo "Shinobi - Install NPM Libraries"
|
||||
sudo npm install
|
||||
echo "============="
|
||||
echo "Shinobi - Install PM2"
|
||||
sudo npm install pm2 -g
|
||||
echo "Shinobi - Finished"
|
||||
sudo chmod -R 755 .
|
||||
touch 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" ] || [ "$startShinobi" = "Y" ]; then
|
||||
sudo pm2 start camera.js
|
||||
sudo pm2 start cron.js
|
||||
sudo pm2 startup
|
||||
sudo pm2 save
|
||||
sudo pm2 list
|
||||
fi
|
||||
if [ "$mysqlDefaultData" = "y" ] || [ "$mysqlDefaultData" = "Y" ]; then
|
||||
echo "details written to INSTALL/installed.txt"
|
||||
echo "====================================="
|
||||
echo "======= Login Credentials ======="
|
||||
echo "|| Username : $userEmail"
|
||||
echo "|| Password : $userPasswordPlain"
|
||||
echo "|| API Key : $apiKey"
|
||||
echo "====================================="
|
||||
echo "====================================="
|
||||
fi
|
||||
if [ ! "$sqliteormariadb" = "M" ] && [ ! "$sqliteormariadb" = "m" ]; then
|
||||
echo "====================================="
|
||||
echo "||===== Install Completed =====||"
|
||||
echo "====================================="
|
||||
echo "|| Login with the Superuser and create a new user!!"
|
||||
echo "||==================================="
|
||||
echo "|| Open http://$(/sbin/ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1):8080/super in your web browser."
|
||||
echo "||==================================="
|
||||
echo "|| Default Superuser : admin@shinobi.video"
|
||||
echo "|| Default Password : admin"
|
||||
echo "====================================="
|
||||
echo "====================================="
|
||||
else
|
||||
echo "+=================================+"
|
||||
echo "||===== Install Completed =====||"
|
||||
echo "|| Access the main Shinobi panel at http://$(/sbin/ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1):8080 in your web browser."
|
||||
echo "+=================================+"
|
||||
fi
|
|
@ -147,7 +147,9 @@ fi
|
|||
# Install NPM Libraries
|
||||
echo "============="
|
||||
echo "Shinobi - Install NPM Libraries"
|
||||
npm install
|
||||
sudo npm i npm -g
|
||||
sudo npm install --unsafe-perm
|
||||
sudo npm audit fix --unsafe-perm
|
||||
echo "============="
|
||||
|
||||
#Install PM2
|
||||
|
|
|
@ -147,7 +147,9 @@ else
|
|||
fi
|
||||
echo "============="
|
||||
echo "Shinobi - Install NPM Libraries"
|
||||
sudo npm install
|
||||
sudo npm i npm -g
|
||||
sudo npm install --unsafe-perm
|
||||
sudo npm audit fix --unsafe-perm
|
||||
echo "============="
|
||||
echo "Shinobi - Install PM2"
|
||||
sudo npm install pm2 -g
|
||||
|
|
|
@ -6,7 +6,7 @@ SHINOBI OPEN SOURCE SOFTWARE LICENSE AGREEMENT
|
|||
|
||||
*We'll try to keep it simple. Thanks for using Shinobi Software!*
|
||||
|
||||
Defintions.
|
||||
Definitions.
|
||||
-----------
|
||||
In this license, which also serves as a general End User License Agreement [EULA], the following
|
||||
terms shall be interpreted by these definitions:
|
||||
|
|
|
@ -20,9 +20,5 @@
|
|||
"cron":{
|
||||
"key":"change_this_to_something_very_random__just_anything_other_than_this"
|
||||
},
|
||||
"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",
|
||||
"OpenALPR":"SomeOpenALPRkeySoPeopleDontMessWithYourShinobi"
|
||||
}
|
||||
}
|
||||
"pluginKeys":{}
|
||||
}
|
||||
|
|
|
@ -114,6 +114,10 @@
|
|||
"Saved Filters": "Saved Filters",
|
||||
"Filter Name": "Filter Name",
|
||||
"Find Where": "Find Where",
|
||||
"Reason": "Reason",
|
||||
"Detection Engine": "Detection Engine",
|
||||
"X Point": "X Point",
|
||||
"Y Point": "Y Point",
|
||||
"Sort By": "Sort By",
|
||||
"Start Time": "Start Time",
|
||||
"End Time": "End Time",
|
||||
|
@ -128,6 +132,10 @@
|
|||
"Greater Than": "Greater Than",
|
||||
"Less Than": "Less Than",
|
||||
"Less Than or Equal to": "Less Than or Equal to",
|
||||
"Contains": "Contains",
|
||||
"Does Not Contain": "Does Not Contain",
|
||||
"AND": "AND",
|
||||
"OR": "OR",
|
||||
"Like": "Like",
|
||||
"Matches": "Matches",
|
||||
"Not Matches": "Not Matches",
|
||||
|
@ -156,6 +164,8 @@
|
|||
"FFmpegTip": "FFprobe is a simple multimedia streams analyzer. You can use it to output all kinds of information about an input including duration, frame rate, frame size, etc.",
|
||||
"Complete Stream URL": "Complete Stream URL",
|
||||
"ONVIF Scanner": "ONVIF Scanner",
|
||||
"ONVIFEventsNotAvailable": "ONVIF Events not Available",
|
||||
"ONVIFnotCompliantProfileT": "Camera is not ONVIF Profile T Compliant",
|
||||
"Scan Settings": "Scan Settings",
|
||||
"ONVIFnote": "Discover ONVIF devices on networks outside your own or leave it blank to scan your current network. <br>Username and Password can be left blank.",
|
||||
"Range or Single": "Range or Single",
|
||||
|
@ -291,6 +301,8 @@
|
|||
"Recording Timestamp": "Recording Timestamp",
|
||||
"Recording Watermark": "Recording Watermark",
|
||||
"Region Editor": "Region Editor",
|
||||
"Detector Filters": "Detector Filters",
|
||||
"Filter for Objects only": "Filter for Objects only",
|
||||
"Custom": "Custom",
|
||||
"Detector": "Detector",
|
||||
"Connected": "Connected",
|
||||
|
@ -378,6 +390,7 @@
|
|||
"Recording Flags": "Recording Flags",
|
||||
"Output Method": "Output Method",
|
||||
"Webhook": "Webhook",
|
||||
"Event Webhook Error": "Event Webhook Error",
|
||||
"Webhook URL": "Webhook URL",
|
||||
"Command on Trigger": "Command on Trigger",
|
||||
"Command": "Command",
|
||||
|
@ -386,6 +399,7 @@
|
|||
"Save Events to SQL": "Save Events to SQL",
|
||||
"Email on Trigger": "Email on Trigger <small>Emails go to the main account holder's login address.</small>",
|
||||
"Attach Video Clip": "Attach Video Clip",
|
||||
"Discord": "Discord",
|
||||
"Discord Alert on Trigger": "Discord Alert on Trigger",
|
||||
"Allow Next Email": "Allow Next Email <small>in Minutes</small>",
|
||||
"Allow Next Discord Alert": "Allow Next Discord Alert <small>in Minutes</small>",
|
||||
|
@ -457,6 +471,7 @@
|
|||
"Audio": "Audio",
|
||||
"Mute Audio": "Mute Audio",
|
||||
"No Audio": "No Audio",
|
||||
"Popout Monitor on Event": "Popout Monitor on Event",
|
||||
"aac": "aac",
|
||||
"ac3": "ac3",
|
||||
"libmp3lame": "libmp3lame",
|
||||
|
@ -471,6 +486,7 @@
|
|||
"Bottom Left": "Bottom Left",
|
||||
"WebM (libvpx)": "WebM (libvpx)",
|
||||
"Poseidon": "Poseidon",
|
||||
"HEVC (H.265)": "HEVC (H.265)",
|
||||
"MP4 (copy, libx264, libx265)": "MP4 (copy, libx264, libx265)",
|
||||
"Default": "Default",
|
||||
"libvpx (Default)": "libvpx (Default)",
|
||||
|
@ -515,8 +531,10 @@
|
|||
"No Videos Found": "No Videos Found",
|
||||
"FileNotExistText": "Cannot save non existant file. Something went wrong.",
|
||||
"CameraNotRecordingText": "Settings may be incompatible. Check encoders. Restarting...",
|
||||
"Camera is not running": "Camera is not running",
|
||||
"Camera is not recording": "Camera is not recording",
|
||||
"Camera is not streaming": "Camera is not streaming",
|
||||
"Process Started": "Process Started",
|
||||
"Restarting Process": "Restarting Process",
|
||||
"Restarting": "Restarting",
|
||||
"Starting": "Starting",
|
||||
|
@ -658,6 +676,10 @@
|
|||
"hevc_qsv": "H.265 (Quick Sync Video)",
|
||||
"vp8_qsv": "VP8 (Quick Sync Video)",
|
||||
"mpeg2_qsv": "MPEG2 (Quick Sync Video)",
|
||||
"h264_mmal": "H.264 (Raspberry Pi)",
|
||||
"mpeg2_mmal": "MPEG-2 (Raspberry Pi)",
|
||||
"mpeg4_mmal": "MPEG-4 (Raspberry Pi)",
|
||||
"h264_omx": "H.264 openMAX (Raspberry Pi)",
|
||||
"h264_vaapi": "H.264 VA-API (Intel HW Accel)",
|
||||
"h264_nvenc": "H.264 NVENC (NVIDIA HW Accel)",
|
||||
"hevc_nvenc": "H.265 NVENC (NVIDIA HW Accel)",
|
||||
|
|
|
@ -610,6 +610,10 @@
|
|||
"h264_nvenc": "H. 264 NVENC (NVIDIA HW Accel)",
|
||||
"h264_qsv": "H. 264 (Quick Sync Video)",
|
||||
"h264_vaapi": "H. 264 VA-API (Intel HW Accel)",
|
||||
"h264_mmal": "H.264 (Raspberry Pi)",
|
||||
"mpeg2_mmal": "MPEG-2 (Raspberry Pi)",
|
||||
"mpeg4_mmal": "MPEG-4 (Raspberry Pi)",
|
||||
"h264_omx": "H.264 openMAX (Raspberry Pi)",
|
||||
"hevc_cuvid": "H. 265 CUVID",
|
||||
"hevc_nvenc": "H. 265 NVENC (NVIDIA HW Accel)",
|
||||
"hevc_qsv": "H. 265 (Quick Sync Video)",
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
"pipe2pam": "^0.6.2",
|
||||
"nodemailer": "^4.0.1",
|
||||
"node-onvif": "^0.1.4",
|
||||
"onvif-nvt": "0.2.8",
|
||||
"path": "^0.12.7",
|
||||
"request": "^2.79.0",
|
||||
"socket.io": "^1.7.1",
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
conf.json
|
||||
events
|
||||
frames
|
|
@ -1,364 +0,0 @@
|
|||
var os = require('os');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var mysql = require('mysql');
|
||||
var moment = require('moment');
|
||||
var request = require("request");
|
||||
var spawn = require('child_process').spawn;
|
||||
var exec = require('child_process').exec;
|
||||
var execSync = require('child_process').execSync;
|
||||
var connectionTester = require('connection-tester');
|
||||
var config = require('./conf.json');
|
||||
|
||||
exec("ps aux | grep -ie ffmpeg | awk '{print $2}' | xargs kill -9");//kill any ffmpeg running
|
||||
process.on('uncaughtException', function (err) {
|
||||
console.error('uncaughtException',err);
|
||||
});
|
||||
s={connected:false,child_node:true,platform:os.platform(),group:{}};
|
||||
|
||||
//connect to master
|
||||
io = require('socket.io-client')('ws://'+config.ws);
|
||||
//spawn conatiner
|
||||
s.spawns={};
|
||||
//emulate master sql query
|
||||
sql={
|
||||
query:function(x,y,z){
|
||||
io.emit('c',{f:'sql',query:x,values:y});if(typeof z==='function'){z();}
|
||||
}
|
||||
}
|
||||
//get this nodes cpu usage
|
||||
s.cpuUsage=function(e){
|
||||
switch(s.platform){
|
||||
case'darwin':
|
||||
e="ps -A -o %cpu | awk '{s+=$1} END {print s}'";
|
||||
break;
|
||||
case'linux':
|
||||
e="grep 'cpu ' /proc/stat | awk '{usage=($2+$4)*100/($2+$4+$5)} END {print usage}'";
|
||||
break;
|
||||
}
|
||||
return execSync(e,{encoding:'utf8'});
|
||||
}
|
||||
setInterval(function(){
|
||||
io.emit('c',{f:'cpu',cpu:parseFloat(s.cpuUsage())});
|
||||
},2000);
|
||||
//interact with server functions
|
||||
s.cx=function(x){io.emit('c',x)}
|
||||
//emulate master socket emitter
|
||||
s.tx=function(x,y){s.cx({f:'s.tx',data:x,to:y})}
|
||||
//emulate master logger
|
||||
s.log=function(x,y){console.log(y);s.cx({f:'s.log',data:s.init('clean',x),to:y})}
|
||||
//emulate master camera function
|
||||
s.camera=function(x,y){s.cx({f:'camera',mode:x,data:y})}
|
||||
|
||||
//load camera controller vars
|
||||
s.nameToTime=function(x){x=x.split('.')[0].split('T'),x[1]=x[1].replace(/-/g,':');x=x.join(' ');return x;}
|
||||
s.ratio=function(width,height,ratio){ratio = width / height;return ( Math.abs( ratio - 4 / 3 ) < Math.abs( ratio - 16 / 9 ) ) ? '4:3' : '16:9';}
|
||||
s.gid=function(x){
|
||||
if(!x){x=10};var t = "";var p = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
for( var i=0; i < x; i++ )
|
||||
t += p.charAt(Math.floor(Math.random() * p.length));
|
||||
return t;
|
||||
};
|
||||
s.moment=function(e,x){if(!e){e=new Date};if(!x){x='YYYY-MM-DDTHH-mm-ss'};return moment(e).utcOffset('-0800').format(x)}
|
||||
s.kill=function(x,e,p){
|
||||
if(e&&s.group[e.ke].mon[e.id].record){
|
||||
clearTimeout(s.group[e.ke].mon[e.id].record.capturing);
|
||||
if(s.group[e.ke].mon[e.id].record.request&&s.group[e.ke].mon[e.id].record.request.abort){s.group[e.ke].mon[e.id].record.request.abort();delete(s.group[e.ke].mon[e.id].record.request);}
|
||||
};
|
||||
if(!x||x===1){return};if(!x.stdin){return};p=x.pid;x.stdin.pause();setTimeout(function(){x.kill('SIGTERM');delete(x);setTimeout(function(){exec('kill -9 '+p)},1000)},1000)
|
||||
}
|
||||
s.cameraVals=function(e){
|
||||
e.t=Object.keys(s.group[e.ke].mon[e.id]);e.a={};
|
||||
e.t.forEach(function(n){
|
||||
if(s.group[e.ke].mon[e.id][n] instanceof Object){e.a[n]=s.group[e.ke].mon[e.id][n]};
|
||||
});
|
||||
return e.a;
|
||||
}
|
||||
//directories
|
||||
s.group={};
|
||||
s.dir={videos:__dirname+'/videos/',frames:__dirname+'/frames/'};
|
||||
if (!fs.existsSync(s.dir.frames)){
|
||||
fs.mkdirSync(s.dir.frames);
|
||||
}
|
||||
if (!fs.existsSync(s.dir.videos)){
|
||||
fs.mkdirSync(s.dir.videos);
|
||||
}
|
||||
////Camera Controller
|
||||
s.init=function(x,e){
|
||||
switch(x){
|
||||
case 0://camera
|
||||
if(!s.group[e.ke]){s.group[e.ke]={}};
|
||||
if(!s.group[e.ke].mon){s.group[e.ke].mon={}}
|
||||
if(!s.group[e.ke].mon[e.mid]){s.group[e.ke].mon[e.mid]={}}
|
||||
if(!s.group[e.ke].mon[e.mid].watch){s.group[e.ke].mon[e.mid].watch={}};
|
||||
if(e.type==='record'){e.record=1}else{e.record=0}
|
||||
if(!s.group[e.ke].mon[e.mid].record){s.group[e.ke].mon[e.mid].record={yes:e.record}};
|
||||
if(!s.group[e.ke].mon[e.mid].started){s.group[e.ke].mon[e.mid].started={}};
|
||||
if(!s.group[e.ke].mon[e.mid].running){s.group[e.ke].mon[e.mid].running={}};
|
||||
break;
|
||||
case'clean':
|
||||
if(e instanceof Object){
|
||||
x={keys:Object.keys(e),ar:{}};
|
||||
x.keys.forEach(function(v){
|
||||
if(v!=='record'&&v!=='spawn'&&v!=='running'&&(typeof e[v]!=='function')){x.ar[v]=e[v];}
|
||||
});
|
||||
return x.ar;
|
||||
}
|
||||
break;
|
||||
case'clean':
|
||||
x={keys:Object.keys(e),ar:{}};
|
||||
x.keys.forEach(function(v){
|
||||
if(v!=='record'&&v!=='spawn'&&v!=='running'&&(v!=='time'&&typeof e[v]!=='function')){x.ar[v]=e[v];}
|
||||
});
|
||||
return x.ar;
|
||||
break;
|
||||
case'url':
|
||||
auth_details='';
|
||||
if(e.details.muser&&e.details.muser!==''&&e.details.mpass&&e.details.mpass!=='') {
|
||||
auth_details=e.details.muser+':'+e.details.mpass+'@';
|
||||
}
|
||||
if(e.port==80){e.porty=''}else{e.porty=':'+e.port}
|
||||
e.url=e.protocol+'://'+auth_details+e.host+e.porty+e.path;return e.url;
|
||||
break;
|
||||
case'url_no_path':
|
||||
auth_details='';
|
||||
if(e.details.muser&&e.details.muser!==''&&e.details.mpass&&e.details.mpass!=='') {
|
||||
auth_details=e.details.muser+':'+e.details.mpass+'@';
|
||||
}
|
||||
if(e.port==80){e.porty=''}else{e.porty=':'+e.port}
|
||||
e.url=e.protocol+'://'+auth_details+e.host+e.porty;return e.url;
|
||||
break;
|
||||
}
|
||||
if(typeof e.callback==='function'){setTimeout(function(){e.callback();delete(e.callback);},2000);}
|
||||
}
|
||||
s.video=function(x,e){
|
||||
if(!e){e={}};
|
||||
if(e.mid){e.id=e.mid};
|
||||
switch(x){
|
||||
case'delete':
|
||||
e.dir=s.dir.videos+e.ke+'/'+e.id+'/';
|
||||
e.save=[e.id,e.ke,s.nameToTime(e.filename),0];
|
||||
sql.query('DELETE FROM Videos WHERE `mid`=? AND `ke`=? AND `time`=? AND `status`=?',e.save)
|
||||
s.tx({f:'video_delete',reason:'Camera Error',filename:e.filename+'.'+e.ext,mid:e.id,ke:e.ke,time:s.nameToTime(e.filename),end:moment().format('YYYY-MM-DD HH:mm:ss')},'GRP_'+e.ke);
|
||||
if(fs.existsSync(e.dir+e.filename+'.'+e.ext)){
|
||||
return fs.unlink(e.dir+e.filename+'.'+e.ext);
|
||||
}
|
||||
break;
|
||||
case'close':
|
||||
e.dir=s.dir.videos+e.ke+'/'+e.id+'/';
|
||||
console.log(e.dir+e.filename+'.'+e.ext)
|
||||
if(fs.existsSync(e.dir+e.filename+'.'+e.ext)){
|
||||
e.filesize=fs.statSync(e.dir+e.filename+'.'+e.ext)["size"];
|
||||
if((e.filesize/100000).toFixed(2)>0.25){
|
||||
e.save=[e.filesize,e.frames,1,e.id,e.ke,s.nameToTime(e.filename)];
|
||||
sql.query('UPDATE Videos SET `size`=?,`frames`=?,`status`=? WHERE `mid`=? AND `ke`=? AND `time`=?',e.save)
|
||||
fs.readFile(e.dir+e.filename+'.'+e.ext,function (err,data) {
|
||||
s.cx({f:'created_file',mid:e.id,ke:e.ke,created_file:data,filename:e.filename+'.'+e.ext,d:s.init('clean',e)});
|
||||
s.tx({f:'video_build_success',filename:e.filename+'.'+e.ext,mid:e.id,ke:e.ke,time:s.nameToTime(e.filename),size:e.filesize,end:s.moment(new Date,'YYYY-MM-DD HH:mm:ss')},'GRP_'+e.ke);
|
||||
});
|
||||
}else{
|
||||
s.video('delete',e);
|
||||
s.log(e,{type:'File Corrupt',msg:{ffmpeg:s.group[e.ke].mon[e.mid].ffmpeg,filesize:(e.filesize/100000).toFixed(2)}})
|
||||
}
|
||||
}else{
|
||||
s.video('delete',e);
|
||||
s.log(e,{type:'File Not Exist',msg:'Cannot save non existant file. Something went wrong.',ffmpeg:s.group[e.ke].mon[e.id].ffmpeg})
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
s.ffmpeg=function(e,x){
|
||||
if(!x){x={tmp:''}}
|
||||
// if(!e.details.cutoff||e.details.cutoff===''){x.cutoff=15}else{x.cutoff=parseFloat(e.details.cutoff)};if(isNaN(x.cutoff)===true){x.cutoff=15}
|
||||
// x.segment=' -f segment -strftime 1 -segment_time '+(60*x.cutoff)+' -segment_format '+e.ext
|
||||
if(!e.details.timestamp||e.details.timestamp==1){x.time=' -vf drawtext=fontfile=/usr/share/fonts/truetype/freefont/FreeSans.ttf:text=\'%{localtime}\':x=(w-tw)/2:y=0:fontcolor=white:box=1:boxcolor=0x00000000@1:fontsize=10';}else{x.time=''}
|
||||
switch(e.ext){
|
||||
case'mp4':
|
||||
x.vcodec='libx265';x.acodec='libfaac';
|
||||
if(e.details.vcodec&&e.details.vcodec!==''){x.vcodec=e.details.vcodec}
|
||||
break;
|
||||
case'webm':
|
||||
x.acodec='libvorbis',x.vcodec='libvpx';
|
||||
break;
|
||||
}
|
||||
if(e.details.acodec&&e.details.acodec!==''){x.acodec=e.details.acodec}
|
||||
if(x.acodec==='none'){x.acodec=''}else{x.acodec=' -acodec '+x.acodec}
|
||||
if(x.vcodec!=='none'){x.vcodec=' -vcodec '+x.vcodec}
|
||||
if(e.fps&&e.fps!==''){x.framerate=' -r '+e.fps}else{x.framerate=''}
|
||||
if(e.details.vf&&e.details.vf!==''){
|
||||
if(x.time===''){x.vf=' -vf '}else{x.vf=','}
|
||||
x.vf+=e.details.vf;
|
||||
x.time+=x.vf;
|
||||
}
|
||||
if(e.details.svf&&e.details.svf!==''){x.svf=' -vf '+e.details.svf;}else{x.svf='';}
|
||||
// if(e.details.svf){'-vf "rotate=45*(PI/180)'}
|
||||
switch(e.type){
|
||||
case'socket':case'jpeg':case'pipe':
|
||||
if(!x.vf||x.vf===','){x.vf=''}
|
||||
x.tmp='-loglevel warning -pattern_type glob -f image2pipe'+x.framerate+' -vcodec mjpeg -i -'+x.vcodec+x.time+x.framerate+' -use_wallclock_as_timestamps 1 -q:v 1'+x.vf+' '+e.dir+e.filename+'.'+e.ext;
|
||||
break;
|
||||
case'mjpeg':
|
||||
if(e.mode=='record'){
|
||||
x.watch=x.vcodec+x.time+' -r 10 -s '+e.width+'x'+e.height+' -use_wallclock_as_timestamps 1 -q:v 1 '+e.dir+e.filename+'.'+e.ext+''
|
||||
}else{
|
||||
x.watch='';
|
||||
};
|
||||
x.tmp='-loglevel warning -reconnect 1 -f mjpeg -i '+e.url+''+x.watch+' -f image2pipe'+x.svf+' -s '+e.ratio+' pipe:1';
|
||||
break;
|
||||
case'h264':
|
||||
if(!x.vf||x.vf===','){x.vf=''}
|
||||
if(e.mode=='record'){
|
||||
x.watch=x.vcodec+x.framerate+x.acodec+' -movflags frag_keyframe+empty_moov -s '+e.width+'x'+e.height+' -use_wallclock_as_timestamps 1 -q:v 1'+x.vf+' '+e.dir+e.filename+'.'+e.ext
|
||||
}else{
|
||||
x.watch='';
|
||||
};
|
||||
x.tmp='-loglevel warning -i '+e.url+' -stimeout 2000'+x.watch+' -f image2pipe'+x.svf+' -s '+e.ratio+' pipe:1';
|
||||
break;
|
||||
case'local':
|
||||
if(e.mode=='record'){
|
||||
x.watch=x.vcodec+x.time+x.framerate+x.acodec+' -movflags frag_keyframe+empty_moov -s '+e.width+'x'+e.height+' -use_wallclock_as_timestamps 1 '+e.dir+e.filename+'.'+e.ext
|
||||
}else{
|
||||
x.watch='';
|
||||
};
|
||||
x.tmp='-loglevel warning -i '+e.path+''+x.watch+' -f image2pipe'+x.svf+' -s '+e.ratio+' pipe:1';
|
||||
break;
|
||||
}
|
||||
s.group[e.ke].mon[e.mid].ffmpeg=x.tmp;
|
||||
return spawn('ffmpeg',x.tmp.split(' '));
|
||||
}
|
||||
|
||||
//child functions
|
||||
var cn={};
|
||||
io.on('connect', function(d){
|
||||
console.log('connected');
|
||||
io.emit('c',{f:'init',socket_key:config.key,u:{name:config.name}})
|
||||
});
|
||||
io.on('c',function(d){
|
||||
console.log(d.f);
|
||||
switch(d.f){
|
||||
case'init_success':
|
||||
s.connected=true;
|
||||
s.other_helpers=d.child_helpers;
|
||||
break;
|
||||
case'kill':
|
||||
s.init(0,d.d);
|
||||
s.kill(s.group[d.d.ke].mon[d.d.id].spawn,d.d)
|
||||
break;
|
||||
case'sync':
|
||||
s.init(0,d.sync);
|
||||
Object.keys(d.sync).forEach(function(v){
|
||||
s.group[d.sync.ke].mon[d.sync.mid][v]=d.sync[v];
|
||||
});
|
||||
break;
|
||||
case'delete_file'://delete video
|
||||
d.dir=s.dir.videos+d.ke+'/'+d.mid+'/'+d.file;
|
||||
if(fs.existsSync(d.dir)){
|
||||
fs.unlink(d.dir);
|
||||
}
|
||||
break;
|
||||
case'close'://close video
|
||||
s.video('close',d.d);
|
||||
break;
|
||||
case'spawn'://start video
|
||||
s.init(0,d.d);
|
||||
s.group[d.d.ke].mon[d.d.id]=d.mon;
|
||||
s.init(0,d.d);
|
||||
if(!s.group[d.d.ke].mon_conf){s.group[d.d.ke].mon_conf={}}
|
||||
if(!s.group[d.d.ke].mon_conf[d.d.id]){s.group[d.d.ke].mon_conf[d.d.id]=s.init('clean',d.d);}
|
||||
if(s.group[d.d.ke].mon[d.d.id].spawn&&s.group[d.d.ke].mon[d.d.id].spawn.stdin){return}
|
||||
if(d.d.mode==='record'){
|
||||
console.log(s.group[d.d.ke].mon[d.d.id])
|
||||
s.group[d.d.ke].mon[d.d.id].record.yes=1;
|
||||
d.d.dir=s.dir.videos+d.d.ke+'/';
|
||||
if (!fs.existsSync(d.d.dir)){
|
||||
fs.mkdirSync(d.d.dir);
|
||||
}
|
||||
d.d.dir=s.dir.videos+d.d.ke+'/'+d.d.id+'/';
|
||||
if (!fs.existsSync(d.d.dir)){
|
||||
fs.mkdirSync(d.d.dir);
|
||||
}
|
||||
}else{
|
||||
s.group[d.d.ke].mon[d.d.mid].record.yes=0;
|
||||
}
|
||||
if(d.d.mode==='record'||d.d.type==='mjpeg'||d.d.type==='h264'||d.d.type==='local'){
|
||||
s.group[d.d.ke].mon[d.d.id].spawn = s.ffmpeg(d.d);
|
||||
s.log(d.d,{type:'FFMPEG Process Starting',msg:{cmd:s.group[d.d.ke].mon[d.d.id].ffmpeg}});
|
||||
}
|
||||
d.d.frames=0;
|
||||
switch(d.d.type){
|
||||
case'jpeg':
|
||||
if(!d.d.details.sfps||d.d.details.sfps===''){
|
||||
d.d.details.sfps=parseFloat(d.d.details.sfps);
|
||||
if(isNaN(d.d.details.sfps)){d.d.details.sfps=1}
|
||||
}
|
||||
d.d.captureOne=function(f){
|
||||
s.group[d.d.ke].mon[d.d.id].record.request=request({url:d.d.url,method:'GET',encoding: null,timeout:3000},function(er,data){
|
||||
++d.d.frames; if(s.group[d.d.ke].mon[d.d.id].spawn&&s.group[d.d.ke].mon[d.d.id].spawn.stdin){
|
||||
if(er){
|
||||
++d.d.error_count;
|
||||
s.log(d.d,{type:'Snapshot Error',msg:er});
|
||||
return;
|
||||
}
|
||||
if(d.d.mode==='record'&&s.group[d.d.ke].mon[d.d.id].spawn&&s.group[d.d.ke].mon[d.d.id].spawn.stdin){
|
||||
s.group[d.d.ke].mon[d.d.id].spawn.stdin.write(data.body);
|
||||
}
|
||||
if(s.group[d.d.ke].mon[d.d.id].watch&&Object.keys(s.group[d.d.ke].mon[d.d.id].watch).length>0){
|
||||
s.tx({f:'monitor_frame',ke:d.d.ke,id:d.d.id,time:s.moment(),frame:data.body.toString('base64'),frame_format:'b64'},'MON_'+d.d.id);
|
||||
}
|
||||
s.group[d.d.ke].mon[d.d.id].record.capturing=setTimeout(function(){d.d.captureOne()},1000/d.d.details.sfps);
|
||||
clearTimeout(d.d.timeOut),d.d.timeOut=setTimeout(function(){d.d.error_count=0;},3000)
|
||||
}
|
||||
}).on('error', function(err){
|
||||
// if(s.group[d.d.ke]&&s.group[d.d.ke].mon[d.d.id]&&s.group[d.d.ke].mon[d.d.id].record&&s.group[d.d.ke].mon[d.d.id].record.request){s.group[d.d.ke].mon[d.d.id].record.request.abort();}
|
||||
clearTimeout(s.group[d.d.ke].mon[d.d.id].record.capturing);
|
||||
if(d.d.error_count>4){d.d.fn();return}
|
||||
d.d.captureOne();
|
||||
});
|
||||
}
|
||||
d.d.captureOne()
|
||||
break;
|
||||
case'mjpeg':case'h264'://case'socket':case'local':
|
||||
if(!s.group[d.d.ke]||!s.group[d.d.ke].mon[d.d.id]){s.init(0,d.d)}
|
||||
if(s.group[d.d.ke].mon[d.d.id].spawn){
|
||||
s.group[d.d.ke].mon[d.d.id].spawn.on('error',function(er){d.d.error({type:'Spawn Error',msg:er})})
|
||||
s.group[d.d.ke].mon[d.d.id].spawn.stdout.on('data',function(de){
|
||||
s.tx({f:'monitor_frame',ke:d.d.ke,id:d.d.id,time:s.moment(),frame:de.toString('base64'),frame_format:'b64'},'MON_'+d.d.id);
|
||||
});
|
||||
s.group[d.d.ke].mon[d.d.id].spawn.stderr.on('data',function(de){
|
||||
de=de.toString();
|
||||
d.d.chk=function(x){return de.indexOf(x)>-1;}
|
||||
switch(true){
|
||||
// case d.d.chk('av_interleaved_write_frame'):
|
||||
case d.d.chk('Connection timed out'):
|
||||
setTimeout(function(){s.log(d.d,{type:"Can't Connect",msg:'Retrying...'});d.d.error_fatal();},1000)//restart
|
||||
break;
|
||||
case d.d.chk('No pixel format specified'):
|
||||
s.log(d.d,{type:"FFMPEG STDERR",msg:{ffmpeg:s.group[d.d.ke].mon[d.d.id].ffmpeg,msg:de}})
|
||||
break;
|
||||
case d.d.chk('RTP: missed'):
|
||||
case d.d.chk('deprecated pixel format used, make sure you did set range correctly'):
|
||||
return
|
||||
break;
|
||||
case d.d.chk('No such file or directory'):
|
||||
case d.d.chk('Unable to open RTSP for listening'):
|
||||
case d.d.chk('timed out'):
|
||||
case d.d.chk('Invalid data found when processing input'):
|
||||
case d.d.chk('Immediate exit requested'):
|
||||
case d.d.chk('reset by peer'):
|
||||
if(d.d.frames===0&&x==='record'){s.video('delete',d.d)};
|
||||
break;
|
||||
}
|
||||
s.log(d.d,{type:"FFMPEG STDERR",msg:de})
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case'video':
|
||||
s.video(d.d[0],d.d[1]);
|
||||
break;
|
||||
}
|
||||
});
|
||||
io.on('disconnect',function(d){
|
||||
s.connected=false;
|
||||
});
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"name":"Macbook",
|
||||
"ws":"66.51.132.100",
|
||||
"key":"3123asdasdf1dtj1hjk23sdfaasd12asdasddfdbtnkkfgvesra3asdsd3123afdsfqw345",
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
{"host":"127.0.0.1","user":"root","password":"","database":"ccio"}
|
|
@ -0,0 +1,3 @@
|
|||
conf.json
|
||||
faces
|
||||
data
|
|
@ -0,0 +1,66 @@
|
|||
#!/bin/bash
|
||||
echo "-----------------------------------------------"
|
||||
echo "-- Installing Python Dlib Plugin for Shinobi --"
|
||||
echo "-----------------------------------------------"
|
||||
echo "-----------------------------------"
|
||||
if [ ! -e "./conf.json" ]; then
|
||||
echo "Creating conf.json"
|
||||
sudo cp conf.sample.json conf.json
|
||||
else
|
||||
echo "conf.json already exists..."
|
||||
fi
|
||||
echo "-----------------------------------"
|
||||
sudo apt update -y
|
||||
echo "Installing python3"
|
||||
sudo apt install python3 python3-dev python3-pip -y
|
||||
echo "-----------------------------------"
|
||||
sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
|
||||
sudo apt update
|
||||
sudo apt-get install gcc-6 g++-6 -y && sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-6 60 --slave /usr/bin/g++ g++ /usr/bin/g++-6
|
||||
echo "-----------------------------------"
|
||||
if ! [ -x "$(command -v nvidia-smi)" ]; then
|
||||
echo "You need to install NVIDIA Drivers to use this."
|
||||
echo "inside the Shinobi directory run the following :"
|
||||
echo "sh INSTALL/cuda9-part1.sh"
|
||||
exit 1
|
||||
else
|
||||
echo "NVIDIA Drivers found..."
|
||||
echo "$(nvidia-smi |grep 'Driver Version')"
|
||||
fi
|
||||
echo "-----------------------------------"
|
||||
if [ ! -d "/usr/local/cuda" ]; then
|
||||
echo "You need to install CUDA Toolkit to use this."
|
||||
echo "inside the Shinobi directory run the following :"
|
||||
echo "sh INSTALL/cuda9-part2-after-reboot.sh"
|
||||
exit 1
|
||||
else
|
||||
echo "CUDA Toolkit found..."
|
||||
fi
|
||||
echo "-----------------------------------"
|
||||
if ! [ -x "$(command -v opencv_version)" ]; then
|
||||
echo "You need to install OpenCV with CUDA first."
|
||||
echo "inside the Shinobi directory run the following :"
|
||||
echo "sh INSTALL/opencv-cuda.sh"
|
||||
exit 1
|
||||
else
|
||||
echo "OpenCV found... : $(opencv_version)"
|
||||
fi
|
||||
echo "-----------------------------------"
|
||||
echo "Getting new pip..."
|
||||
pip3 install --upgrade pip
|
||||
pip install --user --upgrade pip
|
||||
echo "Smoking pips..."
|
||||
pip3 install flask_socketio
|
||||
pip3 install flask
|
||||
pip3 install numpy
|
||||
pip3 install gevent gevent-websocket
|
||||
export PATH=/usr/local/cuda/bin:$PATH
|
||||
echo "Installing Dlib..."
|
||||
cd /opt
|
||||
git clone https://github.com/davisking/dlib.git dlib
|
||||
cd dlib
|
||||
cmake -DCUDA_CUDART_LIBRARY=/usr/local/cuda/lib64/libcudart.so
|
||||
make install
|
||||
pip3 install dlib
|
||||
echo "Start the plugin with pm2 like so :"
|
||||
echo "pm2 start shinobi-python-dlib.js"
|
|
@ -0,0 +1,72 @@
|
|||
# Python DLIB
|
||||
|
||||
> This plugin requires the use of port `7990` by default. You can specify a different port by adding `pythonPort` to your plugin's conf.json.
|
||||
|
||||
**Ubuntu and Debian only**
|
||||
|
||||
Go to the Shinobi directory. **/home/Shinobi** is the default directory.
|
||||
|
||||
```
|
||||
cd /home/Shinobi/plugins/python-dlib
|
||||
```
|
||||
|
||||
Copy the config file.
|
||||
|
||||
```
|
||||
sh INSTALL.sh
|
||||
```
|
||||
|
||||
Start the plugin.
|
||||
|
||||
```
|
||||
pm2 start shinobi-python-dlib.js
|
||||
```
|
||||
|
||||
Doing this will reveal options in the monitor configuration. Shinobi does not need to be restarted when a plugin is initiated or stopped.
|
||||
|
||||
## Run the plugin as a Host
|
||||
> The main app (Shinobi) will be the client and the plugin will be the host. The purpose of allowing this method is so that you can use one plugin for multiple Shinobi instances. Allowing you to easily manage connections without starting multiple processes.
|
||||
|
||||
Edit your plugins configuration file. Set the `hostPort` **to be different** than the `listening port for camera.js`.
|
||||
|
||||
```
|
||||
nano conf.json
|
||||
```
|
||||
|
||||
Here is a sample of a Host configuration for the plugin.
|
||||
- `plug` is the name of the plugin corresponding in the main configuration file.
|
||||
- `https` choose if you want to use SSL or not. Default is `false`.
|
||||
- `hostPort` can be any available port number. **Don't make this the same port number as Shinobi.** Default is `8082`.
|
||||
- `type` tells the main application (Shinobi) what kind of plugin it is. In this case it is a detector.
|
||||
|
||||
```
|
||||
{
|
||||
"plug":"PythonDlib",
|
||||
"hostPort":8082,
|
||||
"key":"SomeOpenALPRkeySoPeopleDontMessWithYourShinobi",
|
||||
"mode":"host",
|
||||
"type":"detector"
|
||||
}
|
||||
```
|
||||
|
||||
Now modify the **main configuration file** located in the main directory of Shinobi. *Where you currently should be.*
|
||||
|
||||
```
|
||||
nano conf.json
|
||||
```
|
||||
|
||||
Add the `plugins` array if you don't already have it. Add the following *object inside the array*.
|
||||
|
||||
```
|
||||
"plugins":[
|
||||
{
|
||||
"id" : "PythonDlib",
|
||||
"https" : false,
|
||||
"host" : "localhost",
|
||||
"port" : 8082,
|
||||
"key" : "SomeOpenALPRkeySoPeopleDontMessWithYourShinobi",
|
||||
"mode" : "host",
|
||||
"type" : "detector"
|
||||
}
|
||||
],
|
||||
```
|
|
@ -0,0 +1 @@
|
|||
python3 -u $@
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"plug":"PythonDlib",
|
||||
"host":"localhost",
|
||||
"port":8080,
|
||||
"pythonPort":7990,
|
||||
"hostPort":8082,
|
||||
"key":"SomeOpenALPRkeySoPeopleDontMessWithYourShinobi",
|
||||
"mode":"client",
|
||||
"type":"detector"
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "shinobi-python-dlib",
|
||||
"version": "1.0.0",
|
||||
"description": "DLIB plugin for Shinobi that uses Python functions for detection.",
|
||||
"main": "shinobi-python-dlib.js",
|
||||
"dependencies": {
|
||||
"socket.io-client": "^1.7.4",
|
||||
"express": "^4.16.2",
|
||||
"moment": "^2.19.2",
|
||||
"socket.io": "^2.0.4"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Moe Alam",
|
||||
"license": "ISC"
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
from flask import Flask, request, jsonify, render_template
|
||||
from flask_socketio import SocketIO, emit
|
||||
from PIL import Image
|
||||
import face_recognition
|
||||
import cv2
|
||||
import os
|
||||
import json
|
||||
import numpy as np
|
||||
import sys
|
||||
|
||||
dirname = sys.argv[1]
|
||||
|
||||
try:
|
||||
with open("{}/conf.json".format(dirname)) as json_file:
|
||||
config = json.load(json_file)
|
||||
httpPort = config['pythonPort']
|
||||
try:
|
||||
httpPort
|
||||
except NameError:
|
||||
httpPort = 7990
|
||||
except Exception as e:
|
||||
print("conf.json not found.")
|
||||
httpPort = 7990
|
||||
|
||||
# Load Flask
|
||||
app = Flask("DLIB for Shinobi (Pumpkin Pie)")
|
||||
socketio = SocketIO(app)
|
||||
# Silence Flask
|
||||
# import logging
|
||||
# log = logging.getLogger('werkzeug')
|
||||
# log.setLevel(logging.ERROR)
|
||||
|
||||
#check for faces dir
|
||||
facesDir = "{}/faces/".format(dirname)
|
||||
if not os.path.exists(facesDir):
|
||||
os.makedirs(facesDir)
|
||||
|
||||
# load faces
|
||||
included_extensions = ['jpg','jpeg', 'bmp', 'png', 'gif']
|
||||
file_names = [fn for fn in os.listdir(facesDir)
|
||||
if any(fn.endswith(ext) for ext in included_extensions)]
|
||||
known_faces = []
|
||||
face_locations = []
|
||||
face_encodings = []
|
||||
face_names = []
|
||||
|
||||
for faceFile in file_names:
|
||||
face = face_recognition.load_image_file(facesDir+faceFile)
|
||||
face_encoding = face_recognition.face_encodings(face)[0]
|
||||
known_faces.append(face_encoding)
|
||||
|
||||
# detection function
|
||||
def spark(filepath):
|
||||
try:
|
||||
filepath
|
||||
except NameError:
|
||||
return "File path not found."
|
||||
img = cv2.imread(filepath)
|
||||
returnData = []
|
||||
frame = img[:, :, ::-1]
|
||||
|
||||
# Find all the faces and face encodings in the current frame of video
|
||||
face_locations = face_recognition.face_locations(frame)
|
||||
face_encodings = face_recognition.face_encodings(frame, face_locations)
|
||||
face_names = []
|
||||
for (top, right, bottom, left), face_encoding in zip(face_locations, face_encodings):
|
||||
# See if the face is a match for the known face(s)
|
||||
matrix = {}
|
||||
matrix["coordinates"] = [
|
||||
{"x" : left, "y" : top},
|
||||
{"x" : right, "y" : top},
|
||||
{"x" : right, "y" : bottom},
|
||||
{"x" : left, "y" : bottom}
|
||||
]
|
||||
(left, top), (right, bottom)
|
||||
match = face_recognition.compare_faces(known_faces, face_encoding, tolerance=0.50)
|
||||
if True in match:
|
||||
first_match_index = match.index(True)
|
||||
name = file_names[first_match_index]
|
||||
matrix["tag"] = name
|
||||
returnData.append(matrix)
|
||||
return returnData
|
||||
|
||||
# bake the image data by a file path
|
||||
# POST body contains the "img" variable. The value should be to a local image path.
|
||||
# Example : /dev/shm/streams/[GROUP_KEY]/[MONITOR_ID]/s.jpg
|
||||
@app.route('/', methods=['GET'])
|
||||
def index():
|
||||
return "Pumpkin.py is running. This web interface should NEVER be accessible remotely. The Node.js plugin that runs this script should only be allowed accessible remotely."
|
||||
|
||||
# bake the image data by a file path
|
||||
# POST body contains the "img" variable. The value should be to a local image path.
|
||||
# Example : /dev/shm/streams/[GROUP_KEY]/[MONITOR_ID]/s.jpg
|
||||
@app.route('/post', methods=['POST'])
|
||||
def post():
|
||||
filepath = request.form['img']
|
||||
return jsonify(spark(filepath))
|
||||
|
||||
# bake the image data by a file path
|
||||
# GET string contains the "img" variable. The value should be to a local image path.
|
||||
# Example : /dev/shm/streams/[GROUP_KEY]/[MONITOR_ID]/s.jpg
|
||||
@app.route('/get', methods=['GET'])
|
||||
def get():
|
||||
filepath = request.args.get('img')
|
||||
return jsonify(spark(filepath))
|
||||
|
||||
@socketio.on('f')
|
||||
def receiveMessage(message):
|
||||
emit('f',{'id':message.get("id"),'data':spark(message.get("path"))})
|
||||
|
||||
# quick-and-dirty start
|
||||
if __name__ == '__main__':
|
||||
socketio.run(app, port=httpPort)
|
|
@ -0,0 +1,292 @@
|
|||
//
|
||||
// Shinobi - Python DLIB Plugin
|
||||
// Copyright (C) 2016-2025 Moe Alam, moeiscool
|
||||
//
|
||||
// # Donate
|
||||
//
|
||||
// If you like what I am doing here and want me to continue please consider donating :)
|
||||
// PayPal : paypal@m03.ca
|
||||
//
|
||||
process.on('uncaughtException', function (err) {
|
||||
console.error('uncaughtException',err);
|
||||
});
|
||||
//main vars
|
||||
var fs=require('fs');
|
||||
var exec = require('child_process').exec;
|
||||
var spawn = require('child_process').spawn;
|
||||
var moment = require('moment');
|
||||
var http = require('http');
|
||||
var express = require('express');
|
||||
var socketIoClient = require('socket.io-client');
|
||||
var config = require('./conf.json');
|
||||
var http = require('http'),
|
||||
app = express(),
|
||||
server = http.createServer(app);
|
||||
s={
|
||||
group:{},
|
||||
dir:{},
|
||||
isWin:(process.platform==='win32'),
|
||||
s:function(json){return JSON.stringify(json,null,3)}
|
||||
}
|
||||
s.checkCorrectPathEnding=function(x){
|
||||
var length=x.length
|
||||
if(x.charAt(length-1)!=='/'){
|
||||
x=x+'/'
|
||||
}
|
||||
return x.replace('__DIR__',__dirname)
|
||||
}
|
||||
s.debugLog = function(){
|
||||
if(config.debugLog === true){
|
||||
console.log(new Date(),arguments)
|
||||
if(config.debugLogVerbose === true){
|
||||
console.log(new Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!config.port){config.port=8080}
|
||||
if(!config.pythonScript){config.pythonScript=__dirname+'/pumpkin.py'}
|
||||
if(!config.pythonPort){config.pythonPort=7990}
|
||||
if(!config.hostPort){config.hostPort=8082}
|
||||
if(config.systemLog===undefined){config.systemLog=true}
|
||||
//default stream folder check
|
||||
if(!config.streamDir){
|
||||
if(s.isWin===false){
|
||||
config.streamDir='/dev/shm'
|
||||
}else{
|
||||
config.streamDir=config.windowsTempDir
|
||||
}
|
||||
if(!fs.existsSync(config.streamDir)){
|
||||
config.streamDir=__dirname+'/streams/'
|
||||
}else{
|
||||
config.streamDir+='/streams/'
|
||||
}
|
||||
}
|
||||
s.dir.streams=config.streamDir;
|
||||
//streams dir
|
||||
if(!fs.existsSync(s.dir.streams)){
|
||||
fs.mkdirSync(s.dir.streams);
|
||||
}
|
||||
s.gid=function(x){
|
||||
if(!x){x=10};var t = "";var p = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
for( var i=0; i < x; i++ )
|
||||
t += p.charAt(Math.floor(Math.random() * p.length));
|
||||
return t;
|
||||
};
|
||||
s.getRequest = function(url,callback){
|
||||
return http.get(url, function(res){
|
||||
var body = '';
|
||||
res.on('data', function(chunk){
|
||||
body += chunk;
|
||||
});
|
||||
res.on('end',function(){
|
||||
try{body = JSON.parse(body)}catch(err){}
|
||||
callback(body)
|
||||
});
|
||||
}).on('error', function(e){
|
||||
// s.systemLog("Get Snapshot Error", e);
|
||||
});
|
||||
}
|
||||
s.multiplerHeight = 1
|
||||
s.multiplerWidth = 1
|
||||
s.detectObject=function(buffer,d,tx){
|
||||
d.tmpFile=s.gid(5)+'.jpg'
|
||||
if(!fs.existsSync(s.dir.streams)){
|
||||
fs.mkdirSync(s.dir.streams);
|
||||
}
|
||||
d.dir=s.dir.streams+d.ke+'/'
|
||||
if(!fs.existsSync(d.dir)){
|
||||
fs.mkdirSync(d.dir);
|
||||
}
|
||||
d.dir=s.dir.streams+d.ke+'/'+d.id+'/'
|
||||
if(!fs.existsSync(d.dir)){
|
||||
fs.mkdirSync(d.dir);
|
||||
}
|
||||
fs.writeFile(d.dir+d.tmpFile,buffer,function(err){
|
||||
if(err) return s.systemLog(err);
|
||||
if(s.isPythonRunning === false){
|
||||
return console.log('Python Script is not Running.')
|
||||
}
|
||||
var callbackId = s.gid(10)
|
||||
s.group[d.ke][d.id].sendToPython({path:d.dir+d.tmpFile,id:callbackId},function(data){
|
||||
console.log('returned',data)
|
||||
if(data.length > 0){
|
||||
var mats=[]
|
||||
data.forEach(function(v){
|
||||
var width = Math.sqrt( Math.pow(v.coordinates[1].x - v.coordinates[0].x, 2) + Math.pow(v.coordinates[1].y - v.coordinates[0].y, 2));
|
||||
var height = Math.sqrt( Math.pow(v.coordinates[2].x - v.coordinates[1].x, 2) + Math.pow(v.coordinates[2].y - v.coordinates[1].y, 2))
|
||||
mats.push({
|
||||
x:v.coordinates[0].x,
|
||||
y:v.coordinates[0].y,
|
||||
width: width,
|
||||
height: height,
|
||||
confidence:v.confidence,
|
||||
tag:v.tag
|
||||
})
|
||||
})
|
||||
tx({
|
||||
f:'trigger',
|
||||
id:d.id,
|
||||
ke:d.ke,
|
||||
details:{
|
||||
plug:config.plug,
|
||||
name:'dlib',
|
||||
reason:'object',
|
||||
matrices:mats,
|
||||
imgHeight:d.mon.detector_scale_y,
|
||||
imgWidth:d.mon.detector_scale_x
|
||||
}
|
||||
})
|
||||
}
|
||||
delete(s.callbacks[callbackId])
|
||||
exec('rm -rf '+d.dir+d.tmpFile,{encoding:'utf8'})
|
||||
})
|
||||
})
|
||||
}
|
||||
s.systemLog=function(q,w,e){
|
||||
if(w===undefined){return}
|
||||
if(!w){w=''}
|
||||
if(!e){e=''}
|
||||
if(config.systemLog===true){
|
||||
return console.log(moment().format(),q,w,e)
|
||||
}
|
||||
}
|
||||
s.MainEventController=function(d,cn,tx){
|
||||
switch(d.f){
|
||||
case'init_plugin_as_host':
|
||||
if(!cn){
|
||||
console.log('No CN',d)
|
||||
return
|
||||
}
|
||||
if(d.key!==config.key){
|
||||
console.log(new Date(),'Plugin Key Mismatch',cn.request.connection.remoteAddress,d)
|
||||
cn.emit('init',{ok:false})
|
||||
cn.disconnect()
|
||||
}else{
|
||||
console.log(new Date(),'Plugin Connected to Client',cn.request.connection.remoteAddress)
|
||||
cn.emit('init',{ok:true,plug:config.plug,notice:config.notice,type:config.type})
|
||||
}
|
||||
break;
|
||||
case'init_monitor':
|
||||
if(s.group[d.ke]&&s.group[d.ke][d.id]){
|
||||
delete(s.group[d.ke][d.id].buffer)
|
||||
}
|
||||
break;
|
||||
case'frame':
|
||||
try{
|
||||
if(!s.group[d.ke]){
|
||||
s.group[d.ke]={}
|
||||
}
|
||||
if(!s.group[d.ke][d.id]){
|
||||
s.group[d.ke][d.id]={
|
||||
sendToPython : s.createCameraBridgeToPython(d.ke+d.id)
|
||||
}
|
||||
}
|
||||
if(!s.group[d.ke][d.id].buffer){
|
||||
s.group[d.ke][d.id].buffer=[d.frame];
|
||||
}else{
|
||||
s.group[d.ke][d.id].buffer.push(d.frame)
|
||||
}
|
||||
if(d.frame[d.frame.length-2] === 0xFF && d.frame[d.frame.length-1] === 0xD9){
|
||||
s.detectObject(Buffer.concat(s.group[d.ke][d.id].buffer),d,tx)
|
||||
s.group[d.ke][d.id].buffer=null;
|
||||
}
|
||||
}catch(err){
|
||||
if(err){
|
||||
s.systemLog(err)
|
||||
delete(s.group[d.ke][d.id].buffer)
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
server.listen(config.hostPort);
|
||||
//web pages and plugin api
|
||||
app.get('/', function (req, res) {
|
||||
res.end('<b>'+config.plug+'</b> for Shinobi is running')
|
||||
});
|
||||
//Conector to Shinobi
|
||||
if(config.mode==='host'){
|
||||
//start plugin as host
|
||||
var io = require('socket.io')(server);
|
||||
io.attach(server);
|
||||
s.connectedClients={};
|
||||
io.on('connection', function (cn) {
|
||||
s.connectedClients[cn.id]={id:cn.id}
|
||||
s.connectedClients[cn.id].tx = function(data){
|
||||
data.pluginKey=config.key;data.plug=config.plug;
|
||||
return io.to(cn.id).emit('ocv',data);
|
||||
}
|
||||
cn.on('f',function(d){
|
||||
s.MainEventController(d,cn,s.connectedClients[cn.id].tx)
|
||||
});
|
||||
cn.on('disconnect',function(d){
|
||||
delete(s.connectedClients[cn.id])
|
||||
})
|
||||
});
|
||||
}else{
|
||||
//start plugin as client
|
||||
if(!config.host){config.host='localhost'}
|
||||
var io = socketIoClient('ws://'+config.host+':'+config.port);//connect to master
|
||||
s.cx=function(x){x.pluginKey=config.key;x.plug=config.plug;return io.emit('ocv',x)}
|
||||
io.on('connect',function(d){
|
||||
s.cx({f:'init',plug:config.plug,notice:config.notice,type:config.type});
|
||||
})
|
||||
io.on('disconnect',function(d){
|
||||
io.connect();
|
||||
})
|
||||
io.on('f',function(d){
|
||||
s.MainEventController(d,null,s.cx)
|
||||
})
|
||||
}
|
||||
|
||||
//Start Python Controller
|
||||
s.callbacks = {}
|
||||
s.createCameraBridgeToPython = function(uniqueId){
|
||||
var pythonIo = socketIoClient('ws://localhost:'+config.pythonPort,{transports : ['websocket']});
|
||||
var sendToPython = function(data,callback){
|
||||
s.callbacks[data.id] = callback
|
||||
pythonIo.emit('f',data)
|
||||
}
|
||||
pythonIo.on('connect',function(d){
|
||||
s.debugLog(uniqueId+' is Connected from Python')
|
||||
})
|
||||
pythonIo.on('disconnect',function(d){
|
||||
s.debugLog(uniqueId+' is Disconnected from Python')
|
||||
setTimeout(function(){
|
||||
pythonIo.connect();
|
||||
s.debugLog(uniqueId+' is Attempting to Reconect to Python')
|
||||
},3000)
|
||||
})
|
||||
pythonIo.on('f',function(d){
|
||||
if(s.callbacks[d.id]){
|
||||
s.callbacks[d.id](d.data)
|
||||
delete(s.callbacks[d.id])
|
||||
}
|
||||
})
|
||||
return sendToPython
|
||||
}
|
||||
|
||||
|
||||
//Start Python Daemon
|
||||
process.env.PYTHONUNBUFFERED = 1;
|
||||
s.createPythonProcess = function(){
|
||||
s.isPythonRunning = false
|
||||
s.pythonScript = spawn('sh',[__dirname+'/bootPy.sh',config.pythonScript,__dirname]);
|
||||
var onStdErr = function(data){
|
||||
s.debugLog(data.toString())
|
||||
}
|
||||
var onStdOut = function(data){
|
||||
s.debugLog(data.toString())
|
||||
}
|
||||
setTimeout(function(){
|
||||
s.isPythonRunning = true
|
||||
},5000)
|
||||
s.pythonScript.stderr.on('data',onStdErr);
|
||||
|
||||
s.pythonScript.stdout.on('data',onStdOut);
|
||||
|
||||
s.pythonScript.on('close', function () {
|
||||
s.debugLog('Python CLOSED')
|
||||
});
|
||||
}
|
||||
s.createPythonProcess()
|
|
@ -0,0 +1,5 @@
|
|||
conf.json
|
||||
cascades
|
||||
cfg
|
||||
weights
|
||||
data
|
|
@ -0,0 +1,109 @@
|
|||
#!/bin/bash
|
||||
echo "-----------------------------------------------"
|
||||
echo "-- Installing Python Yolo Plugin for Shinobi --"
|
||||
echo "-----------------------------------------------"
|
||||
echo "-----------------------------------"
|
||||
if [ ! -d "weights" ]; then
|
||||
echo "Downloading yolov3 weights..."
|
||||
mkdir weights
|
||||
wget -O weights/yolov3.weights https://pjreddie.com/media/files/yolov3.weights
|
||||
else
|
||||
echo "yolov3 weights found..."
|
||||
fi
|
||||
echo "-----------------------------------"
|
||||
if [ ! -d "cfg" ]; then
|
||||
echo "Downloading yolov3 cfg"
|
||||
mkdir cfg
|
||||
wget -O cfg/coco.data https://raw.githubusercontent.com/pjreddie/darknet/master/cfg/coco.data
|
||||
wget -O cfg/yolov3.cfg https://raw.githubusercontent.com/pjreddie/darknet/master/cfg/yolov3.cfg
|
||||
else
|
||||
echo "yolov3 cfg found..."
|
||||
fi
|
||||
echo "-----------------------------------"
|
||||
if [ ! -d "data" ]; then
|
||||
echo "Downloading yolov3 data"
|
||||
mkdir data
|
||||
wget -O data/coco.names https://raw.githubusercontent.com/pjreddie/darknet/master/data/coco.names
|
||||
else
|
||||
echo "yolov3 data found..."
|
||||
fi
|
||||
echo "-----------------------------------"
|
||||
if [ ! -e "./conf.json" ]; then
|
||||
echo "Creating conf.json"
|
||||
sudo cp conf.sample.json conf.json
|
||||
else
|
||||
echo "conf.json already exists..."
|
||||
fi
|
||||
echo "-----------------------------------"
|
||||
sudo apt update -y
|
||||
sudo apt-get install libxml2-dev libxslt-dev libxslt1-dev zlib1g-dev -y
|
||||
echo "Installing python3"
|
||||
sudo apt install python3 python3-dev python3-pip -y
|
||||
sudo apt install python3-lxml libxml2-dev -y
|
||||
echo "-----------------------------------"
|
||||
if ! [ -x "$(command -v gcc-6)" ]; then
|
||||
echo "Installing gcc-6 and g++-6"
|
||||
sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
|
||||
sudo apt-get install gcc-6 g++-6 -y && sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-6 60 --slave /usr/bin/g++ g++ /usr/bin/g++-6
|
||||
else
|
||||
echo "gcc-6 and g++-6 found..."
|
||||
fi
|
||||
echo "-----------------------------------"
|
||||
if ! [ -x "$(command -v nvidia-smi)" ]; then
|
||||
echo "You need to install NVIDIA Drivers to use this."
|
||||
echo "inside the Shinobi directory run the following :"
|
||||
echo "sh INSTALL/cuda9-part1.sh"
|
||||
exit 1
|
||||
else
|
||||
echo "NVIDIA Drivers found..."
|
||||
echo "$(nvidia-smi |grep 'Driver Version')"
|
||||
fi
|
||||
echo "-----------------------------------"
|
||||
if [ ! -d "/usr/local/cuda" ]; then
|
||||
echo "You need to install CUDA Toolkit to use this."
|
||||
echo "inside the Shinobi directory run the following :"
|
||||
echo "sh INSTALL/cuda9-part2-after-reboot.sh"
|
||||
exit 1
|
||||
else
|
||||
echo "CUDA Toolkit found..."
|
||||
fi
|
||||
echo "-----------------------------------"
|
||||
if ! [ -x "$(command -v opencv_version)" ]; then
|
||||
echo "You need to install OpenCV with CUDA first."
|
||||
echo "inside the Shinobi directory run the following :"
|
||||
echo "sh INSTALL/opencv-cuda.sh"
|
||||
exit 1
|
||||
else
|
||||
echo "OpenCV found... : $(opencv_version)"
|
||||
fi
|
||||
echo "-----------------------------------"
|
||||
echo "Getting new pip..."
|
||||
pip3 install --upgrade pip
|
||||
pip install --user --upgrade pip
|
||||
echo "Smoking pips..."
|
||||
pip3 install flask_socketio
|
||||
pip3 install flask
|
||||
pip3 install numpy
|
||||
pip3 install gevent gevent-websocket
|
||||
export PATH=/usr/local/cuda/bin:$PATH
|
||||
pip3 install lxml
|
||||
pip3 install numpy
|
||||
pip3 install cython
|
||||
echo "Installing Darknet..."
|
||||
cd /opt
|
||||
git clone https://github.com/pjreddie/darknet.git darknet
|
||||
cd darknet
|
||||
make
|
||||
cd ..
|
||||
echo "Installing YOLO3-4-Py"
|
||||
echo "Learn more about this wrapper here : https://github.com/madhawav/YOLO3-4-Py"
|
||||
git clone https://github.com/madhawav/YOLO3-4-Py.git YOLO3-4-Py
|
||||
cd YOLO3-4-Py
|
||||
export GPU=1
|
||||
export OPENCV=1
|
||||
pip3 install .
|
||||
apt remove libpython-all-dev python-all python-all-dev python-asn1crypto python-cffi-backend python-crypto python-cryptography python-dbus python-enum34 python-gi python-idna python-ipaddress python-keyring python-keyrings.alt python-pkg-resources python-secretstorage python-setuptools python-six python-wheel python-xdg -y
|
||||
echo "Done Installing Darknet..."
|
||||
export PATH=/opt/darknet:$PATH
|
||||
echo "Start the plugin with pm2 like so :"
|
||||
echo "pm2 start shinobi-python-yolo.js"
|
|
@ -0,0 +1,72 @@
|
|||
# Python Yolo
|
||||
|
||||
> This plugin requires the use of port `7990` by default. You can specify a different port by adding `pythonPort` to your plugin's conf.json.
|
||||
|
||||
**Ubuntu and Debian only**
|
||||
|
||||
Go to the Shinobi directory. **/home/Shinobi** is the default directory.
|
||||
|
||||
```
|
||||
cd /home/Shinobi/plugins/python-yolo
|
||||
```
|
||||
|
||||
Copy the config file.
|
||||
|
||||
```
|
||||
sh INSTALL.sh
|
||||
```
|
||||
|
||||
Start the plugin.
|
||||
|
||||
```
|
||||
pm2 start shinobi-python-yolo.js
|
||||
```
|
||||
|
||||
Doing this will reveal options in the monitor configuration. Shinobi does not need to be restarted when a plugin is initiated or stopped.
|
||||
|
||||
## Run the plugin as a Host
|
||||
> The main app (Shinobi) will be the client and the plugin will be the host. The purpose of allowing this method is so that you can use one plugin for multiple Shinobi instances. Allowing you to easily manage connections without starting multiple processes.
|
||||
|
||||
Edit your plugins configuration file. Set the `hostPort` **to be different** than the `listening port for camera.js`.
|
||||
|
||||
```
|
||||
nano conf.json
|
||||
```
|
||||
|
||||
Here is a sample of a Host configuration for the plugin.
|
||||
- `plug` is the name of the plugin corresponding in the main configuration file.
|
||||
- `https` choose if you want to use SSL or not. Default is `false`.
|
||||
- `hostPort` can be any available port number. **Don't make this the same port number as Shinobi.** Default is `8082`.
|
||||
- `type` tells the main application (Shinobi) what kind of plugin it is. In this case it is a detector.
|
||||
|
||||
```
|
||||
{
|
||||
"plug":"PythonYolo",
|
||||
"hostPort":8082,
|
||||
"key":"SomeOpenALPRkeySoPeopleDontMessWithYourShinobi",
|
||||
"mode":"host",
|
||||
"type":"detector"
|
||||
}
|
||||
```
|
||||
|
||||
Now modify the **main configuration file** located in the main directory of Shinobi. *Where you currently should be.*
|
||||
|
||||
```
|
||||
nano conf.json
|
||||
```
|
||||
|
||||
Add the `plugins` array if you don't already have it. Add the following *object inside the array*.
|
||||
|
||||
```
|
||||
"plugins":[
|
||||
{
|
||||
"id" : "PythonYolo",
|
||||
"https" : false,
|
||||
"host" : "localhost",
|
||||
"port" : 8082,
|
||||
"key" : "SomeOpenALPRkeySoPeopleDontMessWithYourShinobi",
|
||||
"mode" : "host",
|
||||
"type" : "detector"
|
||||
}
|
||||
],
|
||||
```
|
|
@ -0,0 +1 @@
|
|||
python3 -u $@
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"plug":"PythonYolo",
|
||||
"host":"localhost",
|
||||
"port":8080,
|
||||
"pythonPort":7990,
|
||||
"hostPort":8082,
|
||||
"key":"SomeOpenALPRkeySoPeopleDontMessWithYourShinobi",
|
||||
"mode":"client",
|
||||
"type":"detector"
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
echo "Downloading config files..."
|
||||
|
||||
mkdir cfg
|
||||
wget -O cfg/coco.data https://raw.githubusercontent.com/pjreddie/darknet/master/cfg/coco.data
|
||||
wget -O cfg/yolov3.cfg https://raw.githubusercontent.com/pjreddie/darknet/master/cfg/yolov3.cfg
|
||||
|
||||
mkdir data
|
||||
wget -O data/coco.names https://raw.githubusercontent.com/pjreddie/darknet/master/data/coco.names
|
||||
|
||||
echo "Downloading yolov3 weights"
|
||||
mkdir weights
|
||||
wget -O weights/yolov3.weights https://pjreddie.com/media/files/yolov3.weights
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "shinobi-python-yolo",
|
||||
"version": "1.0.0",
|
||||
"description": "YOLOv3 plugin for Shinobi that uses Python functions for detection.",
|
||||
"main": "shinobi-python-yolo.js",
|
||||
"dependencies": {
|
||||
"socket.io-client": "^1.7.4",
|
||||
"express": "^4.16.2",
|
||||
"moment": "^2.19.2",
|
||||
"socket.io": "^2.0.4"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Moe Alam",
|
||||
"license": "ISC"
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
from flask import Flask, request, jsonify, render_template
|
||||
from flask_socketio import SocketIO, emit
|
||||
from pydarknet import Detector, Image
|
||||
import cv2
|
||||
import os
|
||||
import json
|
||||
import numpy as np
|
||||
import sys
|
||||
|
||||
dirname = sys.argv[1]
|
||||
|
||||
try:
|
||||
with open("{}/conf.json".format(dirname)) as json_file:
|
||||
config = json.load(json_file)
|
||||
httpPort = config['pythonPort']
|
||||
try:
|
||||
httpPort
|
||||
except NameError:
|
||||
httpPort = 7990
|
||||
except Exception as e:
|
||||
print("conf.json not found.")
|
||||
httpPort = 7990
|
||||
|
||||
# Load Flask
|
||||
app = Flask("YOLOv3 for Shinobi (Pumpkin Pie)")
|
||||
socketio = SocketIO(app)
|
||||
# Silence Flask
|
||||
import logging
|
||||
log = logging.getLogger('werkzeug')
|
||||
log.setLevel(logging.ERROR)
|
||||
|
||||
# Load Darknet
|
||||
net = Detector(bytes("{}/cfg/yolov3.cfg".format(dirname), encoding="utf-8"), bytes("{}/weights/yolov3.weights".format(dirname), encoding="utf-8"), 0, bytes("{}/cfg/coco.data".format(dirname),encoding="utf-8"))
|
||||
|
||||
def spark(filepath):
|
||||
try:
|
||||
filepath
|
||||
except NameError:
|
||||
return "File path not found."
|
||||
img = cv2.imread(filepath)
|
||||
|
||||
img2 = Image(img)
|
||||
|
||||
# r = net.classify(img2)
|
||||
results = net.detect(img2)
|
||||
returnData = '[]'
|
||||
try:
|
||||
new_list = []
|
||||
for item in results:
|
||||
sub_list = {}
|
||||
i = 0
|
||||
for sub_item in item:
|
||||
if i == 0:
|
||||
key = 'tag'
|
||||
sub_list[key] = sub_item.decode('utf-8')
|
||||
if i == 1:
|
||||
key = 'confidence'
|
||||
sub_list[key] = sub_item
|
||||
if i == 2:
|
||||
key = 'points'
|
||||
points_list = []
|
||||
for points_item in sub_item:
|
||||
points_list.append(points_item)
|
||||
sub_list[key] = points_list
|
||||
i += 1
|
||||
new_list.append(sub_list)
|
||||
returnData = new_list
|
||||
# returnData = json.dumps(results)
|
||||
except Exception as e:
|
||||
returnData = ',\n'.join(map(str, results))
|
||||
|
||||
return returnData
|
||||
|
||||
# bake the image data by a file path
|
||||
# POST body contains the "img" variable. The value should be to a local image path.
|
||||
# Example : /dev/shm/streams/[GROUP_KEY]/[MONITOR_ID]/s.jpg
|
||||
@app.route('/', methods=['GET'])
|
||||
def index():
|
||||
return "Pumpkin.py is running. This web interface should NEVER be accessible remotely. The Node.js plugin that runs this script should only be allowed accessible remotely."
|
||||
|
||||
# bake the image data by a file path
|
||||
# POST body contains the "img" variable. The value should be to a local image path.
|
||||
# Example : /dev/shm/streams/[GROUP_KEY]/[MONITOR_ID]/s.jpg
|
||||
@app.route('/post', methods=['POST'])
|
||||
def post():
|
||||
filepath = request.form['img']
|
||||
return jsonify(spark(filepath))
|
||||
|
||||
# bake the image data by a file path
|
||||
# GET string contains the "img" variable. The value should be to a local image path.
|
||||
# Example : /dev/shm/streams/[GROUP_KEY]/[MONITOR_ID]/s.jpg
|
||||
@app.route('/get', methods=['GET'])
|
||||
def get():
|
||||
filepath = request.args.get('img')
|
||||
return jsonify(spark(filepath))
|
||||
|
||||
@socketio.on('f')
|
||||
def receiveMessage(message):
|
||||
emit('f',{'id':message.get("id"),'data':spark(message.get("path"))})
|
||||
|
||||
# quick-and-dirty start
|
||||
if __name__ == '__main__':
|
||||
socketio.run(app, port=httpPort)
|
|
@ -0,0 +1,296 @@
|
|||
//
|
||||
// Shinobi - Python YOLOv3 Plugin
|
||||
// Copyright (C) 2016-2025 Moe Alam, moeiscool
|
||||
//
|
||||
// # Donate
|
||||
//
|
||||
// If you like what I am doing here and want me to continue please consider donating :)
|
||||
// PayPal : paypal@m03.ca
|
||||
//
|
||||
process.on('uncaughtException', function (err) {
|
||||
console.error('uncaughtException',err);
|
||||
});
|
||||
//main vars
|
||||
var fs=require('fs');
|
||||
var exec = require('child_process').exec;
|
||||
var spawn = require('child_process').spawn;
|
||||
var moment = require('moment');
|
||||
var http = require('http');
|
||||
var express = require('express');
|
||||
var socketIoClient = require('socket.io-client');
|
||||
var config = require('./conf.json');
|
||||
var http = require('http'),
|
||||
app = express(),
|
||||
server = http.createServer(app);
|
||||
s={
|
||||
group:{},
|
||||
dir:{},
|
||||
isWin:(process.platform==='win32'),
|
||||
s:function(json){return JSON.stringify(json,null,3)}
|
||||
}
|
||||
s.checkCorrectPathEnding=function(x){
|
||||
var length=x.length
|
||||
if(x.charAt(length-1)!=='/'){
|
||||
x=x+'/'
|
||||
}
|
||||
return x.replace('__DIR__',__dirname)
|
||||
}
|
||||
s.debugLog = function(){
|
||||
if(config.debugLog === true){
|
||||
console.log(new Date(),arguments)
|
||||
if(config.debugLogVerbose === true){
|
||||
console.log(new Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!config.port){config.port=8080}
|
||||
if(!config.pythonScript){config.pythonScript=__dirname+'/pumpkin.py'}
|
||||
if(!config.pythonPort){config.pythonPort=7990}
|
||||
if(!config.hostPort){config.hostPort=8082}
|
||||
if(config.systemLog===undefined){config.systemLog=true}
|
||||
if(config.alprConfig===undefined){config.alprConfig=__dirname+'/openalpr.conf'}
|
||||
//default stream folder check
|
||||
if(!config.streamDir){
|
||||
if(s.isWin===false){
|
||||
config.streamDir='/dev/shm'
|
||||
}else{
|
||||
config.streamDir=config.windowsTempDir
|
||||
}
|
||||
if(!fs.existsSync(config.streamDir)){
|
||||
config.streamDir=__dirname+'/streams/'
|
||||
}else{
|
||||
config.streamDir+='/streams/'
|
||||
}
|
||||
}
|
||||
s.dir.streams=config.streamDir;
|
||||
//streams dir
|
||||
if(!fs.existsSync(s.dir.streams)){
|
||||
fs.mkdirSync(s.dir.streams);
|
||||
}
|
||||
s.gid=function(x){
|
||||
if(!x){x=10};var t = "";var p = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
for( var i=0; i < x; i++ )
|
||||
t += p.charAt(Math.floor(Math.random() * p.length));
|
||||
return t;
|
||||
};
|
||||
s.getRequest = function(url,callback){
|
||||
return http.get(url, function(res){
|
||||
var body = '';
|
||||
res.on('data', function(chunk){
|
||||
body += chunk;
|
||||
});
|
||||
res.on('end',function(){
|
||||
try{body = JSON.parse(body)}catch(err){}
|
||||
callback(body)
|
||||
});
|
||||
}).on('error', function(e){
|
||||
// s.systemLog("Get Snapshot Error", e);
|
||||
});
|
||||
}
|
||||
s.multiplerHeight = 0.75
|
||||
s.multiplerWidth = 0.96
|
||||
s.detectObject=function(buffer,d,tx){
|
||||
d.tmpFile=s.gid(5)+'.jpg'
|
||||
if(!fs.existsSync(s.dir.streams)){
|
||||
fs.mkdirSync(s.dir.streams);
|
||||
}
|
||||
d.dir=s.dir.streams+d.ke+'/'
|
||||
if(!fs.existsSync(d.dir)){
|
||||
fs.mkdirSync(d.dir);
|
||||
}
|
||||
d.dir=s.dir.streams+d.ke+'/'+d.id+'/'
|
||||
if(!fs.existsSync(d.dir)){
|
||||
fs.mkdirSync(d.dir);
|
||||
}
|
||||
fs.writeFile(d.dir+d.tmpFile,buffer,function(err){
|
||||
if(err) return s.systemLog(err);
|
||||
if(s.isPythonRunning === false){
|
||||
return console.log('Python Script is not Running.')
|
||||
}
|
||||
var callbackId = s.gid(10)
|
||||
s.group[d.ke][d.id].sendToPython({path:d.dir+d.tmpFile,id:callbackId},function(data){
|
||||
if(data.length > 0){
|
||||
var mats=[]
|
||||
data.forEach(function(v){
|
||||
mats.push({
|
||||
x:v.points[0] * s.multiplerWidth,
|
||||
y:v.points[1] * s.multiplerHeight,
|
||||
width:v.points[2],
|
||||
height:v.points[3],
|
||||
confidence:v.confidence,
|
||||
tag:v.tag
|
||||
})
|
||||
})
|
||||
tx({
|
||||
f:'trigger',
|
||||
id:d.id,
|
||||
ke:d.ke,
|
||||
details:{
|
||||
plug:config.plug,
|
||||
name:'yolo',
|
||||
reason:'object',
|
||||
matrices:mats,
|
||||
imgHeight:d.mon.detector_scale_y,
|
||||
imgWidth:d.mon.detector_scale_x
|
||||
}
|
||||
})
|
||||
}
|
||||
delete(s.callbacks[callbackId])
|
||||
exec('rm -rf '+d.dir+d.tmpFile,{encoding:'utf8'})
|
||||
})
|
||||
})
|
||||
}
|
||||
s.systemLog=function(q,w,e){
|
||||
if(w===undefined){return}
|
||||
if(!w){w=''}
|
||||
if(!e){e=''}
|
||||
if(config.systemLog===true){
|
||||
return console.log(moment().format(),q,w,e)
|
||||
}
|
||||
}
|
||||
s.MainEventController=function(d,cn,tx){
|
||||
switch(d.f){
|
||||
case'init_plugin_as_host':
|
||||
if(!cn){
|
||||
console.log('No CN',d)
|
||||
return
|
||||
}
|
||||
if(d.key!==config.key){
|
||||
console.log(new Date(),'Plugin Key Mismatch',cn.request.connection.remoteAddress,d)
|
||||
cn.emit('init',{ok:false})
|
||||
cn.disconnect()
|
||||
}else{
|
||||
console.log(new Date(),'Plugin Connected to Client',cn.request.connection.remoteAddress)
|
||||
cn.emit('init',{ok:true,plug:config.plug,notice:config.notice,type:config.type})
|
||||
}
|
||||
break;
|
||||
case'init_monitor':
|
||||
if(s.group[d.ke]&&s.group[d.ke][d.id]){
|
||||
delete(s.group[d.ke][d.id].buffer)
|
||||
}
|
||||
break;
|
||||
case'frame':
|
||||
try{
|
||||
if(!s.group[d.ke]){
|
||||
s.group[d.ke]={}
|
||||
}
|
||||
if(!s.group[d.ke][d.id]){
|
||||
s.group[d.ke][d.id]={
|
||||
sendToPython : s.createCameraBridgeToPython(d.ke+d.id)
|
||||
}
|
||||
}
|
||||
if(!s.group[d.ke][d.id].buffer){
|
||||
s.group[d.ke][d.id].buffer=[d.frame];
|
||||
}else{
|
||||
s.group[d.ke][d.id].buffer.push(d.frame)
|
||||
}
|
||||
if(d.frame[d.frame.length-2] === 0xFF && d.frame[d.frame.length-1] === 0xD9){
|
||||
s.detectObject(Buffer.concat(s.group[d.ke][d.id].buffer),d,tx)
|
||||
s.group[d.ke][d.id].buffer=null;
|
||||
}
|
||||
}catch(err){
|
||||
if(err){
|
||||
s.systemLog(err)
|
||||
delete(s.group[d.ke][d.id].buffer)
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
server.listen(config.hostPort);
|
||||
//web pages and plugin api
|
||||
app.get('/', function (req, res) {
|
||||
res.end('<b>'+config.plug+'</b> for Shinobi is running')
|
||||
});
|
||||
//Conector to Shinobi
|
||||
if(config.mode==='host'){
|
||||
//start plugin as host
|
||||
var io = require('socket.io')(server);
|
||||
io.attach(server);
|
||||
s.connectedClients={};
|
||||
io.on('connection', function (cn) {
|
||||
s.connectedClients[cn.id]={id:cn.id}
|
||||
s.connectedClients[cn.id].tx = function(data){
|
||||
data.pluginKey=config.key;data.plug=config.plug;
|
||||
return io.to(cn.id).emit('ocv',data);
|
||||
}
|
||||
cn.on('f',function(d){
|
||||
s.MainEventController(d,cn,s.connectedClients[cn.id].tx)
|
||||
});
|
||||
cn.on('disconnect',function(d){
|
||||
delete(s.connectedClients[cn.id])
|
||||
})
|
||||
});
|
||||
}else{
|
||||
//start plugin as client
|
||||
if(!config.host){config.host='localhost'}
|
||||
var io = socketIoClient('ws://'+config.host+':'+config.port);//connect to master
|
||||
s.cx=function(x){x.pluginKey=config.key;x.plug=config.plug;return io.emit('ocv',x)}
|
||||
io.on('connect',function(d){
|
||||
s.cx({f:'init',plug:config.plug,notice:config.notice,type:config.type});
|
||||
})
|
||||
io.on('disconnect',function(d){
|
||||
io.connect();
|
||||
})
|
||||
io.on('f',function(d){
|
||||
s.MainEventController(d,null,s.cx)
|
||||
})
|
||||
}
|
||||
|
||||
//Start Python Controller
|
||||
s.callbacks = {}
|
||||
s.createCameraBridgeToPython = function(uniqueId){
|
||||
var pythonIo = socketIoClient('ws://localhost:'+config.pythonPort,{transports : ['websocket']});
|
||||
var sendToPython = function(data,callback){
|
||||
s.callbacks[data.id] = callback
|
||||
pythonIo.emit('f',data)
|
||||
}
|
||||
pythonIo.on('connect',function(d){
|
||||
s.debugLog(uniqueId+' is Connected from Python')
|
||||
})
|
||||
pythonIo.on('disconnect',function(d){
|
||||
s.debugLog(uniqueId+' is Disconnected from Python')
|
||||
setTimeout(function(){
|
||||
pythonIo.connect();
|
||||
s.debugLog(uniqueId+' is Attempting to Reconect to Python')
|
||||
},3000)
|
||||
})
|
||||
pythonIo.on('f',function(d){
|
||||
if(s.callbacks[d.id]){
|
||||
s.callbacks[d.id](d.data)
|
||||
delete(s.callbacks[d.id])
|
||||
}
|
||||
})
|
||||
return sendToPython
|
||||
}
|
||||
|
||||
|
||||
//Start Python Daemon
|
||||
process.env.PYTHONUNBUFFERED = 1;
|
||||
s.createPythonProcess = function(){
|
||||
s.isPythonRunning = false
|
||||
s.pythonScript = spawn('sh',[__dirname+'/bootPy.sh',config.pythonScript,__dirname]);
|
||||
var onStdErr = function (data) {
|
||||
s.debugLog('Python ERR')
|
||||
data = data.toString()
|
||||
s.debugLog(data)
|
||||
if(data.indexOf('Done!') > -1){
|
||||
console.log('PYTHON READY')
|
||||
s.isPythonRunning = true
|
||||
onStdErr = function(data){
|
||||
s.debugLog(data.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
s.pythonScript.stderr.on('data',onStdErr);
|
||||
|
||||
s.pythonScript.stdout.on('data', function (data) {
|
||||
s.debugLog('Python OUT')
|
||||
s.debugLog(data.toString())
|
||||
});
|
||||
|
||||
s.pythonScript.on('close', function () {
|
||||
s.debugLog('Python CLOSED')
|
||||
});
|
||||
}
|
||||
s.createPythonProcess()
|
|
@ -82,6 +82,13 @@ img{max-width:100%}
|
|||
.nodata .divider{margin:5px 0}
|
||||
.loading .divider{margin:5px 0}
|
||||
|
||||
#accbtn{
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.monitor_item .stream-hud{opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;z-index:2}
|
||||
.monitor_item .stream-hud .controls{position:absolute;top:10px;left:10px;}
|
||||
.monitor_item .stream-hud:hover{opacity:1}
|
||||
|
@ -280,6 +287,7 @@ form.modal-body{margin:0}
|
|||
.dropdown-menu.scrollable{max-height:300px}
|
||||
.upload_file input{display:none}
|
||||
#video_preview .stream-objects{right:0;margin:auto;display:inline-block;position:relative;width:auto}
|
||||
.stream-block,.stream-objects{overflow: hidden!important}
|
||||
.stream-objects{position:absolute;top:0;left:0;width:100%;height:100%;z-index:1}
|
||||
.stream-objects .tag{position:absolute;bottom:100%;left:0;background:red;color:#fff;font-family:monospace;font-size:80%;border-radius:5px 5px 0 0 ;padding:3px 5px;}
|
||||
.stream-objects .stream-detected-object{position:absolute;top:0;left:0;border:3px solid red;background:transparent;border-radius:5px}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -183,7 +183,7 @@ switch($user.details.lang){
|
|||
url=url+'/'
|
||||
}
|
||||
}else{
|
||||
url = ''
|
||||
url = '<%-originalURL%>'
|
||||
}
|
||||
return url
|
||||
break;
|
||||
|
@ -383,6 +383,9 @@ switch($user.details.lang){
|
|||
case'flv':
|
||||
streamURL=$.ccio.init('location',user)+user.auth_token+'/flv/'+d.ke+'/'+d.mid+'/s.flv'
|
||||
break;
|
||||
case'h265':
|
||||
streamURL=$.ccio.init('location',user)+user.auth_token+'/h265/'+d.ke+'/'+d.mid+'/s.hevc'
|
||||
break;
|
||||
case'mp4':
|
||||
streamURL=$.ccio.init('location',user)+user.auth_token+'/mp4/'+d.ke+'/'+d.mid+'/s.mp4'
|
||||
break;
|
||||
|
@ -514,7 +517,7 @@ switch($user.details.lang){
|
|||
$.ccio.mon_groups[b][v.mid]=v;
|
||||
})
|
||||
}catch(er){
|
||||
|
||||
|
||||
}
|
||||
})
|
||||
return $.ccio.mon_groups;
|
||||
|
@ -655,7 +658,7 @@ switch($user.details.lang){
|
|||
d.fn=function(){
|
||||
if(!d.speed){d.speed=1000}
|
||||
switch(d.d.stream_type){
|
||||
case'b64':case'pam':
|
||||
case'b64':case'h265':
|
||||
d.p.resize()
|
||||
break;
|
||||
case'hls':case'flv':case'mp4':
|
||||
|
@ -770,8 +773,10 @@ switch($user.details.lang){
|
|||
ctx.drawImage(img, 0, 0,c.width,c.height);
|
||||
extend(atob(c.toDataURL('image/jpeg').split(',')[1]),c.width,c.height)
|
||||
break;
|
||||
case'pam':
|
||||
alert('Need to add')
|
||||
case'h265':
|
||||
var c = $('[mid='+e.mon.mid+'].monitor_item canvas')[0];
|
||||
var ctx = c.getContext('2d');
|
||||
extend(atob(c.toDataURL('image/jpeg').split(',')[1]),c.width,c.height)
|
||||
break;
|
||||
case'b64':
|
||||
base64 = e.mon.last_frame.split(',')[1];
|
||||
|
@ -779,7 +784,7 @@ switch($user.details.lang){
|
|||
image_data.src = base64;
|
||||
extend(atob(base64),image_data.width,image_data.height)
|
||||
break;
|
||||
case'jpeg':
|
||||
case'jpeg':case'h265':
|
||||
url=e.p.find('.stream-element').attr('src');
|
||||
image_data = new Image();
|
||||
image_data.src = url;
|
||||
|
@ -1129,7 +1134,7 @@ switch($user.details.lang){
|
|||
case'jpeg':
|
||||
tmp+='<img class="stream-element">';
|
||||
break;
|
||||
default://base64
|
||||
default://base64//h265
|
||||
tmp+='<canvas class="stream-element"></canvas>';
|
||||
break;
|
||||
}
|
||||
|
@ -1153,6 +1158,63 @@ switch($user.details.lang){
|
|||
}
|
||||
$.ccio.init('ls')
|
||||
break;
|
||||
case'detector-filters-where':
|
||||
if(!d)d={};
|
||||
d.id=$('#filters_where .row').length;
|
||||
if(!d.p1){d.p1='indifference'}
|
||||
if(!d.p2){d.p2='='}
|
||||
if(!d.p3){d.p3=''}
|
||||
if(!d.p4){d.p4='&&'}
|
||||
tmp+='<div class="row where-row">'
|
||||
tmp+=' <div class="form-group col-md-3">'
|
||||
tmp+=' <label>'
|
||||
tmp+=' <select class="form-control" where="p1">'
|
||||
tmp+=' <option value="indifference" selected><%-cleanLang(lang['Indifference'])%></option>'
|
||||
tmp+=' <option value="name"><%-cleanLang(lang['Region Name'])%></option>'
|
||||
tmp+=' <option value="reason"><%-cleanLang(lang['Reason'])%></option>'
|
||||
tmp+=' <option value="plug"><%-cleanLang(lang['Detection Engine'])%></option>'
|
||||
tmp+=' <optgroup label="Matrix">'
|
||||
tmp+=' <option value="tag"><%-cleanLang(lang['Object Tag'])%></option>'
|
||||
tmp+=' <option value="confidence"><%-cleanLang(lang['Confidence'])%></option>'
|
||||
tmp+=' <option value="x"><%-cleanLang(lang['X Point'])%></option>'
|
||||
tmp+=' <option value="y"><%-cleanLang(lang['Y Point'])%></option>'
|
||||
tmp+=' <option value="height"><%-cleanLang(lang['Height'])%></option>'
|
||||
tmp+=' <option value="width"><%-cleanLang(lang['Width'])%></option>'
|
||||
tmp+=' </optgroup>'
|
||||
tmp+=' </select>'
|
||||
tmp+=' </label>'
|
||||
tmp+=' </div>'
|
||||
tmp+=' <div class="form-group col-md-3">'
|
||||
tmp+=' <label>'
|
||||
tmp+=' <select class="form-control" where="p2">'
|
||||
tmp+=' <option value="===" selected><%-cleanLang(lang['Equal to'])%></option>'
|
||||
tmp+=' <option value="!=="><%-cleanLang(lang['Not Equal to'])%></option>'
|
||||
tmp+=' <option value="indexOf"><%-cleanLang(lang['Contains'])%></option>'
|
||||
tmp+=' <option value="!indexOf"><%-cleanLang(lang['Does Not Contain'])%></option>'
|
||||
tmp+=' <optgroup label="For Numbers">'
|
||||
tmp+=' <option value=">="><%-cleanLang(lang['Greater Than or Equal to'])%></option>'
|
||||
tmp+=' <option value=">"><%-cleanLang(lang['Greater Than'])%></option>'
|
||||
tmp+=' <option value="<"><%-cleanLang(lang['Less Than'])%></option>'
|
||||
tmp+=' <option value="<="><%-cleanLang(lang['Less Than or Equal to'])%></option>'
|
||||
tmp+=' </optgroup>'
|
||||
tmp+=' </select>'
|
||||
tmp+=' </label>'
|
||||
tmp+=' </div>'
|
||||
tmp+=' <div class="form-group col-md-3">'
|
||||
tmp+=' <label>'
|
||||
tmp+=' <input class="form-control" placeholder="Value" title="<%-cleanLang(lang.Value)%>" where="p3">'
|
||||
tmp+=' </label>'
|
||||
tmp+=' </div>'
|
||||
tmp+=' <div class="form-group col-md-3">'
|
||||
tmp+=' <label>'
|
||||
tmp+=' <select class="form-control" where="p4">'
|
||||
tmp+=' <option value="&&" selected><%-cleanLang(lang['AND'])%></option>'
|
||||
tmp+=' <option value="||"><%-cleanLang(lang['OR'])%></option>'
|
||||
tmp+=' </select>'
|
||||
tmp+=' </label>'
|
||||
tmp+=' </div>'
|
||||
tmp+='</div>'
|
||||
break;
|
||||
case'filters-where':
|
||||
if(!d)d={};
|
||||
d.id=$('#filters_where .row').length;
|
||||
|
@ -1382,9 +1444,12 @@ switch($user.details.lang){
|
|||
{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'},
|
||||
{label:'<%-cleanLang(lang['h264_qsv'])%>',value:'h264_qsv',group:'Quick Sync Video'},
|
||||
{label:'<%-cleanLang(lang['hevc_qsv'])%>',value:'hevc_qsv',group:'Quick Sync Video'},
|
||||
{label:'<%-cleanLang(lang['mpeg2_qsv'])%>',value:'mpeg2_qsv',group:'Quick Sync Video'},
|
||||
{label:'<%-cleanLang(lang['h264_mmal'])%>',value:'h264_mmal',group:'Raspberry Pi'},
|
||||
{label:'<%-cleanLang(lang['mpeg2_mmal'])%>',value:'mpeg2_mmal',group:'Raspberry Pi'},
|
||||
{label:'<%-cleanLang(lang['mpeg4_mmal'])%>',value:'mpeg4_mmal',group:'Raspberry Pi'},
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -1494,6 +1559,7 @@ switch($user.details.lang){
|
|||
tmp+=' <option value="h264_qsv"><%-lang["h264_qsv"]%></option>'
|
||||
tmp+=' <option value="hevc_qsv"><%-lang["hevc_qsv"]%></option>'
|
||||
tmp+=' <option value="mpeg2_qsv"><%-lang["mpeg2_qsv"]%></option>'
|
||||
tmp+=' <option value="h264_omx"><%-lang["h264_omx"]%></option>'
|
||||
tmp+=' </optgroup>'
|
||||
tmp+=' </select></div>'
|
||||
tmp+=' </label>'
|
||||
|
@ -1654,6 +1720,14 @@ switch($user.details.lang){
|
|||
k.mon=$.ccio.mon[d.ke+d.mid+user.auth_token]
|
||||
$.ccio.init('monitorInfo',k)
|
||||
break;
|
||||
case'detector-filters-where':
|
||||
$('#detector_filters_where').append(tmp);
|
||||
$('#detector_filters_where .row [where="p4"][disabled]').prop('disabled',false)
|
||||
$('#detector_filters_where .row:last [where="p1"]').val(d.p1)
|
||||
$('#detector_filters_where .row:last [where="p2"]').val(d.p2)
|
||||
$('#detector_filters_where .row:last [where="p3"]').val(d.p3)
|
||||
$('#detector_filters_where .row:last [where="p4"]').val(d.p4).prop('disabled',true)
|
||||
break;
|
||||
case'filters-where':
|
||||
$('#filters_where').append(tmp);
|
||||
$('#filters_where .row:last [where="p1"]').val(d.p1)
|
||||
|
@ -1831,6 +1905,9 @@ $.ccio.globalWebsocket=function(d,user){
|
|||
},user.details.audio_delay * 1000)
|
||||
}
|
||||
}
|
||||
if(user.details.event_mon_pop === '1' && (!$.ccio.mon[d.ke+d.id+user.auth_token].popOut || $.ccio.mon[d.ke+d.id+user.auth_token].popOut.closed === true)){
|
||||
d.e.find('[monitor="pop"]').click()
|
||||
}
|
||||
}
|
||||
break;
|
||||
case'init_success':
|
||||
|
@ -1972,7 +2049,14 @@ $.ccio.globalWebsocket=function(d,user){
|
|||
clearInterval($.ccio.mon[d.ke+d.id+user.auth_token].signal);delete($.ccio.mon[d.ke+d.id+user.auth_token].signal);
|
||||
$.ccio.mon[d.ke+d.id+user.auth_token].watch=0;
|
||||
if($.ccio.mon[d.ke+d.id+user.auth_token].hls){$.ccio.mon[d.ke+d.id+user.auth_token].hls.destroy()}
|
||||
if($.ccio.mon[d.ke+d.id+user.auth_token].Poseidon){$.ccio.mon[d.ke+d.id+user.auth_token].Poseidon.destroy()}
|
||||
if($.ccio.mon[d.ke+d.id+user.auth_token].Base64){$.ccio.mon[d.ke+d.id+user.auth_token].Base64.disconnect()}
|
||||
if($.ccio.mon[d.ke+d.id+user.auth_token].h265Socket){$.ccio.mon[d.ke+d.id+user.auth_token].h265Socket.disconnect()}
|
||||
if($.ccio.mon[d.ke+d.id+user.auth_token].h265Player){$.ccio.mon[d.ke+d.id+user.auth_token].h265Player.stop()}
|
||||
if($.ccio.mon[d.ke+d.id+user.auth_token].dash){$.ccio.mon[d.ke+d.id+user.auth_token].dash.reset()}
|
||||
if($.ccio.mon[d.ke+d.id+user.auth_token].h265HttpStream && $.ccio.mon[d.ke+d.id+user.auth_token].abort){
|
||||
$.ccio.mon[d.ke+d.id+user.auth_token].h265HttpStream.abort()
|
||||
}
|
||||
$.grid.data().removeWidget($('#monitor_live_'+d.id+user.auth_token))
|
||||
}
|
||||
break;
|
||||
|
@ -2188,6 +2272,47 @@ $.ccio.globalWebsocket=function(d,user){
|
|||
case'mjpeg':
|
||||
$('#monitor_live_'+d.id+user.auth_token+' .stream-element').attr('src',$.ccio.init('location',user)+user.auth_token+'/mjpeg/'+d.ke+'/'+d.id+'/?full=true')
|
||||
break;
|
||||
case'h265':
|
||||
var player = $.ccio.mon[d.ke+d.id+user.auth_token].h265Player
|
||||
var video = $('#monitor_live_'+d.id+user.auth_token+' .stream-element')[0]
|
||||
if (player) {
|
||||
player.stop()
|
||||
}
|
||||
$.ccio.mon[d.ke+d.id+user.auth_token].h265Player = new libde265.RawPlayer(video)
|
||||
var player = $.ccio.mon[d.ke+d.id+user.auth_token].h265Player
|
||||
player.set_status_callback(function(msg, fps) {
|
||||
})
|
||||
player.launch()
|
||||
if($.ccio.mon[d.ke+d.id+user.auth_token].h265Socket && $.ccio.mon[d.ke+d.id+user.auth_token].h265Socket.connected){
|
||||
$.ccio.mon[d.ke+d.id+user.auth_token].h265Socket.disconnect()
|
||||
}
|
||||
if($.ccio.mon[d.ke+d.id+user.auth_token].h265HttpStream && $.ccio.mon[d.ke+d.id+user.auth_token].abort){
|
||||
$.ccio.mon[d.ke+d.id+user.auth_token].h265HttpStream.abort()
|
||||
}
|
||||
if(d.d.stream_flv_type==='ws'){
|
||||
$.ccio.mon[d.ke+d.id+user.auth_token].h265Socket = io(url,{transports: ['websocket'], forceNew: false})
|
||||
var ws = $.ccio.mon[d.ke+d.id+user.auth_token].h265Socket
|
||||
ws.on('diconnect',function(){
|
||||
console.log('h265Socket Stream Disconnected')
|
||||
})
|
||||
ws.on('connect',function(){
|
||||
ws.emit('h265',{
|
||||
url: url,
|
||||
auth: user.auth_token,
|
||||
uid: user.uid,
|
||||
ke: d.ke,
|
||||
id: d.id,
|
||||
// channel: channel
|
||||
})
|
||||
ws.on('data',function(imageData){
|
||||
player._handle_onChunk(imageData)
|
||||
})
|
||||
})
|
||||
}else{
|
||||
var url = $.ccio.init('location',user)+user.auth_token+'/h265/'+d.ke+'/'+d.id+'/s.hevc';
|
||||
$.ccio.mon[d.ke+d.id+user.auth_token].h265HttpStream = player.createHttpStream(url)
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
d.signal=parseFloat(d.d.signal_check);
|
||||
|
@ -2348,7 +2473,7 @@ $.ccio.globalWebsocket=function(d,user){
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var eventsToCheck = Object.assign({},events)
|
||||
$.each(data,function(m,b){
|
||||
startTimeFormatted = $.ccio.timeObject(b.time).format('YYYY-MM-DD HH:mm:ss');
|
||||
|
@ -2836,7 +2961,7 @@ $.zO.initCanvas=function(){
|
|||
$.zO.e.find('.cord_name').text(e.val)
|
||||
$.zO.f.find('[name="sensitivity"]').val(e.cord.sensitivity)
|
||||
$.zO.e.find('.canvas_holder canvas').remove();
|
||||
|
||||
|
||||
$.zO.initLiveStream()
|
||||
e.e=$.zO.ca.val(e.ar.join(','))
|
||||
e.e.canvasAreaDraw({
|
||||
|
@ -2916,7 +3041,7 @@ $.zO.e.on('changed','#regions_canvas',function(e){
|
|||
})
|
||||
$.zO.f.submit(function(e){
|
||||
e.preventDefault();e.e=$(this),e.s=e.e.serializeObject();
|
||||
|
||||
|
||||
return false;
|
||||
});
|
||||
$('#regions_points')
|
||||
|
@ -2950,7 +3075,7 @@ $.pB.f.submit(function(e){
|
|||
$.pB.o.empty();
|
||||
$.pB.e.find('.stop').show();
|
||||
$.pB.e.find('[type="submit"]').hide();
|
||||
|
||||
|
||||
e.preventDefault();e.e=$(this),e.s=e.e.serializeObject();
|
||||
e.s.url=e.s.url.trim();
|
||||
var flags = '';
|
||||
|
@ -3111,7 +3236,7 @@ $.multimon.e.find('.import_config').click(function(){
|
|||
newMon.details.auto_host = Monitor.Device
|
||||
break;
|
||||
case'remote':
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
newMon.details = JSON.stringify(newMon.details)
|
||||
|
@ -3254,9 +3379,9 @@ $.each(<%-JSON.stringify(define["Monitor Settings"].blocks)%>,function(n,v){
|
|||
}
|
||||
if(b.name.indexOf('detail=')>-1){
|
||||
b.name=b.name.replace('detail=','')
|
||||
v.element=$('[detail="'+b.name+'"]')
|
||||
v.element=$.aM.e.find('[detail="'+b.name+'"]')
|
||||
}else{
|
||||
v.element=$('[name="'+b.name+'"]')
|
||||
v.element=$.aM.e.find('[name="'+b.name+'"]')
|
||||
}
|
||||
v.parent=v.element.parents('.form-group').find('label div:first-child span')
|
||||
v.parent.find('small').remove()
|
||||
|
@ -3692,12 +3817,12 @@ $.aM.f.submit(function(ee){
|
|||
var copyMonitors = $.aM.monitorsForCopy.val();
|
||||
var chosenSections = [];
|
||||
var chosenMonitors = {};
|
||||
|
||||
|
||||
if(!copyMonitors||copyMonitors.length===0){
|
||||
$.ccio.init('note',{title:'<%-cleanLang(lang['No Monitors Selected'])%>',text:'<%-cleanLang(lang.monSavedButNotCopied)%>'})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
$.aM.e.find('[copy]').each(function(n,v){
|
||||
var el = $(v)
|
||||
if(el.val() === '1'){
|
||||
|
@ -3767,7 +3892,7 @@ $.aM.f.submit(function(ee){
|
|||
})
|
||||
console.log(chosenMonitors)
|
||||
}
|
||||
|
||||
|
||||
$.aM.e.modal('hide')
|
||||
return false;
|
||||
});
|
||||
|
@ -4078,16 +4203,152 @@ $.fI.f.submit(function(e){
|
|||
e.er=[];
|
||||
$.each(e.s,function(n,v){e.s[n]=v.trim()})
|
||||
e.s.where=[];
|
||||
$('.where-row').each(function(n,v){
|
||||
e.e.find('.where-row').each(function(n,v){
|
||||
n={};
|
||||
$(v).find('[where]').each(function(m,b){
|
||||
b=$(b);
|
||||
n[b.attr('where')]=b.val();
|
||||
n[b.attr('where')]=b.val().trim();
|
||||
})
|
||||
e.s.where.push(n)
|
||||
})
|
||||
$.ccio.cx({f:'settings',ff:'filters',fff:'save',form:e.s})
|
||||
});
|
||||
//detector filters window
|
||||
$.detectorFilters={e:$('#detector_filter')};
|
||||
$.detectorFilters.f=$.detectorFilters.e.find('form');
|
||||
$.detectorFilters.md=$.detectorFilters.f.find('[detail]');
|
||||
$.detectorFilters.getSelected = function(){
|
||||
return $('#detector_filters').val()
|
||||
}
|
||||
$.detectorFilters.drawOptions = function(){
|
||||
var dFilters = $.detectorFilters.getCurrent()
|
||||
$('#detector_filters optgroup').empty()
|
||||
$.each(dFilters,function(n,dFilter){
|
||||
$.ccio.tm('option',{auth_token:$user.auth_token,id:dFilter.id,name:dFilter.filter_name},'#detector_filters optgroup')
|
||||
})
|
||||
}
|
||||
$.detectorFilters.getCurrent = function(){
|
||||
try{
|
||||
return JSON.parse($.aM.e.find('[detail="detector_filters"]').val())
|
||||
}catch(err){
|
||||
return {}
|
||||
}
|
||||
}
|
||||
$.detectorFilters.save = function(){
|
||||
var currentVals = $.detectorFilters.getCurrent()
|
||||
currentVals[$.detectorFilters.lastSave.id] = $.detectorFilters.lastSave
|
||||
$.aM.e.find('[detail="detector_filters"]').val(JSON.stringify(currentVals)).change()
|
||||
}
|
||||
$.ccio.tm('detector-filters-where');
|
||||
$.detectorFilters.e.on('change','[where="p1"]',function(e){
|
||||
var el = $(this)
|
||||
var p1v = el.val()
|
||||
var parent = el.parents('.row')
|
||||
var p3 = parent.find('[where="p3"]')
|
||||
var options = []
|
||||
switch(p1v){
|
||||
case'reason':
|
||||
options = [
|
||||
'licensePlate',
|
||||
'object',
|
||||
'motion',
|
||||
]
|
||||
break;
|
||||
case'plug':
|
||||
options = [
|
||||
'PythonYolo',
|
||||
'OpenCV',
|
||||
'built-in',
|
||||
]
|
||||
break;
|
||||
case'tag':
|
||||
options = [
|
||||
'car',
|
||||
'tree',
|
||||
'pottedplant',
|
||||
]
|
||||
break;
|
||||
}
|
||||
var msg = 'Value'
|
||||
if(options.length > 0){
|
||||
msg = 'Example : '+options.join(', ')
|
||||
}
|
||||
p3.attr('placeholder',msg)
|
||||
})
|
||||
$.detectorFilters.e.on('shown.bs.modal',function(e){
|
||||
$.detectorFilters.drawOptions()
|
||||
})
|
||||
$.detectorFilters.e.on('click','.where .add',function(e){
|
||||
$.ccio.tm('detector-filters-where');
|
||||
})
|
||||
$.detectorFilters.e.on('click','.where .remove',function(e){
|
||||
e.e=$('#detector_filters_where .row');
|
||||
if(e.e.length>1){
|
||||
e.e.last().remove();
|
||||
$('#detector_filters_where .row:last [where="p4"]').prop('disabled',true)
|
||||
}
|
||||
})
|
||||
$.detectorFilters.f.find('.delete').click(function(e){
|
||||
var currentVals = $.detectorFilters.getCurrent()
|
||||
var newObject = {}
|
||||
var deleteId = $.detectorFilters.getSelected()
|
||||
$.each(currentVals,function(id,obj){
|
||||
if(id === deleteId)return false;
|
||||
newObject[id] = obj
|
||||
})
|
||||
$.aM.e.find('[detail="detector_filters"]').val(JSON.stringify(newObject)).change()
|
||||
$.detectorFilters.drawOptions()
|
||||
})
|
||||
$('#detector_filters').change(function(){
|
||||
e = {}
|
||||
e.e=$(this),e.id=e.e.val();
|
||||
$('#detector_filters_where').empty()
|
||||
if(e.id&&e.id!==''){
|
||||
var currentFilter = $.detectorFilters.getCurrent()[e.id]
|
||||
e.name=currentFilter.name;
|
||||
$.each(currentFilter.where,function(n,v){
|
||||
$.ccio.tm('detector-filters-where',v)
|
||||
});
|
||||
$.each(currentFilter.actions,function(action,val){
|
||||
$.detectorFilters.e.find('[actions="'+action+'"]').val(val)
|
||||
});
|
||||
$.each(currentFilter,function(n,v){
|
||||
if(n==='where'){return}
|
||||
$.detectorFilters.f.find('[name="'+n+'"]').val(v);
|
||||
});
|
||||
}else{
|
||||
e.name='<%-cleanLang(lang['Add New'])%>';
|
||||
$.detectorFilters.f.find('[name="id"]').val($.ccio.gid(5));
|
||||
$.ccio.tm('detector-filters-where');
|
||||
}
|
||||
$.detectorFilters.e.find('.filter_name').text(e.name)
|
||||
}).change()
|
||||
$.detectorFilters.f.submit(function(ee){
|
||||
ee.preventDefault()
|
||||
e = {}
|
||||
e.e=$(this),e.s=e.e.serializeObject();
|
||||
e.er=[];
|
||||
$.each(e.s,function(n,v){e.s[n]=v.trim()})
|
||||
//create conditions object (where)
|
||||
e.s.where=[];
|
||||
e.e.find('.where-row').each(function(n,v){
|
||||
n={};
|
||||
$(v).find('[where]').each(function(m,b){
|
||||
b=$(b);
|
||||
n[b.attr('where')]=b.val().trim();
|
||||
})
|
||||
e.s.where.push(n)
|
||||
})
|
||||
// create actions object (do)
|
||||
e.s.actions={};
|
||||
e.e.find('.actions-row').each(function(n,v){
|
||||
b=$(v).find('[actions]');
|
||||
e.s.actions[b.attr('actions')] = b.val()
|
||||
})
|
||||
$.detectorFilters.lastSave = e.s
|
||||
$.detectorFilters.save()
|
||||
$.detectorFilters.e.modal('hide')
|
||||
});
|
||||
//settings window
|
||||
$.sM={e:$('#settings')};
|
||||
$.sM.f=$.sM.e.find('form');
|
||||
|
@ -4447,7 +4708,7 @@ $.timelapse.play = function(x){
|
|||
clearInterval($.timelapse.interval)
|
||||
videoNow.currentTime = videoNow.duration
|
||||
}else{
|
||||
videoNow.currentTime += .5
|
||||
videoNow.currentTime += .5
|
||||
}
|
||||
},500 / $.timelapse.playRate)
|
||||
}
|
||||
|
@ -4625,7 +4886,7 @@ $.timelapse.e.on('click','[timelapse]',function(){
|
|||
var vidTime = e.videoNow.duration * percentage;
|
||||
e.videoNow.currentTime = vidTime;
|
||||
});
|
||||
|
||||
|
||||
$.ccio.log('$.timelapse',e.video)
|
||||
$.timelapse.line.find('.timelapse_video').removeClass('active')
|
||||
e.videoCurrentNow=$.timelapse.display.find('.videoNow')
|
||||
|
@ -4746,7 +5007,7 @@ $.pwrvid.e.on('click','[preview]',function(e){
|
|||
})
|
||||
if(e.status==1){
|
||||
$.get($.ccio.init('videoHrefToRead',e.href),function(d){
|
||||
|
||||
|
||||
})
|
||||
}
|
||||
var labels=[]
|
||||
|
@ -4877,7 +5138,7 @@ $.pwrvid.drawTimeline=function(getData){
|
|||
e.eventLimit = $('#pvideo_event_limit').val();
|
||||
if(e.eventLimit===''||isNaN(e.eventLimit)){e.eventLimit=500}
|
||||
if(e.videoLimit===''||isNaN(e.videoLimit)){e.videoLimit=0}
|
||||
|
||||
|
||||
var getTheData = function(){
|
||||
e.live_header.text($.ccio.mon[$user.ke+mid+$user.auth_token].name)
|
||||
e.live.attr('src',$.ccio.init('location',$user)+$user.auth_token+'/embed/'+$user.ke+'/'+mid+'/fullscreen|jquery|relative|gui')
|
||||
|
@ -4996,7 +5257,18 @@ $.grid.e
|
|||
.on('gsresizestop', $.grid.saveElementPositions);
|
||||
//open all monitors
|
||||
$('[class_toggle="list-blocks"][data-target="#left_menu"]').dblclick(function(){
|
||||
$('#monitors_list [monitor="watch"]').click()
|
||||
$('#monitors_list .monitor_block').each(function(n,v){
|
||||
var el = $(v)
|
||||
var ke = el.attr('ke')
|
||||
var mid = el.attr('mid')
|
||||
var auth = el.attr('auth')
|
||||
var monItem = $('.monitor_item[ke='+ke+'][mid='+mid+'][auth='+auth+']')
|
||||
if(monItem.length > 0){
|
||||
monItem.find('[monitor="watch_on"]').click()
|
||||
}else{
|
||||
el.find('[monitor="watch"]').click()
|
||||
}
|
||||
})
|
||||
})
|
||||
//search monitors list
|
||||
$('#monitors_list_search').keyup(function(){
|
||||
|
@ -5123,7 +5395,7 @@ $('body')
|
|||
$.ccio.op(e.localStorage,e.value)
|
||||
})
|
||||
.on('click','[system]',function(e){
|
||||
var e={};
|
||||
var e={};
|
||||
e.e=$(this),
|
||||
e.a=e.e.attr('system');//the function
|
||||
switch(e.a){
|
||||
|
@ -5207,7 +5479,7 @@ $('body')
|
|||
})
|
||||
//monitor functions
|
||||
.on('click','[monitor]',function(){
|
||||
var e={};
|
||||
var e={};
|
||||
e.e=$(this),
|
||||
e.a=e.e.attr('monitor'),//the function
|
||||
e.p=e.e.parents('[mid]'),//the parent element for monitor item
|
||||
|
@ -5310,7 +5582,7 @@ $('body')
|
|||
e.d.detector_scale_x=e.width.val();
|
||||
e.d.detector_scale_y=e.height.val();
|
||||
}
|
||||
|
||||
|
||||
$.zO.e.modal('show');
|
||||
$.zO.o().attr('width',e.d.detector_scale_x).attr('height',e.d.detector_scale_y);
|
||||
$.zO.c.css({width:e.d.detector_scale_x,height:e.d.detector_scale_y});
|
||||
|
@ -5325,6 +5597,9 @@ $('body')
|
|||
$.zO.regionViewerDetails=e.d;
|
||||
$.zO.initRegionList()
|
||||
break;
|
||||
case'detector_filters':
|
||||
$.detectorFilters.e.modal('show');
|
||||
break;
|
||||
case'snapshot':
|
||||
$.ccio.snapshot(e,function(url){
|
||||
$('#temp').html('<a href="'+url+'" download="'+$.ccio.init('tf')+'_'+e.ke+'_'+e.mid+'.jpg">a</a>').find('a')[0].click();
|
||||
|
@ -5673,4 +5948,4 @@ function onFullScreenChange() {
|
|||
$('canvas.stream-element').resize();
|
||||
},2000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,249 @@
|
|||
<!--Detector Filter Window-->
|
||||
<div class="modal full fade dark" id="detector_filter" tabindex="-1" role="dialog" aria-labelledby="detector_filterLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 class="modal-title" id="detector_filterLabel"><i class="fa fa-grav"></i> <%-lang['Detector Filters']%></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group-group green">
|
||||
<h4><%- lang['Saved Filters'] %> : <small class="filter_name"></small></h4>
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<div><span><%-lang.Filters%></span></div>
|
||||
<div><select id="detector_filters" class="form-control">
|
||||
<option value=""><%-lang['Add New']%></option>
|
||||
<optgroup label="Saved"></optgroup>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%- lang['Filter Name'] %></span></div>
|
||||
<div><input class="form-control" name="id" type="hidden">
|
||||
<input class="form-control" name="filter_name"></div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group-group blue where">
|
||||
<h4>Conditions
|
||||
<div class="pull-right">
|
||||
<a class="btn btn-success btn-xs add"> <i class="fa fa-plus"></i> </a>
|
||||
<a class="btn btn-danger btn-xs remove"> <i class="fa fa-trash-o"></i> </a>
|
||||
</div>
|
||||
</h4>
|
||||
<div id="detector_filters_where"></div>
|
||||
</div>
|
||||
<div class="form-group-group red actions">
|
||||
<h4><%- lang['Action for Selected'] %></h4>
|
||||
<%
|
||||
var possibleActions = [
|
||||
{
|
||||
"name": "halt",
|
||||
"field": "Drop Event",
|
||||
"description": "Make the event do nothing, as if it never happened.",
|
||||
"default": "No",
|
||||
"example": "",
|
||||
"possible": [
|
||||
{
|
||||
"name": "No",
|
||||
"value": "0",
|
||||
"info": "Allow other functions to continue.",
|
||||
selected:true
|
||||
},
|
||||
{
|
||||
"name": "Yes",
|
||||
"value": "1",
|
||||
"info": "Use Traditional Recording, Hotswap, or Delete Motionless with their currently set options in the Global Detection Settings section."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "save",
|
||||
"field": "Save Events to SQL",
|
||||
"description": "Save Motion Events in SQL. This will allow display of motion over video during the time motion events occured in the Power Viewer.",
|
||||
"default": "Yes",
|
||||
"example": "",
|
||||
"possible": [
|
||||
{
|
||||
"name": "Default",
|
||||
"value": "",
|
||||
"info": "Use values set in Global Detector Settings.",
|
||||
"selected": true
|
||||
},
|
||||
{
|
||||
"name": "No",
|
||||
"value": "0",
|
||||
"info": "Finish the current 10 minute order."
|
||||
},
|
||||
{
|
||||
"name": "Yes",
|
||||
"value": "1",
|
||||
"info": "Reset the timer"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mail",
|
||||
"field": "Email on Trigger",
|
||||
"description": "Recieve an email of an image during a motion event to the master account for the camera group. You must setup SMTP details in conf.json.",
|
||||
"default": "No",
|
||||
"example": "1",
|
||||
"possible": [
|
||||
{
|
||||
"name": "Default",
|
||||
"value": "",
|
||||
"info": "Use values set in Global Detector Settings.",
|
||||
"selected": true
|
||||
},
|
||||
{
|
||||
"name": "No",
|
||||
"value": "0",
|
||||
"info": "No Email."
|
||||
},
|
||||
{
|
||||
"name": "Yes",
|
||||
"value": "1",
|
||||
"info": "Send Email."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "webhook",
|
||||
"field": "Webhook on Trigger",
|
||||
"description": "Send a GET request during an event to the URL specified. Webhook location can be specified in the Global Detector Settings for the Monitor.",
|
||||
"default": "No",
|
||||
"example": "1",
|
||||
"possible": [
|
||||
{
|
||||
"name": "Default",
|
||||
"value": "",
|
||||
"info": "Use values set in Global Detector Settings.",
|
||||
"selected": true
|
||||
},
|
||||
{
|
||||
"name": "No",
|
||||
"value": "0",
|
||||
"info": "No Webhook."
|
||||
},
|
||||
{
|
||||
"name": "Yes",
|
||||
"value": "1",
|
||||
"info": "Send Webhook."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "discord",
|
||||
"field": "Discord Alert on Trigger",
|
||||
"description": "Recieve a Discord Notification with an image or video during an event to the Discord channel specified. Discord Bot and Channel settings can be changed in your Account Settings.",
|
||||
"default": "No",
|
||||
"example": "1",
|
||||
"possible": [
|
||||
{
|
||||
"name": "Default",
|
||||
"value": "",
|
||||
"info": "Use values set in Global Detector Settings.",
|
||||
"selected": true
|
||||
},
|
||||
{
|
||||
"name": "No",
|
||||
"value": "0",
|
||||
"info": "No Alert."
|
||||
},
|
||||
{
|
||||
"name": "Yes",
|
||||
"value": "1",
|
||||
"info": "Get a Message to Discord."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "command",
|
||||
"field": "Detector Command",
|
||||
"description": "You may use this to trigger a script on command.",
|
||||
"default": "No",
|
||||
"example": "",
|
||||
"possible": [
|
||||
{
|
||||
"name": "Default",
|
||||
"value": "",
|
||||
"info": "Use values set in Global Detector Settings.",
|
||||
"selected": true
|
||||
},
|
||||
{
|
||||
"name": "No",
|
||||
"value": "0",
|
||||
"info": "No script will run."
|
||||
},
|
||||
{
|
||||
"name": "Yes",
|
||||
"value": "1",
|
||||
"info": "Trigger the script that is set in the <b>Command</b> option. <b>Command</b> is only visible when selecting this option."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "record",
|
||||
"field": "Use Record Method",
|
||||
"description": "Use Traditional Recording, Hotswap, or Delete Motionless with their currently set options in the Global Detection Settings section.",
|
||||
"default": "No",
|
||||
"example": "",
|
||||
"possible": [
|
||||
{
|
||||
"name": "Default",
|
||||
"value": "",
|
||||
"info": "Use values set in Global Detector Settings.",
|
||||
"selected": true
|
||||
},
|
||||
{
|
||||
"name": "No",
|
||||
"value": "0",
|
||||
"info": "No Traditional Recording, Hotswap, or Delete Motionless."
|
||||
},
|
||||
{
|
||||
"name": "Yes",
|
||||
"value": "1",
|
||||
"info": "Use Traditional Recording, Hotswap, or Delete Motionless with their currently set options in the Global Detection Settings section."
|
||||
}
|
||||
]
|
||||
},
|
||||
].forEach(function(item){ %>
|
||||
<%
|
||||
var name = 'actions='+item.name
|
||||
if(item.name.indexOf('=') > -1){
|
||||
name = item.name
|
||||
}
|
||||
%>
|
||||
<div class="form-group actions-row">
|
||||
<label>
|
||||
<div>
|
||||
<span>
|
||||
<%-item.field%>
|
||||
<small><%-item.description%></small>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<select <%- name %> class="form-control">
|
||||
<% item.possible.forEach(function(option){ %>
|
||||
<option value="<%-option.value%>"><%-option.name%></option>
|
||||
<% }) %>
|
||||
</select>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<% }) %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="pull-left">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal"><%- lang['Close'] %></button>
|
||||
<button type="button" class="btn btn-danger delete"><%- lang['Delete'] %></button>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success"><%- lang['Save'] %></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
|
@ -26,7 +26,7 @@
|
|||
<%-lang.IdentityText2%>
|
||||
</blockquote>
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<label>
|
||||
<div><span><%-lang.Mode%></span></div>
|
||||
<div><select class="form-control" name="mode" selector="h_m">
|
||||
<option value="stop"><%-lang.Disabled%></option>
|
||||
|
@ -124,26 +124,26 @@
|
|||
<div class="form-group h_p_input h_p_rtsp">
|
||||
<label><div><span><%-lang['RTSP Transport']%></span></div>
|
||||
<div><select class="form-control" detail="rtsp_transport">
|
||||
<option value="no" selected><%-lang['Auto']%></option>
|
||||
<option value="tcp"><%-lang['TCP']%></option>
|
||||
<option value="udp"><%-lang['UDP']%></option>
|
||||
<option value="http"><%-lang['HTTP']%></option>
|
||||
<option value="no" selected><%-lang['Auto']%></option>
|
||||
<option value="tcp"><%-lang['TCP']%></option>
|
||||
<option value="udp"><%-lang['UDP']%></option>
|
||||
<option value="http"><%-lang['HTTP']%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Username']%></span></div>
|
||||
<div><input class="form-control" detail="muser" placeholder=""></div>
|
||||
<div><input class="form-control" detail="muser"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Password']%></span></div>
|
||||
<div><input class="form-control" type="password" detail="mpass" placeholder=""></div>
|
||||
<div><input class="form-control" type="password" detail="mpass"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Host']%></span></div>
|
||||
<div><input class="form-control" name="host" placeholder=""></div>
|
||||
<div><input class="form-control" name="host"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
@ -180,6 +180,19 @@
|
|||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['ONVIF']%></span></div>
|
||||
<div><select class="form-control" detail="is_onvif" selector="h_onvif">
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group h_onvif_input h_onvif_1">
|
||||
<label><div><span><%-lang['ONVIF Port']%></span></div>
|
||||
<div><input class="form-control" detail="onvif_port" placeholder="8000" type="number"></div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END of Connection -->
|
||||
<!-- START of Input -->
|
||||
|
@ -195,7 +208,7 @@
|
|||
<div><input class="form-control" detail="probesize"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group h_t_input h_t_mp4">
|
||||
<div class="form-group h_t_input h_t_mp4 h_t_local">
|
||||
<label><div><span><%-lang["Loop Stream"]%></span></div>
|
||||
<div><select class="form-control" detail="stream_loop">
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
|
@ -246,6 +259,11 @@
|
|||
<option value="hevc_qsv"><%-lang['hevc_qsv']%></option>
|
||||
<option value="mpeg2_qsv"><%-lang['mpeg2_qsv']%></option>
|
||||
</optgroup>
|
||||
<optgroup label="Raspberry Pi">
|
||||
<option value="h264_mmal"><%-lang['h264_mmal']%></option>
|
||||
<option value="mpeg2_mmal"><%-lang['mpeg2_mmal']%></option>
|
||||
<option value="mpeg4_mmal"><%-lang['mpeg4_mmal']%></option>
|
||||
</optgroup>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -276,9 +294,9 @@
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Stream Type']%></span></div>
|
||||
<div><select class="form-control" detail="stream_type" selector="h_st" triggerChange="#add_monitor [detail=stream_vcodec]" triggerChangeIgnore="b64,mjpeg">
|
||||
<div><select class="form-control" detail="stream_type" selector="h_st" triggerChange="#add_monitor [detail=stream_vcodec]" triggerChangeIgnore="b64,mjpeg,jpeg,gif">
|
||||
<option value="mp4"><%-lang['Poseidon']%></option>
|
||||
<!-- <option value="pam">PAM</option>-->
|
||||
<option value="h265"><%-lang['HEVC (H.265)']%></option>
|
||||
<option value="b64" selected><%-lang['Base64 over Websocket']%></option>
|
||||
<option value="jpeg"><%-lang['JPEG (Auto Enables JPEG API)']%></option>
|
||||
<option value="mjpeg"><%-lang['MJPEG']%></option>
|
||||
|
@ -287,7 +305,7 @@
|
|||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group h_st_input h_st_flv h_st_mp4">
|
||||
<div class="form-group h_st_input h_st_flv h_st_mp4 h_st_h265">
|
||||
<label><div><span><%-lang['Connection Type']%></span></div>
|
||||
<div><select class="form-control" detail="stream_flv_type" selector="h_st_lat">
|
||||
<option value="http" selected><%-lang['HTTP']%></option>
|
||||
|
@ -305,7 +323,7 @@
|
|||
<div><input class="form-control" detail="stream_mjpeg_clients" placeholder="20"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="h_st_input h_st_hls h_st_flv h_st_mp4">
|
||||
<div class="h_st_input h_st_hls h_st_flv h_st_mp4 h_st_h265">
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['HLS Video Encoder']%></span></div>
|
||||
<div><select class="form-control" detail="stream_vcodec" selector="h_hls_v">
|
||||
|
@ -321,6 +339,7 @@
|
|||
<option value="h264_qsv"><%-lang['h264_qsv']%></option>
|
||||
<option value="hevc_qsv"><%-lang['hevc_qsv']%></option>
|
||||
<option value="mpeg2_qsv"><%-lang['mpeg2_qsv']%></option>
|
||||
<option value="h264_omx"><%-lang['h264_omx']%></option>
|
||||
</optgroup>
|
||||
</select></div>
|
||||
</label>
|
||||
|
@ -338,7 +357,7 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h_st_input h_st_mjpeg h_st_b64 h_st_hls h_st_flv h_st_mp4 h_hls_v_input h_hls_v_libx264 h_hls_v_libx265 h_hls_v_h264_nvenc h_hls_v_hevc_nvenc h_hls_v_no">
|
||||
<div class="h_st_input h_st_mjpeg h_st_b64 h_st_hls h_st_gif h_st_flv h_st_mp4 h_st_h265 h_hls_v_input h_hls_v_libx264 h_hls_v_libx265 h_hls_v_h264_nvenc h_hls_v_hevc_nvenc h_hls_v_no">
|
||||
<div class="h_st_input h_st_hls">
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['HLS Segment Length']%></span></div>
|
||||
|
@ -351,7 +370,7 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group h_st_input h_st_hls h_st_flv h_st_mp4">
|
||||
<div class="form-group h_st_input h_st_hls h_st_flv h_st_mp4 h_st_h265">
|
||||
<label><div><span><%-lang['HLS Preset']%></span></div>
|
||||
<div><input class="form-control" detail="preset_stream" placeholder="ultrafast"></div>
|
||||
</label>
|
||||
|
@ -434,8 +453,8 @@
|
|||
<div class="form-group">
|
||||
<label><div><span><%-lang['Enabled']%></span></div>
|
||||
<div><select class="form-control" detail="stream_timestamp" selector="h_tm">
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -532,12 +551,12 @@
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Image Width']%></span></div>
|
||||
<div><input class="form-control" detail="snap_scale_x" placeholder=""></div>
|
||||
<div><input class="form-control" detail="snap_scale_x"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Image Height']%></span></div>
|
||||
<div><input class="form-control" detail="snap_scale_y" placeholder=""></div>
|
||||
<div><input class="form-control" detail="snap_scale_y"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
@ -565,8 +584,8 @@
|
|||
<div class="form-group">
|
||||
<label><div><span><%-lang['Record File Type']%></span></div>
|
||||
<div><select class="form-control" name="ext" selector="h_f">
|
||||
<option value="webm"><%-lang['WebM (libvpx)']%></option>
|
||||
<option value="mp4"><%-lang['MP4 (copy, libx264, libx265)']%></option>
|
||||
<option value="webm"><%-lang['WebM (libvpx)']%></option>
|
||||
<option value="mp4"><%-lang['MP4 (copy, libx264, libx265)']%></option>
|
||||
<!-- <option value="mkv">MKV</option> -->
|
||||
</select></div>
|
||||
</label>
|
||||
|
@ -593,6 +612,7 @@
|
|||
<option value="h264_qsv"><%-lang['h264_qsv']%></option>
|
||||
<option value="hevc_qsv"><%-lang['hevc_qsv']%></option>
|
||||
<option value="mpeg2_qsv"><%-lang['mpeg2_qsv']%></option>
|
||||
<option value="h264_omx"><%-lang['h264_omx']%></option>
|
||||
</optgroup>
|
||||
<optgroup label="WebM <%-lang['Hardware Accelerated']%>">
|
||||
<option value="vp8_cuvid"><%-lang['vp8_cuvid']%></option>
|
||||
|
@ -605,12 +625,12 @@
|
|||
<div class="h_vc_input h_vc_libvpx h_vc_libvpx-vp9 h_vc_libx264 h_vc_libx265 h_vc_hevc_nvenc h_vc_h264_nvenc h_vc_h264_vaapi h_vc_hevc_vaapi h_vc_h264_qsv h_vc_hevc_qsv h_vc_mpeg2_qsv h_vc_default h_vc_none">
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Quality']%></span></div>
|
||||
<div><input class="form-control" detail="crf" placeholder=""></div>
|
||||
<div><input class="form-control" detail="crf"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Preset']%></span></div>
|
||||
<div><input class="form-control" detail="preset_record" placeholder=""></div>
|
||||
<div><input class="form-control" detail="preset_record"></div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -692,8 +712,8 @@
|
|||
<div class="form-group">
|
||||
<label><div><span><%-lang['Enabled']%></span></div>
|
||||
<div><select class="form-control" detail="timestamp" selector="h_tm">
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -772,7 +792,7 @@
|
|||
<h4><%-lang['Custom']%></h4>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Input Flags']%></span></div>
|
||||
<div><input class="form-control" detail="cust_input" placeholder=""></div>
|
||||
<div><input class="form-control" detail="cust_input"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group h_sn_input h_sn_1" style="display:none">
|
||||
|
@ -807,7 +827,7 @@
|
|||
</div>
|
||||
<div class="form-group h_m_input h_m_record">
|
||||
<label><div><span><%-lang['Recording Flags']%></span></div>
|
||||
<div><input class="form-control" detail="cust_record" placeholder=""></div>
|
||||
<div><input class="form-control" detail="cust_record"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
@ -992,13 +1012,30 @@
|
|||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Detector Filters']%></span></div>
|
||||
<div><select class="form-control" detail="use_detector_filters" selector="h_det_fil">
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group h_det_fil_input h_det_fil_1">
|
||||
<label><div><span><%-lang['Filter for Objects only']%></span></div>
|
||||
<div><select class="form-control" detail="use_detector_filters_object">
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group h_det_discord_input h_det_discord_1">
|
||||
<label><div><span><%-lang['Allow Next Discord Alert']%></span></div>
|
||||
<div><input class="form-control" detail="detector_discordbot_timeout" placeholder="10"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="hidden">
|
||||
<div><input detail="cords" placeholder=""></div>
|
||||
<input detail="cords">
|
||||
<input detail="detector_filters">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<a class="btn btn-danger btn-block" monitor="region"><i class="fa fa-grav"></i> <%-lang['Region Editor']%></a>
|
||||
|
@ -1077,7 +1114,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group-group orange shinobi-detector-opencv shinobi-detector-openalpr shinobi-detector_plug" section id="monSectionDetectorObject">
|
||||
<div class="form-group-group orange shinobi-detector-opencv shinobi-detector-openalpr shinobi-detector-pythonyolo shinobi-detector_plug" section id="monSectionDetectorObject">
|
||||
<h4><%-lang['Object Detection']%> <small><%-lang['Plugin']%> : <b class="shinobi-detector_name"></b> <b class="shinobi-detector-invert"><%-lang['Not Connected']%></b><b class="shinobi-detector" style="display:none"><%-lang['Connected']%></b></small></h4>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Detect Objects']%></span></div>
|
||||
|
@ -1089,12 +1126,31 @@
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Check for Motion First']%></span></div>
|
||||
<div><select class="form-control" detail="detector_use_motion">
|
||||
<div><select class="form-control" detail="detector_use_motion" selector="h_det_mot_fir">
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="h_det_pam_input h_det_pam_1">
|
||||
<div class="h_det_mot_fir_input h_det_mot_fir_1">
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Frame Rate']%></span></div>
|
||||
<div><input class="form-control" type="number" min="1" detail="detector_fps_object" placeholder="2"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Width']%></span></div>
|
||||
<div><input class="form-control" type="number" min="1" detail="detector_scale_x_object" placeholder="320"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Height']%></span></div>
|
||||
<div><input class="form-control" type="number" min="1" detail="detector_scale_y_object" placeholder="240"></div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class=" h_casc_input h_casc_1" style="display:none">
|
||||
<div class="form-group-group orange shinobi-detector-opencv shinobi-detector-openalpr shinobi-detector_plug" style="display:none" section id="monSectionLisencePlateDetector">
|
||||
<h4><%-lang['Lisence Plate Detector']%></h4>
|
||||
|
@ -1209,8 +1265,8 @@
|
|||
<div class="form-group">
|
||||
<label><div><span><%-lang['Controllable']%></span></div>
|
||||
<div><select class="form-control" detail="control" selector="h_c">
|
||||
<option value="0"><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
<option value="0"><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -1223,7 +1279,7 @@
|
|||
<div class="form-group">
|
||||
<label><div><span><%-lang['Call Method']%></span></div>
|
||||
<div><select class="form-control" detail="control_url_method" selector="h_control_call">
|
||||
<option value="GET">GET (<%-lang.Default%>)</option>
|
||||
<option value="GET">GET (<%-lang.Default%>)</option>
|
||||
<option value="PUT">PUT</option>
|
||||
<option value="POST">POST</option>
|
||||
<option value="ONVIF">ONVIF</option>
|
||||
|
@ -1233,7 +1289,7 @@
|
|||
<div class="form-group h_control_call_input h_control_call_GET h_control_call_PUT h_control_call_POST">
|
||||
<label><div><span><%-lang['Digest Authentication']%></span></div>
|
||||
<div><select class="form-control" detail="control_digest_auth">
|
||||
<option value="0"><%-lang.No%></option>
|
||||
<option value="0"><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
|
@ -1241,7 +1297,7 @@
|
|||
<div class="form-group">
|
||||
<label><div><span><%-lang['Stop Command']%></span></div>
|
||||
<div><select class="form-control" detail="control_stop" selector="h_cs">
|
||||
<option value="0"><%-lang.No%></option>
|
||||
<option value="0"><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
|
@ -1342,8 +1398,8 @@
|
|||
<div class="form-group">
|
||||
<label><div><span><%-lang['Copy to Settings']%></span></div>
|
||||
<div><select class="form-control" id="copy_settings" selector="h_copy_settings">
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -1351,32 +1407,32 @@
|
|||
<div class="form-group">
|
||||
<label><div><span><%-lang['Copy Connection Settings']%></span></div>
|
||||
<div><select class="form-control" copy="#monSectionConnection">
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Copy Input Settings']%></span></div>
|
||||
<div><select class="form-control" copy="#monSectionInput">
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Copy Stream Settings']%></span></div>
|
||||
<div><select class="form-control" copy="#monSectionStream,#monSectionStreamTimestamp,#monSectionStreamWatermark">
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Copy JPEG API Settings']%></span></div>
|
||||
<div><select class="form-control" copy="#monSectionJPEGAPI">
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -1384,8 +1440,8 @@
|
|||
<div class="form-group">
|
||||
<label><div><span><%-lang['Copy Stream Channel Settings']%></span></div>
|
||||
<div><select class="form-control" copy="stream_channel">
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -1393,40 +1449,40 @@
|
|||
<div class="form-group">
|
||||
<label><div><span><%-lang['Copy Recording Settings']%></span></div>
|
||||
<div><select class="form-control" copy="#monSectionRecording">
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Copy Detector Settings']%></span></div>
|
||||
<div><select class="form-control" copy="#monSectionDetector,#monSectionDetectorBuffer,#monSectionLisencePlateDetector,#monSectionNoMotionDetector">
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Copy Custom Settings']%></span></div>
|
||||
<div><select class="form-control" copy="#monSectionCustom">
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Copy Group Settings']%></span></div>
|
||||
<div><select class="form-control" copy="#monSectionGrouping">
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Copy Logging Settings']%></span></div>
|
||||
<div><select class="form-control" copy="#monSectionLogging">
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -1449,15 +1505,14 @@
|
|||
<option value="fatal"><%-lang['Fatal']%></option>
|
||||
<option value="error" selected><%-lang['on Error']%></option>
|
||||
<option value="warning"><%-lang['All Warnings']%></option>
|
||||
<option value="debug"><%-lang['Debug']%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Save Log in SQL']%></span></div>
|
||||
<div><select class="form-control" detail="sqllog">
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -1491,16 +1546,19 @@
|
|||
<li class="mdl-menu__item add_map"><i class="fa fa-rss"></i> <%-lang['Add Input Feed']%></li>
|
||||
<li class="mdl-menu__item add_channel"><i class="fa fa-puzzle-piece"></i> <%-lang['Add Channel']%></li>
|
||||
</ul>
|
||||
<span class="h_det_input h_det_1"><button type="button" style="display:none" class="btn btn-warning shinobi-detector-opencv shinobi-detector-openalpr shinobi-detector-motion shinobi-detector_plug" monitor="region"><i class="fa fa-grav"></i> <%-lang['Region Editor']%></button></span>
|
||||
<div class="h_det_input h_det_1" style="display:inline-block">
|
||||
<button type="button" class="btn btn-primary" monitor="region"><i class="fa fa-grav"></i> <%-lang['Region Editor']%></button>
|
||||
<button type="button" class="btn btn-primary" monitor="detector_filters"><i class="fa fa-grav"></i> <%-lang['Detector Filters']%></button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:inline-block;margin-right:5px">
|
||||
<div><select class="form-control btn-default" dropdown_toggle="monedit_user_type" selector="h_us">
|
||||
<option value="simple" selected><%-lang['Simple']%></option>
|
||||
<option value="advanced"><%-lang['Advanced']%></option>
|
||||
<option value="simple" selected><%-lang['Simple']%></option>
|
||||
<option value="advanced"><%-lang['Advanced']%></option>
|
||||
</select></div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success"><i class="fa fa-check"></i> <%-lang.Save%></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -24,6 +24,33 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group-group grey">
|
||||
<h4><%-lang['2-Factor Authentication']%></h4>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Enabled']%></span></div>
|
||||
<div><select class="form-control" detail="factorAuth">
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang.Email%></span></div>
|
||||
<div><select class="form-control" detail="factor_mail">
|
||||
<option value="1" selected><%-lang.Yes%></option>
|
||||
<option value="0"><%-lang.No%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group u_discord_bot_input u_discord_bot_1">
|
||||
<label><div><span><%-lang.Discord%></span></div>
|
||||
<div><select class="form-control" detail="factor_discord">
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group-group grey">
|
||||
<h4><%-lang.Profile%></h4>
|
||||
<div class="form-group">
|
||||
|
@ -41,14 +68,6 @@
|
|||
<div><input class="form-control" type="password" name="password_again"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['2-Factor Authentication']%> (<%-lang.Email%>)</span></div>
|
||||
<div><select class="form-control" detail="factorAuth">
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
<% if(!details.sub){ %>
|
||||
<% if(details.edit_size!=='0'){ %>
|
||||
<div class="form-group">
|
||||
|
@ -116,11 +135,19 @@
|
|||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Alert Sound Delay']%></span></div>
|
||||
<div><input class="form-control" detail="audio_delay" placeholder="1"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Alert Sound Delay']%></span></div>
|
||||
<div><input class="form-control" detail="audio_delay" placeholder="1"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span><%-lang['Popout Monitor on Event']%></span></div>
|
||||
<div><select class="form-control" detail="event_mon_pop">
|
||||
<option value="0" selected><%-lang.No%></option>
|
||||
<option value="1"><%-lang.Yes%></option>
|
||||
</select></div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<% if(!details.sub){ %>
|
||||
<div class="form-group-group blue">
|
||||
|
|
|
@ -20,6 +20,7 @@ if(data.url.charAt(data.url.length - 1) !== '/'){
|
|||
<script src="<%=data.url%>libs/js/poseidon.js"></script>
|
||||
<script src="<%=data.url%>libs/js/hls.min.js"></script>
|
||||
<script src="<%=data.url%>libs/js/flv.min.js"></script>
|
||||
<script src="<%=data.url%>libs/js/libde265.min.js"></script>
|
||||
<% if(data.addon){
|
||||
var ar={}
|
||||
decodeURI(data.addon).split('|').forEach(function(v){
|
||||
|
@ -133,7 +134,7 @@ $(document).ready(function(){
|
|||
})
|
||||
</script>
|
||||
<div class="shinobi_stream" id="<%= data.name %>">
|
||||
|
||||
|
||||
<% switch(mon.details.stream_type){
|
||||
case'jpeg':
|
||||
%><img class="stream-element"><%
|
||||
|
@ -148,8 +149,8 @@ $(document).ready(function(){
|
|||
%><canvas class="stream-element"></canvas><%
|
||||
break;
|
||||
} %>
|
||||
|
||||
|
||||
|
||||
|
||||
<% if(data.addon&&data.addon.indexOf('gui')>-1){ %>
|
||||
<div class="shinobi_hud">
|
||||
<div class="shinobi_viewers" title="Current number of viewers"></div>
|
||||
|
@ -170,11 +171,30 @@ $(document).ready(function(){
|
|||
$.shinobi.mon={}
|
||||
};
|
||||
$.shinobi.init=function(d){
|
||||
if($.shinobi.mon[d.id].Base64 && $.shinobi.mon[d.id].Base64.connected){
|
||||
$.shinobi.mon[d.id].Base64.disconnect()
|
||||
}
|
||||
if($.shinobi.mon[d.id].Poseidon){
|
||||
$.shinobi.mon[d.id].Poseidon.destroy()
|
||||
}
|
||||
if ($.shinobi.mon[d.id].h265Player) {
|
||||
$.shinobi.mon[d.id].h265Player.stop()
|
||||
}
|
||||
if($.shinobi.mon[d.id].h265Socket && $.shinobi.mon[d.id].h265Socket.connected){
|
||||
$.shinobi.mon[d.id].h265Socket.disconnect()
|
||||
}
|
||||
if($.shinobi.mon[d.id].h265HttpStream && $.shinobi.mon[d.id].abort){
|
||||
$.shinobi.mon[d.id].h265HttpStream.abort()
|
||||
}
|
||||
if($.shinobi.mon[d.id].flv){
|
||||
$.shinobi.mon[d.id].flv.destroy()
|
||||
}
|
||||
if($.shinobi.mon[d.id].hls){
|
||||
$.shinobi.mon[d.id].hls.destroy()
|
||||
}
|
||||
clearInterval($.shinobi.mon[d.id].jpegInterval);
|
||||
switch($.shinobi.mon[d.id].details.stream_type){
|
||||
case'b64':
|
||||
if($.shinobi.mon[d.id].Base64 && $.shinobi.mon[d.id].Base64.connected){
|
||||
$.shinobi.mon[d.id].Base64.disconnect()
|
||||
}
|
||||
$.shinobi.mon[d.id].Base64 = io('<%=data.url%>',{transports: ['websocket'], forceNew: false})
|
||||
var ws = $.shinobi.mon[d.id].Base64
|
||||
ws.on('diconnect',function(){
|
||||
|
@ -209,7 +229,7 @@ $(document).ready(function(){
|
|||
// }
|
||||
// ctx.getContext("2d").drawImage(image,d.x,d.y,d.width,d.height)
|
||||
ctx.getContext("2d").drawImage(image,d.x,d.y,ctx.width,ctx.height)
|
||||
URL.revokeObjectURL($.ccio.mon[d.ke+d.id+user.auth_token].imageUrl)
|
||||
URL.revokeObjectURL($.shinobi.mon[d.id].imageUrl)
|
||||
}
|
||||
ws.on('data',function(imageData){
|
||||
try{
|
||||
|
@ -232,9 +252,6 @@ $(document).ready(function(){
|
|||
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],
|
||||
|
@ -261,11 +278,38 @@ $(document).ready(function(){
|
|||
stream.attr('src','<%=data.url%><%=data.auth%>/mp4/'+d.ke+'/'+d.id+'/s.mp4')
|
||||
}
|
||||
break;
|
||||
case'h265':
|
||||
var player = $.shinobi.mon[d.id].h265Player
|
||||
var video = $('#SHINOBI_'+d.ke+'_'+d.id+' .stream-element')[0]
|
||||
player = new libde265.RawPlayer(video)
|
||||
player.set_status_callback(function(msg, fps) {
|
||||
})
|
||||
player.launch()
|
||||
if($.shinobi.mon[d.id].details.stream_flv_type==='ws'){
|
||||
$.shinobi.mon[d.id].h265Socket = io(url,{transports: ['websocket'], forceNew: false})
|
||||
var ws = $.shinobi.mon[d.id].h265Socket
|
||||
ws.on('diconnect',function(){
|
||||
console.log('h265Socket Stream Disconnected')
|
||||
})
|
||||
ws.on('connect',function(){
|
||||
ws.emit('h265',{
|
||||
auth:'<%=data.auth%>',
|
||||
ke:d.ke,
|
||||
uid:'<%=data.uid%>',
|
||||
id:d.id,
|
||||
url: '<%=data.url%>'
|
||||
// channel: channel
|
||||
})
|
||||
ws.on('data',function(imageData){
|
||||
player._handle_onChunk(imageData)
|
||||
})
|
||||
})
|
||||
}else{
|
||||
$.shinobi.mon[d.id].h265HttpStream = player.createHttpStream('<%=data.url%><%=data.auth%>/h265/'+d.ke+'/'+d.id+'/s.hevc')
|
||||
}
|
||||
break;
|
||||
case'flv':
|
||||
if (flvjs.isSupported()) {
|
||||
if($.shinobi.mon[d.id].flv){
|
||||
$.shinobi.mon[d.id].flv.destroy()
|
||||
}
|
||||
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!==''){
|
||||
|
@ -307,9 +351,9 @@ $(document).ready(function(){
|
|||
k=d.mon.details;
|
||||
k.jpegInterval=parseFloat(k.jpegInterval);
|
||||
if(!k.jpegInterval||k.jpegInterval===''||isNaN(k.jpegInterval)){k.jpegInterval=1}
|
||||
if(!$.shinobi.mon[d.mon.mid].jpegInterval){
|
||||
clearInterval($.shinobi.mon[d.mon.mid].jpegInterval);
|
||||
$.shinobi.mon[d.mon.mid].jpegInterval=setInterval(function(){
|
||||
if(!$.shinobi.mon[d.id].jpegInterval){
|
||||
clearInterval($.shinobi.mon[d.id].jpegInterval);
|
||||
$.shinobi.mon[d.id].jpegInterval=setInterval(function(){
|
||||
$('#SHINOBI_'+d.ke+'_'+d.id+' .stream-element').attr('src','<%=data.url%><%=data.auth%>/jpeg/'+d.mon.ke+'/'+d.mon.mid+'/s.jpg?time='+(new Date()).getTime())
|
||||
},1000/k.jpegInterval);
|
||||
}
|
||||
|
@ -317,9 +361,6 @@ $(document).ready(function(){
|
|||
case'hls':
|
||||
var video = $('#SHINOBI_'+d.ke+'_'+d.id+' .stream-element')[0];
|
||||
d.url='<%=data.url%><%=data.auth%>/hls/'+d.ke+'/'+d.id+'/s.m3u8';
|
||||
if($.shinobi.mon[d.id].hls){
|
||||
$.shinobi.mon[d.id].hls.destroy()
|
||||
}
|
||||
if (navigator.userAgent.match(/(iPod|iPhone|iPad)/)||(navigator.userAgent.match(/(Safari)/)&&!navigator.userAgent.match('Chrome'))) {
|
||||
video.src=d.url;
|
||||
video.play();
|
||||
|
@ -386,7 +427,7 @@ $(document).ready(function(){
|
|||
$(window).resize();
|
||||
// d.mon.details=JSON.stringify(d.mon.details);
|
||||
d.mon.id = d.mon.mid
|
||||
$.shinobi.mon[d.mon.mid]=d.mon;
|
||||
$.shinobi.mon[d.id] = d.mon;
|
||||
$.shinobi.init(d.mon);
|
||||
break;
|
||||
}
|
||||
|
@ -413,4 +454,4 @@ $(document).ready(function(){
|
|||
$.shinobi.init(monitor);
|
||||
})
|
||||
$('.shinobi_ws_http_toggle').show()
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
@ -163,6 +163,7 @@
|
|||
<% include blocks/monitoredit.ejs %>
|
||||
<% include blocks/probe.ejs %>
|
||||
<% include blocks/region.ejs %>
|
||||
<% include blocks/detectorfilters.ejs %>
|
||||
<% include blocks/confirm.ejs %>
|
||||
<% if(config.DropboxAppKey){ %>
|
||||
<!--Dropbox Library, Change data-app-key to your app key. -->
|
||||
|
@ -179,6 +180,7 @@
|
|||
<script src="<%-originalURL%>libs/js/socket.io.js"></script>
|
||||
<script src="<%-originalURL%>libs/js/fullcalendar.min.js"></script>
|
||||
<script src="<%-originalURL%>libs/js/hls.min.js"></script>
|
||||
<script src="<%-originalURL%>libs/js/libde265.min.js"></script>
|
||||
<script type="text/javascript" src="<%-originalURL%>libs/js/flv.shinobi.js">;</script>
|
||||
<script src="<%-originalURL%>libs/js/menu.js"></script>
|
||||
<script src="<%-originalURL%>libs/js/clock.js"></script>
|
||||
|
@ -193,4 +195,4 @@
|
|||
<script src="<%-originalURL%>libs/js/gridstack.min.js"></script>
|
||||
<script src="<%-originalURL%>libs/js/gridstack.jQueryUI.min.js"></script>
|
||||
<script><% include ../libs/js/main.dash2.js %></script>
|
||||
<% include blocks/help.ejs %>
|
||||
<% include blocks/help.ejs %>
|
||||
|
|
Loading…
Reference in New Issue