Merge branch 'dev' into 'master'

Blue Rhydon

See merge request Shinobi-Systems/Shinobi!21
merge-requests/23/head
Moe 2018-08-30 20:31:40 +00:00
commit 5bb19c7d5e
45 changed files with 3292 additions and 1148 deletions

76
COPYING
View File

@ -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.

144
COPYING.md Normal file
View File

@ -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/

View File

@ -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

29
INSTALL/cuda.sh Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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."
;;

190
INSTALL/opensuse.sh Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:

1441
camera.js

File diff suppressed because it is too large Load Diff

View File

@ -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":{}
}

View File

@ -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)",

View File

@ -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)",

View File

@ -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",

View File

@ -1,3 +0,0 @@
conf.json
events
frames

View File

@ -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;
});

View File

@ -1,5 +0,0 @@
{
"name":"Macbook",
"ws":"66.51.132.100",
"key":"3123asdasdf1dtj1hjk23sdfaasd12asdasddfdbtnkkfgvesra3asdsd3123afdsfqw345",
}

View File

@ -1 +0,0 @@
{"host":"127.0.0.1","user":"root","password":"","database":"ccio"}

3
plugins/python-dlib/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
conf.json
faces
data

View File

@ -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"

View File

@ -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"
}
],
```

View File

@ -0,0 +1 @@
python3 -u $@

View File

@ -0,0 +1,10 @@
{
"plug":"PythonDlib",
"host":"localhost",
"port":8080,
"pythonPort":7990,
"hostPort":8082,
"key":"SomeOpenALPRkeySoPeopleDontMessWithYourShinobi",
"mode":"client",
"type":"detector"
}

View File

@ -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"
}

View File

@ -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)

View File

@ -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()

5
plugins/python-yolo/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
conf.json
cascades
cfg
weights
data

View File

@ -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"

View File

@ -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"
}
],
```

View File

@ -0,0 +1 @@
python3 -u $@

View File

@ -0,0 +1,10 @@
{
"plug":"PythonYolo",
"host":"localhost",
"port":8080,
"pythonPort":7990,
"hostPort":8082,
"key":"SomeOpenALPRkeySoPeopleDontMessWithYourShinobi",
"mode":"client",
"type":"detector"
}

View File

@ -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

View File

@ -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"
}

View File

@ -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)

View File

@ -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()

View File

@ -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}

1
web/libs/js/libde265.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -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)
}
}
}

View File

@ -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">&times;</span>
</button>
<h4 class="modal-title" id="detector_filterLabel"><i class="fa fa-grav"></i> &nbsp; <%-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">&nbsp;<i class="fa fa-plus"></i>&nbsp;</a>
<a class="btn btn-danger btn-xs remove">&nbsp;<i class="fa fa-trash-o"></i>&nbsp;</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>

View File

@ -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> &nbsp; <%-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>

View File

@ -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">

View File

@ -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>

View File

@ -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 %>